ECE 2534 Spring 2018 Programming with Time ------------------------------------------ 10:10 It's time .. to program with time! So far, we discussed basic C programming with micro-controllers - Using IO Ports to light up LEDs and read switches - Bit manipulation to turn/on off individual control bits in peripherals Our concept of a running C program is very simple: We write all the things that need to be done in a tight loop, and then work the program until 'it works fine'. Today, we are going to discuss a more foundational approach to writing micro-controller programs. We are in need of a systematic method to deal with real-time aspects of a micro-controller program. So far, we did not pay attention to this, except for pointing out that programs will run slower if the 'tight loop' contains more instructions. The idea of real time, wall clock time, is a genuine aspect of writing programs for a micro-controller. It's unavoidable, because micro-controllers interact with the physical world. They _have_ to work in real time. If you write programs for a general purpose computer, you are only interested in performance ('complete the job as fast as possible'). But if you program microcontrollers, it can be very important to do the right things AT THE RIGHT TIME (not: as fast as possible). For example: - Blink a LED every second - Turn on the LED when the button is pressed - Count the number milliseconds between two button presses Note that all of these examples are tied in once way or the other to real time, or to things happening in real time in the real world. To write such micro-controller programs that handle 'real-time' operation, we need to introduce two concepts. 1/ First, we introduce the idea of EVENTS, which describe activities in the real world 2/ Second, we will introduce a programming principle that helps us to work with events. The programming principle is called the CYCLIC EXECUTIVE 10:15 Events General-purpose C programs work with data values: integers, characters, and so on. To interact with the real world, we need more than just the value, we also need to have the notion of the real time (wall clock time) when that value is relevant. In the following, we will express time as the number of seconds (or milliseconds) since the microcontroller program started running. So, just before the first line of main() executes, we say: time is 0 seconds, or t=0. Everything that happens in the program (input or output), can now be measured in time relative from that moment. What is an event? An event describes a physical activity. An event is a tuple (real-time stamp, value), where 'value' describes what has happened in the physical world For example: A button is pressed at t=5 seconds. If this button drives a GPIO port, we would see the level of the input pin drop from 1 to 0. We can describe this activity as an event. Fundamental to an event is that it is a tuple: EVENT = (TIME, VALUE) So in this case, we could describe the button press as EVENT = (5s, button_down) In this example, the data value for the event can be captured in a single bit, namely: 1 = button_up, 0 = button_down. It is also possible to have events that are abstract, ie. that have a data value that is irrelevant. For example: Consider a (hardware) counter that generates an event each time it wraps around. So let's say that you have a piece of hardware that counts from 0 to 65535 (ie. a 16-bit timer), and that generates an event each time it reaches 65535. Counter Value 0 1 2 3 .. 65534 65535 -> EVENT! counter wraps around 0 1 2 3 ... 65534 65535 -> EVENT! counter wraps around 0 1 2 ... If we assume that it takes 10ms for the counter to reach 65535, then we would observe an infinite stream of events: EVENT = (10ms, _) EVENT = (20ms, _) EVENT = (30ms, _) ... The _ indicates that we're not interested in the value of the counter (since we already know that it wraps around). So key points to remember: (1) events describe physical activities at the inputs or the outputs of a microcontroller (2) each event is characterized by a timestamp and (optionally) a data value 10:30 The cyclic executive +------------+ ---->| |-----> input ---->| C program |-----> output events ---->| |-----> +------------+ A microcontroller program must be written in such a way that it can handle every event without ever loosing an event. 'Loosing an event' can be roughly understood as 'missing an input or an output because of bad timing'. Consider the following programming problem: The microcontroller reads two buttons, each tied to a microcontroller GPIO pin. The microcontroller has two output pins that each drive a LED. The C program on the microcontroller must be written such that each button controls one LED. As long as the button is pressed, the corresponding LED should be set. How should we write such a program? Here's one solution: int main() { while (1) { // .. do activity 1 if (Button1Pressed()) turn_on LED1 else turn_off LED1 // .. do activity 2 if (Button2Pressed()) turn_on LED2 else turn_off LED2 } } This program spins very quickly, alternately scans the position of each button, and sets one of the LEDs in the corresponding state. As long as the while (1) loop spins fast enough, we will never miss a button press. So we will never loose an event. Now, consider what happens when we would write the program as follows: int main() { while (1) { turn_off LED1 turn_off LED2 // .. do activity 1 while (Button1Pressed()) turn_on LED1 // .. do activity 2 while (Button2Pressed()) turn_on LED2 } } What is the problem with this program? This program can handle only one button at a time. If we press either button1 or else button 2, we will see LED1 or LED2 light up. But if we hold one button down, we become 'blind' for button presses of the other button. In other words, we're loosing input events. So this second solution is not a good solution to the programming problem. In general, the way we want to write a program so that is does not loose input events. This tight loop is called the CYCLIC EXECUTIVE: while (1) { if (input_event_1_detected()) do_something_1(); if (input_event_2_detected()) do_something_2(); if (input_event_3_detected()) do_something_3(); ... } We have to make sure that the loop round-trip time is fast enough so that all input events will be detected in time. 10:40 Time Delays So far, the events we worked with are button presses. However, it's very common in microcontroller programming to have to work with real time. For example, consider the following problem Every 500ms, flip the state of LED1 How do we write this as a cyclic executive? A straightforward method (discussed with the 'impatient LED' example) is the write a while (1) loop with delay: while (1) { Toggle_LED1(); for (i=0; i sample = 7. previous = 11. previous > sample. 6 5 4 3 ---------> sample = 3. previous = 7. previous > sample. 2 1 0 512 ---------> sample =512. previous = 3. previous < sample. EVENT ! 511 510 509 508 ---------> sample =508. previous =512. previous > sample. 507 We can write a function that detects timer wrap around. We only need a function to read the current Timer count value. int TimerExpired() { static unsigned int previousSnap; unsigned int currentSnap, ret; currentSnap = Timer32_getValue(TIMER32_0_BASE); ret = (currentSnap > previousSnap); previousSnap = currentSnap; return ret; } The 'previousSnap' variable is a 'static' variable. That means that it will remember its value in between function calls. (By default, local variables do not remember the value). TimerExpired() will return 0 as long as the timer has not expired, and when the timer expires (wraps around), it will return 1 for one call. Of course, this will work only when we call TimerExpired() fast enough. If we are slow to call TimerExpired(), we may miss an event, just as we could miss an event with button presses. We can now write a cyclic executive with a time delay. For example, here is how you would write C code for the following problem: - A microcontroller controls a red LED and a Color LED. When a button S1 is pressed, the red LED is turned on. Every 500ms the ColorLED toggles between white and black (off). - The cyclic executive looks as follows: while (1) { if (ButtonS1Pressed()) RedLEDOn(); else RedLEDOff(); if (TimerExpired()) ColorLEDToggle(); } - Discuss example msp432-blink32 See https://github.com/vt-ece2534-s18/msp432-blink32 10:55 Timer-ordered events The above principle of the cyclic executive (i.e. a while loop that repeatedly tests for a set of conditions) is a good starting point for many of your microcontroller projects. However, it is a bit too simple to handle every case. In particular, time-ordered events are difficult to handle as a cyclic executive. Assume the following example. A microcontroller with two buttons and two LEDs. LED1 should toggle when one presses B2 and next B1 (without releasing B2). LED2 should toggle when one presses B1 and next B2 (without releasing B1). This problem is difficult two write as a cyclic executive. Try it! We will pick up on this problem on Friday, and discuss a solution for it. 10:59 Conclusions - Events - Cyclic Executive - Time Delays in the Cyclic Executive using a hardware counter