BLE-VStick入门手册
BLE-VStick入门手册
第一部分、硬件概述
1.1 BLE-VStick实物图
如下图,是BLE-Vstick实物图;分核心板(顶板)和底板;
核心板有两个晶振提供系统时钟源,由于TMOS的心跳是基于RTC时钟为基准的,也就是32.768KHz的晶振提供时钟源,所以不能用手去摸晶振引脚,否则会导致蓝牙断开和其他不可预期的异常;CH582M内部带有DC-DC模块,一般在低功耗时候开启的话可以省电;天线部分默认使用陶瓷天线,如果天线增益不够,可以修改0R电感到IPX座子上即可接外置天线;
底板板载了一个摇杆电位器,8个轻触开关,2个USB口,LDO和充电模块;
1.2 BLE-VStick原理图
BLE-VStick原理图下图所示,如看不清可打开Hardware目录下Sch的PDF文档查阅
1.2.1核心板
1.2.2底板
第二部分、软件工具
2.1 软件概述
在 /Software 目录下是常用的工具软件:
1. MounRiver: 编译器;
2. WCHISPTool:ISP在系统编程工具
3. 设备测试网站:https://devicetests.com/
2.2 MounRiver软件入门
大家访问以下链接:http://mounriver.com/help
2.3 工程树
-
完成工程创建后出现下图:
其中:
- Includes: 包含的头文件;
- __APP:__应用程序,存放main函数和用户编写的应用程序;
- __HAL:__例程共用的硬件相关文件
- LIB: BLE协议栈库文件及其头文件
- LD: link链接文件
- __Profile:__蓝牙服务配置文件
- __RVMSIS: __RISC-V内核系统
- __Startup: __CH58x系列启动文件
- __StdPeriphDriver: __基本外设驱动源文件及头文件
- __obj: __hex,map,makefile文件
第三部分、预备知识:
3.1 沁恒低功耗蓝牙软件开发参考手册
开发之前,我们先入门学习一下《沁恒低功耗蓝牙软件开发参考手册》,在doc中
我们在B站有视频教程:https://www.bilibili.com/video/BV1wT411J7wg/?vd_source=2bbde87de845d5220b1d8ba075c12fb0
3.2 Bluetooth_LE_Primer_Paper
《Bluetooth_LE_Primer_Paper》,蓝牙低功耗BLE协议栈入门手册,在doc中
我们在B站有视频教程:
Bluetooth_LE_Primer_Paper https://www.bilibili.com/video/BV17B4y1s7bQ/?vd_source=2bbde87de845d5220b1d8ba075c12fb0
3.3 QingKeV4_Processor_Manua
《QingKeV4_Processor_Manual》,QingKeV4 微处理器手册,在doc中
3.4 16-bit UUID Numbers Document
《16-bit UUID Numbers Document》在doc中
3.5 16-bit UUID Numbers Document
《HIDS_SPEC_V10》在doc/Bluetooth SIG中
3.6 资料的获取与技术支持
-
官方技术支持;
-
技术支持邮箱:1101495766@qq.com
-
请直接在B站评论区@up
第四部分、蓝牙HID实战训练
4.1 实例BLE HID Mouse
第一个实例我们将实现蓝牙鼠标功能,即XY坐标轴+左中右按键+滚轮的微软鼠标;
4.1.1 硬件外设
如下图是板载Joystick,摇杆电位器分别接到PA4和PA5,另有一个按键接到PA6;
再有8个轻触按键接到了PB0~PB7;
4.1.2 软件设计
我们先打开/Code/BLE_VStick/BLE/HID_Mouse/HID_Mouse.wvproj 工程,展开Starup开到startup_CH583.S
.word 0
.word 0
.word NMI_Handler /* NMI Handler */
.word HardFault_Handler /* Hard Fault Handler */
.word 0xF5F9BDA9
.word Ecall_M_Mode_Handler /* 5 */
.word 0
.word 0
.word Ecall_U_Mode_Handler /* 8 */
.word Break_Point_Handler /* 9 */
.word 0
.word 0
.word SysTick_Handler /* SysTick Handler */
.word 0
.word SW_Handler /* SW Handler */
.word 0
/* External Interrupts */
.word TMR0_IRQHandler /* 0: TMR0 */
.word GPIOA_IRQHandler /* GPIOA */
.word GPIOB_IRQHandler /* GPIOB */
.word SPI0_IRQHandler /* SPI0 */
.word BB_IRQHandler /* BLEB */
.word LLE_IRQHandler /* BLEL */
.word USB_IRQHandler /* USB */
.word USB2_IRQHandler /* USB2 */
.word TMR1_IRQHandler /* TMR1 */
.word TMR2_IRQHandler /* TMR2 */
.word UART0_IRQHandler /* UART0 */
.word UART1_IRQHandler /* UART1 */
.word RTC_IRQHandler /* RTC */
.word ADC_IRQHandler /* ADC */
.word I2C_IRQHandler /* I2C */
.word PWMX_IRQHandler /* PWMX */
.word TMR3_IRQHandler /* TMR3 */
.word UART2_IRQHandler /* UART2 */
.word UART3_IRQHandler /* UART3 */
.word WDOG_BAT_IRQHandler /* WDOG_BAT */
以上中断请求对应《QingKeV4_Processor_Manual.PDF》第33章PFIC与中断控制的表 3-1 异常和中断向量表
接着,我们定位HidEmu_Init->Hid_AddService->hidAttrTbl
static gattAttribute_t hidAttrTbl[] = {
// HID Service
{
{ATT_BT_UUID_SIZE, primaryServiceUUID}, /* type */
GATT_PERMIT_READ, /* permissions */
0, /* handle */
(uint8_t *)&hidService /* pValue */
},
// Included service (battery)
{
{ATT_BT_UUID_SIZE, includeUUID},
GATT_PERMIT_READ,
0,
(uint8_t *)&include},
// HID Information characteristic declaration
{
{ATT_BT_UUID_SIZE, characterUUID},
GATT_PERMIT_READ,
0,
&hidInfoProps},
// HID Information characteristic
{
{ATT_BT_UUID_SIZE, hidInfoUUID},
GATT_PERMIT_ENCRYPT_READ,
0,
(uint8_t *)hidInfo},
// HID Control Point characteristic declaration
{
{ATT_BT_UUID_SIZE, characterUUID},
GATT_PERMIT_READ,
0,
&hidControlPointProps},
// HID Control Point characteristic
{
{ATT_BT_UUID_SIZE, hidControlPointUUID},
GATT_PERMIT_ENCRYPT_WRITE,
0,
&hidControlPoint},
// HID Protocol Mode characteristic declaration
{
{ATT_BT_UUID_SIZE, characterUUID},
GATT_PERMIT_READ,
0,
&hidProtocolModeProps},
// HID Protocol Mode characteristic
{
{ATT_BT_UUID_SIZE, hidProtocolModeUUID},
GATT_PERMIT_ENCRYPT_READ | GATT_PERMIT_ENCRYPT_WRITE,
0,
&hidProtocolMode},
// HID Report Map characteristic declaration
{
{ATT_BT_UUID_SIZE, characterUUID},
GATT_PERMIT_READ,
0,
&hidReportMapProps},
// HID Report Map characteristic
{
{ATT_BT_UUID_SIZE, hidReportMapUUID},
GATT_PERMIT_ENCRYPT_READ,
0,
(uint8_t *)hidReportMap},
// HID External Report Reference Descriptor
{
{ATT_BT_UUID_SIZE, extReportRefUUID},
GATT_PERMIT_READ,
0,
hidExtReportRefDesc
},
// HID Report characteristic, mouse input declaration
{
{ATT_BT_UUID_SIZE, characterUUID},
GATT_PERMIT_READ,
0,
&hidReportMouseInProps},
// HID Report characteristic, mouse input
{
{ATT_BT_UUID_SIZE, hidReportUUID},
GATT_PERMIT_ENCRYPT_READ,
0,
&hidReportMouseIn},
// HID Report characteristic client characteristic configuration
{
{ATT_BT_UUID_SIZE, clientCharCfgUUID},
GATT_PERMIT_READ | GATT_PERMIT_ENCRYPT_WRITE,
0,
(uint8_t *)&hidReportMouseInClientCharCfg},
// HID Report Reference characteristic descriptor, mouse input
{
{ATT_BT_UUID_SIZE, reportRefUUID},
GATT_PERMIT_READ,
0,
hidReportRefMouseIn},
// Feature Report declaration
{
{ATT_BT_UUID_SIZE, characterUUID},
GATT_PERMIT_READ,
0,
&hidReportFeatureProps},
// Feature Report
{
{ATT_BT_UUID_SIZE, hidReportUUID},
GATT_PERMIT_ENCRYPT_READ | GATT_PERMIT_ENCRYPT_WRITE,
0,
&hidReportFeature},
// HID Report Reference characteristic descriptor, feature
{
{ATT_BT_UUID_SIZE, reportRefUUID},
GATT_PERMIT_READ,
0,
hidReportRefFeature},
};
打开gattAttribute_t可看到,
/**
* GATT Attribute Type format.
*/
typedef struct
{
uint8_t len; //!< Length of UUID (2 or 16)
const uint8_t *uuid; //!< Pointer to UUID
} gattAttrType_t;
/**
* GATT Attribute format.
*/
typedef struct attAttribute_t
{
gattAttrType_t type; //!< Attribute type (2 or 16 octet UUIDs)
uint8_t permissions; //!< Attribute permissions
uint16_t handle; //!< Attribute handle - assigned internally by attribute server
uint8_t *pValue; //!< Attribute value - encoding of the octet array is defined in
//!< the applicable profile. The maximum length of an attribute
//!< value shall be 512 octets.
} gattAttribute_t;
gattAttrType_t的len代表UUID的长度2 or 16,uuid可以参考《16-bit UUID Numbers Document》
Attribute permissions有如下的GATT Attribute Access Permissions Bit Fields
#define GATT_PERMIT_READ 0x01 //!< Attribute is Readable
#define GATT_PERMIT_WRITE 0x02 //!< Attribute is Writable
#define GATT_PERMIT_AUTHEN_READ 0x04 //!< Read requires Authentication
#define GATT_PERMIT_AUTHEN_WRITE 0x08 //!< Write requires Authentication
#define GATT_PERMIT_AUTHOR_READ 0x10 //!< Read requires Authorization
#define GATT_PERMIT_AUTHOR_WRITE 0x20 //!< Write requires Authorization
#define GATT_PERMIT_ENCRYPT_READ 0x40 //!< Read requires Encryption
#define GATT_PERMIT_ENCRYPT_WRITE 0x80 //!< Write requires Encryption
handle是assigned internally by attribute server,即句柄
pValue是表的值;
我们HID Report Map的characteristic,查阅《16-bit UUID Numbers Document》可知REPORT_MAP_UUID=0x2A4B,
#define REPORT_MAP_UUID 0x2A4B // Report Map
// HID Report Map characteristic declaration
{
{ATT_BT_UUID_SIZE, characterUUID},
GATT_PERMIT_READ,
0,
&hidReportMapProps},
// HID Report Map characteristic
{
{ATT_BT_UUID_SIZE, hidReportMapUUID},
GATT_PERMIT_ENCRYPT_READ,
0,
(uint8_t *)hidReportMap},
hidReportMap是设备的报告描述符,看参考USB的报告描述符,以下是鼠标的报告描述符;
// HID Report Map characteristic value
static const uint8_t hidReportMap[] = {
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
0x09, 0x02, // USAGE (Mouse)
0xa1, 0x01, // COLLECTION (Application)
0x09, 0x01, // USAGE (Pointer)
0xa1, 0x00, // COLLECTION (Physical)
0x05, 0x09, // USAGE_PAGE (Button)
0x19, 0x01, // USAGE_MINIMUM (Button 1)
0x29, 0x03, // USAGE_MAXIMUM (Button 3)
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)
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
0x09, 0x30, // USAGE (X)
0x09, 0x31, // USAGE (Y)
0x09, 0x38, // USAGE (Wheel)
0x15, 0x81, // LOGICAL_MINIMUM (-127)
0x25, 0x7f, // LOGICAL_MAXIMUM (127)
0x75, 0x08, // REPORT_SIZE (8)
0x95, 0x03, // REPORT_COUNT (3)
0x81, 0x06, // INPUT (Data,Var,Rel)
0xc0, // END_COLLECTION
0xc0 // END_COLLECTION
};
在HidEmu_Init中,我们看到如下代码
hidEmuTaskId = TMOS_ProcessEventRegister(HidEmu_ProcessEvent);
这是注册HidEmu的事件回调函数HidEmu_ProcessEvent,
在HidEmu_Init最后
tmos_set_event(hidEmuTaskId, START_DEVICE_EVT)
这是启动START_DEVICE_EVT这个事件打开HidEmu_ProcessEvent,在START_DEVICE_EVT启动tmos_start_task(hidEmuTaskId, START_REPORT_EVT, 800);
这是START_REPORT_EVT启动报文上报
/*********************************************************************
* @fn HidEmu_ProcessEvent
*
* @brief HidEmuKbd Application Task event processor. This function
* is called to process all events for the task. Events
* include timers, messages and any other user defined events.
*
* @param task_id - The TMOS assigned task ID.
* @param events - events to process. This is a bit map and can
* contain more than one event.
*
* @return events not processed
*/
uint16_t HidEmu_ProcessEvent(uint8_t task_id, uint16_t events)
{
if(events & SYS_EVENT_MSG)
{
uint8_t *pMsg;
if((pMsg = tmos_msg_receive(hidEmuTaskId)) != NULL)
{
hidEmu_ProcessTMOSMsg((tmos_event_hdr_t *)pMsg);
// Release the TMOS message
tmos_msg_deallocate(pMsg);
}
// return unprocessed events
return (events ^ SYS_EVENT_MSG);
}
if(events & START_DEVICE_EVT)
{
tmos_start_task(hidEmuTaskId, START_REPORT_EVT, 800);
return (events ^ START_DEVICE_EVT);
}
if(events & START_PARAM_UPDATE_EVT)
{
// Send connect param update request
GAPRole_PeripheralConnParamUpdateReq(hidEmuConnHandle,
DEFAULT_DESIRED_MIN_CONN_INTERVAL,
DEFAULT_DESIRED_MAX_CONN_INTERVAL,
DEFAULT_DESIRED_SLAVE_LATENCY,
DEFAULT_DESIRED_CONN_TIMEOUT,
hidEmuTaskId);
return (events ^ START_PARAM_UPDATE_EVT);
}
if(events & START_PHY_UPDATE_EVT)
{
// start phy update
PRINT("Send Phy Update %x...\n", GAPRole_UpdatePHY(hidEmuConnHandle, 0,
GAP_PHY_BIT_LE_2M, GAP_PHY_BIT_LE_2M, GAP_PHY_OPTIONS_NOPRE));
return (events ^ START_PHY_UPDATE_EVT);
}
if(events & START_REPORT_EVT)
{
hidEmuSendMouseReport(MOUSE_BUTTON_NONE, 2, 2);
tmos_start_task(hidEmuTaskId, START_REPORT_EVT, 800);
return (events ^ START_REPORT_EVT);
}
return 0;
}
START_REPORT_EVT中调用hidEmuSendMouseReport上报鼠标报文。
4.1.3 下载验证
我们把固件程序下载进去可以,连接上电脑后可以看到HID Mouse设备,连上之后,我们将看到电脑桌面的鼠标指针45°斜着走;
可以从B站以下视频P6看到我们的实验现象:https://www.bilibili.com/video/BV16t4y1t71a/?vd_source=2bbde87de845d5220b1d8ba075c12fb0
4.2 实例BLE HID_Mouse_Simulation
第一个实例我们将实现蓝牙鼠标功能,即XY坐标轴+左中右按键+滚轮的微软鼠标;
4.2.1 硬件外设
如下图是板载Joystick,摇杆电位器分别接到PA4和PA5,另有一个按键接到PA6;
再有8个轻触按键接到了PB0~PB7;
4.2.2 软件设计
我们先打开HID_Mouse_Simulation 工程,展开APP->MyBSP.c
void MyBsp_Init(void)
{
uint8_t i = 0;
/* DMA单通道采样:选择adc通道0(PA4),通道1(PA5)做采样 */
GPIOA_ModeCfg(GPIO_Pin_4, GPIO_ModeIN_Floating);
GPIOA_ModeCfg(GPIO_Pin_5, GPIO_ModeIN_Floating);
ADC_ExtSingleChSampInit(SampleFreq_3_2, ADC_PGA_1_4);
ADC_ChannelCfg(0);
ADC_AutoConverCycle(192); // 采样周期为 (256-192)*16个系统时钟
ADC_DMACfg(ENABLE, (uint16_t) (uint32_t) &abcBuff[0],
(uint16_t) (uint32_t) &abcBuff[20], ADC_Mode_Single);
PFIC_EnableIRQ(ADC_IRQn);
ADC_StartDMA();
while(!DMA_end);
DMA_end = 0;
PRINT("AIN1 DMA end \n");
for (i = 0; i < 20; i++) {
PRINT("%d \n", abcBuff[i]);
}
ADC_ChannelCfg(1);
ADC_StartDMA();
while(!DMA_end);
DMA_end = 0;
PRINT("AIN2 DMA end \n");
for (i = 0; i < 20; i++) {
PRINT("%d \n", abcBuff[i]);
}
//Joystick button
GPIOA_ModeCfg(GPIO_Pin_6, GPIO_ModeIN_PU);//鼠标中键
GPIOB_ModeCfg(GPIO_Pin_0, GPIO_ModeIN_PU);//Wheel 上
GPIOB_ModeCfg(GPIO_Pin_1, GPIO_ModeIN_PU);//鼠标左键
GPIOB_ModeCfg(GPIO_Pin_2, GPIO_ModeIN_PU);//鼠标右键
GPIOB_ModeCfg(GPIO_Pin_3, GPIO_ModeIN_PU);//Wheel 下
}
以上MyBsp_Init主要功能是配置adc通道0(PA4),通道1(PA5)DMA采样,由于CH582只支持单通道采集,所以需要切换通道;
接着,我们再看Joystick_Handler,是实现鼠标的数据解析的;
void Joystick_Handler(void)
{
static u16 WheelTickUp=0,WheelTickDn=0;
uint8_t buf[HID_MOUSE_RPT_LEN]={0};
u8 i = 0;
u32 Ad_Sum = 0;
//X
ADC_ChannelCfg(0);
ADC_StartDMA();
while(!DMA_end);
DMA_end = 0;
for (i = 0; i < 20; i++) {
Ad_Sum += abcBuff[i];
}
PRINT("X=%d\r\n", Ad_Sum / 20);
Ad_X=map2(Ad_Sum / 20,1560,3174,0,255);
if(Ad_X<(X_CENTRE-20))
{
buf[1]= (u8)(-(X_CENTRE-Ad_X)/HID_MOUSE_DIV);
}else if(Ad_X>(X_CENTRE+20)){
buf[1]=(Ad_X-X_CENTRE)/HID_MOUSE_DIV;
}else{
buf[1]=0;
}
//Y
Ad_Sum = 0;
ADC_ChannelCfg(1);
ADC_StartDMA();
while(!DMA_end);
DMA_end = 0;
for (i = 0; i < 20; i++) {
Ad_Sum += abcBuff[i];
}
PRINT("Y=%d\r\n", Ad_Sum / 20);
Ad_Y=map2(Ad_Sum / 20,1560,3174,0,255);
Ad_Sum = 0;
if(Ad_Y<(Y_CENTRE-20))
{
buf[2]= (u8)(-(Y_CENTRE-Ad_Y)/HID_MOUSE_DIV);
}else if(Ad_Y>(Y_CENTRE+20)){
buf[2]=(Ad_Y-Y_CENTRE)/HID_MOUSE_DIV;
}else{
buf[2]=0;
}
//Wheel 上
if(GPIOB_ReadPortPin(GPIO_Pin_0)==0)
{
if(WheelTickUp++>5)
{
WheelTickUp=0;
buf[3]=1;
}
}else{
WheelTickUp=0;
}
//Wheel 下
if(GPIOB_ReadPortPin(GPIO_Pin_3)==0)
{
if(WheelTickDn++>5)
{
WheelTickDn=0;
buf[3]=-1;
}
}else{
WheelTickDn=0;
}
//左
if(GPIOB_ReadPortPin(GPIO_Pin_1)==0)
{
buf[0]|=0x01;
}else{
buf[0]&=(~0x01);
}
//右
if(GPIOB_ReadPortPin(GPIO_Pin_2)==0)
{
buf[0]|=0x02;
}else{
buf[0]&=(~0x02);
}
//中
if(GPIOA_ReadPortPin(GPIO_Pin_6)==0)
{
buf[0]|=0x04;
}else{
buf[0]&=(~0x04);
}
HidDev_Report(HID_RPT_ID_MOUSE_IN, HID_REPORT_TYPE_INPUT,
HID_MOUSE_RPT_LEN, buf);
}
4.2.3 下载验证
我们把固件程序下载进去,具体实验现象参考视频
https://www.bilibili.com/video/BV16t4y1t71a/?vd_source=2bbde87de845d5220b1d8ba075c12fb0
4.3 实例Eg3_BLE _HID_Joystick_MAXMIN
在实现HID_Joystick之前,我们先确定Joystick的最值,就是X轴与Y轴的最大最小值;
4.3.1 硬件外设
请参考原理图;
4.3.2 软件设计
我们先打开我们先打开HID_Joystick_MAXMIN 工程,展开APP->MyBSP.c,我们看到MyBsp_Init,主要是初始化ADC DMA 读取Joystick XY电压,并初始化按键IO;
void MyBsp_Init(void)
{
uint8_t i = 0;
/* DMA单通道采样:选择adc通道0(PA4),通道1(PA5)做采样 */
GPIOA_ModeCfg(GPIO_Pin_4, GPIO_ModeIN_Floating);
GPIOA_ModeCfg(GPIO_Pin_5, GPIO_ModeIN_Floating);
ADC_ExtSingleChSampInit(SampleFreq_3_2, ADC_PGA_1_4);
ADC_ChannelCfg(0);
ADC_AutoConverCycle(192); // 采样周期为 (256-192)*16个系统时钟
ADC_DMACfg(ENABLE, (uint16_t) (uint32_t) &abcBuff[0],
(uint16_t) (uint32_t) &abcBuff[20], ADC_Mode_Single);
PFIC_EnableIRQ(ADC_IRQn);
ADC_StartDMA();
while(!DMA_end);
DMA_end = 0;
PRINT("AIN1 DMA end \n");
for (i = 0; i < 20; i++) {
PRINT("%d \n", abcBuff[i]);
}
ADC_ChannelCfg(1);
ADC_StartDMA();
while(!DMA_end);
DMA_end = 0;
PRINT("AIN2 DMA end \n");
for (i = 0; i < 20; i++) {
PRINT("%d \n", abcBuff[i]);
}
//Joystick button
GPIOB_ModeCfg(GPIO_Pin_0, GPIO_ModeIN_PU);//1
GPIOB_ModeCfg(GPIO_Pin_1, GPIO_ModeIN_PU);//2
GPIOB_ModeCfg(GPIO_Pin_2, GPIO_ModeIN_PU);//3
GPIOB_ModeCfg(GPIO_Pin_3, GPIO_ModeIN_PU);//4
GPIOB_ModeCfg(GPIO_Pin_4, GPIO_ModeIN_PU);//5
GPIOB_ModeCfg(GPIO_Pin_5, GPIO_ModeIN_PU);//6
GPIOB_ModeCfg(GPIO_Pin_6, GPIO_ModeIN_PU);//7
GPIOB_ModeCfg(GPIO_Pin_7, GPIO_ModeIN_PU);//8
}
接着,我们看Joystick_Handler,主要是为了确定XY的最值;
void Joystick_Handler(void)
{
uint8_t buf[HID_JOYSTICK_RPT_LEN]={0};
u8 i = 0;
u32 Ad_Sum = 0;
//X
ADC_ChannelCfg(0);
ADC_StartDMA();
while(!DMA_end);
DMA_end = 0;
for (i = 0; i < 20; i++) {
Ad_Sum += abcBuff[i];
}
Ad_X=Ad_Sum / 20;
if(Ad_X<adx_min)
{
adx_min= Ad_X;
}
if(Ad_X>adx_max)
{
adx_max= Ad_X;
}
PRINT("max=%d,X=%d,min=%d\r\n",adx_max, Ad_X,adx_min);
//Y
Ad_Sum = 0;
ADC_ChannelCfg(1);
ADC_StartDMA();
while(!DMA_end);
DMA_end = 0;
for (i = 0; i < 20; i++) {
Ad_Sum += abcBuff[i];
}
Ad_Y=Ad_Sum / 20;
Ad_Sum = 0;
if(Ad_Y<ady_min)
{
ady_min= Ad_Y;
}
if(Ad_Y>ady_max)
{
ady_max= Ad_Y;
}
PRINT("max=%d,Y=%d,min=%d\r\n",ady_max, Ad_Y,ady_min);
HidDev_Report(HID_RPT_ID_MOUSE_IN, HID_REPORT_TYPE_INPUT,
HID_JOYSTICK_RPT_LEN, buf);
}
4.3.3 下载验证
我们把固件程序下载进去可以,通过串口调试助手接收log信息,确定XY的最值;
4.4 实例Eg4_BLE_HID_Joystick
这一节我们直接解析joystick数据;
4.4.1 硬件外设
请参考原理图;
4.4.2 软件设计
我们先看报告描述符
// HID Report Map characteristic value
static const uint8_t hidReportMap[] = {
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
0x09, 0x04, // USAGE (Joystick)
0xa1, 0x01, // COLLECTION (Application)
0xa1, 0x02, // COLLECTION (Logical)
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)
0x75, 0x08, // REPORT_SIZE (8)
0x95, 0x02, // REPORT_COUNT (2)
0x81, 0x02, // INPUT (Data,Var,Abs)
0x05, 0x09, // USAGE_PAGE (Button)
0x19, 0x01, // USAGE_MINIMUM (Button 1)
0x29, 0x08, // USAGE_MAXIMUM (Button 8)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x25, 0x01, // LOGICAL_MAXIMUM (1)
0x95, 0x08, // REPORT_COUNT (8)
0x75, 0x01, // REPORT_SIZE (1)
0x81, 0x02, // INPUT (Data,Var,Abs)
0xc0, 0xc0 // END_COLLECTION
};
我们直接打开MyBSP.c,Joystick_Handler是解析XY绝对值坐标,按键数据;
/**
* @brief Transform value from input range to value in output range
* @param x: Value to transform
* @param in_min: Minimum value of input range
* @param in_max: Maximum value of input range
* @param out_min: Minimum value of output range
* @param out_max: Maximum value of output range
* @retval Transformed value
*/
static u32 map2(u32 x, u32 in_min, u32 in_max, u32 out_min, u32 out_max) {
if (x < in_min)
return out_min;
if (x > in_max)
return out_max;
x = (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
return x;
}
void Joystick_Handler(void) {
uint8_t buf[HID_JOYSTICK_RPT_LEN] = { 0 };
u8 i = 0;
u32 Ad_Sum = 0;
//X
ADC_ChannelCfg(0);
ADC_StartDMA();
while(!DMA_end);
DMA_end = 0;
for (i = 0; i < 20; i++) {
Ad_Sum += abcBuff[i];
}
Ad_X = Ad_Sum / 20;
PRINT("max=%d,X=%d,min=%d\r\n", adx_max, Ad_X, adx_min);
buf[0] = map2(Ad_X, ADX_MIN, ADX_MAX, 0, 255);
//Y
Ad_Sum = 0;
ADC_ChannelCfg(1);
ADC_StartDMA();
while(!DMA_end);
DMA_end = 0;
for (i = 0; i < 20; i++) {
Ad_Sum += abcBuff[i];
}
Ad_Y = Ad_Sum / 20;
buf[1] = map2(Ad_Y, ADY_MIN, ADY_MAX, 0, 255);
Ad_Sum = 0;
PRINT("max=%d,Y=%d,min=%d\r\n", ady_max, Ad_Y, ady_min)
if (GPIOB_ReadPortPin(GPIO_Pin_0) == 0) {
buf[2] |= 0x01;
} else {
buf[2] &= (~0x01);
}
if (GPIOB_ReadPortPin(GPIO_Pin_1) == 0) {
buf[2] |= 0x02;
} else {
buf[2] &= (~0x02);
}
if (GPIOB_ReadPortPin(GPIO_Pin_2) == 0) {
buf[2] |= 0x04;
} else {
buf[2] &= (~0x04);
}
if (GPIOB_ReadPortPin(GPIO_Pin_3) == 0) {
buf[2] |= 0x08;
} else {
buf[2] &= (~0x08);
}
if (GPIOB_ReadPortPin(GPIO_Pin_4) == 0) {
buf[2] |= 0x10;
} else {
buf[2] &= (~0x10);
}
if (GPIOB_ReadPortPin(GPIO_Pin_5) == 0) {
buf[2] |= 0x20;
} else {
buf[2] &= (~0x20);
}
if (GPIOB_ReadPortPin(GPIO_Pin_6) == 0) {
buf[2] |= 0x40;
} else {
buf[2] &= (~0x40);
}
if (GPIOB_ReadPortPin(GPIO_Pin_7) == 0) {
buf[2] |= 0x80;
} else {
buf[2] &= (~0x80);
}
HidDev_Report(HID_RPT_ID_MOUSE_IN, HID_REPORT_TYPE_INPUT,HID_JOYSTICK_RPT_LEN, buf);
}
4.4.3 下载验证
我们把固件程序下载进去可以,摇动摇杆电位器和按键,电脑上的映射的joystick也跟着动作;
4.5 实例Eg5_BLE_HID_Keyboard
这一节我们修改上一节的工程,实现一个HID_Keyboard;
4.5.1 硬件外设
请参考原理图,这里用到9个按键,PA6作为keyboard的shift键,PA0PA7作为主键盘区18键;
4.5.2 软件设计
我们先看报告描述符,
// HID Report Map characteristic value
static const uint8_t hidReportMap[] = {
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
0x09, 0x06, // USAGE (Keyboard)
0xa1, 0x01, // COLLECTION (Application)
0x05, 0x07, // USAGE_PAGE (Keyboard)
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)
0x95, 0x01, // REPORT_COUNT (1)
0x75, 0x08, // REPORT_SIZE (8)
0x81, 0x03, // 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)
0x95, 0x01, // REPORT_COUNT (1)
0x75, 0x03, // REPORT_SIZE (3)
0x91, 0x03, // OUTPUT (Cnst,Var,Abs)
0x95, 0x06, // REPORT_COUNT (6)
0x75, 0x08, // REPORT_SIZE (8)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x25, 0xFF, // LOGICAL_MAXIMUM (255)
0x05, 0x07, // USAGE_PAGE (Keyboard)
0x19, 0x00, // USAGE_MINIMUM (Reserved (no event indicated))
0x29, 0x65, // USAGE_MAXIMUM (Keyboard Application)
0x81, 0x00, // INPUT (Data,Ary,Abs)
0xC0 // END_COLLECTION
};
我们直接打开MyBSP.c,Joystick_Handler是解析按键数据的;
/*
* MyBSP.c
*
* Created on: Jul 30, 2022
* Author: Administrator
*/
#include "MyBSP.h"
#include "HAL.h"
#include "devinfoservice.h"
#include "hiddev.h"
#include "hidmouse.h"
#include "hidmouseservice.h"
#define HID_MOUSE_RPT_LEN 4
#define HID_MOUSE_DIV 8
#define X_CENTRE 128
#define Y_CENTRE 128
uint16_t abcBuff[20];
volatile uint8_t adclen;
volatile uint8_t DMA_end = 0;
u32 Ad_X = 0;
u32 Ad_Y = 0;
u8 JyButton = 0;
void MyBsp_Init(void)
{
uint8_t i = 0;
/* DMA单通道采样:选择adc通道0(PA4),通道1(PA5)做采样 */
GPIOA_ModeCfg(GPIO_Pin_4, GPIO_ModeIN_Floating);
GPIOA_ModeCfg(GPIO_Pin_5, GPIO_ModeIN_Floating);
ADC_ExtSingleChSampInit(SampleFreq_3_2, ADC_PGA_1_4);
ADC_ChannelCfg(0);
ADC_AutoConverCycle(192); // 采样周期为 (256-192)*16个系统时钟
ADC_DMACfg(ENABLE, (uint16_t) (uint32_t) &abcBuff[0],
(uint16_t) (uint32_t) &abcBuff[20], ADC_Mode_Single);
PFIC_EnableIRQ(ADC_IRQn);
ADC_StartDMA();
while(!DMA_end);
DMA_end = 0;
PRINT("AIN1 DMA end \n");
for (i = 0; i < 20; i++) {
PRINT("%d \n", abcBuff[i]);
}
ADC_ChannelCfg(1);
ADC_StartDMA();
while(!DMA_end);
DMA_end = 0;
PRINT("AIN2 DMA end \n");
for (i = 0; i < 20; i++) {
PRINT("%d \n", abcBuff[i]);
}
//Joystick button
GPIOA_ModeCfg(GPIO_Pin_6, GPIO_ModeIN_PU);//shift
GPIOB_ModeCfg(GPIO_Pin_0, GPIO_ModeIN_PU);//1
GPIOB_ModeCfg(GPIO_Pin_1, GPIO_ModeIN_PU);//2
GPIOB_ModeCfg(GPIO_Pin_2, GPIO_ModeIN_PU);//3
GPIOB_ModeCfg(GPIO_Pin_3, GPIO_ModeIN_PU);//4
GPIOB_ModeCfg(GPIO_Pin_4, GPIO_ModeIN_PU);//5
GPIOB_ModeCfg(GPIO_Pin_5, GPIO_ModeIN_PU);//6
GPIOB_ModeCfg(GPIO_Pin_6, GPIO_ModeIN_PU);//7
GPIOB_ModeCfg(GPIO_Pin_7, GPIO_ModeIN_PU);//8
}
/**
* @brief Transform value from input range to value in output range
* @param x: Value to transform
* @param in_min: Minimum value of input range
* @param in_max: Maximum value of input range
* @param out_min: Minimum value of output range
* @param out_max: Maximum value of output range
* @retval Transformed value
*/
static u32 map2(u32 x, u32 in_min, u32 in_max, u32 out_min, u32 out_max)
{
if (x < in_min)
return out_min;
if (x > in_max)
return out_max;
x = (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
return x;
}
uint8_t Keyboad_Buf[8]={0};
uint8_t lastshift=0,currentshift=0;
uint8_t lastkeycode[8]={0},currentkeycode[8]={0};
static uint8_t KdataFL=0;
void Joystick_Handler(void)
{
memset(Keyboad_Buf,0,8);
uint8_t i=0;uint8_t idx=2;
if(GPIOA_ReadPortPin(GPIO_Pin_6)==0)
{
currentshift|=0x02;
Keyboad_Buf[0]=currentshift;
}else{
currentshift&=(~0x02);
Keyboad_Buf[0]=currentshift;
}
if(GPIOB_ReadPortPin(GPIO_Pin_0)==0)
{
currentkeycode[0]=CODE1;
}else{
currentkeycode[0]=0x00;
}
if(GPIOB_ReadPortPin(GPIO_Pin_1)==0)
{
currentkeycode[1]=CODE2;
}else{
currentkeycode[1]=0x00;
}
if(GPIOB_ReadPortPin(GPIO_Pin_2)==0)
{
currentkeycode[2]=CODE3;
}else{
currentkeycode[2]=0x00;
}
if(GPIOB_ReadPortPin(GPIO_Pin_3)==0)
{
currentkeycode[3]=CODE4;
}else{
currentkeycode[3]=0x00;
}
if(GPIOB_ReadPortPin(GPIO_Pin_4)==0)
{
currentkeycode[4]=CODE5;
}else{
currentkeycode[4]=0x00;
}
if(GPIOB_ReadPortPin(GPIO_Pin_5)==0)
{
currentkeycode[5]=CODE6;
}else{
currentkeycode[5]=0x00;
}
if(GPIOB_ReadPortPin(GPIO_Pin_6)==0)
{
currentkeycode[6]=CODE7;
}else{
currentkeycode[6]=0x00;
}
if(GPIOB_ReadPortPin(GPIO_Pin_7)==0)
{
currentkeycode[7]=CODE8;
}else{
currentkeycode[7]=0x00;
}
for(i=0;i<8;i++)
{
if(currentkeycode[i]!=lastkeycode[i])
{
Keyboad_Buf[idx]=currentkeycode[i];
if(++idx>=8)
{
idx=2;
}
KdataFL=1;
}else{
Keyboad_Buf[idx]=0x00;
}
}
if(currentshift!=lastshift)
{
KdataFL=1;
}
if(KdataFL!=0)
{
KdataFL=0;
HidDev_Report(HID_RPT_ID_MOUSE_IN, HID_REPORT_TYPE_INPUT,8, Keyboad_Buf);
}
memcpy(lastkeycode,currentkeycode,8);
lastshift=currentshift;
}
/*********************************************************************
* @fn ADC_IRQHandler
*
* @brief ADC中断函数
*
* @return none
*/
__INTERRUPT
__HIGH_CODE
void ADC_IRQHandler(void) //adc中断服务程序
{
if (ADC_GetDMAStatus()) {
ADC_ClearDMAFlag();
ADC_StopDMA();
R16_ADC_DMA_BEG = (uint16_t) (uint32_t) &abcBuff[0];
DMA_end = 1;
}
}
最后在修改GAP参数
// GAP Profile - Name attribute for SCAN RSP data
static uint8_t scanRspData[] = {
0x0D, // length of this data
GAP_ADTYPE_LOCAL_NAME_COMPLETE, // AD Type = Complete local name
'H',
'I',
'D',
' ',
'K',
'e',
'y',
'B',
'o',
'a',
'r',
'd',
// connection interval range
0x05, // length of this data
GAP_ADTYPE_SLAVE_CONN_INTERVAL_RANGE,
LO_UINT16(DEFAULT_DESIRED_MIN_CONN_INTERVAL), // 100ms
HI_UINT16(DEFAULT_DESIRED_MIN_CONN_INTERVAL),
LO_UINT16(DEFAULT_DESIRED_MAX_CONN_INTERVAL), // 1s
HI_UINT16(DEFAULT_DESIRED_MAX_CONN_INTERVAL),
// service UUIDs
0x05, // length of this data
GAP_ADTYPE_16BIT_MORE,
LO_UINT16(HID_SERV_UUID),
HI_UINT16(HID_SERV_UUID),
LO_UINT16(BATT_SERV_UUID),
HI_UINT16(BATT_SERV_UUID),
// Tx power level
0x02, // length of this data
GAP_ADTYPE_POWER_LEVEL,
0 // 0dBm
};
// Device name attribute value
static CONST uint8_t attDeviceName[GAP_DEVICE_NAME_LEN] = "HID KeyBoard";
4.5.3 下载验证
我们把固件程序下载进去可以,按下按键后,电脑上的映射的键盘也跟着动作;
第五部分、USB HID实战训练
5.1 实例USB HID Mouse
第一个实例我们将实现USB鼠标功能,即XY坐标轴+左中右按键+滚轮的微软鼠标;
5.1.1 硬件外设
如下图是板载Joystick,摇杆电位器分别接到PA4和PA5,另有一个按键接到PA6;
再有8个轻触按键接到了PB0~PB7;
5.1.2 软件设计
我们先打开/Code/BLE_VStick/USB/Device/UsbHidMouse/UsbHidMouse.wvproj 工程,
void MyUsbDev_Init(void)
{
pEP0_RAM_Addr = EP0_Databuf; //配置缓存区64字节。
pEP1_RAM_Addr = EP1_Databuf;
USB_DeviceInit();
PFIC_EnableIRQ(USB_IRQn); //启用中断向量
}
MyUsbDev_Init这个函数主要是配置了端点0和端点2的缓冲区预计USB Device相关寄存器的初始化以及启用中断;
接着,是USB中断函数的声明
/*********************************************************************
* @fn USB_IRQHandler
*
* @brief USB中断函数
*
* @return none
*/
__attribute__((interrupt("WCH-Interrupt-fast")))
__attribute__((section(".highcode")))
void USB_IRQHandler(void) /* USB中断服务程序,使用寄存器组1 */
{
USB_DevTransProcess();
}
USB_DevTransProcess这个中断函数主要处理的是USB事务,设备的枚举过程,
这个过程比较复杂,我们主要了解标准请求过程,我们将通过以下视频链接讲解;
<>
最后是鼠标数据的解析并上报,函数如下(同4.2.2):
void Joystick_Handler(void)
{
static uint16_t WheelTickUp=0,WheelTickDn=0;
uint8_t buf[HID_MOUSE_RPT_LEN]={0};
uint8_t i = 0;
uint32_t Ad_Sum = 0;
//X
ADC_ChannelCfg(0);
ADC_StartDMA();
while(!DMA_end);
DMA_end = 0;
for (i = 0; i < 20; i++) {
Ad_Sum += abcBuff[i];
}
PRINT("X=%d\r\n", Ad_Sum / 20);
Ad_X=map2(Ad_Sum / 20,1560,3174,0,255);
if(Ad_X<(X_CENTRE-20))
{
buf[1]= (uint16_t)(-(X_CENTRE-Ad_X)/HID_MOUSE_DIV);
}else if(Ad_X>(X_CENTRE+20)){
buf[1]=(Ad_X-X_CENTRE)/HID_MOUSE_DIV;
}else{
buf[1]=0;
}
//Y
Ad_Sum = 0;
ADC_ChannelCfg(1);
ADC_StartDMA();
while(!DMA_end);
DMA_end = 0;
for (i = 0; i < 20; i++) {
Ad_Sum += abcBuff[i];
}
PRINT("Y=%d\r\n", Ad_Sum / 20);
Ad_Y=map2(Ad_Sum / 20,1560,3174,0,255);
Ad_Sum = 0;
if(Ad_Y<(Y_CENTRE-20))
{
buf[2]= (uint8_t)(-(Y_CENTRE-Ad_Y)/HID_MOUSE_DIV);
}else if(Ad_Y>(Y_CENTRE+20)){
buf[2]=(Ad_Y-Y_CENTRE)/HID_MOUSE_DIV;
}else{
buf[2]=0;
}
//Wheel 上
if(GPIOB_ReadPortPin(GPIO_Pin_0)==0)
{
if(WheelTickUp++>2)
{
WheelTickUp=0;
buf[3]=1;
}
}else{
WheelTickUp=0;
}
//Wheel 下
if(GPIOB_ReadPortPin(GPIO_Pin_3)==0)
{
if(WheelTickDn++>2)
{
WheelTickDn=0;
buf[3]=-1;
}
}else{
WheelTickDn=0;
}
//左
if(GPIOB_ReadPortPin(GPIO_Pin_1)==0)
{
buf[0]|=0x01;
}else{
buf[0]&=(~0x01);
}
//右
if(GPIOB_ReadPortPin(GPIO_Pin_2)==0)
{
buf[0]|=0x02;
}else{
buf[0]&=(~0x02);
}
//中
if(GPIOA_ReadPortPin(GPIO_Pin_6)==0)
{
buf[0]|=0x04;
}else{
buf[0]&=(~0x04);
}
if(Ready)
{
Ready = 0;
DevHIDReport(buf);
}
}
5.1.3 下载验证
我们把固件程序下载进去可以,连接上电脑后可以看到LD Mouse设备,摇动摇杆鼠标坐标移动,按下按键鼠标按键按下;
5.2 实例USB HID Keyboard
这一节我们,实现一个USB Keyboard功能;
5.2.1 硬件外设
请参考原理图,这里用到9个按键,PA6作为keyboard的shift键,PA0PA7作为主键盘区18键;
5.2.2 软件设计
我们在上一节的基础上,第一步我们先来修改报告描述符,键盘的报表描述符如下
const UINT8 KeyboardRepDesc[]=
{
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
0x09, 0x06, // USAGE (Keyboard)
0xa1, 0x01, // COLLECTION (Application)
0x05, 0x07, // USAGE_PAGE (Keyboard)
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)
0x95, 0x01, // REPORT_COUNT (1)
0x75, 0x08, // REPORT_SIZE (8)
0x81, 0x03, // 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)
0x95, 0x01, // REPORT_COUNT (1)
0x75, 0x03, // REPORT_SIZE (3)
0x91, 0x03, // OUTPUT (Cnst,Var,Abs)
0x95, 0x06, // REPORT_COUNT (6)
0x75, 0x08, // REPORT_SIZE (8)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x25, 0xFF, // LOGICAL_MAXIMUM (255)
0x05, 0x07, // USAGE_PAGE (Keyboard)
0x19, 0x00, // USAGE_MINIMUM (Reserved (no event indicated))
0x29, 0x65, // USAGE_MAXIMUM (Keyboard Application)
0x81, 0x00, // INPUT (Data,Ary,Abs)
0xC0 // END_COLLECTION
};
接着,我们修改按键处理并上报
void Joystick_Handler(void)
{
memset(Keyboad_Buf,0,8);
uint8_t i=0;uint8_t idx=2;
if(GPIOA_ReadPortPin(GPIO_Pin_6)==0)
{
currentshift|=0x02;
Keyboad_Buf[0]=currentshift;
}else{
currentshift&=(~0x02);
Keyboad_Buf[0]=currentshift;
}
if(GPIOB_ReadPortPin(GPIO_Pin_0)==0)
{
currentkeycode[0]=CODE1;
}else{
currentkeycode[0]=0x00;
}
if(GPIOB_ReadPortPin(GPIO_Pin_1)==0)
{
currentkeycode[1]=CODE2;
}else{
currentkeycode[1]=0x00;
}
if(GPIOB_ReadPortPin(GPIO_Pin_2)==0)
{
currentkeycode[2]=CODE3;
}else{
currentkeycode[2]=0x00;
}
if(GPIOB_ReadPortPin(GPIO_Pin_3)==0)
{
currentkeycode[3]=CODE4;
}else{
currentkeycode[3]=0x00;
}
if(GPIOB_ReadPortPin(GPIO_Pin_4)==0)
{
currentkeycode[4]=CODE5;
}else{
currentkeycode[4]=0x00;
}
if(GPIOB_ReadPortPin(GPIO_Pin_5)==0)
{
currentkeycode[5]=CODE6;
}else{
currentkeycode[5]=0x00;
}
if(GPIOB_ReadPortPin(GPIO_Pin_6)==0)
{
currentkeycode[6]=CODE7;
}else{
currentkeycode[6]=0x00;
}
if(GPIOB_ReadPortPin(GPIO_Pin_7)==0)
{
currentkeycode[7]=CODE8;
}else{
currentkeycode[7]=0x00;
}
for(i=0;i<8;i++)
{
if(currentkeycode[i]!=lastkeycode[i])
{
Keyboad_Buf[idx]=currentkeycode[i];
if(++idx>=8)
{
idx=2;
}
KdataFL=1;
}else{
Keyboad_Buf[idx]=0x00;
}
}
if(currentshift!=lastshift)
{
KdataFL=1;
}
if(KdataFL!=0)
{
KdataFL=0;
if(Ready)
{
Ready = 0;
DevHIDReport(Keyboad_Buf,8);
}
}
mDelaymS(10);
memcpy(lastkeycode,currentkeycode,8);
lastshift=currentshift;
}
其中上面的代码与4.5实例基本一致,细心的同学肯定发现DevHIDReport多了个参数,第二个参数是上报数据的长度,这里是8;
5.2.3 下载验证
我们把固件程序下载进去可以,连接上电脑后可以看到LD Keyboard设备,按下按键键盘按下;
5.3 实例Usb Hid Joystick
这一节我们,实现一个USB Joystick功能;
5.3.1 硬件外设
请参考原理图;
5.3.2 软件设计
我们先在上一节的基础上修改报表描述符
const UINT8 JoystickRepDesc[]=
{
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
0x09, 0x04, // USAGE (Joystick)
0xa1, 0x01, // COLLECTION (Application)
0xa1, 0x02, // COLLECTION (Logical)
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)
0x75, 0x08, // REPORT_SIZE (8)
0x95, 0x02, // REPORT_COUNT (2)
0x81, 0x02, // INPUT (Data,Var,Abs)
0x05, 0x09, // USAGE_PAGE (Button)
0x19, 0x01, // USAGE_MINIMUM (Button 1)
0x29, 0x08, // USAGE_MAXIMUM (Button 8)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x25, 0x01, // LOGICAL_MAXIMUM (1)
0x95, 0x08, // REPORT_COUNT (8)
0x75, 0x01, // REPORT_SIZE (1)
0x81, 0x02, // INPUT (Data,Var,Abs)
0xc0, 0xc0 // END_COLLECTION
};
然后修改Joystick数据解析函数
void Joystick_Handler(void)
{
uint8_t buf[HID_JOYSTICK_RPT_LEN] = { 0 };
uint8_t i = 0;
uint32_t Ad_Sum = 0;
//X
ADC_ChannelCfg(0);
ADC_StartDMA();
while(!DMA_end);
DMA_end = 0;
for (i = 0; i < 20; i++) {
Ad_Sum += abcBuff[i];
}
Ad_X = Ad_Sum / 20;
buf[0] = map2(Ad_X, ADX_MIN, ADX_MAX, 0, 255);
//Y
Ad_Sum = 0;
ADC_ChannelCfg(1);
ADC_StartDMA();
while(!DMA_end);
DMA_end = 0;
for (i = 0; i < 20; i++) {
Ad_Sum += abcBuff[i];
}
Ad_Y = Ad_Sum / 20;
buf[1] = map2(Ad_Y, ADY_MIN, ADY_MAX, 0, 255);
Ad_Sum = 0;
if (GPIOB_ReadPortPin(GPIO_Pin_0) == 0) {
buf[2] |= 0x01;
} else {
buf[2] &= (~0x01);
}
if (GPIOB_ReadPortPin(GPIO_Pin_1) == 0) {
buf[2] |= 0x02;
} else {
buf[2] &= (~0x02);
}
if (GPIOB_ReadPortPin(GPIO_Pin_2) == 0) {
buf[2] |= 0x04;
} else {
buf[2] &= (~0x04);
}
if (GPIOB_ReadPortPin(GPIO_Pin_3) == 0) {
buf[2] |= 0x08;
} else {
buf[2] &= (~0x08);
}
if (GPIOB_ReadPortPin(GPIO_Pin_4) == 0) {
buf[2] |= 0x10;
} else {
buf[2] &= (~0x10);
}
if (GPIOB_ReadPortPin(GPIO_Pin_5) == 0) {
buf[2] |= 0x20;
} else {
buf[2] &= (~0x20);
}
if (GPIOB_ReadPortPin(GPIO_Pin_6) == 0) {
buf[2] |= 0x40;
} else {
buf[2] &= (~0x40);
}
if (GPIOB_ReadPortPin(GPIO_Pin_7) == 0) {
buf[2] |= 0x80;
} else {
buf[2] &= (~0x80);
}
if(Ready)
{
Ready = 0;
DevHIDReport(buf,3);
}
mDelaymS(10);
}
5.3.3 下载验证
我们把固件程序下载进去可以,连接上电脑后可以看到Joystick设备,摇动摇杆按下按键joystick映射也跟着动;
第六部分、外设基础学习篇
6.1 实例LED(GPIO Output)
第一个实例我们将实现跑马灯,主要是为了学习ch582如何配置GPIO推挽输出;
5.1.1 硬件外设
如下图是板载LED的原理图
5.1.2 软件设计
我们先打开/Code/BLE_VStick/USB/Device/UsbHidMouse/UsbHidMouse.wvproj 工程,
void MyUsbDev_Init(void)
{
pEP0_RAM_Addr = EP0_Databuf; //配置缓存区64字节。
pEP1_RAM_Addr = EP1_Databuf;
USB_DeviceInit();
PFIC_EnableIRQ(USB_IRQn); //启用中断向量
}
MyUsbDev_Init这个函数主要是配置了端点0和端点2的缓冲区预计USB Device相关寄存器的初始化以及启用中断;
接着,是USB中断函数的声明
/*********************************************************************
* @fn USB_IRQHandler
*
* @brief USB中断函数
*
* @return none
*/
__attribute__((interrupt("WCH-Interrupt-fast")))
__attribute__((section(".highcode")))
void USB_IRQHandler(void) /* USB中断服务程序,使用寄存器组1 */
{
USB_DevTransProcess();
}
USB_DevTransProcess这个中断函数主要处理的是USB事务,设备的枚举过程,
这个过程比较复杂,我们主要了解标准请求过程,我们将通过以下视频链接讲解;
<>
最后是鼠标数据的解析并上报,函数如下(同4.2.2):
void Joystick_Handler(void)
{
static uint16_t WheelTickUp=0,WheelTickDn=0;
uint8_t buf[HID_MOUSE_RPT_LEN]={0};
uint8_t i = 0;
uint32_t Ad_Sum = 0;
//X
ADC_ChannelCfg(0);
ADC_StartDMA();
while(!DMA_end);
DMA_end = 0;
for (i = 0; i < 20; i++) {
Ad_Sum += abcBuff[i];
}
PRINT("X=%d\r\n", Ad_Sum / 20);
Ad_X=map2(Ad_Sum / 20,1560,3174,0,255);
if(Ad_X<(X_CENTRE-20))
{
buf[1]= (uint16_t)(-(X_CENTRE-Ad_X)/HID_MOUSE_DIV);
}else if(Ad_X>(X_CENTRE+20)){
buf[1]=(Ad_X-X_CENTRE)/HID_MOUSE_DIV;
}else{
buf[1]=0;
}
//Y
Ad_Sum = 0;
ADC_ChannelCfg(1);
ADC_StartDMA();
while(!DMA_end);
DMA_end = 0;
for (i = 0; i < 20; i++) {
Ad_Sum += abcBuff[i];
}
PRINT("Y=%d\r\n", Ad_Sum / 20);
Ad_Y=map2(Ad_Sum / 20,1560,3174,0,255);
Ad_Sum = 0;
if(Ad_Y<(Y_CENTRE-20))
{
buf[2]= (uint8_t)(-(Y_CENTRE-Ad_Y)/HID_MOUSE_DIV);
}else if(Ad_Y>(Y_CENTRE+20)){
buf[2]=(Ad_Y-Y_CENTRE)/HID_MOUSE_DIV;
}else{
buf[2]=0;
}
//Wheel 上
if(GPIOB_ReadPortPin(GPIO_Pin_0)==0)
{
if(WheelTickUp++>2)
{
WheelTickUp=0;
buf[3]=1;
}
}else{
WheelTickUp=0;
}
//Wheel 下
if(GPIOB_ReadPortPin(GPIO_Pin_3)==0)
{
if(WheelTickDn++>2)
{
WheelTickDn=0;
buf[3]=-1;
}
}else{
WheelTickDn=0;
}
//左
if(GPIOB_ReadPortPin(GPIO_Pin_1)==0)
{
buf[0]|=0x01;
}else{
buf[0]&=(~0x01);
}
//右
if(GPIOB_ReadPortPin(GPIO_Pin_2)==0)
{
buf[0]|=0x02;
}else{
buf[0]&=(~0x02);
}
//中
if(GPIOA_ReadPortPin(GPIO_Pin_6)==0)
{
buf[0]|=0x04;
}else{
buf[0]&=(~0x04);
}
if(Ready)
{
Ready = 0;
DevHIDReport(buf);
}
}