UHCOE ECE532: Super Loop Structure in 'C'

UHCOE ECE532: Super Loop Structure in 'C'



Introduction

I began my study of the Super Loop software structure, also known as foreground-background systems when I taught a microprocessor course at Worcester Polytechnic Institute.  My original Super Loop notes assume that students are using 8088 Assembly Language in an embedded application.  The following notes assume that students are programming in 'C' using Metrowerks CodeWarrior for the Motorola 68HC12 microcontroller.

Labrosse points out that most high-volume low-complexity microcontroller based applications such as microwave ovens, toys, and the like use a loop structure like that described here, that repeatedly calls a set of functions in order.  Each function is referred to as a task and collectively the functions 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.  Here, task 2 is temporarily suspended by an interrupt service routine (ISR). 

task and interrupt levels
Task and interrupt levels

The notion of a context is basically a list of things associated with a running program or task and is basically what the CPU knows at a given moment.  At the very least, the context is the stack and the list of CPU register values.  An operating system switches between tasks by changing the context, that is the current context is saved and is replaced with another.  In a super loop however, execution in the task level passes from function to function in a simple way, within a single context.  It is important to understand that 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 by restoring it's context.

The DELAY block is optional, it provides a measure of control over execution time.  A simple delay simply slows the execution rate.  A delay associated with an independent timer can be used to provide reliable timing, as we shall see.  To reduce so-called interrupt bloat, it is common to begin handling of external events at the interrupt level and then complete the handling in a task at the task level. 

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 simpler and better understood.  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 the current time is not being set
 
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

When the 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 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 actually increments the time once in every 600 calls.

Information that is handled by an interrupt service routine and a task, or just by a task will not be fully 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 is the overall loop time plus additional time to execute interrupt service routines.  This notion of allowing tasks to forego execution is a good thing as it allows the overall loop time to be moderately small. 

A simple counter 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 super loop repeats.  Once the counter reaches zero the timer is reset and the task's work is performed.

task flowchart 1
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 a request, the task below is triggered once.

task flowchart 2
Flow inside a one-shot task

Producing a beep sound calls for periodic and non-periodic behavior.  Work for such a task involves toggling the state of a pin or pins attached to a speaker, producing an effect that our ears recognize as a beep sound.  A non-zero flag indicates a request; 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 required to silence the speaker.

task flowchart 3
Flow inside a beep 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 68HC12.  The board I use has a 16MHz crystal.  The real time clock is programmed for the shortest delay, approximately 1.024ms.  The following are the tasks:
UpdateLED
This task toggles the LED once for every 488 calls, or approximately every 0.4997 seconds.  Each time the LED is toggled, a flag is also set to request that a beep sound be made.
 
UpdateBeep
This task generates a beep sound by toggling speaker pins every time the task is called until a counter elapses.  A flag is used to request that the task start producing a beep sound.

The Achilles heel of super loop structures involves the overall timing of the overall loop.  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 1.024ms, regardless of when it is cleared.  Thus, if the tasks execute in 0.024ms then the delay waits for 1ms.  If the tasks execute in 1.020ms then the delay waits for 0.004ms. If the total amount of time to execute the tasks is less than 1.024ms then the overall loop time is constant. 

It is recommended that the blink-and-beep example be constructed using the Metrowerks stationary provided with the 'Hello' project.  The following includes file contains the declarations and prototypes.

/*********************************************
 * includes.h - Krista Hill - Feb 21, 2004
 * Collect definitions and includes 
 ********************************************/
#include <mc68hc912b32.h>
#define  P_CLOCK_FREQ  8000000L

// Constants
#define LED_COUNT_MAX  488
#define BEEP_COUNT_MAX 100

// Function prototypes
void UpdateLED(void);
void UpdateBeep(void);
void DelayGate(void);

/* end of includes.h */

The following is the main file.  After the real time clock and DLC port are initialized, the main program loop begins.  Rather than using a constant value to control the main while-loop, the variable done is used in a simple trick to prevent a compiler warning.  The UpdateLED task is triggered periodically and the UpdateBeep task starts triggering with a flag.

/**************************************************
 * BlinkBeep - main.c - Krista Hill Feb 21, 2004
 * A demonstration of a super loop application.  
 * Blinks an LED and produce a beep sound
 *************************************************/
#include "includes.h"

// Global variables
int BeepFlag = 0;

