You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: README.md
+182-4Lines changed: 182 additions & 4 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -15,7 +15,9 @@ As many Arduino boards are powered by AVR micro-controllers, this library can be
15
15
16
16
# Application Programming Interface
17
17
18
-
## Data Types
18
+
## Context Switching
19
+
20
+
### Data Types
19
21
20
22
```
21
23
avr_context_t
@@ -24,7 +26,7 @@ avr_context_t
24
26
The `avr_context_t` represents a machine context. It should be treated as an opaque data type.
25
27
26
28
27
-
## Functions
29
+
###Functions
28
30
29
31
The four functions `avr_getcontext()`, `avr_setcontext()`, `avr_swapcontext()`, and `avr_makecontext()` provide facility for context switching between multiple threads of execution.
30
32
@@ -63,7 +65,7 @@ The function `avr_makecontext()` modifies the context obtained by a call to `avr
63
65
64
66
Before invoking the `avr_makecontext()`, the caller must allocate a new stack for the modifiable context and pass pointer to it (`stackp`) and the size of the memory region (`stack_size`).
As these macros implemented on top of the `AVR_SAVE_CONTEXT` and `AVR_RESTORE_CONTEXT`, please make sure that you understand how they work.
115
117
118
+
## Coroutines
119
+
120
+
There are four functions that implement the asymmetric stackful coroutine facility: `avr_coro_init()`, `avr_coro_resume()`, `avr_coro_yield()`, `avr_coro_state()`. They are implemented on top of the context switching facility.
121
+
122
+
A coroutine is represented by the `avr_coro_t` opaque data type. A coroutine can be in one of the following states.
123
+
124
+
1. Suspended
125
+
2. Running
126
+
3. Dead
127
+
128
+
All of the functions return `0` on success or `1` on failure with one notable exception: `avr_coro_state()` returns either a state of a coroutine (represented as a member of the `avr_coro_state_t` data type), or `AVR_CORO_ILLEGAL` status on failure.
129
+
130
+
Failure status usually means that a wrong argument has been passed to the function.
131
+
132
+
### Data Types
133
+
134
+
```
135
+
avr_coro_t
136
+
```
137
+
138
+
An opaque data type which represents a coroutine.
139
+
140
+
### Functions
141
+
142
+
```
143
+
int avr_coro_init(avr_coro_t *coro,
144
+
void *stackp, const size_t stack_size,
145
+
avr_coro_func_t func);
146
+
```
147
+
The function `avr_coro_init()` initialises a coroutine, represented by a structure pointed at by `coro`. The coroutine gets initialised in the suspended state. Upon resumption, the function `func` gets called with two arguments applied: a pointer to the `avr_coro_t` structure itself (`self`), an argument passed on at first resumption.
148
+
149
+
150
+
```
151
+
int avr_coro_resume(avr_coro_t *coro, void **data);
152
+
int avr_coro_yield(avr_coro_t *self, void **data);
153
+
```
154
+
The function `avr_coro_resume()` resumes execution of a coroutine, represented by a structure pointed at by `coro.` The coroutine might return control to the invoker by calling the function `avr_coro_yield()`. It accepts a currently running coroutine represented by a structure pointed at by `self`.
155
+
156
+
Both of the functions accept pointer to a pointer as the last argument (`data`). The coroutine and its invoker can exchange data using this last argument. When the coroutine gets resumed using `avr_coro_resume()` for the first time, a value of the pointer, to which `data` points, gets passed as the second argument to the coroutine function.
157
+
158
+
After the call to the avr_coro_resume() returns, the pointer to which `data` points during the call, gets initialised to the value, correspondingly passed as the last argument of `avr_coro_yield()`.
159
+
160
+
Symmetrically, after the call to the `avr_coro_yield()` returns, the pointer to which `data` points during the call, gets initialised to the value correspondingly passed as the last argument of `avr_coro_resume()`.
161
+
162
+
When the coroutine's function returns (and, thus, becomes dead), the returned value initialises the pointer to which `data` points during the corresponding `avr_coro_resume()` call.
163
+
164
+
If there is no need to exchange data between the coroutine and its invoker, one can pass the `NULL` value as the `data` argument to `avr_coro_resume()`/`avr_coro_yield()`.
The function `avr_coro_state()` returns the current state of a coroutine:
180
+
181
+
1. If the coroutine is suspended, the function returns `AVR_CORO_SUSPENDED`.
182
+
2. If the coroutine is running, the function returns `AVR_CORO_RUNNING`. This value can be obtained only from within the context of the currently running coroutine.
183
+
3. If the coroutine is dead, the function returns `AVR_CORO_DEAD`.
184
+
185
+
The value `AVR_CORO_ILLEGAL` gets returned in the case of error (e.g. the `NULL` value was passed instead of a pointer to a coroutine).
186
+
116
187
# Usage
117
188
118
189
## Arduino
@@ -167,7 +238,9 @@ Every example below is a complete Arduino sketch. It was decided to use Arduino
167
238
168
239
Nevertheless, this library can be used on its own on AVR controllers: it does not contain any Arduino-specific code. Hopefully, the examples are easy to adapt to any AVR based hardware.
169
240
170
-
## [GOTO via Context Switching](./examples/Context_Switching/01.GOTO_via_Context_Switching/01.GOTO_via_Context_Switching.ino)
241
+
## Context Switching
242
+
243
+
### [GOTO via Context Switching](./examples/Context_Switching/01.GOTO_via_Context_Switching/01.GOTO_via_Context_Switching.ino)
171
244
172
245
This example demonstrates how `avr_getcontext()` and `avr_setcontext()` could be used to emulate the `GOTO` operator.
173
246
@@ -234,6 +307,111 @@ Please keep in mind that our intention here is to show how task switching can be
234
307
235
308
Nevertheless, if you are brave enough to write your own RTOS, this tiny sketch might be a good start.
This example demonstrates how an asymmetric coroutine might be used as a generator. Every time the coroutine gets invoked it generates the next number in sequence (`0...N`) and returns it back to the invoker.
315
+
316
+
One of the important points of this example is to demonstrate how a value from the coroutine can be passed back to the invoker.
317
+
318
+
When being uploaded to an Arduino board, this sketch print numbers in increasing order via serial port every one second, e.g.:
This example demonstrates how one could implement symmetric coroutines on top of the asymmetric ones.
335
+
336
+
The most important difference between the symmetric and asymmetric coroutines is that the asymmetric ones cannot pass control to the other coroutine directly: they can return control only back to the coroutine invoker. Nevertheless, they are no less powerful.
337
+
338
+
To bypass the above-mentioned limitation, a coroutine, which yields control, passes the information about the coroutine which it wants to activate to a dispatching loop. The loop, in turn, activates the next coroutine.
339
+
340
+
It is a version of the other example, which was implemented directly on top of `avr_makecontext()`, `avr_swapcontext()`.
341
+
342
+
When being uploaded to an Arduino board, this sketch produces the following (or very similar) output via serial port every two seconds:
This example demonstrates how asymmetric coroutines can be used to solve a well known "producer-consumer" problem.
364
+
365
+
Coroutines communicate using the `send()` and `receive()` functions. `send()` passes a value to the consumer and resumes it, `receive()` gets value from the producer and returns control back to it. These actions are repeated forever.
366
+
367
+
It is a consumer-driven design because the program starts by calling the consumer. When the consumer needs an item, it resumes the producer, which runs until it has an item to give to the consume, and then pauses until the consumer restarts it again.
368
+
369
+
When being uploaded to an Arduino board, this sketch produces a pair of messages via serial port every one second in the following format:
This example demonstrates how asymmetric coroutines can be used to solve the "producer-filter-consumer" problem (one can also call the filter as "mediator").
387
+
388
+
Coroutines communicate using the `send()` and `receive()` functions. The function `send()` passes a value to the receiver and returns control back to the invoker, the function `receive()` gets value from the sender and resumes it.
389
+
390
+
It is a consumer-driven design because the program starts by calling a consumer. The consumer receives a value from a filter and thus resumes it. Then the filter receives the value from a producer (again, by resuming it) and doubles it. After that, the filter sends the doubled value to the consumer. By doing so it resumes the consumer. Then the whole process repeats.
391
+
392
+
When being uploaded to an Arduino board, this sketch produces a triplet of messages via serial port every one second in the following format.
0 commit comments