UHCOE ECE332: Super Loops in 9S12 Assembly Language

Super Loops in 9S12 Assembly Language


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.

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

Outlining Examples

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.

Triggering a Periodic Task

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.

task flowchart 1
Figure 2: Flow inside a periodic task

Semiperiodic 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.

task flowchart 3
Figure 3: Flow inside a task that makes a beep

Blink-and-Beep Source Code

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 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 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 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
; Reset vector
          ORG    $FFFE   ; section for a constant
          DC.W Start     ; Reset Vector

The Fundamental Assumption

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. 

Figure 4: Time scale showing work, delay, and loop period T

Working Along with Interrupts

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.

task flowchart 2
Figure 5: Flow inside a one-shot task

Closing and References

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


  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

        clr  SpkReq

  6. 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.

  7. 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?

  8. 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.

  9. 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.

Copyright and Revision Date

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.
Author: Krista Hill - kmhill at hartford dot edu
Copyright: Tue Nov 3 10:43:20 EST 2009
Last Revised: Wed Nov 4 20:00:55 EST 2009