Task and interrupt levels
When the program's point of execution reaches a task, that task is simply given an opportunity to run. However many tasks do not execute at every opportunity. When a task does run, it is said to be triggered. In such a system, information that is made available will not be processed until the intended task is triggered. Labrosse points out that this is called the task level response, the worst case task level response time depends on how long the overall loop takes to execute. Labrosse also points out that despite the shortcomings, most high volume microcontroller based applications such as microwave ovens, toys and the like use similar software architectures.
A simple delay count provides a means to trigger a task in a periodic fashion. In his first example, Peatman decrements a counter, when the counter reaches zero the task is triggered. The flowchart below is of such a single task which will be triggered at a nearly periodic rate. The variable timer is the delay count and is decremented by one, each time the loop repeats. Once the counter reaches zero the timer is reset and the task's work is performed.
Flow inside a task
Many variations are possible; Consider a situation where the arrival of new data activates an interrupt service routine (ISR). The idea here is that an ISR should interrupt for only brief moments. If the ISR sets a flag to signal data arrival then a task can be triggered to process the new data whenever it has an opportunity to do so. Another variation to consider is a one-shot delay which does not reset the timer value. Some other task or interrupt service routine is expected to reset the timer.
Note that a third task inserts a one millisecond delay, the number of iterations used here is based on the 375nsec system clock period. This leads us to the Achilles heel of super loop structures. All tasks in a super loop structure are dependent on each other for timely behavior. For the overall timing to be predictable, the timing of all tasks must be predictable. In the example presented here, the one millisecond delay is predictable. In designing the system I assumed that all other tasks trigger and execute in much less than one millisecond. The entire system depends on this assumption.
As pointed out in chapter 4 of Peatman as such a system is made to perform more useful work, the overall timing becomes more unpredictable. Peatman shows how to improve the situation by replacing the final delay task that slows the overall loop, with a task based on an interrupt timer instead. Despite the improvement, the Achilles heel of timing unpredictability remains.
This example program is assembled with the startasm.inc include file and is built using the supplemental notes Writing an Embedded Application, which is located at the class web page.
%TITLE "Blink and beep example program" ; This program was written for EE4801 ; Worcester Polytechnic Institute C' Term 2000 include startasm.inc extrn startup:near IDEAL PUBLIC Start DELAY_COUNT equ 94d LED_PORT equ 00h LED_DELAY equ 500d SOUND_PORT equ 01h SOUND_INCR equ 64d SOUND_DELAY equ 100d segment _data ; ; Interrupt vectors ; DIVIDE_ERR_OFF dw offset startup DIVIDE_ERR_SEG dw seg startup SINGLE_STEP_OFF dw offset startup SINGLE_STEP_SEG dw seg startup NMI_OFF dw offset startup NMI_SEG dw seg startup BREAK_POINT_OFF dw offset startup BREAK_POINT_SEG dw seg startup OVERFLOW_OFF dw offset startup OVERFLOW_SEG dw seg startup ; ; Variables ; LED_TIME dw 01h LED_SAVE db 02h SOUND_ARG db 00h SOUND_TIME dw 00h GRAY_CODE db 0d, 1d, 3d, 2d ends _data segment _text ;============================================================================== ; main procedure is an endless loop ;============================================================================== start: ; ; Initialize the system variables ; mov ax, _data mov ds, ax ; Assign LEDs with initial values mov al, [LED_SAVE] out LED_PORT, al ; Assign speaker with initial values mov al, [GRAY_CODE] out SOUND_PORT, al @@top: call ToggleLEDs call MakeTone call one_msec jmp @@top ;============================================================================== ; ToggleLEDs ; ; This task toggles the display LEDs after delay LED_DELAY ;============================================================================== PROC ToggleLEDs NEAR ; check for clock trigger dec [LED_TIME] jg @@exit ; restart the trigger mov [LED_TIME], LED_DELAY ; toggle the LEDs mov al, [LED_SAVE] xor al, 03h mov [LED_SAVE], al out LED_PORT, al ; restart the sound mov [SOUND_TIME], SOUND_DELAY @@exit: ; all done for now ret ENDP ToggleLEDs ;============================================================================== ; MakeTone ; ; Drives the speaker to make an audible tone ;============================================================================== PROC MakeTone NEAR ; check if active mov cx, [SOUND_TIME] jcxz @@pass dec cx mov [SOUND_TIME], cx ; update the phase angle xor bx, bx mov bl, [SOUND_ARG] add bl, SOUND_INCR mov [SOUND_ARG], bl ; look up the output value shr bl, 6d mov al, [GRAY_CODE+bx] out SOUND_PORT, al jmp @@exit @@pass: xor al, al out SOUND_PORT, al @@exit: ret ENDP MakeTone ;============================================================================== ; one_msec ; ; This procedure inserts a one millisecond delay ;============================================================================== PROC one_msec NEAR push cx mov cx, DELAY_COUNT @@top: nop dec cx jnz @@top pop cx ret ENDP one_msec ends end start ;End of module/entry point
While the above example does work correctly, a software engineer should rightly point out several reasons why it is dangerous to allow the ToggleLEDs task to directly manipulate the SOUND_TIME variable. Ideally, each task should know as little of the inner-working's of other tasks, as is practical. As one objection, if the MakeTone task is rewritten then changes to other tasks, especially ToggleLEDs may be needed. In a more practical example, even a minor change may impact other tasks in unforseen ways. It is better to write a program in such a way that details associated with each task are not known by other tasks, this is the principle of information hiding.
As an exercise, improve the above example. Define a flag called SOUND_RESET_REQ that any task can set. In seeing the flag set, the MakeTone task then clears the flag and responds accordingly. In a super-loop structure such as this, boundaries between tasks are not clearly drawn by the assembler. In reviewing the example, make sure to notice the naming convention for variables. Each prefix is given to provide an association with a specific task. This simple trick helps the programmer immensely. In writing such a program it is the programmer's responsibility to be particularly careful in applying good software engineering principles.