Several Program Architectures for Embedded Software
-
Three Common Execution Frameworks
- Foreground-Background Sequential Execution:
Advantages: For beginners, this is the easiest and most intuitive program architecture. It is simple and clear, suitable for software development with low complexity.
Disadvantages: Low real-time performance. Due to millisecond-level delays in each function, even a 1ms delay can cause different execution intervals for other functions. Although this can be mitigated using timer interrupts, the interrupt execution function must be short. As the program logic complexity increases, it can lead to confusion for maintenance personnel, making it difficult to understand the program's running state.
- Time-Slice Round-Robin:
- This design requires a certain level of real-time performance and uses a timer, typically set to 1ms (the timing can be adjusted, but too frequent interrupts reduce efficiency, while too long interrupts degrade real-time performance). Therefore, the execution time of each task function must be considered, ideally not exceeding 1ms (optimization is recommended if possible; if not, the task execution cycle must be much longer than the task execution time). Additionally, there should be no millisecond-level delays in the main loop or task functions.
- How to determine the task cycle for each function? It depends on the task's duration and effect. For example, a key scan task cycle might be 10ms (to improve responsiveness), an LED control task cycle might be 100ms (usually, a 100ms blinking frequency is appropriate, unless special requirements exist), and an LCD/OLED display cycle might be 100ms (since SPI/IIC interfaces can take 1~10ms or longer, the task cycle must be much longer than the execution time, and to meet the refresh rate acceptable to the human eye, it should not be too long; a 100ms task cycle is suitable).
- The following introduces two different implementation schemes, targeting friends who are not familiar with function pointers and those who want to learn more.
Design without Function Pointers:
* @brief Main function.
* @param None.
* @return None.
*/
int main(void)
{
System_Init();
while (1)
{
if (TIM_1msFlag) // 1ms
{
CAN_CommTask(); // CAN send/receive communication task
TIM_1msFlag = 0;
}
if (TIM_10msFlag) // 10ms
{
KEY_ScanTask(); // Key scan processing task
TIM_10msFlag = 0;
}
if (TIM_20msFlag) // 20ms
{
LOGIC_HandleTask(); // Logic processing task
TIM_20msFlag = 0;
}
if (TIM_100msFlag) // 100ms
{
LED_CtrlTask(); // LED control task
TIM_100msFlag = 0;
}
if (TIM_500msFlag) // 500ms
{
TIM_500msFlag = 0;
}
if (TIM_1secFlag) // 1s
{
WDog_Task(); // Watchdog feeding task
TIM_1secFlag = 0;
}
}
}
/**
@brief Timer 3 interrupt service function.
@param None.
@return None.
*/
void TIM3_IRQHandler(void)
{
if(TIM_GetITStatus(TIM3,TIM_IT_Update) == SET) // Overflow interrupt
{
sg_1msTic++;
sg_1msTic % 1 == 0 ? TIM_1msFlag = 1 : 0;
sg_1msTic % 10 == 0 ? TIM_10msFlag = 1 : 0;
sg_1msTic % 20 == 0 ? TIM_20msFlag = 1 : 0;
sg_1msTic % 100 == 0 ? TIM_100msFlag = 1 : 0;
sg_1msTic % 500 == 0 ? TIM_500msFlag = 1 : 0;
sg_1msTic % 1000 == 0 ? (TIM_1secFlag = 1, sg_1msTic = 0) : 0;
}
TIM_ClearITPendingBit(TIM3,TIM_IT_Update); // Clear interrupt flag
}
Design with Function Pointers:
/**
@brief Task function information structure definition.
*/
typedef struct{
uint8 m_runFlag; /*!< Program run flag: 0-not running, 1-running */
uint16 m_timer; /*!< Timer */
uint16 m_itvTime; /*!< Task run interval time */
void (*m_pTaskHook)(void); /*!< Task function to run */
} TASK_InfoType;
#define TASKS_MAX 5 // Define the number of tasks
/** Task function information */
static TASK_InfoType sg_tTaskInfo[TASKS_MAX] = {
{0, 1, 1, CAN_CommTask}, // CAN communication task
{0, 10, 10, KEY_ScanTask}, // Key scan task
{0, 20, 20, LOGIC_HandleTask}, // Logic processing task
{0, 100, 100, LED_CtrlTask}, // LED control task
{0, 1000, 1000, WDog_Task}, // Watchdog feeding task
};
/**
@brief Task function run flag processing.
@note This function is called by the 1ms timer interrupt
@param None.
@return None.
*/
void TASK_Remarks(void)
{
uint8 i;
for (i = 0; i < TASKS_MAX; i++)
{
if (sg_tTaskInfo[i].m_timer)
{
sg_tTaskInfo[i].m_timer--;
if (0 == sg_tTaskInfo[i].m_timer)
{
sg_tTaskInfo[i].m_timer = sg_tTaskInfo[i].m_itvTime;
sg_tTaskInfo[i].m_runFlag = 1;
}
}
}
}
/**
@brief Task function run processing.
@note This function is called by the main loop
@param None.
@return None.
*/
void TASK_Process(void)
{
uint8 i;
for (i = 0; i < TASKS_MAX; i++)
{
if (sg_tTaskInfo[i].m_runFlag)
{
sg_tTaskInfo[i].m_pTaskHook(); // Run task
sg_tTaskInfo[i].m_runFlag = 0; // Clear flag
}
}
}
/**
@brief Main function.
@param None.
@return None.
*/
int main(void)
{
System_Init();
while (1)
{
TASK_Process();
}
}
/**
@brief Timer 3 interrupt service function.
@param None.
@return None.
*/
void TIM3_IRQHandler(void)
{
if(TIM_GetITStatus(TIM3,TIM_IT_Update) == SET) // Overflow interrupt
{
TASK_Remarks();
}
TIM_ClearITPendingBit(TIM3,TIM_IT_Update); // Clear interrupt flag
}