Funpack2-6-基于nRF7002-DK实现蓝牙鼠标+键盘复合设备
基于nRF7002-DK开发板,完成蓝牙鼠标+键盘复合设备功能
标签
BLE
HID
Funpack2-6
蓝牙鼠标+键盘复合设备
JustWantToSeeYouAgain
更新2023-10-13
502

                                    Funpack第二季第六期

                                                                  --基于nRF7002-DK实现蓝牙鼠标+键盘复合设备

一.项目介绍

Funpack活动是硬禾学堂联合Digi-Key发起的为期一年的“玩成功就全额退”活动,本次的开发板套件nRF7002-DK——用于nRF7002 Wi-Fi 6双频辅助IC的开发套件。

项目任务:

使用板卡的蓝牙连接,设计一个蓝牙鼠标+键盘复合设备,按键1作为鼠标点击,按键2作为键盘输入按下时输入“eetree”字符,电脑开启大写锁定时,板卡的LED亮起。

 

二.设计思路

本次的主要任务是用nRF5340的ble功能,实现蓝牙鼠标和蓝牙键盘的复合设备

我们需要掌握的知识点:
1.低功耗蓝牙ble:蓝牙广播、蓝牙扫描、蓝牙连接、蓝牙角色以及ble是如何通信的。

 开发板作为peripheral,电脑作为central,peripheral去发送广播,central去扫描广播,扫描到之后发起连接。连接成功后,peripheral角色转变成slaver,central角色转变成master。

服务和特性
低功耗蓝牙设备之间通信,都是基于服务和特性。一个蓝牙设备中可以包含若干个服务,一个服务中可以包含若干个特性,每一个服务或者特性都要有一个UUID。蓝牙的数据交互都是基于一个个特性进行的,数据交互的方式有五种,分别是Read,Write,Write WithOutRespons,Notify,Indication。

 

2.鼠标和键盘都属于hid设备,那么我们需要了解ble的hid设备,hid是如何构建出鼠标和键盘。

其实是通过hid map表来描述的

Ftd5NbNaOIHEZtiR-CR8lpyoLK8d

 

这个数组中的数据实际上是鼠标的3个报告描述符,这3个报告描述符分别描述了:

(1)、鼠标按键和滚轮数据是如何组织的。

(2)、鼠标移动数据是如何组织的。

(3)、描述了一个高级按键用来播放音乐等功能。

 

 

 

Fh2wJmCeJALVt2ftnmVd5YOfBh6F

这个数组中的数据实际上是键盘的报告描述符,这2个报告描述符分别描述了:键盘按键的输入和输出。

 

其中传输数据:

Input Reports,输入报告
Ble 中,表示 Bluetooth HID device 发送数据给 Bluetooth HID Host.
USB 中输入报告通常通过 中断输入端点来传输。当然也可以通过 控制端点由 HOST 使用 GET REPORT 控制传输请求来获取数据,即host 先发送 get report 命令,device 随后回复 input report,之后 host 会回复一个状态(0 字节数据表示成功)。


Output Reports,输出报告
Ble 中表示 Bluetooth HID Host.发送数据给 Bluetooth HID device.
USB 中输出报告通常通过 中断输出端点来传输。当然也可以通过 控制端点由 HOST 使用 SET REPORT 控制传输请求来发出数据,即HOST 先发送 set report 命令,随后 HOST 发送待发送的数据,最后 DEVICE 回复一个状态(0 字节表示成功)。

想要搞懂可以查看USB中文网:HID报表描述符原理解释 - USB中文网 (usbzh.com)

 

三.代码和流程说明

作为蓝牙设备开发,我认为功能分成以下几点:

1.蓝牙广播,

这里的ad 内容封装的很好,广播的内容就是由多个 ad struct组成,每个struct又是由 length,ad type,data组成,这里用 宏 BT_DATA_BYTES(_type, _bytes...) 来封装 更简介、清晰。

static const struct bt_data ad[] = {
	BT_DATA_BYTES(BT_DATA_GAP_APPEARANCE,
		      (CONFIG_BT_DEVICE_APPEARANCE >> 0) & 0xff,
		      (CONFIG_BT_DEVICE_APPEARANCE >> 8) & 0xff),
	BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
	BT_DATA_BYTES(BT_DATA_UUID16_ALL, BT_UUID_16_ENCODE(BT_UUID_HIDS_VAL),
					  BT_UUID_16_ENCODE(BT_UUID_BAS_VAL)),
};

