The Importance of Modular Programming and Driver Separation in Embedded Systems

Follow and star our official account, to access exciting content

The Importance of Modular Programming and Driver Separation in Embedded Systems

Source: zhzht19861011

Introduction

When a project team undertakes a relatively complex engineering task, it means that you are no longer working alone. Instead, you collaborate with team members, each responsible for a part of the project. For example, you might only be responsible for the communication or display section.At this point, you should write your part of the program as a module, debug it separately, and leave interfaces for other modules to call.Finally, after all team members have completed and debugged their respective modules, the project leader will conduct integration testing.Such scenarios require the program to be modularized. The benefits of modularization are numerous; it not only facilitates division of labor but also aids in debugging, helps in structuring the program, and increases the program’s readability and portability.

What Needs to Be Said

Beginners often struggle to understand how to program modularly; in fact, it is a simple and effective method for organizing well-structured programs.This article will first briefly discuss the methods and considerations of modularization, and finally provide detailed steps for modular programming using the most widely used Keil C compiler as an example.Modular program design should understand the following overview:

A module is a combination of a .c file and a .h file, where the header file (.h) contains the declarations for the module’s interfaces;

This summarizes the implementation method and essence of modularization: writing the code for a functional module separately in a .c file, and placing the interface functions of that module in a .h file. For example, if you are using an LCD display, you might write an LCD driver module to implement the display of characters, Chinese characters, and images, named: led_device.c. The .c file for this module can be roughly written as:

#include …
…
// Define variables
 unsigned char value; // Global variable
…
// Define functions
// This is the first function of this module, which serves to delay, and is only called by functions within this module, hence it is marked with the static keyword
/******************** Delay Subroutine ************************/
static void delay (uint us) // delay time
{}
// This is the second function of this module, which needs to be called in other modules
/********************* Write Character Program **************************
** Function: Write character to LCD
** Parameter: dat_comm is 1 to write data, 0 to write command
 content is the number or command to be written
******************************************************/
void wr_lcd (uchar dat_comm, uchar content)
{}
……
……
/***************************** END Files ***********************************/

Note: Only these two functions are written here; the first delay function’s scope is within the module, while the second is needed by other modules. For simplicity, the function body is not provided here.The .h file provides the module’s interfaces. In the above example, the function for writing characters to the LCD: wr_lcd (uchar dat_comm, uchar content) is an interface function because it will be called by other modules, so it must be declared as an external function in the .h file (using the extern keyword). The other delay function: void delay (uint us) is only used within this module (local function, marked with the static keyword), so it does not need to be included in the .h file.The format of the .h file is as follows:

// Declare global variable
extern unsigned char value;
// Declare interface function
extern void wr_lcd (uchar dat_comm, uchar content); // Write character to LCD
……
/***************************** END Files ***********************************/

Here are three important points to note:

  1. In the Keil compiler, even if the extern keyword is not declared, the compiler will not report an error, and the program will run well, but this is not guaranteed with other compilers. It is strongly recommended to include it to develop good programming habits.

  2. Functions in the .c file will only appear in the .h file if they are used by other modules. For instance, the local delay function static void delay (uint us) appearing in the .h file is redundant because other modules cannot call it (due to the limitations of the static keyword).

  3. Be sure to add a semicolon ‘;’ at the end of this statement; many students have encountered the strange compiler error: error C132: ‘xxxx’: not in formal parameter list. This error is actually due to the missing semicolon at the end of the function declaration in the .h file.

Application of modules: If you need to use the function void wr_lcd (uchar dat_comm, uchar content) from the LCD driver module lcd_device.c in the LCD menu module lcd_menu.c, you only need to include the header file of the LCD driver module lcd_device.h in the lcd_menu.c file.

#include "lcd_device.h" // Include the LCD driver program header file, after which you can call the global functions in // lcd_device.h in this .c file, using global variables from the LCD driver program (if any).
…
// Call the function to write characters to the LCD
wr_lcd (0x01, 0x30);
…
// Assign value to the global variable
value = 0xff;
…

External functions and data provided by a module for other modules to call must be declared with the extern keyword in the .h file;

This statement has already been illustrated in the previous example, where external functions and global variables provided by a module for other modules to call must be declared with the extern keyword in the .h file.Next, let’s focus on the use of global variables. One of the challenges of using modular programming (for beginners) is the setting of global variables. Beginners often find it difficult to understand how shared variables between modules are implemented. The conventional approach is as mentioned in this statement, declaring external data with the extern keyword in the .h file.For example, the variable value in the previous example is a global variable. If a certain module also uses this variable, it can be accessed just like using an external function; simply include #include “lcd_device.h” in the .c file of the module using it.Another method for handling global variables between modules comes from the embedded operating system uCOS-II. This operating system has a unique and somewhat complex way of handling global variables, but once learned, it can be extremely useful. This method only needs to be defined once in the header file. The method is:In the header file where all global variables are defined (uCOS-II defines all global variables in one .h file):

