Lecture 15 - Software Timers and Include Files 10:10 Software Timers and Include Files In this lecture, we will discuss two useful techniques to deal with more complex aspects of programming. - The first is the idea of a 'virtual peripheral', or a peripheral that extends the functionality of a hardware peripheral with a software copy. We will discuss a technique to create a large number of 'software timers' from a single hardware timer. - The second is the idea of partitioning a program into multiple files, such that each file can be separately compiled and tested. In Lab 3, we will require you to develop your software in this manner. 10:12 Virtual Timers The MSP432P4 has several hardware peripherals; so far, we have used only the Timer32 modules, the timers that sit inside of the ARM core. As a recap, these hardware timer modules - can be configured as 16-bit counters or 32-bit counters - can be run at the system clock, 1/16th of the system clock, or 1/256th of the system clock - can be run as a period counter (with a given period), or as a free running counter The difference between these two is defined by periodic counter: period, period-1, ...., 2, 1, 0, period, ... free running: MAXINT, MAXINT-1, ..., 2, 1, 0, MAXINT - can be run as a continuous counter, or as a one-shot counter Relevant driverlib functions: // initialize the timer void Timer32_initModule (uint32_t timer, uint32_t preScaler, uint32_t resolution, uint32_t mode) // load the period register void Timer32_setCount (uint32_t timer, uint32_t count) // run the timer void Timer32_startTimer (uint32_t timer, bool oneShot) // read the timer value uint32_t Timer32_getValue (uint32_t timer) We have discussed how to build a periodic timer and a one-shot timer by using two simple software functions. For the periodic counter, we need a function that reads when a timer is 'expired'. int TimerExpired() { static unsigned int previousSnap; unsigned int currentSnap, ret; currentSnap = Timer32_getValue(TIMER32_0_BASE); ret = (currentSnap > previousSnap); previousSnap = currentSnap; return ret; } For the one-shot timer, we need two functions: one that starts the one-shot timer, and one that tests when it is complete void TimerStartOneShot() { Timer32_setCount(TIMER32_0_BASE, 300000); Timer32_startTimer(TIMER32_0_BASE, true); } int TimerExpiredOneShot() { return (Timer32_getValue(TIMER32_0_BASE) == 0); } 10:18 Software Timers The Timer32 hardware is useful, but it has a disadvantage: there are only two such modules. A real embedded system will frequently need many more periodic timers and one-shot timers. We can get around this by using software to build a virtual copy of a hardware timer. The software relies on the same hardware counter, but it uses additional logic to deal with the testing of counter values in order to detect when a software timer expires. So we would like to build something like this: SWtimer1 SWtimer2 SWtimer3 ... - period1 - period2 - period3 | | | | +---------------+-------+-------+---------------+ | Hardware Timer - maxperiod (eg MAX_UINT16 or MAX_UINT32) The key problem to solve, is to figure out how we can do timing for arbitrary periods on a hardware timer that has a fixed period. Of course, if we know the SWtimer period, we can compute when the SWtimer should expire. Assume that the software timer is started when the value in the hardware timer counter is hwtimer, then the software expiration time is bound = hwtimer - period The 'bound' is the expiration time of the software timer when measured in hardware timer tick values. Note that hwtimer is always decrementing. For a 16-bit hardware timer, the hwtimer decrements from 65535 to 0. Let's write this out in more detail. If we assume a 16-bit hardware timer, then bound, hwtimer and period are all unsigned 16-bit values. Now, when we start the software timer, hwtimer can have any value between 65535 and 0. The period of the software timer is a value between 1 and 65535. And bound is computed as follows: start_softwaretimer { hwtimer = Timer32_getValue(); bound = hwtimer - period; } Example 1: Assume period = 5000 If we start the software timer when hwtimer = 20000, then bound = 15000 Example 2: Assume period = 5000 If we start the software timer when hwtimer = 2000, then bound = ? Since bound is an unsigned number, it's value will 'wrap around' to a positive 16-bit value, ie. If hwtimer = 2000, then bound = 65536 - 2000 = 63536 ==================================================================== Question: Given this observation, how can we write an expression that will be true when the software timer expires? Given: bound hwtimer = Timer32_getValue() period Answer: The unsigned difference between Timer32_getValue() and bound will decrease after we compute the bound (when starting the software timer). So (Timer32_getValue() - bound) expresses the 'time left' in the software timer. This time left will be smaller than the software timer period. However, at some point, Timer32_getValue() becomes smaller than bound. Then, suddenly, the difference jumps from a small number to a very large number. This is because we are using unsigned arithmetic. Therefore, the expression that shows when the software timer expires is the following: (Timer32_getValue() - bound) > period ===================================================================== Let's demonstrate using the examples. Example 1: SW timer period is 5000 Started when hwtimer = 20000 Therefore, bound is 15000 unsigned subtraction Timer32_getValue bound (Timer32_getValue - bound) Expired ---------------- ----- -------------------------- ------- 19000 15000 4000 No 16000 15000 1000 No 15000 15000 0 No 14999 15000 65535 Yes 14000 15000 64536 Yes Example 2: SW timer period is 5000 Started when hwtimer = 2000 Therefore, bound is 63536 unsigned subtraction Timer32_getValue bound (Timer32_getValue - bound) Expired ---------------- ----- -------------------------- ------- 1500 63536 3500 No 1000 63536 3000 No 0 63536 2000 No 65535 63536 1999 No 63536 63536 0 No 63535 63536 65535 Yes 10:30 Software Timers as a struct Now that we understand the basics of software timekeeping, let's build a data structure that helps us manage software timers typedef struct { uint32_t hwtimer; // hardware timer used as basis for this software timer uint16_t period; // period of the software timer uint16_t bound; // next expiration time for software timer bool expired; } tSWTimer; We initialize a software timer by associating it with a hardware timer, and a period. void InitSWTimer(tSWTimer *T, uint32_t hwtimer, uint16_t period) { T->hwtimer = hwtimer; T->period = period; T->bound = Timer32_getValue(hwtimer) - period; T->expired = false; } The software timer can now be started, by (re)computing the bound. void StartSWTimer(tSWTimer *T) { T->bound = Timer32_getValue(T->hwtimer) - T->period; T->expired = false; } And to check when the software timer expires, we test the expression (Timer32_getValue() - bound) > period. bool SWTimerExpired(tSWTimer *T) { bool expired; uint16_t delta = Timer32_getValue(T->hwtimer) - T->bound; expired = (delta > T->period); if (expired) T->bound = T->bound - T->period; return expired; } Of course, the above logic is for a 16-bit timer. If we are using a 32-bit timer, then we need to use a 32 bit value for the period and the bound. 10:35 Software one-shot expiration values We can extend the periodic software timer to a one-shot timer as follows. The only thing that has to change is that the one-shot timer can expire only once. So we do this using a boolean flag expired. bool SWTimerOneShotExpired(tSWTimer *T) { bool expired; uint16_t delta = Timer32_getValue(T->hwtimer) - T->bound; expired = (delta > T->period); if (expired) T->expired = true; return T->expired; } 10:37 Here is a main program that demonstrates the use of software timers. We are using four timers. We will blink two LED's using a different period. In addition, we will flash the LEDs using one-shot timers, rather then turning them on and off with a 50% duty cycle int main(void) { WDT_A_hold(WDT_A_BASE); tSWTimer timer1; tSWTimer timer2; tSWTimer timer3; tSWTimer timer4; InitLEDs(); InitTimer(); InitSWTimer(&timer1, TIMER32_1_BASE, (uint16_t) 5000); InitSWTimer(&timer2, TIMER32_1_BASE, (uint16_t) 15000); InitSWTimer(&timer3, TIMER32_1_BASE, (uint16_t) 1000); InitSWTimer(&timer4, TIMER32_1_BASE, (uint16_t) 1000); StartSWTimer(&timer1); StartSWTimer(&timer2); while (1) { if (SWTimerExpired(&timer1)) { SensorColorLEDSet(red); StartSWTimer(&timer3); } if (SWTimerExpired(&timer2)) { MainColorLEDSet(blue); StartSWTimer(&timer4); } if (SWTimerOneShotExpired(&timer3)) SensorColorLEDSet(black); if (SWTimerOneShotExpired(&timer4)) MainColorLEDSet(black); } } The clock of the hardware timer is 3MHz/256, or about 11KHz. So a count of 5000 will result in a SensorColorLED flashing period of about 0.5 seconds. Note how timer 3 is started when timer 1 expires. Timer 3 is a one-shot timer that expires about 100ms after the LED turns on. It turns the LED off again. A similar mechanism is used for timer 4 and timer 2. Overall, timer 2 has a period that is three times longer than timer 1. So, one LED will flash at only 1/3 of the rate of the other LED. 10:40 Program Structure Next, we discuss the idea of partitioning a program into multiple files, such that each file can be separately compiled and tested. - Slides: include files 10:50 Apply this to the software timer Include files swtimer.h leds.h C files swtimer.c leds.c vtimerfile.c -> main program 10:55 Conclusions This week we discussed the following topics: - LCD graphics - context - color - text/fonts - bitmaps - structs - include files & partitioning your program in multiple C files