Super loop software structures, also known as foreground-background systems, provides small microprocessor systems with no operating system, a rudimentary sense of multi-tasking. My original Super Loop notes are written for embedded 8088 assembly language applications. I have a version for 68HC11 assembly language as well as 68HC12 using 'C' and Metrowerks CodeWarrior. These notes are for the Freescale 9S12 processor, assuming use of the absolute assembler feature available with CodeWarrior.
Jean Labrosse points out that most high-volume low-complexity microcontroller applications use a structure like that in Figure 1. The tasks are collectively referred to as the task level. Interrupt service routines handle asynchronous events in what is called the interrupt level. In Figure 1, task 2 is temporarily suspended by an interrupt service routine (ISR). There are a few key points to remember: (1) Each task is really just a subroutine. (2) Each task is responsible for returning, to pass control of the CPU to the next task. (3) Following an ISR, control is always returned to the interrupted task.
Figure 1: Task and interrupt levels
The notion of a context is basically a list of things associated with a running program or task and is what the CPU knows at a given moment. At the very least, the context is the list of CPU register values and the stack. An operating system switches between actual tasks by saving the current context and replacing it with another. In a super loop however, execution in the task level passes from subroutine to subroutine in a simple way, within a single context. A super loop does not provide an actual context switch between tasks. It is for this principal reason that super loop applications are not regarded as truly multitasking.
The DELAY block in Figure 1 is optional, it provides a measure of control over execution time. In general terms, the time to execute one iteration of a super loop, including the DELAY is called the overall loop time. A delay associated with an independent hardware timer can be used to provide super loop applications with particularly reliable timing. Such a delay mechanism is referred to here as a delay gate as it prevents the task code from executing, until the hardware timer counts out.
Consider the design of a digital alarm clock. The clock has push buttons that the user manipulates to set the current time, alarm time, display mode, and enable or disable the alarm. Dividing the alarm clock program into tasks helps in that each task is relatively simple and understandable. The following is a list of tasks that might be useful in designing a digital alarm clock:
- Display Task
- Depending on display mode, display either the current time or the alarm time
- Time Update Task
- Updates the current time, when not being set by the user
- Button State Task
- Detect valid button closure and release events
- Alarm Task
- Manage the alarm and alarm state
In the alarm clock example, the display is updated constantly. For the pushbuttons to behave normally the period between the times the pushbuttons are examined is in the neighborhood of 100ms or less. The time of day however is updated once per minute. To allow such a variation in time scales, we will allow each task to forgo performing work. Hence the overall loop time serves as a basic time quanta and the period of each periodic task is a multiple of the overall loop time.
The design of an actual alarm clock is complicated, but we can see that there is a mixture of repeating and non-repeating tasks. Without losing any sense of generality, let's consider a program that we refer to as the blink-and-beep example. This example was inspired by the examples presented in Peatman. The example presented here blinks an LED, on for 0.5 seconds and then off for 0.5 seconds. Each time the LED changes state, a beep sound is produced for 0.1 seconds using a speaker. The delay task is controlled by the real time clock in the 9S12 procesor. Our 9S12 boards each have a 8MHz crystal, causing the bus cycle E-clock to be 4 MHz. The real time clock is programmed here for a 1.024 ms delay.
- LED Task
- Blink an LED, on for 0.5 second, off for 0.5 second
- SPK Task
- Following a change in the LED state, produce a beep for approximately 0.1 second.
Each task has a defined relationship and dependency on other tasks in the system. The delay gate, which is linked to a separate timer deserves special attention. Given that the average time for the tasks in the task level to do work is less than the timer period, the overall loop time will be constant. If the tasks finish work in little time, there is a long delay wait. Otherwise in cases where the tasks consume considerable time, there is a very short delay wait.
Calling a task simply gives it an opportunity to execute, or in other words, perform useful work. However many tasks do not execute at every opportunity. When a task does execute, it is also said to be triggered. This notion of allowing tasks to forego execution is a good thing as it allows the overall loop time to be small and yet allows tasks to have a range of time scales.
In the blink-and-beep example the LED state is toggled every 0.5 seconds. A simple counter variable provides a means to trigger a task in a periodic fashion. Peatman uses such a counter like that in the figure below. The name LedCount refers to the timer count and is decremented by one each time the task is called. Once the counter reaches zero the maximum value is assigned to the variable and the task's work is performed.
Figure 2: Flow inside a periodic task
A task named LedTask resembling the flowchart in Figure 2 is responsible for controlling the LED. This task performs work once every 488 calls, or approximately every 0.49971 seconds. Each time the LED is toggled, a flag is set to request that a beep sound be made. A second task named SpkTask produces a beep sound by providing periodic and non-periodic behavior. Work for such a task involves toggling the state of a pin or pins attached to a speaker, over a brief time interval, producing an effect that our ears recognize as a beep sound. In Figure 3, a non-zero flag indicates a request made for a beep sound. The task clears the flag and assigns a new value to the counter. In the situation where the task is not performing work, 'other work' is performed to silence the speaker.
Figure 3: Flow inside a task that makes a beep
In the example program, the first few lines are just opening comments and equates. Get into the habit of starting all your programs with comments that includes the name of the file, your name, the date, and a brief description of what the program does. Equates are used to assign a constant value to a name, called a symbol. Using symbols helps to make a program more readable. The next few lines define variables.
;************************************************************* ;* BlinkBeep - main.asm - Krista Hill - Nov.2, 2009 ;* The blink and beep super loop example. This code is ;* written assuming the use of Background Debug Mode. ;************************************************************* XDEF Start ; export symbols ABSENTRY Start ; required for absolute assembly INCLUDE "mc9s12c32.inc" LED_MAX: EQU 488 SPK_MAX: EQU 96 ORG $0800 ; section for data in RAM LedCount: DS.W 1 ; Delay count for LED SpkCount: DS.W 1 ; Count for speaker SpkReq: DS.B 1 ; request for a beep
The next part executes only once when the program starts, its job is to initialize the stack and then call a subroutine to initialize variables and devices. Pin 7 in port T is used to control the LED and pins 6 and 5 in port T are used to control the speaker, which is in series with a current limiting resistor. The SysInit subroutine is given further below.
ORG $4000 ; section for code, constants Start: lds #$1000 ; initialize the stack pointer jsr SysInit
The main loop in the program is surprisingly simple, just a series of subroutine calls that repeat. In limiting the dependencies between tasks, there is little danger in shutting down a task by simply commenting it out in the source code.
; ; The Main Loop ; Top: jsr LedTask ; update the LED jsr SpkTask ; update the Speaker jsr Delay ; bra Top ; all done for now
The task controlling the LED matches the flowchart in Figure 2. In most cases when this code is called, the first bne instruction passes execution to the rts instruction. The exclusive-or instruction is used to toggle the LED state.
The task controlling the speaker matches the flowchart in Figure 3. Notice that by inserting blank lines, to form three blocks of instructions helps us to read the code and see the correspondence with the flowchart. Please use techniques like this to make your programs more readable.; ; The LED Task ; LedTask: ldx LedCount ; get the count dex ; decrement stx LedCount ; save value bne LedDone ; exit from work? ldx #LED_MAX ; Reset the LED count stx LedCount ; to maximum ldaa #$80 ; LED bit eora PTT ; toggle the LED staa PTT ; save LED value ldaa #$FF ; Value to make a staa SpkReq ; make a request LedDone: rts ; done for now
; ; The Speaker Task - Beep when asked to ; SpkTask: ldx SpkCount ; test the count bne SpkNext ; update speaker? tst SpkReq ; test the request bne SpkStart ; start new beep? ldaa PTT ; get bits anda #$9F ; claer spk bits staa PTT ; update output rts ; done for now SpkNext: dex ; decrement count stx SpkCount ; update ldaa PTT ; Speaker bits eora #$60 ; toggle the speaker staa PTT ; save new speaker value rts ; done for now SpkStart: clr SpkReq ; clear the request ldx #SPK_MAX ; restart the count stx SpkCount ; value and ldaa PTT ; get the port oraa #$20 ; make a spk bit high staa PTT ; perform update rts ; done for now
This example uses the real time clock to control the overall loop time. The real time clock is really just a counter that sets a flag named RTIF, in a periodic fashion, whether or not the flag is cleared. This program does not use interrupts, rather the delay subroutine is a tight loop that repeatedly inspects the real time clock status. The moment RTIF becomes high, the subroutine clears the flag and exits.
; ; Delay Gate - wait for RTIF flag bit ; Delay: ldaa CRGFLG ; get status anda #$80 ; test RTIF beq Delay ; still low? staa CRGFLG ; restart rts ; done for now
The SysInit subroutine is essentially a collection of instructions used to intialize things. Also, with absolute assembly code the reset vector must be provided.
; ; SysInit - Initialize the system ; ; Contfigure clock for 1.024 ms delay SysInit: movb #$00,CLKSEL movb #$00,PLLCTL movb #$40,RTICTL ; Configure port T pin 7,6,5 as outputs movb #$E0,DDRT ; Initial count values movw #LED_MAX,LedCount movw #$00,SpkCount rts ; ; Reset vector ; ORG $FFFE ; section for a constant DC.W Start ; Reset Vector
The Achilles heel of super loop structures involves the overall loop timing. All the tasks in a super loop are dependent on each other for timely behavior. For the overall loop timing to be constant, the timing of all tasks must be somewhat predictable. In this example the real time clock is said to be periodic with period T in that the RTIF bit is set every 1.024 ms, regardless of when it is cleared. Thus, if the tasks execute in 0.024 ms then the delay subroutine waits for 1.000 ms. If the tasks execute in 0.994 ms then the delay subroutine waits for 0.030 ms. The point is that if the time to execute the tasks is less than the period T or 1.024 ms in our example, then the overall loop time is constant.
Before invoking an ISR the context is saved on the stack, so that when the ISR returns, execution can return to the interrupted code, as if nothing happened. While the interrupt mechanism provides a tempting means to approximate multitasking, actually using an ISR in this way undermines one reason for having interrupts in the first place; to provide a immediate response to external events. In a nutshell, any time spent in an ISR cannot be used to service other interrupts.
To reduce so-called interrupt bloat, it is common to begin the handling of an external event at the interrupt level and then complete the handling at the task level. Information that is handled by the combination of an ISR and a task, or just by a task will not be fully processed until the intended task is executed. Labrosse defines the term task level response to be the time for a task to respond to an external event. The worst case task level response time is the overall loop time plus the additional time to execute the ISR, if there was one. Hence, for a reasonable task level response time, the overall loop time should be small.
There are many possible mechanisms for triggering a task. A so called flagged task might be triggered by an interrupt service routine (ISR) or another task. The ISR can acts like a first responder which starts the work of responding to an event but the ISR then assigns a non-zero value to a global variable that serves as a flag to request that a task complete the work. In recognizing such a request, the task in Figure 5 is triggered once.
Figure 5: Flow inside a one-shot task
In closing, the super loop structure provides a simple outline for developing real time software for low complexity microcontroller based applications. In truth, such a structure is ideally suited to microcontrollers that are limited in memory and don't allow the programmer to have access to the stack. Despite its strengths, the super loop structure has numerous weaknesses which you will discover. Such weaknesses tend to cause super loop structures to take on a spaghetti appearance.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.
The BlinkBeep example program beeps the speaker every time the LED changes state. Consider how to change the LedTask subroutine so that a beep is produced only when the LED is turned on. Describe the change in words and submit a printout or handwritten copy of the new LedTask subroutine.
Consider how to change the LedTask task in the BlinkBeep example so that a beep is produced only when pin 7 in port A, used as an input is high. Describe the change in words and submit a printout or handwritten copy of the new LedTask subroutine.
In the example program, LedTask starts a beep by setting the SpkReq flag. In looking at the SpkTask subroutine it appears that all it does is set a counter value and set the speaker pin values a certain way. To simplify the program you might be tempted to remove the SpkReq global variable and allow the LedTask to directly manipulate 'things' to start a beep. Think of and justify in a few sentences at least one reason why making such a change is a bad idea.
Determine the the amount of time for which a beep is made. Don't bother counting instruction cycles. Assume the time spent waiting in the delay subroutine is very long compared to the rest of the program, so the overall loop time is constant. You may assume that the beep starts at the moment the speaker pins are non-zero and stops when the speaker pins become zero.
What is the significance of the following instruction in the SpkTask code in the BlinkBeep example program? How would the behavior of the program change if the instruction was removed? Give a clear explanation in a few sentences.
The SpkReq flag is used to request that the SpkTask subroutine start producing a beep. In a few brief sentences describe what will happen if SpkReq flag is set while SpkTask is currently producing a beep. In particular describe what happens to the value in SpkReq and for how long a beep is actually made.
In general terms it is considered a bad idea to include a spin lock or delay loop inside a super loop task. Consider a subroutine that waits for a character to be transmitted, in RS232 style serial form. Assume that each ASCII character being transmitted has a start bit, 8 data bits, and a stop bit, comprising ten bits. The Baud Rate is the the inverse of the Baud Period, which is the time to transmit each such bit. Assume the serial port is transmitting at 300 Baud. Approximately how much time does it take to send one character and how does this compare with the overall loop time in the BlinkBeep example program?
The real time clock is periodic in that the RTIF bit is set every T seconds, regardless of when it is cleared. Consider a super loop structure where the real time clock period is 4.096 ms. Suppose that for every iteration the average amount of time for the tasks is 2 ms but that one loop iteration out of every one hundred must perform an excessive amount of work, executing in 5 ms. Indicate whether or not the overall average loop time will be 4.096 ms. Justify your answer with a few sentences and if it helps, include a diagram to support your answer.
In the BlinkBeep example program, the LedTask subroutine communicates a request that SpkTask produce a beep by setting the SpkReq flag. Once set, SpkTask clears the flag and starts producing a beep. This dependency between tasks is called a simple agreement. One task only sets a flag and the other only clears the flag. Briefly suppose that in certain cases that LedTask needs to revoke a previously made request. Would it be enough for LedTask to clear SpkReq? Use a few sentences to justify your answer.
Please Let me know that you read my web pages.This document is written for ECE332, the Microprocessor Applications course offered by the ECE department in the College of Engineering at the University of Hartford. Copies of this document may be made gratis for educational purposes only, provided that this statement remains intact. The author retains the copyright on this document.