我想通过USB从STM32发送数据。我编写了以下裸机代码来执行此操作。但是我的电脑在插入 USB 设备时无法识别。
#include "stm32f10x.h"
void USB_Endpoint_Configure(uint8_t endpoint, uint8_t type, uint8_t dir) {
// Check the parameters.
if (endpoint >= 16) {
return;
}
// Set the endpoint type.
*((volatile unsigned int *)0x40005C00 + endpoint * 4 + 0x00) = type;
// Set the endpoint direction.
*((volatile unsigned int *)0x40005C00 + endpoint * 4 + 0x01) = dir;
// Set the endpoint max packet size.
*((volatile unsigned int *)0x40005C00 + endpoint * 4 + 0x02) = 64;
// Enable the endpoint.
*((volatile unsigned int *)0x40005C00 + endpoint * 4 + 0x04) |= 0x00000001;
}
int USB_Endpoint_Write(uint8_t endpoint, const void *data, int size) {
// Check if the endpoint is valid.
if (endpoint >= 0x80 || endpoint >= 0xE0) {
return -1;
}
// Check if the data is large enough to fill the endpoint buffer.
if (size > 64) {
return -1;
}
// Write the data to the endpoint buffer.
for (int i = 0; i < size; i++) {
*((uint8_t *)(0x40000000 + (endpoint << 7) + i)) = *((const uint8_t *)data + i);
}
// Send the data to the device.
return 0;
}
int main(void){
//USB_Init()
// Reset the USB controller.
RCC->APB1ENR |= RCC_APB1ENR_USBEN;
*((volatile unsigned int *)0x40005C00) |= 0x00000001;
while (*((volatile unsigned int *)0x40005C00) & 0x00000001);
// Initialize the USB clock.
RCC->APB1ENR |= 0x00000001;
// Initialize the USB interrupts.
NVIC_EnableIRQ(5);
USB_Endpoint_Configure(0x00, 0x00, 0x00);
USB_Endpoint_Configure(0x80, 0x00, 0x40);
USB_Endpoint_Configure(0x00, 0x00, 0x00);
char data[]="Hello WOrld!!!";
USB_Endpoint_Write(0x00, data, sizeof(data));
//Enable endpoint EP0
*((uint8_t *)(0x40000000 + (0x00 << 7) + 0)) |= 1;
// Wait for the data to be transferred.
while (!*((uint8_t *)(0x40000000 + (0x00 << 7) + 1)) & 0x80) {
// Do nothing.
}
//Disable endpoint EP0
*((uint8_t *)(0x40000000 + (0x00 << 7) + 0)) |= 0;
}
我可以在通过 HAL 库编码时做同样的事情,并且成功。因此,USB 电缆或设备不存在任何物理问题。那么有人可以帮助在裸机中做到这一点吗? (我使用的是STM32F103C8芯片)
USB 恰好比这更复杂。您至少应该从主机接收 SETUP 数据包,用一些合理的内容回答它,在操作系统认为您的设备工作之前继续进行总线枚举 - 一旦您出现,您就可以开始交换数据。
不幸的是,这意味着一些额外的“聊天”,这就是您了解描述您的设备、其 EP 等的“USB 描述符”的地方。参见例如linux下wireshark其他设备如何做枚举阶段和做同样的事情。仅供参考,USB 设备无法自行发送数据,除非主机要求它这样做并且设备用数据应答。这就是 EP 缓冲区的真正用途 - 当主机询问时,硬件将从缓冲区中排序正确的答案。
您可以尝试查看类似 https://github.com/eddyem/stm32samples - 它具有一些 USB、裸机风格,甚至伪造一些 USB 串行桥接 IC 以避免编写操作系统驱动程序。很少有其他类似的例子。这个使用(Cortex M3 端)中断来服务端点,我还遇到了一些在“轮询模式”下执行 EP 服务的东西,但是执行 SETUP 等操作并不完全是养眼的代码,因为您基本上应该包含一些 USB 协商逻辑.
另外:USB 对时序相当严格。最值得注意的是,它在 D+ 或 D- 线上有“下拉电阻”的概念,并且最好由软件控制。大多数支持 USB 的主板都会有 USB 下拉控制电路,您可以知道它在主板上的位置。
流程为:
建议这样做,因为如果您的系统初始化无法满足严格的 USB 时序(IIRC 自总线上检测到下拉电阻以来大约 10 毫秒),主机可能/会认为您的设备有故障,并且可能会忽略它或执行各种奇怪的“恢复” “东西(例如总线重置/失速/......你显然还没有处理)。因此,一旦您能够真正承受严格的 USB 超时计时,完成所有操作并启用下拉会更安全。
顺便说一句,Linux 使事情变得更加容易,因为您至少可以看到操作系统是否检测到您的设备以及它在“dmesg”输出中得到了什么,给出了上拉是否有效、操作系统如何看到您的 USB 描述符、枚举是否有效以及像wireshark这样的工具可以帮助立即查看USB I/O数据包流(它的字面意思是“modprobe usbmon”,然后只运行wireshark,选择USB作为源 - 只需2步即可进行低级USB调试)。