简体   繁体   English

STM32裸机USB实现

[英]STM32 bare metal USB implementation

TLDR: I have a STM32G441 and want to implement a USB driver without the use of any HAL Libraries, just using CMSIS - for learning experience, for space and because what I want to do would require to change the hal anyway. TLDR:我有一个STM32G441并且想在不使用任何 HAL 库的情况下实现 USB 驱动程序,只使用 CMSIS - 学习经验、空间以及因为我想做的事情无论如何都需要更改 hal。

But I can't get this thing to receive anything.但我不能让这东西收到任何东西。 I'm stuck trying to get the Device Address, which is never handed to the code.我一直在尝试获取设备地址,该地址从未交给代码。 The hal middleware works just fine, so it's not a HW issue. hal 中间件工作得很好,所以这不是硬件问题。

What I'm doing我在做什么

I'm enabling the USB clock (correctly as I assume, because it can send ACK signals using my Logic Analyzer), power up the USB peripheral as defined in the datasheet, enable all the necessary Interrupts and handle the reset event by initializing the BTable and Endpoint 0. Now I expect to receive a CTR-Interrupt which never appears.我正在启用 USB 时钟(正如我假设的那样,因为它可以使用我的逻辑分析仪发送 ACK 信号),按照数据表中的定义为 USB 外设加电,启用所有必要的中断并通过初始化 BTable 来处理复位事件和端点 0。现在我希望收到一个永远不会出现的 CTR 中断。

Reference Manual 参考手册

Clock

The μC runs on a 25MHz HSE clock. μC 在 25MHz HSE 时钟上运行。 The USB periphery runs on the PLL Q clock at ~48MHz, RCC settings were verified with the CubeMX clock configurator. USB 外设在 ~48MHz 的 PLL Q 时钟上运行,RCC 设置已通过 CubeMX 时钟配置器进行验证。 AHB runs at half speed, because I get a bus error hard fault if I try to run it at full speed, but that's another question. AHB 以半速运行,因为如果我尝试全速运行它会出现总线错误硬故障,但这是另一个问题。 The System Clock is set to 143.75MHz.系统时钟设置为 143.75MHz。

RCC->CR |= RCC_CR_HSEON | RCC_CR_HSION;

// Configure PLL (R=143.75, Q=47.92)
RCC->CR &= ~RCC_CR_PLLON;
while (RCC->CR & RCC_CR_PLLRDY) {
}
RCC->PLLCFGR |= RCC_PLLCFGR_PLLSRC_HSE | RCC_PLLCFGR_PLLM_0 | (23 << RCC_PLLCFGR_PLLN_Pos) | RCC_PLLCFGR_PLLQ_1;
RCC->PLLCFGR |= RCC_PLLCFGR_PLLREN | RCC_PLLCFGR_PLLQEN;
RCC->CR |= RCC_CR_PLLON;

// Select PLL as main clock, AHB/2 > otherwise Bus Error Hard Fault
RCC->CFGR |= RCC_CFGR_HPRE_3 | RCC_CFGR_SW_PLL;

// Select & Enable IO Clocks (PLL > USB, ADC; HSI16 > UART)
RCC->CCIPR = RCC_CCIPR_CLK48SEL_0 | RCC_CCIPR_ADC12SEL_1 | RCC_CCIPR_USART1SEL_1 | RCC_CCIPR_USART2SEL_1 | RCC_CCIPR_USART3SEL_1 | RCC_CCIPR_UART4SEL_1;
RCC->AHB2ENR |= RCC_AHB2ENR_ADC12EN | RCC_AHB2ENR_GPIOAEN | RCC_AHB2ENR_GPIOBEN | RCC_AHB2ENR_GPIOCEN;
RCC->APB1ENR1 |= RCC_APB1ENR1_USBEN | RCC_APB1ENR1_UART4EN | RCC_APB1ENR1_USART3EN | RCC_APB1ENR1_USART2EN;
RCC->APB2ENR |= RCC_APB2ENR_USART1EN;

// Enable DMAMUX & DMA1 Clock
RCC->AHB1ENR |= RCC_AHB1ENR_DMAMUX1EN | RCC_AHB1ENR_DMA1EN;

USB Memory USB Memory

As far as I know, the USB BTable and endpoint buffers need to be placed in the USB-SRAM, not in regular SRAM.据我所知,USB BTable 和端点缓冲区需要放置在 USB-SRAM 中,而不是常规 SRAM 中。 I've added some linker directives to create a section for that, which seems to work just fine according to the memory analyzer.我添加了一些 linker 指令来为此创建一个部分,根据 memory 分析器,这似乎工作得很好。 Mem2Usb just recalculates the offset from absolute to relative to the USB-SRAM offset. Mem2Usb 只是重新计算从绝对偏移到相对于 USB-SRAM 偏移的偏移。

#define __USB_MEM __attribute__((section(".usbbuf")))
#define __USBBUF_BEGIN 0x40006000
#define __MEM2USB(X) (((int)X - __USBBUF_BEGIN))

First question : The access is only allowed to be 16 Bytes wide.第一个问题:访问只能是 16 字节宽。 But, contrary to eg STM32F103 there is no need for padding as it seems.但是,与 STM32F103 等相反,似乎不需要填充。 The memory tool has some problems displaying this region, because it is only handling WORD access while the tool uses DWORD access, but copying the memory allocated by the HAL word by word also shows no padding. memory 工具在显示该区域时存在一些问题,因为它只处理 WORD 访问,而该工具使用 DWORD 访问,但是逐字复制 HAL 分配的 memory 也显示没有填充。 Is that correct?那是对的吗? So I should be able to use all 1024 bytes, not just seeing them but only having 512. This is also the reason why mem2usb does not divide the address by 2.所以我应该能够使用所有的 1024 字节,不仅看到它们而且只有 512。这也是 mem2usb 不将地址除以 2 的原因。

Then I create some structures for the BTable and the zero-endpoint.然后我为 BTable 和零端点创建了一些结构。 The BTable ends up at 0x40006000 by default. BTable 默认为0x40006000 Endpoint 0 has a rx and a tx buffer with max 64 bytes as per USB spec.根据 USB 规范,端点 0 具有最大 64 字节的 rx 和 tx 缓冲区。 The alignments are taken from the Reference manual.对齐方式取自参考手册。 The memory is not automatically zeroed out. memory 不会自动归零。

typedef struct {
    unsigned short ADDR_TX;
    unsigned short COUNT_TX;
    unsigned short ADDR_RX;
    unsigned short COUNT_RX;
} USB_BTABLE_ENTRY;

__ALIGNED(8)
__USB_MEM
static USB_BTABLE_ENTRY BTable[8] = {0};

__ALIGNED(2)
__USB_MEM
static char EP0_Buf[2][64] = {0};

Initialization初始化

Enabling the NVIC, then power up, wait 1μs until clock is stable as per datasheet, then clear reset state, clear pending interrupts, enable interrupts and last enable the internal pull up to start enumeration.启用 NVIC,然后上电,等待 1μs 直到时钟根据数据表稳定,然后清除复位 state,清除挂起的中断,启用中断并最后启用内部上拉以开始枚举。

NVIC_SetPriority(USB_HP_IRQn, 0);
NVIC_SetPriority(USB_LP_IRQn, 0);
NVIC_SetPriority(USBWakeUp_IRQn, 0);
NVIC_EnableIRQ(USB_HP_IRQn);
NVIC_EnableIRQ(USB_LP_IRQn);
NVIC_EnableIRQ(USBWakeUp_IRQn);

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;

USB->CNTR &= ~USB_CNTR_FRES;
USB->ISTR = 0;

USB->CNTR |= USB_CNTR_RESETM | USB_CNTR_CTRM | USB_CNTR_WKUPM | USB_CNTR_SUSPM | USB_CNTR_ESOFM;
USB->BCDR |= USB_BCDR_DPPU;

USB Reset USB 复位

Now the host sends a reset signal, which is triggered correctly.现在主机发送一个复位信号,该信号被正确触发。 During the reset signal, I initialize the BTable and EP0.在复位信号期间,我初始化 BTable 和 EP0。 I set EP0 to ACK on RX and NACK on TX requests, as do other bare metal USB examples and the HAL (they are toggle, not write, but the register is in a known state of 0x00 as the hardware resets them on a reset).我将 EP0 在 RX 上设置为 ACK,在 TX 请求上设置为 NACK,其他裸机 USB 示例和 HAL 也是如此(它们是切换的,而不是写入的,但寄存器位于已知的 0x00 的 state 中,因为硬件会在重置时重置它们) . Lastly I put the USB peripheral in enable mode and reset the device address to 0.最后,我将 USB 外设置于启用模式并将设备地址重置为 0。

