BLE-VStick入门手册

BLE-VStick入门手册

第一部分、硬件概述

1.1 BLE-VStick实物图

如下图,是BLE-Vstick实物图;分核心板(顶板)和底板;

核心板有两个晶振提供系统时钟源,由于TMOS的心跳是基于RTC时钟为基准的,也就是32.768KHz的晶振提供时钟源,所以不能用手去摸晶振引脚,否则会导致蓝牙断开和其他不可预期的异常;CH582M内部带有DC-DC模块,一般在低功耗时候开启的话可以省电;天线部分默认使用陶瓷天线,如果天线增益不够,可以修改0R电感到IPX座子上即可接外置天线;

底板板载了一个摇杆电位器,8个轻触开关,2个USB口,LDO和充电模块;

微信图片_20220703214020(1)

1.2 BLE-VStick原理图

BLE-VStick原理图下图所示,如看不清可打开Hardware目录下Sch的PDF文档查阅

1.2.1核心板

CH582M_第1页

1.2.2底板

CH582M_第1页(1)

第二部分、软件工具

2.1 软件概述

在 /Software 目录下是常用的工具软件:

  1.  MounRiver: 编译器;
  2.  WCHISPTool:ISP在系统编程工具
  3.  设备测试网站:https://devicetests.com/    

2.2 MounRiver软件入门

大家访问以下链接:http://mounriver.com/help

2.3 工程树

  • 完成工程创建后出现下图:

    微信截图_20220703231558

    其中:

    • 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 资料的获取与技术支持

第四部分、蓝牙HID实战训练

4.1 实例BLE HID Mouse

第一个实例我们将实现蓝牙鼠标功能,即XY坐标轴+左中右按键+滚轮的微软鼠标;

4.1.1 硬件外设

如下图是板载Joystick,摇杆电位器分别接到PA4和PA5,另有一个按键接到PA6;

微信截图_20220705011245

再有8个轻触按键接到了PB0~PB7;

image-20220705011543185

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;

微信截图_20220705011245

再有8个轻触按键接到了PB0~PB7;

image-20220705011543185

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;

微信截图_20220705011245

再有8个轻触按键接到了PB0~PB7;

image-20220705011543185

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的原理图
image

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 下载验证

posted @ 2022-10-17 11:40  LiJin_hh  阅读(717)  评论(0编辑  收藏  举报