stm32读取驾驶模拟器数据 stm32F407读取joystick数据

需求

实习工作,老板要求用单片机读取驾驶模拟器(Joystick)返回的数据,驾驶模拟器usb输出,输出信息包括:方向盘转角、左右拨杆、按键等。
模拟器的样子

完整代码传送门

https://download.csdn.net/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位数。

附:几种即使模拟器各个数据位的含义

  1. 安路迪
数据位含义
Data[0]方向盘低八位
Data[1]方向盘高八位
Data[2]随着油门增大128->0;刹车踩下128->255
Data[3]离合踩下128->255
Data[4]右转向灯、左转向灯、鸣笛、空、空、手刹、右按键、下按键
Data[5]上、雨刷二、雨刷一、近光灯、空、远光晃、近光、钥匙
Data[6]空、倒挡、五档、四档、三档、二档、一档、左按键
Data[7]校验
  1. 罗技
STM32F103C8 (Source Code) Serial(UART) to USB HID Keyboard Mouse Joystick 串口 转 USB键盘;鼠标;手柄 源码 (1) 使用Composite Device 组合(复合)设备 (1.1) 1个Device -> 1个 Configuation -> 3个Interfance (Keyboard & Mouse & Joystick) (1.2) Keyboard Interfance -> HID (boot mode) -> 2个Endpoint(IN_0x81 & OUT_0x01) -> KeyboardReportDescriptor(不使用Report ID) (1.3) Mouse Interfance -> HID (boot mode) -> 1个Endpoint(IN_0x82) -> MouseReportDescriptor(不使用Report ID) (1.4) Joystick Interfance -> HID -> 1个Endpoint(IN_0x83) -> JoyStickReportDescriptor (1.5) 使用HID boot模式, 不使用Report ID, 以便兼容在 计算器设定BIOS模式 中的操作 (1.6) 支持反馈Keyboard_LED灯号: All Off; Num Lock; Caps Lock; Scroll Lock; Compose; Kana (2) 串口接收 命令 (2.1) UART协议: 115200, n, 8, 1 (2.2) 1帧发送字符串格式, 以 '{'开始, '}'结束 ','分隔 共9个10进制数字 例如: {1,2,3,4,5,6,7,8,9} (2.3) 第1位 区分 Keyboard(128) 或是 Mouse(64) 或是 Joystick(32) 命令 例如: {32, 0,0,0,0,0,0,0,0} --- 发送Joystick命令 {64, 0,0,0,0,0,0,0,0} --- 发送Mouse命令 {128,0,0,0,0,0,0,0,0} --- 发送Keyboard命令 (3) 发送Keyboard键盘命令时 : 第2~9位 分别如下 (3.1) 第2位 : Modify_Key(修饰键) Key_Release = 0x00, Left_Control = 0x01, Left_Shift = 0x02, Left_Alt = 0x04, Left_GUI = 0x08, Right_Control = 0x10, Right_Shift = 0x20, Right_Alt = 0x40, Right_GUI = 0x80, 例如: {128, 8,0,0,0,0,0,0,0} --- 发送 Win_Key键 {128, 128,0,0,0,0,0,0,0} --- 发送 WinApp_Key键 {128, 32,0,0,0,0,0,0,0} --- 发送 右Shift键 (3.2) 第3位 : 保留,不使用,一律填0 (3.3) 第4~9位 : 可以同时发送6个Keyboard按键 例如: {128, 0,0,4,5,6,7,8,9} --- 发送 'abcdef'键 {128, 2,0,4,5,6,7,8,9} --- 按住 左Shift 发送 'abcdef'键 => 'ABCDEF' {128, 0,0,0,5,0,7,0,9} --- 发送 'bdf'键 (0表示 无按键) 按键码 可参阅: (HID Usage ID) http://download.microsoft.com/download/1/6/1/161ba512-40e2-4cc9-843a-923143f3456c/translate.pdf https://www.hiemalis.org/~keiji/PC/scancode-translate.pdf https://gist.github.com/MightyPork/6da26e382a7ad91b5496ee55fdc73db2 http://www.usb.org/developers/hidpage/Hut1_12v2.pdf (4) 发送Mouse鼠标命令时 : 第8~9位 分别如下 (4.1) 第2位 : 鼠标按钮(左,中,右)占3bits Button_Release = 0x00, Left_Button = 0x01, Right_Button = 0x02, Mid_Button = 0x04, 例如: {64, 1,0,0,0,0,0,0,0} --- 点击 左键 {64, 2,0,0,0,0,0,0,0} --- 点击 右键 {64, 4,0,0,0,0,0,0,0} --- 点击 中键 (4.2) 第3~5位 : 移动(X,Y), 滚轮(Wheel) X: -127~127:左右移动鼠标 Y: -127~127:上下移动鼠标 Wheel: -127~127:上下转动滚轮 例如: {64, 0,20,-10,0,0,0,0,0} --- 鼠标 右移20,上移10 {64, 0,0,0,-30,0,0,0,0} --- 滚轮-30 (4.3) 第6~9位 : 保留,不使用,一律填0 (5) 发送Joystick手柄命令时 : 第8~9位 分别如下 (5.1) 第2~4位 : 移动X,Y,Z X: -127~127:X轴左右移动手柄 Y: -127~127:Y轴上下移动手柄 Z: -127~127:Z轴转动手柄 例如: {32, -127,0,0,0,0,0,0,0} --- 移动手柄X轴到-127(最右边) {32, 0,127,0,0,0,0,0,0} --- 移动手柄Y轴到127(最下面) {32, -95,32,96,0,0,0,0,0} --- 移动手柄X,Y,Z轴到(-95,32,96) (5.2) 第5~7位 : 旋转X,Y,Z X: -127~127:X轴旋转 Y: -127~127:Y轴旋转 Z: -127~127:Z轴旋转 例如: {32, 0,0,0, 63,0,0,0,0} --- 旋转手柄X轴到63 {32, 0,0,0, 0,-32,0,0,0} --- 旋转手柄Y轴到-32 {32, 0,0,0, 0,0,127,0,0} --- 旋转手柄Z轴到127 (5.3) 第8位 : 2个 Hat_switch(方向帽) POV1, POV2 POV1_0 = 0x00, POV1_45 = 0x01, POV1_90 = 0x02, POV1_135 = 0x03, POV1_180 = 0x04, POV1_225 = 0x05, POV1_270 = 0x06, POV1_315 = 0x07, POV2_0 = 0x00, POV2_45 = 0x10, POV2_90 = 0x20, POV2_135 = 0x30, POV2_180 = 0x40, POV2_225 = 0x50, POV2_270 = 0x60, POV2_315 = 0x70, 例如: {32, 0,0,0, 0,0,0, 3,0} --- POV1到90度 {32, 0,0,0, 0,0,0, 103,0} --- POV1到135度(0x07);POV2到270度(0x60) 即(0x07+0x60=0x67=103) (5.4) 第9位 : 8个按钮(每个按钮占1bit) 例如: {32, 0,0,0, 0,0,0, 0,85} --- 按钮: (0x55=85) 1,3,5,7:ON; 2,4,6,8:OFF {32, 0,0,0, 0,0,0, 0,170} --- 按钮: (0xAA=170) 1,3,5,7:OFF; 2,4,6,8:ON
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

清欢_小铭

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值