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

前言

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

方案实现

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

HID基本概念

HID(The Human Interface Device)人机交互设备,定义了在人机接口设备中的协议、特征和使用规范,蓝牙HID协议以USB HID协议规范为基础
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);
}
按键回调

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规范
在这里插入图片描述

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值