rust-embedded-community / usb-device

Experimental device-side USB framework for microcontrollers in Rust.
MIT License
451 stars 76 forks source link

Can't allocate multiple serial devices #111

Closed kpishere closed 9 months ago

kpishere commented 1 year ago

I'm trying to create two serial devices over USB and I get an error at initialization of the second serial device.

#![allow(unused_imports)]
#![no_std]
#![no_main]

//Global dependancies
use core::panic::PanicInfo;
use defmt_rtt as _;
use rtic::app;

#[app(device = rp_pico::hal::pac, peripherals = true, dispatchers = [TIMER_IRQ_1])]
mod app {
    // Application Dependancies
    use embedded_hal::digital::v2::{OutputPin, ToggleableOutputPin};
    use rp2040_monotonic::{
        fugit::Duration,
        fugit::RateExtU32, // For .kHz() conversion funcs
        Rp2040Monotonic,
    };
    use rp_pico::hal;
    use rp_pico::hal::{
        clocks, gpio, gpio::pin::bank0::Gpio25, gpio::pin::PushPullOutput, pac, sio::Sio,
        watchdog::Watchdog, I2C,
    };
    use rp_pico::XOSC_CRYSTAL_FREQ;
    // USB Device support
    use usb_device::{class_prelude::*, prelude::*};
    // USB Communications Class Device support
    use usbd_serial::SerialPort;

    // Application types
    #[monotonic(binds = TIMER_IRQ_0, default = true)]
    type Rp2040Mono = Rp2040Monotonic;

    // Apllication constants
    const MONO_NUM: u32 = 1;
    const MONO_DENOM: u32 = 1_000_000;
    const POLL_PERIOD_TICKS: u64 = 1_000_000;

    // Apllication Data structures
    #[shared]
    struct Shared {
        serial_cmd: SerialPort<'static, hal::usb::UsbBus>,
        usb_dev_cmd: usb_device::device::UsbDevice<'static, hal::usb::UsbBus>,
        //
        serial_log: SerialPort<'static, hal::usb::UsbBus>,
        usb_dev_log: usb_device::device::UsbDevice<'static, hal::usb::UsbBus>,
    }

    #[local]
    struct Local {
        led: gpio::Pin<Gpio25, PushPullOutput>,
    }

    // Apllication System level components
    #[init(local = [usb_bus: Option<usb_device::bus::UsbBusAllocator<hal::usb::UsbBus>> = None])]
    fn init(mut _cx: init::Context) -> (Shared, Local, init::Monotonics) {
        defmt::info!("init()");
        // Configure the clocks, watchdog - The default is to generate a 125 MHz system clock
        let mut watchdog = Watchdog::new(_cx.device.WATCHDOG);
        let clocks = clocks::init_clocks_and_plls(
            XOSC_CRYSTAL_FREQ,
            _cx.device.XOSC,
            _cx.device.CLOCKS,
            _cx.device.PLL_SYS,
            _cx.device.PLL_USB,
            &mut _cx.device.RESETS,
            &mut watchdog,
        )
        .ok()
        .unwrap();
        let mono = Rp2040Mono::new(_cx.device.TIMER);

        // Init LED pin
        let sio = Sio::new(_cx.device.SIO);
        let gpioa = rp_pico::Pins::new(
            _cx.device.IO_BANK0,
            _cx.device.PADS_BANK0,
            sio.gpio_bank0,
            &mut _cx.device.RESETS,
        );
        let mut led = gpioa.led.into_push_pull_output();
        led.set_low().unwrap();

        // USB
        //
        // Set up the USB driver
        // The bus that is used to manage the device and class below.
        let usb_bus: &'static _ =
            _cx.local
                .usb_bus
                .insert(UsbBusAllocator::new(hal::usb::UsbBus::new(
                    _cx.device.USBCTRL_REGS,
                    _cx.device.USBCTRL_DPRAM,
                    clocks.usb_clock,
                    true,
                    &mut _cx.device.RESETS,
                )));

        // Set up the USB Communications Class Device driver.
        let serial_cmd = SerialPort::new(usb_bus);

        // Create a USB device with a fake VID and PID
        let usb_dev_cmd = UsbDeviceBuilder::new(usb_bus, UsbVidPid(0x16c0, 0x27dd))
            .manufacturer("Virtual")
            .product("Serial port 0")
            .serial_number("CMD")
            .device_class(2) // from: https://www.usb.org/defined-class-codes
            .build();

        // Set up the USB Communications Class Device driver.
        let serial_log = SerialPort::new(usb_bus);

        // Create a USB device with a fake VID and PID
        let usb_dev_log = UsbDeviceBuilder::new(usb_bus, UsbVidPid(0x16c0, 0x27dd))
            .manufacturer("Virtual")
            .product("Serial port 1")
            .serial_number("LOG")
            .device_class(2) // from: https://www.usb.org/defined-class-codes
            .build();

        // Spawn react task
        react::spawn().unwrap();

        (
            Shared {
                serial_cmd,
                usb_dev_cmd,
                //
                serial_log,
                usb_dev_log,
            },
            Local { led },
            init::Monotonics(mono),
        )
    }

