ftCommunity | Wiki | Combining RoboPro with C

Wiki

Thema: Combining RoboPro with C

Version 1

von: Ad

am: 17.02.2009, 21:50:37 Uhr


Combining C and RoboPro Programs


When I was developing the Graphical LCD interface for the FT Robo Interface, I did this
completely in C (Renesas). The communication with the firmware was conducted entirely
over the Transferarea. It would be nice to have a somewhat more sophisticated
communication such that data from RoboPro programs could be displayed, comparable to
the control panels in the online mode. This paper is intended as a first crack at the
problem and an invitation to others to do further investigations and share their knowledge
in this Wiki. The attempts in this Wiki are certainly not bulletproof solutions and may
become obsolete at every new release of RoboPro or the firmware.

Problems


Although we can load a RoboPro program in one memory (say Flash1) and a C program
in another (say RAM), we can only start one at a time. Even if we could start both
programs we would still have to deal with the resources, some of which we would like to
share (variables, CPU time, timers) and some not (private data, stacks). We know which
resources are used by our C program but we don't know which resources are used by
RoboPro, this has to be found out as the information is unfortunately not public.

Memory


The default startup files for C programs put the data segments at address 4000H, this is
not a good place because it is also the place where RoboPro puts its variables so let's
have a look at the memory map to find better places.

1. Memory Map


Address Range Size Function FT name/use Comment
0-3FF 1k Special Function See the Renesas
Registers (SFR) documentation
400-5FF 512 internal RAM Transfer Area See FT
documentation
600-11FF 3k internal RAM Int. RAM 2 Bit addressable
1200-2BFF 6k5 internal RAM FW data and
stacks
2C00-3FFF N/A
4000-FFFF 48k Ext.RAM I (cs3 & cs2) Int. RAM 1 Used by RoboPro
10000-27FFF 96k Ext.RAM II (cs2) Shadow of 50000-
67FFF
28000-2FFFF N/A (cs1)
30000-3FFFF 64k MEMSELQ0 Module on JP6
40000-43FFF 16k Ext.RAM Message buffers Mostly unused
44000-4FFFF Shadow of ext.RAM I
50000-7FFFF 192k Ext.RAM Prog 3 50000-500FF is a
header
80000-9FFFF 128k Ext.Flash Prog 1 80000-800FF is a
header
A0000-BFFFF 128k Ext.Flash Prog 2 A0000-A00FF is a
header
C0000-C0FFF 4k IOSEL0Q Dig. In, Motors Out Only 1 16bit port
C1000-C1FFF 4k IOSEL2Q Module on JP6 My LCD
C2000-C2FFF 4k IOSEL1Q RF Module Only 1 2bit port
C3000-C3FFF 4k IOSEL3Q Module on JP6
C4000-CFFFF Shadow of C0000-
C3FFF
D0000-DFFFF N/A
E0000-FFFFF 128k Int.Flash Firmware


This means that in the NEAR memory area (below 64k), space is available in the bit
addressable area and possibly near the end of the internal RAM, depending on how
much memory is needed by RoboPro. In the FAR area most of the region 40000H-
43FFFH is unused and also the entire area 50000H-7FFFFH is under our control (unless
we put the RoboPro program in RAM).

Control


The next problem is how to get control, it is not possible to call C functions from RoboPro
for instance like you can call functions in a DLL from LabView. The other way around, we
could call functions in RoboPro if we would know where they were and how they worked.
We could try to start the RoboPro programming with a call to its load address (e.g.
80100H) because this is also how a C program is launched. The remaining problem is
how to get control back once the RoboPro program is running.

Data


A huge problem is the exchange of data between a C program and RoboPro. Even in
RoboPro itself it is already difficult to know the difference between local, global and object
variables, so there is probably no easy way to know the location of these variables such
that they could be shared. The Transferarea allows of course communication but only for
inputs and outputs, not for general variables. Maybe something like a virtual I/O extension
could be implemented to increase the possibilities in this area. Another possibility might
be the exchange of messages between RoboPro and the C program.

