1. Limitations of Traditional Programming Structures
When not using an RTOS, embedded software typically employs two traditional programming structures: one is called the “foreground-background structure” or “super loop structure,” which is essentially an event-triggered programming method, and the other is a time-triggered programming method, as described in Michael J. Pont’s “Time-Triggered Programming Model.”
In practical applications, when the system becomes slightly complex, it becomes evident that both methods have certain limitations. Below is an example of a problem encountered in an actual product design.
In designing a wall-mounted smart power distribution meter for a distribution cabinet, the CPU program must accomplish the following tasks:
(1) Refresh the display data on the front screen every half second.
(2) Refresh the DI/DO every 0.1 seconds.
(3) Scan the keyboard every 0.2 seconds.
(4) Recollect and calculate measurement data every half second.
(5) Communicate with the host computer via Modbus over an asynchronous serial port, with a maximum rate of 19200 bps.
(6) Communicate with the clock chip and EEPROM via the IIC bus.
(7) Communicate with the LED display and acquisition chip via the SPI bus.
(8) Perform FFT transformation on the collected 6 channels of signals.
(9) When the system loses power, the CPU must quickly respond to write the current power meter reading into the EEPROM.
Among these tasks, tasks (5) and (9) are strongly real-time. If the serial port’s send and receive events do not receive timely responses, it can lead to byte loss during reception and excessive time intervals during sending, causing Modbus frame delimitation errors on the receiving end. If the system power loss event does not receive a timely response, it can result in EEPROM write failures. Other tasks only need to be executed within the specified cycle, but task (8) is particularly special; using a typical 8-bit CPU to perform FFT on 6 signals, even if each signal only does a 128-point FFT, it takes several seconds to compute once. Now let’s look at the challenges encountered when implementing the above design using traditional programming structures.
1.1 Programming Using the “Foreground-Background Method”
When programming using the “foreground-background method,” to ensure the timeliness of task (5), UART interrupts were used. An interrupt is generated after the UART completes the transmission and reception of a byte, and in the interrupt routine, the received character is stored in the receive buffer or the next character to be sent is taken from the send buffer and loaded into the UART for transmission. The handling of the Modbus protocol can be managed by a separate task outside the interrupt, ensuring that the interrupt routine remains short. To ensure the timely response of task (9), it must also be assigned an interrupt. When the system loses power, there is only about 10 ms of transition time; if the system cannot complete the relevant operations within this time, the system voltage will drop below the effective voltage and lose its operational capability.
After arranging the background interrupt tasks, let’s see how the foreground tasks are completed. The biggest challenge encountered here is handling task (8), as the execution time required for task (8) is too long. Simply treating it as a single task will affect the system’s response to other tasks. The code structure in the super loop is as follows:
while(1)
{
Task (1);
Task (2);
………
Task (8);
}
Since task (8) takes several seconds to execute, the entire super loop takes at least longer than the time required for task (8), meaning that one iteration of this super loop takes several seconds, which will not meet the response time requirements for each task.
To solve this problem, the only way is to break task (8) into many sub-tasks, compressing the execution time of each sub-task to around 10 milliseconds, and defining the state after each sub-task is completed. In the super loop, only one sub-task is executed each time based on the state, and the program structure is as follows:
while(1)
{
Task (1);
Task (2);
………
Switch (sub-task state)
{
case sub-task state 1:
Sub-task 1;
break;
case sub-task state 2:
Sub-task 2;
break;
…………
case sub-task state n:
Sub-task n;
break;
}
}
Thus, a task that takes several seconds for FFT computation must be broken down into hundreds of sub-tasks, each taking about 10 ms, which is clearly unacceptable.
In addition, the super loop structure has an implicit drawback: as the number of tasks increases, the execution time of the loop body increases linearly. In actual design, even without high-cost tasks like task (8), ensuring the system’s timely response as functionality increases is also a significant challenge.
1.2 Programming Using the “Time-Triggered Programming Model”
The core of the “time-triggered programming model” is to establish a cooperative task scheduler based on time triggers, minimizing event triggers (reducing the use of interrupts) in the system. The system completes the scheduling and execution of tasks through the task scheduler. Below is a typical program structure of the “time-triggered programming model”:
/*——————– Main Function ———————–*/
Void main(void)
{
SCH_Init();// Set up the scheduler
SCH_Add_Task(task function name, task scheduling delay, task scheduling period);// Add task to the scheduler’s task queue
SCH_Start();// Refresh the task queue
while(1)
{
SCH_Dispatch_Tasks(); // Execute the task scheduler
}
}
/*——————- Timer Interrupt Function ———————*/
Void SCH_Update(void) interrupt
{
// Refresh the task queue
}
Each task in the system is defined with a priority, task cycle period, and task delay time. The system timer interrupt program SCH_Update() refreshes the task queue at the set beat, and in the super loop, only the task scheduler SCH_Dispatch_Tasks() is executed, arranging the execution of tasks based on the state of the task queue.
This programming structure avoids the problem of the super loop structure’s loop time increasing linearly with the amount of code, but since tasks are non-preemptive, once a task starts executing, the task scheduler only has the opportunity to execute after the current task is completed. This requires that each task cannot occupy the CPU for too long; otherwise, it will affect the overall system response speed. Therefore, FFT computation must still be effectively split in this programming model; otherwise, it will require upgrading the CPU or using a preemptive RTOS, which will inevitably increase system costs. So, is there a better solution?
The following programming structure improves the “time-triggered programming model” to allow programmers to define tasks more intuitively without increasing hardware costs, reducing the impact of task characteristics on the system program structure, simplifying the program structure, and improving the system’s real-time response speed.
2. Improvements to the “Time-Triggered Programming Model”
Based on years of experience in embedded system programming, tasks in embedded systems can typically be divided into three types:
(1) Timely tasks;
(2) Periodic tasks;
(3) Background tasks;
The characteristics of timely tasks: these tasks are event-triggered; once an event occurs, the system must respond within a specified time. The most natural way to handle these tasks is to use interrupts, defining them as background tasks in the “foreground-background method.”
The characteristics of periodic tasks: these tasks are time-triggered and periodic; the system must ensure that tasks are executed within the specified period, and the “time-triggered programming model” can meet the needs of these tasks well.
The characteristics of background tasks: these tasks are non-real-time; timeliness is not very important, and the system can interrupt these tasks at any time during operation to execute the first two types of tasks. The system only needs to utilize resources to complete these tasks as quickly as possible. These tasks are best defined as foreground tasks in the “foreground-background method.”
Based on the above task classification, the improvements to the “time-triggered programming model” can be summarized as follows:
(1) Tasks are divided into three categories, with category 1 having the highest priority and category 3 having the lowest;
(2) High-priority tasks can interrupt the execution of low-priority tasks, while tasks of the same priority cannot preempt each other;
(3) In actual design, to improve the system’s predictability, the number of category 1 tasks and their execution time should be minimized;
(4) To reduce system resource usage, the system does not allocate separate stack space for tasks.
The essence of the above improvements is to design a simple task scheduling mechanism with three priorities, where high-priority tasks can interrupt low-priority tasks, and tasks of the same priority cannot preempt each other. This scheduling mechanism does not save the task context and separate stack for each individual task, thus reducing the resource requirements of this programming model.
In a preemptive RTOS, when a high-priority task interrupts a low-priority task, it saves the context of the low-priority task and stores its local variables in a separate stack for that task. If the system does not allocate separate stacks for tasks, how can it ensure the recovery of the execution environment of the low-priority task after the high-priority task exits?
This issue can be addressed by borrowing the interrupt handling mechanism with the following approach:
(1) Design a timer interrupt function in the system, whose function is to execute periodic task scheduling. This timer interrupt has the lowest priority among all interrupts;
(2) Design another timer interrupt function in the system, whose function is to refresh the task management queue of periodic tasks, providing support for task scheduling. This timer interrupt function has the second-lowest priority in the system;
(3) The periodic task is a function where the first operation at the entry is to enable interrupts (Question: does this interrupt refer to the interrupt that triggers timely tasks? If so, should it be enabled or disabled outside the periodic task? If enabled,); allowing the task to be interrupted during execution to respond to timely tasks.
(4) Background tasks are the code executed in the main function’s super loop, which can be interrupted at any time by timely and periodic tasks. The code for background tasks only executes when there are no timely and periodic tasks.
Through the above measures, the program structure of the “Improved Time-Triggered Programming Model” is as follows:
/*——————– Main Function ———————–*/
Void main(void)
{
SCH_Init();// Set up the scheduler
SCH_Add_Task(task function name, task scheduling delay, task scheduling period);// Add task to the scheduler’s task queue
SCH_Start();// Refresh the task queue
while(1)
{
Background Task 1;
………
Background Task n;
}
}
/*——————- Second-Lowest Priority Timer Interrupt Function ———————*/
Void SCH_Update(void) interrupt
{
// Refresh the task queue
}
/*——————- Lowest Priority Timer Interrupt Function ———————*/
Void SCH_Dispatch_Tasks(void) interrupt
{
// Schedule periodic tasks
}
/*——————- Typical Structure of Periodic Tasks ———————*/
Void SCH_Cycle_Task1(void)
{
// Enable interrupts /* This function can rely on interrupts to execute timely tasks */
// Execute task
return; // Task returns
}
Conclusion
Using the “Improved Time-Triggered Programming Model” for small embedded system programming is akin to programming with an RTOS. Once designers plan the tasks, they can focus on the design of each task, and the system can manage the CPU time usage uniformly, reducing coupling between tasks, making product program design and modifications clear and straightforward. This programming model effectively addresses the complex design issues faced by wall-mounted smart power distribution meters, proving that this method is simple and effective.
Currently, this design model has only established a task scheduler, and variable passing between tasks still requires the use of global variables. If semaphore and messaging mechanisms can be added, this model will be even more complete, making programming for low-cost small embedded systems more convenient and clear.
1.Experience Monologue of a Senior FPGA FAE~
2.From Chip Nuclear Bomb to Future Platform: Nvidia’s Transformation Ambition from CES
3.Writing Business Code Every Day, How to Become a Technical Expert?
4.For Embedded Systems, Three Key Technologies from ARM to Watch in 2017!
5.Where FPGA Excels Compared to Traditional CPUs?
6.From Chip Nuclear Bomb to Future Platform: Nvidia’s Transformation Ambition from CES
Disclaimer: This article is a network reprint, and the copyright belongs to the original author. If there are any copyright issues, please contact us, and we will confirm the copyright based on the materials you provide and pay for the manuscript or delete the content.