STM32裸机USB实现

5

更新 对于任何感兴趣的人,这里提供了一步一步的说明和解释,介绍如何构建裸机USB堆栈,如何处理这样的项目以及每个步骤需要了解的内容:STM32USB@GitHub

TLDR: 我有一个STM32G441,想要实现一个USB驱动程序,不使用任何HAL库,只使用CMSIS-用于学习经验、节省空间,并且因为我想做的事情需要改变hal。

但是我无法接收到任何东西。我卡在了获取设备地址上,该地址从未传递给代码。hal中间件运行正常,所以这不是硬件问题。

我正在做什么

我启用了USB时钟(正确地假定,因为它可以使用我的逻辑分析器发送ACK信号),按照数据手册定义的方式启动USB外设,启用所有必要的中断并通过初始化BTable和Endpoint 0来处理复位事件。现在我期望接收到一个CTR中断,但它从未出现。

参考手册

时钟

μC运行在25MHz的HSE时钟上。USB外设在PLL Q时钟下以约48MHz运行,RCC设置使用了CubeMX时钟配置器进行验证。AHB以半速运行,因为如果我尝试以全速运行它,会出现总线错误硬故障,但这是另一个问题。系统时钟设置为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存储设备

据我所知,USB BTable和端点缓冲区需要放置在USB-SRAM中,而不是常规SRAM中。我添加了一些链接器指令来创建一个用于此目的的部分,根据内存分析器的结果看起来运行得很好。Mem2Usb只是重新计算了从绝对地址到USB-SRAM偏移量的相对偏移量。

#define __USB_MEM __attribute__((section(".usbbuf")))
#define __USBBUF_BEGIN 0x40006000
#define __MEM2USB(X) (((int)X - __USBBUF_BEGIN))
第一个问题: 访问只允许16字节宽度。但与例如STM32F103相反,似乎没有需要填充的必要。由于内存工具仅处理WORD访问,而工具使用DWORD访问,因此内存工具在显示此区域时存在一些问题,但是逐字复制HAL分配的内存也不显示填充。这正确吗?因此,我应该能够使用所有1024个字节,而不仅仅是看到它们但只有512个。这也是为什么mem2usb不将地址除以2的原因。
然后我为BTable和零端点创建一些结构。默认情况下,BTable位于0x40006000。端点0具有最大64字节的rx和tx缓冲区,符合USB规范。对齐方式取自参考手册。内存不会自动清零。
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};

初始化

启用NVIC,然后上电,在数据手册规定的等待1微秒直到时钟稳定之后,清除复位状态,清除挂起中断,启用中断并最后启用内部上拉以开始枚举。

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复位

现在主机发送了一个复位信号,它被正确触发。在复位信号期间,我初始化了BTable和EP0。我将EP0设置为对RX请求进行ACK应答,对TX请求进行NACK应答,就像其他裸机USB示例和HAL一样(它们是toggle,而不是write,但寄存器处于已知状态0x00,因为硬件在复位时会重置它们)。最后,我将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;
}

调试显示BTable确实位于0x40006000,缓冲区地址被正确写入(我假设)。EP0寄存器与工作的HAL实现进行了比较,在该点上它们是相同的。

这里我卡住了

我期望主机接下来发送设备地址(它没有,它先发送了一个睡眠和唤醒,然后再发送另一个重置),这将触发CRT中断(被屏蔽)。问题是,它从未这样做。主机很好地发送请求,设备也很好地在该请求上发送了ACK(逻辑分析仪),但是CRT从未被触发。有什么其他的想法或者可以尝试的地方吗?

更新

我现在已经将我的实现消息与HAL的消息进行了比较。中断现在以完全相同的顺序处理完全相同的消息,并且USB寄存器对于每个请求也包含完全相同的值。我已经更改了BTable和USB-SRAM布局,使其在Reset-Interrupt之后包含与HAL完全相同的值。

为此,我必须实现SUSPWKUP,这可能是其中缺少的东西之一。现在它们的行为完全相同。问题是,我从未收到适当的SOF-Package。HAL在第二次重置(HW-Reset > 2x ESOF > SUSP > WKUP > RESET >(可选1 ESOF)> SOF)之后直接获得其第一个SOF,而我的则会收到ERR而不是SOF

看起来错误与USB寄存器或USB-SRAM无关。下一步将是比较我可以想到的所有与两个实现相关的寄存器。也许我忘记了时钟?


1
我会先尝试使用HAL,然后逐步浏览其源代码,并用自己的函数替换其中的函数。在这个过程中,我将学习如何做以及如何不做。很可能我也会发现HAL中的错误。当只剩下我的函数时,我可以深入重构,直到完全了解其工作原理。 - the busybee
CRT中断是什么?你是否打错了,实际上是指USB中断状态寄存器(USB_ISTR)中的CTR(Correct Transfer)位? - David Grayson
当接收到USB复位信号时,我会仔细检查您设置USB->EP0R的方式。我不明白为什么您要以那种方式设置/切换该寄存器中的位。您可以使用调试器读取工作HAL代码设置该寄存器时的确切值,并对您的代码执行相同操作并比较两个值(如果您希望您的代码正常工作,则它们应该相等)。还要注意,该寄存器中的某些位只能被切换;它们不能直接设置为0或1。 - David Grayson
@DavidGrayson 是的,就是那个 ^^' 与此同时,我已经检查并比较了所有USB寄存器与HAL,并在同一时间步骤中使它们包含完全相同的信息。有一些新的情报,但即使请求现在完全相同,它仍然无法工作。我已经将我的观察结果添加到问题中。 - CShark
这是该死的时钟,该死! - CShark
显示剩余2条评论
1个回答

7

花了将近一周的时间才发现我错误地配置了我的48MHz时钟源...

RCC->CCIPR = RCC_CCIPR_CLK48SEL_0 | ...

这将把CLK48SEL设置为保留(01),而不是PLLQ时钟(10)...

RCC->CCIPR = RCC_CCIPR_CLK48SEL_1 | ...

现在我已经正确接收到了 SOFCTR 包。希望这个问题能够在将来作为USB裸机的参考。

也许我的USB CDC(PL2303仿真)对你有用。 - Eddy_Em
@Eddy_Em 谢谢,我已经超越 CDC,现在进入以太网卡仿真 :) - CShark

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接