Skip to content

Commit 5f9cb9b

Browse files
committed
[resumable] Deprecate resumable functions
1 parent 2658292 commit 5f9cb9b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+395
-282
lines changed

README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@ git clone --recurse-submodules --jobs 8 https://github.com/modm-io/modm.git
6161
- UART, I<sup>2</sup>C, SPI, CAN and Ethernet.
6262
- Interfaces and drivers for many external I<sup>2</sup>C and SPI sensors and devices.
6363
- Debug/logging system with IOStream and printf interface.
64-
- Cooperative, stackless protothreads and resumable functions.
6564
- Cooperative, stackful fibers and scheduler.
6665
- Functional (partial) libstdc++ implementation for AVRs.
6766
- Useful filter, interpolation and geometric algorithms.

docs/src/how-modm-works.md

Lines changed: 31 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -372,64 +372,47 @@ Heap: 197240B (98.2% available)
372372

373373
<!-- (⚡️ requires module documentation for protothread/resumable) -->
374374

375-
modm uses stackless cooperative multitasking, for which we have ported
376-
protothreads to C++ and extended them with resumable functions.
375+
modm implements stackful, cooperative fibers and provides a concurrency
376+
support library based on the C++ interface.
377377
This enables you to split up your application into separate tasks, and use
378378
synchronous APIs in all of them, without sacrificing overall responsiveness.
379-
This works on even the most resource restricted AVRs, since each task only
380-
requires 2 bytes of static RAM!
381-
382-
All our IC drivers are implemented using resumable functions, which can be
383-
called from within protothreads or explicitly blocking outside of them.
384379
Here is an example of [reading out the accelerometer][accel]:
385380

386381
```cpp
387-
class ReaderThread : public modm::pt::Protothread
382+
// This accelerometer is connected via I2C.
383+
modm::Lis3dsh< modm::Lis3TransportI2c< I2cMaster > > accelerometer;
384+
modm::filter::MovingAverage<float, 25> averageX;
385+
modm::filter::MovingAverage<float, 25> averageY;
386+
modm::Fiber fiber_sensor([]
388387
{
389-
public:
390-
bool run()
388+
// The driver does several I2C transfer here to initialize and configure the
389+
// external sensor. The CPU is free to do other things while this happens though.
390+
accelerometer.configure(accelerometer.Scale::G2);
391+
392+
while (true)
391393
{
392-
PT_BEGIN();
393-
// The driver does several I2C transfer here to initialize and configure the
394-
// external sensor. The CPU is free to do other things while this happens though.
395-
PT_CALL(accelerometer.configure(accelerometer.Scale::G2));
396-
397-
while (true) // this feels quite similar to regular threads
398-
{
399-
// this resumable function will defer execution back to other protothreads
400-
PT_CALL(accelerometer.readAcceleration());
401-
402-
// smooth out the acceleration data a little bit
403-
averageX.update(accelerometer.getData().getX());
404-
averageY.update(accelerometer.getData().getY());
405-
406-
// set the boards LEDs depending on the acceleration values
407-
LedUp::set( averageX.getValue() < -0.2);
408-
LedDown::set( averageX.getValue() > 0.2);
409-
LedLeft::set( averageY.getValue() < -0.2);
410-
LedRight::set(averageY.getValue() > 0.2);
411-
412-
// defer back to other protothreads until the timer fires
413-
PT_WAIT_UNTIL(timer.execute());
414-
}
415-
PT_END();
394+
// More I2C transfers in the background
395+
accelerometer.readAcceleration();
396+
397+
// smooth out the acceleration data a little bit
398+
averageX.update(accelerometer.getData().getX());
399+
averageY.update(accelerometer.getData().getY());
400+
401+
// set the boards LEDs depending on the acceleration values
402+
LedUp::set( averageX.getValue() < -0.2);
403+
LedDown::set( averageX.getValue() > 0.2);
404+
LedLeft::set( averageY.getValue() < -0.2);
405+
LedRight::set(averageY.getValue() > 0.2);
406+
407+
// defer back to other fiber while this one sleeps
408+
modm::this_fiber::sleep_for(5ms);
416409
}
417-
private:
418-
// This accelerometer is connected via I2C.
419-
modm::Lis3dsh< modm::Lis3TransportI2c< I2cMaster > > accelerometer;
420-
modm::PeriodicTimer timer = modm::PeriodicTimer(5); // 5ms periodic timer.
421-
modm::filter::MovingAverage<float, 25> averageX;
422-
modm::filter::MovingAverage<float, 25> averageY;
423-
};
424-
ReaderThread reader; // Protothread is statically allocated!
410+
});
425411

426-
int main() // Execution entry point.
412+
int main()
427413
{
428-
while(true)
429-
{ // the main loop with implicit round robin cooperative scheduling.
430-
reader.run();
431-
otherProtothreads.run();
432-
}
414+
Board::initialize();
415+
modm::fiber::Scheduler::run();
433416
return 0;
434417
}
435418
```

