rust-community / novemb.rs

A distributed hack event for Rustceans in Africa/Europe
http://novemb.rs
7 stars 9 forks source link

Project idea: better embedded driver support #4

Open thejpster opened 7 years ago

thejpster commented 7 years ago

How about working on some embedded dev boards and building up some driver support crates? UARTs, SPI, PWM, that sort of thing. I'm thinking things along the line of japaric's F3, or my Launchpad/LM4F120. Looking at a variety of hardware platforms might help #rust-embedded work towards a common API for common peripherals.

If you get the right boards, they're relatively cheap. I just picked up some Kinetis Cortex-M0+ boards (with 5V I/O!!) for about £10. TI Launchpads are about the same. Raspberry Pi Zeros even. You might get a sponsor to cover the cost, and developers can take the boards away at the end to carry on working.

@japaric has created an Etherpad for elaborating on some of the ideas here. Check it out.

skade commented 7 years ago

That sounds like a cool idea and there's still some time for people to buy boards. Would you like to be personally involved? (Maybe even on site in Cambridge or London, maybe. In London, we are still specifically searching for people, we might have a room at Mozilla London) 😉

thejpster commented 7 years ago

I would like to be involved but I need to check availability for that weekend. Cambridge would be better than London (I started https://github.com/rust-community/novemb.rs/issues/5 for that).

skade commented 7 years ago

I edited the title to better express what the idea is. I hope thats a good one, feel free to change if you come up with a better one.

japaric commented 7 years ago

Thanks to Rust Belt Rust (shameless plug) we got around 15 rustaceans with the right hardware (STM32F3DISCOVERY boards). I've informed all of them about this. I don't know how many of them are in Europe or can be in Europe for the date of novemb.rs but perhaps some of them can join remotely.

I'm not in Europe but would love to participate remotely during the hours that are not crazy for me (I'm on UTC -5)

skade commented 7 years ago

@japaric they are readily available at German resellers. I'm pretty sure DHL can make us tremble in fear, though. The problem is more of a budget one, if we want to supply people with them.

thejpster commented 7 years ago

I'd like to make sure we get a spread of different hardware available too. By implementing several UART drivers for several boards, we can hopefully work out what a generic UART trait might look like. There are many boards using Cortex-M4 based microcontrollers available at the £10-£15 price point.

SimonSapin commented 7 years ago

Where C or C++ drivers exist, https://github.com/servo/rust-bindgen could be used to generate Rust bindings for them. I've had success doing this for Teensy hardware: https://github.com/jamesmunns/teensy3-rs

One downside is that FFI functions require unsafe to call, and constants generated from #define may require casting with as.

japaric commented 7 years ago

@SimonSapin I just took a look at the teensy3 crate and it seems it only exposes a blocking API. Do you know if the underlying C/C++ libraries expose an async/non-blocking API or if/how the Teensy API can be used to run concurrent tasks?

The other issue I see there is that the documentation says that e.g. Serial is supposed to be a singleton but it exposes it a struct that the user can create many instances of. This is probably not a problem for the teensy3 crate right now because it doesn't (seem to) expose async IO, interrupts or threads/tasks but once you introduce any of those then you'll realize that you want to enforce peripheral "ownership" in your API or at very least expose peripherals as Mutexed globals.

Those are the three topics I'd like to discuss during this event: async IO, "ownership" of hardware and dealing with interrupts. FWIW, I've begun to experiment with futures-based async IO in my f3 crate in this branch.

I don't have experience with production embedded systems so I don't know how C applications dealt with those (callbacks?) but I hope that someone (@thejpster?) who has the experience shows up at the event because it would be illuminating.

thejpster commented 7 years ago

So, to take the UART as an example, they vary across the embedded systems I've worked on.

At the fundamental level they all implement a putc and a getchar (but probably not called that). They then may have a string write routine which calls putc in a loop. Both putc and getchar will block. There may be a 'haschar' function which is non-blocking. The problem with haschar is that the system can't efficiently poll the UART - you often want your system blocked in the wfi instruction (wait for interrupt) to save power, rather than spinning madly.

