Author: P. Mather
Having understood how to program a CFunction in the first thread we can build on this by looking at how we incorporate floating point arithmetic into a CFunction. Eventually in this thread we will code a PID control loop but we have a way to go first.
To start we need to go back to the old chestnut of position independence and some of the other limitations on CFunctions.
You may have noticed in the previous thread that when I wanted to output CR/LF I inserted the characters one at a time into the array. Normal C for this would have been char crlf[3]=”\r\n”;
“\r\n” is just the C version for chr$(13)+chr$(10). However, I couldn’t do that because when the compiler/linker sees a string like “Hello World” it allocates it in flash and that will be outside of the CFunction.
Similarly, I can’t do something like:
const char mychar[]={10,20,30,40,50};
to initialise an array, because again the compiler will try and put the array in flash.
I can’t even go:
char mychar[]={10,20,30,40,50};
because this time the compiler will put the array in a separate part of RAM from the code.
With floating point numbers the situation gets even worse. The PIC32MX does not have a hardware floating point unit. This means that all floating point operations are executed as calls to library routines which are not part of the Cfunction code and therefore will fail.
What this means is that you cannot use any operators on floating point numbers including both arithmetic and logical operators
+ – / * < > != == ^
are all banned
To get round this in 4.7 Geoff has included a set of basic functions that can replicate these so:
a=FAdd(b,c); adds b and c and puts the answer into a
FMul, FSub, and FDiv operate in similar fashion
To deal with the logical operators there is a single function:
a=FCmp(b,c); this returns 1 if b>c, 0 if b=c, and -1 if b<c
Together, these allow us to do pretty much any floating point arithmetic
EXCEPT!!!!!
a=FAdd(b,2.0) doesn’t work because even converting a literal to its floating point representation does something that breaks the CFunction
Luckily, there is a way round this too:
a=FAdd(b, LoadFloat(0x40000000));
Obvious really, or perhaps not.
0x40000000 is the HEX representation of the number 2.0 expressed as a 32-bit floating point number. I won’t go into floating point representations but Wikipedia will explain all. LoadFloat is a special function that puts 0x40000000 into a floating point variable, in this case the LoadFloat function return.
So we need a mechanism to translate any floating point literals we need in our program to their hex representations – I feel a CFunction coming on
long long floatconv(float *a){
char b[11],crlf[3];
union ftype{
unsigned long a;
float b;
}f;
f.b=*a;
crlf[0]=13;
crlf[1]=10;
crlf[2]=0;
b[0]=48; //0
b[1]=120; //x
IntToStr(b+2,f.a,16);
MMPrintString(b);
MMPrintString(crlf);
return f.a;
}
This code mostly uses things we have seen before except the single input is a floating point number. The interesting bit is:
union ftype{
unsigned long a;
float b;
}f;
f.b=*a;
The union says that the “unsigned long a” and the “float b” are actually using the same bit of memory which we have called f. Then I can copy the incoming floating point number *a into f.a, but I can also treat it as an integer by using f.b
I can then use the function we have seen before IntToStr to convert the integer representation into a number-string in the b array in base 16. Note that I am telling the conversion to start at position 2 in b as I have put the characters “0x” into positions 0 and 1. “0x” is the C equivalent of “&H” in Basic.
MMPrintString is then used to output the results of the conversion on the terminal.
1 2 3 4 5 6 7 8 9 10 11 |
dim a!=pi dim i%=floatconv(a!) end CFunction floatconv 00000000 27BDFFC8 AFBF0034 AFB10030 AFB0002C 8C910000 2402000D A3A20024 2402000A A3A20025 A3A00026 24020030 A3A20018 24020078 A3A20019 3C109D00 24020010 AFA20010 8E020030 27A4001A 02203021 0040F809 00003821 8E02002C 0040F809 27A40018 8E02002C 0040F809 27A40024 02201021 00001821 8FBF0034 8FB10030 8FB0002C 03E00008 27BD0038 End CFunction |
My example program converts PI to get the answer 0x40490FDA. Note that the program does not go i%=floatconv(PI) as this would give the wrong answer. The Micromite firmware converts pi to 3 if used this way – a feature.
Of course I could have done the same thing in Basic but that just makes it too easy and we are supposed to be learning about CFunctions
1 2 3 4 |
dim a!=pi i%=peek(varaddr a!) print "0x"+hex$(peek(word i%),8) end |
This all makes it seem difficult to use floating point numbers in CFunctions and it is indeed a bit clunky.
I’m not going to go into the following code sample but want to show that even complex arithmetic is possible. The following code calculates and displays a two pole bezier curve.
long long fbezier(float *x0, float *y0, float *x1, float *y1,float *x2, float *y2, float *x3, float *y3, long long *colour){//Cfunction
float tmp,tmp1,tmp2,tmp3,tmp4,tmp5,tmp6,tmp7,tmp8,t,xt,yt;
int xti,yti,xtlast=-1, ytlast=-1;
int i;
t = 0x0;
for (i=0;i<500;i++)
{
tmp =FSub(LoadFloat(0x3f800000),t);
tmp3=FMul(t,t);
tmp4=FMul(tmp,tmp);
tmp1=FMul(tmp3,t);
tmp2=FMul(tmp4,tmp);
tmp5=FMul(LoadFloat(0x40400000), t);
tmp6=FMul(LoadFloat(0x40400000), tmp3);
tmp7=FMul(tmp5,tmp4);
tmp8=FMul(tmp6,tmp);
xt = FAdd(FAdd(FAdd(FMul(tmp2 , *x0) ,
FMul(tmp7 , *x1)) ,
FMul(tmp8 , *x2)) ,
FMul(tmp1 , *x3));
yt = FAdd(FAdd(FAdd(FMul(tmp2 ,*y0) ,
FMul(tmp7 , *y1)) ,
FMul(tmp8 , *y2)) ,
FMul(tmp1 , *y3));
xti=FloatToInt(xt);
yti=FloatToInt(yt);
if((xti!=xtlast) || (yti!=ytlast)) {
DrawPixel(xti, yti, *colour);
xtlast=xti;
ytlast=yti;
}
t=FAdd(t,LoadFloat(0x3B03126F));
}
return 0;
}
This part of the tutorial will introduce some more advanced mechanisms available to CFunctions. Some of this may seem difficult and obscure but stick with it. Much of the code can be used as boiler plate and the two key techniques I will introduce in this post open up the possibilities for CFunctions to an almost unlimited extent.
To build our PID controller, first we need to set up some infrastructure. We want the PID loop to be run on a regular basis. We could probably do this with settick but we are CFunction programmers and settick is for wimps
4.7 includes a facility to call a CFunction from within the 1 msec timer interrupt inside the Micromite firmware. To use this we just need to load the address of the CFunction into “CFuncmSec” and then C uses the mechanism of pointers to functions to execute it.
To calculate the address of our CFunction we need to pass the CFunction address of an initialisation Cfunction to itself and then this initialisation routine can calculate and set up the interrupt to use the actual runtime CFunction.
We probably also need to make parameters available to the runtime CFunction (in a PIDs case Kp, Kd, Ki, and dt) and to do this we need some memory to be allocated which is preserved between calls to the CFunction and we need the runtime CFunction to know where that memory is.
The following example does all these things:
#include "../cfunctions.h"
void msec(void){
unsigned int * p;
p=(void *)(unsigned int)StartOfCFuncRam;
p[1]++; //increment p[1]
if(p[1]==p[2]){ //check whether it is time to execute
PinSetBit(p[0], LATINV);
p[1]=0;
}
}
long long main(long long *func, long long *MyAddress, long long *digoutpin, long long *rate){
int Offset;
int * p; //Set up a pointer which will be used to access the allocated memory
//If already Open just use the memory
if (StartOfCFuncRam){
p=(void *)(unsigned int)StartOfCFuncRam;
}else{
//Get some persistent memory
p = GetMemory(256);
//Save the address for other functions
StartOfCFuncRam=(unsigned int)p;
}
//Close Request ?
if (*func==1){
CFuncmSec=0;
FreeMemory(p);
StartOfCFuncRam=0;
return 0;
}
if ((unsigned int)&msec < (unsigned int)&main){ //calculate the address of the msec routine
Offset=*MyAddress - ((unsigned int)&main - (unsigned int)&msec);
}else{
Offset=*MyAddress + ((unsigned int)&msec - (unsigned int)&main);
}
p[0]=*digoutpin;
p[1]=0;
p[2]=*rate;
ExtCfg(p[0],EXT_DIG_OUT,0);
//Set the CFuncmSec vector to point to our msec function
CFuncmSec=Offset;
msec(); //run this once to avoid the compiler "optimising" it away completely
return Offset;
}
Let us start by looking at the runtime function “msec”. This starts by getting the address of the permanently allocated memory and putting it into a pointer p
” p=(void *)(unsigned int)StartOfCFuncRam;”
“StartOfCFuncRam” is a memory location defined in CFunctions.h specially for this purpose.
In this example I define p as being a pointer to an integer
“int * p;”
This allows me to use it to access an array of integer values: p[0], p[1], etc.
in the next statement I increment the value in p[1] “p[1]++;” which I am using to count msec between executing my real code:
” if(p[1]==p[2]){ //check whether it is time to execute
PinSetBit(p[0], LATINV);
p[1]=0;
}”
This says invert the state of the output pin whose number is contained in p[0] and then zero my counter. When we write the PID controller the real logic will be inside this conditional.
So that is all easy enough apart from setting up “p”, but that is boilerplate.
Now we can look at the initialisation function “main”. Note this must always be called “main” and note that we will be converting the binary to our Basic code using the MERGE function in CFGEN. This is essential to package all the code together and allow one CFunction to call another.
“if (StartOfCFuncRam){” checks if the permanent memory is already set up and
“p=(void *)(unsigned int)StartOfCFuncRam;” is the same code we already saw in “msec” to use it if it is.
Assuming the memory isn’t already set up we call:
“p = GetMemory(256);”
This gets 256 bytes of memory and sets the pointer p to point to it. As in msec, we can now access this memory as an array “p[n]”. Memory is allocated in 256 byte chunks so there is no advantage to asking for less than this.
We then save the address of this memory in the special variable we used in msec:
“StartOfCFuncRam=(unsigned int)p;”
The next section of code looks at the first parameter to “main” *func and if it is set to 1 this triggers a cleanup:
“CFuncmSec=0;” disconnects the interrupt routine
“FreeMemory(p);” returns the permanent memory to the pool
“StartOfCFuncRam=0;” “sets the memory marker to say no memory is allocated
The next section is the most important, here we calculate the address of “msec”
We use “peek(CFUNADDR fname)” in Basic to get the address of “main”, the initialisation routine. We then calculate the address of msec from that. We don’t know if “msec” is going to be before or after “main” in memory, the compiler seems to vary depending on how it optimises so we check which it is and calculate accordingly.
Next we set up the reference information in the permanent memory that will be accessed by “msec”
“p[0]=*digoutpin;” set the pin which will be toggled
“p[1]=0;” initialise the msec counter
“p[2]=*rate;” set the number of milliseconds between executing the main code
We set up the pin as an output:
“ExtCfg(p[0],EXT_DIG_OUT,0);”
And very finally, we load the address of msec into the memory location that the Micromite firmware uses to check if it needs to run a CFunction:
“CFuncmSec=Offset;”
To avoid the compiler optimising “msec” away we then call it once.
The basic code is then:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
dim myaddr%=peek(cfunaddr test) dim i%=test(0,myaddr%,23,250) pause 4000 i%=test(1,myaddr%,23) end CFunction test 00000016 'msec 27BDFFE8 AFBF0014 AFB00010 3C029D00 8C42008C 8C500000 8E020004 24420001 AE020004 8E030008 14430008 8FBF0014 3C029D00 8C42001C 8E040000 0040F809 24050007 AE000004 8FBF0014 8FB00010 03E00008 27BD0018 'main 27BDFFD8 AFBF0024 AFB40020 AFB3001C AFB20018 AFB10014 AFB00010 00808021 00A09021 00C09821 3C029D00 8C42008C 8C420000 14400007 00E08821 3C149D00 8E82003C 0040F809 24040100 8E83008C AC620000 8E040000 24030001 1483000F 3C039D00 8E030004 1460000C 3C039D00 3C109D00 8E030084 AC600000 8E030044 0060F809 00402021 8E02008C AC400000 00002021 1000001C 00002821 24630058 3C049D00 24840000 0083282B 10A00004 00831823 8E450000 10000003 00659021 8E450000 00659021 8E640000 AC440000 AC400004 8E230000 AC430008 3C109D00 8E020010 24050008 0040F809 00003021 8E020084 AC520000 0411FFAB 00000000 02402021 00122FC3 00801021 00A01821 8FBF0024 8FB40020 8FB3001C 8FB20018 8FB10014 8FB00010 03E00008 27BD0028 End CFunction |
As explained above, this gets the address of the Cfunction and calls the initialisation routine specifying to use pin 23 and 250 msec update rate.
This will cause an LED connected to pin 23 to flash twice /second for 4 seconds
Then we call the intialisation routine again with mode=1 to turn off the routine and return everything to normal.
As I said at the beginning, this is the most difficult structure we will be using in any of the tutorials but it can be treated as boilerplate.
We have seen how we can set up a CFunction to run on a regular basis without interacting with Basic.
We have seen how to pass parameters to it and how it can remember things between calls.
And we have seen how to tidy up after ourselves.
Many of the concepts here will also be relevant in the third series of tutorials which will look at display drivers.
Leave a Reply