Skip to content

02. USB Macrocell

Nathanael Schneider edited this page Sep 10, 2024 · 5 revisions

Now we create a new file to contain all the bare-bone USB stuff. The first thing we implement is the initialization of the USB peripheral.

2.0 Theory

Initializing the periphery is pretty straight forward and well laid-out in the reference manual. You'll need to enable the NVIC-Interrupts, enable the USB-subcircuit, set the interruptmask and optionally the internal pull-up on D+ before clearing the reset state.

Clearing the reset state will signal the USB-Host to start the enumeration process.

2.1 Initialization

void USB_Init() {
    // Initialize the NVIC
    NVIC_SetPriority(USB_LP_IRQn, 8);
    NVIC_EnableIRQ(USB_LP_IRQn);

    // Enable USB macrocell
    USB->CNTR &= ~USB_CNTR_PDWN;

    // Wait 1μs until clock is stable
    SysTick->LOAD = 100;
    SysTick->VAL = 0;
    SysTick->CTRL = 1;
    while ((SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk) == 0) {
    }
    SysTick->CTRL = 0;

    // Enable all interrupts & the internal pullup to put 1.5K on D+ for FullSpeed USB
    USB->CNTR |= USB_CNTR_RESETM | USB_CNTR_CTRM;
    USB->BCDR |= USB_BCDR_DPPU;

    // Clear the USB Reset (D+ & D- low) to start enumeration
    USB->CNTR &= ~USB_CNTR_FRES;
}

We activate the NVIC-Interrupts necessary for the USB-Peripheral and then enable the peripheral. The USB-Peripheral needs to be activated in three steps:

  1. First the macrocell needs to be enabled. We need to wait 1μs until the clock is stable before we can clear the reset state. The SysTick delay waits for 100 clock cycles of the AHB clock (72MHz), which is a bit over 1μs.
  2. We Enable all the interrupts for the USB peripheral and enable the internal pull-up on the D+ line. If the chip does not have an internal pull-up, an external one with 1.5K on D+ is required for FullSpeed USB.
  3. Lastly, we clear the reset state of the peripheral so it can start to work.

2.2 NVIC-Handlers

We define the NVIC-Handler we enabled during initialization as well.

void USB_LP_IRQHandler() {

}

There are two more Handlers available (USB_HP_IRQHandler & USBWakeUp_IRQHandler), but we don't need them. The HighPriority-handler gets called on Burst- and Isochronous Transfers, while the WakeUp-handler gets called when the peripheral receives a WakeUp-Signal.

2.3 Verification & Debugging

Now the device should jump into the LP-Handler as soon as you plug it in, meaning that the peripheral had a successful reset.

To verify the source of the Interrupt, inspect the USB_ISTR-register. It should denote a reset-event. Also note, that USB cannot be stepped through, as the protocol is timing critical. More on that as we go.

This means that the USB_ISTR register might contain more than what it should, because the USB-peripheral might trigger additional interrupts even in break mode. To get an accurate view of the register, copy it into a variable just before the breakpoint.

Windows should show a message that it failed to enumerate the USB-device. The device manager should contain an Unknown USB Device (Device Descriptor Request Failed).