The Smallest Software Machine (Part 1)

The Smallest Software Machine (Part 1)

Patrick Schaumont bio photo By Patrick Schaumont

Near the conclusion of our ECE 2534 Micro course, I spent several lectures on the big ideas in micro-controller hardware and in micro-controller programming. As a follow up to my earlier post on the difficulty of using interrupts for programming, I’ll rewrite that story in a more comprehensive manner, considering the task of writing complete programs for a micro-controller.

The term ‘program’ is a rather bland term for micro-controller software. A micro-controller application requires much more than just an algorithm. I’ll use the term ‘software machine’ in this post. A software machine is a logical machine that drives the hardware forward towards reaching the objective of the application.

There are three basic species of software machines, and each of them has a different level of complexity. From the most simple to the most complex, I call them (1) the event machine, (2) the event sequence machine and (3) the threaded event sequence machine. Mind you that the type of micro-controller software discussed here is compact by nature. Even the ‘most complex’ of our species is still pretty simple compared to the architecture of a full operating system.

Let’s look at the features of each of the three machines, using examples for an MSP430 Launchpad.

The event machine

A Simple Event Engine

So what does software machine do for a micro-controller? Fundamentally, the purpose of micro-controller software is to be the logic glue between the hardware peripherals and the application. The software responds to activities originating from the hardware, and the software generates results that eventually are processed by the hardware. With ‘hardware’, I mean things like parallel input/output ports (PIO), timers, serial interfaces (UART, SPI, I2C), analog-to-digital converters (ADC), and more.

Activities in the hardware happen in real time, and they include things such as a level change on a micro-controller pin, a character received on a communications link, a timer expiring, and so forth. We’ll use the term event to describe such a change in hardware. A fundamental property of an event is that it is bound to something happening in real time.

The software must respond as quickly as possible to hardware events. Virtually every application with a micro-controller has this real-time aspect. When you flick a light switch, you expect the light to turn on right away, not a few hundred milliseconds later!

A classic technique to create the illusion of instant response is to write software in a tight loop that repeatedly scans for input events from the hardware, and that returns output data to the hardware in response to those input events. This happens very fast, such that a real-time input will never be missed. This tight loop is called the cyclic executive.

The following is an example of a cyclic executive for an application for an MSP430FR994 Launchpad. The application scans two input buttons and drives two LEDs in response. The buttons and LED are mapped into GPIO ports.

   #include <driverlib.h>
 
   int main(void) {

     WDT_A_hold(WDT_A_BASE);     
     
     GPIO_setAsOutputPin(GPIO_PORT_P1, GPIO_PIN0); // LED 1
     GPIO_setAsOutputPin(GPIO_PORT_P1, GPIO_PIN1); // LED 2
     GPIO_setAsInputPinWithPullUpResistor (GPIO_PORT_P5, GPIO_PIN6); // PB 1
     GPIO_setAsInputPinWithPullUpResistor (GPIO_PORT_P5, GPIO_PIN5); // PB 2
     PMM_unlockLPM5();
 
     while(1) {
        if (GPIO_getInputPinValue(GPIO_PORT_P5, GPIO_PIN6) == 0)
            GPIO_setOutputHighOnPin(GPIO_PORT_P1, GPIO_PIN0);
        else
            GPIO_setOutputLowOnPin(GPIO_PORT_P1, GPIO_PIN0);
 
        if (GPIO_getInputPinValue(GPIO_PORT_P5, GPIO_PIN5) == 0)
            GPIO_setOutputHighOnPin(GPIO_PORT_P1, GPIO_PIN1);
        else
            GPIO_setOutputLowOnPin(GPIO_PORT_P1, GPIO_PIN1);
     }
   }

For the purpose of this example, we’ll focus on the overall software architecture. The main function, in this case, contains an initialization part and a while-loop (the cyclic executive). Inside of the while loop, the value of each button is scanned and a LED is set accordingly.

