EE4801: Super Loop Structure


Introduction

Small systems of low complexity are generally designed as a loop structure that repeatedly calls a set of procedures. In this context each procedure is referred to as a task and collectively the procedures are referred to as the task level. Interrupt service routines handle asynchronous events in what is called the interrupt level. The figure below illustrates the general idea of how execution progresses. The figure shows that task 2 is temporarily suspended by an interrupt service routine (ISR). When an ISR exits, execution will return to the suspended task.

task and interrupt levels
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.

task flowchart
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.

Example System

This example was inspired by that first example presented in chapter 3 of Peatman. The example presented here not only toggles a pair of LEDs at a 1Hz rate, but also makes a speaker beep each time the LEDs change state. A periodic task named ToggleLEDs drives the LEDs.  The two lowest bits of the I/O port at 00h each cause an LED to become lit when logic low.  A one-shot delay triggered task controls the speaker, a 300 ohm resistor in series with the speaker connects to the two lowest bits of the 01h I/O port.

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.

References

J.J. Labrosse's MicroC/OS text provides background in real-time operating systems and includes source code and examples.  He presents only a few comments regarding super loop structures. J.B. Peatman provides an example in chapter 3 for the PIC that is very similar to the blink circuit presented here, also an example in chapter 4 uses timers and an interrupt to provide more reliable timing.

Copyright and Revision Date

This document is written for EE4801, the advanced undergraduate class in microprocessor based design offered by the ECE department at Worcester Polytechnic Institute (WPI). Copies of this document may be made gratis for educational purposes only, provided that this statement remains intact. The WPI ECE department retains the copyright on this document.
Author: Jonathan Hill - jmhill@ece.wpi.edu
Last Revised: Thu Jun 28 16:50:47 EDT 2001