前言

很久没开发蓝牙相关的项目,找到一块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 报告Id0x09, 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) button50x15, 0x00,       // Logical Minimum (0) 键值00x25, 0x01,       // Logical Maximum (1) 键值10x81, 0x02,       // Input (Data, Variable, Absolute)0x95, 0x01,       // Report Count (1)0x75, 0x03,       // Report Size (3)0x81, 0x01,       // Input (Constant) for padding0x75, 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 20x09, 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服务 0x1812APP_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规范

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

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

    前言 很久没开发蓝牙相关的项目,找到一块Noridc蓝牙kit板,正好配合6轴来做一个拟合姿态的无线鼠标 如有异议,欢迎指正 方案实现 使用了6轴传感器和Nordic kit开发板,6轴获取姿态换算获 ...

  2. 基于MATLAB的关节型六轴机械臂轨迹规划仿真(2021实测完整代码)

    我是一个目录 基于MATLAB的关节型六轴机械臂轨迹规划仿真 1 实验目的 2 实验内容 2.1标准D-H参数法 2.2实验中使用的Matlab函数 3实验结果 4 全部代码 基于MATLAB的关节型 ...

  3. 六轴机器人光机_烂大街的六轴机器人

    刚开始写公众号的时候,就有朋友问我什么时候也讲讲六轴.我说行,等把SCARA跟DELTA讲完了我就讲.现在SCARA跟DELTA的基本介绍终于写完了,但是想要写六轴却不知怎么写好.无法像之前那些做盘点 ...

  4. 基于模型设计(MBD)工程方法开发六轴机械臂系统实战

    本课程采用基于模型设计(MBD)的工程开发方法,实现对六轴机械臂系统的开发,包括六轴机械臂的知识和控制器的软硬件实现,有助于设计串联.并联的机械臂.康复医疗机器人等其它机电设备的电控系统.目前,这种世 ...

  5. C语言 | 基于MPU605(六轴传感器)的I2C实现LCD1602显示(代码类)

    博主github:https://github.com/MichaelBeechan 博主CSDN:https://blog.csdn.net/u011344545 基于MPU605(六轴传感器)的I ...

  6. 用imspost制作catia后处理_基于IMS POST五轴海德汉系统后处理的开发

    摘要:为发挥五轴数控加工高速.高效.高精的加工优势,本文以HEIDENHAIN数控系统为例,基于IMSPOST开发了五轴HEIDENHAIN系统专用后处理.经实际验证,所生成的加工代码符合五轴数控加工 ...

  7. 基于蚁群算法的六轴机械臂路径规划(运动学模型建立)

    机器人运动学模型的建立 1 D-H参数法建立坐标系 2 机器人运动学分析 2.1 运动学正解 2.2 运动学逆解 3 机器人的轨迹仿真 1 D-H参数法建立坐标系 代码: clear; clc; %建 ...

  8. 基于战舰V3的MPU6050六轴陀螺仪实验

    基于战舰V3的MPU6050六轴陀螺仪实验 陀螺仪的分类 3轴传感器指的是3轴的加速度,根据这个加速度我们解算出XY两轴的角度. 6轴传感器指的是3轴的加速度和3轴角速度,根据这两个数据我们解算出XY ...

  9. 【机械】基于简化几何解法的六轴机械臂位置规划附matlab代码

    1 内容介绍 基于简化几何解法的六轴机械臂位置规划附matlab代码 2 部分代码 clc; clear; %载入数据 importfile('shuiping.mat'); theta_shuipi ...

最新文章

  1. AndroidStudio(1 下载安装,环境搭建,使用设置)
  2. [Oracle11g] 通过伪列查询
  3. The type java.lang.Object cannot be resolved
  4. python基础1 第一天
  5. 微信小程序 AppID和AppSecret的获取方式
  6. websocket如何保持连接压力测试统计最后断开了几个链接
  7. linux下无线网卡的ioctl 接口
  8. MyBatis框架及原理分析
  9. arduino学习笔记-库函数解析_LiquidCrystal_i2c使用说明以及lcd1602的驱动
  10. PR软件下载Adobe Premiere Pro CC 2019安装教程
  11. 双碳目标下综合能源系统低碳运行优化调度Matlab程序,包含光伏、风电、热电联产、燃气锅炉、电锅炉、电
  12. GCN的Python实现与源码分析
  13. 曼哈顿算法公式_距离计算方法总结 | Public Library of Bioinformatics
  14. 网站收录查询-批量网站收录查询软件
  15. 进制转换(十进制转二进制)
  16. C++基础知识-----命名空间
  17. FRR、FAR和ERR
  18. 一切成功源于积累——20140927 认识货币——美元
  19. 【九天教您南方cass 9.1】 04 编码法Ⅱ绘制地形图
  20. 用istioctl看istio生成的envoy xds配置(一)

热门文章

  1. Overwatch模拟主页(因版权问题无法跳转暴雪官网)
  2. 量化双均线策略:(二)判断买入卖出信号
  3. 【文献综述】陈恩红: 社交网络的信息传播分析及其应用
  4. 智慧赋能 高效监管 | 工程勘察作业与质量监管数字化平台
  5. 哪种蓝牙耳机戴着最舒服?佩戴最舒服的真无线蓝牙耳机
  6. 齐治堡垒机任意用户登录漏洞
  7. 软件工程基础知识--软件过程模型
  8. linux内核分析课程笔记(二)
  9. 字节跳动网文野心侧漏
  10. Python爬虫——简单爬取(从网站上爬取一本小说)