static void advertising_start(void)
{
	int err;
	struct bt_le_adv_param *adv_param = BT_LE_ADV_PARAM(
						BT_LE_ADV_OPT_CONNECTABLE |
						BT_LE_ADV_OPT_ONE_TIME,
						BT_GAP_ADV_FAST_INT_MIN_2,
						BT_GAP_ADV_FAST_INT_MAX_2,
						NULL);

	err = bt_le_adv_start(adv_param, ad, ARRAY_SIZE(ad), sd,
			      ARRAY_SIZE(sd));
	if (err) {
		if (err == -EALREADY) {
			printk("Advertising continued\n");
		} else {
			printk("Advertising failed to start (err %d)\n", err);
		}

		return;
	}

	is_adv = true;
	printk("Advertising successfully started\n");
}


2.蓝牙连接配对

   1)注册蓝牙connected、disconnected 回调函数。

BT_CONN_CB_DEFINE(conn_callbacks) = {
	.connected = connected,
	.disconnected = disconnected,
	.security_changed = security_changed,
};

 

 

  2)注册蓝牙配对相关函数

static struct bt_conn_auth_cb conn_auth_callbacks = {
	.passkey_display = auth_passkey_display,
	.passkey_confirm = auth_passkey_confirm,
	.cancel = auth_cancel,
#if CONFIG_NFC_OOB_PAIRING
	.oob_data_request = auth_oob_data_request,
#endif
};

static struct bt_conn_auth_info_cb conn_auth_info_callbacks = {
	.pairing_complete = pairing_complete,
	.pairing_failed = pairing_failed
};

 

  3)为了防止中间人攻击,即 mitm,且由于开发板的 IO资源限制,只能确认或者否定,所以配对过程中的安全模式选择了电脑作为passkey显示,开发板作为确认的模式

下面代码即为按下按钮 去确认验证passkey

static void num_comp_reply(bool accept)
{
	struct pairing_data_mitm pairing_data;
	struct bt_conn *conn;

	if (k_msgq_get(&mitm_queue, &pairing_data, K_NO_WAIT) != 0) {
		return;
	}

	conn = pairing_data.conn;

	if (accept) {
		bt_conn_auth_passkey_confirm(conn);
		printk("Numeric Match, conn %p\n", conn);
	} else {
		bt_conn_auth_cancel(conn);
		printk("Numeric Reject, conn %p\n", conn);
	}

	bt_conn_unref(pairing_data.conn);

	if (k_msgq_num_used_get(&mitm_queue)) {
		k_work_submit(&pairing_work);
	}
}



static void button_changed(uint32_t button_state, uint32_t has_changed)
{
	static bool pairing_button_pressed;

	uint32_t buttons = button_state & has_changed;

	if (k_msgq_num_used_get(&mitm_queue)) {
		if (buttons & KEY_PAIRING_ACCEPT) {
			pairing_button_pressed = true;
			num_comp_reply(true);

			return;
		}

		if (buttons & KEY_PAIRING_REJECT) {
			pairing_button_pressed = true;
			num_comp_reply(false);

			return;
		}
	}
}

 

3.读取蓝牙Service和Characteristic

从nrf connect 可以看到除了基础的gatt 、 gap 服务 和 Device Information 服务之外,还有 HID服务 和 电池电量服务。

FsXC24rrnYv73ukzYRW21iE32Sf6

 

而我们这里最重要的就是注册 HID 服务,是在下面代码中注册的

