Embedded Real Time
Embedded Real Time
)
Week 4+5 Real Time Problems
problems
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.
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.
/*------------------------------------------------------------*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
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 }
// 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));
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 }
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; }
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
/*------------------------------------------------------------*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
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.
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.
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; // . . .
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
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 -----------------------------------------------*/
A simple OS becomes part of the developers application, rather than being seen as a separate system that has nothing to do with us