David-OConnor / stm32-hal

This library provides access to STM32 peripherals in Rust.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Usb-otg does not work for STM32L476ZGTx

seanybaggins opened this issue · comments

Here's the code I am running.

#![no_std]
#![no_main]

use cortex_m::asm;

use stm32_hal2::{
    self,
    clocks::Clocks,
    gpio::{Pin, PinMode, Port},
    pac,
};

use stm32::timers::MonoTimer;

use stm32_hal2::pac::TIM2;

use stm32_hal2::usb_otg::{Usb1, Usb1BusType, UsbBus};
use stm32_usbd::UsbPeripheral;

use usb_device::prelude::*;

use usbd_serial::SerialPort;
//use stm32_usbd::UsbBus;

use defmt_rtt as _;
use panic_probe as _;

const TIMER_FREQ_HZ: u32 = 1_000_000;

#[rtic::app(device = pac, dispatchers = [USART1])]
mod app {

    use usb_device::class_prelude::UsbBusAllocator;
    use usbd_serial::USB_CLASS_CDC;

    // This line is required to allow imports inside an RTIC module.
    use super::*;

    pub struct PeripheralUsb {
        pub serial: SerialPort<'static, UsbBus<Usb1>>,
        pub device: UsbDevice<'static, UsbBus<Usb1>>,
    }

    #[shared]
    struct Shared {
        peripheral_usb: PeripheralUsb,
    }

    #[local]
    struct Local {
        //usb_serial_port: SerialPort<'static, Usb1BusType>,
        //usb_device: UsbDevice<Usb1>,
    }

    #[monotonic(binds = TIM2, default = true)]
    type Mono2 = MonoTimer<TIM2, TIMER_FREQ_HZ>;

    #[init(local = [
           usb_buf: [u32; 64] = [0; 64],
           usb_bus: Option<UsbBusAllocator<UsbBus<Usb1>>> = None,
    ])]
    fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) {
        let clock_cfg = Clocks {
            ..Default::default()
        };
        clock_cfg.setup().unwrap();
        clock_cfg.enable_msi_48();

        //stm32::usb::enable_crs();
        //stm32::usb::enable_usb_pwr();
        //assert!(false);

        let _usb_dm = Pin::new(Port::A, 11, PinMode::Alt(14));
        let _usb_dp = Pin::new(Port::A, 12, PinMode::Alt(14));

        let usb1 = Usb1::new(
            cx.device.OTG_FS_GLOBAL,
            cx.device.OTG_FS_DEVICE,
            cx.device.OTG_FS_PWRCLK,
            clock_cfg.hclk(),
        );
        let buf: &'static mut _ = cx.local.usb_buf;
        let bus: &'static mut _ = cx.local.usb_bus.insert(UsbBus::new(usb1, buf));
        let serial = SerialPort::new(bus);
        defmt::println!("Does print");
        let device = UsbDeviceBuilder::new(bus, UsbVidPid(0x16c0, 0x27dd))
            .manufacturer("Fake Company")
            .product("Serial Port")
            .serial_number("SN")
            .device_class(USB_CLASS_CDC)
            .self_powered(true)
            .build();
        defmt::println!("Does not print");
        let peripheral_usb = PeripheralUsb { serial, device };

        let mono_timer2: Mono2 = MonoTimer::new(cx.device.TIM2, &clock_cfg);

        (
            Shared { peripheral_usb },
            Local {},
            init::Monotonics(mono_timer2),
        )
    }

    #[idle()]
    fn idle(_cx: idle::Context) -> ! {
        usb_say_hello::spawn().unwrap();
        loop {
            asm::nop()
        }
    }

    #[task(shared = [peripheral_usb], priority = 1)]
    fn usb_say_hello(cx: usb_say_hello::Context) {
        defmt::println!("usb_say_hello");
        let mut peripheral_usb = cx.shared.peripheral_usb;
        //let &mut usb_device = &mut cx.local.usb_device;
        //let &mut usb_serial_port = &mut cx.local.usb_serial_port;

        peripheral_usb.lock(|PeripheralUsb { device, serial }| loop {
            if !device.poll(&mut [serial]) {
                continue;
            }

            defmt::println!("Something in buffer");

            let mut buf = [0u8; 64];

            match serial.read(&mut buf[..]) {
                Ok(count) => {
                    serial.write(&buf[..count]).unwrap();
                }
                Err(UsbError::WouldBlock) => {
                    panic!("usb buffer full");
                }
                Err(err) => {
                    panic!("{:?}", err);
                }
            }
        })
    }
}

Digging into the build function, it looks like synopsys-usb-otg dependency is trying to modify a register that is not supported for l476 chips.

synopsys-usb-otg/src/bus.rs

            // ...
            // Perform core soft-reset
            while read_reg!(otg_global, regs.global(), GRSTCTL, AHBIDL) == 0 {}
            modify_reg!(otg_global, regs.global(), GRSTCTL, CSRST: 1);
            while read_reg!(otg_global, regs.global(), GRSTCTL, CSRST) == 1 {} // <- Hanging on this line
            //...

Section 7 of reference manual
image

Oh interesting. Of note, the CRS register block, and enable bit in RCC is supported in most STM32 families. It's supported in this lib on L4, for example. Probably all families that have an HSI48. I haven't ops tested it on L4, but I use it on G4, and it compiles in the PAC. I'm a bit surprised it's part of the OTG package; I have it as an optional-enable flag in the clock config. ie:

// Enable the Clock Recovery System, which improves HSI48 accuracy.
#[cfg(feature = "h7")]
clocks::enable_crs(CrsSyncSrc::OtgHs);
#[cfg(feature = "g4")]
clocks::enable_crs(CrsSyncSrc::Usb);

Of note

Taking a closer look at the data sheet, looks like this board does not have a 48 MHz HSI Clock

image

Here are the possible clocks that feed into the USB-otg for the STM32L476ZGTx

image

So, I think the core is this: The PAC and SVDs are divided by the final number in L4 series. There are a few exceptions re features that use the second. For example, I had to add a new PAC branch for L412 and 422 that use the RTC from newer series. I'm not sure the proper way to handle this. Some combo of changes to PAC and usbotg lib? What do you think?

If you get a chance, could you skin the CRS section of this lib's baseline clock section? I think it blanket allows CRS for all L4, which is evidently not correct. Not a huge deal since you can just choose not to enable if you know yours doesn't have it. Thoughts?

In general, the L4 line is a bit of a pain because diff models have diff versions of periphs! Ie some L4s have the newer periphs for RTC, USB, DMA etc you see on G4/L5/H7, while some have various combos of the old ones.

Sounds like, for example, if you tried to enable CRS via the HAL fn on your board, weird stuff might happen.

I'm not sure the proper way to handle this. Some combo of changes to PAC and usbotg lib? What do you think?

I definitely think there should be a change at the PAC. Looking at the memory map and comparing the L476 to the other variants, its clear that the CRS register is not meant to be accessed for the L476 variant.

image
image

Looking at modifying the synopsys-usb-otg to get it working for my board.

If you get a chance, could you skin the CRS section of this lib's baseline clock section? I think it blanket allows CRS for all L4, which is evidently not correct. Not a huge deal since you can just choose not to enable if you know yours doesn't have it. Thoughts?

Definitely in favor of adding some conditional compilation to get rid of the usb::enable_crs as an option for boards that don't support it. My first priority is to get usb working for my board. Once I have that working I will try to circle back on this library and do some polishing.

Sounds good. Of note, I caught the RTC thing after swapping what I thought were equivalent MCUs (L432 vice L412), and the RTC broke. Sounds like a similar case here. The fix was a pain because how the PAC broke down L4 variants wasn't compatible with which ones used the new RTC. With that in mind... I'm not sure how to feature gate the enable_crs fn based on how the PAC breaks down variants. Most L4 variants have it. (ie if you check the other L4 RM)

So I commented out the following lines in synopsys-usb-otg/src/bus.rs

            // ...
            // Perform core soft-reset
            // while read_reg!(otg_global, regs.global(), GRSTCTL, AHBIDL) == 0 {}
            // modify_reg!(otg_global, regs.global(), GRSTCTL, CSRST: 1);
            // while read_reg!(otg_global, regs.global(), GRSTCTL, CSRST) == 1 {} // <- Hanging on this line
            //...

In my example, I am making it to the poll function call. I still don't see a usb device presented on my PC though. My guess is that I am still getting something wrong in initialization.

Hmm. Of note, one thing that can get you is if the MCU doesn't respond to the host very quickly, the host will permanently drop the connection. So the USB poll needs to be on an interrupt (or blocking loop to test), and there can't be any long blocking delays anywhere on the program after it's initialized.

Or, relevant to prev discussion here, if the 48mhz clock isn't set up correctly. My goto is HSI48 with CRS synced to USB, but it sounds like you can't do that.

Managed to get this working using a different clock. See #65.

There are compiler warnings within the hal. I thought the example could help chase down and get rid of those warnings.