本文介绍如何使用stm32连接usb接口的飞行摇杆。
开发环境
硬件: STM32F429IGT6开发板,USB接口的飞行摇杆。
软件:STM32CubeIDE 1.11
仿真器:stlink
参考书:《圈圈教你玩USB》
USB设备描述符
一个USB设备只有一个设备描述符。USB主机通过不同种类的描述符,获得一个设备的特性。描述符的种类包括:设备描述符,配置描述符,接口描述符,端点描述符。USB描述符之间的关系是一种树状结构。设备描述符包含多个配置描述符,配置描述符包含多个接口描述符,接口描述符包含多个端点描述符。如下图所示:
STM32CubeIDE的USB HOST模块实现了USB设备通讯,实现了设备发现到设备枚举,获得描述符等一系列操作。在此基础之上,可以方便的开发USB主机模式的应用。本文使用USB HOST模块实现连接USB飞行摇杆。
建立项目
使用CubeIDE 新建一个F429项目,项目的名称为F429JoyStick。
芯片配置:
RCC
USB_OTG_FS
USB_HOST
时钟
USB使用的时钟,必须是48MHz,否则在运行代码时,通讯会失败,无法获得设备描述符。
配置完成,保存并生成代码。
USB设备处理流程
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USB_HOST_Init(); //初始化USB硬件
while (1)
{
MX_USB_HOST_Process(); //更新USB设备状态。
}
}
main方法循环调用MX_USB_HOST_Process(),所有USB操作都在MX_USB_HOST_Process()方法里完成。MX_USB_HOST_Process()最终会调用usbh_core.c文件里的 USBH_StatusTypeDef USBH_Process (USBH_HandleTypeDef *phost) 方法,这个方法的源代码较长。以下是这个方法的流程图:
USBH_Process()使用状态机在不同的状态之间转换,一切顺利的情况下,将会获得所有的描述符。
调试及修改
用仿真器烧录程序、调试,会发现运行到HOST_CHECK_CLASS状态时,直接跳转到了HOST_ABORT_STATE状态。问题出在这一段代码:
第714行 if (phost->pActiveClass->Init(phost) ==USBH_OK)
只有在返回值为USBH_OK时,才能转到下一个状态,否则转到HOST_ABORT_STATE状态。
phost->pActiveClass->Init(phost)调用的是usbh_hid.c文件里的static USBH_StatusTypeDef USBH_HID_InterfaceInit(USBH_HandleTypeDef*phost)方法
代码如下:
USBH_HID_InterfaceInit方法继续调用USBH_FindInterface方法:
这段代码遍历&pcfg->Itf_Desc的所有接口描述符,如果有符合条件的描述符,返回描述符的序号,否则返回0xFFU。
下图是使用仿真器获得的飞行摇杆的接口描述符:
其中的pcfg->Itf_Desc[0]接口描述符里的三个值是
bInterfaceClass = 3
bInterfaceSubClass = 0
bInterfaceProtocol = 0
这三个值很重要,后面的代码修改都是围绕这三个值。
if (((pif->bInterfaceClass ==Class) || (Class == 0xFFU)) &&
((pif->bInterfaceSubClass ==SubClass) || (SubClass == 0xFFU)) &&
((pif->bInterfaceProtocol == Protocol) || (Protocol == 0xFFU)))
这个判断条件里的其他值
Class = 3
SubClass = 1
Protocol = 0xFFU
显然不满足判断语句的条件。USB HOST默认生成的代码不支持Class=3,SubClass=0,Protocol=0的USB设备。
修改USBH_HID_InterfaceInit(USBH_HandleTypeDef*phost)方法的代码,修改结果如下:
重新编译后继续调试,会发现USBH_HID_InterfaceInit(USBH_HandleTypeDef*phost)方法里的这段代码又出现了问题:
同样的道理,对于不支持的USB设备,会直接返回USBH_FAIL。为此,需要在项目里增加一个飞行摇杆设备。
增加usbh_hid_joystick.h, usbh_hid_joystick.c文件
新增文件的目录结构如下:
usbh_hid_joystick.h
#ifndef __USBH_HID_JOYSTICK_H
#define __USBH_HID_JOYSTICK_H
#ifdef __cplusplus
extern "C" {
#endif
#include "usbh_hid.h"
#define PROTOLENGTH 8
typedef struct _HID_JOYSTICK_Info
{
uint8_t data[PROTOLENGTH];
}
HID_JOYSTICK_Info_TypeDef;
USBH_StatusTypeDef USBH_HID_JoyStickInit(USBH_HandleTypeDef *phost);
HID_JOYSTICK_Info_TypeDef *USBH_HID_GetJoyStickInfo(USBH_HandleTypeDef *phost);
#ifdef __cplusplus
}
#endif
#endif
usbh_hid_joystick.c
#include "usbh_hid_joystick.h"
#include "usbh_hid_parser.h"
#include <string.h>
HID_JOYSTICK_Info_TypeDef joystick_info;
uint8_t joystick_report_data[PROTOLENGTH];
uint8_t joystick_rx_report_buf[PROTOLENGTH];
static USBH_StatusTypeDef USBH_HID_JoyStickDecode(USBH_HandleTypeDef *phost);
USBH_StatusTypeDef USBH_HID_JoyStickInit(USBH_HandleTypeDef *phost)
{
uint32_t i;
HID_HandleTypeDef *HID_Handle = (HID_HandleTypeDef *) phost->pActiveClass->pData;
for (i = 0U; i <PROTOLENGTH; i++)
{
joystick_info.data[i] = 0U;
joystick_report_data[i] = 0U;
joystick_rx_report_buf[i] = 0U;
}
if (HID_Handle->length > sizeof(joystick_report_data))
{
HID_Handle->length = (uint16_t)sizeof(joystick_report_data);
}
HID_Handle->pData = (uint8_t *)(void *)joystick_rx_report_buf;
USBH_HID_FifoInit(&HID_Handle->fifo, phost->device.Data, (uint16_t)(HID_QUEUE_SIZE * sizeof(joystick_report_data)));
return USBH_OK;
}
HID_JOYSTICK_Info_TypeDef *USBH_HID_GetJoyStickInfo(USBH_HandleTypeDef *phost)
{
if (USBH_HID_JoyStickDecode(phost) == USBH_OK)
{
return &joystick_info;
}
else
{
return NULL;
}
}
static USBH_StatusTypeDef USBH_HID_JoyStickDecode(USBH_HandleTypeDef *phost)
{
HID_HandleTypeDef *HID_Handle = (HID_HandleTypeDef *) phost->pActiveClass->pData;
if (HID_Handle->length == 0U)
{
return USBH_FAIL;
}
if (USBH_HID_FifoRead(&HID_Handle->fifo, &joystick_report_data, HID_Handle->length) == HID_Handle->length)
{
for(int i=0;i<HID_Handle->length;i++)
{
joystick_info.data[i] = *(joystick_report_data + i);
}
return USBH_OK;
}
return USBH_FAIL;
}
修改其他文件,修改内容如下:
增加适当的include文件后,保存所有修改,重新生成文件,调试。
将断点设置在usb_host.c的如下位置:
程序能运行到Appli_state = APPLICATION_READY这一行说明USB设备可以正常连接了。
其他
在usbh_hid_joystick.h里的PROTOLENGTH是USB数据传输时使用的长度
#define PROTOLENGTH 8
PROTOLENGTH定义的长度值需要从端点描述符里获得:
获得位置也是在USBH_HID_InterfaceInit(USBH_HandleTypeDef*phost)方法里
其中HID_Handle->length变量的值就是PROTOLENGTH需要定义的值。
usbh_hid_joystick.c :
USBH_HID_GetJoyStickInfo方法用来获得飞行摇杆的数据。