Skip to content

Instantly share code, notes, and snippets.

@dlancea
Created June 29, 2025 18:34
Show Gist options
  • Select an option

  • Save dlancea/0008a8618da3affb934e73ac04a3d35d to your computer and use it in GitHub Desktop.

Select an option

Save dlancea/0008a8618da3affb934e73ac04a3d35d to your computer and use it in GitHub Desktop.
//! CDC-ACM serial port example using polling in a busy loop
//!
//! Tested with a NUCLEO-H723ZG
//!
//! This example uses the USB1 peripheral. On parts that have multiple USB
//! OTG_HS peripherals the USB1 D+/D- pins are located on PB14 and PB15. If your
//! development board uses PA11 and PA12 instead, you should adapt the example
//! to use the USB2 peripheral together with PA11 and PA12. This applies to the
//! NUCLEO-H743ZI2 board.
//!
// #![deny(warnings)]
#![no_std]
#![no_main]
use cortex_m::asm::nop;
use defmt_rtt as _; // global logger
// TODO(5) adjust HAL import
// use some_hal as _; // memory layout
use panic_probe as _;
// use panic_abort as _;
// same panicking *behavior* as `panic-probe` but doesn't print a panic message
// this prevents the panic message being printed *twice* when `defmt::panic` is invoked
#[defmt::panic_handler]
fn panic() -> ! {
cortex_m::asm::udf()
}
use core::mem::MaybeUninit;
#[macro_use]
#[allow(unused)]
mod utilities;
use cortex_m_rt::entry;
use stm32h7xx_hal::rcc::rec::UsbClkSel;
use stm32h7xx_hal::usb_hs::{UsbBus, USB1};
use stm32h7xx_hal::{prelude::*, stm32};
// use usb_device::prelude::*;
use usb_device::{bus::UsbBusAllocator, prelude::UsbDevice, prelude::UsbVidPid, prelude::UsbDeviceBuilder, prelude::StringDescriptors};
use usbd_midi::{CableNumber, UsbMidiClass, UsbMidiEventPacket, UsbMidiPacketReader};
use midi_convert::midi_types::{Channel, MidiMessage, Note, Value7};
use midi_convert::{parse::MidiTryParseSlice, render_slice::MidiRenderSlice};
use stm32h7xx_hal::time::Hertz;
use stm32h7xx_hal::time::MegaHertz;
use stm32h7xx_hal::rcc;
pub const CLOCK_RATE_HZ: Hertz = Hertz::from_raw(480_000_000_u32);
// pub const CLOCK_RATE_HZ: Hertz = Hertz::from_raw(480_000_000_u32);
pub const AUDIO_SAMPLE_HZ: Hertz = Hertz::from_raw(48_000);
const HSE_CLOCK_MHZ: MegaHertz = MegaHertz::from_raw(16);
// const HCLK_MHZ: MegaHertz = MegaHertz::from_raw(200);
// const HCLK2_MHZ: MegaHertz = MegaHertz::from_raw(200);
// PCLKx
const PCLK_HZ: Hertz = Hertz::from_raw(CLOCK_RATE_HZ.raw() / 4);
// 49_152_344
// PLL1
const PLL1_P_HZ: Hertz = CLOCK_RATE_HZ;
const PLL1_Q_HZ: Hertz = Hertz::from_raw(CLOCK_RATE_HZ.raw() / 18);
const PLL1_R_HZ: Hertz = Hertz::from_raw(CLOCK_RATE_HZ.raw() / 32);
// PLL2
const PLL2_P_HZ: Hertz = Hertz::from_raw(4_000_000);
// PLL3
const FS: Hertz = Hertz::from_raw(48_000);
const PLL3_P_HZ: Hertz = Hertz::from_raw(FS.to_Hz() * 256);
static mut EP_MEMORY: MaybeUninit<[u32; 1024]> = MaybeUninit::uninit();
#[entry]
fn main() -> ! {
let dp = stm32::Peripherals::take().unwrap();
// Power
let pwr = dp.PWR.constrain();
// let vos = pwr.freeze();
let vos = pwr.vos0(&dp.SYSCFG).freeze();
//ORIG
// // RCC
let rcc = dp.RCC.constrain();
let mut ccdr = rcc
.use_hse(16.MHz()) // high speed external crystal @ 16 MHz
.sys_ck(CLOCK_RATE_HZ)
.pll1_strategy(rcc::PllConfigStrategy::Iterative)
.pll1_p_ck(PLL1_P_HZ)
.pll3_strategy(rcc::PllConfigStrategy::FractionalNotLess)
.pll3_p_ck(PLL3_P_HZ) // used for SAI1
.freeze(vos, &dp.SYSCFG);
// 48MHz CLOCK
// let _ = ccdr.clocks.hsi48_ck().expect("HSI48 must run");
// ccdr.peripheral.kernel_usb_clk_mux(UsbClkSel::Hsi48);
// let rcc = dp.RCC.constrain();
// let mut ccdr = rcc
// .use_hse(HSE_CLOCK_MHZ.convert())
// .sys_ck(CLOCK_RATE_HZ)
// .pclk1(PCLK_HZ) // DMA clock // Redundant?
// .pclk2(PCLK_HZ) // DMA clock // Redundant?
// // PLL1
// .pll1_strategy(rcc::PllConfigStrategy::Iterative)
// .pll1_p_ck(PLL1_P_HZ)
// .pll1_q_ck(PLL1_Q_HZ)
// // .pll1_r_ck(PLL1_R_HZ) // Redundant?
// // PLL2
// .pll2_p_ck(PLL2_P_HZ) // Default adc_ker_ck_input // Issue?
// // PLL3
// .pll3_strategy(rcc::PllConfigStrategy::FractionalNotLess)
// .pll3_p_ck(PLL3_P_HZ) // used for SAI1
// .freeze(vos, &dp.SYSCFG);
// dp.RCC.cr.read().hserdy().is_not_ready();
// let mut ccdr = rcc
// .use_hse(16.MHz()) // high speed external crystal @ 16 MHz
// .pll1_strategy(rcc::PllConfigStrategy::Iterative) // pll1 drives system clock
// .pll1_q_ck(48.MHz()) // required for SPI display
// .pll3_strategy(rcc::PllConfigStrategy::Fractional) // ensure we get as close as possible to 12.288 MHz (audio clock)
// .sys_ck(480.MHz()) // system clock @ 480 MHz
// .pll3_p_ck(PLL3_P_HZ) // audio clock @ 12.288 MHz
// .freeze(vos, &dp.SYSCFG);
ccdr.peripheral.kernel_usb_clk_mux(stm32h7xx_hal::rcc::rec::UsbClkSel::Hsi48);
let _ = ccdr.clocks.hsi48_ck().expect("HSI48 must run");
let _ = ccdr.clocks.hse_ck().expect("hse_ck must run");
// let _ = ccdr.clocks.hse_ck()
// .expect("hse_ck must run");
// defmt::info!("{}", hz1.to_Hz());
// defmt::info!("{}", ccdr.clocks.hse_ck().unwrap().to_Hz());
// defmt::info!("{}", ccdr.clocks.hsi48_ck().unwrap().to_Hz());
// for _ in 0..100 {
// nop();
// }
//ORIG
// // RCC
// let rcc = dp.RCC.constrain();
// let mut ccdr = rcc.sys_ck(80.MHz()).freeze(vos, &dp.SYSCFG);
// // 48MHz CLOCK
// let _ = ccdr.clocks.hsi48_ck().expect("HSI48 must run");
// ccdr.peripheral.kernel_usb_clk_mux(UsbClkSel::Hsi48);
// If your hardware uses the internal USB voltage regulator in ON mode, you
// should uncomment this block.
// unsafe {
// let pwr = &*stm32::PWR::ptr();
// pwr.cr3.modify(|_, w| w.usbregen().set_bit());
// while pwr.cr3.read().usb33rdy().bit_is_clear() {}
// }
// IO
let (pin_dm, pin_dp) = {
let gpiob = dp.GPIOB.split(ccdr.peripheral.GPIOB);
(gpiob.pb14.into_alternate(), gpiob.pb15.into_alternate())
};
// let (pin_dm, pin_dp) = {
// let gpioa = dp.GPIOA.split(ccdr.peripheral.GPIOA);
// (gpioa.pa11, gpioa.pa12)
// };
let usb = USB1::new(
dp.OTG1_HS_GLOBAL,
dp.OTG1_HS_DEVICE,
dp.OTG1_HS_PWRCLK,
pin_dm,
pin_dp,
ccdr.peripheral.USB1OTG,
&ccdr.clocks,
);
// Initialise EP_MEMORY to zero
unsafe {
let buf: &mut [MaybeUninit<u32>; 1024] =
&mut *(core::ptr::addr_of_mut!(EP_MEMORY) as *mut _);
for value in buf.iter_mut() {
value.as_mut_ptr().write(0);
}
}
// Now we may assume that EP_MEMORY is initialised
#[allow(static_mut_refs)] // TODO: Fix this
let usb_bus = UsbBus::new(usb, unsafe { EP_MEMORY.assume_init_mut() });
// let mut serial = usbd_serial::SerialPort::new(&usb_bus);
let mut midi_class = UsbMidiClass::new(&usb_bus, 1, 1).unwrap();
let usb_dev = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(0x16c0, 0x5e4))
.device_class(0)
.device_sub_class(0)
.strings(&[StringDescriptors::default()
.manufacturer("Music Company")
.product("MIDI Device")
.serial_number("12345678")])
.unwrap();
let mut usb_dev = usb_dev.build();
defmt::info!("Built!");
loop {
// info!("loop!");
if usb_dev.poll(&mut [&mut midi_class]) {
// info!("poll!");
// Receive messages.
let mut buffer = [0; 64];
if let Ok(size) = midi_class.read(&mut buffer) {
let packet_reader = UsbMidiPacketReader::new(&buffer, size);
for packet in packet_reader.into_iter().flatten() {
if !packet.is_sysex() {
// Just a regular 3-byte message that can be processed directly.
let message = MidiMessage::try_parse_slice(packet.payload_bytes());
defmt::println!(
"Regular Message, message: {:?}",
// packet.cable_number(),
message
);
}
}
}
}
}
// loop {
// if !usb_dev.poll(&mut [&mut serial]) {
// continue;
// }
// let mut buf = [0u8; 64];
// match serial.read(&mut buf) {
// Ok(count) if count > 0 => {
// // Echo back in upper case
// for c in buf[0..count].iter_mut() {
// if 0x61 <= *c && *c <= 0x7a {
// *c &= !0x20;
// }
// }
// let mut write_offset = 0;
// while write_offset < count {
// match serial.write(&buf[write_offset..count]) {
// Ok(len) if len > 0 => {
// write_offset += len;
// }
// _ => {}
// }
// }
// }
// _ => {}
// }
// }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment