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

nRF52840 USB Bulk Out doesn't detect ZLPs #3820

Closed
jamesmunns opened this issue Jan 28, 2025 · 2 comments
Closed

nRF52840 USB Bulk Out doesn't detect ZLPs #3820

jamesmunns opened this issue Jan 28, 2025 · 2 comments
Labels

Comments

@jamesmunns
Copy link
Contributor

As far as I can tell, we don't get any kind of interrupts when a ZLP is sent from host to nrf. This occurs when we transfer messages with a multiple of the 64-byte bulk packet size, and need to terminate them.

Using the following nrf52840 code:

#![no_std]
#![no_main]

use defmt::{info, panic};
use embassy_executor::Spawner;
use embassy_futures::join::join;
use embassy_nrf::usb::vbus_detect::HardwareVbusDetect;
use embassy_nrf::usb::Driver;
use embassy_nrf::{bind_interrupts, pac, peripherals, usb};
use embassy_usb::driver::EndpointError;
use embassy_usb::driver::Endpoint;
use embassy_usb::driver::EndpointOut;
use embassy_usb::driver::EndpointIn;
use embassy_usb::msos::{self, windows_version};
use embassy_usb::{Builder, Config};
use {defmt_rtt as _, panic_probe as _};

bind_interrupts!(struct Irqs {
    USBD => usb::InterruptHandler<peripherals::USBD>;
    CLOCK_POWER => usb::vbus_detect::InterruptHandler;
});

const DEVICE_INTERFACE_GUIDS: &[&str] = &["{AFB9A6FB-30BA-44BC-9232-806CFC875321}"];

#[embassy_executor::main]
async fn main(_spawner: Spawner) {
    let p = embassy_nrf::init(Default::default());

    info!("Enabling ext hfosc...");
    pac::CLOCK.tasks_hfclkstart().write_value(1);
    while pac::CLOCK.events_hfclkstarted().read() != 1 {}

    // Create the driver, from the HAL.
    let driver = Driver::new(p.USBD, Irqs, HardwareVbusDetect::new(Irqs));

    // Create embassy-usb Config
    let mut config = Config::new(0xc0de, 0xcafe);
    config.manufacturer = Some("Embassy");
    config.product = Some("USB-serial example");
    config.serial_number = Some("12345678");
    config.max_power = 100;
    config.max_packet_size_0 = 64;

    // Create embassy-usb DeviceBuilder using the driver and config.
    // It needs some buffers for building the descriptors.
    let mut config_descriptor = [0; 256];
    let mut bos_descriptor = [0; 256];
    let mut msos_descriptor = [0; 256];
    let mut control_buf = [0; 64];

    let mut builder = Builder::new(
        driver,
        config,
        &mut config_descriptor,
        &mut bos_descriptor,
        &mut msos_descriptor,
        &mut control_buf,
    );

    // Add the Microsoft OS Descriptor (MSOS/MOD) descriptor.
    // We tell Windows that this entire device is compatible with the "WINUSB" feature,
    // which causes it to use the built-in WinUSB driver automatically, which in turn
    // can be used by libusb/rusb software without needing a custom driver or INF file.
    // In principle you might want to call msos_feature() just on a specific function,
    // if your device also has other functions that still use standard class drivers.
    builder.msos_descriptor(windows_version::WIN8_1, 0);
    builder.msos_feature(msos::CompatibleIdFeatureDescriptor::new("WINUSB", ""));
    builder.msos_feature(msos::RegistryPropertyFeatureDescriptor::new(
        "DeviceInterfaceGUIDs",
        msos::PropertyData::RegMultiSz(DEVICE_INTERFACE_GUIDS),
    ));

    // Add a vendor-specific function (class 0xFF), and corresponding interface,
    // that uses our custom handler.
    let mut function = builder.function(0xFF, 0, 0);
    let mut interface = function.interface();
    let mut alt = interface.alt_setting(0xFF, 0, 0, None);
    let mut read_ep = alt.endpoint_bulk_out(64);
    let mut write_ep = alt.endpoint_bulk_in(64);
    drop(function);

    // Build the builder.
    let mut usb = builder.build();

    // Run the USB device.
    let usb_fut = usb.run();

    // Do stuff with the class!
    let echo_fut = async {
        loop {
            read_ep.wait_enabled().await;
            info!("Connected");
            loop {
                let mut data = [0; 64];
                match read_ep.read(&mut data).await {
                    Ok(n) => {
                        info!("Got bulk ({=usize}): {:a}", n, data[..n]);
                        if n == 64 {
                            info!("More to come...");
                        } else {
                            info!("End of chunk");
                        }
                        write_ep.write(&data[..n]).await.ok();
                        info!("Wrote back. {=usize}", n);
                    }
                    Err(_) => break,
                }
            }
            info!("Disconnected");
        }
    };

    // Run everything concurrently.
    // If we had made everything `'static` above instead, we could do this using separate tasks instead.
    join(usb_fut, echo_fut).await;
}

struct Disconnected {}

impl From<EndpointError> for Disconnected {
    fn from(val: EndpointError) -> Self {
        match val {
            EndpointError::BufferOverflow => panic!("Buffer overflow"),
            EndpointError::Disabled => Disconnected {},
        }
    }
}

And the following host side code:

use nusb::transfer::RequestBuffer;

const BULK_OUT_EP: u8 = 0x01;
const BULK_IN_EP: u8 = 0x81;

#[tokio::main]
async fn main() {
    let di = nusb::list_devices()
        .unwrap()
        .find(|d| d.vendor_id() == 0xc0de && d.product_id() == 0xcafe)
        .expect("no device found");
    let device = di.open().expect("error opening device");
    let interface = device.claim_interface(0).expect("error claiming interface");

    // This works
    let result = interface.bulk_out(BULK_OUT_EP, b"hello world".into()).await;
    println!("{result:?}");
    let result = interface.bulk_in(BULK_IN_EP, RequestBuffer::new(512)).await;
    println!("{result:?}");

    // This works
    let mut data65 = vec![];
    for _x in 0..4 {
        data65.extend_from_slice(b"hello world12345");
    }
    data65.push(b'!');
    let result = interface.bulk_out(BULK_OUT_EP, data65).await;
    println!("tx1 {result:?}");
    let result = interface.bulk_in(BULK_IN_EP, RequestBuffer::new(512)).await;
    println!("rx1 {result:?}");

    // This doesn't
    let mut data64 = vec![];
    for _x in 0..4 {
        data64.extend_from_slice(b"hello world12345");
    }
    assert_eq!(data64.len(), 64);
    let result = interface.bulk_out(BULK_OUT_EP, data64).await;
    println!("tx2 {result:?}");
    let result = interface.bulk_in(BULK_IN_EP, RequestBuffer::new(512)).await;
    println!("rx2 {result:?}");
}
@jamesmunns
Copy link
Contributor Author

This might just be a skill OS issue. I also don't see the ZLPs on an rp2040 either. My host is MacOS.

@jamesmunns
Copy link
Contributor Author

Yep, addressed this over in jamesmunns/postcard-rpc#84 instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant