Electronics > Microcontrollers
How do you structure a large C project for embedded applications.
diyaudio:
Hi
I've been playing around with some project concepts around a PIC24 using MPLABX, I was wondering as I come from a C# background how does one structure a large C embedded project (from a variable convention, file name layout ect.., I'm really new to C, I read the hardware datasheets, read C books and prototype simultaneously.. its working out well its just very time consuming as I don't work with embedded programming for a living, however I don't see much literature around embedded C conventions, coding style..
Can anyone comment I know this is an opinionated area, so I would just like to hear what others are doing ?
diyaudio:
--- Quote from: mojo-chan on May 02, 2014, 03:48:26 pm ---This is a huge topic on which entire books have been written. Some key things I would suggest doing:
1. Split code into separate files. Don't be afraid to have files with one function or a few #defines in them. The more modular the better, and it makes it easier to find things in a large project. It also makes compilation faster as unchanged files don't need to be re-compiled.
2. Use a naming scheme that makes it obvious where stuff is and what scope it exists in. I use a two or three character prefix for each file, and uppercase for externally available stuff (public) and lowercase for internal stuff. For example, if I have a file called "adc.c" that has all my ADC driving code in it the function names will all start with "ADC" or "adc".
3. Come up with a consistent naming scheme. In my code I use "wake" and "sleep" in drivers. Using the ADC example I have "ADC_wake()" and "ADC_sleep()" to power the ADC on and off. Similarly I have DAC_wake() and DAC_sleep(), RF_wake() and RF_sleep() etc. in other driver files.
4. Keep scope as limited as possible. Try not to make variables and functions externally accessible if possible.
5. Watch your interrupts. Any static variable that is used by an interrupt should be named with a prefix, like "_AT" (for atomic) to remind you that it needs special handling in the main code. Providing getter/setter functions isn't a bad idea.
--- End quote ---
@mojo-chan
Thanks for the tips !
#1 Good point never thought of that. :-+
#2 and #3 Great, seems I have been following these conventions.
#4 Would like to hear more what others are doing or recommend for data sharing, tricks ect.. between modules. at the moment I write my global variables like this example:
--- Code: ---extern int g_current_adc = 0;
--- End code ---
#5 Do you mean static variable initialization in the main function?
diyaudio:
--- Quote from: Mechatrommer on May 02, 2014, 03:54:50 pm ---learn history... of why the c, the c++ and the c#... it is the ugly truth of the very question you asked.
--- End quote ---
I'm interested in embedded C stylistics.. NOT the history.
dannyf:
--- Quote ---so I would just like to hear what others are doing ?
--- End quote ---
Most firms have their own coding styles and they are generally good. Here is what I do:
1) be good in C: coding a mcu isn't that different from coding a PC - just that writing to certain memory locations have particular, device-specific meanings (to control the peripherals). So you want to be proficient in C, particularly when it comes to modular programming;
2) limit your hardware access: because of 1), you want your code to be as little "embedded" as possible. That means that you want to shield your application away from the actual hardware. Any access to hardware should be done consistently and as much "single-point-of-entry' as possible. You should use the same routines to read a spi module, or send something over uart, or set a pin to output mode, for example. You shouldn't see hardware specific notations in your user code.
3) minimize writing new code: whenever you write a piece of code, think about ways of writing that code so that it can be used later by other programs, on the same mcu or different mcus. This will allow you to build up your own library and greatly speeds up code development.
Take the frequency meter code I wrote recently on a PIC. It contains a few routines:
1) configuration fuse settings: that's a file over 120KB now that sets the fuse settings for the various PICs I use. By including that in my project, I know that I will have a reasonably good starting point to work with and should I decide to change the fuse settings, the file has its own documentation and I can do so easily. Each time I work on a new PIC, I add the default fuse setting to that file and I don't have to look at the datasheet next time;
2) a gpio routine: that manages all my pin-oriented operations - setting a pin, clearing a pin, setting a pin to input or output, etc.
3) a software delay routine: standardized, frequency-conscious delays that will get me rough timing;
4) tmr0/1/2 routines: that initialize tmr0, sets up its isr, and allows a user to install his/her own handle.
5) a led display routine: that at each invocation, updates the led display.
In my application, I simply use the gpio routines to reset the pins, use the tmr0 routines to set tmr0 to trigger periodically and in the tmr0 isr, it runs the led display routine; I used th tmr1 routines to count external pulses; and tmr2 routines to gate tmr1.
The whole main() was done in 15 minutes and the whole thing was up and running in no time. and you could not see a reference to the actual hardware. So if I were to decide to port the code to AVR, or STM32F3, I can simply hook up the same routines there and hit the compile button.
Code portability is really about writing to a logic machine, improving code reliability, and speeding up development time.
Over the years, I have accumulated over 1000 such files for various functions on many mcus. That collection of routines and constant accumulation of those routines allow me to put something out much faster, and with higher reliability than I would otherwise.
Mechatrommer:
--- Quote from: dannyf on May 02, 2014, 04:29:54 pm ---1) be good in C: coding a mcu isn't that different from coding a PC...particularly when it comes to modular programming
--- End quote ---
didnt he said he dont like history? isnt that history?
--- Quote from: dannyf on May 02, 2014, 04:29:54 pm ---1), you want your code to be as little "embedded" as possible. That means that you want to shield your application away from the actual hardware. Any access to hardware should be done consistently and as much "single-point-of-entry' as possible.
--- End quote ---
thats called "encapsulation" in c++ terminology. in c# "encapsulation" is taken for granted. as far as i can tell, you are teaching him history.
Navigation
[0] Message Index
[#] Next page
Go to full version