    #[task(local = [led])]
    fn react(ctx: react::Context) {
        let next_period = monotonics::now()
            + Duration::<u64, MONO_NUM, MONO_DENOM>::from_ticks(POLL_PERIOD_TICKS);

        // Flicker the built-in LED
        _ = ctx.local.led.toggle();

        // Congrats, you can use your i2c and have access to it here,
        // now to do something with it!

        react::spawn_at(next_period).unwrap();
    }

    #[task(binds = USBCTRL_IRQ, priority = 1, shared = [serial_cmd, usb_dev_cmd, serial_log,
        usb_dev_log])]
    fn usb_rx(cx: usb_rx::Context) {
        let usb_dev = cx.shared.usb_dev_cmd;
        let serial = cx.shared.serial_cmd;
        let log = cx.shared.serial_log;

        (usb_dev, serial /*, log*/).lock(|usb_dev_a, serial_a /*, serial_b*/| {
            // Check for new data
            if usb_dev_a.poll(&mut [serial_a]) {
                let mut buf = [0u8; 64];
                match serial_a.read(&mut buf) {
                    Err(_e) => {
                        //defmt::error!("UsbError");
                    }
                    Ok(0) => {
                        //defmt::error!("Usb NoData");
                        // Do nothing
                        //let _ = serial_a.write(b"Didn't received data.");
                        //let _ = serial_a.flush();
                    }
                    Ok(_count) => {
                        // Code to echo the characters in Upper Case.
                        // Convert to upper case
                        buf.iter_mut().take(_count).for_each(|b| {
                            b.make_ascii_uppercase();
                        });
                        // Send back to the host
                        let mut wr_ptr = &buf[.._count];
                        while !wr_ptr.is_empty() {
                            match serial_a.write(wr_ptr) {
                                Ok(len) => {
                                    wr_ptr = &wr_ptr[len..];
                                    let _ = serial_a.flush();
                                    //
                                    let _ = serial_b.write(wr_ptr);
                                    let _ = serial_b.flush();
                                }
                                // On error, just drop unwritten data.
                                // One possible error is Err(WouldBlock), meaning the USB
                                // write buffer is full.
                                Err(_) => break,
                            };
                        }
                    }
                }
            }
        });
    }
}

#[defmt::panic_handler]
fn defmt_panic() -> ! {
    loop {}
}
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
    loop {}
}

The above code crashes at let serial_log = SerialPort::new(usb_bus);

In that call, lower down, here is where it fails. Screen Shot 2023-01-07 at 9 26 51 AM

Cryowatt commented 1 year ago

It looks like what you're trying to do is expose two serial port interfaces, but you've implemented it as two usb devices. Unfortunately, your microcontroller is only a single device and it cannot act is two devices simultaneously. What you'd want is to expose two interfaces as an interface association as a single device. This is something the usbd_serial crate would have to support for you to use. Otherwise you'll have to do it yourself.

ryan-summers commented 9 months ago

Just to follow up, this should be supported with the current implementation. You would simply create multiple usbd_serial::SerialPort items and change the device_class to 0 (https://www.usb.org/defined-class-codes).

That being said, I was trying to do this with the usbd_serial crate and am having trouble getting both ports to enumerate. https://github.com/rust-embedded-community/usbd-serial/issues/34 should be the proper issue for tracking this behavior.