In the last of a three-part series, you'll see one way to create preemptive task scheduling on a PIC microchip.
In the last article, I presented a working project of four concurrent display tasks that shared embedded system resources and switched a PIC18F452 MCU equally and willingly among them. I called this kind of a system a cooperative multitasking. Shrinking the tasks' program into a single reentrant program was another accomplishment of that project that helps in fitting complex tasks into the limited size of embedded memory.
In this article, we'll look at the next level of sophistication with my second project as an example. Here, we force tasks to suspend their execution upon periodic system interrupts. We call such an embedded system preemptive . I built this project on macros and work areas used in the previous project. This project demonstrates how modularity and reusability of embedded designs can provide added-value to your projects.
Preemptive task scheduling
This project builds on the cooperative task scheduling project discussed in the previous article. I recommend reading this article first as it will help you understand the changes required to move from cooperative to preemptive multitasking to achieve the policy differences. Preemption does not rely on tasks giving up the PIC control to the scheduler as cooperation did. Instead, the scheduler uses internal or external interrupts to restore system control from user tasks. The project is composed of a group of assembler code and work areas declarations in Microchip's MPLAB IDE (Integrated Development Environment) shown in Figure 1.
Figure 1: Project files window.
Software stack is one way around the limitation of PICmicro's hardware return stack of only 31 entries. Software stack implementation overwhelms some forums' discussions. My impression of what I have learned from the useful and variant opinions around this issue is that there isn't a formal answer to it.
Here, I represent my contribution to the implementation of the software stack by creating a shadow register memory map for each task. The PIC18F452 uses data memory area 0x0F80-0x0FFF to keep its own map. My shadow maps are created as follows in data memory:
0x0280-0x02FF for task #0,
0x0380-0x03FF for task #1,
0x0480-0x04FF for task #2,
0x0580-0x05FF for task #3.
You can change those allocations in program memory labeled 'Pgm-stk' . The interrupt handler and the scheduler modules maintain those shadow register maps as you will see later on when I describe the operation of each module.
It is worth mentioning that I dedicated the indirect address register FSR2 for maintaining the software stack. This made life much easier for me. You may work around this fixation by using temporary storage for pushing and popping FSR2 contents as needed.
The project is composed of four basic modules: the task initialization, the task scheduler, and the reentrant program .
Task initialization module
This module runs only once every time the embedded system is booted. This module is based on the cooperative initialization macro (COPINIT macro) that initializes the reentrant program and tasks' work areas. A complementing macro (PREINIT macro) is introduced to initialize the software stack entries of each of the four display tasks. Task initialization is simple and is confined to pointing to the reentrant program in the top-of-stack (TOS) register memory map. Handling the rest of the entries of the register memory map will be tackled in both the interrupt handler and the task scheduler modules.
I rely on TIMER0 to generate periodic interrupts to the reentrant program so that different tasks can acquire its execution for a preset duration (in program memory byte labeled Time slot ). The initialization module loads TMR0 register with that preset value and gives up system control to the reentrant program.
I mentioned in the previous article the possibility of conflicts, in a multitasking environment, between tasks contending for the same embedded resources, such as the hardware display module. In this project, I allocated a different display line to each task to make sure they do not overwrite each others' text. However, I assumed that if a task was interrupted while sending its text to the display, the effect of rotation would be hindered. I considered this as a conflict of task interests. So, I put a display semaphore (managed by the variable Dsp-sym ) that enables a single task to block access to the display in the face of the other tasks till it releases the semaphore. The reader may enable the semaphore code if his hardware display is affected by task switching.The interrupt handler
This module is activated whenever TIMER0 overflows cause a high-priority interrupt to occur. HIINT is the macro responsible for serving the interrupt (coded in Reen-Mac1.inc ). The module disables interrupts, saves the contents of registers FSR0 , FSR1 , TBLPTR , PROD , WREG , STATUS and TOS in memory work area and then pushes those work areas to the interrupted task's shadow register map identified by macro TASSTK . In this way the task's state is fully backed up as far as the display task is concerned. Now, the interrupt handler is ready to pass on system control to the schedule module. Control transfer is done by executing an unconditional branch instruction goto to scheduler. Note that the stack pointer at this stage is still pointing to the pushed program counter entry of the interrupted task (in other words, STKPTR is set to 0x01 ). The scheduler will make sure that the STKPTR does not overflow while interrupts hit the system periodically.
Task scheduler module
This scheduler (PRESCHED macro) has a more complex role than that of the cooperative scheduler. Upon interrupt, the interrupt handler (discussed later on) passes the control to the scheduler to prepare the reentrant program for the next task in the row to resume it operation. This scheduler has something in common with its cooperative kin; they both increment the (cur-task) variable to point to the next task that should gain control. They make sure that task number never exceeds three as we have four display tasks to manage.
The scheduler module is responsible for TIMER0 reinitialization following each interrupt. Just like the initialization module, the scheduler loads TMR0 register with the preset value of task activity duration. Note that fixing interrupt frequency for all tasks gives them equal priority in gaining system control. This kind of scheduling is called round-robin . If we change interrupt frequency setting for each task and use low-priority interrupts for some tasks, we simply change the scheduler policy to become priority based.
The scheduler also manages the return address stack and the register memory map by restoring their settings previously backed up by the interrupt handler task interruption. Affected registers are: FSR0 , FSR1 , TBLPTR , PROD , WREG , STATUS , and TOS . The restored settings are brought from the task's shadow register memory map and copied into the PIC's map. The top-of-stack (TOS) is a serious entry that forces the system to resume task execution, from the interrupt point, by executing a return instruction in the scheduler program causing the system to load the program counter (PC) with the TOS contents and resets the stack pointer (STKPTR) to avoid its overflow. This action causes system control to resume at some point within the reentrant program.Reentrant program
Each round of this program sends to the display buffers the preset message text of one of the four display tasks, in some order, producing text rotation effect. This action is identical to that of the previous project's reentrant program.
The program loads the preset display text (TASDAT macro) and the new starting position for display within that text (TASPOS macro) . Prior to displaying text, the program may clear the display line (if you wish to) to make the rotation effect more visible. Figure 2 illustrates how display would look like at some point in time during the project run. Each task data is identified by the task identifier (in other words, 0, 1, 2, and 3). Comparing the displayed text in the four lines indicates the rotation effect you may expect.
Figure 2: Debugger Watch window.
Following the display of a single line of text, the program may check the display semaphore to make sure that no other task is accessing the display. If not, it raises the semaphore and proceeds with sending text from program memory to the display buffer. The program then lowers the semaphore to allow other tasks access the display. Lastly and prior to returning control to the scheduler, the program moves the text start pointer one step forward for the task it serves, or resets the pointer if end-of-text is reached.
Similar to the previous project, I included an optional delay routine (DELAY macro) to sustain the displayed text for a preset duration to reasonably slow down fast PICs so that the user can comfortably catch the text rotation.
I created macro (TMR0INIT) to handle the setup of the timer and enable interrupts based on its overflow. The reentrant program invokes this macro at each round of its execution to make sure that TIMER0 is properly set.
Note that the reentrant program in this project loops back to itself (via instruction goto Cont Dsp) rather than branching back to the scheduler as in the previous project. This is a fundamental difference highlighting the fact that tasks in a preemptive environment do not return system control to the operating system willingly. On the contrary, they keep running the reentrant program till they are suspended by an interrupt.Implementation aspects
The project is composed of a main program file 'ReentPre.asm' which groups all modules together. Macros used to realize different operations of those modules are coded in file 'ReentMac1.inc' (see Table 1 for a list of those macros and their roles), which complements the macros of file 'ReentMac.inc' of the previous project. The project work areas are declared in file 'ReentWork1.inc' complementing those of the previous project as well (file 'ReentWork.inc') . The reentrant program is coded in the main assembly file 'ReentPre.asm' since it represents the embedded system core application.
Table 1: Project complementing macros list.
Contact me at for code listings for all files. Two additional files 'P18F452.inc' and '18f452.lkr' are required for the assembler to generate the object code of the project, and they are provided with MPLAB IDE package (see final paragraph of this article for more details).
To know how the project utilizes the embedded system memory, review Figures 3 and 4 that illustrate the program and data memory maps. It is interesting to know that the executable code of the project, apart from data and work areas, occupies around 580 bytes of program memory, which is only 55% of that required for the four display tasks without reentrant programming.
Figure 3: Program memory allocation.
Figure 4: Data memory allocation
Building up the project using MPLAB IDE follows the same steps of the previous project.
A different perspective
This series of articles is concerned with embedded multitasking. Although this issue is realized in some products on the market, I tackled the issue from a different perspective. Running multiple identical tasks in an embedded system is limited by the available memory and the processing power of the PIC device. Reentrant programming is one way around this limitation. The first article discussed the concepts of multitasking and reentrant programming.
Having a single reentrant program providing the functionality of user tasks is the key to realizing multitasking in a limited resources system. I presented two working projects applying different task scheduling policies: cooperative and preemptive . The latter builds on the code and structure of the former demonstrating to the reader how we can enforce variations of policies without reinventing the wheel.
You can download the development tools for those projects, MPLAB IDE for free from Microchip's web site www.microchip.com. I can provide you with code listings for both projects if you contact me at .
The parts in this series are located at:
Part 1: The basics of embedded multitasking on a PIC–Introduction
Part 2: The basics of embedded multitasking on a PIC–Cooperative multitasking
Gamal Ali Labib is an IT consultant in Cairo, Egypt. He specializes in IT security and IT turn-key projects management. He is also interested in parallel processing and VLSI. Dr. Labib has a B.Sc. and M.Sc. in computer engineering and electronics from Ain Shams University, Egypt, and a PhD in computer science from University of London, U.K. You can reach Dr. Labib at .