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

Can't allocate multiple serial devices #111

Closed
kpishere opened this issue Jan 7, 2023 · 2 comments
Closed

Can't allocate multiple serial devices #111

kpishere opened this issue Jan 7, 2023 · 2 comments

Comments

@kpishere
Copy link

kpishere commented Jan 7, 2023

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
Copy link

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
Copy link
Member

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. rust-embedded-community/usbd-serial#34 should be the proper issue for tracking this behavior.

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

3 participants