UHCOE ECE332: Super Loops in 68HC11 Assembly Language

Super Loops in 68HC11 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 also have notes for the 68HC12 using 'C' and Metrowerks Code Warrior.  These notes are for the Motorola 68HC11, assuming use of an absolute assembler that largely follows Motorola's familiar 68HC11 syntax:

o The free-ware as11 assembler
o Peter Gargano's ASHC11 freely available assembler
o The THRSim assembler THRAss11 product

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).  A super loop is sometimes thought of as cooperative multi-taking for two reasons: (1) each task is responsible for passing control of the CPU to the next task, and (2) 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 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 reason alone 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.

It is important to understand that invoking an interrupt creates a temporary new context that exists only as long as the ISR executes.  When the ISR exits, execution is returned to the suspended task, destroying the ISR context.  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.  The actual details of interrupt handling are beyond the scope of this document and will not be discussed much further.

Outlining an Example

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
Delay Gate Task
Wait for timer timeout to provide accurate loop timing

Each of the above tasks can be thought of as a mini-program of sorts.  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

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.

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 deal with a range of time scales.  In the alarm clock example, for the pushbuttons to behave normally an overall loop time in the neighborhood of 100ms or less is needed.  The time however is updated once per minute.  Hence, given a 100ms overall loop time, the Time Update Task increments the number of minutes, once in every 600 calls.

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 variable timer is the 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

Starting a Flagged Task

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.  Any time spent by an ISR is time that the tasks and other interrupts cannot use, hence an ISR should interrupt for only a brief moment.  To reduce ISR bloat, it is common practice to start handling an external event in an ISR and triggering a task to later complete the handling at its first possible opportunity.  In recognizing such a request, the task in Figure 3 is triggered once.

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

The Blink-and-Beep Example

This example was inspired by the examples presented in Peatman.  The example presented here toggles an LEDs at a 1Hz rate and briefly beeps a speaker each time the LEDs change state.  The delay task is controlled by the real time clock in the 68HC11.  Our 68HC11 boards each have a 8MHz crystal, causing the bus cycle E-clock to be 2MHz.  The real time clock is programmed here for the shortest delay, approximately 4.096ms. 

A task named LedTask resembling the flowchart in Figure 2 is responsible for controlling the LED.  This task performs work once every 122 calls, or approximately every 0.4997 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 4, 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 4: Flow inside a task that makes a beep

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.

* blink.asm - Jonathan Hill - 11/7/2004
* Blink and Beep example for the 68HC11E9
* This is an example superloop application
* University of Hartford College of Engineering

LED_MAX:  EQU     122
SPK_MAX:  EQU     24
PORTA:    EQU     $1000
PORTB:    EQU     $1004
TFLG2:    EQU     $1025
RTIF:     EQU     $40
PACTL:    EQU     $1026

The next few lines define variables.  While Buffalo assumes the addresses $0000 to $0041, comprising 66 bytes are for the user stack, this application uses only a small part of it.  Since the stack grows downward, it is safe to use the first few bytes for variables.

          ORG     $0000
* The Data Section
LedCount  RMB     1
LedVal    RMB     1
SpkCount  RMB     1
SpkVal    RMB     1
SpkReq    RMB     1

The next section of code executes only once when the program starts, its job is to initialize the stack, variables, and devices.  Pin 4 in port A is used to control the LED and pins 0 and 1 in port B are used to control the speaker, which is in series with a current limiting resistor.  Since these port pins are outputs, the only device to be initialized is the real time clock, which is configured using the PACTL register.

* Program starting point
          ORG     $0100
Start:    lds     #$41

* Initialize the variables
          clr     LedVal   ; clear store for LED
          clr     SpkVal   ; clear store for speaker
          ldaa    #LED_MAX ; Initialize LED count
          staa    LedCount ; to maximim and
          clr     SpkCount ; turn off the speaker
          clr     SpkReq   ; clear the req flag

* Initialize ports and real time counter
          clr     PORTA    ; clear LED
          clr     PORTB    ; clear speaker
          ldaa    PACTL	   ; load control value
          anda    #$FC	   ; set RTI rate
          staa    PACTL	   ; update the register

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  ; run the LED task
          jsr     SpkTask  ; run the speaker task
          jsr     delay    ; wait for timeout
          bra     Top      ; back for more

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 function is used to toggle the LED state.

* The LED Task
LedTask:  dec     LedCount ; decrement LED count
          bne     LedDone  ; exit from work?

          ldaa    #LED_MAX ; Reset the LED count
          staa    LedCount ; to maximum

          ldaa    #$10     ; LED bit
          eora    LedVal   ; toggle the LED
          staa    LedVal   ; save LED value
          staa    PORTA    ; update output

          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 4.  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:  tst     SpkCount ; test the count
          bne     SpkNext  ; update speaker?
          tst     SpkReq   ; test the request
          bne     SpkStart ; start new beeo?
          clr     PORTB    ; clear the port
          rts              ; done for now

SpkNext:  dec     SpkCount ; decrement count
          ldaa    #$03     ; Speaker bits
          eora    SpkVal   ; toggle the speaker
          staa    SpkVal   ; save new speaker value
          staa    PORTB    ; update the speaker
          rts              ; done for now

SpkStart: clr     SpkReq   ; clear the request
          ldaa    #SPK_MAX ; restart the count
          staa    SpkCount ; value and
          ldaa    #$01     ; value to turn
          staa    SpkVal   ; on the speaker
          staa    PORTB    ; 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. 

* The delay gate - wait for clock to timeout
delay:    ldaa    TFLG2    ; get the flag register
          anda    #RTIF    ; mask out the timer flag
          beq     delay    ; and wait if not done

          ldaa    #RTIF    ; if done then clear the
          staa    TFLG2    ; flag and restart
          rts              ; done for now

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 in that the RTIFLG is set every 4.096ms, regardless of when it is cleared.  Thus, if the tasks execute in 0.096ms then the delay subroutine waits for 4ms.  If the tasks execute in 4.090ms then the delay subroutine waits for 0.006ms.  If the total amount of time to execute the tasks is less than 4.096ms then the overall loop time is constant. 

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 blink.asm 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 blink.asm 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 function SpkTask(void) in the blink.asm example program?  How would the behavior of the program change if the instruction was removed?  Give your answer 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.  The Buffalo subroutines that handle output, described in Part 3 of Introduction to Buffalo are said to be blocking as they must wait for the serial port transmitter to complete work.  Assume that each byte being transmitted, also has a start bit and a stop bit, comprising ten bits.  The Baud Rate is the the inverse of the Baud Period, which is the time to transmit one such bit.  Assume the serial port is communicating at 300 Baud.  Approximately how much time does it take to send one character and what percentage difference is this, compared with the overall loop time in the blink.asm 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.096ms.  Suppose that for every iteration the average amount of time for the tasks is 2ms but that one loop iteration out of every one hundred must perform an excessive amount of work, executing in 5ms.  Indicate whether or not the overall average loop time will be 4.096ms.  Justify your answer with a few sentences and if it helps, include a diagram to support your answer.

  9. In the blink.asm 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: Jonathan Hill - jmhill at hartford dot edu
Copyright: Sun Nov 7 23:27:52 EST 2004
Last Revised: Sun Nov 7 23:52:35 EST 2004