查看: 4658|回复: 2

基于QN908x着手开发蓝牙键盘

[复制链接]
  • TA的每日心情
    开心
    2025-7-11 08:53
  • 签到天数: 301 天

    连续签到: 2 天

    [LV.8]以坛为家I

    3873

    主题

    7477

    帖子

    0

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    39276
    最后登录
    2025-7-21
    发表于 2019-11-7 09:36:25 | 显示全部楼层 |阅读模式
    一、概述


    低功耗蓝牙(Bluetooth Low Energy,简称BLE)的应用十分广泛,其中包括鼠标/键盘等人机交互设备(Human Interface Device,简称HID)。


    蓝牙联盟定义了HID over GATT Profile(简称HOGP)规范,开发标准BLE HID设备需遵循此规范。而HOGP基于USB HID定义,采用USB HID数据格式,所以在开发前需要对USB HID和HOGP规范有一定的掌握。


    本文会对HID规范做简要的介绍,然后基于NXP QN908x、官方SDK和QN908x DK板开发一个简单的键盘demo。

    二、HID基本定义


    ① 角色类型


    HID定义了两种通讯角色:Host和Device。一般PC、手机为Host,BLE键盘、鼠标等为Device。
    ② 数据传输


    数据传输采用Report的方式,分三种:Input Report, Output Report和Feature Report。
    Input Report:表示Device向Host传输数据,比如键盘向PC上报一个键值。传输方向是基于Host定义的,所以Host接收数据为Input,这点要注意。
    Output Report:表示Host向Device传输数据,比如PC向键盘下发一个LED状态指示命令。
    Feature Report:特征数据,可双向传输。
    ③ 报告模式


    Report有两种工作模式:Boot Protocol和Report Protocol。
    Boot Protocol传输数据格式固定,而Report Protocol为自定义格式。
    当设置为Boot Protocol时,PC BIOS启动时就能够识别键盘的键值或鼠标移动。Host正常运行时,Device一般采用Report Protocol方式。

    三、HOGP规范


    HOGP规范《HID over GATT Profile Specification》定义了BLE HID Device的相关规程和特性。


    1) Service要求


    规范中规定了HID Device需要包含以下几个Service,其中HID Service、Battery Service和Device Information Service为强制要求。
    1.png
    2)HID Service定义



    HID Service定义了相关characteristic,用于传输获取HID数据(详细参考《HID Service Specification》)。
    2.png
    Protocol Mode:上文有提到,可为Boot Protocol Mode和Report Protocol Mode(默认)。
    Input Report: 用于Device向Host报告数据,通常采用Notify方式。
    Output Report:用于Host向Device发送数据,通过采用Write方式。
    Feature Report:用于双向通讯,采用Read/Write方式。
    Report Map:这个非常重要,用于描述HID Device的功能特性,如主要功能、报告方式、数据大小和格式等,最大可为512 bytes。Input/Output/Feature Report这几个characteristic需要跟Report Map严格对应。
    举个例子:


                            Item                                                                          Value

                                                                                                             (Hex)
    3.png
    以上表格中,Value栏的第一个字节为识别码,比如0x05表示Usage Page,0x85表示Report ID,后面紧跟字节表示数值(数值字节数根据前面识别码确定,通常1-2个字节)。每一行的Value按顺序组成的数组{0x05, 0x01, 0x09, 0x06, …0xC0}即为这个HID Service的Report Map。


    上述例子表示这是个Keyboard应用,有一个Input Report,它的Report ID为0x01,数据为1个byte(8-bit)绝对变量,最小值为0x1E,最大为0x22。


    详细Report Map定义和用法请参考《USB Device Class Definition for Human Interface Devices》、《USB HID Usage Tables》这两份标准文档。

    四、环境搭建


    QN908x是一款超低功耗BLE SoC,32位arm Cortex-M4F内核,支持BLE 5.0。片上存储丰富,带512KB Flash,128KB SRAM和256KB ROM。发送电流为3.5mA @ 0dB(1Mbps/2Mbps),接收电流在1Mbps和2Mbps速率下分别为3.5mA和5.0mA,休眠电流可达1.0uA,很适合键盘/鼠标这类对功耗要求比较高的电池设备上应用。
    可从恩智浦官网http://mcuxpresso.nxp.com/下载最新SDK。SDK里的应用支持IAR、Keil和MCUXpresso IDE三种开发环境,可自由选择下载。

    另外可从恩智浦官网http://www.nxp.com下载相关开发文档,以及参考公众号下文章《深入NXP低功耗蓝牙SDK开发系列—编程框架剖析》,来熟悉NXP BLE SDK架构以及应用工程代码。
    4.png
    可根据项目具体需求基于QN908x进行电路板设计,或者向NXP原厂/代理商申请或购买QN9080 开发板进行前期调试。本文是基于QN9080 DK-V1.2进行开发。
    5.png
    五、工程开发


    SDK包含丰富的应用例子,其中SDK_2.2_QN908XC\boards\qn908xcdk\wireless_examples\bluetooth\hid_device工程已实现BLE HID鼠标的基本功能。我们可以基于这个工程进行BLE HID键盘的开发。



    首先创建HID Device所需要的Service和Characteristic。在gatt_db.h文件中可看到已实现GAP、GATT、Battery、Device Information和HID Service关于鼠标应用的Service和Characteristic,其中GATT、Battery Service的定义保持不变,GAP和Device Information中将鼠标的定义改成键盘相关即可。


    PRIMARY_SERVICE(service_gap, gBleSig_GenericAccessProfile_d)
        CHARACTERISTIC(char_appearance, gBleSig_GapAppearance_d, (gGattCharPropRead_c) )
            VALUE(value_appearance, gBleSig_GapAppearance_d, (gPermissionFlagReadable_c), 2, UuidArray(gKeyboard_c))


    PRIMARY_SERVICE(service_device_info, gBleSig_DeviceInformationService_d)
        CHARACTERISTIC(char_model_no, gBleSig_ModelNumberString_d, (gGattCharPropRead_c) )

            VALUE(value_model_no, gBleSig_ModelNumberString_d, (gPermissionFlagReadable_c), 17, "HID Keyboard Demo")


    接下来进行Report Map的构建。

    通常一个标准键盘含有A-Z字母、0-9数字、标点符号、F1-F12、空格、回车、Caps Lock、Num Lock、Ctrl、Shift、Alt、Delete、上下左右、音量调节等功能键,标准《USB HID Usage Tables》均对这些按键进行了编码定义。另外也定义了8-byte Input Report和8-bit Output Report标准结构体用于键值上报和LED灯控接收:
    6.png
    7.png
    Report Map构建如下:

    8.png
    根据Report Map,在gatt_db.h的HID Service下定义Input Report (Report ID=1)、Output Report (Report ID=1)、Feature Report (Report ID=1)和Input Report (Report ID=2)等4个characteristics用于数据传输:


    PRIMARY_SERVICE(service_hid, gBleSig_HidService_d)
        CHARACTERISTIC(char_protocol_mode, gBleSig_ProtocolMode_d, (gGattCharPropRead_c | gGattCharPropWriteWithoutRsp_c) )
               VALUE(value_protocol_mode, gBleSig_ProtocolMode_d, (gPermissionFlagReadable_c | gPermissionFlagWritable_c), 1, 0x01)
        CHARACTERISTIC(char_input_report1, gBleSig_Report_d, (gGattCharPropRead_c | gGattCharPropNotify_c | gGattCharPropWrite_c) )
               VALUE_VARLEN(value_input_report1, gBleSig_Report_d, (gPermissionFlagReadable_c), 8, 8, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0)
               CCCD(cccd_input_report1)

               DESCRIPTOR(desc_input_report_ref1, 0x2908, (gPermissionFlagReadable_c), 2, 0x01, 0x01)


    注:由于篇幅原因,不详细描述所有characteristics的定义代码,读者可根据上文小节《Hid Service定义》自行增加。
    至此,针对键盘的GATT database创建完成。接下来定义数据收发的数据结构,比如 Input Report可定义如下:


    typedef struct keyboardHidInputReport_tag{
      uint8_t modifier;
      uint8_t reserved;
      uint8_t keycode_1;
      uint8_t keycode_2;
      uint8_t keycode_3;
      uint8_t keycode_4;
      uint8_t keycode_5;
      uint8_t keycode_6;
    } keyboardHidInputReport_t;
    在hid_interface.h和hid_service.c中声明和定义数据发送函数,如Input Report可定义如下:
    bleResult_t Hid_SendKeyboardInputReport(uint16_t handle uint16_t reportlen, void* pInReport)
    {
        bleResult_t result;


        /* Update characteristic value and send notification */
        result = GattDb_WriteAttribute(handle, reportlen, pInReport);
        if (result != gBleSuccess_c)
            return result;
        Hid_SendReportNotifications(hReport);
        return gBleSuccess_c;
    }
    在hid_device.c定义以下函数方便调用:
    static void SendKeyboardInputReport (keyboardInputHidReport_t *pReport)
    {
        Hid_SendInputReport(value_input_report1, sizeof(keyboardHidInputReport_t), pReport);
    }
    在按键处理函数调用键值发送函数,由于QN9080 DK板只有2个按键SW1、SW2,所以只能模拟2个键盘按键,不妨定义为数字0 (keycode=39)和数字1 (keycode=30),在按键按下时分别发送对应keycode。注意对应按键释放时要重置keycode为0并上报,即按键上升下降沿都要检测,需在app_preinclude.h定义:


    #define gKeyEventNotificationMode_d gKbdEventPressHoldReleaseMode_c
    static keyboardHidInputReport_t input_report =
    {
        .modifier = 0,
        .reserved = 0,
        .keycode_1 = 0,
        .keycode_2 = 0,
        .keycode_3 = 0,
        .keycode_4 = 0,
        .keycode_5 = 0,
        .keycode_6 = 0,
    };
    按键处理函数如下:
    void BleApp_HandleKeys(key_event_t events)
    {
        switch (events)
        {
            case gKBD_EventPressPB1_c:
            {
                input_report.keycode_1 = 39; //0
                SendKeyboardInputReport((keyboardHidInputReport_t *)&input_report);
                break;
            }
            case gKBD_EventPressPB2_c:
            {
                input_report.keycode_1 = 30; //1
                SendKeyboardInputReport((keyboardHidInputReport_t *)&input_report);
                break;
            }
            case gKBD_EventReleasePB1_c:
            case gKBD_EventReleasePB2_c:
                input_report.keycode_1 = 0;
                SendKeyboardInputReport((keyboardHidInputReport_t *)&input_report);
                break;
            default:
                break;
        }
    }
    标准键盘中按键处理方法类似。注意以上函数并没有考虑多个按键同时按下的情况,实际开发中需增加判断和对多个keycode字节进行赋值。
    至于命令接收,可直接在BleApp_GattServerCallback里增加处理函数:


    static void BleApp_GattServerCallback (deviceId_t deviceId, gattServerEvent_t* pServerEvent)
    {
        switch (pServerEvent->eventType)
        {
            case gEvtAttributeWritten_c:
            {
                //增加命令接收处理函数,比如LED控制
            }
            break;
        default:
            break;
        }
    }
    至此,我们完成了蓝牙键盘基本功能的开发。
    由于硬件限制,我们实现了只有两个按键0和1的“程序员”二进制键盘。不过基于此文,大家也可以根据自身需求,开发功能更加强大、外观更加炫酷的蓝牙键盘。
    篇幅限制,未详述之处,请参考HID标准文档及NXP BLE开发文档。文中若有纰漏错误,欢迎指正。


    六、后记


    实际蓝牙键盘基本都是电池供电设备,需在必要时候进入低功耗模式,尽可能节省电量。


    工程hid_device本身并没有实现低功耗,大家可参考heart_rate_sensor这个工程,为键盘设置低功耗,以及设置合理的连接参数。



    OTA升级功能可参考otap_client_att工程、开发文档《BLE Application Developer’s Guide》以及公众号下文章《OTAP大挪移》进行添加。






    作者:叶国欣Aaron@NXP                    文章出处:恩智浦MCU加油站

    qiandao qiandao
    回复

    使用道具 举报

  • TA的每日心情
    开心
    2025-6-10 23:03
  • 签到天数: 1502 天

    连续签到: 1 天

    [LV.Master]伴坛终老

    97

    主题

    4688

    帖子

    12

    版主

    Rank: 7Rank: 7Rank: 7

    积分
    10080
    最后登录
    2025-7-2
    发表于 2019-11-8 09:47:42 | 显示全部楼层
    这么强大的一款MCU,仅用来实现键盘的功能。会不会大马拉小车了啊
    该会员没有填写今日想说内容.
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    开心
    2025-7-11 08:53
  • 签到天数: 301 天

    连续签到: 2 天

    [LV.8]以坛为家I

    3873

    主题

    7477

    帖子

    0

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    39276
    最后登录
    2025-7-21
     楼主| 发表于 2019-11-8 09:58:35 | 显示全部楼层
    jobszheng5 发表于 2019-11-8 09:47
    这么强大的一款MCU,仅用来实现键盘的功能。会不会大马拉小车了啊

    先拉个小车试试
    qiandao qiandao
    回复 支持 反对

    使用道具 举报

    您需要登录后才可以回帖 注册/登录

    本版积分规则

    关闭

    站长推荐上一条 /3 下一条

    Archiver|手机版|小黑屋|恩智浦技术社区

    GMT+8, 2025-7-21 14:42 , Processed in 0.093603 second(s), 21 queries , MemCache On.

    Powered by Discuz! X3.4

    Copyright © 2001-2024, Tencent Cloud.

    快速回复 返回顶部 返回列表