Skip to content

Building a high level library for embedded-hal compatible drivers #62

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
rahul-thakoor opened this issue Mar 13, 2018 · 12 comments
Closed

Comments

@rahul-thakoor
Copy link

I released a crate- rust_gpiozero which is a Rust implementation of the python gpiozero library.

The idea of having such a library is to get more people started with the Rust programming language through physical computing. Also, having a similar API to the original gpiozero library would be appealing to those who are already familiar with it.

Currently, the rust_gpiozero uses the std library. It has components such as LED, Buzzer, and Motor. Having a high-level component interface would make it easier to control hardware with Rust. For e.g creating a LED component and interfacing with it is as easy as :

// Create a new LED attached to Pin 17
    let mut led = LED::new(17);

There are also implementations of methods related a component such as blink() for LED or forward/ backward for Motor.

I am planning on making the rust_gpiozero crate compatible with the linux-embedded-hal crate. This will allow integrating embedded-hal compatible drivers into the library which would allow interfacing with common components easily irrespective of their controllers/drivers. As more and more drivers are being implemented through the weekly Driver Initiative(#39), having high-level component interfaces seems more intuitive. For e.g, there are several accelerometer controllers( MPU6050, ADXL345, ADXL335, MMA7361, LSM303C) and one could interface with an accelerometer specifying which controller to use.
e.g:

    // an accelerometer which has the ADXL345 controller
    let accel = Accelerometer::new(Controller::ADXL345);

A good example of this is the Johhny-Five(http://johnny-five.io/) Framework for javascript robotics.

Any thoughts? Are there similar works being done?

@DomonkosSuranyi
Copy link

Hey @rahul-thakoor
I am about to start to implement a driver for MPU6050 gyro+accelerometer and I just saw in rust-embedded/awesome-embedded-rust repo that there are some implementations for different hardwares with (partially) the same functionalities.
So I was thinking about something similar that you wrote here. I started to create a new issue for discussing that with the community but then I found your one.
I think it's a pretty good idea to have traits for common-used functionalities (e.g. Accelerometer, Gyro, Temperature sensor, Humidity sensor, RTC, ...)
This way for the high-level user it would be pretty straightforward to change some hardware in his already-working-system e.g a stepper motor to another type or MPU6050 to MPU9250 because the drivers would be very modular.

The only problem can be that it won't be easy to define that which features should REALLY be part of a trait like that. I absolutely think it's possible to define this kind of trait but needs to be discussed very well.

I am really interested in others opinion.

@RandomInsano
Copy link

I think you can look to how Linux has abstracted some if it’s drivers.

The AXP209 I’m working on gets exposed via something like four or five drivers: one for the GPIO, one for temperature, one for voltage, another for the power button, etc.

I’m not sure what their abstraction is for humidity, but I think one more level of abstraction is a great idea as long as the price is paid at compile time.

@rahul-thakoor
Copy link
Author

Hey @DomonkosSuranyi !

I think we need to decide on an API for such devices.
Perhaps this can help? http://johnny-five.io/examples/accelerometer-mpu6050/

@therealprof
Copy link
Contributor

@DomonkosSuranyi I absolutely agree. There should be traits which unify the API for working with sensor data so when using the API one can rely on getting the same information with the same unit (and easily convert to another one if necessary).

@rahul-thakoor rahul-thakoor changed the title Building a high level library for Raspberry Pi using embedded-hal compatible drivers Building a high level library for embedded-hal compatible drivers Apr 8, 2018
@BenBergman
Copy link

I've been thinking about something like this for the past week or so since I've started working with embedded rust. @therealprof pointed me to this thread.

My thoughts on implementing a high level API like this would be to just declare generic traits for driver writers to implement on their drivers. Initialization for each sensor has its own complications so I think it makes sense to leave that up to the driver writer to define whatever they need in that regard. The high level abstraction would just give driver writers standard traits to implement that provide users an interface to get, for example, standard SI units.

@therealprof suggested that there was another conversation somewhere where it was determined that it should maybe be broken up even further:

<@therealprof> As I recall it those were treated as two different topics: Abstracting sensor readings by kind/type of sensor and providing those in common SI units.
< benbergman> Iiiiiinteresting
<@therealprof> The "problem" with the traits per kind of sensor was (as I recall it) that the sensors typically require custom setup per connection type (I2C/SPI/Serial/1Wire…) and also custom configuration before they yield useful data (continuous vs oneshot, oversampling, resolution, powersave…).
<@therealprof> Yielding the sensor readings is a comparably small smart compared to the rest.
<@therealprof> The problem with units are that there're many different approaches one can take here to implement that and each of them has different trade-offs in terms of performance and code size which means that there's no good universal choice that fits all usecases.

I figure I would revive this discussion and possibly take a stab at making a basic implementation if a general direction can be agreed on.

P.S. - Another project that aimed to solve this in another language is Adafruit's Unified Sensor Driver: https://learn.adafruit.com/using-the-adafruit-unified-sensor-driver

@little-arhat
Copy link

@DomonkosSuranyi btw, https://github.com/copterust/mpu9250 is released and support mpu9250, MPU9255, and MPU6500. Adding support for mpu6050 might be as easy as adding WHO_AM_I value to the list of supported devices.

@GregWoods
Copy link

Sometimes (and only sometimes), the perspective of a noob (like myself) is useful, because soon my experience and familiarity with the hal will grow, and make all my bad thoughts disappear!

Here is the code from an stm32f1 blink program to setup a pin for output

let dp = pac::Peripherals::take().unwrap();
let mut flash = dp.FLASH.constrain();
let mut rcc = dp.RCC.constrain();
let clocks = rcc.cfgr.freeze(&mut flash.acr);
let mut gpioc = dp.GPIOC.split(&mut rcc.apb2);
let mut led = gpioc.pc13.into_push_pull_output(&mut gpioc.crh);
led.set_high().unwrap();

To an embedded Rust newcomer, perhaps someone coming from Arduino, this is an insane level of complexity to simply set up a pin.
Also, rather annoyingly, this code is different for stm32f4

let dp = stm32::Peripherals::take().unwrap();
let rcc = dp.RCC.constrain();
let clocks = rcc.cfgr.freeze();
let gpioc = dp.GPIOC.split();
let mut led = gpioc.pc13.into_push_pull_output();
led.set_high().unwrap();

I've spent many an hour reading through both chips' Reference Manuals... so I can see why they are different. I also see why the code is complex. It is less of an abstraction and more about taking what is in the reference manual (or more accurately the svd) and converting it into safe Rust.

The problem with this, is that until the new user has a firm grasp of the hal, they constantly need to refer to both the Reference Manual and read the embedded-hal source code (I always have a clone of stm32-rs-nightlies open at all times for reference). It does often feel that it would be quicker for me to just read the reference manual and use the PAC directly. I am sticking with the HAL though, I'm sure it will become easier with time.

All this has not been said to criticize, it is just to remind everyone of the pain a new user experiences, and that yes, there is a need for something with a higher level of abstraction.

I also think that talking about sensors is probably too high a level. Given that sensors often run on well know protocols like spi and i2c, I feel that it is the setup of these peripherals which needs a higher level of abstraction, before sensor libraries are added on top,

Compare the above code to Arduino code, which although being C++ is simple to comprehend.

pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, HIGH);

Now that is more like it! But then it doesn't have the benefits of Rust.

The levels of abstraction I'd like to see are

  • Peripheral Access Crate
  • Embedded-Hal
  • New easy peripheral abstraction, perhaps like gpiozero
  • Device specific libraries

I am sure my expectations are naive, and I'm happy to be told what an idiot I am, but I can't help feeling that the pool of embedded Rust developers needs to grow, and it needs a higher level library to get more people on board.

@jamesmunns
Copy link
Member

@GregWoods I really like this idea. I'm going to think a bit about how we could make a higher level abstraction that is willing to pay some overhead costs for the sake of consistency and ease of use.

Thanks for the feedback!

@adamgreig
Copy link
Member

I think one of the reasons the Arduino code is able to be so simple is because (in our terminology) it's an automatically selected BSP for your specific board, which is always higher level than our HALs or PACs. It knows the precise pinout and resources of your board. That said, even our BSPs are fairly complicated to use in comparison:

    if let (Some(p), Some(cp)) = (stm32::Peripherals::take(), Peripherals::take()) {
        let gpiod = p.GPIOD.split();

        // Initialize on-board LEDs
        let mut leds = Leds::new(gpiod);

        // Constrain clock registers
        let rcc = p.RCC.constrain();

        // Configure clock to 168 MHz (i.e. the maximum) and freeze it
        let clocks = rcc.cfgr.sysclk(168.mhz()).freeze();

        // Get delay provider
        let mut delay = Delay::new(cp.SYST, clocks);

        loop {
            // Turn LEDs on one after the other with 500ms delay between them
            leds[LedColor::Orange].on();
            delay.delay_ms(500_u16);
       ....

I could imagine a somewhat simpler BSP would be possible, though not having unsynchronised global access to everything at all times will always make things a bit more complicated. It would probably work well with the Arduino-style rt discussed in https://github.com/rust-embedded/cortex-m-rt/issues/250. I think on the whole a higher level abstraction focused on making it as simple as possible to get started with a popular board (e.g. Teensy, Arduinos with Cortex-M, ST discovery boards, etc) should look like a BSP, i.e. we don't need another different layer.

@therealprof
Copy link
Contributor

I think one of the reasons the Arduino code is able to be so simple is because (in our terminology) it's an automatically selected BSP for your specific board, which is always higher level than our HALs or PACs. It knows the precise pinout and resources of your board.

Not just that but it also makes some important decisions about things like clock speeds and setup and leaves the user completely in the dark about the made choices. It's also very easy to accidentally end up with incompatible peripheral configurations and if you want to do something advanced which is not supported by Arduino out of the box you have to reside to either weird vendor specific functions (like the STM32 Arduino port with the HAL/LL interface) or bang random bits into random adresses and hunt a full day or two for bugs.

I think some simplification of our Rust crates may be possible and desirable but I don't think it will ever be as dumbed down as Arduino and I do consider that a good thing.

@adamgreig
Copy link
Member

I think some simplification of our Rust crates may be possible and desirable but I don't think it will ever be as dumbed down as Arduino and I do consider that a good thing.

I agree; I think the Arduino approach quickly breaks down in larger or more complicated projects and it doesn't offer good support when you need more than its library can provide (as in, it's of course possible to change random settings registers, but going outside its framework quickly leads to trouble). It's important that the WG's work continues to support larger projects requiring very low-level control.

However I think there could still be room for a crate that did provide the very easy to use Arduino approach, which knew exactly what platform you were using (i.e., which Arduino board), and made opinionated choices about clocks and peripherals that are likely to be correct, to make it easier to get started. We (or someone) could provide such a crate without hindering efforts to work on powerful alternatives which require more setup.

@adamgreig
Copy link
Member

I'm closing this old issue as part of today's triage. I think the basic idea of making some high-level traits that represent useful devices like accelerometers is nice, but there's no motivation today to develop then inside the WG. This thread itself has become more of a discussion of alternative or simplified runtimes, which would be better discussed in a separate thread if someone is still interested in working on that.

@adamgreig adamgreig closed this as not planned Won't fix, can't repro, duplicate, stale Jun 11, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants