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

Example of usage with STM32 #19

Open
snorkman88 opened this issue Oct 3, 2024 · 29 comments
Open

Example of usage with STM32 #19

snorkman88 opened this issue Oct 3, 2024 · 29 comments

Comments

@snorkman88
Copy link

Describe the solution you'd like
A clear and concise description of what you want to happen.

I am in the process of building my own control surface and would like to use this crate an an STM32F4 Blackpill.
Do you have an example that I can use as a starting point?

@sourcebox
Copy link
Collaborator

Which HAL are you using for the STM32F4?

@snorkman88
Copy link
Author

Hi there, I am using v1.0.0 for all my projects.

@sourcebox
Copy link
Collaborator

I think you mean the embedded-hal crate, but that's not what I was asking. There are several options for HAL implementations targeting an STM32F4 like:

Each of these HALs have their own way to setup things and also contain example code how to use USB in general. The first two options can be used with this crate, embassy however has it's own implementation of a MIDI device class.

@snorkman88
Copy link
Author

Oh my bad, sorry!.
I am using stm32f4xx-hal.

@sourcebox
Copy link
Collaborator

Here's some code based on the README using a modification of the serial example.

You have to activate the features stm32f411, otg-fs and usb_fs for the stm32f4xx-hal crate, of course and also adapt the clock configuration.

//! USB MIDI example using polling in a busy loop.
//! Target board: any STM32F4 with a OTG FS peripheral and a 25MHz HSE crystal
#![no_std]
#![no_main]

use panic_halt as _;

use cortex_m_rt::entry;
use stm32f4xx_hal::otg_fs::{UsbBus, USB};
use stm32f4xx_hal::{pac, prelude::*};
use usb_device::prelude::*;
use usbd_midi::{
    data::{
        midi::{channel::Channel, message::Message, notes::Note},
        usb_midi::midi_packet_reader::MidiPacketBufferReader,
    },
    midi_device::MidiClass,
};

static mut EP_MEMORY: [u32; 1024] = [0; 1024];

#[entry]
fn main() -> ! {
    let dp = pac::Peripherals::take().unwrap();

    let rcc = dp.RCC.constrain();

    let clocks = rcc
        .cfgr
        .use_hse(25.MHz())
        .sysclk(48.MHz())
        .require_pll48clk()
        .freeze();

    let gpioa = dp.GPIOA.split();

    let usb = USB::new(
        (dp.OTG_FS_GLOBAL, dp.OTG_FS_DEVICE, dp.OTG_FS_PWRCLK),
        (gpioa.pa11, gpioa.pa12),
        &clocks,
    );

    let usb_bus = UsbBus::new(usb, unsafe { &mut EP_MEMORY });

    // Create a MIDI class with 1 input and 1 output jack.
    let mut midi = MidiClass::new(&usb_bus, 1, 1).unwrap();

    let mut usb_dev = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(0x16c0, 0x5e4))
        .device_class(0)
        .device_sub_class(0)
        .strings(&[StringDescriptors::default()
            .manufacturer("Fake Company")
            .product("Product")
            .serial_number("TEST")])
        .unwrap()
        .build();

    loop {
        if !usb_dev.poll(&mut [&mut midi]) {
            continue;
        }

        let mut buffer = [0; 64];

        if let Ok(size) = midi.read(&mut buffer) {
            let buffer_reader = MidiPacketBufferReader::new(&buffer, size);
            for packet in buffer_reader.into_iter() {
                if let Ok(packet) = packet {
                    match packet.message {
                        Message::NoteOn(Channel::Channel1, Note::C2, ..) => {
                            // Do something.
                        }
                        Message::NoteOff(Channel::Channel1, Note::C2, ..) => {
                            // Do something.
                        }
                        _ => {}
                    }
                }
            }
        }
    }
}

@snorkman88
Copy link
Author

@sourcebox Thanks a lot!!!

@snorkman88
Copy link
Author

snorkman88 commented Nov 1, 2024

@sourcebox I have tried this code with a minor change to punt DP and DM pins into altenate mode (AF10). The code looks like this:

        // Configure the button pin as input and obtain handle
        // On the Blackpill STM32F411CEU6 there is a button connected to pin PA0
        // 6) Promote the GPIOA PAC struct
        let gpioa: gpio::gpioa::Parts = dp.GPIOA.split();

        // Configure USB pins to be of type alternate function 10 (AF10)
        let usb_dm: gpio::Pin<'A', 11, gpio::Alternate<10>>  = gpioa.pa11.into_alternate();
        let usb_dp: gpio::Pin<'A', 12, gpio::Alternate<10>> = gpioa.pa12.into_alternate();

        // 7) Configure USB peripheral
        let usb = USB::new(
            (dp.OTG_FS_GLOBAL, dp.OTG_FS_DEVICE, dp.OTG_FS_PWRCLK),
            (usb_dm, usb_dp),
            &clocks,
        );
        let usb_bus = UsbBus::new(usb, unsafe { &mut EP_MEMORY });

        // Create a MIDI class with 1 input and 1 output jack.
        let mut midi = MidiClass::new(&usb_bus, 1, 1).unwrap();
        let mut usb_dev = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(0x16c0, 0x5e4))
            .device_class(0)
            .device_sub_class(0)
            .strings(&[StringDescriptors::default()
                .manufacturer("Fake Company")
                .product("Product")
                .serial_number("TEST")])
            .unwrap()
            .build();

        defmt::info!("Before entering loop");
        loop {
            if !usb_dev.poll(&mut [&mut midi]) {
                continue;
            }
            defmt::info!("Left USB Poll");
            let mut buffer = [0; 64];
            let payload = UsbMidiEventPacket::from_midi(
                CableNumber::Cable0,
                Message::NoteOn(
                    Channel::Channel1,                            // Channel
                    Note::C3,                                     // Note
                    U7::from(Note::try_from(127 as u8).unwrap()), // Velocity
                ),
            );
            let result = midi.send_message(payload);
            delay.delay_ms(1000_u32);
        }

this code compiles fine but it hangs when UsbDeviceBuilder::new(...) is invoked. Did I miss anything?
Thanks in advance for any help.

@sourcebox
Copy link
Collaborator

Unfortunately, I don't have the appropriate hardware to run this. Did you activate the control-buffer-256 feature on the usb-device crate? Because not doing so can lead to crashes when the config descriptor gets too large and with USB MIDI, that can happen quite easily.

@snorkman88
Copy link
Author

Unfortunately, I don't have the appropriate hardware to run this. Did you activate the control-buffer-256 feature on the usb-device crate? Because not doing so can lead to crashes when the config descriptor gets too large and with USB MIDI, that can happen quite easily.

I have just re-tried enabling control-buffer-256 and the behaviour is the same. Any other suggestion?

@sourcebox
Copy link
Collaborator

I have just re-tried enabling control-buffer-256 and the behaviour is the same. Any other suggestion?

If the code stalls inside UsbDeviceBuilder::new(), there's a good chance that the problem is not related to this crate but usb-device in general.

Some suggestions:

  • Try to use the current git version of usb-device via [patch.crates-io]. You need some changes in your code because of different control buffer management.
  • Try to run another USB example from the HAL like the {serial-example](https://github.com/stm32-rs/stm32f4xx-hal/blob/master/examples/usb-serial-poll.rs) and check if it also crashes.
  • There's a room on Matrix where the maintainers of usb-device are also present and maybe someone has a compatible board to run your code.
  • There's also Discord-Server where someone else can help.

Overall, this is the kind of issue that has to be checked on the target hardware with a JTAG debug probe connected. Using that, you can single step the code up to the point where it hangs or crashes.

@snorkman88
Copy link
Author

Thanks again for the suggestions, I will keep you posted if I make it work.

@snorkman88
Copy link
Author

I have jsut tested and I can confirm this works.

@sourcebox
Copy link
Collaborator

I have jsut tested and I can confirm this works.

Ok, good to know. But the serial device has a much simpler descriptor, so it's not sure whether usbd-midi is doing something wrong internally.

@sourcebox
Copy link
Collaborator

There was some issue with STM32F4 devices mentioned on the tracker a while ago, but this can be unrelated:

rust-embedded-community/usb-device#112 (comment)

@snorkman88
Copy link
Author

snorkman88 commented Nov 2, 2024

Aghh!! I have just noticed I missed sth very basic: I forgot to enable the PLL!
It lists for a short period of time now :-) but with the following line, afterwards it disappears.

Screenshot 2024-11-02 at 11 24 20

I suspect this is due to me running macOS.

@sourcebox
Copy link
Collaborator

That can be caused by enabling the pullup on the USB D+ line. The host then tries to access the port, but if the clocks aren't 100% correct, then there's no reply.

@snorkman88
Copy link
Author

That can be caused by enabling the pullup on the USB D+ line. The host then tries to access the port, but if the clocks aren't 100% correct, then there's no reply.

Oh I see so if I am understanding correctly, despite DP being configured in alternate mode, there is the need for it to be pulled up?

@snorkman88
Copy link
Author

I have added a 1.5k pull-up but no change. I am a bit confused, do not understand how is one example working and the other one requires a pull up?

@sourcebox
Copy link
Collaborator

I have added a 1.5k pull-up but no change. I am a bit confused, do not understand how is one example working and the other one requires a pull up?

The pullup is always required, but some STM32 series have it internally, so it just needs to be enabled by the HAL. Others (like the F3 series) need an external pullup. Not sure what's on your board already, but if one example works, then the pullup is working.

@sourcebox
Copy link
Collaborator

What you also can do: remove just the MIDI class part and check if the device then enumerates. If not, there's some deeper problem like the USB clock not running at exactly 48MHz.

@snorkman88
Copy link
Author

So just to make sure it is not OS related, I plugged it on a Raspberry Pi 5 running Raspbian and I see

[  416.552130] usb usb3-port2: attempt power cycle. 
[  416.963971] usb 3-2: new full-speed USB device number 6 using xhci-hcd. 
[  422.183893] xhci-hcd xhci-hcd.1: Timeout while waiting for setup device command. 
[  427.559761] xhci-hcd xhci-hcd.1: Timeout while waiting for setup device command. 
[  427.767724] usb 3-2: device not accepting address 6, error -62. 
[  427.895716] usb 3-2: new full-speed USB device number 7 using xhci-hcd. 
[  439.079519] usb 3-2: device descriptor read/all, error -110. 
[  439.079594] usb usb3-port2: unable to enumerate USB device. 

What you also can do: remove just the MIDI class part and check if the device then enumerates. If not, there's some deeper problem like the USB clock not running at exactly 48MHz.

I am testing that in a min.

@snorkman88
Copy link
Author

What you also can do: remove just the MIDI class part and check if the device then enumerates. If not, there's some deeper problem like the USB clock not running at exactly 48MHz.

If I remove the MIDI class,
let mut midi = MidiClass::new(&usb_bus, 1, 1).unwrap();
then I think I also need to remove

if !usb_dev.poll(&mut [&mut midi]) {
                continue;
            }

right?

@sourcebox
Copy link
Collaborator

What you also can do: remove just the MIDI class part and check if the device then enumerates. If not, there's some deeper problem like the USB clock not running at exactly 48MHz.

If I remove the MIDI class, let mut midi = MidiClass::new(&usb_bus, 1, 1).unwrap(); then I think I also need to remove

if !usb_dev.poll(&mut [&mut midi]) {
                continue;
            }

right?

Yes.

@sourcebox
Copy link
Collaborator

But it's possible that you need to call the poll() function without any classes to get the USB communication handled. Not sure about this though.

@snorkman88
Copy link
Author

Yes.

Same, no changes.

[ 1411.362920] xhci-hcd xhci-hcd.1: Timeout while waiting for setup device command
[ 1416.738868] xhci-hcd xhci-hcd.1: Timeout while waiting for setup device command
[ 1416.946842] usb 3-2: device not accepting address 19, error -62
[ 1416.946925] usb usb3-port2: unable to enumerate USB device
[ 1472.914254] usb 3-2: new full-speed USB device number 20 using xhci-hcd
[ 1488.290109] usb 3-2: device descriptor read/64, error -110
[ 1503.905926] usb 3-2: device descriptor read/64, error -110
[ 1504.141901] usb 3-2: new full-speed USB device number 21 using xhci-hcd
[ 1519.521743] usb 3-2: device descriptor read/64, error -110```

@snorkman88
Copy link
Author

But it's possible that you need to call the poll() function without any classes to get the USB communication handled. Not sure about this though.

That does not seem to be possible since .poll( expects an arg of type mut dyn UsbClass<stm32f4xx_hal::otg_fs::UsbBus<USB>>

@sourcebox
Copy link
Collaborator

Same, no changes.

That indicates a more fundamental problem in the hardware setup or the clocks.

@snorkman88
Copy link
Author

Maybe another thing to check would the MCO output...sadly I do not have an oscilloscope.

@snorkman88
Copy link
Author

Hi @sourcebox, I wanted to say the code you provided in #19 (comment) works fine. The device is listed correctly in Raspbian.
Screenshot 2024-11-03 at 11 49 36

Since I am trying to make this work using RTIC, I suppose the issue is related to something else.

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

2 participants