Click the card below to follow Arm Technology Academy
This article is from CSDN, and it mainly shares a solution for achieving high cohesion and low coupling during context switching in embedded programming..
Original link:
https://blog.csdn.net/qq_34245464/article/details/111804661
The demands of embedded programming are ever-changing. To ensure system stability while maintaining code reusability, high cohesion and low coupling must be achieved.
1 Introduction
It is commonly believed that time-consuming operations cannot be executed during interrupts, as this would affect system stability, especially in embedded programming. For programs with an operating system, interrupt handling can be divided into two parts through the operating system’s scheduling, allowing time-consuming operations to be executed in threads. However, how should this be handled in the absence of an operating system?
In common practice, we might define some global variables as flags, continuously check these flags in the main loop, modify them in the interrupt, and finally execute specific logic in the main loop. However, this undoubtedly increases coupling and raises the maintenance cost of the program.
1.1 cpost
cpost is a simple yet very convenient tool designed for this situation, allowing for easy context switching and reducing module coupling.
cpost link:
https://github.com/NevermindZZT/cpost
cpost draws inspiration from the Android handler mechanism by running a task in the main loop and allowing functions to be thrown from other places, whether from interrupts or module logic, to execute in the main loop, thus decoupling from the calling context. cpost also supports delayed processing, allowing you to specify how long after being thrown a function should execute.
1.2 Usage
Using cpost is very simple. Here, we will take its application in an embedded system without an operating system as an example, mainly for interrupt delay processing.
1.2.1 Configure system tick
Configure the macro CPOST_GET_TICK() in cpost.h to retrieve the system tick, using STM32 HAL as an example.
#define CPOST_GET_TICK() HAL_GetTick()
1.2.2 Configure processing process
// Call cpostProcess in the main loop
int main(void){ ... while (1) { cpostProcess(); } return 0;}
1.2.3 Throw task
Call the cpost interface in places where context switching is needed, allowing it to run in the main loop.
cpost(intHandler);
1.3 Principle Analysis
The principle of cpost is quite simple, and its code is very minimal, totaling only a few dozen lines. cpost maintains a global array.
CpostHandler cposhHandlers[CPOST_MAX_HANDLER_SIZE] = {0};
Each element of the array represents a function and its parameters that need to be executed. When the cpost interface is called, the posted function and parameters are saved in this array. The cpostProcess function running in the main loop traverses this array and executes the corresponding function when conditions are met, achieving context switching.
void cpostProcess(void){ for (size_t i = 0; i < CPOST_MAX_HANDLER_SIZE; i++) { if (cposhHandlers[i].handler) { if (cposhHandlers[i].time == 0 || CPOST_GET_TICK() >= cposhHandlers[i].time) { cposhHandlers[i].handler(cposhHandlers[i].param); cposhHandlers[i].handler = NULL; } } }}
In fact, the cpost method is quite similar to the initial approach of using global flags for context switching. However, cpost simplifies the process by maintaining an array and directly posting functions, eliminating the need to manage flags and coupling the functions to the main loop, making it easier to use.
2 Perfect Decoupling – cevent Application
For modular programming, achieving decoupling between modules has always been a challenging issue, especially in embedded programming, where complex control logic and size constraints often lead to independent modules calling each other. Therefore, the cevent component in cpost provides a very simple implementation for decoupling modules by mimicking the broadcast mechanism in the Android system.
2.1 Principle
cevent draws from the broadcast mechanism of the Android system. Each module, while working, will have multiple specific event points. In high-coupling programming, other module functionalities may be called at these points. For example, when the communication module receives a command, it may need to blink an indicator light.
Using cevent, we can throw an event at these points, allowing the current module to not worry about which other module’s logic needs to be executed at these locations. Other modules or user-defined event listeners can execute the corresponding actions when specific events occur.
2.2 Usage
cevent uses a registration method to listen for events and depends on the compilation environment. Currently, it supports Keil, IAR, and GCC. For GCC, the linker file (.ld) needs to be modified to add:
_cevent_start = .;KEEP (*(cEvent))_cevent_end = .;
2.2.1 Initialize cevent
During system initialization, call ceventInit.
ceventInit();
2.2.2 Register cevent event listener
In the C file, call CEVENT_EXPORT to export the event listener.
CEVENT_EXPORT(0, handler, (void *)param);
2.2.3 Send cevent event
In the place where the event occurs, call ceventPost to throw the event.
ceventPost(0);
2.3 Using cevent to Decouple Module Initialization
In embedded programming, it is common to call the initialization functions of various modules at program startup. This is also a form of coupling, leading to lengthy initialization code in the main function. With cevent, we can optimize and decouple initialization.
2.3.1 Define initialization events
Define values for initialization events. Some modules may depend on the initialization of other modules, requiring a specific order. Therefore, we can divide initialization into two stages and define two events. If more complex requirements exist, additional events can be defined.
#define EVENT_INIT_STAGE1 0
#define EVENT_INIT_STAGE2 1
2.3.2 Initialize cevent and throw events
In the main function, initialize cevent and throw initialization events.
int main(void){ ... ceventInit();
ceventPost(EVENT_INIT_STAGE1); ceventPost(EVENT_INIT_STAGE2); ... return 0;}
2.3.3 Register event listeners
Register event listeners for all functions that need initialization. Here, I will take registering the letter-shell as an example, divided into two parts: initializing the serial port and initializing the shell.
In the serial module, register the serial initialization to the first initialization stage. cevent supports passing up to 7 parameters directly to the registered listener function. The following registration method is equivalent to calling serialInit(&debugSerial) at the location of the EVENT_INIT_STAGE1 event in the main function.
CEVENT_EXPORT(EVENT_INIT_STAGE1, serialInit, (void *)(&debugSerial));
Then, in the shell module, register the shell initialization function to the second initialization stage.
CEVENT_EXPORT(EVENT_INIT_STAGE2, shellInit);
2.4 Using cevent to Decouple Main Loop
In embedded programming without an operating system, if we want to run the logic of multiple modules simultaneously, we usually loop through calls in the main loop. Writing functions into the main loop increases coupling.
int main(void){ ...
while (1) { // Module logic written in the main loop
shellTask(&shell); LedProcess(); ... } return 0;}
By using cevent, this coupling can also be easily eliminated.
2.4.1 Define main loop events
Define values for main loop events.
#define EVENT_MAIN_LOOP 3
2.4.2 Throw events in the main loop
Remove calls to other modules from the main loop and replace them with main loop events.
int main(void){ ...
while (1) { ceventPost(EVENT_MAIN_LOOP); } return 0;}
2.4.3 Register event listeners in each module
Register listeners for the main loop event in each module.
CEVENT_EXPORT(EVENT_MAIN_LOOP, shellTask, (void *)(&shell));
CEVENT_EXPORT(EVENT_MAIN_LOOP, LedProcess);
3 Conclusion
cevent is a very small module with simple code, but by mimicking the broadcast mechanism, cevent can perform powerful functions. Additionally, it can be combined with cpost to implement delayed events and other functionalities.
Recommended Reading
-
Classification and Introduction of GUI in Embedded Systems – Focused on MCU Microcontrollers
-
Essential Drawing Tools for Embedded Development
-
Some Excellent Open Source Projects in Embedded Systems
Long press to recognize the QR code to add Miss Ji Shu’s WeChat (aijishu20) and join the Arm Technology Academy reader group.
3100+ courses using 11 types of Arm educational kits are available for free download, including embedded systems, chip design, signal processing, operating systems, etc.
Follow Arm Technology Academy