- /**
- * [url=home.php?mod=space&uid=247401]@brief[/url] Configuration descriptor
- */
- uint8_t g_usbConfigDescriptor[USB_CONFIG_DESCRIPTOR_SIZE] =
- {
- /* bLength */
- 0x09,
- /* bDescriptorType */
- USBD_DESC_CONFIGURATION,
- /* wTotalLength */
- USB_CONFIG_DESCRIPTOR_SIZE & 0XFF, USB_CONFIG_DESCRIPTOR_SIZE >> 8,
- /* bNumInterfaces */
- 0X01,
- /* bConfigurationValue */
- 0X01,
- /* iConfiguration */
- 0X00,
- /* bmAttributes */
- 0XE0,
- /* MaxPower */
- 0X32,
- /* bLength */
- 0X09,
- /* bDescriptorType */
- USBD_DESC_INTERFACE,
- /* bInterfaceNumber */
- 0X00,
- /* bAlternateSetting */
- 0X00,
- /* bNumEndpoints */
- 0X01,
- /* bInterfaceClass */
- 0X03,
- /* bInterfaceSubClass */
- 0X01,
- /* bInterfaceProtocol */
- 0X02,
- /* iInterface */
- 0X00,
- /* bLength */
- 0X09,
- /* Functional Descriptor */
- 0x21,
- /* bcdHID */
- 0X00, 0X01,
- /* bCountryCode */
- 0X00,
- /* bNumDescriptors */
- 0X01,
- /* bDescriptorType */
- 0X22,
- /* wItemLength */
- 120 & 0xFF, 120 >> 8,
- /* bLength */
- 0X07,
- /* bDescriptorType */
- USBD_DESC_ENDPOINT,
- /* bEndpointAddress */
- HID_IN_EP,
- /* bmAttributes */
- 0X03,
- /* wMaxPacketSize */
- 0X40, 0X00,
- /* bInterval */
- 0X05
- };
- /**
- * [url=home.php?mod=space&uid=247401]@brief[/url] HID report descriptor
- */
- uint8_t g_hidMouseReportDescriptor[120] =
- {
- 0x05, 0x01, // USAGE_PAGE (Generic Desktop)
- 0x09, 0x05, // USAGE (Game Pad)
- 0xa1, 0x01, // COLLECTION (Application)
- 0xa1, 0x00, // COLLECTION (Physical)
- 0x09, 0x30, // USAGE (X)
- 0x09, 0x31, // USAGE (Y)
- 0x15, 0x00, // LOGICAL_MINIMUM (0)
- 0x26, 0xff, 0x00, // LOGICAL_MAXIMUM (255)
- 0x35, 0x00, // PHYSICAL_MINIMUM (0)
- 0x46, 0xff, 0x00, // PHYSICAL_MAXIMUM (255)
- 0x95, 0x02, // REPORT_COUNT (2)
- 0x75, 0x08, // REPORT_SIZE (8)
- 0x81, 0x02, // INPUT (Data,Var,Abs)
- 0xc0, // END_COLLECTION
- 0xa1, 0x00, // COLLECTION (Physical)
- 0x09, 0x33, // USAGE (Rx)
- 0x09, 0x34, // USAGE (Ry)
- 0x15, 0x00, // LOGICAL_MINIMUM (0)
- 0x26, 0xff, 0x00, // LOGICAL_MAXIMUM (255)
- 0x35, 0x00, // PHYSICAL_MINIMUM (0)
- 0x46, 0xff, 0x00, // PHYSICAL_MAXIMUM (255)
- 0x95, 0x02, // REPORT_COUNT (2)
- 0x75, 0x08, // REPORT_SIZE (8)
- 0x81, 0x02, // INPUT (Data,Var,Abs)
- 0xc0, // END_COLLECTION
- 0xa1, 0x00, // COLLECTION (Physical)
- 0x09, 0x32, // USAGE (Z)
- 0x15, 0x00, // LOGICAL_MINIMUM (0)
- 0x26, 0xff, 0x00, // LOGICAL_MAXIMUM (255)
- 0x35, 0x00, // PHYSICAL_MINIMUM (0)
- 0x46, 0xff, 0x00, // PHYSICAL_MAXIMUM (255)
- 0x95, 0x01, // REPORT_COUNT (1)
- 0x75, 0x08, // REPORT_SIZE (8)
- 0x81, 0x02, // INPUT (Data,Var,Abs)
- 0xc0, // END_COLLECTION
- 0x05, 0x09, // USAGE_PAGE (Button)
- 0x19, 0x01, // USAGE_MINIMUM (Button 1)
- 0x29, 0x0a, // USAGE_MAXIMUM (Button 10)
- 0x95, 0x0a, // REPORT_COUNT (10)
- 0x75, 0x01, // REPORT_SIZE (1)
- 0x81, 0x02, // INPUT (Data,Var,Abs)
- 0x05, 0x01, // USAGE_PAGE (Generic Desktop)
- 0x09, 0x39, // USAGE (Hat switch)
- 0x15, 0x01, // LOGICAL_MINIMUM (1)
- 0x25, 0x08, // LOGICAL_MAXIMUM (8)
- 0x35, 0x00, // PHYSICAL_MINIMUM (0)
- 0x46, 0x3b, 0x10, // PHYSICAL_MAXIMUM (4155)
- 0x66, 0x0e, 0x00, // UNIT (None)
- 0x75, 0x04, // REPORT_SIZE (4)
- 0x95, 0x01, // REPORT_COUNT (1)
- 0x81, 0x42, // INPUT (Data,Var,Abs,Null)
- 0x75, 0x02, // REPORT_SIZE (2)
- 0x95, 0x01, // REPORT_COUNT (1)
- 0x81, 0x03, // INPUT (Cnst,Var,Abs)
- 0x75, 0x08, // REPORT_SIZE (8)
- 0x95, 0x02, // REPORT_COUNT (2)
- 0x81, 0x03, // INPUT (Cnst,Var,Abs)
- /* USER CODE END 0 */
- 0xC0 /* END_COLLECTION */
- };
我们定义的是9个字节的报文:在标准请求stdReqCallback的setConfiguration Callback Fuction里面需要修改一下,将最后一个参数改为9如下代码:- /*!
- * [url=home.php?mod=space&uid=247401]@brief[/url] Standard request set configuration call back
- *
- * @param None
- *
- * @retval None
- */
- static void USBD_HID_SetConfigCallBack(void)
- {
- USBD_OpenInEP(HID_IN_EP & 0x7F, USB_EP_TYPE_INTERRUPT, 9);
- }
2、ADC DMA驱动:我们XYZ轴和Rx(X旋转)、Ry(Y旋转)的模拟输入采样ADC之DMA模式,关于ADC DMA的配置,我主要是参考了APM32F103的,因为都是M3内核,都是Geehy的,所以代码比较通用,这里参考了这篇帖子
https://bbs.21ic.com/icview-3217292-1-1.html
在此表示感谢!最后代码如下:
- #define ADC1_DR_Address ((uint32_t)0x40012400+0x4c) /* ADC1数据寄存器地址(ADC基地址+偏移) */
- uint16_t dma_buffer[5] = {0}; /* 存储DMA传输ADC数据的buffer */
- //uint8_t dma_data_done_flag = 0; /* DMA传输完成中断标志 */
- //uint8_t adc_int_eoc_flag = 0; /* ADC转换完成中断标志 */
- void RCM_Configuration(void)
- {
- RCM_ConfigADCCLK(RCM_PCLK2_DIV_6); /* 6分频 72/6=12MHZ ADCCLK不能超过14MHZ*/
-
- RCM_EnableAPB2PeriphClock( RCM_APB2_PERIPH_GPIOC); /* 使能GPIO时钟 */
- RCM_EnableAHBPeriphClock(RCM_AHB_PERIPH_DMA1); /* 使能DMA1时钟 */
- RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_ADC1); /* 使能ADC1时钟 */
- }
- void ADC_GPIO_Init(void)
- {
- GPIO_Config_T GPIO_ConfigStruct;
- /* ADC_GPIO初始化 */
- GPIO_ConfigStruct.pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3|GPIO_PIN_4; /* 选择端口 分别对应ADC通道10 11 12 13 14*/
- GPIO_ConfigStruct.mode = GPIO_MODE_ANALOG; /* IO工作方式 模拟输入*/
- GPIO_Config(GPIOC, &GPIO_ConfigStruct);
- }
- void ADC_Init(void)
- {
- ADC_Config_T ADC_configStruct;
-
- ADC_Reset(ADC1); /* 复位ADC1 */
- /** ADC1 Configuration */
- ADC_configStruct.mode = ADC_MODE_INDEPENDENT; /* ADC1工作在独立模式 */
- ADC_configStruct.scanConvMode = ENABLE; /* 使能扫描 */
- ADC_configStruct.continuosConvMode = ENABLE; /* 使能ADC连续转换模式 轮询方式使用*/
- // ADC_configStruct.continuosConvMode = DISABLE; /* 不使能ADC连续转换模式 中断方式使用*/
- ADC_configStruct.externalTrigConv = ADC_EXT_TRIG_CONV_None; /* 软件控制转换 */
- ADC_configStruct.dataAlign = ADC_DATA_ALIGN_RIGHT; /* 转换数据右对齐 */
- ADC_configStruct.nbrOfChannel = 5; /* 顺序进行规则转换的ADC通道的数目 */
- ADC_Config(ADC1, &ADC_configStruct); /* 初始化ADC1寄存器 */
- /* 设置指定ADC的规则组通道,设置它们的转化顺序和采样时间 */
- ADC_ConfigRegularChannel(ADC1, ADC_CHANNEL_10, 1, ADC_SAMPLETIME_239CYCLES5); /* ADC1选择通道10 采样顺序1 采样时间13.5个周期 */
- ADC_ConfigRegularChannel(ADC1, ADC_CHANNEL_11, 2, ADC_SAMPLETIME_239CYCLES5); /* ADC1选择通道11 采样顺序2 采样时间13.5个周期 */
- ADC_ConfigRegularChannel(ADC1, ADC_CHANNEL_12, 3, ADC_SAMPLETIME_239CYCLES5); /* ADC1选择通道12 采样顺序3 采样时间13.5个周期 */
- ADC_ConfigRegularChannel(ADC1, ADC_CHANNEL_13, 4, ADC_SAMPLETIME_239CYCLES5); /* ADC1选择通道13 采样顺序4 采样时间13.5个周期 */
- ADC_ConfigRegularChannel(ADC1, ADC_CHANNEL_14, 5, ADC_SAMPLETIME_239CYCLES5); /* ADC1选择通道14 采样顺序5 采样时间13.5个周期 */
- // ADC_EnableInterrupt(ADC1, ADC_INT_EOC); /* 使能ADC转换完成中断 */
- ADC_EnableDMA(ADC1); /* 使能ADC的DMA支持 */
- ADC_Enable(ADC1); /* 使能ADC1 */
- ADC_ResetCalibration(ADC1); /* 复位ADC1的校准寄存器 */
- while(ADC_ReadResetCalibrationStatus(ADC1)); /* 等待ADC1复位校准完成 */
- ADC_StartCalibration(ADC1); /* 开始ADC1校准 */
- while(ADC_ReadCalibrationStartFlag(ADC1)); /* 等待ADC1校准完成 */
- ADC_EnableSoftwareStartConv(ADC1); /* 启动ADC1转换 */
- }
- void DMA_Init(void)
- {
- DMA_Config_T DMA_ConfigStruct;
- DMA_Reset(DMA1_Channel1); /* 复位DMA1通道1 */
- DMA_ConfigStruct.peripheralBaseAddr = ADC1_DR_Address; /* DMA通道外设基地址 */
- DMA_ConfigStruct.memoryBaseAddr = (uint32_t)dma_buffer; /* DMA通道ADC数据存储器 */
- DMA_ConfigStruct.dir = DMA_DIR_PERIPHERAL_SRC; /* 指定外设为源地址 */
- DMA_ConfigStruct.bufferSize = 5; /* DMA缓冲区大小(根据ADC采集通道数量修改) */
- DMA_ConfigStruct.peripheralInc = DMA_PERIPHERAL_INC_DISABLE; /* 当前外设寄存器地址不变(即不自增) */
- DMA_ConfigStruct.memoryInc = DMA_MEMORY_INC_ENABLE; /* 当前存储器地址:Disable不变,Enable递增(用于多通道采集) */
- DMA_ConfigStruct.peripheralDataSize = DMA_PERIPHERAL_DATA_SIZE_HALFWORD; /* 外设数据宽度16位 */
- DMA_ConfigStruct.memoryDataSize = DMA_MEMORY_DATA_SIZE_HALFWORD; /* 存储器数据宽度16位 */
- DMA_ConfigStruct.loopMode = DMA_MODE_CIRCULAR; /* DMA通道操作模式位环形缓冲模式 */
- DMA_ConfigStruct.priority = DMA_PRIORITY_HIGH; /* DMA通道优先级高 */
- DMA_ConfigStruct.M2M = DMA_M2MEN_DISABLE; /* 禁止DMA通道存储器到存储器传输 */
- DMA_Config(DMA1_Channel1, &DMA_ConfigStruct);
- //DMA_EnableInterrupt(DMA1_Channel1, DMA_INT_TC);
- DMA_Enable(DMA1_Channel1);
- }
- void MyADC_DMA_Init(void)
- {
- RCM_Configuration();
- NVIC_EnableIRQRequest(ADC1_2_IRQn, 0, 0);
- ADC_GPIO_Init();
- DMA_Init();
- ADC_Init();
-
- }
- /* Re-maps a number from one range to another
- *
- */
- int32_t map(int32_t x, int32_t in_min, int32_t in_max, int32_t out_min, int32_t out_max){
- return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
- }
- u8 X=128,Y=128,Z=128,Rx=128,Ry=128;
- void MyADC_Handle(void)
- {
- while(!ADC_ReadStatusFlag(ADC1, ADC_FLAG_EOC))
- {
- X=map(dma_buffer[0],0,4095,0,255);
- Y=map(dma_buffer[1],0,4095,0,255);
- Z=map(dma_buffer[2],0,4095,0,255);
- Rx=map(dma_buffer[3],0,4095,0,255);
- Ry=map(dma_buffer[4],0,4095,0,255);
- }
-
- //
- // printf("ADC1采样数据:\r\n");
- // for (uint8_t i = 0; i < 5; i++) {
- // printf("ADC_CHANNEL_%d:%d\r\n", 10+i, dma_buffer[i]);
- // }
- // printf("\r\n");
- }
- void GetAnalogValue(u8* x,u8* y,u8* z,u8* rx,u8* ry)
- {
- x[0]=X;
- y[0]=Y;
- z[0]=Z;
- rx[0]=Rx;
- ry[0]=Ry;
- }
3、开源软件的移植:
我们的单片机的系统架构移植了一个MultiTimer,这就是多个软件定时器,然后在软件定时器回调函数里面处理我们的
ADC DMA采集、按键扫描、以及Usb HID数据的处理,另外一个是MultiButton,这两个开源软件都是同一作者,并且都是在GitHub上
开源了:可以通过这两个链接获取:
https://github.com/0x1abin/MultiButton
https://github.com/0x1abin/MultiTimer
移植MultiTimer需要配置一个时钟滴答,这里和大多数RTOS一样选择Systick,最后移植代码如下:
- #include <stdio.h>
- MultiTimer timer1;
- MultiTimer timer2;
- MultiTimer timer3;
- volatile uint32_t tick = 0;
- uint64_t PlatformTicksGetFunc(void)
- {
- return (uint64_t)tick;
- }
- void ButtonTimerCallback(MultiTimer* timer, void *userData)
- {
- button_ticks();
- MultiTimerStart(timer, 5, ButtonTimerCallback, userData);
- }
- void ADCTimerCallback(MultiTimer* timer, void *userData)
- {
- MyADC_Handle();
- MultiTimerStart(timer, 5, ADCTimerCallback, userData);
- }
- void ReportTimerCallback(MultiTimer* timer, void *userData)
- {
- USBD_HID_Proc();
- MultiTimerStart(timer, 10, ReportTimerCallback, userData);
- }
- void MyBSP_Init(void)
- {
- MyADC_DMA_Init();
- MyButton_Init();
- SysTick_Config(SystemCoreClock / 1000);
- MultiTimerInstall(PlatformTicksGetFunc);
- MultiTimerStart(&timer1, 5, ButtonTimerCallback, NULL);
- MultiTimerStart(&timer2, 5, ADCTimerCallback, NULL);
- MultiTimerStart(&timer3, 10, ReportTimerCallback, NULL);
- }
还有就是MultiButton,这是一个事件型驱动的软件模块,而它同样需要一个5ms的按键扫描,这里直接使用MultiTimer中的Timer1的回调函数ButtonTimerCallback,如上代码,然后关于怎样使用MultiButton,,由于篇幅原因,这里不看代码了,因为作者的使用说明写的非常详细,这里就看一下我们在按键动作的回调函数中做了什么动作:
- void BTN1_PRESS_DOWN_Handler(void* btn)
- {
- button|=BIT0;
- printf("BTN1_PRESS_DOWN_Handler\r\n");
- }
- void BTN1_PRESS_UP_Handler(void* btn)
- {
- button&=(~BIT0);
- printf("BTN1_PRESS_UP_Handler\r\n");
- }
如上代码,我们只是在按下和弹起的时候对按键置位了,这真是对应报表描述符中对应的button1~10。不过我们还定义了PE7~PE10也作为按键(视觉头盔)输入,我们一起扫描了,所以这里定义button是uint16类型的,这里用了14bit表示这些按键的扫描动作。
4、USB HID的数据解析:
timer3回调函数中处理的正是ADC数据、按键数据转成XYZ轴和Rx(X旋转)、Ry(Y旋转),以及带有Button1~Button10共10颗按钮以及视觉头盔的过程,以及如何将数据上报给主机的,我们直接看USBD_HID_Proc:
- void USBD_HID_Proc(void)
- {
- uint16_t button=0;
- uint16_t Hat=0;
-
- uint8_t Buf[9]={128,128,128,128,128,0,0,0,0};
- static uint8_t lastBuf[9]={0,0,0,0,0,0,0,0};
- /* Check the usb device configured state */
- if(g_usbDev.devState != USBD_DEVICE_STATE_CONFIGURED)
- {
- return;
- }
- GetAnalogValue(&Buf[0],&Buf[1],&Buf[2],&Buf[3],&Buf[4]);
-
- button = GetButtonValue();
-
- Buf[5]=button&0xFF;
- Buf[6]|=((button>>8)&0x03);
-
- //Hat
- Hat=(button>>10)&0x0F;
- if((Hat==0x01)||(Hat==0x0D))
- {
- Buf[6]|=HATSW1;
- }else if(Hat==0x09)
- {
- Buf[6]|=HATSW2;
- }else if((Hat==0x08)||(Hat==0x0B))
- {
- Buf[6]|=HATSW3;
- }else if(Hat==0x0A)
- {
- Buf[6]|=HATSW4;
- }else if((Hat==0x02)||(Hat==0x0E))
- {
- Buf[6]|=HATSW5;
- }else if(Hat==0x06)
- {
- Buf[6]|=HATSW6;
- }else if((Hat==0x04)||(Hat==0x07))
- {
- Buf[6]|=HATSW7;
- }else if(Hat==0x05)
- {
- Buf[6]|=HATSW8;
- }else{
- Buf[6]&=(~0x3C);
- }
-
-
-
- if(memcmp(Buf,lastBuf,9)!=0)
- {
- if(s_statusEP)
- {
- s_statusEP = 0;
- USBD_TxData(USB_EP_1, Buf, 9);
- }
- }
- memcpy(lastBuf,Buf,9);
-
- }
三、实现的效果:
效果就是第一张图,
不过我们有录制视频了,接下来一起看一下最后DIY的效果