Lecture: 34
Objective: An introduction to Real Time Operating Systems.
Extra: Salvo
Code: vendingMachine.c

Embedded Software Architectures

The architectures are driven by the need for response time.

Round-Robin (or superloop)

A main loop checks each of the I/O devices and services each in a prescribed order.
void main() {
    init();
    while(1) {
	task1();
	task2();
	...
}   }
Example A digital multimeter which checks the position of a switch, reads a value from a proble, performs an ADC conversion and then displays the result on an LCD is such an example.

Advantage Works well when there are few I/O devices, no lengthy processing, and no tight response requirements.

Disadvantage If any device has a response time which is less than the time required to get around the superloop. If any of the tasks requires length processing. Modification made to meet requirements results in a fragile architecture.

Round-Robin with interrupts

A main loop checks each of the I/O devices and services each in a prescribed order. Interrupts are used to deal with the time constrained I/O devices.
int8 data_for_device_A;

void main() {
    init();
    while(1) {
	task1();
	task2();
	...
	if (global_flag_A) taskA();
}   }

void ISR_deviceA() {
    service_A(data_for_device_A);
    set(global_flag_A);
}
Example 36 position rotary encoder which selects which function to perform on a DS1302 real time clock. Assume that it takes 100mS to read the time from the DS1302. We assume that we want to be able to monitor the rotary encoder when it is turned slower than 1 rotation per second. This means that we must examine the rotary encoder at least 36 clicks/sec * 4 detents / click = 144 detents / sec or 7mS / detent. Thus, we need to put either the DS1302 or the rotart encoder onto an interrupt so that we can perform both tasks.

Advantage Simple.

Disadvantage Open to problems associated with shared data. All the tasks in main operate with the same priority. For example, a laser printer spends lots of time calculating where to put the tiny dots of ink. Main would then get "stuck" working on this task at the exclusion of all the other tasks. Moving the other tasks into ISRs is a solution, but then a low priority interrupt might take to long to service. In addition, if there were a pair of time consuming tasks then one of them would always have to wait for the other.

Shared Data Problem

As soon as we use interrupts to share information between an ISR and main we can create problems. The foundation for these problems arises from the fact that we don't want the ISR to perform all the work. Generally, we use the ISR to manipulate some I/O device and pass all the work of actually processing the information from the sensor to main.

The problems that can arise from sharing data between an ISR and main are shown in the following code snippet. In this example we are to monitor the temperature from two sensors and set off an alarm if these are different (we are to assume that this condition indicates that there is a problem with the sensors that someone needs to know about).
int8 iTemp[2];

void interrupt ReadTemp(void) {
    iTemp[0] = ReadSensor0;
    iTemp[1] = ReadSensor1;
}

void main(void) {
    int8 iTemp0, iTemp1;

    while(1) {
        iTemp0 = iTemp[0];
        iTemp1 = iTemp[1];
        if (iTemp0 != iTemp1)
            Set off the klaxon;
}   }
So where is the error? Well it arises out of a sequence of events.
  1. The temperature is changing.
  2. We have just finished executing the "iTemp0..." line in main
  3. The interrupt occurs and updates both values of iTemp[]
The alarm will sound even though there was no real error. Even worse this error will not be repeatable. This is a instance of a so-called Heisenberg. How about trying the following fix?
int8 iTemp[2];

void interrupt ReadTemp(void) {
    iTemp[0] = ReadSensor0;
    iTemp[1] = ReadSensor1;
}

void main(void) {
    int8 iTemp0, iTemp1;

    while(1) {
        if (iTemp[0] != iTemp[1])
            Set off the klaxon;
}   }
Well this really doesn;t work either. So how to we solve this problem. Well go the source. The interrupt is the real culprit, if it occurs when we are making the comparison then we can get a problem. The solution will consist of making a "critical section" around the comparison, not allowing interrupts to occur.
int8 iTemp[2];

void interrupt ReadTemp(void) {
    iTemp[0] = ReadSensor0;
    iTemp[1] = ReadSensor1;
}

void main(void) {
    int8 iTemp0, iTemp1;

    while(1) {
	disable_interrupts();
        iTemp0 = iTemp[0];
        iTemp1 = iTemp[1];
	enable_interrupts();
        if (iTemp0 != iTemp1)
            Set off the klaxon;
}   }

Interrupt Latency

If we enable and disable interrupts to solve the problems associated with the shared data problem then we will increase the interrupts latency; the time delay between the occurrence of an interrupting event and its being serviced. In some cases you will need to calculate the latency, so how can you do this? You need to know 4 things.
  1. The longest period of time that the interrupt is disabled.
  2. The length of time required to service interrupts at a higher priority level.
  3. The time required to enter an ISR. This is MCU book-keeping required to save the state of the MCU so that its not perturbed by the ISR.
  4. How long if takes the ISR to set itself up and then "service" the interrupting event.
Lets look at an example
  1. You have to disable interrupts for 125us for your task code to use a pair of temperature variables it shares with the interrupt routine that reads the temperatures from the hardware and writes them into the variables.
  2. You have to disable interrupts for 250us for your task code to get the tie variables from variables it shares with the interrupt routine that responds to the timer interrupt.
  3. It takes 10us for the PIC to switch contexts.
  4. You must complete a response within 625us when you get a special signal (an interrupt) from another processor in your system; the inter-processor interrupt routine takes 300us to complete.
This solution assumes that the inter-process communication is given the highest priority and there are no other interrupts with that interrupt priority. Always work these types of problems assuming a worst-case scenario from the current state of the MCU to the resolution of the interrupt. That is assume that we have just entered the portion of the foreground code which disables the interrupts. Interrupts are disabled for 250us, it takes 10us to switch to the inter-process ISR, and inter-process communications require 300us. Thus it takes 560us to service this requires, well within the 625us requirement. What if we assumed that all the interrupts were of the same priority?

Real Time Operating Systems

A RTOS allows a collection of independent tasks to simultaneously use the MCU. A task are small embedded systems programs with a superloop structure. Tasks have a priority which determines their relative importance to one another. Since we have only one processor, we know that only one of the tasks can actually be running at a time. Hence, the tasks will have to be in variety of states.

Non-preemptive Real Time Operating System

Advantage Simple to write a non-preemptive RTOS. Simple to program applications.

Disadvantage The longest delay to service a high priority event is the time required by the longest task. The RTOS cannot preempt any running task. Consequently a bug in one task may very well bring the entire system down. Using an RTOS consumes system resources (memory and MCU processing time).

Preemptive Real-Time Operating System

A preemptive RTOS can suspend one task to run another.

Advantage The response time of the system is stable if the code is changed.

Disadvantage Using an RTOS consumes system resources (memory and MCU processing time). They increase the delivery cost of your product.