// The main loop
void main(void)  {

  int done = 0;
  RTICTL  = 0x01; //set RTI timer to minimum delay
  PORTDLC = 0x00; //initialize port register
  DDRDLC  = 0x07; //set pin to output
  
  while (!done) { // Perform tasks in order
    UpdateLED();
    UpdateBeep();
    DelayGate();
  }
}

/**************************************************
 * UpdateLED()
 *
 * When the LED count runs out, toggle and beep
 *************************************************/
void UpdateLED(void)
{
  static int LedCount = LED_COUNT_MAX;
  
  if (--LedCount == 0) {
    BeepFlag = 1;
    LedCount = LED_COUNT_MAX;
    PORTDLC ^= 0x01;
  }
}

/**************************************************
 * UpdateBeep()
 *
 * If the Beep is started, then update the speaker
 *************************************************/
void UpdateBeep(void)
{
  static int BeepCount = BEEP_COUNT_MAX;
  if (BeepCount != 0) {	    // Active - Toggle spkr
    BeepCount--;
    PORTDLC ^= 0x06; }
  else if (BeepFlag != 0) { // Request - Start beep
    BeepFlag = 0;
    BeepCount = BEEP_COUNT_MAX;
    PORTDLC |= 0x04; }
  else {                   // Off - Silence speaker
    PORTDLC &= 0xF9;
  }
}

/**************************************************
 * DelayGate
 *
 * Wait for timeout, then restart the RTI clock
 *************************************************/
void DelayGate(void)
{
  while (RTIFLG == 0) ;
  RTIFLG = 0x80;
}

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 stregths, the super loop structure has numerous weaknesses which you will undoubtably discover.  Such weaknesses tend to cause super loop structures to take on a spaghetti apperance.

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.

Questions

  1. The blink-and-beep example beeps the speaker every time the LED changes state.  Consider how to change the UpdateLED task to 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 UpdateLED task.

  2. Consider how to change the UpdateLED task in the blink-and-beep example so that a beep is produced only when pin 3 in the DLC port, used as an input is high.  Describe the change in words and submit a printout or handwritten copy of the new UpdateLED task.

  3. In the example program the UpdateLED task starts a beep by setting the BeepFlag.  In looking at the UpdateBeep task 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 BeepFlag global variable and allow the UpdateLED 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. Calculate the the amount of time for which a beep is made.  You may assume that the overall loop time is 1.024ms and 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 line of code in the function UpdateBeep(void) in the blink-and-beep example program?  How would the behavior of the program change if the line were commented out?

        BeepFlag = 0;
        

  6. The BeepFlag flag is used to request that the UpdateBeep task start producing a beep.  In a few brief sentences describe what will happen if BeepFlag is asserted while the UpdateBeep task is currently producing a beep.  In particular desribe what happens to the value in BeepFlag and for how long a beep is made or not.

  7. Consider the significance of what would happen if the delay task in the example beep-and-blink example was replaced with a simple do-nothing delay loop that simpy provides a constant delay.  Would the overall loop time be more or less predictable.  Justify your answer with a few sentences.

  8. In general terms it is considered a bad idea to include a spin lock or delay loop inside a super loop task.  The code in bxlib is said to be blocking as the while-loop in bx_putchar() will wait indefinitely for the UART to be ready to receive a character.  For the blink-and-beep example calculate the worst case and average loop time if the UpdateLED task were to call bx_putchar() to transmit an asterisk ( '*' ) every time the LED blinks.  Assume serial port is communicating at 300 Baud.  Assume that other than the obvious delays, the 'C' executes instantaneously.

  9. The real time clock is periodic in that the RTIF bit is set every T seconds, regardless of when it is cleard.  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 brief paragraph and if it helps, include a diagram to support your answer.

  10. In the blink-and-beep example, the UpdateLED task communicates a request that the UpdateBeep task produce a beep by setting the BeepFlag flag.  Once set, the UpdateBeep task 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 the UpdateLED task needs to revoke a previously made request.  Would it be enough for the UpdateLED task to clear BeepFlag?  If so, justify your answer in a brief paragraph.  If not, outline in a brief paragraph a minor improvement that would work.

Copyright and Revision Date

Please Let me know that you read my web pages.

This document is written for ECE532, the embedded microprocessors 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@hartford.edu
Copyright: Sat Feb 21 22:37:22 EST 2004
Last Revised: Tue Feb 21 15:56:01 EST 2006