更新 对于任何感兴趣的人,这里提供了一步一步的说明和解释,介绍如何构建裸机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完全相同的值。
为此,我必须实现SUSP
和WKUP
,这可能是其中缺少的东西之一。现在它们的行为完全相同。问题是,我从未收到适当的SOF-Package。HAL在第二次重置(HW-Reset > 2x ESOF > SUSP > WKUP > RESET >(可选1 ESOF)> SOF)之后直接获得其第一个SOF
,而我的则会收到ERR
而不是SOF
。
看起来错误与USB寄存器或USB-SRAM无关。下一步将是比较我可以想到的所有与两个实现相关的寄存器。也许我忘记了时钟?
USB->EP0R
的方式。我不明白为什么您要以那种方式设置/切换该寄存器中的位。您可以使用调试器读取工作HAL代码设置该寄存器时的确切值,并对您的代码执行相同操作并比较两个值(如果您希望您的代码正常工作,则它们应该相等)。还要注意,该寄存器中的某些位只能被切换;它们不能直接设置为0或1。 - David Grayson