japaric / f3

Board Support Crate for the STM32F3DISCOVERY
Apache License 2.0
95 stars 35 forks source link

async API #52

Closed japaric closed 7 years ago

japaric commented 8 years ago

Update

Everything has been updated to use futures: Timers, Serial, I2C, SPI and the sensors.

Documentation


this deprecates the delay module as the new Timer API can easily replicate the delay::ms functionality.

I want to try a few more complicated peripherals like I2C before deciding whether this makes sense or not. Probably will do an async version of the API of one the sensors as well.

cc @thejpster This may interest you. You can start by looking at the examples.

japaric commented 8 years ago

Crazy idea: async iprintln!. It would no longer be a macro though. Or rather it would a macro that returns a future ... :confounded:

thejpster commented 8 years ago

blink

I'm going to need to brush up on Futures and get back to you!

japaric commented 8 years ago

Something else to think about: error handling. The real Future trait returns Result from the poll method. We can test that here by having e.g. Bytes return an Error when the RX buffer is overrun.

japaric commented 8 years ago

And Timer can use ! (the bottom type) as its error type.

japaric commented 8 years ago

cc @jamesmunns This may also interest you. A futures-based nonblocking API for timers and serial (UART). BTW, do you know if the teensy C library has a nonblocking API that can I can look at for inspiration?

japaric commented 7 years ago

Update

Everything has been updated to use futures: Timers, Serial, I2C, SPI and the sensors.

Documentation

cc @istankovic There's a "session types" based I2C "concrete" (no traits) API. See the i2c module. It ended looking quite different from what I sketched before; the main reason is that, at least on the stm32f303 microcontroller, (a) along START you have to send the slave address and wheter you'll be reading/writing bytes across the wire and (b) you have to declare how many bytes you are going to read/write before you send START. The implication of all this is that you can't have and call a start(address) method (that changes the session type) and then decide whether you are going to read/write an arbitrary number of bytes to that device; it's just not possible. Instead the read/write methods must specify which slave they are going to deal with.

thejpster commented 7 years ago

Do you have a plan to get Future::wait to not spin tightly?

jamesmunns commented 7 years ago

Definitely interesting! I'll try and look more in depth ASAP.

@thejpster I haven't had my morning coffee yet, but basically the choices for wait would be:

  1. A tight loop (maybe with delays) or WFI waiting for an interrupt or callback to trigger a volatile flag marking "ready for processing"
  2. A real threaded environment (separated stacks, like what you see in most RTOSs), where the wait yields the flow
  3. A semi-real threaded environment (non separated stacks, like what you see in Contiki), where there is still a yield, kind of (state is preserved through static variables).

Probably the best implementation of 1 is a WFI that allows for low power modes, etc. There would be some kind of way to "register" or "check" if you have multiple pending tasks. Options 2 or 3 would require some kind of multithreading, and would more closely match how threading works on a desktop platform

japaric commented 7 years ago

@thejpster

Do you have a plan to get Future::wait to not spin tightly?

I don't intent to change Future::wait behavior as it's the simplest and cheapest (code size wise) way to transform async functions into blocking functions, which make sense to use in e.g. initialization code. (I might change its name to busy_wait though)

To avoid busy waiting at all in the main loop, I'm going to look into WFI to sleep when there's nothing to do. I'm hoping that the final API usage will look like this:

loop {
    let mut progress = false;
    // Try to advance tasks
    progress |= task1.advance();  // returns false if no progress was made
    progress |= task2.advance();
    (..)

    // If no task progressed
    if !progress {
        // call WFI and wait until an interrupt unblocks progress on some task
        sleep();
    }
}

I might have to change Async to look like this to make this work though:

enum Async<T> {
    /// Done
    Ready(T),
    /// Made some progress
    Progressed,
    Blocked,
}
homunkulus commented 7 years ago

:umbrella: The latest upstream changes (presumably ccba1d3) made this pull request unmergeable. Please resolve the merge conflicts.

istankovic commented 7 years ago

@japaric I really like this:

loop {
    let mut progress = false;
    // Try to advance tasks
    progress |= task1.advance();  // returns false if no progress was made
    progress |= task2.advance();
    (..)

    // If no task progressed
    if !progress {
        // call WFI and wait until an interrupt unblocks progress on some task
        sleep();
    }
}
japaric commented 7 years ago

All the API is nonblocking now. You can use the RTFM framework to do multitasking.