基于Nordic52832的六轴HID蓝牙鼠标开发

前言
很久没开发蓝牙相关的项目,找到一块Noridc蓝牙kit板,正好配合6轴来做一个拟合姿态的无线鼠标
如有异议,欢迎指正

方案实现
使用了6轴传感器和Nordic kit开发板,6轴获取姿态换算获得欧拉角,然后对应的角度拟合到HID鼠标描述符的XY轴移动改变量,通过蓝牙传给到PC端,用于控制鼠标指针动作

在这里插入图片描述
HID基本概念
HID(The Human Interface Device)人机交互设备,定义了在人机接口设备中的协议、特征和使用规范,蓝牙HID协议以USB HID协议规范为基础 https://www.usb.org/hid

 

HID角色
HID device:HID 设备,PC主机
HID Host:HID 主机,鼠标、键盘
设备描述符结构
HID的定义在接口描述符中,HID接口描述包含了报告描述符和物理描述符

在这里插入图片描述
HID report
HID Device支持三种报告

 

Input report(输入报告): hid device发送给 hid host的封包;例如鼠标将移动和点击事件信息发送给PC端
Output report(输出报告):hid host发送给 hid device的封包;例如PC端发送数据来控制键盘灯
Feature report(特征报告):传输双向数据
HID逻辑链路
HID Control:通道主要用于传输控制封包,这个通道传输的封包为同步封包(synchronous reports),L2CAP的psm为0x0011
HID Interrupt:通道传输的封包不需要确认,所有称为异步封包(asynchronous reports),L2CAP的psm为0x0013

在这里插入图片描述

 

HID Report Models报告模式
两种协议

boot protocol:boot protocl不解析报告描述符,设备支持该协议,在PC系统处于BIOS时就可以识别设备
report protocol:需要解析报告描述符,PC系统启动完成后进行枚举描述符
蓝牙HID
工程实例
使用nordic52832 Kit开发板,SDK为当前最新版本nRF5_SDK_17.0.2_d674dde,工程目录examples\ble_peripheral\ble_app_hids_mouse,协议栈使用S132

广播报文
广播报文包含服务Serivice UUIDs为 0x1812,指示为HID服务,Apperance外观(Type 0x19)为962(960~968),标识HID子类型为鼠标Mouse

在这里插入图片描述
代码分析
主函数
主函数初始化按键与协议栈,在循环中加入6轴的拟合数据后发送鼠标移动量到PC端

int main(void)
{
    bool erase_bonds;

    // Initialize.
    log_init();
    timers_init(); //初始化定时模块
    buttons_leds_init(&erase_bonds); //按键led初始化,注册事件回调
    power_management_init();电源管理
	
/*
* 下面是对蓝牙相关配置的初始化,一般不需要修改
*/	
    ble_stack_init();//协议栈初始化
    scheduler_init();//调度器
    gap_params_init();//gap初始化
    gatt_init();//gatt初始化
    advertising_init();//广播初始化
    services_init();//服务初始化
    sensor_simulator_init();//模拟电池
    conn_params_init();//连接参数初始化
    peer_manager_init();//配对绑定初始化

    // Start execution.
    NRF_LOG_INFO("HID Mouse example started.");
    timers_start();// 开启定时器
	//erase_bonds = true;//擦除绑定
    advertising_start(erase_bonds);//广播开启后,蓝牙内部协议栈的调度就开始跑起来了
	
	Lsm6dlSensor_Init();//6轴初始化
    // Enter main loop.
    for (;;)
    {
		x_mov = 0, y_mov = 0;
		Lsm6dl_Read(&x_mov, &y_mov);//获取移动量
		if (m_conn_handle != BLE_CONN_HANDLE_INVALID)
		{			
			if(x_mov != 0 || y_mov != 0)
				mouse_movement_send(x_mov, y_mov);//发送
		}
       idle_state_handle();//等待事件
    }
}


服务初始化

static void services_init(void)
{
    qwr_init();//
    dis_init();//初始化设备信息服务(产品信息)
    bas_init();//注册电池服务
    hids_init();//hid服务初始化
}


报告描述符
HID设备是通过报告(report)来传输数据的,包含有输入报告(Input)和输入(Output)报告

在这里插入图片描述
bSize(bit0-bit1):用来标识后面所带的数据字节数,00 - 11指代0~4字节(0、1、2、4)
bType(bit2-bit3):标识条目类型,0-主条目、1-全局条目、2-局部条目、3-保留
主条目:分为Input(输入)、Output(输出)、Feature(特性)、Collection(集合)、End Collection(结束集合)
全局条目:选择用途页,定义数据域长度、数量、报告ID,对后面出现的主条目都有效
局部条目:局部有效,遇到主条目后失效,定义控制的特性,如最大最小值
bTag(bit4-bit7):标识条目功能,具体可以查看HID协议
查看hids_init函数
数组rep_map_data为HID的报告描述符,按域划分,用来描述报告的结构与通信数据中具体的作用;代码中主要描述了鼠标的按键与滚轮数据、鼠标移动组织结构,还描述了播放音乐的高级按键

static void hids_init(void)
{
    ret_code_t                err_code;
    ble_hids_init_t           hids_init_obj;
    ble_hids_inp_rep_init_t * p_input_report;
    uint8_t                   hid_info_flags;

    static ble_hids_inp_rep_init_t inp_rep_array[INPUT_REPORT_COUNT];
    static uint8_t rep_map_data[] = //HID 报告描述
    {
        0x05, 0x01, // Usage Page (Generic Desktop)
        0x09, 0x02, // Usage (Mouse)鼠标

        0xA1, 0x01, // Collection (Application)指示下面对mouse解释

        // Report ID 1: Mouse buttons + scroll/pan 按键滚轮
        0x85, 0x01,       // Report Id 1 id = 1 报告Id
        0x09, 0x01,       // Usage (Pointer) 指针形式
        0xA1, 0x00,       // Collection (Physical) 指针定义
        0x95, 0x05,       // Report Count (3)
        0x75, 0x01,       // Report Size (1)
        0x05, 0x09,       // Usage Page (Buttons) //接下来定义按键
        0x19, 0x01,       // Usage Minimum (01) button1 左键
        0x29, 0x05,       // Usage Maximum (05) button5
        0x15, 0x00,       // Logical Minimum (0) 键值0
        0x25, 0x01,       // Logical Maximum (1) 键值1
        0x81, 0x02,       // Input (Data, Variable, Absolute)
        0x95, 0x01,       // Report Count (1)
        0x75, 0x03,       // Report Size (3)
        0x81, 0x01,       // Input (Constant) for padding
        0x75, 0x08,       // Report Size (8)
        0x95, 0x01,       // Report Count (1)
        0x05, 0x01,       // Usage Page (Generic Desktop)
        0x09, 0x38,       // Usage (Wheel) 
        0x15, 0x81,       // Logical Minimum (-127)
        0x25, 0x7F,       // Logical Maximum (127)
        0x81, 0x06,       // Input (Data, Variable, Relative)
        0x05, 0x0C,       // Usage Page (Consumer)
        0x0A, 0x38, 0x02, // Usage (AC Pan)
        0x95, 0x01,       // Report Count (1)
        0x81, 0x06,       // Input (Data,Value,Relative,Bit Field)
        0xC0,             // End Collection (Physical)

        // Report ID 2: Mouse motion 移动
        0x85, 0x02,       // Report Id 2
        0x09, 0x01,       // Usage (Pointer)
        0xA1, 0x00,       // Collection (Physical)
        0x75, 0x0C,       // Report Size (12)
        0x95, 0x02,       // Report Count (2)
        0x05, 0x01,       // Usage Page (Generic Desktop)
        0x09, 0x30,       // Usage (X)
        0x09, 0x31,       // Usage (Y)
        0x16, 0x01, 0xF8, // Logical maximum (2047)
        0x26, 0xFF, 0x07, // Logical minimum (-2047)
        0x81, 0x06,       // Input (Data, Variable, Relative)
        0xC0,             // End Collection (Physical)
        0xC0,             // End Collection (Application)

        // Report ID 3: Advanced buttons 高级按键
        0x05, 0x0C,       // Usage Page (Consumer) 
        0x09, 0x01,       // Usage (Consumer Control)
        0xA1, 0x01,       // Collection (Application)
        0x85, 0x03,       // Report Id (3)
        0x15, 0x00,       // Logical minimum (0)
        0x25, 0x01,       // Logical maximum (1)
        0x75, 0x01,       // Report Size (1)
        0x95, 0x01,       // Report Count (1)

        0x09, 0xCD,       // Usage (Play/Pause)播放暂停
        0x81, 0x06,       // Input (Data,Value,Relative,Bit Field)
        0x0A, 0x83, 0x01, // Usage (AL Consumer Control Configuration)
        0x81, 0x06,       // Input (Data,Value,Relative,Bit Field)
        0x09, 0xB5,       // Usage (Scan Next Track)下一首
        0x81, 0x06,       // Input (Data,Value,Relative,Bit Field)
		0x09, 0xB6,       // Usage (Scan Previous Track)//上一首
        0x81, 0x06,       // Input (Data,Value,Relative,Bit Field)

        0x09, 0xEA,       // Usage (Volume Down)音量-
        0x81, 0x06,       // Input (Data,Value,Relative,Bit Field)
        0x09, 0xE9,       // Usage (Volume Up)音量+
        0x81, 0x06,       // Input (Data,Value,Relative,Bit Field)
        0x0A, 0x25, 0x02, // Usage (AC Forward)应用控制
        0x81, 0x06,       // Input (Data,Value,Relative,Bit Field)
        0x0A, 0x24, 0x02, // Usage (AC Back)返回键
        0x81, 0x06,       // Input (Data,Value,Relative,Bit Field)
        0xC0              // End Collection
    };
    memset(inp_rep_array, 0, sizeof(inp_rep_array));
    // Initialize HID Service.
    p_input_report                      = &inp_rep_array[INPUT_REP_BUTTONS_INDEX];//按键输入报告
    p_input_report->max_len             = INPUT_REP_BUTTONS_LEN;
    p_input_report->rep_ref.report_id   = INPUT_REP_REF_BUTTONS_ID;
    p_input_report->rep_ref.report_type = BLE_HIDS_REP_TYPE_INPUT;

    p_input_report->sec.cccd_wr = SEC_JUST_WORKS;
    p_input_report->sec.wr      = SEC_JUST_WORKS;
    p_input_report->sec.rd      = SEC_JUST_WORKS;

    p_input_report                      = &inp_rep_array[INPUT_REP_MOVEMENT_INDEX];//移动输入报告
    p_input_report->max_len             = INPUT_REP_MOVEMENT_LEN;
    p_input_report->rep_ref.report_id   = INPUT_REP_REF_MOVEMENT_ID;
    p_input_report->rep_ref.report_type = BLE_HIDS_REP_TYPE_INPUT;

    p_input_report->sec.cccd_wr = SEC_JUST_WORKS;
    p_input_report->sec.wr      = SEC_JUST_WORKS;
    p_input_report->sec.rd      = SEC_JUST_WORKS;

    p_input_report                      = &inp_rep_array[INPUT_REP_MPLAYER_INDEX];//播放输入报告
    p_input_report->max_len             = INPUT_REP_MEDIA_PLAYER_LEN;
    p_input_report->rep_ref.report_id   = INPUT_REP_REF_MPLAYER_ID;
    p_input_report->rep_ref.report_type = BLE_HIDS_REP_TYPE_INPUT;

    p_input_report->sec.cccd_wr = SEC_JUST_WORKS;
    p_input_report->sec.wr      = SEC_JUST_WORKS;
    p_input_report->sec.rd      = SEC_JUST_WORKS;

    hid_info_flags = HID_INFO_FLAG_REMOTE_WAKE_MSK | HID_INFO_FLAG_NORMALLY_CONNECTABLE_MSK;

    memset(&hids_init_obj, 0, sizeof(hids_init_obj));

    hids_init_obj.evt_handler                    = on_hids_evt;
    hids_init_obj.error_handler                  = service_error_handler;
    hids_init_obj.is_kb                          = false; //禁用键盘
    hids_init_obj.is_mouse                       = true; //使能鼠标
    hids_init_obj.inp_rep_count                  = INPUT_REPORT_COUNT;//3个输入
    hids_init_obj.p_inp_rep_array                = inp_rep_array;//报告描述符
    hids_init_obj.outp_rep_count                 = 0;//无输出报告
    hids_init_obj.p_outp_rep_array               = NULL;
    hids_init_obj.feature_rep_count              = 0;//无特征报告
    hids_init_obj.p_feature_rep_array            = NULL;//
    hids_init_obj.rep_map.data_len               = sizeof(rep_map_data);
    hids_init_obj.rep_map.p_data                 = rep_map_data;
    hids_init_obj.hid_information.bcd_hid        = BASE_USB_HID_SPEC_VERSION;
    hids_init_obj.hid_information.b_country_code = 0;
    hids_init_obj.hid_information.flags          = hid_info_flags;
    hids_init_obj.included_services_count        = 0;
    hids_init_obj.p_included_services_array      = NULL;

    hids_init_obj.rep_map.rd_sec         = SEC_JUST_WORKS;
    hids_init_obj.hid_information.rd_sec = SEC_JUST_WORKS;

    hids_init_obj.boot_mouse_inp_rep_sec.cccd_wr = SEC_JUST_WORKS;
    hids_init_obj.boot_mouse_inp_rep_sec.wr      = SEC_JUST_WORKS;
    hids_init_obj.boot_mouse_inp_rep_sec.rd      = SEC_JUST_WORKS;

    hids_init_obj.protocol_mode_rd_sec = SEC_JUST_WORKS;
    hids_init_obj.protocol_mode_wr_sec = SEC_JUST_WORKS;
    hids_init_obj.ctrl_point_wr_sec    = SEC_JUST_WORKS;

    err_code = ble_hids_init(&m_hids, &hids_init_obj);//添加HID服务 0x1812
    APP_ERROR_CHECK(err_code);
}
————————————————
版权声明:本文为CSDN博主「玉怀一捧雪」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/libin55/article/details/115309215


按键回调
kit板上带有四个按键,例程中是用来做XY轴的鼠标移动行为,这里我改成了鼠标按键的功能

新增发送按键功能

void mouse_button_send(int8_t click, int8_t wheel, int8_t pan)
{
    ret_code_t err_code;


    uint8_t buffer[INPUT_REP_BUTTONS_LEN];

    APP_ERROR_CHECK_BOOL(INPUT_REP_BUTTONS_LEN == 3);


    buffer[0] = click;
    buffer[1] = wheel;
    buffer[2] = wheel;

    err_code = ble_hids_inp_rep_send(&m_hids,
                                     INPUT_REP_BUTTONS_INDEX,
                                     INPUT_REP_BUTTONS_LEN,
                                     buffer,
                                     m_conn_handle);
    

    if ((err_code != NRF_SUCCESS) &&
        (err_code != NRF_ERROR_INVALID_STATE) &&
        (err_code != NRF_ERROR_RESOURCES) &&
        (err_code != NRF_ERROR_BUSY) &&
        (err_code != BLE_ERROR_GATTS_SYS_ATTR_MISSING)
       )
    {
        APP_ERROR_HANDLER(err_code);
    }
}



按键事件回调

static void bsp_event_handler(bsp_event_t event)
{
    ret_code_t err_code;

    switch (event)
    {
        case BSP_EVENT_SLEEP:
            sleep_mode_enter();
            break;

        case BSP_EVENT_DISCONNECT:
            err_code = sd_ble_gap_disconnect(m_conn_handle,
                                             BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
            if (err_code != NRF_ERROR_INVALID_STATE)
            {
                APP_ERROR_CHECK(err_code);
            }
            break;

        case BSP_EVENT_WHITELIST_OFF:
            if (m_conn_handle == BLE_CONN_HANDLE_INVALID)
            {
                err_code = ble_advertising_restart_without_whitelist(&m_advertising);
                if (err_code != NRF_ERROR_INVALID_STATE)
                {
                    APP_ERROR_CHECK(err_code);
                }
            }
            break;

        case BSP_EVENT_KEY_0:
            if (m_conn_handle != BLE_CONN_HANDLE_INVALID)
            {
                 mouse_button_send(1, 0, 0);//左键  //mouse_movement_send(-MOVEMENT_SPEED, 0);
            }
            break;

        case BSP_EVENT_KEY_1:
            if (m_conn_handle != BLE_CONN_HANDLE_INVALID)
            {
                     mouse_button_send(2, 0, 0);//右键   //mouse_movement_send(0, -MOVEMENT_SPEED);
            }
            break;

        case BSP_EVENT_KEY_2:
            if (m_conn_handle != BLE_CONN_HANDLE_INVALID)
            {
                mouse_button_send(4, 0, 0);//中键    mouse_movement_send(MOVEMENT_SPEED, 0);
            }
            break;

        case BSP_EVENT_KEY_3:
            if (m_conn_handle != BLE_CONN_HANDLE_INVALID)
            {
                mouse_button_send(0, 0, 0);//取消 mouse_movement_send(0, MOVEMENT_SPEED);
            }
            break;

        default:
            break;
    }
}



总结
PC端绑定设备后会在设备管理器中出现Sim_Mouse的鼠标设备,此时已经实现了鼠标的功能。BLE工程代码并未做太多的深入,这个后续有时间在进行解读,蓝牙HID协议以USB HID协议规范为基础,新增功能可以在报告描述中添加,具体完整版本可以阅读USB HID规范

在这里插入图片描述

 

————————————————
版权声明:本文为CSDN博主「玉怀一捧雪」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/libin55/article/details/115309215

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值