Development mbed library for MAX32630FTHR
Dependents: blinky_max32630fthr
events/equeue/README.md@3:1198227e6421, 2016-12-16 (annotated)
- Committer:
- switches
- Date:
- Fri Dec 16 16:27:57 2016 +0000
- Revision:
- 3:1198227e6421
- Parent:
- 0:5c4d7b2438d3
Changed ADC scale for MAX32625 platforms to 1.2V full scale to match MAX32630 platforms
Who changed what in which revision?
User | Revision | Line number | New contents of line |
---|---|---|---|
switches | 0:5c4d7b2438d3 | 1 | ## The equeue library ## |
switches | 0:5c4d7b2438d3 | 2 | |
switches | 0:5c4d7b2438d3 | 3 | The equeue library is designed as a simple but powerful library for scheduling |
switches | 0:5c4d7b2438d3 | 4 | events on composable queues. |
switches | 0:5c4d7b2438d3 | 5 | |
switches | 0:5c4d7b2438d3 | 6 | ``` c |
switches | 0:5c4d7b2438d3 | 7 | #include "equeue.h" |
switches | 0:5c4d7b2438d3 | 8 | #include <stdio.h> |
switches | 0:5c4d7b2438d3 | 9 | |
switches | 0:5c4d7b2438d3 | 10 | int main() { |
switches | 0:5c4d7b2438d3 | 11 | // creates a queue with space for 32 basic events |
switches | 0:5c4d7b2438d3 | 12 | equeue_t queue; |
switches | 0:5c4d7b2438d3 | 13 | equeue_create(&queue, 32*EQUEUE_EVENT_SIZE); |
switches | 0:5c4d7b2438d3 | 14 | |
switches | 0:5c4d7b2438d3 | 15 | // events can be simple callbacks |
switches | 0:5c4d7b2438d3 | 16 | equeue_call(&queue, print, "called immediately"); |
switches | 0:5c4d7b2438d3 | 17 | equeue_call_in(&queue, 2000, print, "called in 2 seconds"); |
switches | 0:5c4d7b2438d3 | 18 | equeue_call_every(&queue, 1000, print, "called every 1 seconds"); |
switches | 0:5c4d7b2438d3 | 19 | |
switches | 0:5c4d7b2438d3 | 20 | // events are executed in equeue_dispatch |
switches | 0:5c4d7b2438d3 | 21 | equeue_dispatch(&queue, 3000); |
switches | 0:5c4d7b2438d3 | 22 | |
switches | 0:5c4d7b2438d3 | 23 | print("called after 3 seconds"); |
switches | 0:5c4d7b2438d3 | 24 | |
switches | 0:5c4d7b2438d3 | 25 | equeue_destroy(&queue); |
switches | 0:5c4d7b2438d3 | 26 | } |
switches | 0:5c4d7b2438d3 | 27 | ``` |
switches | 0:5c4d7b2438d3 | 28 | |
switches | 0:5c4d7b2438d3 | 29 | The equeue library can be used as a normal event loop, or it can be |
switches | 0:5c4d7b2438d3 | 30 | backgrounded on a single hardware timer or even another event loop. It |
switches | 0:5c4d7b2438d3 | 31 | is both thread and irq safe, and provides functions for easily composing |
switches | 0:5c4d7b2438d3 | 32 | multiple queues. |
switches | 0:5c4d7b2438d3 | 33 | |
switches | 0:5c4d7b2438d3 | 34 | The equeue library can act as a drop-in scheduler, provide synchronization |
switches | 0:5c4d7b2438d3 | 35 | between multiple threads, or just act as a mechanism for moving events |
switches | 0:5c4d7b2438d3 | 36 | out of interrupt contexts. |
switches | 0:5c4d7b2438d3 | 37 | |
switches | 0:5c4d7b2438d3 | 38 | ## Documentation ## |
switches | 0:5c4d7b2438d3 | 39 | |
switches | 0:5c4d7b2438d3 | 40 | The in-depth documentation on specific functions can be found in |
switches | 0:5c4d7b2438d3 | 41 | [equeue.h](equeue.h). |
switches | 0:5c4d7b2438d3 | 42 | |
switches | 0:5c4d7b2438d3 | 43 | The core of the equeue library is the `equeue_t` type which represents a |
switches | 0:5c4d7b2438d3 | 44 | single event queue, and the `equeue_dispatch` function which runs the equeue, |
switches | 0:5c4d7b2438d3 | 45 | providing the context for executing events. |
switches | 0:5c4d7b2438d3 | 46 | |
switches | 0:5c4d7b2438d3 | 47 | On top of this, `equeue_call`, `equeue_call_in`, and `equeue_call_every` |
switches | 0:5c4d7b2438d3 | 48 | provide easy methods for posting events to execute in the context of the |
switches | 0:5c4d7b2438d3 | 49 | `equeue_dispatch` function. |
switches | 0:5c4d7b2438d3 | 50 | |
switches | 0:5c4d7b2438d3 | 51 | ``` c |
switches | 0:5c4d7b2438d3 | 52 | #include "equeue.h" |
switches | 0:5c4d7b2438d3 | 53 | #include "game.h" |
switches | 0:5c4d7b2438d3 | 54 | |
switches | 0:5c4d7b2438d3 | 55 | equeue_t queue; |
switches | 0:5c4d7b2438d3 | 56 | struct game game; |
switches | 0:5c4d7b2438d3 | 57 | |
switches | 0:5c4d7b2438d3 | 58 | // button_isr may be in interrupt context |
switches | 0:5c4d7b2438d3 | 59 | void button_isr(void) { |
switches | 0:5c4d7b2438d3 | 60 | equeue_call(&queue, game_button_update, &game); |
switches | 0:5c4d7b2438d3 | 61 | } |
switches | 0:5c4d7b2438d3 | 62 | |
switches | 0:5c4d7b2438d3 | 63 | // a simple user-interface framework |
switches | 0:5c4d7b2438d3 | 64 | int main() { |
switches | 0:5c4d7b2438d3 | 65 | equeue_create(&queue, 4096); |
switches | 0:5c4d7b2438d3 | 66 | game_create(&game); |
switches | 0:5c4d7b2438d3 | 67 | |
switches | 0:5c4d7b2438d3 | 68 | // call game_screen_udpate at 60 Hz |
switches | 0:5c4d7b2438d3 | 69 | equeue_call_every(&queue, 1000/60, game_screen_update, &game); |
switches | 0:5c4d7b2438d3 | 70 | |
switches | 0:5c4d7b2438d3 | 71 | // dispatch forever |
switches | 0:5c4d7b2438d3 | 72 | equeue_dispatch(&queue, -1); |
switches | 0:5c4d7b2438d3 | 73 | } |
switches | 0:5c4d7b2438d3 | 74 | ``` |
switches | 0:5c4d7b2438d3 | 75 | |
switches | 0:5c4d7b2438d3 | 76 | In addition to simple callbacks, an event can be manually allocated with |
switches | 0:5c4d7b2438d3 | 77 | `equeue_alloc` and posted with `equeue_post` to allow passing an arbitrary |
switches | 0:5c4d7b2438d3 | 78 | amount of context to the execution of the event. This memory is allocated out |
switches | 0:5c4d7b2438d3 | 79 | of the equeue's buffer, and dynamic memory can be completely avoided. |
switches | 0:5c4d7b2438d3 | 80 | |
switches | 0:5c4d7b2438d3 | 81 | The equeue allocator is designed to minimize jitter in interrupt contexts as |
switches | 0:5c4d7b2438d3 | 82 | well as avoid memory fragmentation on small devices. The allocator achieves |
switches | 0:5c4d7b2438d3 | 83 | both constant-runtime and zero-fragmentation for fixed-size events, however |
switches | 0:5c4d7b2438d3 | 84 | grows linearly as the quantity of differently-sized allocations increases. |
switches | 0:5c4d7b2438d3 | 85 | |
switches | 0:5c4d7b2438d3 | 86 | ``` c |
switches | 0:5c4d7b2438d3 | 87 | #include "equeue.h" |
switches | 0:5c4d7b2438d3 | 88 | |
switches | 0:5c4d7b2438d3 | 89 | equeue_t queue; |
switches | 0:5c4d7b2438d3 | 90 | |
switches | 0:5c4d7b2438d3 | 91 | // arbitrary data can be moved to a different context |
switches | 0:5c4d7b2438d3 | 92 | int enet_consume(void *buffer, int size) { |
switches | 0:5c4d7b2438d3 | 93 | if (size > 512) { |
switches | 0:5c4d7b2438d3 | 94 | size = 512; |
switches | 0:5c4d7b2438d3 | 95 | } |
switches | 0:5c4d7b2438d3 | 96 | |
switches | 0:5c4d7b2438d3 | 97 | void *data = equeue_alloc(&queue, 512); |
switches | 0:5c4d7b2438d3 | 98 | memcpy(data, buffer, size); |
switches | 0:5c4d7b2438d3 | 99 | equeue_post(&queue, handle_data_elsewhere, data); |
switches | 0:5c4d7b2438d3 | 100 | |
switches | 0:5c4d7b2438d3 | 101 | return size; |
switches | 0:5c4d7b2438d3 | 102 | } |
switches | 0:5c4d7b2438d3 | 103 | ``` |
switches | 0:5c4d7b2438d3 | 104 | |
switches | 0:5c4d7b2438d3 | 105 | Additionally, in-flight events can be cancelled with `equeue_cancel`. Events |
switches | 0:5c4d7b2438d3 | 106 | are given unique ids on post, allowing safe cancellation of expired events. |
switches | 0:5c4d7b2438d3 | 107 | |
switches | 0:5c4d7b2438d3 | 108 | ``` c |
switches | 0:5c4d7b2438d3 | 109 | #include "equeue.h" |
switches | 0:5c4d7b2438d3 | 110 | |
switches | 0:5c4d7b2438d3 | 111 | equeue_t queue; |
switches | 0:5c4d7b2438d3 | 112 | int sonar_value; |
switches | 0:5c4d7b2438d3 | 113 | int sonar_timeout_id; |
switches | 0:5c4d7b2438d3 | 114 | |
switches | 0:5c4d7b2438d3 | 115 | void sonar_isr(int value) { |
switches | 0:5c4d7b2438d3 | 116 | equeue_cancel(&queue, sonar_timeout_id); |
switches | 0:5c4d7b2438d3 | 117 | sonar_value = value; |
switches | 0:5c4d7b2438d3 | 118 | } |
switches | 0:5c4d7b2438d3 | 119 | |
switches | 0:5c4d7b2438d3 | 120 | void sonar_timeout(void *) { |
switches | 0:5c4d7b2438d3 | 121 | sonar_value = -1; |
switches | 0:5c4d7b2438d3 | 122 | } |
switches | 0:5c4d7b2438d3 | 123 | |
switches | 0:5c4d7b2438d3 | 124 | void sonar_read(void) { |
switches | 0:5c4d7b2438d3 | 125 | sonar_timeout_id = equeue_call_in(&queue, 300, sonar_timeout, 0); |
switches | 0:5c4d7b2438d3 | 126 | sonar_start(); |
switches | 0:5c4d7b2438d3 | 127 | } |
switches | 0:5c4d7b2438d3 | 128 | ``` |
switches | 0:5c4d7b2438d3 | 129 | |
switches | 0:5c4d7b2438d3 | 130 | From an architectural standpoint, event queues easily align with module |
switches | 0:5c4d7b2438d3 | 131 | boundaries, where internal state can be implicitly synchronized through |
switches | 0:5c4d7b2438d3 | 132 | event dispatch. |
switches | 0:5c4d7b2438d3 | 133 | |
switches | 0:5c4d7b2438d3 | 134 | On platforms where multiple threads are unavailable, multiple modules |
switches | 0:5c4d7b2438d3 | 135 | can use independent event queues and still be composed through the |
switches | 0:5c4d7b2438d3 | 136 | `equeue_chain` function. |
switches | 0:5c4d7b2438d3 | 137 | |
switches | 0:5c4d7b2438d3 | 138 | ``` c |
switches | 0:5c4d7b2438d3 | 139 | #include "equeue.h" |
switches | 0:5c4d7b2438d3 | 140 | |
switches | 0:5c4d7b2438d3 | 141 | // run a simultaneous localization and mapping loop in one queue |
switches | 0:5c4d7b2438d3 | 142 | struct slam { |
switches | 0:5c4d7b2438d3 | 143 | equeue_t queue; |
switches | 0:5c4d7b2438d3 | 144 | }; |
switches | 0:5c4d7b2438d3 | 145 | |
switches | 0:5c4d7b2438d3 | 146 | void slam_create(struct slam *s, equeue_t *target) { |
switches | 0:5c4d7b2438d3 | 147 | equeue_create(&s->queue, 4096); |
switches | 0:5c4d7b2438d3 | 148 | equeue_chain(&s->queue, target); |
switches | 0:5c4d7b2438d3 | 149 | equeue_call_every(&s->queue, 100, slam_filter); |
switches | 0:5c4d7b2438d3 | 150 | } |
switches | 0:5c4d7b2438d3 | 151 | |
switches | 0:5c4d7b2438d3 | 152 | // run a sonar with it's own queue |
switches | 0:5c4d7b2438d3 | 153 | struct sonar { |
switches | 0:5c4d7b2438d3 | 154 | equeue_t equeue; |
switches | 0:5c4d7b2438d3 | 155 | struct slam *slam; |
switches | 0:5c4d7b2438d3 | 156 | }; |
switches | 0:5c4d7b2438d3 | 157 | |
switches | 0:5c4d7b2438d3 | 158 | void sonar_create(struct sonar *s, equeue_t *target) { |
switches | 0:5c4d7b2438d3 | 159 | equeue_create(&s->queue, 64); |
switches | 0:5c4d7b2438d3 | 160 | equeue_chain(&s->queue, target); |
switches | 0:5c4d7b2438d3 | 161 | equeue_call_in(&s->queue, 5, sonar_update, s); |
switches | 0:5c4d7b2438d3 | 162 | } |
switches | 0:5c4d7b2438d3 | 163 | |
switches | 0:5c4d7b2438d3 | 164 | // all of the above queues can be combined into a single thread of execution |
switches | 0:5c4d7b2438d3 | 165 | int main() { |
switches | 0:5c4d7b2438d3 | 166 | equeue_t queue; |
switches | 0:5c4d7b2438d3 | 167 | equeue_create(&queue, 1024); |
switches | 0:5c4d7b2438d3 | 168 | |
switches | 0:5c4d7b2438d3 | 169 | struct sonar s1, s2, s3; |
switches | 0:5c4d7b2438d3 | 170 | sonar_create(&s1, &queue); |
switches | 0:5c4d7b2438d3 | 171 | sonar_create(&s2, &queue); |
switches | 0:5c4d7b2438d3 | 172 | sonar_create(&s3, &queue); |
switches | 0:5c4d7b2438d3 | 173 | |
switches | 0:5c4d7b2438d3 | 174 | struct slam slam; |
switches | 0:5c4d7b2438d3 | 175 | slam_create(&slam, &queue); |
switches | 0:5c4d7b2438d3 | 176 | |
switches | 0:5c4d7b2438d3 | 177 | // dispatches events from all of the modules |
switches | 0:5c4d7b2438d3 | 178 | equeue_dispatch(&queue, -1); |
switches | 0:5c4d7b2438d3 | 179 | } |
switches | 0:5c4d7b2438d3 | 180 | ``` |
switches | 0:5c4d7b2438d3 | 181 | |
switches | 0:5c4d7b2438d3 | 182 | ## Platform ## |
switches | 0:5c4d7b2438d3 | 183 | |
switches | 0:5c4d7b2438d3 | 184 | The equeue library has a minimal porting layer that is flexible depending |
switches | 0:5c4d7b2438d3 | 185 | on the requirements of the underlying platform. Platform specific declarations |
switches | 0:5c4d7b2438d3 | 186 | and more information can be found in [equeue_platform.h](equeue_platform.h). |
switches | 0:5c4d7b2438d3 | 187 | |
switches | 0:5c4d7b2438d3 | 188 | ## Tests ## |
switches | 0:5c4d7b2438d3 | 189 | |
switches | 0:5c4d7b2438d3 | 190 | The equeue library uses a set of local tests based on the posix implementation. |
switches | 0:5c4d7b2438d3 | 191 | |
switches | 0:5c4d7b2438d3 | 192 | Runtime tests are located in [tests.c](tests/tests.c): |
switches | 0:5c4d7b2438d3 | 193 | |
switches | 0:5c4d7b2438d3 | 194 | ``` bash |
switches | 0:5c4d7b2438d3 | 195 | make test |
switches | 0:5c4d7b2438d3 | 196 | ``` |
switches | 0:5c4d7b2438d3 | 197 | |
switches | 0:5c4d7b2438d3 | 198 | Profiling tests based on rdtsc are located in [prof.c](tests/prof.c): |
switches | 0:5c4d7b2438d3 | 199 | |
switches | 0:5c4d7b2438d3 | 200 | ``` bash |
switches | 0:5c4d7b2438d3 | 201 | make prof |
switches | 0:5c4d7b2438d3 | 202 | ``` |
switches | 0:5c4d7b2438d3 | 203 | |
switches | 0:5c4d7b2438d3 | 204 | To make profiling results more tangible, the profiler also supports percentage |
switches | 0:5c4d7b2438d3 | 205 | comparison with previous runs: |
switches | 0:5c4d7b2438d3 | 206 | ``` bash |
switches | 0:5c4d7b2438d3 | 207 | make prof | tee results.txt |
switches | 0:5c4d7b2438d3 | 208 | cat results.txt | make prof |
switches | 0:5c4d7b2438d3 | 209 | ``` |
switches | 0:5c4d7b2438d3 | 210 |