A subtle point is that the micro-controller executes the cyclic executive sequentially, which means that only a single button is read at a time, and a single LED is set or reset at a time. When you run the program, you don’t notice this because the while loop spins so fast. At a micro-controller frequency of 1 MHz, even a short button press will take several thousand iterations of the cyclic executive, each time reading the button status and setting the LED accordingly.

The above example is almost complete as an event engine, but it misses a crucial component. The event engine also needs to support interrupts from the hardware. The interrupts are there to allow hardware events to insert values for processing into the cyclic executive. An interrupt is handled in a Interrupt Service Routine (ISR), a software function separate from the cyclic executive. In order to feed data into the cyclic executive, the ISR has to make use of global variables.

The following example shows how this fits together. In this case, both buttons will drive the same LED, while the other LED is blinking periodically. The length of the period is defined by a timer peripheral. Every time it reaches the full 16-bit period, the timer hardware generates an interrupt which translates into the calling of an ISR.

   #include <driverlib.h>
   #include <timer_a.h>
   
   volatile unsigned timerExpired = 0;
   
   int main(void) {
     Timer_A_initContinuousModeParam tpA = {0};
    
     WDT_A_hold(WDT_A_BASE);
   
     GPIO_setAsOutputPin(GPIO_PORT_P1, GPIO_PIN0); // LED 1
     GPIO_setAsOutputPin(GPIO_PORT_P1, GPIO_PIN1); // LED 2
     GPIO_setAsInputPinWithPullUpResistor (GPIO_PORT_P5, GPIO_PIN6); //  PB 1
     GPIO_setAsInputPinWithPullUpResistor (GPIO_PORT_P5, GPIO_PIN5); // PB 2
     GPIO_setOutputLowOnPin(GPIO_PORT_P1, GPIO_PIN1);
   
     tpA.clockSource               = TIMER_A_CLOCKSOURCE_SMCLK;
     tpA.clockSourceDivider        = TIMER_A_CLOCKSOURCE_DIVIDER_16;
     tpA.timerInterruptEnable_TAIE = TIMER_A_TAIE_INTERRUPT_ENABLE;
     tpA.timerClear                = TIMER_A_DO_CLEAR;
     tpA.startTimer                = true;
     Timer_A_initContinuousMode(TIMER_A0_BASE, &tpA);
   
     __enable_interrupt();
     PMM_unlockLPM5();
   
     while(1) {
       if ((GPIO_getInputPinValue(GPIO_PORT_P5, GPIO_PIN6) == 0) ||
           (GPIO_getInputPinValue(GPIO_PORT_P5, GPIO_PIN5) == 0))
         GPIO_setOutputHighOnPin(GPIO_PORT_P1, GPIO_PIN0);
       else
         GPIO_setOutputLowOnPin(GPIO_PORT_P1, GPIO_PIN0);
   
       if (timerExpired) {
         GPIO_toggleOutputOnPin(GPIO_PORT_P1, GPIO_PIN1);
         timerExpired = 0;
       }
     }
   }
   
   #pragma vector = TIMER0_A1_VECTOR
   __interrupt void Timer_A (void) {
      timerExpired = 1;
      Timer_A_clearTimerInterrupt(TIMER_A0_BASE);
   }

When the timer reaches its full period, the timer interrupt calls an ISR. The ISR sets a flag timerExpired to indicate the timer interrupt occured, clears the timer interrupt flag, and terminates. The timerExpired is a global, defined as a volatile unsigned variable. The volatile part ensures that any reference in the C program to timerExpired will always refer to the variable as stored in RAM, and not to some intermediate copy of it in a register.

Putting everything together, we now have all the elements of an event machine: it includes a cyclic executive, and optionally interrupt service routines that send data into the cyclic executive through global variables. A graphical model of the event machine is shown in the figure at the top of this post.

It is very tempting to add functionality inside of the ISR. However, this would break the concept of the event machine. In a previous post, I argued against the use of interrupts as a programming technique, and I still think this is very much true. Interrupts and ISRs are only there to enable capturing of hardware events and to annotate global state for the software. All processing in response to the hardware event belongs in the cyclic executive.

In the next part, I’ll extend this model into an ‘event sequence machine’, a machine that can recognize sequences of events rather than single events.