static void hid_init(void)
{
	int err;
	struct bt_hids_init_param    hids_init_obj = { 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[] = {...};

	hids_init_obj.rep_map.data = report_map;
	hids_init_obj.rep_map.size = sizeof(report_map);

	hids_init_obj.info.bcd_hid = BASE_USB_HID_SPEC_VERSION;
	hids_init_obj.info.b_country_code = 0x00;
	hids_init_obj.info.flags = (BT_HIDS_REMOTE_WAKE |
				    BT_HIDS_NORMALLY_CONNECTABLE);


	hids_inp_rep =
		&hids_init_obj.inp_rep_group_init.reports[0];
	hids_inp_rep->size = INPUT_REP_BUTTONS_LEN;
	hids_inp_rep->id = INPUT_REP_MOUSE_BUTTONS_ID;
	hids_init_obj.inp_rep_group_init.cnt++;

	hids_inp_rep++;
	hids_inp_rep->size = INPUT_REP_MOVEMENT_LEN;
	hids_inp_rep->id = INPUT_REP_MOUSE_MOTION_ID;
	hids_inp_rep->rep_mask = mouse_movement_mask;
	hids_init_obj.inp_rep_group_init.cnt++;



	hids_inp_rep++;
	hids_inp_rep->size = INPUT_REPORT_KEYS_MAX_LEN;
	hids_inp_rep->id = INPUT_REP_KEYS_REF_ID;
	hids_init_obj.inp_rep_group_init.cnt++;

	hids_outp_rep =
		&hids_init_obj.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_obj.outp_rep_group_init.cnt++;


	hids_init_obj.is_mouse = true;
	hids_init_obj.is_kb = true;
	hids_init_obj.boot_kb_outp_rep_handler = hids_boot_kb_outp_rep_handler;
	hids_init_obj.pm_evt_handler = hids_pm_evt_handler;

	err = bt_hids_init(&hids_obj, &hids_init_obj);
	__ASSERT(err == 0, "HIDS initialization failed\n");
}

 

4.鼠标左键 和 键盘按键的report

我是在键盘的例程中 增加了鼠标的 map和按键触发。

static void button_changed(uint32_t button_state, uint32_t has_changed)
{
	static bool pairing_button_pressed;

	uint32_t buttons = button_state & has_changed;

	if (k_msgq_num_used_get(&mitm_queue)) {
		if (buttons & KEY_PAIRING_ACCEPT) {
			pairing_button_pressed = true;
			num_comp_reply(true);

			return;
		}

		if (buttons & KEY_PAIRING_REJECT) {
			pairing_button_pressed = true;
			num_comp_reply(false);

			return;
		}
	}

	/* Do not take any action if the pairing button is released. */
	if (pairing_button_pressed &&
	    (has_changed & (KEY_PAIRING_ACCEPT | KEY_PAIRING_REJECT))) {
		pairing_button_pressed = false;

		return;
	}
	//按键触发键盘 report
	if (has_changed & KEY_TEXT_MASK) {
		button_text_changed((button_state & KEY_TEXT_MASK) != 0);
	}
	//按键触发鼠标 report
	if (has_changed & MOUSE_CLICK_MASK) {

		uint8_t btnclick = 0;
		if(button_state)
		{
			btnclick = 1;
		}
		uint8_t buffer[3] = {btnclick,0,0};
		for (size_t i = 0; i < CONFIG_BT_HIDS_MAX_CLIENT_COUNT; i++) {

			if (!conn_mode[i].conn) {
				continue;
			}

			if (conn_mode[i].in_boot_mode) {

				bt_hids_boot_mouse_inp_rep_send(&hids_obj,
									conn_mode[i].conn,
									buffer,
									NULL,
									NULL,
									NULL);
			} else {

				bt_hids_inp_rep_send(&hids_obj, conn_mode[i].conn,
								0,
								buffer, sizeof(buffer), NULL);
			}
		}
	}
}

 

 

四.硬件介绍

nRF5340 是全球首款配备两个 Arm® Cortex-M33® 处理器的无线 SoC。它有1 MB的闪存、512 KB的RAM,一个浮点单元(FPU)、一个8 KB的双向关联高速缓存和DSP指令。网络处理器的主频为64 MHz,并针对低功耗和效率(101 CoreMark/mA)进行了优化。它有256 KB的闪存和64 KB的RAM。

nRF5340 系统级芯片支持各种无线协议。它支持低功耗蓝牙,并且蓝牙测向可实现所有到达角(AoA)和出发角(AoD)的测量功能。此外,它支持低功耗蓝牙音频,2 Mbps高吞吐量、广播扩展和长距离。像蓝牙Mesh、Thread和Zigbee这样的Mesh协议可以与低功耗蓝牙同时运行,从而使智能手机能够配网、入网、配置和控制Mesh节点。还支持NFC、ANT、802.15.4和2.4 GHz专有协议。

 

FjHfZtaQ4mag6182aXJhhxYvdFtd

 

 

五.功能介绍

1.蓝牙搜索的时候出现鼠标加键盘的标志,其实是修改蓝牙的广播内容中的标志CONFIG_BT_DEVICE_APPEARANCE 其ID为 0x03C0 ,十进制数为 960

Fl-RKNhNFNPWy218nNuIY-Y3ULIb

 

2.按下按键1,键盘输入“eetree”

FrDGXg9W4vU9MUmMdUGXgp_GNhDo

 

3.按下按键2,鼠标触发左键,点击了win菜单栏

Fko6Pcjt0hAPAo74IImkwNDQkhGU

 

4.电脑键盘capslock 开启大写锁定时,板卡的LED亮起

Fv8ZBs9VEP5Yc7EfSeBIFqqwb5k5

 

 

 

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