In addition, more advanced systems (most of the ones I've worked on) can't afford the character drops that will ensue as they don't usually have automatic hardware flow control wired up. To fix this they will remove characters under interrupt from the receive buffer into a largish software FIFO. There may also be an outgoing FIFO so that println and friends at least manage to get some of the output buffered and so block for less time. The API is unchanged. The FIFOs implement atomicity by disabling interrupts around critical sections. Main loops can sit in WFI because an interrupt will occur whenever data arrives.

The next 'level' if you like is the multitasking system. This is a system where every layer in the protocol stack (I make radios/modems) is a task and they message pass requests, confirmations and indications between them. Here, we'll get the UART low level ISR to trigger a high level ISR, and get the high level ISR to either trigger an event in an OS flag group that another task is pending on (as well as put the received bytes in a buffer) or to put the bytes in a 'data received indication' message sent to the registered handler. This means that, say, the command line interpreter task, or the PPP task, is blocked by the OS waiting for new data and not wasting CPU spinning.

In terms of ownership, this is just managed by only including the header if you need access to the UART. For simpler systems, access is through the C library functions because we'd map stdio and stdout (or maybe some other file descriptors) to a UART. There is no 'object' - it's all global data. Where I've written C++ UART drivers, it's easy to get tired of passing the UART object around, and if the object is a global you have the problem of ensuring the constructor runs before anyone that needs the UART.

Multiple UARTs in C would be handled by passing the UART number as the first argument to all the functions.

So, how do I think UARTs should look in Rust? Well, working out how to fill a receive FIFO under interrupt is high on my list. This will probably involve some sort of interrupt disabling Mutex that you can also grab from interrupt context. Likewise for transmit. Currently I implement a C++ style object approach but I'm not sure how to implement a println! which can be called anywhere. Maybe lazy-static is my friend here.

SimonSapin commented 7 years ago

@japaric For Serial specifically, the underlying library is documented at https://www.pjrc.com/teensy/td_serial.html. It looks like the Serial.available() and Serial.read() pair can do non-blocking reads, but I don’t know about writing. As to concurrency with that API (which is largely that of Arduino) I don’t know if anyone ever tried.

The Rust Serial struct has no field / is zero-sized. It’s effectively a singleton, that struct only exists because I think the foo.bar() method call syntax looks better than Foo::bar().

What is the point of enforcing device ownership or using Mutex when the hardware does not provide paralellism or preemption?

Anyway, my approach to this project was to get something running quickly for a very simple application, all over the span of a few weekends. So any design choice is more likely the first thing that worked rather than a rejection of alternatives. If it turns out there are good reasons to change it nothing in teensy3-rs is frozen, as long as someone wants to spend the time/effort to develop it.

japaric commented 7 years ago

@thejpster Very enlightening! Thank you. I've got a few questions but I'm going to ask them over IRC to avoid going (too) off-topic.


@SimonSapin

when the hardware does not provide paralellism or preemption?

Oh, but it does! Interrupts are a preemption mechanism and their "handlers" (the functions that get called when an interrupt occurs) have their own "execution context" (stack frame) so in a sense they are like threads. As soon as your program has to dealt with interrupts, you run into the need of wanting to exchange between your main thread / loop and interrupts and the "obvious" way is to use a static muts which are a can of worms data races.

The issue I was referring to about Serial looks like this:

fn main() -> ! {
    // .. initialization stuff ..

    loop {
        // NOTE blocking!
        Serial.write_all(b"The quick brown fox jumps over the lazy dog.");
    }
}

// This is an interrupt handler. It runs e.g. every 10 milliseconds
// When those 10 milliseconds pass the processor will *stop* executing the `main` function,
// it will then execute this function and then return to `main` when this function is done
fn tim7() {
    // NOTE blocking as well
    Serial.putc(b'X');
}

This without an OS just using hardware features. At best this will print something like this:

ThXe XquXic...

At worst the interrupt handler may kick while write_all was mid through modifying several registers and the interrupt handler will start its execution with registers in a bad/unexpected state.

"But nobody writes code like that" is not a good argument. The question here is whether the compiler can stop you from writing racy code like the above. And I think it can, if you structure your code differently:

fn main() -> ! {
    // .. initialization stuff ..

    // A single instance of Serial
    let mut serial = Serial::init_once();

    loop {
        if done_with_previous_write {
            // NOTE Non-blocking!
            serial.async_write_all(b"The quick brown fox jumps over the lazy dog.");
        }

        // moved the interrupt handler logic into the main loop
        if ten_milliseconds_have_passed {
            serial.putc(b'X');
        }
    }
}

This should error with "trying to mutably borrow serial twice". But if you want to write code like that, code that runs concurrent tasks in the main loop, you can't use a blocking API.

Anyway, my approach to this project was to get something running quickly for a very simple application ...

Totally understandable. I hope we can continue working on top of the teensy3 crate during this event.


OK, back to the main topic. It seems to me that if we want to work on designing a non-blocking API we should have some list of applications that have to deal with concurrent tasks / events that we must implement during this event. And implementing those applications will trigger discussion and tease out the API design.

The applications should be implementable without needing (too much) external hardware to keep the cost low. Ideally we should use try to use all the stuff that the development board already have on them.

Possible applications could be:

Once we have of these applications we can merge the tasks in them to build more complex applications.

These applications also force you to actually write the peripheral/sensor drivers.

Thoughts about this last idea? Can think of more "applications" to tackle during novemb.rs?

japaric commented 7 years ago

you often want your system blocked in the wfi instruction (wait for interrupt) to save power, rather than spinning madly. -- @thejpster

To increase the difficulty level, we should strive to make the above applications always sleep when there's nothing to do rather than "busy wait" (spin endlessly).

japaric commented 7 years ago

So I have been thinking about what we could work on during the event and turns out there's lot of stuff that we could do. I've collected my ideas into a public etherpad so others can add their ideas as well. The "focus areas" I've proposed are:

The etherpad contains way more details about these areas. Let's discuss the details here.

The etherpad also contains a list of participants which is more like a hardware / time zone survey. I encourage you to add yourself to it if you plan to participate in the code sprint.

skade commented 7 years ago

In general, would someone be able to give me a list of hardware that would be interesting at a certain location (with a full address to send it to)? Also, would someone give me a contact for someone who would be willing to take care of the hardware after it was used for the workshop?

I would be willing to find a way then to maybe get some kits to you, if I can arrange funds.

Email: flo@andersground.net for personal info.

thejpster commented 7 years ago

There seems to be some support for this, so would it be possible to get this mentioned on the novemb.rs front page?

skade commented 7 years ago

I'll put it in as a project and link to this issue. Takes me a few hours, though.

japaric commented 7 years ago

would someone be able to give me a list of hardware that would be interesting

My recommendations:

The STM32F3DISCOVERY board is great for beginners as they can follow the rust-discovery material pretty much on their own and I can answer any question about that material. cf. #20

But, in general, any development board with a Cortex-M microcontroller in it plus a programmer (hardware) that are both supported by the OpenOCD project will work. There's lots of boards that fit this description. Some, like the DISCOVERY boards, even have the programmer "on-board" so you don't need any extra hardware other than a DISCOVERY board.

skade commented 7 years ago

Well, with list, I was more thinking about something I can just bash into a web interface, hit "buy" and have it shipped to the location where people find the equipment interesting :D.

japaric commented 7 years ago

Oh, OK. I don't know many retailers other than Mouser and Digikey but I found these links for the F3 board:

The F3 doesn't seem to be available in Digikey Germany or Digikey UK. :-/

Hmm, the mouser site seems to "latch" to a country after the first visit but you can change the country with the "Change Location" button on the top right.

thejpster commented 7 years ago

The Tiva-C launchpad (which is almost identical to the Stellaris Launchpad I use) is available from Farnell for about £10. As with the STMicro Discovery board, an OpenOCD compatible USB flash tool and debugger is included.

http://uk.farnell.com/texas-instruments/ek-tm4c123gxl/tm4c123g-launchpad-tiva-c-eval/dp/2314937

I think this is an interesting board because it has a close relative which adds Ethernet. Understanding how to support similar, but slightly different, microcontrollers is a useful exercise. Also, IP stacks written in Rust ftw.

http://uk.farnell.com/texas-instruments/ek-tm4c1294xl/eval-brd-tiva-c-connected-launchpad/dp/2399965

I have one or two of the former (the LM4F120 version anyway) and could probably borrow a couple of the latter from work.

As an aside, the STM32F3Discovery is no longer available according to Farnell - http://uk.farnell.com/stmicroelectronics/stm32f3discovery/evaluation-f3-cortex-m4-discovery/dp/2215108

japaric commented 7 years ago

an OpenOCD compatible USB flash tool and debugger is included.

This is very, very important.

@thejpster Do any of these launchpad boards have an on-board Serial <-> USB converter? Or do you need extra hardware to send serial data to a computer? And if they do, can the Serial <-> USB and the debugger functions be used at the same time?

The ST-LINK, the on-board programmer/debugger that DISCOVERY boards ship with, has a Virtual COM Port function (for Serial over USB communication) but can't be used if the ST-LINK is already acting as a debugger. So you need extra hardware to be able to both debug and send serial data to a PC.

thejpster commented 7 years ago

Yes, there's a whole extra LM4F dedicated to OpenOCD and USB Serial. I can use OpenOCD and open /dev/ttyAMA0 at the same time.

japaric commented 7 years ago

@thejpster Awesome. The DISCOVERY has the hardware wired in the right way but the firmware doesn't support it :disappointed: . (May I should write my own ST-LINK firmware :smiling_imp:)

jcsoo commented 7 years ago

Putting in my vote for the STM32F4 Discovery (note F4) which is a more powerful variant in the same family. It is also about $15 and comes with the same integrated ST-LINK debugger. There is a lot of material and code online (mostly C) that works with this particular dev board.

Of note, the F4 has an on-board 10/100 Ethernet MAC which you can connect to an external PHY and Ethernet jack. The STM32F4DIS-BB is about $40 and includes Ethernet and a number of other components. There are also cheaper PHY+RJ45 breakout boards that you can find for ~$10. And yes, I am starting on an IP network stack in Rust.

As I look online right now, it looks like the original STM32F4DISCOVERY has now also been discontinued. However, it looks like the STM32F407G-DISC1 is available with the same specifications.

For programming / debugging, the Black Magic Probe V2 is an interesting open source device that should work with most of these devices. It provides simultaneous debug + serial and doesn't even require OpenOCD - you can connect to it directly from GDB once you plug it in.

japaric commented 7 years ago

includes Ethernet

Right, everyone has their own agenda. @jcsoo will you be willing to lead a group to work on an ethernet/IP stack? I must admit I have no idea how much effort that requires.

the Black Magic Probe V2 is an interesting open source device ...

FWIW, you can use the ST-LINK in any DISCOVERY board to flash another board and the DISCOVERYs only cost like $15. Also, OpenOCD works fine in the three major OSes so I think you'll be only saving yourself from typing one command every now and then if you don't use it.


We should decide on some "deliverables" for the focus areas. I think that for the API design, one of the deliverables could be a crate with traits for blocking IO and just IO; no place for configuration API in that crate. Another deliverable could be extending that crate with async methods or, if it makes sense, different traits for async IO.

I know that @brandonedens wants to work on a preemptive scheduler so that could be a deliverable, assuming they don't get it done before the event. Otherwise, we could test that scheduler during the event on different hardware to see where it falls short.

And so on.


@thejpster Could you copy the etherpad link into the top comment?

genbattle commented 7 years ago

To add my 2c to the discussion, it would be nice to see some support for the STM Nucleo boards. All Nucleo boards have ST-Link onboard, so should be compatible with OpenOCD, and come with a range of STM MCUs of varying sizes and capabilities. Most of the Nucleo boards are about $10.

They're a little nicer to use than the base discovery boards since they include Arduino-compatible pinouts (they also include discovery-style headers).

japaric commented 7 years ago

should be compatible with OpenOCD

They are, indeed, supported by OpenOCD (grep for nucleo).

it would be nice to see some support for the STM Nucleo boards.

An unsolved problem is figuring out how to organize crates to reduce the effort required to support a new board. There's some previous discussion about that here

jamesmunns commented 7 years ago

Hey, sorry to show up late to the party. @thejpster and @japaric you have both hit some really good points here, but I thought I could weigh in on the trait based API.

Since we have traits and composition here, I think the best thing we can do is define the minimum common behavior, and build out functionality based on that.

Using the Serial example, we may come to agreement that this includes something like:

trait Serial {
  fn read(&self) -> Option<u8>;
  fn write(&self, u8) -> Result<(), ()>;
}

It doesn't cover how this is implemented, but allows us to define a common denominator, which can then be composed into more and more complex (or more and more specific) kinds of interfaces. Sometimes blocking will be right, sometimes FIFO-backed non-blocking will be right, sometimes callbacks will be right.

This could be implemented by many kinds of Serial, such as AsyncSerial, BlockingSerial, CallbackSerial, etc. I think the defining characteristic of embedded development is that there is no single abstraction that works for all cases (based on size, speed, etc). Since we can use trait specialization at compile time, this helps remove ambiguity at "zero cost".

Later, when building more complex examples, we can specify certain interfaces as such:

impl<T: Serial> Modem for T {}

or

impl<S: Serial + Async> Terminal for S {}

As an aside, I would love to develop the teensy3 APIs along side something like the f3 crate, to show how portable Rust can make embedded code. Right now, it is more or less just a proof of concept. The Arduino is a huge success for many reasons, but a hard-defined abstraction layer has absolutely helped that ecosystem (you can take the same code written 5 years ago for an AVR micro, and port it with zero effort to an ARM Cortex M3 - e.g. the Teensy3).

jamesmunns commented 7 years ago

Expanding a little on my last example, I thought I would write a little more pseudocode.

struct FifoSerial {
// some fields omitted
}

impl Serial for FifoSerial {
  fn read(&mut self) -> Option<u8> {
    self.rd_fifo.pop()
  }

  fn write(&mut self, data: u8) -> Result<(), ()> {
    self.wr_fifo.push(data)
  }
}

// Marker trait used as a tag
impl Nonblocking for FifoSerial {};

fn forward<I: Serial, O: Serial+Nonblocking>(in: I, out: O) -> Result<(), ()> {
  // Take from a high priority port to a low priority, memory backed port
  if Some(b) = in.read() {
    try!(out.write(b));
  }
  Ok(())
}
jamesmunns commented 7 years ago

I've opened a separate RFC here, since I feel like I am being too specific for this thread :). See here: https://github.com/rust-embedded/rfcs/issues/19

skade commented 7 years ago

Hm, I still don't have a proper "shopping list" :). Can someone pick one or more retailers for me, give me a rough quote and tell me which items to buy (and exactly how many) and where to send them?

Please shoot a bit high, if I cannot fulfill, I'll just deduce some items.

thejpster commented 7 years ago

Apologies I didn't get this sorted earlier. Cambridge Consultants is now officially on, so I'll bring a bunch of boards down from my personal stash. I have a TI Stellaris Launchpad, two Freescale Kinetis KE06Zs, and one or two TI Tiva-C Connected Launchpads (the ones with Ethernet).

What's the mechanism for co-ordinating all the groups on the day? IRC? WebEx?

skade commented 7 years ago

IRC and then anything the group would like.

On 18 Nov 2016, at 12:06, thejpster notifications@github.com wrote:

Apologies I didn't get this sorted earlier. Cambridge Consultants is now officially on, so I'll bring a bunch of boards down from my personal stash. I have a TI Stellaris Launchpad, two Freescale Kinetis KE06Zs, and one or two TI Tiva-C Connected Launchpads (the ones with Ethernet).

What's the mechanism for co-ordinating all the groups on the day? IRC? WebEx?

— You are receiving this because you commented. Reply to this email directly, view it on GitHub, or mute the thread.