Skip to content
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

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

Open
wants to merge 7 commits into
base: main
Choose a base branch
from

Conversation

mutantbob
Copy link
Contributor

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>,
) {

…rates how to declare the serial port using generics such that it will compile on multiple targets
@stappersg
Copy link
Contributor

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.

@mutantbob
Copy link
Contributor Author

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 mutantbob changed the title make serial port example portable across targets make serial port, ADC, and SPI examples portable across targets Apr 26, 2022
@Rahix
Copy link
Owner

Rahix commented May 1, 2022

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.

@stappersg
Copy link
Contributor

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

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

@mutantbob
Copy link
Contributor Author

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

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

Successfully merging this pull request may close these issues.

None yet

3 participants