Until now, we used pretty simple program examples where all code fit into a single file and didn’t look very complicated. We didn’t care much about programming styles and modularizing. But due time, programs are going to grow bigger and more complex. It may become tough to maintain your code and even more complicated for other developers to read. We should follow some simple rules that make microcontroller programming more fun than scratching along.
Split your programs
Say you are writing a complex program that deals with LCD, USART, and even more peripherals. To make them all work, you need routines to init, read, write, and change mode. Imagine how many functions your single file would have to hold, and I am not talking about the main routine. It would end up with more scrolling than coding. So, the smart solution is to split your project into several files – libraries.
Each library would have to take care of one type task like controlling USART, other LCDs, and so on. For instance, our program needs these functions:
int main(); void MainPortInit(); uint8_t MainReadPort(); void USARTInit(); void USARTSendByte(uint8_t Data); uint8_t USARTReceiveByte(); void LCDInit(); void LCDclr(); void LCDGotoXY(uint8_t, uint8_t); void LCDSendChar(uint8_t);
Putting all these functions in one file would work, but the code would become heavy, hard to read and reuse. So let’s split all these routines by assigning some logic. Logic relies on related functions like USART functions would go to one group, LCD to another. Some general functions may stay in the main code. After grouping our example, we have three groups of functions that go to separate files:
int main(); void MainPortInit(void); uint8_t MainReadPort(void);
void USARTInit(void); void USARTSendByte(uint8_t Data); uint8_t USARTReceiveByte(void);
void LCDInit(void); void LCDclr(void); void LCDGotoXY(uint8_t, uint8_t); void LCDSendChar(uint8_t);
After this operation, we end up with three source files. But how to link them into a single program. This is done by using header files. So we need to create a pair file for each source file.
main.c – main.h(not required)
usart.c – usart.h;
lcd.c – lcd.h
Header files must be the same name as source files but with extension .h. So when one source file needs to use a routine from another source file, you must include a header file by defining header file using quotes:
#include"main.h" #include"usart.h" #include"lcd.h"
We already include some headers in our programs but in side brackets, meaning that these are system header files located in system directories.
#include <avr/io.h> #include <avr/interrupt.h>
The last thing to do is to make header files work. The header file is wrapped around preprocessor, defines so-called header guard:
#ifndef LCD_H #define LCD_H //contents #endif
This prevents multiple includes of the same header. First, it checks if this file has been included; if not, then current is added. The current situation may occur when several source files use the same include or include each other. For instance, usart.c would include lcd.h, and lcd.c would include usart.h – this would lead to recursive inclusion during compilation. So don’t worry too much and use preprocessor defines. Header guard has to be named with all caps. Better use the same name as a file with an underscore instead dot like LCD_H. To make the header function, we need to put something in it. First of all, the header must have all function prototypes from c file included. For instance, lcd.h may look like:
#ifndef LCD_H #define LCD_H //is 4 bit mode? #define 4BIT void LCDInit(void); void LCDclr(void); void LCDGotoXY(uint8_t, uint8_t); void LCDSendChar(uint8_t); #endif
Also, there can be various definitions, type definitions, or macro functions.
Also, header files can declare global variables that might be used in multiple source files. To make it possible, declare the variable inside c file:
uint8_t Mode = 1;
Then in header file lcd.h define this variable by adding “extern” keyword.
extern uint8_t Mode;
This variable will be visible in other c files if the header is included. Just be sure not to define variables in header files – do this in c file.
The last thing to mention about modularizing code is “static” functions. Let’s say we created separate lcd.c and lcd.h files. We wrote a bunch of routines like LCDsendCommand(uint8_t), LCDInit(), LCDWrite(uint8_t), LCDGotoXY(uint8_t, uint8_t), and so on. Some of these functions definitely will be used in other files, but some not. For instance, LCDsendCommand(uint8_t) will be used only inside lcd.c to make other routines work. We don’t need this function to be accessible by other c files. To do so, we add the keyword “static” to this function.
static void LCDsendCommand(uint8_t);
Add this keyword in both – c and h files. This is a valuable practice that helps the compiler to optimize functions as it doesn’t have to be called outside the own c file.
Benefits of modularizing
It may seem not very easy at the beginning, but it is straightforward and makes programming smooth. It is good practice to write separate libraries for separate function groups like USART, LCD, ADC, and TWI because once written, you can include these libraries multiple times in different projects– just be smart and write them to be more universal – don’t tie to specific hardware, but use preprocessor defines for device specific stuff. Later you will see that programming microcontrollers may be the same as using/adapting right code library with minimal coding efforts.
Again this is only scope of few things. We are not going back to them as these are basics of C programming and can be found on every corner of the internet where “C tutorial” is mentioned. Better let’s focus on microcontroller stuff again.
Pingback: Electronics-Lab.com Blog » Blog Archive » When your project grows bigger
There’s just one bit of caution that’s in order here: it does make coding easier if you work with separate smaller files. However, if you are coding for AVR using the latest release of the Arduino IDE (1.0) , it appears to have a bug that makes it only show as many included modules as fits on the screen given a particular minimum width of the tab. I don’t really know how to explain it better but if you are opening a project that has more than 10-12 included files, chances are, you’ll loose some of your files. I mean, it will compile fine, it’s just that you will be able to neither see nor edit any of the files that did not fit into the window. I’ve just ran into this the other day trying to modify a firmware from RepRap.org – most of their firmware projects have 20 included libraries and modules.
So, yes, if the project grows big, by all means do split it, but be mindful of the point where the number of files in the project will start becoming unmanageable and this Arduino IDE limitation is one example of it.
why do you have to include static functions in .h file since they are not meant to be used outside the .c file ?