if ((USB->ISTR & USB_ISTR_RESET) != 0) {
    USB->ISTR = ~USB_ISTR_RESET;

    // Enable EP0
    USB->BTABLE = __MEM2USB(BTable);

    BTable[0].ADDR_TX = __MEM2USB(EP0_Buf[0]);
    BTable[0].COUNT_TX = 0;
    BTable[0].ADDR_RX = __MEM2USB(EP0_Buf[1]);
    BTable[0].COUNT_RX = (1 << 15) | (1 << 10);

    USB->EP0R = USB_EP_CONTROL | (2 << 4) | (3 << 12);
    USB->CNTR = USB_CNTR_CTRM | USB_CNTR_RESETM;

    USB->DADDR = USB_DADDR_EF;
}

Debugging shows that the BTable is indeed at 0x40006000 and the Buffer address is written (I assume) correctly.调试显示 BTable 确实位于0x40006000并且缓冲区地址已正确写入(我假设)。 The EP0 register was compared to a working HAL implementation and they are the same at that point.将 EP0 寄存器与工作中的 HAL 实现进行了比较,此时它们是相同的。

Here I'm stuck我在这里卡住了

I expect the host to send the device address next (it doesn't, it sends a sleep and a wakeup and then another reset first), which will trigger the CRT interrupt (which is masked).我希望主机接下来发送设备地址(它没有,它发送睡眠和唤醒,然后首先发送另一个复位),这将触发 CRT 中断(被屏蔽)。 Point is, it never does.关键是,它永远不会。 And I don't know why.我不知道为什么。 The host sends the request just fine and the device sends an ACK on that request just fine (logic analyzer), but the CRT is never triggered.主机发送请求就好了,设备就该请求发送一个 ACK 就好了(逻辑分析仪),但 CRT 永远不会被触发。 Any ideas what else I can try or where to look?有什么想法我还可以尝试或在哪里看?

Update更新

I've now compared the messages from my implementation with the HAL ones.我现在已经将我实现的消息与 HAL 的消息进行了比较。 The interrupt now handles the exact same messages in the exact same order and the USB-Registers also contain exactly the same values for every request.中断现在以完全相同的顺序处理完全相同的消息,并且 USB 寄存器也为每个请求包含完全相同的值。 I've changed the BTable and USB-SRAM layout to contain the exact same values as the HAL after the Reset-Interrupt.我已更改 BTable 和 USB-SRAM 布局以包含与重置中断后的 HAL 完全相同的值。

I had to implement the SUSP and WKUP for this to work, which was probably one of the things thats missing.我必须实现SUSPWKUP才能工作,这可能是缺少的东西之一。 Now they both behave exactly the same.现在它们的行为完全相同。 It turns out, the problem is that I never receive a proper SOF-Package.事实证明,问题是我从来没有收到过合适的 SOF 包裹。 The HAL gets its first SOF directly after the second reset (HW-Reset > 2x ESOF > SUSP > WKUP > RESET > (Optional 1 ESOF) > SOF), while mine gets an ERR instead of the SOF . HAL 在第二次重置后直接获得第一个SOF (HW-Reset > 2x ESOF > SUSP > WKUP > RESET > (Optional 1 ESOF) > SOF),而我的获得的是ERR而不是SOF

Looks like the error is not related to the USB registers or USB-SRAM.看起来该错误与 USB 寄存器或 USB-SRAM 无关。 Next step will be to compare all registers I can think of as relevant between the two implementations.下一步将是比较我认为在这两种实现之间相关的所有寄存器。 Maybe I forgot a clock?也许我忘记了时钟?

Spend almost a week.花差不多一个星期。 Just to figure out I misconfigured my 48MHz clock source...只是为了弄清楚我错误地配置了我的 48MHz 时钟源......

RCC->CCIPR = RCC_CCIPR_CLK48SEL_0 | ...

This sets the CLK48SEL to Reserved (01), not the PLLQ-Clock (10)...这会将 CLK48SEL 设置为Reserved (01),而不是 PLLQ 时钟 (10)...

RCC->CCIPR = RCC_CCIPR_CLK48SEL_1 | ...

Now I get the SOF packages and the CTR alright.现在我得到了SOF包和CTR May that question serve as a USB bare metal reference in the future.愿这个问题在未来作为 USB 裸机参考。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM