avr-hal icon indicating copy to clipboard operation
avr-hal copied to clipboard

make serial port, ADC, and SPI examples portable across targets

Open mutantbob opened this issue 2 years ago • 5 comments

extract the serial port example into a portable function that demonstrates how to declare the serial port using generics such that it will compile on multiple targets.

I think folks can benefit from seeing concrete examples of how to write cross-platform fuctions like

pub fn report<H, USART: UsartOps<H, RX, TX>, RX, TX, CLOCK>(
    serial: &mut Usart<H, USART, RX, TX, CLOCK>,
) {

mutantbob avatar Apr 22 '22 19:04 mutantbob

arduino_portable::report(&mut serial);

Uhm, I like the idea, but ... having arduino in the name is something that I dislike. (note that I have no approval privilege, that I'm just a passenger, surely not a crew member)

The word arduino has too many meanings. It was once a PCB with an avr328 and it was/is an "IDE".

I think ( I suggest) that portable across targets is also portable outside the arduino island. Think ARM (STM32) and RISC-V.

stappersg avatar Apr 22 '22 19:04 stappersg

Sweet platypus on a unicycle : I forgot to add the new directory to the repo. While I'm fixing that, I'll rename it to avr-portable.

mutantbob avatar Apr 25 '22 13:04 mutantbob

Hey, thanks a lot for this PR!

I like the idea of including examples which show how to write "generic" code ontop of avr-hal. That said, there are two dimensions to this:

  • One one hand, there are "real" generics. This is what your code is making use of. Generics are best used when trying to either write a library (which then works with any peripheral from any supported MCU) or when writing application code that works with any of multiple similar peripherals (e.g. any of the USART blocks). In code:

    pub fn read_command<H, USART: UsartOps<H, RX, TX>, RX, TX, CLOCK>(
        serial: &mut UsartReader<H, USART, RX, TX, CLOCK>,
        buffer: &mut [u8],
    ) -> Result<(), Error> {
        // reads a command from the given UART
    }
    
  • On the other hand, the crates of avr-hal are designed to be (where possible) drop-in replacements for each other. This means you could use cargo-features to switch between different boards. The application code would, in this case, not require any generics at all. Instead, it would use type-aliases and cfg-directives. To make it more clear, consider this example code:

    # Cargo.toml
    [dependencies]
    cfg-if = "0.1.10"
    
    [dependencies.arduino-hal]
    git = "https://github.com/rahix/avr-hal"
    rev = "todotodo"
    
    [features]
    # Build for Arduino-Uno
    uno = ["arduino-hal/arduino-uno"]
    # Build for Arduino-Leonardo
    leonardo = ["arduino-hal/arduino-leonardo"]
    
    // src/main.rs
    #![no_std]
    #![no_main]
    
    use cfg_if::cfg_if;
    use panic_halt as _;
    use arduino_hal::hal::port;
    
    // Let's say on the Uno we want to use pin d0 and on Leonardo pin d4 for something
    cfg_if! {
        if #[cfg(feature = "uno")] {
            // D0
            type FooPin = port::Pin<port::mode::Output, port::PD0>;
        } else if #[cfg(feature = "leonardo")] {
            // D4
            type FooPin = port::Pin<port::mode::Output, port::PD4>;
        } else {
            compile_error!("Select uno or leonardo!");
        }
    };
    
    fn do_foo(pin: &mut FooPin) {
        pin.toggle();
    }
    
    #[arduino_hal::entry]
    fn main() -> ! {
        let dp = arduino_hal::Peripherals::take().unwrap();
        let pins = arduino_hal::pins!(dp);
    
        // D13 is an LED on both uno & leonardo so this just works (tm)
        let mut led = pins.d13.into_output();
        led.set_high();
    
        let mut foo_pin: FooPin = cfg_if! {
            if #[cfg(feature = "uno")] {
                pins.d0.into_output()
            } else if #[cfg(feature = "leonardo")] {
                pins.d4.into_output()
            }
        };
    
        loop {
            led.toggle();
            do_foo(&mut foo_pin);
            arduino_hal::delay_ms(100);
        }
    }
    

    You can compile this program for both uno and leonardo so it is also "portable" in that sense. This kind of "portability" is, what I imagine most users will need. The big advantage is that it does not require any kind of generic juggling.

That said, there of course still is a use-case for the former as well. So I would like to include your examples to demonstrate it. However, I don't think we should modify the existing examples to make use of it. Instead I would just add your avr-portable crate and add some binaries to call into the library to it. You can then make it work for a number of boards using the cargo-feature mechanism shown above.

Rahix avatar May 01 '22 08:05 Rahix

Does the existence of #264 allow to close this merge request?

I'm asking because dangling merge requests do obstruct the flow of evolution.

stappersg avatar May 04 '22 05:05 stappersg

If #264 is accepted, this merge request becomes superfluous.

mutantbob avatar May 04 '22 14:05 mutantbob