src/modm/processing/fiber/functions.hpp renamed to src/modm/architecture/interface/fiber.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,15 @@ namespace modm::fiber
2121
{
2222

2323
/// Identifier of a fiber task.
24-
/// @ingroup modm_processing_fiber
24+
/// @ingroup modm_architecture_fiber
2525
using id = uintptr_t;
2626

2727
} // namespace modm::fiber
2828

2929
namespace modm::this_fiber
3030
{
3131

32-
/// @ingroup modm_processing_fiber
32+
/// @ingroup modm_architecture_fiber
3333
/// @{
3434

3535
/**
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# Fiber Interface
2+
3+
This module provides an interface to yield control back to a scheduler. The
4+
basic functionality is provided by the `yield()` function which transparently
5+
gives control back to the scheduler and returns afterwards. It is particularly
6+
important to call yield in long running loops to prevent the system from locking
7+
up and preventing other fibers from making progress:
8+
9+
```cpp
10+
while(true)
11+
{
12+
// run your code here
13+
// but always yield to other fibers whenever possible
14+
modm::this_fiber::yield();
15+
}
16+
```
17+
18+
For convenience a `poll()` function is provided that can be used to yield until
19+
a condition is met:
20+
21+
```cpp
22+
modm::this_fiber::poll([&]{ return condition; });
23+
```
24+
25+
An extension of this concept is provided by the `poll_for()` and `poll_until()`
26+
functions, which yield until the condition is met or until a timeout occurs:
27+
28+
```cpp
29+
bool condition_met = modm::this_fiber::poll_for(1s, [&]{ return condition; });
30+
// if (not condition_met) condition did not return true for 1s.
31+
```
32+
33+
If microseconds are passed for the duration, the functions use the
34+
`modm::chrono::micro_clock` (= `modm::PreciseClock`), otherwise they use
35+
`modm::chrono::milli_clock` (= `modm::Clock`). This requires that these clocks
36+
are already initialized and running.
37+
38+
These basic building blocks are then used to implement the `sleep_for()` and
39+
`sleep_until()` convenience functions:
40+
41+
```cpp
42+
modm::this_fiber::sleep_for(1s);
43+
```
44+
45+
46+
## Implementation
47+
48+
The `yield()` function is implemented by the `modm:processing:fiber` module
49+
which provides a cooperative multitasking scheduler that is able to switch
50+
between multiple fiber contexts.
51+
52+
If `yield()` is called outside of a fiber context, for example, in the `main()`
53+
function when the scheduler is not yet running, `yield()` will return in-place.
54+
This mechanism allows for a graceful fallback to a blocking API without changes
55+
to the code using `yield()`.
56+
57+
```cpp
58+
modm::Fiber fiber_nonblocking([]
59+
{
60+
modm::Timeout timeout(100ms);
61+
timeout.wait(); // non-blocking call!
62+
});
63+
int main()
64+
{
65+
modm::Timeout timeout(100ms);
66+
timeout.wait(); // blocking call!
67+
68+
modm::fiber::Scheduler::run()
69+
return 0;
70+
}
71+
```
72+
73+
This mechanism also supports running modm on devices with very small memories
74+
where a stackful scheduler may be to resource intensive:
75+
The `modm:processing:fiber` module is strictly opt-in and if not selected the
76+
scheduler is not included and the `yield()` function is implemented as an empty
77+
stub while still allowing for the whole API to be used without changes:
78+
79+
```cpp
80+
modm::Lis3mdl<I2cMaster1> sensor{};
81+
int main()
82+
{
83+
sensor.configure(); // blocking but works just fine
84+
while(true)
85+
{
86+
modm::Vector3f magVector;
87+
sensor.readMagnetometer(magVector); // another blocking call
88+
modm::this_fiber::sleep_for(10ms); // this will wait in place
89+
}
90+
}
91+
```
92+
93+
Therefore, if you use these functions in your code, only depend on
94+
`modm:architecture:fiber` and let the user decide on the implementation by
95+
including `modm:processing:fiber` or not. This compromise allows for a seamless
96+
transition between different devices and scheduling strategies.
97+
98+
99+
## Identifier
100+
101+
You can check what fiber your code is executed in by calling the `get_id()`
102+
function:
103+
104+
```cpp
105+
auto id = modm::this_fiber::get_id();
106+
// if (id == 0) called outside a fiber
107+
// if (id < 1024) called from inside an interrupt
108+
// else called from inside a fiber
109+
```
110+
111+
The returned ID is the address of the currently running fiber object. If called
112+
outside of a fiber, for example, in the main function before the scheduler is
113+
running, the function returns `0`. If called inside a ARM Cortex-M interrupt,
114+
the ID returns the IRQ number. The implementation ensures that all returned
115+
values are unique and thus allow the ID to be used for tracking ownership of
116+
various recursive locks, for example.

src/modm/architecture/interface/spi_master.hpp

Lines changed: 9 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -68,23 +68,23 @@ class SpiMaster : public ::modm::PeripheralDriver, public Spi
6868
setDataOrder(DataOrder order);
6969

7070
/**
71-
* Request access to the spi master within a context.
72-
* You may acquire the spi master multiple times within the same context.
71+
* Request access to the SPI master within a context.
72+
* You may acquire the SPI master multiple times within the same context.
7373
*
74-
* The configuration handler will only be called when aquiring the spi
74+
* The configuration handler will only be called when acquiring the SPI
7575
* master for the first time (if it is not a `nullptr`).
7676
*
77-
* @warning Aquires must be balanced with releases of the **same** context!
78-
* @warning Aquires are persistent even after calling `initialize()`!
77+
* @warning Acquires must be balanced with releases of the **same** context!
78+
* @warning Acquires are persistent even after calling `initialize()`!
7979
*
80-
* @return `0` if another context is using the spi master, otherwise
80+
* @return `0` if another context is using the SPI master, otherwise
8181
* `>0` as the number of times this context acquired the master.
8282
*/
8383
static uint8_t
8484
acquire(void *ctx, ConfigurationHandler handler = nullptr);
8585

8686
/**
87-
* Release access to the spi master within a context.
87+
* Release access to the SPI master within a context.
8888
*
8989
* @warning Releases must be balanced with acquires of the **same** context!
9090
* @warning Releases are persistent even after calling `initialize()`!
@@ -95,37 +95,10 @@ class SpiMaster : public ::modm::PeripheralDriver, public Spi
9595
static uint8_t
9696
release(void *ctx);
9797

98-
/**
99-
* Swap a single byte and wait for completion.
100-
*
101-
* @param data
102-
* data to be sent
103-
* @return received data
104-
*/
105-
static uint8_t
106-
transferBlocking(uint8_t data);
107-
108-
/**
109-
* Set the data buffers and length with options and starts a transfer.
110-
* This may be hardware accelerated (DMA or Interrupt), but not guaranteed.
111-
*
112-
* @param[in] tx
113-
* pointer to transmit buffer, set to `nullptr` to send dummy bytes
114-
* @param[out] rx
115-
* pointer to receive buffer, set to `nullptr` to discard received bytes
116-
* @param length
117-
* number of bytes to be shifted out
118-
*/
119-
static void
120-
transferBlocking(const uint8_t *tx, uint8_t *rx, std::size_t length);
121-
12298
/**
12399
* Swap a single byte and wait for completion non-blocking!.
124100
*
125-
* You must call this inside a Protothread or Resumable
126-
* using `PT_CALL` or `RF_CALL` respectively.
127-
* @warning These methods differ from Resumables by lacking context protection!
128-
* You must ensure that only one driver is accessing this resumable function
101+
* @warning You must ensure that only one driver is accessing this resumable function
129102
* by using `acquire(ctx)` and `release(ctx)`.
130103
*
131104
* @param data
@@ -140,10 +113,7 @@ class SpiMaster : public ::modm::PeripheralDriver, public Spi
140113
* starts a non-blocking transfer.
141114
* This may be hardware accelerated (DMA or Interrupt), but not guaranteed.
142115
*
143-
* You must call this inside a Protothread or Resumable
144-
* using `PT_CALL` or `RF_CALL` respectively.
145-
* @warning These methods differ from Resumables by lacking context protection!
146-
* You must ensure that only one driver is accessing this resumable function
116+
* @warning You must ensure that only one driver is accessing this resumable function
147117
* by using `acquire(ctx)` and `release(ctx)`.
148118
*
149119
* @param[in] tx

src/modm/architecture/module.lb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,20 @@ class Delay(Module):
170170
env.copy("interface/delay.hpp")
171171
# -----------------------------------------------------------------------------
172172

173+
class Fiber(Module):
174+
def init(self, module):
175+
module.name = "fiber"
176+
module.description = FileReader("interface/fiber.md")
177+
178+
def prepare(self, module, options):
179+
module.depends(":stdc++", ":architecture:clock", ":processing")
180+
return True
181+
182+
def build(self, env):
183+
env.outbasepath = "modm/src/modm/architecture"
184+
env.copy("interface/fiber.hpp")
185+
# -----------------------------------------------------------------------------
186+
173187
class Gpio(Module):
174188
def init(self, module):
175189
module.name = "gpio"
@@ -389,6 +403,7 @@ def prepare(module, options):
389403
module.add_submodule(Can())
390404
module.add_submodule(Clock())
391405
module.add_submodule(Delay())
406+
module.add_submodule(Fiber())
392407
module.add_submodule(Gpio())
393408
module.add_submodule(GpioExpander())
394409
module.add_submodule(I2c())

0 commit comments

Comments
 (0)