在嵌入式开发中,STM32 USB HID 设备的应用非常广泛,尤其是在一些需要简单人机交互的场景。最近在做一个项目,需要将一个基于 LAT1466 的 STM32 USB HID 设备,从原有的复杂系统独立出来,作为一个 standalone 应用运行。过程中遇到了不少坑,特此记录。
LAT1466 简介与问题场景
LAT1466 并非一个标准的 STM32 外设或库,而是指某些特定厂商或项目中使用的一种自定义的 USB 接口或者模块,其 HID (Human Interface Device) 功能允许设备通过 USB 接口模拟键盘、鼠标等输入设备,实现与上位机的通信。我们遇到的问题是:原工程依赖大量的底层库和中间件,体积庞大,不适合作为一个独立的 HID 设备应用。
问题:依赖复杂,资源占用高
原工程为了实现各种功能,集成了 FreeRTOS、LWIP 等重量级组件。但我们的 HID 设备只需要简单的 USB 通信功能,这些组件完全是多余的。此外,原工程的代码结构复杂,耦合度高,难以分离出 HID 设备相关的代码。
问题:USB 描述符配置不清晰
LAT1466 相关的 USB 描述符配置分散在多个文件中,难以理解和修改。特别是 HID 报告描述符,定义了设备与上位机之间的数据传输格式,稍有不慎就会导致设备无法正常工作。
底层原理深度剖析
要解决上述问题,我们需要深入理解 STM32 USB HID 设备的工作原理,以及 USB 协议的相关知识。
USB HID 协议
USB HID 协议定义了设备与主机之间的通信方式,包括设备描述符、配置描述符、接口描述符、端点描述符和 HID 描述符。其中,HID 报告描述符最为关键,它定义了设备与主机之间的数据传输格式。
STM32 USB 中断处理
STM32 通过 USB 外设的中断来处理 USB 事件,例如设备连接、数据接收和数据发送。我们需要编写中断服务程序(ISR)来处理这些事件,并更新设备的状态。
端点配置
USB 通信通过端点进行,每个端点都有一个唯一的地址和传输类型。HID 设备通常使用中断端点进行数据传输,以保证数据的实时性。
解决方案与代码示例
下面我们将逐步介绍如何将 LAT1466 的 STM32 USB HID 设备独立出来,作为一个 standalone 应用运行。
步骤 1:创建独立工程
首先,我们需要创建一个新的 STM32 工程,只包含必要的启动代码、时钟配置和 USB 外设初始化代码。可以使用 STM32CubeIDE 或其他开发工具创建工程。
步骤 2:移植 USB 驱动
将原工程中与 USB HID 设备相关的驱动代码移植到新工程中。这些代码通常包括 USB 初始化函数、中断服务程序、数据接收和发送函数。
步骤 3:精简 USB 描述符
仔细分析原工程中的 USB 描述符,删除不必要的字段,并确保 HID 报告描述符的正确性。下面是一个简化的 HID 报告描述符示例:
/* HID report descriptor */
__ALIGN_BEGIN static uint8_t USBD_HID_ReportDesc[USBD_HID_REPORT_DESC_SIZE] __ALIGN_END =
{
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
0x09, 0x06, // USAGE (Keyboard)
0xA1, 0x01, // COLLECTION (Application)
0x05, 0x07, // USAGE_PAGE (Keyboard/Keypad)
0x19, 0xE0, // USAGE_MINIMUM (Keyboard LeftControl)
0x29, 0xE7, // USAGE_MAXIMUM (Keyboard Right GUI)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x25, 0x01, // LOGICAL_MAXIMUM (1)
0x75, 0x01, // REPORT_SIZE (1)
0x95, 0x08, // REPORT_COUNT (8)
0x81, 0x02, // INPUT (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x95, 0x01, // REPORT_COUNT (1)
0x75, 0x08, // REPORT_SIZE (8)
0x81, 0x01, // INPUT (Cnst,Var,Abs)
0x95, 0x05, // REPORT_COUNT (5)
0x75, 0x01, // REPORT_SIZE (1)
0x05, 0x08, // USAGE_PAGE (LEDs)
0x19, 0x01, // USAGE_MINIMUM (Num Lock)
0x29, 0x05, // USAGE_MAXIMUM (Kana)
0x91, 0x02, // OUTPUT (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x95, 0x01, // REPORT_COUNT (1)
0x75, 0x03, // REPORT_SIZE (3)
0x91, 0x01, // OUTPUT (Cnst,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x95, 0x06, // REPORT_COUNT (6)
0x75, 0x08, // REPORT_SIZE (8)
0x05, 0x07, // USAGE_PAGE (Keyboard/Keypad)
0x19, 0x00, // USAGE_MINIMUM (Reserved (no event indicated))
0x29, 0x65, // USAGE_MAXIMUM (Keyboard Application)
0x81, 0x00, // INPUT (Data,Ary,Abs)
0xC0 // END_COLLECTION
};
步骤 4:编写主循环
在主循环中,我们需要不断地检测 USB 连接状态,并处理接收到的数据。下面是一个简单的主循环示例:
int main(void)
{
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* Configure the system clock */
SystemClock_Config();
/* Initialize USB device */
USBD_Init(&hUsbDeviceFS, &FS_Desc, 0);
/* Register HID class */
USBD_RegisterClass(&hUsbDeviceFS, &USBD_HID);
/* Start device */
USBD_Start(&hUsbDeviceFS);
/* Infinite loop */
while (1)
{
// 检测 USB 连接状态
if (hUsbDeviceFS.dev_state == USBD_STATE_CONFIGURED) {
// 处理接收到的数据
}
}
}
实战避坑经验总结
避免使用 HAL 库的默认配置
HAL 库的默认配置可能不适合我们的应用,需要仔细检查并修改。
仔细阅读 STM32 的 USB 参考手册
STM32 的 USB 参考手册包含了大量的技术细节,是解决问题的关键。
使用 USB 分析仪
USB 分析仪可以帮助我们抓取 USB 数据包,分析通信过程,快速定位问题。
注意中断优先级
确保 USB 中断的优先级高于其他中断,以保证 USB 通信的实时性。
通过以上步骤,我们可以将 LAT1466 的 STM32 USB HID 设备独立出来,作为一个 standalone 应用运行。这个过程需要耐心和细致,但最终可以提高代码的可维护性和可移植性。在实际操作中,还需要根据具体情况进行调整,例如优化代码结构、改进 USB 描述符等。希望这篇文章能帮助大家解决类似的问题。
冠军资讯
键盘上的咸鱼