0% found this document useful (0 votes)
164 views60 pages

Embedded Real Time

The document discusses identifying and solving problems with real-time constraints in embedded C code. It describes issues with switch interface code that has no definite time duration and could cause catastrophes. It then introduces using timers 0 and 1 to create hardware delays, setting the TCON and TMOD registers. Problems with hardwired delays are noted around changes to oscillator frequency and ensuring delays are portable. The need for timeout mechanisms to prevent code from hanging indefinitely is also discussed.
Copyright
© Attribution Non-Commercial (BY-NC)
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
164 views60 pages

Embedded Real Time

The document discusses identifying and solving problems with real-time constraints in embedded C code. It describes issues with switch interface code that has no definite time duration and could cause catastrophes. It then introduces using timers 0 and 1 to create hardware delays, setting the TCON and TMOD registers. Problems with hardwired delays are noted around changes to oscillator frequency and ensuring delays are portable. The need for timeout mechanisms to prevent code from hanging indefinitely is also discussed.
Copyright
© Attribution Non-Commercial (BY-NC)
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 60

Embedded C (contd.

)
Week 4+5 Real Time Problems

Meeting Real Time Constraints

Identifying problems with some simple switch-interface code


bit SWITCH_Get_Input(const tByte DEBOUNCE_PERIOD) { tByte Return_value = SWITCH_NOT_PRESSED; if (Switch_pin == 0) { // Switch is pressed // Debounce just wait... DELAY_LOOP_Wait(DEBOUNCE_PERIOD); // POTENTIAL PROBLEM // Check switch again if (Switch_pin == 0) { // Wait until the switch is released. while (Switch_pin == 0); // POTENTIAL CATASTROPHE Return_value = SWITCH_PRESSED; } } // Now (finally) return switch value return Return_value; }

problems

No definite time duration Failed entry/user may cause catastrophes

Creating hardware delays using Timer 0 and Timer 1


We need the features of: The TCON SFR; The TMOD SFR; and, The THx and TLx registers.

Timer
We calculate the required starting value for the timer. We load this value into the timer. We start the timer. The timer will be incremented, without software intervention, at a rate determined by the oscillator frequency; we wait for the timer to reach its maximum value and roll over. The timer signals the end of the delay by changing the value of a flag variable.

The TCON SFR

TR0, TR1 Timer run control bits These bits need to be set to 1 (by the programmer) to run the corresponding timer (TR0 controls Timer 0, TR1 controls Timer 1). If set to 0, the corresponding timer will not run. Default value (after reset) is 0. TF0, TF1 Timer overflow flags These bits are set to 1 (by the microcontroller) when the corresponding timer overflows. They need to be manually cleared (set to 0) by the programmer. Default value (after reset) is 0.

TMOD SFR

The main thing to note is that there are three modes of operation (for each timer), set using the M1 and M0 bits. We will only be concerned in this book with Mode 1 and Mode 2, which operate in the same way for both Timer 0 and Timer 1, as follows: Mode 1 (M1 = 1; M2 = 0) 16-bit timer/counter (with manual reload)23 Mode 2 (M1 = 0; M2 = 1) 8-bit timer/counter (with 8-bit automatic reload) The remaining bits in TMOD have the following purpose: GATE Gating control We will not use gating control in this book. We will ensure that this bit is cleared, which means that Timer 0 or Timer 1 will be enabled whenever the corresponding control bit (TR0 or TR1) is set in the TCON SFR. C / T Counter or timer select bit We will not use counter mode in this book. We will ensure that this bit is cleared (for the appropriate timer), so that timer mode is used.

TH1 and TL1 settings

/*------------------------------------------------------------*Hardware_Delay_50ms.C (v1.00) ------------------------------------------------------------A test program for hardware-based delays. -*------------------------------------------------------------*/ #include <reg52.h> sbit LED_pin = P1^5; bit LED_state_G; void LED_FLASH_Init(void); void LED_FLASH_Change_State(void); void DELAY_HARDWARE_One_Second(void); void DELAY_HARDWARE_50ms(void); /*............................................................*/ void main(void) { LED_FLASH_Init(); while(1) { // Change the LED state (OFF to ON, or vice versa) LED_FLASH_Change_State(); // Delay for approx 1000 ms DELAY_HARDWARE_One_Second(); } } /*------------------------------------------------------------*LED_FLASH_Init() Prepare for LED_Change_State() function see below. -*------------------------------------------------------------*/ void LED_FLASH_Init(void) { LED_state_G = 0; } /*------------------------------------------------------------*LED_FLASH_Change_State() Changes the state of an LED (or pulses a buzzer, etc) on a specified port pin. Must call at twice the required flash rate: thus, for 1 Hz

flash (on for 0.5 seconds, off for 0.5 seconds) must call every 0.5 seconds. -*------------------------------------------------------------*/ void LED_FLASH_Change_State(void) { // Change the LED from OFF to ON (or vice versa) if (LED_state_G == 1) { LED_state_G = 0; LED_pin = 0; } else { LED_state_G = 1; LED_pin = 1; } } /*------------------------------------------------------------*DELAY_HARDWARE_One_Second() Hardware delay of 1000 ms. *** Assumes 12MHz 8051 (12 osc cycles) *** -*------------------------------------------------------------*/ void DELAY_HARDWARE_One_Second(void) { unsigned char d; // Call DELAY_HARDWARE_50ms() twenty times for (d = 0; d < 20; d++) { DELAY_HARDWARE_50ms(); } } /*------------------------------------------------------------*DELAY_HARDWARE_50ms() Hardware delay of 50ms.

*** Assumes 12MHz 8051 (12 osc cycles) *** -*------------------------------------------------------------*/ void DELAY_HARDWARE_50ms(void) { // Configure Timer 0 as a 16-bit timer TMOD &= 0xF0; // Clear all T0 bits (T1 left unchanged) TMOD |= 0x01; // Set required T0 bits (T1 left unchanged) ET0 = 0; // No interupts // Values for 50 ms delay TH0 = 0x3C; // Timer 0 initial value (High Byte) TL0 = 0xB0; // Timer 0 initial value (Low Byte) TF0 = 0; // Clear overflow flag TR0 = 1; // Start timer 0 while (TF0 == 0); // Loop until Timer 0 overflows (TF0 == 1) TR0 = 0; // Stop Timer 0 } /*------------------------------------------------------------*---- END OF FILE --------------------------------------------*------------------------------------------------------------*/

TMOD &= 0xF0; // Clear all T0 bits (T1 left unchanged) TMOD |= 0x01; // Set required T0 bits (T1 left unchanged) TH0 = 0x3C; // Timer 0 initial value (High Byte) TL0 = 0xB0; // Timer 0 initial value (Low Byte)

In this case, we assume again the standard 12 MHz / 12 oscillations-perinstruction microcontroller environment. We require a 50 ms delay, so the timer requires the following number of increments before it overflows:

The timer overflows when it is incremented from its maximum count of 65535. Thus, the initial value we need to load to produce a 50 ms delay is: 65536 50000 = 15536 (decimal) = 0x3CB0 Then we are ready to clear the timer flag, and start the timer running: TF0 = 0; // Clear overflow flag TR0 = 1; // Start timer 0

Some possible problems with hardwired delay


If your application is battery powered, you will generally wish to use as low an operating frequency as possible. To do this, you will typically work with late prototypes of your system (first in the simulator and then on hardware) to profile the code and determine the minimum safe operating frequency. If delay code is hard wired it is easy to forget to adjust the timing in these circumstances. If you add a serial interface then you are likely to use an 11.059 MHz crystal. If you assumed a 12 MHz oscillator and forget to adjust delay timing, then the differences can be difficult to detect in bench tests, and any problems may only show up after your product has been released. This can prove very costly. System maintenance is always an issue. Operation of your code may be clear to you, but less experienced developers may subsequently assume that your 50 ms delay code always gives a 50 ms delay (no matter what oscillator they use).

Solution: Portability
The relevant parts of Main.H are as follows: // Oscillator / resonator frequency (in Hz) e.g. (11059200UL) #define OSC_FREQ (12000000UL) // Number of oscillations per instruction (12, etc) // 12 Original 8051 / 8052 and numerous modern versions // 6 Various Infineon and Philips devices, etc. // 4 Dallas 320, 520 etc. // 1 Dallas 420, etc. #define OSC_PER_INST (12)

The timer reload values are then determined as follows (please refer to the file Delay_T0.C): // Timer preload values for use in simple (hardware) delays // Timers are 16-bit, manual reload ('one shot'). // // NOTE: These values are portable but timings are *approximate* // and *must* be checked by hand if accurate timing is required. // // Define Timer 0 / Timer 1 reload values for ~1 msec delay // NOTE: // Adjustment made to allow for function call overheard etc. #define PRELOAD01 (65536 (tWord)(OSC_FREQ / (OSC_PER_INST * 1000))) #define PRELOAD01H (PRELOAD01 / 256) #define PRELOAD01L (PRELOAD01 % 256) . . . // Delay value is *approximately* 1 ms per loop for (ms = 0; ms < N; ms++) { TH0 = PRELOAD01H; TL0 = PRELOAD01L; TF0 = 0; // Clear overflow flag TR0 = 1; // Start timer 0 while (TF0 == 0); // Loop until Timer 0 overflows (TF0 == 1) TR0 = 0; // Stop Timer 0 }

Then use this delay as:


for (ms = 0; ms < N; ms++) { TH0 = PRELOAD01H; TL0 = PRELOAD01L; TF0 = 0; // clear overflow flag TR0 = 1; // start timer 0 while (TF0 == 0); // Loop until Timer 0 overflows (TF0 == 1) TR0 = 0; // Stop Timer 0 }

The need for timeout mechanisms!


while (Switch_pin == 0); // is potentiall dangerous as we said The system will hang if the switch is never pressed // Wait until AD conversion finishes (checking ADCI) while ((ADCON & ADCI) == 0); Such code is potentially unreliable, because there are circumstances under which our application may hang. This might occur for one or more of the following reasons: If the ADC has been incorrectly initialized, we cannot be sure that a data conversion will be carried out. If the ADC has been subjected to an excessive input voltage, then it may not operate at all. If the variable ADCON or ADCI were not correctly initialized, they may not operate as required.

The need for timeout mechanisms!


Unfortunately, code in this form is common in embedded applications. If the systems you create are to be reliable, you need to be able to guarantee that no function will hang in this way. There are several ways we can provide such a guarantee. We will consider two of the most popular: The loop timeout The hardware timeout

The loop timeout


tWord Timeout_loop = 0; while (++Timeout_loop != 0); Such a loop will timeout after sometime! // with slight problem in delay estimation

// modified ADC example with timeout tWord Timeout_loop = 0; // Take sample from ADC // Wait until conversion finishes (checking ADCI) // simple loop timeout while (((ADCON & ADCI) == 0) && (++Timeout_loop != 0));

Application of Loop Timeout in Debounce (removing finger off the key)


bit SWITCH_Get_Input(const tByte DEBOUNCE_PERIOD) { tByte Return_value = SWITCH_NOT_PRESSED; tLong Timeout_loop = LOOP_TIMEOUT_INIT_10000ms; if (Switch_pin == 0) { // Switch is pressed // Debounce just wait... DELAY_T0_Wait(DEBOUNCE_PERIOD); // Check switch again if (Switch_pin == 0) { // Wait until the switch is released. // (WITH TIMEOUT LOOP 10 seconds) while ((Switch_pin == 0) && (++Timeout_loop != 0)); // Check for timeout if (Timeout_loop == 0) { Return_value = SWITCH_NOT_PRESSED; } else { Return_value = SWITCH_PRESSED; } } } // Now (finally) return switch value return Return_value; }

Creating hardware timeouts


// Wait until AD conversion finishes (checking ADCI) while ((ADCON & ADCI) == 0); // initial case Here is a solution with a hardware timeout, providing a delay of 10 ms which will, with reasonable accuracy, apply across the whole 8051 family (without code modifications): // Configure Timer 0 as a 16-bit timer TMOD &= 0xF0; // Clear all T0 bits (T1 left unchanged) TMOD |= 0x01; // Set required T0 bits (T1 left unchanged) ET0 = 0; // No interrupts // Simple timeout feature approx 10 ms TH0 = PRELOAD_10ms_H; // See Timeout.H for PRELOAD details TL0 = PRELOAD_10ms_L; TF0 = 0; // Clear flag TR0 = 1; // Start timer while (((ADCON & ADCI) == 0) && !TF0);

Creating an embedded O/S

The current speed of the vehicle must be measured at 0.5 second intervals. The display must be refreshed 40 times every second. The calculated new throttle setting must be applied every 0.5 seconds. A time-frequency transform must be performed 20 times every second. The engine vibration data must be sampled 1000 times per second. The frequency-domain data must be classified 20 times every second. The keypad must be scanned every 200 ms. The master (control) node must communicate with all other nodes (sensor nodes and sounder nodes) once per second. The new throttle setting must be calculated every 0.5 seconds. The sensors must be sampled once per second.

Trying to use the Super Loop architecture to execute functions at regular intervals

void main(void) { Init_System(); while(1) // 'for ever' (Super Loop) { X(); // Call the function (10 ms duration) Delay_50ms(); // Delay for 50 ms } } We know the precise duration of function X(), ? This duration never varies.?

Embedded OS basics
To obtain periodic function executions and avoid wasting processor cycles we can use interrupts.

1 The timer will generate an interrupt when it overflows, and 2 The timer will be automatically reloaded, and will immediately begin counting again.

The framework of an application using a timer ISR to call functions on a periodic basis
/*-----------------------------------------------------------*Main.c ------------------------------------------------------------Simple timer ISR demonstration program. -*-----------------------------------------------------------*/ #include <Reg52.H> #define INTERRUPT_Timer_2_Overflow 5 // Function prototype // NOTE: // ISR is not explictly called and does not require a prototype void Timer_2_Init(void); /* --------------------------------------------------------- */ void main(void) { Timer_2_Init(); // Set up Timer 2 EA = 1; // Globally enable interrupts while(1); // An empty Super Loop }

/* --------------------------------------------------------- */ void Timer_2_Init(void) {


// // // // // // // // Timer 2 is configured as a 16-bit timer, which is automatically reloaded when it overflows This code (generic 8051/52) assumes a 12 MHz system osc. The Timer 2 resolution is then 1.000 s Reload value is FC18 (hex) = 64536 (decimal) Timer (16-bit) overflows when it reaches 65536 (decimal) Thus, with these setting, timer will overflow every 1 ms

T2CON = 0x04; // Load Timer 2 control register TH2 = 0xFC; // Load Timer 2 high byte RCAP2H = 0xFC; // Load Timer 2 reload capt. reg. high byte TL2 = 0x18; // Load Timer 2 low byte RCAP2L = 0x18; // Load Timer 2 reload capt. reg. low byte
// Timer 2 interrupt is enabled, and ISR will be called // whenever the timer overflows

ET2 = 1;
// Start Timer 2 running

TR2 = 1; }

The interrupt service routine (ISR)


/* --------------------------------------------------------- */ void X(void) interrupt INTERRUPT_Timer_2_Overflow { // This ISR is called every 1 ms // Place required code here... } //---- END OF FILE --------------------------------------------

#define INTERRUPT_Timer_2_Overflow 5 // IE SFR MOV IE,#08h.

Capture Registers
We require a series of interrupts, generated for a long period, at a precisely determined intervals. We would like to generate these interrupts without imposing a significant load on the CPU. Timer 2 matches these requirements precisely. When Timer 2 overflows, it is automatically reloaded, and immediately begins counting again. In this case, the timer is reloaded using the contents of the capture registers (note that the names of these registers vary slightly between chip manufacturers):
RCAP2H = 0xFC; // Load Timer 2 reload capt. reg. high byte RCAP2L = 0x18; // Load Timer 2 reload capt. reg. low byte

Need to Re-implement Using sEOS


void main(void) { Init_System(); while(1) // 'for ever' (Super Loop) { X(); // Call the function (10 ms duration) Delay_50ms(); // Delay for 50 ms } }

/*------------------------------------------------------------*Main.c (v1.00) ------------------------------------------------------------Demonstration of sEOS running a dummy task. -*------------------------------------------------------------*/ #include "Main.H" 152 Embedded C 8322 Chapter 7 p143-188 21/2/02 10:02 am Page 152 #include "Port.H" #include "Simple_EOS.H" #include "X.H" /* --------------------------------------------------------- */ void main(void) { // Prepare for dummy task X_Init(); // Set up simple EOS (60 ms tick interval) sEOS_Init_Timer2(60); while(1) // Super Loop { // Enter idle mode to save power sEOS_Go_To_Sleep(); } }

/*------------------------------------------------------------*Simple_EOS.C (v1.00) ------------------------------------------------------------Main file for Simple Embedded Operating System (sEOS) for 8051. Demonstration version with dummy task X(). -*------------------------------------------------------------*/ #include "Main.H" #include "Simple_EOS.H" // Header for dummy task #include "X.H" /*------------------------------------------------------------*sEOS_ISR() Invoked periodically by Timer 2 overflow: see sEOS_Init_Timer2() for timing details. -*------------------------------------------------------------*/ void sEOS_ISR() interrupt INTERRUPT_Timer_2_Overflow { // Must manually reset the T2 flag TF2 = 0; //===== USER CODE Begin ================================== // Call dummy task here X(); //===== USER CODE End ==================================== } /*------------------------------------------------------------*-

sEOS_Init_Timer2() /* Sets up Timer 2 to drive the simple EOS. Parameter gives tick interval in MILLISECONDS. Max tick interval is ~60ms (12 MHz oscillator).Note: Precise tick intervals are only possible with certain oscillator / tick interval combinations. If timing is important, you should check the timing calculations manually. -----------------------------------------------------------*/ void sEOS_Init_Timer2(const tByte TICK_MS) { tLong Inc; tWord Reload_16; tByte Reload_08H, Reload_08L; // Timer 2 is configured as a 16-bit timer, // which is automatically reloaded when it overflows T2CON = 0x04; // Load Timer 2 control register // Number of timer increments required (max 65536) Inc = ((tLong)TICK_MS * (OSC_FREQ/1000)) / (tLong)OSC_PER_INST; // 16-bit reload value Reload_16 = (tWord) (65536UL Inc); // 8-bit reload values (High & Low) Reload_08H = (tByte)(Reload_16 / 256); Reload_08L = (tByte)(Reload_16 % 256); // Used for manually checking timing (in simulator) //P2 = Reload_08H; //P3 = Reload_08L; TH2 = Reload_08H; // Load T2 high byte RCAP2H = Reload_08H; // Load T2 reload capt. reg. high byte TL2 = Reload_08L; // Load T2 low byte RCAP2L = Reload_08L; // Load T2 reload capt. reg. low byte // Timer 2 interrupt is enabled, and ISR will be called // whenever the timer overflows. ET2 = 1; // Start Timer 2 running TR2 = 1; EA = 1; // Globally enable interrupts } /*------------------------------------------------------------*-

void sEOS_Go_To_Sleep(void) { PCON |= 0x01; // Enter idle mode (generic 8051 version) }

Note that the processor will automatically return to Normal mode when thetimer next overflows (generating an interrupt). Overall, this application is asleep for 99.9% of the time, meaning that the total load imposed by this operating system is around 0.1% of the available CPU capacity

Using Timer 0 or Timer 1


void sEOS_ISR() interrupt INTERRUPT_Timer_0_Overflow { // Flag cleared automatically but must reload the timer sEOS_Manual_Timer0_Reload(); //===== USER CODE Begin ================================== // Call dummy task here X(); //===== USER CODE End ==================================== } /*------------------------------------------------------------*sEOS_Init_Timer0() Sets up Timer 0 to drive the simple EOS. Parameter gives tick interval in MILLISECONDS. Max tick interval is ~60ms (12 MHz oscillator). Note: Precise tick intervals are only possible with certain oscillator / tick interval combinations. If timing is important, you should check the timing calculations manually. -*------------------------------------------------------------*/

Using Timer 0 or Timer 1


void sEOS_Init_Timer0(const tByte TICK_MS) { tLong Inc; tWord Reload_16; // Using Timer 0, 16-bit *** manual reload *** TMOD &= 0xF0; // Clear all T0 bits (T1 left unchanged) TMOD |= 0x01; // Set required T0 bits (T1 left unchanged) // Number of timer increments required (max 65536) Inc = ((tLong)TICK_MS * (OSC_FREQ/1000)) / (tLong)OSC_PER_INST; // 16-bit reload value Reload_16 = (tWord) (65536UL Inc); // 8-bit reload values (High & Low) Reload_08H = (tByte)(Reload_16 / 256); Reload_08L = (tByte)(Reload_16 % 256); // Used for manually checking timing (in simulator) //P2 = Reload_08H; //P3 = Reload_08L; TL0 = Reload_08L; TH0 = Reload_08H; // Timer 0 interrupt is enabled, and ISR will be called // whenever the timer overflows. ET0 = 1; // Start Timer 0 running TR0 = 1; EA = 1; // Globally enable interrupts }

Using Timer 0 or Timer 1


/*------------------------------------------------------------*sEOS_Manual_Timer0_Reload() This OS uses a (manually reloaded) 16-bit timer. The manual reload means that all timings are approximate. THIS OS IS NOT SUITABLE FOR APPLICATIONS WHERE ACCURATE TIMING IS REQUIRED!!! Timer reload is carried out in this function. -*------------------------------------------------------------*/ void sEOS_Manual_Timer0_Reload() { // Stop Timer 0 TR0 = 0; // See 'init' function for calculations TL0 = Reload_08L; TH0 = Reload_08H; // Start Timer 0 TR0 = 1; }

Trigger Mechanisms
Time Triggered
functions are started (or triggered) at pre-determined points in time.

Event Triggered
Trigger only at event occurence
The doctor might arrange for one of the nursing staff to waken her if there is a significant problem with one of the patients. This is the event triggered solution. The doctor might set her alarm clock to ring every hour. When the alarm goes off, she will get up and visit each of the patients, in turn, to check that they are well and, if necessary, prescribe treatment. This is the time triggered solution.

Doctor may get some sleep with event Trig Doctor may lack sleep with Time Trig But
Time Triggered is more stable and safe Since Event Trigs may overload the doctor Or simultaneous Events may complicate things

Interrupt Priorities
If Interrupt 1 is a low-priority interrupt and Interrupt 2 is a high-priority interrupt. The interrupt service routine (ISR) invoked by a low-priority interrupt can be interrupted by a high-priority interrupt. In this case, the low-priority ISR will be paused, to allow the highpriority ISR to be executed, after which the operation of the low-priority ISR will be completed. In most cases, the system will operate correctly (provided that the two ISRs do not interfere with one another). If Interrupt 1 is a low-priority interrupt and Interrupt 2 is also a low-priority interrupt. The ISR invoked by a low-priority interrupt cannot be interrupted by another lowpriority interrupt. As a result the response to the second interrupt will be at the very least delayed; under some circumstances it will be ignored altogether. If Interrupt 1 is a high-priority interrupt and Interrupt 2 is a low-priority interrupt. The interrupt service routine (ISR) invoked by a high-priority interrupt cannot be interrupted by a low-priority interrupt. As a result the response to the second interrupt will be at the very least delayed; under some circumstances it will be ignored altogether. If Interrupt 1 is a high-priority interrupt and Interrupt 2 is also a high-priority interrupt. The interrupt service routine (ISR) invoked by a high-priority interrupt cannot be interrupted by another high-priority interrupt. As a result the response to the second interrupt will be at the very least delayed; under some circumstances it will be ignored altogether.

Note carefully what this means!


There is a common misconception among the developers of embedded applications that interrupt events will never be lost. This simply is not true. If you have multiple sources of interrupts that may appear at random time intervals, interrupt responses can be missed: indeed, where there are several active interrupt sources, it is practically impossible to create code that will deal correctly with all possible combinations of interrupts.

It is the need to deal with the simultaneous occurrence of more than one event that both adds to the system complexity and reduces the ability to predict the behaviour of an event-triggered system under all circumstances. By contrast, in a time-triggered embedded application, the designer is able to ensure that only single events must be handled at a time, in a carefully controlled sequence.

Stopping Tasks
When we say that an operating system is co-operative, we mean that a task, once started, will run until it is complete: that is, the OS will never interrupt an active task. This is a single task approach to operating system design.
eEOS has time-triggered, co-operatively scheduled architecture

The alternative is a pre-emptive or time sliced approach. In a preemptive system, tasks will typically run for say a millisecond. The OS will then pause this task, and run another task for a millisecond, and so on. From the perspective of the user, the pre-emptive OS appears to be running multiple tasks at the same time.
consider that we wish to run two tasks on a pre-emptive system, and that both tasks require access to the same port. Suppose that one task is reading from this port, and that the scheduler performs a context switch, causing the second task to access the same port: under these circumstances, unless we take action to prevent it, data may be lost or corrupted.

critical sections in multitasking


Code which modifies or reads variables, particularly global variables used for inter-task communication. In general, this is the most common form of critical section, since inter-task communication is often a key requirement. Code which interfaces to hardware, such as ports, analog-to-digital converters (ADCs), and so on. What happens, for example, if the same ADC is used simultaneously by more than one task? Code which calls common functions. What happens, for example, if the same function is called simultaneously by more than one task?

To deal with critical sections of code in a pre-emptive system, we have two possibilities:

Pause the scheduling by disabling the scheduler interrupt before beginning the critical section; re-enable the scheduler interrupt when we leave the critical section, or; Use a lock (or some other form of semaphore mechanism) to achieve a similar result.

Lock

1 Task A checks the lock for Port X it wishes to access. 2 If the section is locked, Task A waits. 3 When the port is unlocked, Task A sets the lock and then uses the port. 4 When Task A has finished with the port, it leaves the critical section and unlocks the port.

Lock example
#define UNLOCKED 0 #define LOCKED 1 bit Lock; // Global lock flag // . . . // Ready to enter critical section // wait for lock to become clear // (FOR SIMPLICITY, NO TIMEOUT CAPABILITY IS SHOWN) while(Lock == LOCKED); // Lock is clear // Enter critical section // Set the lock Lock = LOCKED; // CRITICAL CODE HERE // // Ready to leave critical section // Release the lock Lock = UNLOCKED; // . . .

Problems in pre-emptive lock


Task A has checked the lock for Port X and found that the port is not locked; Task A has, however, not yet changed the lock flag. Task B is then switched in. Task B checks the lock flag and it is still clear. Task B sets the lock flag and begins to use Port X. Task A is switched in again. As far as Task A is concerned, the port is not locked; this task therefore sets the flag, and starts to use the port, unaware that Task B is already doing so.
this simple lock code violates the principal of mutual exclusion: that is, it allows more than one task to access a critical code section. The problem arises because it is possible for the context switch to occur after a task has checked the lock flag but before the task changes the lock flag. In other words, the lock check and set code (designed to control access to a critical section of code), is itself a critical section.

Worst-case task execution time


the designer must ensure that the execution time for a task can never exceed the tick interval

Suppose: The operating system has a 10 ms tick interval. A task runs for 22 milliseconds???

In these circumstances, at least one tick will be lost and the system may fail to operate as required. So we need to calculate the worst-case execution time!

Attention..
The sEOS initialization function enables the generation of interrupts associated with the overflow of one of the microcontroller timers. To ensure correct operation of the system, it is essential that with the exception of the single timer interrupt driving the OS all interrupts are disabled. If you fail to do this, then you are trying to operate sEOS as an event-triggered system

Example: Milk pasteurization


The system discussed in this example is intended to monitor the rate of liquid (milk) flow through a pasteurization system. The monitoring is required primarily because too high a flow rate can result in incomplete sterilization of the product, and a consequent reduction in shelf life.

Measuring the flow rate


To measure the rate of flow, we will be measuring the frequency of a stream of pulses. This is a common requirement: for example, many industrial systems require measurement of rotational speed

The output from this program


A bargraph display, giving the operator an instant visual representation of the flow rate. An audible alarm, which will sound when the flow rate falls below an acceptable level.

We need: An efficient code for counting the pulses: no delay code is required. The techniques used to generate the bargraph display.

Relevant Code
/*------------------------------------------------------------*Port.H (v1.00) ------------------------------------------------------------Port Header file for the milk pasteurization example (Chapter 7) -*------------------------------------------------------------*/ // ------ Pulse_Count.C --------------------------------------// Connect pulse input to this pin debounced in software sbit Sw_pin = P3^0; // Connect alarm to this pin (set if pulse is below threshold) sbit Alarm_pin = P3^7; // ------ Bargraph.C -----------------------------------------// Bargraph display on these pins // The 8 port pins may be distributed over several ports if required sbit Pin0 = P1^0; sbit Pin1 = P1^1; sbit Pin2 = P1^2; sbit Pin3 = P1^3; sbit Pin4 = P1^4; sbit Pin5 = P1^5; sbit Pin6 = P1^6; sbit Pin7 = P1^7; /*------------------------------------------------------------*---- END OF FILE --------------------------------------------*------------------------------------------------------------*/

/*------------------------------------------------------------*Main.c (v1.00) ------------------------------------------------------------Milk pasteurization example. -*------------------------------------------------------------*/ #include "Main.H" #include "Port.H" #include "Simple_EOS.H" #include "Bargraph.H" #include "Pulse_Count.H" /* --------------------------------------------------------- */ void main(void) { PULSE_COUNT_Init(); BARGRAPH_Init(); // Set up simple EOS (30ms tick interval) sEOS_Init_Timer2(30); while(1) // Super Loop { // Enter idle mode to save power sEOS_Go_To_Sleep(); } } /*------------------------------------------------------------*---- END OF FILE --------------------------------------------*------------------------------------------------------------*/

ISR
/*---------------------------------------------------------*sEOS_ISR() Invoked periodically by Timer 2 overflow: -*-------------------------------------------------------*/ void sEOS_ISR() interrupt INTERRUPT_Timer_2_Overflow { // Must manually reset the T2 flag TF2 = 0; //===== USER CODE Begin ========================= // Call 'Update' function here PULSE_COUNT_Update(); //===== USER CODE End ============================ } /*---------------------------------------------------------*-

/*------------------------------------------------------------*sEOS_Init_Timer2() Sets up Timer 2 to drive the simple EOS. Parameter gives tick interval in MILLISECONDS. Max tick interval is ~60ms (12 MHz oscillator). Note: Precise tick intervals are only possible with certain oscillator / tick interval combinations. If timing is important, you should check the timing calculations manually. -*------------------------------------------------------------*/ void sEOS_Init_Timer2(const tByte TICK_MS) { tLong Inc; tWord Reload_16; tByte Reload_08H, Reload_08L; // Timer 2 is configured as a 16-bit timer, // which is automatically reloaded when it overflows T2CON = 0x04; // Load Timer 2 control register // Number of timer increments required (max 65536) Inc = ((tLong)TICK_MS * (OSC_FREQ/1000)) / (tLong)OSC_PER_INST; Reload_16 = (tWord) (65536UL Inc); // 16-bit reload value Reload_08H = (tByte)(Reload_16 / 256); // 8-bit reload values (High & Low) Reload_08L = (tByte)(Reload_16 % 256); TH2 = Reload_08H; // Load T2 high byte RCAP2H = Reload_08H; // Load T2 reload capt. reg. high byte TL2 = Reload_08L; // Load T2 low byte RCAP2L = Reload_08L; // Load T2 reload capt. reg. low byte // Timer 2 interrupt is enabled, and ISR will be called // whenever the timer overflows. ET2 = 1; TR2 = 1; // Start Timer 2 running EA = 1; // Globally enable interrupts }

Pulse count
/*------------------------------------------------------------*PULSE_COUNT_Check_Below_Threshold() Checks to see if pulse count is below a specified threshold value. If it is, sounds an alarm. -*------------------------------------------------------------*/ void PULSE_COUNT_Check_Below_Threshold(const tByte THRESHOLD) { if (Data_G < THRESHOLD) { Alarm_pin = 0; } else { Alarm_pin = 1; } } /*------------------------------------------------------------*-

void PULSE_COUNT_Update(void) { // Clear timer flag TF2 = 0; // Shuffle the test results Test4 = Test3; Test3 = Test2; Test2 = Test1; Test1 = Test0; // Get latest test result Test0 = Sw_pin; // Required result: Test4 == HI_LEVEL Test3 == HI_LEVEL Test1 == LO_LEVEL Test0 == LO_LEVEL if ((Test4 == HI_LEVEL) && (Test3 == HI_LEVEL) && (Test1 == LO_LEVEL) && (Test0 == LO_LEVEL)) { Falling_edge_G = 1; // Falling edge detected } else { Falling_edge_G = 0; // Default } // Calculate average every 45 calls to this task maximum count over this period is 9 pulses // if (++Calls_G < 45) 450 used here for test purposes (in simulator) // [Because there is a limit to how fast you can simulate pulses by hand...] if (++Calls_G < 450) { Total_G += (int) Falling_edge_G; } else { // Update the display Data_G = Total_G; // Max is 9 Total_G = 0; Calls_G = 0; PULSE_COUNT_Check_Below_Threshold(3); BARGRAPH_Update(); } }

/*------------------------------------------------------------*BARGRAPH_Init() Prepare for the bargraph display. -*------------------------------------------------------------*/ void BARGRAPH_Init(void) { Pin0 = BARGRAPH_OFF; Pin1 = BARGRAPH_OFF; Pin2 = BARGRAPH_OFF; Pin3 = BARGRAPH_OFF; Pin4 = BARGRAPH_OFF; Pin5 = BARGRAPH_OFF; Pin6 = BARGRAPH_OFF; Pin7 = BARGRAPH_OFF; // Use a linear scale to display data // Remember: *9* possible output states // do all calculations ONCE M9_1_G = (BARGRAPH_MAX BARGRAPH_MIN) / 9; M9_2_G = M9_1_G * 2; M9_3_G = M9_1_G * 3; M9_4_G = M9_1_G * 4; M9_5_G = M9_1_G * 5; M9_6_G = M9_1_G * 6; M9_7_G = M9_1_G * 7; M9_8_G = M9_1_G * 8; } /*-----------------BARGRAPH_Update() Update the bargraph display. ------------------------------------*/ void BARGRAPH_Update(void) { tBargraph Data = Data_G BARGRAPH_MIN; Pin0 = ((Data >= M9_1_G) == BARGRAPH_ON); Pin1 = ((Data >= M9_2_G) == BARGRAPH_ON); Pin2 = ((Data >= M9_3_G) == BARGRAPH_ON); Pin3 = ((Data >= M9_4_G) == BARGRAPH_ON); Pin4 = ((Data >= M9_5_G) == BARGRAPH_ON); Pin5 = ((Data >= M9_6_G) == BARGRAPH_ON); Pin6 = ((Data >= M9_7_G) == BARGRAPH_ON); Pin7 = ((Data >= M9_8_G) == BARGRAPH_ON); } /*------------------------------------------------------------- END OF FILE -----------------------------------------------*/

sEOS becomes part of the application itself

A simple OS becomes part of the developers application, rather than being seen as a separate system that has nothing to do with us

You might also like