Funpack2-6---基于nRF7002-DK板卡的蓝牙鼠标+键盘复合设备
使用板卡的蓝牙连接,设计一个蓝牙鼠标+键盘复合设备,按键1作为鼠标点击,按键2作为键盘输入按下时输入“eetree”字符,电脑开启大写锁定时,板卡的LED亮起
标签
Funpack2-6
nRF7002
蓝牙鼠标
蓝牙键盘
冷月烟
更新2023-10-10
490

任务目的

使用板卡的蓝牙连接,设计一个蓝牙鼠标+键盘复合设备,按键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实现服务。

 

硬件介绍

project-1.png?2

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网络核心

 

软件框图

FrIhVt20-oGLXNSYCKbldcJka23K

 

主要代码

初始化代码

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板卡蓝牙广播

FhfNJa7Rr1R3L47iI1fPr2lnTc2T

模拟鼠标点击演示见视频。

模拟键盘输入eetree。

FrVspgd4li6NEyRHL9GiQfq3NWz9

开启大写锁定后,LED点亮。

FhqnytXGdPTwa0tOpZzWig7QgF19

 

总结感想

本次的板卡硬禾也算出了血本,板卡很好,玩的也很开心,体验到Nordic第一款双核芯片nrf5340,也是Nordic第一款wifi芯片nrf7002,感觉设计的还是很不错的。

不知不觉Funpack活动第二季已经到了最后一期了,很开心能一直参加本次的活动,我也算硬禾的老粉丝了,第一季一共参加了10期,第二季参加了6期,也算是见证硬禾一步步成长了,希望Funpack活动第三季赶紧出来,我好继续参加。

 

意见建议

尽快安排第三季 (๑ᵔ⤙ᵔ๑)。

 

注意

附件代码下载后,需重新选择板卡进行编译,板卡选择配置如下:

FlOvRCxqH3igvJtaEdT1MdqWMIvd

如果无法编译,请检查代码存放路径是否正常。

附件下载
mouse_keyboard.zip
代码
团队介绍
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号