Author: P. Mather
The first thing to understand is that because CFunctions work within the Micromite environment all the difficult stuff normally associated with programming a microprocessor from scratch in C is already done for you. The code in a CFunction is called from Basic and returns to Basic and the interface between the two is really very simple.
So first things first; we need to install a development environment for coding in C. To do this download and install MPLabX for your environment.
Assuming you install MPLabX V3.10 you can accept the installation defaults until you get to the screen titled “Completing the MPLAB X IDE v3.10 Setup Wizard” untick all of these options and then press finish.
Then you need to install a compiler. To maintain compatibility with the Micromite firmware we are going to use XC32 v1.33. This is available on the Downloads Archive tab of the same page accessed before.
Again accept the defaults during installation except on the screen Compiler Settings. Here it is essential you tick the option to add XC32 to the PATH environment variable.
Finally, to convert the code from MPLabX to be usable in the Micromite we need to convert the binary file output by the compiler into text. Download the conversion program from here
Assuming that all works properly you will then have the (completely free) environment you need to write CFunctions.
To make things really easy, I have attached a sample MPLabX project
Download and extract the zip file to your development directory and there should be a complete MPLabX project ready to start work.
Run MPLabX. From the file menu select “Open Project”. Browse to the directory containing the extracted sample “CFunction.X” and click “Open Project”
This should open various windows, on the left hand side is the project browser, the main panel is the source code for the sample.
Click on the hammer icon with the brush and the output window will display build progress and hopefully a message saying “Build Successful”
Next to get the code usable in Basic we need to run CFGen.exe which will immediately open a window to allow selection of the binary file. Browse to the top level CFunctions project directory, then “dist”/”default”/”production”. You should then see a file “CFunctions.X.production.elf”, select this file and click open. Next select where you want the Basic version saved and enter a filename for the .BAS file. Click “Save”.
This example file is a set of individual CFunctions so we now select “Join” to create an output file with each of them individually included. The use of “Merge” will be explained in a subsequent post.
As well as outputting the Basic file the cfgen program puts the output onto the clipboard so you can paste it direct into, for example, MMEdit.
You have now created your first CFunction.
Assuming that everything explained in the first post worked OK you have a working CFunction development environment but before proceeding there are a couple of gotchas that need to be understood.
When a CFunction is loaded into the Micromite we don’t know where in memory it is going to end up so the code has to be capable of running without making assumptions about its position in memory – it must be position independent.
Unfortunately the MIPS instruction set as used by the GCC compiler (XC32 is actually the GCC open source compiler rebadged by Microchip) doesn’t produce true position independent code.
Open the example project and in the “projects” tab, right click on CFunctions. Select the last option “properties”
Note that in the Compiler toolchain window you should see XC32 (V1.33) highlighted. If it isn’t select it.
Then click on “xc32-gcc” under “XC32(Global Options)” in the additional options you will see the text:
“-fPIC -mno-abicalls”
This is essential and tells the compiler to create, as far as possible, position independent code.
In the “Option Categories” dropdown select “Optimization”. The optimization level is set to 1. This is also essential for a CFunction to work.
If you want to create a Cfunction project from scratch it is essential that both the above are set the same.
Given the above setup we can successfully write CFunctions but there are limitations as we will see later when discussing doing any floating point arithmetic in a CFunction.
Hopefully you now have a development environment up and running so we can start to develop something.
Why are CFunctions useful?
From my perspective, there are two main reasons to use them which to a great extent overlap:
First, they can increase processing speed for things that are time critical
Second, they allow control of I/O in a more flexible way than may be possible in Basic.
The display drivers are an obvious example. It is possible to code one in Basic but it would be too slow to be useful.
As an example CFunction rather than just repeat things that have been done before I’m going to develop in this thread a simple function generator and hopefully show the sources of information that provide the information needed.
The PIC32 can generate a comparator reference, basically a very simple DAC, which can be used in various ways.
See page 215 of the manual for the PIC32MX1xx chips
This all looks a bit complicated but there is a Microchip library that makes handling the various peripherals on the chip fairly easy. This is called plib and has its own reference manual which happily includes some example code for the comparator reference on page 305.
To turn this into a CFunction we need to make a few changes:
long long triangleout(long long *count){
unsigned int step;
unsigned int loop;
unsigned int ramp;
unsigned int i=*count;
while(i){
i--;
for ( loop =0; loop <= 15; loop ++){
for ( ramp = 0; ramp <= 31; ramp ++){
if ( ramp <= 15 ){
// ramp up
step = ramp;
} else {
// ramp down
step = 31 - ramp;
}
CVREFOpen( CVREF_ENABLE | CVREF_OUTPUT_ENABLE | CVREF_RANGE_LOW| CVREF_SOURCE_AVDD | step );
}
}
CVREFClose(); // Disable CVREF (not executed)
}
return 0;
}
Cut and paste this into the CFUnctions.c example file immediately after the statement #include “cfunctions.h”. You will see the previous line is “#include <plib.h>” this is the statement that tells the compiler to access the plib library to provide the routines we need to compile successfully.
You should then find you can press the hammer to re-compile, press “join” in cfgen (assuming it is still open) and the new code is ready to paste into Basic.
Looking at the C code, the first line says that my new CFunction will be called triangleout and can be translated into Basic as:
function triangleout(count as integer) as integer. Note that “long long” in C is the equivalent of “INTEGER” in Micromite Basic, i.e. a 64-bit signed number
The “{” symbol marks the beginning of the code in the function and is paired with the last “}” which is equivalent to END FUNCTION
In general “{” and “}” are used to replace all paired Basic statements such as IF/ENDIF, FOR/NEXT and DO/LOOP.
“;” is used to terminate a statement rather than the end-of-line in Basic, and note that C is case sensitive “Hello” is not the same as “hello”.
There are lots of C primers around which I won’t try and duplicate, rather I will concentrate on the syntax needed to create a working CFunction.
The really important thing to note in the function declaration is that the input count is defined as “*count”. In normal Basic terms this means the data is passed by reference rather than by value. What this means is that “count” is actually the location in memory of the information rather than the information itself.
*count says get the information that is in the location count. In C terms this is a pointer but for our purposes we can just use “*count” whenever we want to use the information.
The next four lines are the equivalent of LOCAL INTEGER ramp etc. except that rather than being 64-bit signed integers they are 32-bit unsigned integers. 32-bits is the size of the hardware registers in the PIC, hence PIC32MX, so arithmetic on 32-bit numbers will be more efficient than 64.
“while(i)” = DO WHILE i>0
“i++;” = i=i+1
“for ( loop =0; loop <= 15; loop ++){” = FOR loop = 0 TO 15 STEP 1
“for ( ramp = 0; ramp <= 31; ramp ++){” = FOR ramp = 0 to 31 STEP 1
“if ( ramp <= 15 ){” = IF ramp <= 15 THEN
all very easy and obvious, just remember the “}” symbols substitute for “NEXT”, “LOOP” , “ENDIF” etc.
the next line:
CVREFOpen( CVREF_ENABLE | CVREF_OUTPUT_ENABLE | CVREF_RANGE_LOW| CVREF_SOURCE_AVDD | step );
uses the plib library call to enable the comparator reference, enable the output and set the range to 0-0.677 of AVDD and specify which step in the potential divider to use.
Once the count in the while loop finishes then “CVREFClose();” turns off the reference voltage
“return 0” = triangleout=0
From the PIC32 manual we can see that the reference voltage will be output on pin 25 of a 28-pin MX170 and pin 14 of a 44-pin part. Page 23 of theMX470 manual says the reference will be on pin 23 of a MM+ (untested)
My test program looks like this:
1 2 3 4 5 6 7 8 9 10 11 |
dim i%=triangleout(1000000) end CFunction triangleout 00000000 8C8A0000 8C8B0004 014B1025 50400021 00001021 3C06BF81 24050020 2407001F 24090010 3C0FBF81 340E8000 254CFFFF 018A502B 256BFFFF 014B6821 01805021 01A05821 1000000B 00004021 00E21823 0044180B 34638060 ACC39800 24420001 1445FFFA 2C440010 25080001 11090004 00000000 00001021 1000FFF6 00001821 ADEE9804 018D6025 1580FFE9 254CFFFF 00001021 03E00008 00001821 End CFunction |
On my Scope I get a rough triangle waveform of 2V amplitude and frequency of 212KHz
he previous version of the code just ran for an arbitrary amount of time – 1000000*15 waveforms.
It would be much better to tell it how long to run for. To do this we need to use a timer. We don’t have access to the TIMER variable directly inside a CFunction but using a couple of tricks we can do what we want.
We need a couple of magic instructions to do this. These don’t change so can be copied and pasted whenever we need to time something. They use the PIC32MX chips internal core timer. This is a 32-bit register that is updated at half the clock speed. So if we set “CPU 40” it will be updated 20,000,000 times a second.
The core timer register is not accessible in the same way as normal I/O register which is simply mapped as a memory address. However, we can include into our C code a couple of in-line assembler instructions to set and read the core timer
asm volatile("mtc0 %0, $9": "+r"(current_ticks)); //set the core timer to the value of current_ticks (i.e. 0)
while(current_ticks<time_in_ticks){
asm volatile("mfc0 %0, $9" : "=r"(current_ticks));//get the value of the core timer and put it into current_ticks
So we now have a way of measuring time in core timer ticks. However to convert this into real time we need to know the current clock speed. This is done by using one of the macros in the cfunctions.h header file:
unsigned int tick_rate;
tick_rate = CurrentCpuSpeed /2; // Get the number of core clock timck per second
Putting this all together we can easily do something (or nothing) for a defined number of seconds:
long long pausesecs(long long *time_in_seconds){
unsigned int time_in_ticks;
unsigned int tick_rate;
tick_rate = CurrentCpuSpeed /2; //get the number of clock ticks per second
time_in_ticks=*time_in_seconds * tick_rate;
unsigned int current_ticks = 0;
asm volatile("mtc0 %0, $9": "+r"(current_ticks)); //set the current number of ticks to 0
while(current_ticks<time_in_ticks){
asm volatile("mfc0 %0, $9" : "=r"(current_ticks));//get the time in ticks since zeroed
}
return CurrentCpuSpeed;
}
Note that this code also returns the CPU speed to Basic. You can copy this code into cfunctions.c and try it. My test program is:
1 2 3 4 5 6 7 8 9 |
dim i%=pausesecs(4) print "CPU Speed is ",i%, " Hz" end CFunction pausesecs 00000000 3C029D00 8C420000 8C420000 00021842 8C840000 70641802 00002021 40844800 0083202B 50800007 00001821 40044800 0083202B 1480FFFD 00000000 03E00008 00001821 03E00008 00000000 End CFunction |
Taking this mechanism we can change triangleout to run for a set number of seconds:
long long triangleout(long long *time){
unsigned int step;
unsigned int ramp;
unsigned int time_in_ticks;
unsigned int tick_rate;
tick_rate = CurrentCpuSpeed /2; //get the number of clock ticks per second
time_in_ticks=*time * tick_rate;
unsigned int current_ticks = 0;
asm volatile("mtc0 %0, $9": "+r"(current_ticks)); //set the current number of ticks to 0
while(current_ticks<time_in_ticks){
asm volatile("mfc0 %0, $9" : "=r"(current_ticks));//get the time in ticks since zeroed
for ( ramp = 0; ramp <= 31; ramp ++){
if ( ramp <= 15 ){
// ramp up
step = ramp;
} else {
// ramp down
step = 31 - ramp;
}
CVREFOpen( CVREF_ENABLE | CVREF_OUTPUT_ENABLE | CVREF_RANGE_LOW| CVREF_SOURCE_AVDD | step );
}
}
CVREFClose(); // Disable CVREF (not executed)
return CurrentCpuSpeed;
}
Replace the earlier version in cfunctions.c with this code, recompile and test it .
My version is:
1 2 3 4 5 6 7 8 9 10 11 |
dim i%=triangleout(10) ' run for 10 seconds print i% ' CPU speed end CFunction triangleout 00000000 3C029D00 8C420000 8C420000 00021042 8C830000 70431002 00001821 40834800 0062182B 50600013 34038000 3C07BF81 24060020 2408001F 40094800 00001821 10000003 00002021 01032023 0065200B 34848060 ACE49800 24630001 1466FFFA 2C650010 0122182B 1460FFF3 00000000 34038000 3C02BF81 AC439804 3C029D00 8C420000 8C420000 03E00008 00001821 End CFunction |
The current program runs for a time in seconds determined by the input parameter to the CFunction. However, later we will want to control the frequency of the waveform and whilst it would be possible to use the core timer for both purposes. It would be nice to dedicate it to helping drive the frequency calculations.
So we need a way of telling the CFunction to stop running and to return to Basic.
One way we can do this is to check if the user has entered CTRL-C on the keyboard, the same mechanism we would use to stop any program.
long long triangleout(void){
unsigned int step;
unsigned int ramp;
while(1){
for ( ramp = 0; ramp <= 31; ramp ++){
if ( ramp <= 15 ){
// ramp up
step = ramp;
} else {
// ramp down
step = 31 - ramp;
}
CVREFOpen( CVREF_ENABLE | CVREF_OUTPUT_ENABLE | CVREF_RANGE_LOW| CVREF_SOURCE_AVDD | step );
}
CheckAbort();
}
In this version of the code we are calling the function CheckAbort each time round the loop. If a control C has been pressed the firmware will automatically stop the program running and return control to the Basic prompt – how easy is that?
Note that in this version we now have no parameters to the CFunction so we tell the compiler this by using the keyword “void” in between the brackets.
Also note that we have created a never ending loop by using the construct:
while(1){
}
As in Basic the value zero means false and anything else means true.
As we are now using a never ending loop which can only be stopped by pressing CTRL-C there is now no way for the program to tidy up the way it did before by turning off the constant voltage reference – not good
So another way to terminate the loop would be to test for an input changing state:
long long triangleout(long long *pin){
unsigned int step;
unsigned int ramp;
ExtCfg(*pin,EXT_DIG_IN,CNPUSET); //set the pin specified as an input with a pull up
while(PinRead(*pin)){
for ( ramp = 0; ramp <= 31; ramp ++){
if ( ramp <= 15 ){
// ramp up
step = ramp;
} else {
// ramp down
step = 31 - ramp;
}
CVREFOpen( CVREF_ENABLE | CVREF_OUTPUT_ENABLE | CVREF_RANGE_LOW| CVREF_SOURCE_AVDD | step );
}
}
CVREFClose(); // Disable CVREF (not executed)
return CurrentCpuSpeed;
}
In this version we pass a pin number to the function in “long long *pin”
The call “ExtCfg(*pin,EXT_DIG_IN,CNPUSET);” sets the pin we have specified as a digital input and adds an option to enable the pullup. CNPUSET is defined in cfunctions.h, we could also have used CNPDSET to use a pull down. CNPUCLR and CNPDCLR can be used to remove the pullup/down. These codes actually represent the hardware registers that are used to control the behaviour of the processor pins; see page 127 onwards of the processor manual
ExtCfg is of course the code that the Micromite firmware uses to execute SETPIN.
So we could have used ExtCfg(*pin,EXT_ANA_IN,0) to set the pin to an analogue input or ExtCfg(*pin,EXT_DIG_OUT,0) to set it as a digital output. The list of possible codes is at the bottom of cfunctions.h
To test the state of the pin we use “PinRead(*pin)” which returns 1 or 0 depending on the state of the pin. We can use PinRead because we know the input is a digital input. There is a more flexible version “ExtInp(*pin)” which will also work if the pin is set as an analogue input. In this case it returns a 10-bit integer number representing the 10-bit ADC value.
So our test routine for this version is:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
dim i%=triangleout(9) ' CFunction will run until pin 9 goes low end CFunction triangleout 00000000 27BDFFD8 3C029D00 AFB00010 8C420010 00808021 8C840000 AFB40020 AFB3001C AFB20018 AFB10014 AFBF0024 24050002 0040F809 2406000E 3C149D00 3C12BF81 24110020 2413001F 8E820020 0040F809 8E040000 10400015 00001021 00001821 34638060 24420001 2C440010 AE439800 5051FFF6 8E820020 1480FFF9 00401821 02621823 34638060 24420001 2C440010 AE439800 5051FFED 8E820020 5080FFF9 02621823 1000FFEE 00401821 3C029D00 8C420000 3C03BF81 34048000 8FBF0024 AC649804 8C420000 00001821 8FB40020 8FB3001C 8FB20018 8FB10014 8FB00010 03E00008 27BD0028 End CFunction |
As I suggested above, a useful addition to our program would be to be able to control the frequency of the output of our “DAC” and this version does that by adding a second parameter to the CFunction call.
This is also a good time to talk about debugging
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
CFunction triangleout 00000000 27BDFFB0 AFBF004C AFB70048 AFB60044 AFB50040 AFB4003C AFB30038 AFB20034 AFB10030 AFB0002C 0080B021 3C129D00 8E420000 8C570000 0017B842 8CA20000 00021140 02E2001B 004001F4 00008012 2610FFFC 2402000D A3A20024 2402000A A3A20025 A3A00026 2402000A AFA20010 8E420030 27A40018 02003021 0040F809 00003821 8E42002C 0040F809 27A40018 8E42002C 0040F809 27A40024 00008821 40914800 8E420010 8EC40000 24050002 0040F809 2406000E 3C159D00 3C12BF81 24130020 1000000E 2414001F 40024800 0050102B 1440FFFD 00000000 40914800 2C640010 02831023 0064100B 34428060 AE429800 24630001 1473FFF4 00000000 8EA20020 0040F809 8EC40000 1440FFEF 00001821 34038000 3C02BF81 AC439804 02E01021 00001821 8FBF004C 8FB70048 8FB60044 8FB50040 8FB4003C 8FB30038 8FB20034 8FB10030 8FB0002C 03E00008 27BD0050 End CFunction |
long long triangleout(long long *pin, long long *freq){
char b[10];
char crlf[3];
unsigned int step;
unsigned int ramp;
unsigned int samplefreq=*freq * 32; //frequency at which each step must be changed
unsigned int tick_rate;
unsigned int frigfactor=4;
tick_rate = CurrentCpuSpeed /2; //get the number of clock ticks per second
unsigned int dwell=tick_rate/samplefreq - frigfactor;
unsigned int zero=0 ;
unsigned int current_ticks;
crlf[0]=13;
crlf[1]=10;
crlf[2]=0;
IntToStr(b,dwell,10);
MMPrintString(b);
MMPrintString(crlf);
asm volatile("mtc0 %0, $9": "+r"(zero)); //set the current number of ticks to 0
ExtCfg(*pin,EXT_DIG_IN,CNPUSET); //set the pin specified as an input with a pull up
while(PinRead(*pin)){
for ( ramp = 0; ramp <= 31; ramp ++){
do{
asm volatile("mfc0 %0, $9" : "=r"(current_ticks));//get the time in ticks since zeroed
}while(current_ticks<dwell);
asm volatile("mtc0 %0, $9": "+r"(zero)); //set the current number of ticks to 0
if ( ramp <= 15 ){
// ramp up
step = ramp;
} else {
// ramp down
step = 31 - ramp;
}
CVREFOpen( CVREF_ENABLE | CVREF_OUTPUT_ENABLE | CVREF_RANGE_LOW| CVREF_SOURCE_AVDD | step );
}
}
CVREFClose(); // Disable CVREF (not executed)
return tick_rate;
}
So we have added a second parameter to the function call “long long *freq” which we will use to tell the CFunction the frequency of the waveform.
Using the code we saw earlier in the thread we calculate the tick rate of our clock:
“tick_rate = CurrentCpuSpeed /2; //get the number of clock ticks per second ”
We then need to do a calculation to work out how many clock ticks must occur between each change in the output in order to get our desired frequency. We know than we output 32 different levels for each complete waveform so if we multiply 32 by the desired frequency we get the frequency at which we need to change the output
“unsigned int samplefreq=*freq * 32; //frequency at which each step must be ”
If we divide this into the number of ticks per second that should give us the number of ticks to hold each output level. However, we will use some machine cycles repeatedly reading the clock until it has changed by the desired amount so we had better add a frig factor to allow for this:
“unsigned int dwell=tick_rate/samplefreq – frigfactor;”
This looks like the correct sort of calculation and we can tune the frig factor assuming the basic equation is correct but it would be good to have some diagnostics to help in debugging.
There are two basic ways of doing this. First we can return any values of interest in the function return. In the code above we return the tick_rate and print that out from Basic. Alternatively we can use one of the firmware routines to output to the console “MMPrintString”.
As the name suggest this outputs a string. In Basic a string is a character array with the length of the string in character zero. In C a string uses a zero character to mark the end of the string.
However, before we can output anything we need to convert the binary representation of the number to a string. We do this Using another firmware function “IntToStr” which takes as its arguments the character array, the number to be converted and the base we want for the conversion.
So we have declared a character array b set up with size 10 “char b[10];” and an array crlf with size 3. We can then use IntToStr to convert the value in “dwell” to a string and load the ascii values for a carriage return and line feed into crlf (remembering to set the third character to zero)
We can then call MMPrintString to output to the console and confirm that the value in dwell looks sensible.
This is debugging in the old style
Note that you cannot use the MPLabX debugging facilities to step through the code for Cfunctions – it cannot work as they are not at the address the compiler thinks they are (position independence again!)
The final bit of this version is to use our timing mechanism to wait in each loop for the correct amount of time
do{
asm volatile("mfc0 %0, $9" : "=r"(current_ticks));//get the time in ticks since zeroed
}while(current_ticks<dwell);
asm volatile("mtc0 %0, $9": "+r"(zero)); //set the current number of ticks to 0
Note that C has almost exactly the same syntax as Basic for a “DO…WHILE test” loop.
Executing the loop takes a few machine cycles so the core timer will actually be greater than dwell by the time we reset it to zero. This is where the frig factor come in to tune the loop. 4 seems to work reasonably well in this program.
So with this version we have a working function generator outputting a triangle wave at a specified frequency – not bad!
One final iteration of this program. It would be good to display other waveforms so we could include square wave, sine wave, saw-tooth, as well as triangle.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
DIM sinewave%(31)=( 8,9,10,12,13,14,14,15,15,15,14,14,13,12,10,9,7,6,5,4,2,1,1,0,0,0,1,1,2,3,5,6) dim i%=funcgen(9,5000,"Sine",sinewave%()) 'pin number for the stop key, frequency output pause 1000 i%=funcgen(9,5000,"Triangle") pause 1000 i%=funcgen(9,5000,"Saw") pause 1000 i%=funcgen(9,5000,"Square") end CFunction funcgen 00000000 27BDFFC0 AFBF003C AFBE0038 AFB70034 AFB60030 AFB5002C AFB40028 AFB30024 AFB20020 AFB1001C AFB00018 0080B821 00C08021 00E0F021 3C029D00 8C430000 8C630000 00031842 AFA30014 8CB50000 0015A940 0075001B 02A001F4 0000A812 00008812 00001821 40834800 8C420010 8C840000 24050002 0040F809 2406000E 3C169D00 24120053 3C13BF81 24140020 00151140 10000023 AFA20010 40024800 0043102B 1440FFFD 00000000 82020001 10520006 00711821 10470004 2C860010 01041023 10000010 0086100B 82060002 50C9000D 8CA20000 14CA0003 00000000 10000009 8CA20000 10CB0004 2C820010 14CC0005 00041042 2C820010 00003021 01A2300B 00C01021 34428060 AE629800 24840001 1494FFE1 24A50008 8FA20010 02A2A821 8EC20020 0040F809 8EE40000 1040000B 03C02821 02A01821 00002021 24090049 240A0069 240B0051 240D000F 240C0071 24070073 1000FFD0 2408001F 34038000 3C02BF81 AC439804 8FA20014 00001821 8FBF003C 8FBE0038 8FB70034 8FB60030 8FB5002C 8FB40028 8FB30024 8FB20020 8FB1001C 8FB00018 03E00008 27BD0040 End CFunction |
long long funcgen(long long *pin, long long *freq, char mode[], long long sinewave[]){
char b[10];
char crlf[3];
unsigned int step;
unsigned int ramp;
unsigned int samplefreq=*freq * 32; //frequency at which each step must be changed
unsigned int tick_rate;
unsigned int frigfactor=0;
tick_rate = CurrentCpuSpeed /2; //get the number of clock ticks per second
unsigned int dwell=tick_rate/samplefreq - frigfactor;
unsigned int next_clock=dwell;
unsigned int current_ticks=0;
crlf[0]=13;
crlf[1]=10;
crlf[2]=0;
asm volatile("mtc0 %0, $9": "+r"(current_ticks)); //set the current number of ticks to 0
ExtCfg(*pin,EXT_DIG_IN,CNPUSET); //set the pin specified as an input with a pull up
while(PinRead(*pin)){
for ( ramp = 0; ramp <= 31; ramp ++){
do{
asm volatile("mfc0 %0, $9" : "=r"(current_ticks));//get the time in ticks since zeroed
}while(current_ticks<next_clock);
next_clock+=dwell;
if(mode[1]!=83 && mode[1]!=115){ //mode doesn't start with an "S" or "s"
if ( ramp <= 15 ){
// ramp up
step = ramp;
} else {
// ramp down
step = 31 - ramp;
}
} else if (mode[2]==73 || mode[2]==105) { //sIne wave
step=sinewave[ramp];
} else if (mode[2]==81 || mode[2]==113){ //sQuare wave
step = ramp >15 ? 0 : 15;
} else { //must be saw tooth
step=ramp/2;
}
CVREFOpen( CVREF_ENABLE | CVREF_OUTPUT_ENABLE | CVREF_RANGE_LOW| CVREF_SOURCE_AVDD | step );
}
}
CVREFClose(); // Disable CVREF (not executed)
return tick_rate;
}
This allows the demonstration of passing strings and arrays to the CFunction. First we need to pass information about which mode to use. This is done in the array “mode”. Note that in C we use square brackets for the array index. C arrays always start at position zero. The CFunction has no information about how big the array is unless we pass the information as a separate parameter. In this case we know that a Basic string will have its size in location “mode[0]” if we wanted to use it.
The CFunction does some very simple parsing of the string looking for upper or lower case characters
If it starts with a “T” or “t” it must be a triangle
otherwise look at the second character which must be a “Q” or “q” for a square wave, “I” or “i” for a sine wave and the code assumes anything else must have been a Sawtooth.
Note the syntax of the C “if” statements:
“==” means is the same as
“!=” means not the same as
“||” means logical OR
“&&” means logical AND
This is one of the biggest areas for error in beginners with C. if(a=b) does not do what you would expect. It actually says assign the value in b to a and then see if a is true or false, perfectly valid but probably not what we usually want- BEWARE!!!
There is one statement in the code which does not have a direct parallel in Basic but which I really like:
step = ramp >15 ? 0 : 15;
This can be translated as: “If ramp>15 then: step=0 : else : step=15 : endif
The other new parameter to the renamed function “funcgen” is “long long sinewave[]”. This is simply an integer array and you can see in the Basic code this has been pre-loaded with the amplitude values for a sine wave using 32 points and a 4-bit DAC
These number were generated by a great little excel spreadsheet I found:
To use the Basic program just run it and you will get the sinewave on the output pin. Briefly (less than one second, hence the pause) ground pin 9 and it will step on to the next output.
Hopefully, this thread has shown the development of a simple CFunction that does something that couldn’t done in Basic but might even be useful.
January 2016 Notes:
All the work I do is with XC32 1.33. This is the version Geoff uses for the firmware so it is probably best to stick with it. It was also the last version which Microchip distributed with the integrated plib library. You should definitely use:
-fPIC -mno-abicalls
the abicalls bit stops the compiler generating Unix System V Relase 4 style code (SVR4). It is the -fPIC that sets up position independence (to the extent that MIPS supports it).
The CFGEN program actually modifies the machine code to try and remove non-position independent instructions.
I’m now using the compiler options:
-fPIC -mno-abicalls -fno-toplevel-reorder -shared -membedded-data -mno-long-calls -fno-jump-tables -mno-jals -mgpopt -Wuninitialized -Wunused-variable -Wunused-value -Wunreachable-code
In addition I set the: “Exclude Standard Libraries”, “Do not link startup code”, and “Exclude Floating point library” tick boxes in the Libraries drop down on the options for xc32-ld
The above setting have been determined by Nathan and allow the use of things like “switch” statements that were previously a problem.
Make sure you are using optimisation level 1. Anything else can give problems. It is worth noting the xc32-1.4 has a very different optimisation engine which may be an issue.
Leave a Reply