The Importance of Modular Programming and Driver Separation in Embedded Development

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 instance, 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, once all team members have completed and debugged their respective modules, the project leader will conduct integration testing.

Such scenarios require the program to be modular. The benefits of modularity 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.

Beginners often struggle to understand how to program modularly; however, it is simple to learn and is one of the effective methods for organizing a well-structured program.

This article will first briefly discuss the methods and considerations for modularization, and finally provide detailed steps for modular programming using the widely used Keil C compiler as an example.

Understanding Modular Program Design:

A module consists of a .c file and a .h file, where the header file (.h) contains 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 module’s interface functions 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:

The Importance of Modular Programming and Driver Separation in Embedded DevelopmentNote: 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.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 (a 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:The Importance of Modular Programming and Driver Separation in Embedded DevelopmentHere 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 may not be 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 example, the local delay function static void delay (uint us) appearing in the .h file is unnecessary because other modules do not call it and cannot call it due to the static keyword’s restrictions.
  3. Make 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, which is actually due to the missing semicolon at the end of the function declaration in the .h file.

Application of the module: 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 lcd_device.h in the lcd_menu.c file.The Importance of Modular Programming and Driver Separation in Embedded Development

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 has already been illustrated in the previous example, where external functions and global variables provided by a module for other modules 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 setting global variables. Beginners often find it difficult to understand how variables shared 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 included in the module’s .c file by adding #include “lcd_device.h”.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 requires defining them once in the header file.In the header file where all global variables are defined (uCOS-II defines all global variables in one .h file):The Importance of Modular Programming and Driver Separation in Embedded DevelopmentEach global variable in the .H file is prefixed with xxx_EXT, where xxx represents the module name.The .C file for that module contains the following definitions:The Importance of Modular Programming and Driver Separation in Embedded DevelopmentWhen 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 been defined).Thus, the compiler allocates memory space for each global variable, while when the compiler processes other .C files, xxx_GLOBAL 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:The Importance of Modular Programming and Driver Separation in Embedded DevelopmentAt the same time, uCOS_II.H contains the following definitions:The Importance of Modular Programming and Driver Separation in Embedded DevelopmentWhen the compiler processes uCOS_II.C, it transforms the header file as shown below because OS_EXT is set to empty.The Importance of Modular Programming and Driver Separation in Embedded DevelopmentThus, the compiler will allocate these global variables in memory. When the compiler processes other .C files, the header file transforms as shown below because OS_GLOBAL is not defined, so OS_EXT is defined as extern.The Importance of Modular Programming and Driver Separation in Embedded DevelopmentIn 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, the delay function defined in the LCD driver module .c file as static void delay (uint us) is marked with static, which limits the function’s scope to only operate within this module, while also conveying the information that this function will not be called by other modules.Next, let’s detail the effects of this keyword. In C language, the static keyword has three distinct effects:

  1. In a function body, a variable declared as static maintains its value unchanged during the function’s calls.
  2. Within a module (but outside the function body), 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 effects are relatively easy to understand, while the last effect is exemplified by the delay function mentioned earlier (static void delay (uint us)), which has significant benefits as a localized function.

Never define variables in .h files!

Compare the following codes:Code One:The Importance of Modular Programming and Driver Separation in Embedded DevelopmentThe result of the above program is that an 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:The Importance of Modular Programming and Driver Separation in Embedded DevelopmentIn 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.

Leave a Comment