Notice. New forum software under development. It's going to miss a few functions and look a bit ugly for a while, but I'm working on it full time now as the old forum was too unstable. Couple days, all good. If you notice any issues, please contact me.
|
Forum Index : Microcontroller and PC projects : CFunctions - learning by example
Page 2 of 2 | |||||
Author | Message | ||||
matherp Guru Joined: 11/12/2012 Location: United KingdomPosts: 8601 |
Looking at the above posts, I'm not sure if I should continue the thread or not. The idea was to progressively develop something using the CFunction capability that couldn't be done in Basic and in the process use more of the fantastic facilities that 4.7 gives for CFunction development with each iteration. Views? |
||||
Chris Roper Senior Member Joined: 19/05/2015 Location: South AfricaPosts: 280 |
Please continue, it is an extremely valuable resource that you were building and an excellent contribution. I apologise for being complicit in the derailing of your thread and sincerely do look forward to working through your tutorial. Cheers Chris http://caroper.blogspot.com/ |
||||
Grogster Admin Group Joined: 31/12/2012 Location: New ZealandPosts: 9073 |
I 2nd that. Please continue. I plan to follow along, once I download the newer MPLAB X, which I hope to do tonight. Cfunction are a choice - you don't HAVE to learn byte one of a Cfunction, to be able to use MMBASIC just fine. But if you choose to learn about Cfunctions, threads like yours will be of much use to many. Smoke makes things work. When the smoke gets out, it stops! |
||||
kiiid Guru Joined: 11/05/2013 Location: United KingdomPosts: 671 |
Yes, please continue. I only expressed concerns about the future. Right now the Cfunctions are very useful http://rittle.org -------------- |
||||
disco4now Guru Joined: 18/12/2014 Location: AustraliaPosts: 844 |
Yes continue from me as well. Regards Gerry Latest F4 Latest H7 |
||||
paceman Guru Joined: 07/10/2011 Location: AustraliaPosts: 1328 |
Same from me. Greg |
||||
WhiteWizzard Guru Joined: 05/04/2013 Location: United KingdomPosts: 2794 |
Lots of CFunction coders being created then Keep posting Peter - will be great to see a simple-to-follow online tutorial For everything Micromite visit micromite.org Direct Email: whitewizzard@micromite.o |
||||
Frank N. Furter Guru Joined: 28/05/2012 Location: GermanyPosts: 815 |
YES PLEASE!!! I am lost in space, ...äh, lost in C!!! Please keep on and many THANKS!!!! Frank |
||||
matherp Guru Joined: 11/12/2012 Location: United KingdomPosts: 8601 |
Thanks for the support guys This post we will look at more of the Micromite internal functions that Geoff has made available to CFunctions. These are all listed in cfunctions.h 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: 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 |
||||
twofingers Guru Joined: 02/06/2014 Location: GermanyPosts: 1138 |
The sticking point (for me) is NOT the C-language! Just for learning purposes it might be helpful for BASIC users to have a C / C ++ interpreter as "CH" of www.softintegration.com (the free student version). But I think the main obstacle why we don't have more CFunction coders is because you need much more informations about the PIC32 hardware (and the internal structures of MMBasic too). It would be helpful to have more examples as C sources. How/where can I find informations about the meaning of asm volatile("mtc0 %0, $9": "+r"(current_ticks));
?? Are there dokumentations or books for this? Is e.g. Lucio Di Jasios "Programming 32-bit Microcontrollers" a good starting point? BTW there is already a excellent step by step tutorial for CFunction (made by G8JCF). @matherp: thanks for your efforts (all of them)! My 2c Michael |
||||
matherp Guru Joined: 11/12/2012 Location: United KingdomPosts: 8601 |
If you look at my posts I've included the C source for nearly everything I've written. If there is anything missing you would like let me know and I'll post it. The only way to get into the hardware is to get stuck into the various Microchip manuals and look at the various examples. Geoff uses the Microchip plib library as the main interface to the hardware, I've referenced the plib manual in a previous post. The basic hardware design of the PIC32 is exactly the same as all the smaller PICs. Hardware is controlled by registers, you read and write to the registers and they affect the way the hardware works. The code for MMBasic is freely available, it is beautifully written and commented, download it and get stuck in. I'm afraid there is no easy way other than quite a bit of study. The PIC32 processor has a co-processor that does critical things like exception handling (and controlling the core timer). There are specific instructions that are only used to work with the co-processor. plib has routines to do this but they won't work in a CFunction (I assume a position independence issue). These two lines use the standard GCC syntax for including in-line assembler into the C code. I just treat them as "magic"; use them as is and they will do what you want. These are the only "magic" lines I will or have ever needed to use. If you really want to know more google "mtc0" and follow the various links, it is all there. |
||||
twofingers Guru Joined: 02/06/2014 Location: GermanyPosts: 1138 |
I hope you don't misinterpreted my post as criticism. Your examples (and your "drivers" too) are always appreciated as I hopefully already expressed. Maybe we should convert (after cleaning) this thread to PDF if it is completed? |
||||
MicroBlocks Guru Joined: 12/05/2012 Location: ThailandPosts: 2209 |
Here is a link to Microchips online training material. http://microchip.wikidot.com/training:start It has lots of information and also a whole section about C in the context of Microchip. I have been going through the material for a while now and it is really a good resource. Microblocks. Build with logic. |
||||
matherp Guru Joined: 11/12/2012 Location: United KingdomPosts: 8601 |
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 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! |
||||
matherp Guru Joined: 11/12/2012 Location: United KingdomPosts: 8601 |
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. 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: 2015-09-18_114049_Dac.zip 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. When my fingers have recovered I'll start a new thread and look at the use of floating point numbers in CFunctions |
||||
Chris Roper Senior Member Joined: 19/05/2015 Location: South AfricaPosts: 280 |
Excellent Tutorial, thanks. Also a very useful piece of code, I was wondering if the DAC could be used for other than a Voltage reference, you have answered that too :) I Look forward to the next edition. Cheers Chris http://caroper.blogspot.com/ |
||||
matherp Guru Joined: 11/12/2012 Location: United KingdomPosts: 8601 |
Just one point about the output from CVREF (DAC). It is very high impedance so I use a BUF634 to create a 50ohm output. The BUF634 is a great device if a little pricey. It is a unity gain opamp with up to 250ma drive capability in a 5-pin TO220 style package. Only 4 pins need to be used, VCC, GND, Vin, Vout, and it works happily on a single 5V supply. It even survives a continuous short-circuited output. To use the function generator at audio frequencies, you can improve the waveforms by feeding the buffer opamp via a 10k resistor with a 220pF capacitor across the input and ground |
||||
Page 2 of 2 |
Print this page |