任务目的
使用板卡的蓝牙连接,设计一个蓝牙鼠标+键盘复合设备,按键1作为鼠标点击,按键2作为键盘输入按下时输入“eetree”字符,电脑开启大写锁定时,板卡的LED亮起。
设计思路
Nordic提供了大量参考代码,里面就有蓝牙鼠标(..\v2.4.0\nrf\samples\bluetooth\peripheral_hids_mouse)跟蓝牙键盘(..\v2.4.0\nrf\samples\bluetooth\peripheral_hids_keyboard)的代码。我们可以学习官方代码思路,在原有代码基础上,将两个功能合并,开发出蓝牙鼠标+键盘复合设备。
本项目使用板载的nRF5340 Soc作为主控,利用其内置的蓝牙支持,开发蓝牙HIDS设备。利用板卡外置的按钮和LED进行交互操作,完成蓝牙配对和按键模拟。
主要是实现HID协议接口。HID(Human Interface Device)人体学接口设备,是生活中常见的输入设备,比如键盘鼠标游戏手柄等等。早期的HID是设备大部分都是通过USB接口来实现,蓝牙技术出现后,通过蓝牙作为传输层,实现了无线HID设备。通过低功耗蓝牙实现的HID功能一般简称为HOGP(HID over Gatt Profile)。
任何低功耗蓝牙芯片都可以通过软件开发实现HID功能,一个蓝牙芯片要实现HID的功能,一般需满足如下两个条件:
一、在广播数据中广播HID的UUID,设备外观,设备名称等信息。
蓝牙HID广播数据
HID服务的UUID是0x1812,键盘的外观是0x03C1,鼠标的外观是0x03C2,游戏手柄的外观是0x03C3。
二、在GATT中实现HID要求的服务和特性。
蓝牙HIDS UUID,0x1812是HID Service的UUID,必须要使用该UUID实现服务。
硬件介绍
nRF5340是支持低功耗蓝牙、蓝牙Mesh、NFC、Thread和Zigbee的双核蓝牙5.3 SoC,并且蓝牙测向可实现所有到达角(AoA)和出发角(AoD)的测量功能。此外,它支持低功耗蓝牙音频,2 Mbps高吞吐量、广播扩展和长距离。像蓝牙Mesh、Thread和Zigbee这样的Mesh协议可以与低功耗蓝牙同时运行,从而使智能手机能够配网、入网、配置和控制Mesh节点。还支持NFC、ANT、802.15.4和2.4 GHz专有协议。
特性:
- Arduino连接器
- 两个可编程的按钮
- 搭载nRF7002 Wi-Fi协同IC
- 作为主处理器的nRF5340 SoC
- 电流测量引脚
- 2.4GHz、2.4/5 GHz和NFC天线
- 高性能的128MHz Arm Cortex-M33应用内核
- 超低功率的64MHz Arm Cortex-M33网络核心
软件框图
主要代码
初始化代码
int err;
printk("Starting Bluetooth Peripheral HIDS mouse example\n");
if (IS_ENABLED(CONFIG_BT_HIDS_SECURITY_ENABLED)) {
err = bt_conn_auth_cb_register(&conn_auth_callbacks);
if (err) {
printk("Failed to register authorization callbacks.\n");
return 0;
}
err = bt_conn_auth_info_cb_register(&conn_auth_info_callbacks);
if (err) {
printk("Failed to register authorization info callbacks.\n");
return 0;
}
}
/* DIS initialized at system boot with SYS_INIT macro. */
hid_init();
err = bt_enable(NULL);
if (err) {
printk("Bluetooth init failed (err %d)\n", err);
return 0;
}
printk("Bluetooth initialized\n");
k_work_init(&adv_work, advertising_process);
if (IS_ENABLED(CONFIG_BT_HIDS_SECURITY_ENABLED)) {
k_work_init(&pairing_work, pairing_process);
}
if (IS_ENABLED(CONFIG_SETTINGS)) {
settings_load();
}
advertising_start();
configure_gpio();
hid实例初始化
int err;
struct bt_hids_init_param hids_init_param = { 0 };
struct bt_hids_inp_rep *hids_inp_rep;
struct bt_hids_outp_feat_rep *hids_outp_rep;
static const uint8_t mouse_movement_mask[DIV_ROUND_UP(INPUT_REP_MOVEMENT_LEN, 8)] = {0};
static const uint8_t report_map[] = {
0x05, 0x01, /* Usage Page (Generic Desktop) */
0x09, 0x06, /* Usage (Keyboard) */
0xA1, 0x01, /* Collection (Application) */
/* Keys */
0x85, 0x01,
0x05, 0x07, /* Usage Page (Key Codes) */
0x19, 0xe0, /* Usage Minimum (224) */
0x29, 0xe7, /* Usage Maximum (231) */
0x15, 0x00, /* Logical Minimum (0) */
0x25, 0x01, /* Logical Maximum (1) */
0x75, 0x01, /* Report Size (1) */
0x95, 0x08, /* Report Count (8) */
0x81, 0x02, /* Input (Data, Variable, Absolute) */
0x95, 0x01, /* Report Count (1) */
0x75, 0x08, /* Report Size (8) */
0x81, 0x01, /* Input (Constant) reserved byte(1) */
0x95, 0x06, /* Report Count (6) */
0x75, 0x08, /* Report Size (8) */
0x15, 0x00, /* Logical Minimum (0) */
0x25, 0x65, /* Logical Maximum (101) */
0x05, 0x07, /* Usage Page (Key codes) */
0x19, 0x00, /* Usage Minimum (0) */
0x29, 0x65, /* Usage Maximum (101) */
0x81, 0x00, /* Input (Data, Array) Key array(6 bytes) */
/* LED */
0x85, 0x01,
0x95, 0x05, /* Report Count (5) */
0x75, 0x01, /* Report Size (1) */
0x05, 0x08, /* Usage Page (Page# for LEDs) */
0x19, 0x01, /* Usage Minimum (1) */
0x29, 0x05, /* Usage Maximum (5) */
0x91, 0x02, /* Output (Data, Variable, Absolute), */
/* Led report */
0x95, 0x01, /* Report Count (1) */
0x75, 0x03, /* Report Size (3) */
0x91, 0x01, /* Output (Data, Variable, Absolute), */
/* Led report padding */
0xC0, /* End Collection (Application) */
0x05, 0x01, /* Usage Page (Generic Desktop) */
0x09, 0x02, /* Usage (Mouse) */
0xA1, 0x01, /* Collection (Application) */
/* Report ID 2: Mouse buttons + scroll/pan */
0x85, 0x02, /* Report Id 2 */
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) */
0x29, 0x05, /* Usage Maximum (05) */
0x15, 0x00, /* Logical Minimum (0) */
0x25, 0x01, /* Logical Maximum (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 3: Mouse motion */
0x85, 0x03, /* Report Id 3 */
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 4: Advanced buttons */
0x05, 0x0C, /* Usage Page (Consumer) */
0x09, 0x01, /* Usage (Consumer Control) */
0xA1, 0x01, /* Collection (Application) */
0x85, 0x04, /* Report Id (4) */
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 (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 */
};
// Initialize Mouse HID Service.
hids_init_param.rep_map.data = report_map;
hids_init_param.rep_map.size = sizeof(report_map);
hids_init_param.info.bcd_hid = BASE_USB_HID_SPEC_VERSION;
hids_init_param.info.b_country_code = 0x00;
hids_init_param.info.flags = (BT_HIDS_REMOTE_WAKE |
BT_HIDS_NORMALLY_CONNECTABLE);
hids_inp_rep = &hids_init_param.inp_rep_group_init.reports[0];
hids_inp_rep->size = INPUT_REPORT_KEYS_MAX_LEN;
hids_inp_rep->id = INPUT_REP_KEYS_REF_ID;
hids_init_param.inp_rep_group_init.cnt++;
hids_outp_rep = &hids_init_param.outp_rep_group_init.reports[0];
hids_outp_rep->size = OUTPUT_REPORT_MAX_LEN;
hids_outp_rep->id = OUTPUT_REP_KEYS_REF_ID;
hids_outp_rep->handler = hids_outp_rep_handler;
hids_init_param.outp_rep_group_init.cnt++;
hids_init_param.is_kb = true;
hids_init_param.boot_kb_outp_rep_handler = hids_boot_kb_outp_rep_handler;
hids_init_param.pm_evt_handler = hids_pm_evt_handler;
hids_inp_rep = &hids_init_param.inp_rep_group_init.reports[1];
hids_inp_rep->size = INPUT_REP_BUTTONS_LEN;
hids_inp_rep->id = INPUT_REP_REF_BUTTONS_ID;
hids_init_param.inp_rep_group_init.cnt++;
hids_inp_rep++;
hids_inp_rep->size = INPUT_REP_MOVEMENT_LEN;
hids_inp_rep->id = INPUT_REP_REF_MOVEMENT_ID;
hids_inp_rep->rep_mask = mouse_movement_mask;
hids_init_param.inp_rep_group_init.cnt++;
hids_inp_rep++;
hids_inp_rep->size = INPUT_REP_MEDIA_PLAYER_LEN;
hids_inp_rep->id = INPUT_REP_REF_MPLAYER_ID;
hids_init_param.inp_rep_group_init.cnt++;
hids_init_param.is_mouse = true;
hids_init_param.pm_evt_handler = hids_pm_evt_handler;
err = bt_hids_init(&hids_obj, &hids_init_param);
__ASSERT(err == 0, "HIDS initialization failed\n");
按键检测代码
void button_changed(uint32_t button_state, uint32_t has_changed)
{
uint32_t buttons = button_state & has_changed;
if (IS_ENABLED(CONFIG_BT_HIDS_SECURITY_ENABLED)) {
if (k_msgq_num_used_get(&mitm_queue)) {
if (buttons & KEY_PAIRING_ACCEPT) {
num_comp_reply(true);
return;
}
if (buttons & KEY_PAIRING_REJECT) {
num_comp_reply(false);
return;
}
}
}
if (has_changed & DK_BTN1_MSK) {
if((button_state & DK_BTN1_MSK) !=0 )
mouse_buttons_send(1);
else
mouse_buttons_send(0);
}
if (buttons & DK_BTN2_MSK) {
key_send_flag = 1;
}
}
键盘数据处理代码
static const uint8_t *chr = eetree_str;
if(key_send_flag == 1)
{
hid_keyboard_state.keys_state[0] = *chr;
key_report_send();
k_msleep(10U);
hid_keyboard_state.keys_state[0] = 0;
key_report_send();
k_msleep(40U);
if (++chr == (eetree_str + sizeof(eetree_str))) {
chr = eetree_str;
key_send_flag = 0;
}
}
鼠标数据处理代码
static void mouse_buttons_send(int8_t click)
{
if (!conn_mode[0].conn) {
return;
}
uint8_t buffer[INPUT_REP_MOVEMENT_LEN];
buffer[0] = click;
buffer[1] = 0;
buffer[2] = 0;
bt_hids_inp_rep_send(&hids_obj, conn_mode[0].conn,
INPUT_REP_BUTTONS_INDEX,
buffer, sizeof(buffer), NULL);
}
功能展示及说明
电脑扫描nRF7002 DK板卡蓝牙广播。
模拟鼠标点击演示见视频。
模拟键盘输入eetree。
开启大写锁定后,LED点亮。
总结感想
本次的板卡硬禾也算出了血本,板卡很好,玩的也很开心,体验到Nordic第一款双核芯片nrf5340,也是Nordic第一款wifi芯片nrf7002,感觉设计的还是很不错的。
不知不觉Funpack活动第二季已经到了最后一期了,很开心能一直参加本次的活动,我也算硬禾的老粉丝了,第一季一共参加了10期,第二季参加了6期,也算是见证硬禾一步步成长了,希望Funpack活动第三季赶紧出来,我好继续参加。
意见建议
尽快安排第三季 (๑ᵔ⤙ᵔ๑)。
注意
附件代码下载后,需重新选择板卡进行编译,板卡选择配置如下:
如果无法编译,请检查代码存放路径是否正常。