Combining RoboPro with C

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.

Memory Map

Address RangeSizeFunctionFT name/useComment
0-3FF1kSpecial Function
Registers (SFR)
See the Renesas
documentation
400-5FF512internal RAMTransfer AreaSee FT
documentation
600-11FF3kinternal RAMInt. RAM 2Bit addressable
1200-2BFF6k5internal RAMFW data
and stacks
2C00-3FFFN/A
4000-FFFF48kExt.RAM I (cs3 & cs2)Int. RAM 1Used by RoboPro
10000-27FFF96kExt.RAM II (cs2)Shadow of
50000-67FFF
28000-2FFFFN/A (cs1)
30000-3FFFF64kMEMSELQ0Module on JP6
40000-43FFF16kExt.RAMMessage buffersMostly unused
44000-4FFFFShadow of ext.RAM I
50000-7FFFF192kExt.RAMProg 350000-500FF
is a header
80000-9FFFF128kExt.FlashProg 180000-800FF
is a header
A0000-BFFFF128kExt.FlashProg 2A0000-A00FF
is a header
C0000-C0FFF4kIOSEL0QDig. In, Motors OutOnly 1 16bit port
C1000-C1FFF4kIOSEL2QModule on JP6My LCD
C2000-C2FFF4kIOSEL1QRF ModuleOnly 1 2bit port
C3000-C3FFF4kIOSEL3QModule on JP6
C4000-CFFFFShadow of
C0000-C3FFF
D0000-DFFFFN/A
E0000-FFFFF128kInt.FlashFirmware

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_FirmwareTAF_00D.h"
#include "TA_FirmwareTAF_00P.h"
#include "TA_FirmwareMsg_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[] =
"x7bxd9x00x7bxf9x02x7bxe9x04x75x40x75x42x73x92x0cx7bx35x73x90x0cx73x0f";
const unsigned char terminate_signature[] = "x75xd3x75xd5xebx63xebx73x75xc0x00x00xf3x73x53x73x91x0cx73xf0";

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 serviceroutine 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
}

int Run(void)
{ if (progstart)
return progstart();
return -1;
}

//this is an exact copy of the robo_yield
#pragma ASM
.section program
.glb _Yield
_Yield:
STC SP,00H[A1]
STC FB,02H[A1]
STC SB,04H[A1]
MOV.W:G 0CH[A1],A1
LDC 00H[A1],SP
LDC 02H[A1],FB
LDC 04H[A1],SB
RTS

#pragma ENDASM

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 errorn");
}

_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 foundn");
break;
case 1: grPuts(" Error: No program selectedn");
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.


Erstellt und hochgeladen von Ad.
Hochgeladen am 18.2.2009.

Hinweis: Wir vertrauen auf die Sachkunde und Sorgfalt unserer Nutzer. Trotzdem könnten sich Fehler eingeschlichen haben. Eine Haftung für die Richtigkeit der Inhalte können wir nicht übernehmen.