Possible Solutions


Now I will look into some solutions to get something running quasi-parallel to a RoboPro
program and to do a useful exchange of data between the programs.
Believe it or not, directly calling the entry point of a RoboPro program works! A RoboPro
program is almost completely self contained and does not require a large runtime library. It
only makes calls to the special page functions (the JSRS #22 type functions) that are also
made available to the C programmer. Also the data exchange takes place via the
Transferarea. So the firmware is responsible for input, output, interrupts etc. and the
RoboPro developer is in the same position as the C programmer, use what is made
available to you. It's just a pity the RoboPro developer hasn't made his ideas available to
us.
So RoboPro can simply be started with:

void (*robopro)() = (void(*)())0x80100; //for a program in Flash1
void main()
{
robopro(); //this function returns when all threads in RoboPro are ended
}

Now, can we get control back before RoboPro ends?

Timer


The firmware provides us with a function to set a 1ms timer interrupt. This results in a call
to a user supplied function (but running in interrupt context) every 1ms. Luckily for us,
RoboPro does not use this function (as far as I know) because the functions are not
chained, only the last is maintained. So at least we have control back but it's not a good
idea to do extensive processing here because almost nothing else can run. In normal
microcontroller programming we would just set a flag and do the actual processing in the
main loop of the program, here that won't work because our main program is stalled
because of the call to RoboPro.

void timerint()
{ //do whatever but don't take too long
}
void main()
{
SetFt1msTimerAddress(timerint);
robopro();
}

With this setup it is possible to show input and output values on the LCD in real time and
even as a function of time (like a chart).

Messages


The main limitation of the timer method is that we can only show data for which we know
the address, like data in the Transferarea. By carefully analysing the disassembly listing
of the RoboPro program we could figure out the addresses of some of the variables but it
is a lot of work and has to be redone after every change to the RoboPro program. A
solution could be the use of messages, we can send messages from RoboPro to itself
and if we could then intercept these messages in C, we would be back in business. The
firmware provides us with a function to set the message handler and there is a good
example on how to use this from C. But unfortunately RoboPro and C were not designed
to work together and in this case RoboPro replaces our message handler with its own.
Every RoboPro program contains the same message handler, very similar to the one in
the example. It just copies the incoming message to a large buffer (128 entries of 6 bytes).
This buffer is located somewhere between 4000H and the stacks but not always in the
same place. The address of the message handler however is always in the same place
and we could replace the RoboPro handler with our own after RoboPro has installed its
own handler, using the timer interrupt. Now we can inspect the message, treat it, ignore it,
store it or forward it to RoboPro at our discretion.

#include "TA_Firmware\TAF_00D.h"
#include "TA_Firmware\TAF_00P.h"
#include "TA_Firmware\Msg_00D.h"
#include "grlcd.h"

typedef void (*msghandler)(SMESSAGE near*);

static int time = 0;
static char flag = 0;
void PrMsg(SMESSAGE near *pMsg);
msghandler robomsghandler = 0;
msghandler *msghandleraddr = (msghandler*)0x1830;

char roboready = 0;

void far timerint()
{ if (robomsghandler == 0 && *msghandleraddr != 0)
{ roboready = 1;
robomsghandler = *msghandleraddr;
*msghandleraddr = PrMsg;
time++;
if (time >= 1000) time = 0;
}

void PrMsg(SMESSAGE near *pMsg)
{
grPutHex(pMsg->W.uiMsgId);
grPuts("\t");
if (robomsghandler != 0)
robomsghandler(pMsg);
}

char main(void)
{
grInit();
grBitblt(fischerlogo);
FtDelay(500);
grClrScr();
SetFt1msTimerTickAddress((void far *())timerint);

//Wait for some event
return 0;
}

Sending messages back is relatively easy, were it not that there is an error in the macro in
TAF_00P.h has an error.

// Firmwarefunction "SendFtMessage()"
static UCHAR SendFtMessage(UCHAR, UCHAR, ULONG, UINT, UINT);
#pragma __ASMMACRO SendFtMessage(R1L, R1H, A1A0, R0, R2)
#pragma ASM
_SendFtMessage .macro
PUSH.W R2
PUSH.W R0
PUSH.W A1
PUSH.W A0
PUSH.B R1H
JSRS #27
; add.b #7h,sp ;original FT code is probably wrong
ADD.B #9H,SP ;I think this is better but the
;question is: Why does the
example
;work?
.endm
#pragma ENDASM // Message-System

I use 0 as HwId (SELF) and 9 as SubId (All interfaces)

Thread


Having to run our own routines only as part of a timer interrupt or as part of a message
handler is not the best solution because we block all further execution of RoboPro. It
would be much nicer if we could use a thread like RoboPro does. One solution would be
to have a preemptive multitasking system and run RoboPro in one thread and our own
program in another. I tried another solution, namely to run my own program in a RoboPro
thread.
RoboPro implements a co-operative multithreading system, the structure of a RoboPro
program is roughly as follows:

Initialise ComPort and Distance sensor inputs
Create stacks (5 by default)
Move the main thread onto the first stack
Initialise variables and lists
Set up the event handler chains
Spawn the tasks for the Robo user program
Install the message handler
From this point on the main thread handles events until all
threads terminate

Each RoboPro program comes with some routines to implement the multithreading, they
are:
- Create_Thread
- Spawn_Thread
- Terminate_Thread
- Yield

RoboPro maintains a circular list of active threads and a linear list of free stacks.
Spawn_Thread moves a stack from the free list into the active list, initialises the
stackpointer and then jumps to the new task. The new task initialises the base pointers
and then calls Yield. The Yield function implements the actual task switch. It saves the
important register in the descriptor of the current stack (register A1 points to the current
stack descriptor). It then loads the registers (including the stackpointer) from the next
stack in the chain and executes return. Because the stack has changed it returns to a
return point in a different task. As long as every tasks calls Yield every now and then,
all tasks get CPU-time in a round-robin fashion.
When a task ends it must call Terminate_Thread that moves the stack back to the free
list end returns to the next task. If all user tasks have ended, the RoboPro program
terminates and returns to the calling environment which is the firmware or in our case
our own C program.
For our C program we cannot simply call Create_Thread because of the memory
allocation and the chaining of the stacks. This is not a problem because we can leave
the creation of the stacks to RoboPro. A bigger problem is the call to Spawn_Thread
because it is not designed to be called from an interrupt routine (which is our case
because we want to spawn from the timer interrupt). Note that we cannot spawn a task
before rhe RoboPro program is started because the stacks are not created yet.
Therefore we have to write our own spawn function. The Terminate_Thread and Yield
can be used as they are. The only problem is that we have to find out where the
functions are and where the free and the active lists are.
Because these functions are always the same (apart from the addresses) we can
easily scan the memory to find them and deduce all relevant addresses from this.

enum prognames { Flash1, Flash2, Ram, None};

struct robostack
{ unsigned sp, fb, sb;
char near *begin, *end, *lim;
struct robostack near *next;
unsigned unknown;
char stack[4096];
};

typedef int far (*entrypoint)(void);

struct robostack near *taskhandle = 0;

const unsigned char spawn_signature[] =
"\x7b\xd9\x00\x7b\xf9\x02\x7b\xe9\x04\x75\x40\x75\x42\x73\x92\x0c\x7b\x35\x73\x90\x0c\x7
3\x0f";
const unsigned char terminate_signature[] =
"\x75\xd3\x75\xd5\xeb\x63\xeb\x73\x75\xc0\x00\x00\xf3\x73\x53\x73\x91\x0c\x73\xf0";
const static entrypoint progs[3] = {(entrypoint)0x80100, (entrypoint)0xA0100,
(entrypoint)0x50100};

static struct robostack near *active = 0;
static struct robostack near *free = 0;
static void (*robo_spawn)(void (*)()) = 0;
static void (*robo_terminate)() = 0;
static entrypoint progstart = 0;

static struct robostack near **activeptr = 0;
static struct robostack near **freeptr = 0;
static unsigned *nrofstacksptr = 0;

char find_robo_functions(void)
{ char far *p;
struct robostack near *free = 0;
if (!progstart)
return 1;
for(p=(char far*)progstart;p<(char far*)progstart+0x20000;p++)
if (memcmp(p, spawn_signature, sizeof(spawn_signature)-1)==0)
{ robo_spawn = p - 16; //the entrypoint is 16 bytes before the signature string
freeptr = (struct robostack near**)(*(unsigned far*)(robo_spawn+2));
activeptr = (struct robostack near**)((*(unsigned far*)(robo_spawn+2))-2);
nrofstacksptr = (unsigned near*)(*(unsigned far*)(robo_spawn+0x35));
break;
}
if (!robo_spawn)
return 2;
for(p=(char far*)progstart;p<(char far*)progstart+0x20000;p++)
if (memcmp(p, terminate_signature, sizeof(terminate_signature)-1)==0)
{ robo_terminate = p - 0x42; //the entrypoint is 66 bytes before the signature
string
break;
}
if (!robo_terminate)
return 3;
return 0;
}

There are of course other ways of finding the relevant addresses, this is just an
example. The ‘spawn’ function is the most critical, better not mess with it unless you
know what you’re doing.

#pragma ASM
.section program
_start_and_return: ;contrary to Yield, this function is called only once
STC SP,00H[A1] ;the first 3 instructions are equal to yield
STC FB,02H[A1] ;we save the current context in the stack
descriptor
STC SB,04H[A1]
LDC _savesp,SP ;but instead of the next task we return to spawn
in the timer interrupt routine
LDC _savefb,FB ;this allows us to leave the interrupt service
routine as soon as possible
LDC _savesb,SB ;once we REIT from interrupt the Yield function
will restore the stackpointers to the USP
MOV.B #0,R0L ;return value for spawn
EXITD ;use EXITD if spawn uses ENTER/EXITD
;RTS ;otherwise use RTS
#pragma ENDASM

//this spawn function is call from the timer interrupt, hence we run on the I stack
char spawn(void (*proc)())//must be called after 'Run' because the stacks must be
initialised
{ //long dummy = 0;//just to force ENTER instruction
static void (*myproc)() = 0; myproc = proc;
if (activeptr) //we have the address of the pointer to the list of active stacks
active = *activeptr; //active points to a stack in the circular list of active
stacks
else
return 1;
if (freeptr) //we have the address of the pointer to the list of free stacks
{ free = *freeptr;
if (free) //there are still stacks on the free list
{ taskhandle = active->next;
active->next = free;
*freeptr = free->next; //free is not the same variable as *freeptr
active->next->next = taskhandle;
taskhandle = active->next; //points to the new stack
*nrofstacksptr += 1;
//now the first stack of the free list is moved into the circular active list
_asm("STC SP,$@",savesp); //preserve the context, this will be restored in
start_and_return
_asm("STC FB,$@",savefb); //at this point the stack (ISP) contains a normal
stackframe
_asm("STC SB,$@",savesb); //and requires EXITD to return to timerint
_asm("MOV.W $@,A1",taskhandle); //keep address of new stack in A1,
parameter to start_and_return
_asm("LDC #0,SB"); //here we define the context for our own C
task (proc)
_asm("LDC #0,FB");
_asm("LDC 06H[A1],SP");
start_and_return(); //the return from start_and_return to here is saved on the
new stack
//start_and_return returns to the saved context, this
means it returns immediately from spawn back to timerint.
//we come back here after another task
yields to this stack
myproc();//we use the static variable 'myproc' because the context (FB) is
destroyed, we are not on the ISP stack anymore!
// debug();
robo_terminate(); //the robo_terminate never returns here, it returns to
the Run function when all threads are terminated
return 4; //terminated
}
else
return 3;//no more stacks
}
else
return 2; //free pointer not found
}

Using the functions now available to us we can execute our own robo thread.

void mytask()
{//make sure not to mess up A1 before calling Yield
while (!(sTrans.E_Main & 0x02))
{ _asm("PUSH.W A1");
if (flag)
{ grPutc('-');
flag = 0;
if (SendFtMessage(MSG_HWID_SELF, 9, msg, 0 /*ms*/, MSG_SEND_NORMAL)!=
ERROR_SUCCESS)
grPuts(" send error\n");
}
_asm("POP.W A1"); //A1 could also be restored from taskhandle: asm("MOV.W
$@,A1",taskhandle)
//the registers (except sp, fb, sb and a1) are not preserved by other tasks, so you
may want to save them
//asm("PUSHM R0,R1,R2,R3,A0");
Yield();
//asm("POPM R0,R1,R2,R3,A0");
}
}

void far timerint()
{ char err;
if (robomsghandler == 0 && *msghandleraddr != 0)
{ roboready = 1;
robomsghandler = *msghandleraddr;
*msghandleraddr = PrMsg;
err = spawn(mytask); //this is a good place for spawn because the robo message
handler is initialised after the stacks
switch(err)
{case 0: grPuts("Spawned succesfully"); break;
case 1: grPuts("No active stack"); break;
case 2: grPuts("Free stack not found"); break;
case 3: grPuts("No more free stacks"); break;
case 4: grPuts("Return from Terminate"); break;
default: grPuts("Unknown spawn error"); break;
}
}
time++;
if (time >= 1000) time = 0;
if (roboready && time == 0)
{ grPutc('*');
flag = 1;
}
}


void PrMsg(SMESSAGE near *pMsg)
{ if (robomsghandler != 0)
robomsghandler(pMsg);
}

char main(void)
{ int errorcode = 0;

msg = make_message("aaa", 7);

grInit();
grBitblt(fischerlogo);
FtDelay(500);
grClrScr();

SetFt1msTimerTickAddress((void far *())timerint);
selectProg(Flash2); //Flash2
errorcode = find_robo_functions();
switch(errorcode)
{ case 0: grPuts(" Functions found\n");
break;
case 1: grPuts(" Error: No program selected\n");
break;
case 2: grPuts(" Spawn not found!\n");
break;
case 3: grPuts(" Terminate not found!\n");
break;
default: grPuts(" Unknown find error!!!\n");
break;
}
errorcode = Run(); //run the selected RoboPro program
switch(errorcode)
{ case -1: grPuts(" No program selected. ");
break;
case 0: grPuts(" Finished ");
break;
case 1: grPuts(" Error: No more stacks available! ");
break;
case 2: grPuts(" Error: Stack overflow! ");
break;
default: grPuts(" Unknown Run error!!! ");
break;
}
SetFt1msTimerTickAddress(0);
grPuts("\nend of C program");

return (0);
}


Conclusion


With some effort it is quite possible to have C and RoboPro working together. It would be
nice though if the RoboPro system would be more open. I rather spend my time on
creative things than on figuring out the inner details of somebody else’s programs and
finding work-arounds just because information is missing.


 

Aktuelle Version von Ad am 18.02.2009, 21:27:23 Uhr
Version 3 von Ad am 18.02.2009, 21:07:46 Uhr
Version 2 von Ad am 18.02.2009, 21:06:53 Uhr
Version 1 von Ad am 17.02.2009, 21:50:37 Uhr

 

Wenn sie sich einloggen, können Sie dieses Thema bearbeiten.

 

Zurück zur Übersicht