#ifdef xxx_GLOBALS
#define xxx_EXT
#else
#define xxx_EXT extern
#endif

Each global variable in the .H file is prefixed with xxx_EXT, where xxx represents the module name.The .C file of that module has the following definition:

#define xxx_GLOBALS
#include "includes.h"

When the compiler processes the .C file, it forces xxx_EXT (which can be found in the corresponding .H file) to be empty (because xxx_GLOBALS has already been defined).Thus, the compiler allocates memory space for each global variable, while when the compiler processes other .C files, xxx_GLOBALS is not defined, and xxx_EXT is defined as extern, allowing users to call external global variables. To illustrate this concept, refer to uC/OS_II.H, which includes the following definitions:

#ifdef OS_GLOBALS
#define OS_EXT
#else
#define OS_EXT extern
#endif
OS_EXT INT32U OSIdleCtr;
OS_EXT INT32U OSIdleCtrRun;
OS_EXT INT32U OSIdleCtrMax;

At the same time, uCOS_II.H has the following definition:

#define OS_GLOBALS

#include "includes.h"

When the compiler processes uCOS_II.C, it transforms the header file as follows because OS_EXT is set to empty.

INT32U OSIdleCtr;

INT32U OSIdleCtrRun;

INT32U OSIdleCtrMax;

Thus, the compiler allocates memory for these global variables. When the compiler processes other .C files, the header file appears as follows because OS_GLOBALS is not defined, so OS_EXT is defined as extern.

extern INT32U OSIdleCtr;

extern INT32U OSIdleCtrRun;

extern INT32U OSIdleCtrMax;

In this case, no memory allocation occurs, and any .C file can use these variables. This way, you only need to define them once in the .H file.

Functions and global variables within a module must be declared with the static keyword at the beginning of the .c file;

This statement mainly discusses the role of the static keyword. Static is a very important keyword that can impose certain constraints on functions and variables and convey some information.For example, in the LCD driver module’s .c file, the delay function defined as static void delay (uint us) has its scope limited to this module, and it also conveys the information that this function will not be called by other modules.Now let’s discuss the role of this keyword in detail. In C language, the static keyword has three distinct functions:

  1. In a function body, a variable declared as static maintains its value throughout the function’s calls.

  2. Within a module (but outside of function bodies), a variable declared as static can be accessed by functions within the module but cannot be accessed by functions outside the module. It is a local global variable.

  3. Within a module, a function declared as static can only be called by other functions within that module. This means that the function is restricted to the local scope of the module that declares it.

The first two are relatively easy to understand, while the last function is exemplified by the delay function mentioned earlier (static void delay (uint us)), which is quite beneficial as a localized function.

Never define variables in a .h file!

Compare the following codes:Code One:

/*module1.h*/
int a = 5; /* Define int a in module1's .h file */
/*module1.c*/
#include "module1.h" /* Include module1's .h file in module1 */
/*module2.c*/
#include "module1.h" /* Include module1's .h file in module2 */
/*module3.c*/
#include "module1.h" /* Include module1's .h file in module3 */

The result of the above program is that the integer variable a is defined in modules 1, 2, and 3, with a corresponding different address in each module. Such a program is unnecessary. The correct approach is:Code Two:

/*module1.h*/
extern int a; /* Declare int a in module1's .h file */
/*module1.c*/
#include "module1.h" /* Include module1's .h file in module1 */
int a = 5; /* Define int a in module1's .c file */
/*module2.c*/
#include "module1.h" /* Include module1's .h file in module2 */
/*module3.c*/
#include "module1.h" /* Include module1's .h file in module3 */

In this way, if modules 1, 2, and 3 operate on a, they correspond to the same memory unit.Note:An embedded system typically includes two categories (note that it is two categories, not two) of modules:

  • Hardware driver modules, where each specific hardware corresponds to one module;

  • Software functional modules, where the division of modules should meet the requirements of low coupling and high cohesion.

‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧ END ‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧

Follow my WeChat public account, reply "Join Group" to join the technical exchange group according to the rules.

Welcome to follow my video account:

Click "Read the original" for more sharing, feel free to share, bookmark, like, and view.

Leave a Comment