需求
实习工作,老板要求用单片机读取驾驶模拟器(Joystick)返回的数据,驾驶模拟器usb输出,输出信息包括:方向盘转角、左右拨杆、按键等。
完整代码传送门
https://download.youkuaiyun.com/download/renzemingcsdn/15726059
硬件
采用正点原子探索者开发板,即插即用,硬件不需要改动。开发板做主设备,因此必须选用f4系列,f103只能做从设备,其他能做主设备的f1,没就用过,不做尝试,直接用f407zgt6。驾驶模拟器上是一个stm32单片机,商家已经擦除芯片型号,盲猜是f1系列,因为模拟驾驶器是USB从设备,只需要f1系列即可。
探索的思路
最初不知道驾驶模拟器是用什么协议,于是开始漫长的探索过程。
usb HID设备
首先,将驾驶模拟器插入windows电脑,再任务管理器中发现,多出两行,表明该设备为USB HID设备。USB HID是Human Interface Device的缩写,由其名称可以了解HID设备是直接与人交互的设备,例如键盘、鼠标与游戏杆等。通过window的系统调用,可以接收到模拟量和数字量。协议讲解 在这里。
使用Windows读取数据程序如下,也可以参考这里。
#include<stdio.h>
#include <iostream>
#include<stdlib.h>
#include<conio.h>
#include <iostream>
#include<string>
#include<Windows.h>
//添加joystick操作api的支持库
#include<MMSystem.h>
#pragma comment(lib, "Winmm.lib")
using namespace std;
int main()
{
UINT joyNums;
joyNums = joyGetNumDevs();//读取手柄信息
printf("当前手柄数量:%d \n", joyNums);//采集手柄数量;
JOYINFO joyinfo;//定义joystick信息结构体
JOYINFOEX joyinfoex;
joyinfoex.dwSize = sizeof(JOYINFOEX);
joyinfoex.dwFlags = JOY_RETURNALL;
while (1)
{
MMRESULT joyreturn = joyGetPosEx(JOYSTICKID1, &joyinfoex);
cout << joyreturn<< endl;
cout << joyinfoex.dwXpos << endl;
Sleep(1000);
}
Sleep(3000);
return 0;
}
Windows下读取到的数据写在Joyinfoex结构体中,这里不再赘述。
stm32官方usb库移植
官方usb库下载教程。
STM32官方USB例程JoyStick详解。
移植过程可以参考正点原子教程,正点原子将回调函数写好了,但仅限打印相关提示话语,具体如何处理数据还需要自行修改。
修改为读取驾驶模拟器程序
官方库中有Joystick从机,也就是驾驶模拟器那边的例程,还有鼠标键盘读取的例程,因为鼠标键盘也是USB HID设备,我这里在正点原子提供的,移植好的鼠标键盘例程的基础上,修改为可以读取Joystick的程序。
直接用鼠标的程序,把驾驶器插进去,会有如下输出信息,说明两边USB协议层已经识别,而且驾驶模拟器这边方向盘自行转动做初始化操作,说明从机设备有响应。但显示无法识别的USB设备,推测应该是HID层的问题。
用文本搜索功能,找到回调函数中显示的内容
//usbh_usr.c文件
//无法识别的USB设备
void USBH_USR_DeviceNotSupported(void)
{
printf("无法识别的USB设备!\r\n\r\n");
}
继续搜索该回调函数,发现他是一个结构体的成员。
//usbh_usr.c文件
//USB HOST 用户回调函数.
USBH_Usr_cb_TypeDef USR_Callbacks =
{
USBH_USR_Init,
USBH_USR_DeInit,
USBH_USR_DeviceAttached,
USBH_USR_ResetDevice,
USBH_USR_DeviceDisconnected,
USBH_USR_OverCurrentDetected,
USBH_USR_DeviceSpeedDetected,
USBH_USR_Device_DescAvailable,
USBH_USR_DeviceAddressAssigned,
USBH_USR_Configuration_DescAvailable,
USBH_USR_Manufacturer_String,
USBH_USR_Product_String,
USBH_USR_SerialNum_String,
USBH_USR_EnumerationDone,
USBH_USR_UserInput,
NULL,
USBH_USR_DeviceNotSupported,
USBH_USR_UnrecoveredError
};
//usbh_core.h文件中
typedef struct _USBH_USR_PROP
{
void (*Init)(void); /* HostLibInitialized */
void (*DeInit)(void); /* HostLibInitialized */
void (*DeviceAttached)(void); /* DeviceAttached */
void (*ResetDevice)(void);
void (*DeviceDisconnected)(void);
void (*OverCurrentDetected)(void);
void (*DeviceSpeedDetected)(uint8_t DeviceSpeed); /* DeviceSpeed */
void (*DeviceDescAvailable)(void *); /* DeviceDescriptor is available */
void (*DeviceAddressAssigned)(void); /* Address is assigned to USB Device */
void (*ConfigurationDescAvailable)(USBH_CfgDesc_TypeDef *,
USBH_InterfaceDesc_TypeDef *,
USBH_EpDesc_TypeDef *);
/* Configuration Descriptor available */
void (*ManufacturerString)(void *); /* ManufacturerString*/
void (*ProductString)(void *); /* ProductString*/
void (*SerialNumString)(void *); /* SerialNubString*/
void (*EnumerationDone)(void); /* Enumeration finished */
USBH_USR_Status (*UserInput)(void);
int (*UserApplication) (void);
void (*DeviceNotSupported)(void); /* Device is not supported*/
void (*UnrecoveredError)(void);
}
继续搜索USR_Callbacks结构体,发现在主函数中,以指针形式传入USB初始化函数。
//main,c
int main(void)
{
u32 t;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2
delay_init(168); //初始化延时函数
uart_init(115200); //初始化串口波特率为115200
LED_Init(); //初始化LED
//定时器时钟84M,分频系数8400,所以84M/8400=10Khz(0.1ms)的计数频率
TIM3_Int_Init(TIMER_TIME*10-1,8400-1);
uart2_init(9600);
//DMA1,STEAM6,CH4,外设为串口2,存储器为SendBuff,长度为:SEND_BUF_SIZE.
MYDMA_Config(DMA1_Stream6,DMA_Channel_4,(u32)&USART2->DR,(u32)SendBuff,SEND_BUF_SIZE);
//初始化USB主机
USBH_Init(&USB_OTG_Core_dev,USB_OTG_FS_CORE_ID,&USB_Host,&HID_cb,&USR_Callbacks);
while(1)
{
USBH_Process(&USB_OTG_Core_dev, &USB_Host); //
if(bDeviceState==1)//连接建立了
{
if(USBH_Check_HIDCommDead(&USB_OTG_Core_dev,&HID_Machine))//检测USB HID通信,是否还正常?
{
USBH_HID_Reconnect();//重连
}
}else //连接未建立的时候,检测
{
if(USBH_Check_EnumeDead(&USB_Host)) //检测USB HOST 枚举是否死机了?死机了,则重新初始化
{
USBH_HID_Reconnect();//重连
}
}
t++;
if(t==200000)
{
LED0=!LED0;
t=0;
}
}
}
按经验应该是向USB驱动程序注册回调函数,而且调用的是DeviceNotSupported这个结构体成员名。
//usbh_core.c文件
void USBH_Init(USB_OTG_CORE_HANDLE *pdev,
USB_OTG_CORE_ID_TypeDef coreID,
USBH_HOST *phost,
USBH_Class_cb_TypeDef *class_cb,
USBH_Usr_cb_TypeDef *usr_cb)
{
/* Hardware Init */
USB_OTG_BSP_Init(pdev);
/* configure GPIO pin used for switching VBUS power */
USB_OTG_BSP_ConfigVBUS(0);
/* Host de-initializations */
USBH_DeInit(pdev, phost);
/*Register class and user callbacks */
phost->class_cb = class_cb;
phost->usr_cb = usr_cb;
/* Start the USB OTG core */
HCD_Init(pdev , coreID);
/* Upon Init call usr call back */
phost->usr_cb->Init();
/* Enable Interrupts */
USB_OTG_BSP_EnableInterrupt(pdev);
}
注册回调函数时,将USR_Callbacks指针赋值给phost->usr_cb,继续搜索->DeviceNotSupported,发现在。
//usb_hid_core.c
static USBH_Status USBH_HID_InterfaceInit ( USB_OTG_CORE_HANDLE *pdev,
void *phost)
{
uint8_t maxEP;
USBH_HOST *pphost = phost;
uint8_t num =0;
USBH_Status status = USBH_BUSY ;
HID_Machine.state = HID_ERROR;
if(pphost->device_prop.Itf_Desc[0].bInterfaceSubClass == HID_JOTSTICK)//方向盘的话修改为0
{
/*识别设备标识*/
if(pphost->device_prop.Itf_Desc[0].bInterfaceProtocol == HID_JOTSTICK)
{
HID_Machine.cb = &HID_MOUSE_cb;//将HID设备确定为鼠标设备,cb应该是call back
}
HID_Machine.state = HID_IDLE;
HID_Machine.ctl_state = HID_REQ_IDLE;
HID_Machine.ep_addr = pphost->device_prop.Ep_Desc[0][0].bEndpointAddress;
HID_Machine.length = pphost->device_prop.Ep_Desc[0][0].wMaxPacketSize;
HID_Machine.poll = pphost->device_prop.Ep_Desc[0][0].bInterval ;
if (HID_Machine.poll < HID_MIN_POLL)
{
HID_Machine.poll = HID_MIN_POLL;
}
/* Check fo available number of endpoints */
/* Find the number of EPs in the Interface Descriptor */
/* Choose the lower number in order not to overrun the buffer allocated */
maxEP = ( (pphost->device_prop.Itf_Desc[0].bNumEndpoints <= USBH_MAX_NUM_ENDPOINTS) ?
pphost->device_prop.Itf_Desc[0].bNumEndpoints :
USBH_MAX_NUM_ENDPOINTS);
/* Decode endpoint IN and OUT address from interface descriptor */
for (num=0; num < maxEP; num++)
{
if(pphost->device_prop.Ep_Desc[0][num].bEndpointAddress & 0x80)
{
HID_Machine.HIDIntInEp = (pphost->device_prop.Ep_Desc[0][num].bEndpointAddress);
HID_Machine.hc_num_in =\
USBH_Alloc_Channel(pdev,
pphost->device_prop.Ep_Desc[0][num].bEndpointAddress);
/* Open channel for IN endpoint */
USBH_Open_Channel (pdev,
HID_Machine.hc_num_in,
pphost->device_prop.address,
pphost->device_prop.speed,
EP_TYPE_INTR,
HID_Machine.length);
}
else
{
HID_Machine.HIDIntOutEp = (pphost->device_prop.Ep_Desc[0][num].bEndpointAddress);
HID_Machine.hc_num_out =\
USBH_Alloc_Channel(pdev,
pphost->device_prop.Ep_Desc[0][num].bEndpointAddress);
/* Open channel for OUT endpoint */
USBH_Open_Channel (pdev,
HID_Machine.hc_num_out,
pphost->device_prop.address,
pphost->device_prop.speed,
EP_TYPE_INTR,
HID_Machine.length);
}
}
start_toggle =0;
status = USBH_OK;
}
else
{
pphost->usr_cb->DeviceNotSupported();
}
return status;
}
(以上代码我已改过)由此推断是因为设备号不同导致大的无法识别,修改最开始两个if中的编号后,便可以识别Joystick设备。
返回的数据长度
此时仍不能读取到数据,从获取获取鼠标数据的路线从后向前反推,逆向找数据来源。
//usb_hid_mouce.c
static void MOUSE_Decode(uint8_t *data)
{ //修改过了
USR_MOUSE_ProcessData(data);
}
搜索MOUSE_Decode(),发现HID_MOUSE_cb是鼠标设备的回调函数结构体,其中包含MOUSE_Init()和MOUSE_Decode()。HID设备初始化函数USBH_HID_InterfaceInit()中,将HID设备绑定为为鼠标设备,将HID_MOUSE_cb指针,赋值给HID_Machine.cb。查找发现在USBH_HID_Handle(),有调用MOUSE_Decode()。
//usb_hid_core.c
static USBH_Status USBH_HID_Handle(USB_OTG_CORE_HANDLE *pdev ,
void *phost)
{
USBH_HOST *pphost = phost;
USBH_Status status = USBH_OK;
switch (HID_Machine.state)
{
case HID_IDLE:
HID_Machine.cb->Init();
HID_Machine.state = HID_SYNC;
case HID_SYNC:
/* Sync with start of Even Frame */
if(USB_OTG_IsEvenFrame(pdev) == TRUE)
{
HID_Machine.state = HID_GET_DATA;
}
break;
case HID_GET_DATA:
USBH_InterruptReceiveData(pdev,
HID_Machine.buff,
HID_Machine.length,
HID_Machine.hc_num_in);
start_toggle = 1;
HID_Machine.state = HID_POLL;
HID_Machine.timer = HCD_GetCurrentFrame(pdev);
break;
case HID_POLL:
if(( HCD_GetCurrentFrame(pdev) - HID_Machine.timer) >= HID_Machine.poll)
{
//printf("goto USBH_HID_Handle() 5.1\r\n");
HID_Machine.state = HID_GET_DATA;
}
else if(HCD_GetURB_State(pdev , HID_Machine.hc_num_in) == URB_DONE)
{
//printf("goto USBH_HID_Handle() 5.2\r\n");
if(start_toggle == 1) /* handle data once */
{
//printf("goto USBH_HID_Handle() 5.3\r\n");
start_toggle = 0;
HID_Machine.cb->Decode(HID_Machine.buff);
}
}
else if(HCD_GetURB_State(pdev, HID_Machine.hc_num_in) == URB_STALL) /* IN Endpoint Stalled */
{
//printf("goto USBH_HID_Handle() 5.4\r\n");
/* Issue Clear Feature on interrupt IN endpoint */
if( (USBH_ClrFeature(pdev,
pphost,
HID_Machine.ep_addr,
HID_Machine.hc_num_in)) == USBH_OK)
{
//printf("goto USBH_HID_Handle() 5.5\r\n");
/* Change state to issue next IN token */
HID_Machine.state = HID_GET_DATA;
}
}
break;
default:
break;
}
return status;
}
USBH_HID_Handle()中HID_Machine.state在3和5之间跳,在status是5时,执行数据处理函数。MOUSE_Decode()中需要用到HID_Machine.length,匹配收到数据的长度,返回的数据长度是8字节,直接取消长度限制,最终获得目标数据,返回的是64个8位数。
附:几种即使模拟器各个数据位的含义
- 安路迪
数据位 | 含义 |
---|---|
Data[0] | 方向盘低八位 |
Data[1] | 方向盘高八位 |
Data[2] | 随着油门增大128->0;刹车踩下128->255 |
Data[3] | 离合踩下128->255 |
Data[4] | 右转向灯、左转向灯、鸣笛、空、空、手刹、右按键、下按键 |
Data[5] | 上、雨刷二、雨刷一、近光灯、空、远光晃、近光、钥匙 |
Data[6] | 空、倒挡、五档、四档、三档、二档、一档、左按键 |
Data[7] | 校验 |
- 罗技