Development mbed library for MAX32630FTHR

Dependents:   blinky_max32630fthr

Committer:
switches
Date:
Fri Nov 11 20:59:50 2016 +0000
Revision:
0:5c4d7b2438d3
Initial commit

Who changed what in which revision?

UserRevisionLine numberNew contents of line
switches 0:5c4d7b2438d3 1 # About the mbed OS event loop
switches 0:5c4d7b2438d3 2
switches 0:5c4d7b2438d3 3 One of the optional mbed OS features is an event loop mechanism that can be used to defer the execution of code in a different context. In particular, a common uses of an event loop is to postpone the execution of a code sequence from an interrupt handler to an user context. This is useful because of the specific constraints of code that runs in an interrupt handler:
switches 0:5c4d7b2438d3 4
switches 0:5c4d7b2438d3 5 - the execution of certain functions (notably some functions in the C library) is not safe.
switches 0:5c4d7b2438d3 6 - various RTOS objects and functions can't be used from an interrupt context.
switches 0:5c4d7b2438d3 7 - as a general rule, the code needs to finish as fast as possible, to allow other interrupts to be handled.
switches 0:5c4d7b2438d3 8
switches 0:5c4d7b2438d3 9 The event loop offers a solution to these issues in the form of an API that can be used to defer execution of code from the interrupt context to the user context. More generally, the event loop can be used anywhere in a program (not necessarily in an interrupt handler) to defer code execution to a different context.
switches 0:5c4d7b2438d3 10
switches 0:5c4d7b2438d3 11 # Overview of the mbed OS event loop
switches 0:5c4d7b2438d3 12
switches 0:5c4d7b2438d3 13 An event loop has two main components:
switches 0:5c4d7b2438d3 14
switches 0:5c4d7b2438d3 15 1. an **event queue**, used to store events. In mbed OS, *events* are pointers to functions (and optionally function arguments).
switches 0:5c4d7b2438d3 16 2. an **event loop** that extracts events from the queue and executes them.
switches 0:5c4d7b2438d3 17
switches 0:5c4d7b2438d3 18 The mbed OS event queue is implemented by the [mbed-events library](http://github.com/ARMmbed/mbed-os/tree/master/events). It's a good idea to go through the [README of mbed-events](https://github.com/ARMmbed/mbed-os/blob/master/events/README.md), as it shows how to use the event queue.
switches 0:5c4d7b2438d3 19
switches 0:5c4d7b2438d3 20 The event loop must be created and started manually. The simplest way to achieve that is to create a `Thread` and run the event queue's `dispatch` method in the thread:
switches 0:5c4d7b2438d3 21
switches 0:5c4d7b2438d3 22 ```
switches 0:5c4d7b2438d3 23 #include "mbed.h"
switches 0:5c4d7b2438d3 24 #include "mbed_events.h"
switches 0:5c4d7b2438d3 25
switches 0:5c4d7b2438d3 26 // Create a queue that can hold a maximum of 32 events
switches 0:5c4d7b2438d3 27 Queue queue(32 * EVENTS_EVENT_SIZE);
switches 0:5c4d7b2438d3 28 // Create a thread that'll run the event queue's dispatch function
switches 0:5c4d7b2438d3 29 Thread t;
switches 0:5c4d7b2438d3 30
switches 0:5c4d7b2438d3 31 int main () {
switches 0:5c4d7b2438d3 32 // Start the event queue's dispatch thread
switches 0:5c4d7b2438d3 33 t.start(callback(&queue, &EventQueue::dispatch_forever));
switches 0:5c4d7b2438d3 34 ...
switches 0:5c4d7b2438d3 35 }
switches 0:5c4d7b2438d3 36 ```
switches 0:5c4d7b2438d3 37
switches 0:5c4d7b2438d3 38 Note that although this document assumes the presence of a single event loop in the system, there's nothing preventing the programmer to run more than one event loop, simply by following the create/start pattern above for each of them.
switches 0:5c4d7b2438d3 39
switches 0:5c4d7b2438d3 40 ## Using the event loop
switches 0:5c4d7b2438d3 41
switches 0:5c4d7b2438d3 42 Once the event loop is created, it can be used for posting events. Let's consider a very simple example of a program that attaches two interrupt handlers for an InterruptIn object, using the InterruptIn `rise` and `fall` functions. The `rise` handler will run in interrupt context, while the `fall` handler will run in user context (more specifically, in the context of the event loop's thread). The full code for the example can be found below:
switches 0:5c4d7b2438d3 43
switches 0:5c4d7b2438d3 44 ```
switches 0:5c4d7b2438d3 45 #include "mbed.h"
switches 0:5c4d7b2438d3 46 #include "mbed_events.h"
switches 0:5c4d7b2438d3 47
switches 0:5c4d7b2438d3 48 DigitalOut led1(LED1);
switches 0:5c4d7b2438d3 49 InterruptIn sw(SW2);
switches 0:5c4d7b2438d3 50 EventQueue queue(32 * EVENTS_EVENT_SIZE);
switches 0:5c4d7b2438d3 51 Thread t;
switches 0:5c4d7b2438d3 52
switches 0:5c4d7b2438d3 53 void rise_handler(void) {
switches 0:5c4d7b2438d3 54 printf("rise_handler in context %p\r\n", Thread::gettid());
switches 0:5c4d7b2438d3 55 // Toggle LED
switches 0:5c4d7b2438d3 56 led1 = !led1;
switches 0:5c4d7b2438d3 57 }
switches 0:5c4d7b2438d3 58
switches 0:5c4d7b2438d3 59 void fall_handler(void) {
switches 0:5c4d7b2438d3 60 printf("fall_handler in context %p\r\n", Thread::gettid());
switches 0:5c4d7b2438d3 61 // Toggle LED
switches 0:5c4d7b2438d3 62 led1 = !led1;
switches 0:5c4d7b2438d3 63 }
switches 0:5c4d7b2438d3 64
switches 0:5c4d7b2438d3 65 int main() {
switches 0:5c4d7b2438d3 66 // Start the event queue
switches 0:5c4d7b2438d3 67 t.start(callback(&queue, &EventQueue::dispatch_forever));
switches 0:5c4d7b2438d3 68 printf("Starting in context %p\r\n", Thread::gettid());
switches 0:5c4d7b2438d3 69 // The 'rise' handler will execute in IRQ context
switches 0:5c4d7b2438d3 70 sw.rise(rise_handler);
switches 0:5c4d7b2438d3 71 // The 'fall' handler will execute in the context of thread 't'
switches 0:5c4d7b2438d3 72 sw.fall(queue.event(fall_handler));
switches 0:5c4d7b2438d3 73 }
switches 0:5c4d7b2438d3 74
switches 0:5c4d7b2438d3 75 ```
switches 0:5c4d7b2438d3 76
switches 0:5c4d7b2438d3 77 The above code executes two handler functions (`rise_handler` and `fall_handler`) in two different contexts:
switches 0:5c4d7b2438d3 78
switches 0:5c4d7b2438d3 79 1. in interrupt context when a rising edge is detected on `SW2` (`rise_handler`).
switches 0:5c4d7b2438d3 80 2. in the context of the event loop's thread function when a falling edge is detected on `SW2` (`fall_handler`). `queue.event()` is called with `fall_handler` as an argument to specify that `fall_handler` will run in user context instead of interrupt context.
switches 0:5c4d7b2438d3 81
switches 0:5c4d7b2438d3 82 This is the output of the above program on a FRDM-K64F board after resetting the board and pressing the SW2 button twice:
switches 0:5c4d7b2438d3 83
switches 0:5c4d7b2438d3 84 ```
switches 0:5c4d7b2438d3 85 Starting in context 0x20002c50
switches 0:5c4d7b2438d3 86 fall_handler in context 0x20002c90
switches 0:5c4d7b2438d3 87 rise_handler in context 0x0
switches 0:5c4d7b2438d3 88 fall_handler in context 0x20002c90
switches 0:5c4d7b2438d3 89 rise_handler in context 0x0
switches 0:5c4d7b2438d3 90 ```
switches 0:5c4d7b2438d3 91
switches 0:5c4d7b2438d3 92 The program starts in the context of the thread that runs the `main` function (`0x29992c5`). When the uses presses SW2, `fall_handler` is automatically queued in the event queue, and it runs later in the context of thread `t` (`0x20002c90`). When the user releases the button, `rise_handler` is executed immediately, and it displays `0x0`, indicating that the code runs in interrupt context.
switches 0:5c4d7b2438d3 93
switches 0:5c4d7b2438d3 94 The code for `rise_handler` is problematic, since it calls `printf` in interrupt context, which is a potentially unsafe operation. Fortunately, this is exactly the kind of problem that event queues can solve. We can make the code safe by running `rise_handler` in user context (like we already do with `fall_handler`) by replacing this line:
switches 0:5c4d7b2438d3 95
switches 0:5c4d7b2438d3 96 ```
switches 0:5c4d7b2438d3 97 sw.rise(rise_handler);
switches 0:5c4d7b2438d3 98 ```
switches 0:5c4d7b2438d3 99
switches 0:5c4d7b2438d3 100 with this line:
switches 0:5c4d7b2438d3 101
switches 0:5c4d7b2438d3 102 ```
switches 0:5c4d7b2438d3 103 sw.rise(queue.event(rise_handler));
switches 0:5c4d7b2438d3 104 ```
switches 0:5c4d7b2438d3 105
switches 0:5c4d7b2438d3 106 The code is safe now, but we might've introduced another problem: latency. After the change above, the call to `rise_handler` will be queued, which means that it doesn't run immediately after the interrupt is raised anymore. For this example code, this isn't a problem, but some applications might require the code to respond as fast as possible to an interrupt. Let's assume that `rise_handler` must toggle the LED as quickly as possible in response to the user's action on SW2. To do that, in must run in interrupt context. However, `rise_handler` still needs to print a message indicating that the handler was called, but that's problematic since it's not safe to call `printf` from an interrupt context. The solution is to split `rise_handler` in two parts: the time critical part will run in interrupt context, while the non-critical part (displaying the message) will run in user context. This is easily doable using `queue.call`:
switches 0:5c4d7b2438d3 107
switches 0:5c4d7b2438d3 108 ```
switches 0:5c4d7b2438d3 109 void rise_handler_user_context(void) {
switches 0:5c4d7b2438d3 110 printf("rise_handler_user_context in context %p\r\n", Thread::gettid());
switches 0:5c4d7b2438d3 111 }
switches 0:5c4d7b2438d3 112
switches 0:5c4d7b2438d3 113 void rise_handler(void) {
switches 0:5c4d7b2438d3 114 // Execute the time critical part first
switches 0:5c4d7b2438d3 115 led1 = !led1;
switches 0:5c4d7b2438d3 116 // The rest can execute later in user context (and can contain code that's not interrupt safe).
switches 0:5c4d7b2438d3 117 // We use the 'queue.call' function to add an event (the call to 'rise_handler_user_context') to the queue.
switches 0:5c4d7b2438d3 118 queue.call(rise_handler_user_context);
switches 0:5c4d7b2438d3 119 }
switches 0:5c4d7b2438d3 120
switches 0:5c4d7b2438d3 121 ```
switches 0:5c4d7b2438d3 122
switches 0:5c4d7b2438d3 123 After replacing the code for `rise_handler` as above, the output of our example becomes:
switches 0:5c4d7b2438d3 124
switches 0:5c4d7b2438d3 125 ```
switches 0:5c4d7b2438d3 126 Starting in context 0x20002c50
switches 0:5c4d7b2438d3 127 fall_handler in context 0x20002c90
switches 0:5c4d7b2438d3 128 rise_handler_user_context in context 0x20002c90
switches 0:5c4d7b2438d3 129 fall_handler in context 0x20002c90
switches 0:5c4d7b2438d3 130 rise_handler_user_context in context 0x20002c90
switches 0:5c4d7b2438d3 131 ```
switches 0:5c4d7b2438d3 132
switches 0:5c4d7b2438d3 133 The scenario above (splitting an interrupt handler's code into time critical code and non-time critical code) is another common pattern that's easily implemented with event queues. Another thing to learn from this example is that queuing code that's not interrupt safe is not the only thing that event queues can be used for. Any kind of code can be queued and deferred for later execution.
switches 0:5c4d7b2438d3 134
switches 0:5c4d7b2438d3 135 We used `InterruptIn` for the example above, but the same kind of code can be used with any `attach()`-like functions in the SDK. Example include `Serial::attach()`, `Ticker::attach()`, `Ticker::attach_us()`, `Timeout::attach()`.
switches 0:5c4d7b2438d3 136
switches 0:5c4d7b2438d3 137 ## Where to go from here
switches 0:5c4d7b2438d3 138
switches 0:5c4d7b2438d3 139 We just scratched the surface of how event queues work in mbed OS. The `EventQueue` and `Event` classes in the `mbed-events` library offer a lot of features that are not covered in this document, including calling functions with arguments, queueing functions to be called after a delay, or queueing functions to be called periodically. The [README of the mbed-events library](https://github.com/ARMmbed/mbed-os/blob/master/events/README.md) shows more ways to use events and event queues. For more details about how the events library is implemented, check [this file](https://github.com/ARMmbed/mbed-os/blob/master/events/equeue/README.md).