Until now we used pretty simple program examples where all code fit in to single file and didn’t look very complicated. Probably because we programmed one simple function at a time. 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 very hard maintain your own code and even harder for other developers to read. We should follow some simple rules that makes microcontroller programming more fun than scratching along.
Split your programs
Say you are writing pretty complex program which deals with LCD, USART and even more peripherals. To make them all work, you need routines to init , read, write, change mode. Imagine how many functions your single file would have to hold and I am not talking about main routine. It would end up with more scrolling than coding. So, smart solution is to split your project in to several files – libraries. Each library would have to take care of one type tasks like controlling USART, other LCD 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 code would become heavy, hard to read and reuse. So lets split all these routines by assigning some logic. Logic basically relies on related functions like USART functions would go to one group, LCD to another. Some general functions may stay in main code. After grouping our example we have three groups of functions that goes 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 in to single program. This is done by using header files. So we need to create a pair file to each source file.
main.c – main.h(not required)
usart.c – usart.h;
lcd.c – lcd.h
Header files must be same name as source files but with extension .h. So when one source file needs to use 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>
Last thing to do is to make header files work. Header file is wrapped around preprocessor defines so called header guard:
#ifndef LCD_H #define LCD_H //contents #endif
This prevents from multiple includes of same header. First it checks if this file has been included, if not then current is included. The current situation may occur when several source files use 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 to much and use preprocessor defines. Header guard has to be named with all caps. Better use same name as file with underscore instead dot like LCD_H. In order to make header function we need to put something in it. First of all 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 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 will make this variable visible in other c files if header is included. Just be sure not to define variables in header files – do this in c file.
Last thing to mention about modularizing code is “static” functions. Lets 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 keyword “static” to this function.
static void LCDsendCommand(uint8_t);
Add this keyword in both – c and h files. This is useful practice that helps compiler to optimize function as it doesn’t have to be called outside the own c file.
Benefits of modularizing
It may seem complicated at the beginning but it is really easy and makes programming smooth. It is good practice to write separate libraries for separate function groups like USART, LCD, ADC, 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 hardware specific stuff. Later you will see that programming microcontrollers may be same as using/adapting right code library with minimal coding efforts.
Again this is only a 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 in internet where “C tutorial” is mentioned. Better lets focus on microcontroller stuff again.