A Method for Context Switching and Perfect Decoupling in Embedded Programming

Follow “Embedded Mix” and select “Star the Official Account” to progress together!

A Method for Context Switching and Perfect Decoupling in Embedded Programming

Source: CSDN

Fast Context Switching – cpost Application

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 a thread. But how should this be handled in the absence of an operating system?

Typically, we might define some global variables as flags, then continuously check these flags in the main loop, modifying them in the interrupt, and finally executing specific logic in the main loop. However, this undoubtedly increases coupling and the maintenance cost of the program.

cpost

cpost is a simple yet very convenient tool applied in such situations, allowing for easy context switching and reducing module coupling.

cpost link:

https://github.com/NevermindZZT/cpost

cpost draws inspiration from the Android handler mechanism, running a task in the main loop, and allowing functions to be thrown from other places, whether from interrupts or module logic, to run 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.

Usage

Using cpost is very simple; here we take its application in an embedded system without an operating system as an example, mainly for interrupt delay processing.

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()

2. Configure Processing Process

Call cpostProcess function in the main loop

int main(void)
{
    ...
    while (1)
    {
        cpostProcess();
    }
    return 0;
}

3. Throw Task

Call the cpost interface in places where context switching is needed, allowing it to run in the main loop.

cpost(intHandler);

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, and the cpostProcess function running in the main loop traverses this array, executing the corresponding function when conditions are met, thus 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 previously mentioned method of using global flags for context switching, except that cpost eliminates the cost of maintaining flags through an array and directly posting functions, making it simpler and easier to use.

Perfect Decoupling – cevent Application

In 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. Thus, the cevent component in cpost provides a very simple implementation for decoupling modules by mimicking the broadcast mechanism in the Android system.

Principle

cevent draws from the broadcast mechanism of the Android system. Each module has multiple specific event points during operation. In tightly coupled 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 points. Other modules or user-defined event listeners can execute corresponding actions when specific events occur.

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 = .;

1. Initialize cevent

Call ceventInit during system initialization.

ceventInit();

2. Register cevent Event Listener

In the C file, call CEVENT_EXPORT to export the event listener.

CEVENT_EXPORT(0, handler, (void *)param);

3. Send cevent Event

In the place where the event occurs, call ceventPost to throw the event.

ceventPost(0);

Using cevent to Decouple Module Initialization

In embedded programming, we often call the initialization functions of various modules at the program startup, which is also a form of coupling, leading to long initialization code in the main function. With cevent, we can optimize and decouple initialization.

1. Define Initialization Events

Define the values for initialization events. Some modules may depend on the initialization of other modules, requiring a specific order. Therefore, we can divide the initialization into two stages, defining two events. If there are more complex requirements, we can define more events.

#define     EVENT_INIT_STAGE1       0
#define     EVENT_INIT_STAGE2       1

2. Initialize cevent and Throw Events

In the main function, initialize cevent and throw the initialization events.

int main(void)
{
    ...
    ceventInit();

    ceventPost(EVENT_INIT_STAGE1);
    ceventPost(EVENT_INIT_STAGE2);
    ...
    return 0;
}

3. Register Event Listeners

Register event listeners for all functions that need initialization. Here, I will take registering event listeners for 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 stage of initialization. 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 place where EVENT_INIT_STAGE1 occurs in the main function.

CEVENT_EXPORT(EVENT_INIT_STAGE1, serialInit, (void *)(&amp;debugSerial));

Then, in the shell module, register the shell initialization function to the second stage of initialization.

CEVENT_EXPORT(EVENT_INIT_STAGE2, shellInit);

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. This practice of writing functions into the main loop also increases coupling.

int main(void)
{
    ...

    while (1)
    {
        // Module logic written in the main loop
        shellTask(&amp;shell);
        LedProcess();
        ...
    }
    return 0;
}

By using cevent, this coupling can also be easily eliminated.

1. Define Main Loop Events

Define the values for main loop events.

#define     EVENT_MAIN_LOOP         3

2. Throw Events in Main Loop

Remove calls to other modules from the main loop and replace them with posting main loop events.

int main(void)
{
    ...

    while (1)
    {
        ceventPost(EVENT_MAIN_LOOP);
    }
    return 0;
}

3. Register Event Listeners in Each Module

Register event listeners for the main loop event in each module.

CEVENT_EXPORT(EVENT_MAIN_LOOP, shellTask, (void *)(&amp;shell));
CEVENT_EXPORT(EVENT_MAIN_LOOP, LedProcess);

Conclusion

cevent is a very small module, with its code being extremely simple. However, by mimicking the broadcast mechanism, cevent can perform powerful functions. Additionally, it can be combined with cpost to implement delayed events and other functionalities.

Source:https://blog.csdn.net/qq_34245464/article/details/111427317

This article is sourced from the internet, and the copyright belongs to the original author. If there are any copyright issues, please contact me for deletion.

You May Also Like:

Previous Recommendations

Practical | Analyzing Program Auto-Start

Understanding Cohesion and Coupling in C Language Programs

Practical | Teach You to Light Up a Web Page in 10 Minutes

Practical | Teach You to Build an Embedded Web Server in 10 Minutes

Practical | How to Remotely Log into a Development Board?

Leave a Comment