Funpack2-6 使用nRF7002-DK开发板制作的蓝牙键盘鼠标复合设备
Funpack2-6 nRF7002-DK nRF5340 蓝牙 键盘鼠标 环境搭建
标签
嵌入式系统
Funpack活动
测试
nRF7002
aramy
更新2023-10-10
790

1、硬件介绍:
nRF7002-DK——用于nRF7002 Wi-Fi 6双频辅助IC的开发套件。nRF7002 DK是nRF7002 Wi-Fi 6协同IC的开发评估板,它包含了在单板上开发所需要的所有元器件。开发板采用nRF5340多协议SoC作为主处理器,配合nRF7002 Wi-Fi协同芯片。可以同时支持低功耗蓝牙和Wi-Fi 应用的开发,并实现如 OFDMA、波束成形和目标唤醒时间等多项 Wi-Fi 6 功能。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专有协议。

2、任务选择:
这次的板子,自己从来没有接触过。有四个任务,感觉都是全新的挑战,所以就不再选择,直接入手任务一:使用板卡的蓝牙连接,设计一个蓝牙鼠标+键盘复合设备,按键1作为鼠标点击,按键2作为键盘输入按下时输入“eetree”字符,电脑开启大写锁定时,板卡的LED亮起。 首先分析一下这个任务,这个任务需要使用到蓝牙、led灯。开发部模拟成键盘,鼠标与其它设备通讯,官方分别提供了键盘、鼠标的例程,自己只需要将两个例程合二为一即可。看上去简单的任务,确成了噩梦的开始。

3、任务实现:
定好了目标接下来就是努力去实现它。nRF7002-DK支持C语言编程。所以第一步就是搭建开发环境。

首先是参考官方的环境搭建文档,写的很详细,按他的文档一步一步操作下来,不出意外地失败了。分析一下失败原因,这里搭建环境使用了west,需要从github上拉取环境信息,众所周知的原因,网络是拉不下来的。经过多天的努力终于在Ubuntu下将环境搭建起来了。这里说一下需要留意的地方。
1 需要使用代理,否则3G的文件是没法完整拉取下来了的。

git config --global url."https://ghproxy.com/https://github.com" insteadof "https://github.com"

如果这样设置后,依然不行,就编辑文件夹下边的west.yml文件,将文件中所有的“https://github.com” 都替换成“https://ghproxy.com/https://github.com”。这样就可以使用代理来下载github了,速度能快很多。
2 按官方文档   west init ~/zephyrproject 但是,按这个操作,拉取下来的文件不知什么原因总是缺失nfc文件夹。缺失nfc文件夹,会导致无法编译这块板子的例程。所以建议这样操作,下载后会有这样几个文件夹,大小约3.3G。

west init -m https://ghproxy.com/https://github.com/nrfconnect/sdk-nrf.git ~/zephyrproject

FuG72WxNbBSA47E8YdtDlyIrePFq

然后是安装jlink工具(JLink_Linux_V792a_x86_64.deb)和烧写工具(nrf-command-line-tools_10.22.0_amd64.deb)。这样开发环境就搭建好了,接着在Vscode中添加nrf的组件,就可以在Vscode里愉快地编译、烧写程序了。

开发环境搭建不易,花了近一周时间,总算是搞好了。接下来查看官方的例子:nrf/samples/bluetooth/peripheral_hids_keyboard 和 nrf/samples/bluetooth/peripheral_hids_mouse 。 这两个例子分别是 模拟键盘和模拟鼠标的例子。模拟键盘的例子中实现了按键的输入和大小写按键切换LED灯亮起、熄灭的功能。鼠标的例子实现了,鼠标的移动和按键的功能,还有高级按键的使用,没有仔细看。接下来就是合并两个例程了。

动手做才发现这个任务挺难的,在群里老师的指导下,总算是完成了。过程就不详细说了,直接说完成的过程。
Fj4nCvVwUFQJLiCpn5hLyl6ysS6W
第一步:我用nrf/samples/bluetooth/peripheral_hids_keyboard 这个例程,作为自己的基础项目,然后来添加鼠标功能。从例程创建项目后,检查一下按键和LED的设置是否正确,确保和手头的开发板的管脚对应(有几次创建后管脚对应错误,没找到原因)。

Fp0pxzvxkwtxkq0DwRznHLedWWec

FoMQhbCtAop36vErL_H7qesWhGZw

第二步:蓝牙通讯使用了一个report_map,细节自己还是一知半解,但是基本理解就是个用来描述蓝牙通讯中的协议包。键盘使用了8字节长度的通信包,鼠标通讯使用了3字节长度的包。我这里需要同时满足键盘和鼠标,所以将鼠标的report_map添加到键盘中来,这里注意修改了report ID。

static const uint8_t report_map[] = {
		0x05, 0x01, /* Usage Page (Generic Desktop) */
		0x09, 0x06, /* Usage (Keyboard) */
		0xA1, 0x01, /* Collection (Application) */

	/* Keys */
#if INPUT_REP_KEYS_REF_ID
		0x85, INPUT_REP_KEYS_REF_ID,
#endif
		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 */
#if OUTPUT_REP_KEYS_REF_ID
		0x85, OUTPUT_REP_KEYS_REF_ID,
#endif
		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 1 id = 1报告*/ 
		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 3: Mouse motion 移动*/
		0x85, 0x03,       /* 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 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 */
	};

第三步:一共有4个input事件,键盘输入、鼠标按键、鼠标移动、鼠标高级按键;一个output事件:大小写键切换。然后分别将这些事件注册。需要留意,代码中将鼠标、键盘都设置为true了,也就是将设备注册成鼠标键盘的复合设备。

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[INPUT_REP_KEYS_IDX];
	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[OUTPUT_REP_KEYS_IDX];
	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_inp_rep++;
	hids_inp_rep->size = INPUT_REP_BUTTONS_LEN;
	hids_inp_rep->id = INPUT_REP_REF_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_REF_MOVEMENT_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_REP_MEDIA_PLAYER_LEN;
	hids_inp_rep->id = INPUT_REP_REF_MPLAYER_ID;
	hids_init_obj.inp_rep_group_init.cnt++;
	//------------------------------------------

	hids_init_obj.is_kb = true;
	hids_init_obj.is_mouse = 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");

第四步:修改例程中按键事件中的1、2键对应的事件。当按键1按下时,就触发鼠标右键按下、弹起两个蓝牙消息事件。

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;
	}

	if (has_changed & KEY_TEXT_MASK)
	{
		button_text_changed((button_state & KEY_TEXT_MASK) != 0);
		// printk("============");
	}
	if (has_changed & KEY_SHIFT_MASK)		//修改这个按键1 的功能为鼠标右键
	{
		// button_shift_changed((button_state & KEY_SHIFT_MASK) != 0);
		// printk("++++++++++++");
		mouse_button_send(2);
		mouse_button_send(0);
	}
#if CONFIG_NFC_OOB_PAIRING
	if (has_changed & KEY_ADV_MASK)
	{
		size_t i;

		for (i = 0; i < CONFIG_BT_HIDS_MAX_CLIENT_COUNT; i++)
		{
			if (!conn_mode[i].conn)
			{
				advertising_start();
				return;
			}
		}

		printk("Cannot start advertising, all connections slots are"
			   " taken\n");
	}
#endif
}
static void mouse_button_send(uint8_t val)
{
	uint8_t buffer[INPUT_REP_MOVEMENT_LEN];
	buffer[0] = val;
	buffer[1] = 0;
	buffer[2] = 0;
	printk("sendval:[ %d, %d, %d] i=[%d]\n",buffer[0],buffer[1],buffer[2],conn_mode[0].in_boot_mode);
	bt_hids_inp_rep_send(&hids_obj, conn_mode[0].conn,
		  INPUT_REP_BUTTONS_INDEX,
		  buffer, sizeof(buffer), NULL);
}

按键2对应功能是调用例程中,依次发送字符串中的字符的功能,这里只需要修改例程中发送的字符串内容即可。

static const uint8_t hello_world_str[] = {
	0x08, /* Key e */
	0x08, /* Key e */
	0x17, /* Key t */
	0x15, /* Key r */
	0x08, /* Key e */
	0x08, /* Key e */
	0x28, /* Key Return */
};

 

,第五步:修改prj.conf 文件。修改设备名称,改成自己的"mouse_keyboard",修改设备的图标,961是鼠标的图标,962是键盘的图标,更换为960,成为鼠标键盘的复合设备。还需要加大栈的大小,否则开发板不工作。还有个GATT属性描述符的最大数量,这里设置为50,如果不设置的话,开发板会无限重启。这里感谢群里老师的指导。

#
# Copyright (c) 2019 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#
CONFIG_NCS_SAMPLES_DEFAULTS=y

CONFIG_BT=y
CONFIG_BT_DEBUG_LOG=y
CONFIG_BT_MAX_CONN=2
CONFIG_BT_MAX_PAIRED=2
CONFIG_BT_SMP=y
CONFIG_BT_L2CAP_TX_BUF_COUNT=5
CONFIG_BT_PERIPHERAL=y
CONFIG_BT_DEVICE_NAME="mouse_keyboard"
CONFIG_BT_DEVICE_APPEARANCE=960

CONFIG_BT_BAS=y
CONFIG_BT_HIDS=y
CONFIG_BT_HIDS_MAX_CLIENT_COUNT=2
CONFIG_BT_HIDS_DEFAULT_PERM_RW_ENCRYPT=y
CONFIG_BT_GATT_UUID16_POOL_SIZE=40
CONFIG_BT_GATT_CHRC_POOL_SIZE=20

CONFIG_BT_CONN_CTX=y

CONFIG_BT_DIS=y
CONFIG_BT_DIS_PNP=y
CONFIG_BT_DIS_MANUF="NordicSemiconductor"
CONFIG_BT_DIS_PNP_VID_SRC=2
CONFIG_BT_DIS_PNP_VID=0x1915
CONFIG_BT_DIS_PNP_PID=0xEEEF
CONFIG_BT_DIS_PNP_VER=0x0100

CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048

CONFIG_BT_SETTINGS=y
CONFIG_FLASH=y
CONFIG_FLASH_PAGE_LAYOUT=y
CONFIG_FLASH_MAP=y
CONFIG_NVS=y
CONFIG_SETTINGS=y

CONFIG_DK_LIBRARY=y

CONFIG_HEAP_MEM_POOL_SIZE=8192
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=8192
CONFIG_NFC_OOB_PAIRING=n
CONFIG_MAIN_STACK_SIZE=4096
CONFIG_BT_HIDS_ATTR_MAX=50

FrVEs7aYmKZJ-xm2ciXwsOsb1rdK
FhlRRcUAa3hB2nwZr9Zh9r5VQfZD

FkcgtPmEvZDMH8b7Z_F2KbydnXCTFqH-8lsUX-DTATe8vdeX9aDCf7wj

最后,如果遇到编译烧写后,开发板无限重启情况,再次烧写时会报错(原因未知)。我的解决方案是,找到之前编译好的点灯例程,然后运行 west flash --recover 就可以将点灯的例程正常烧入开发板了,然后再在vscode下继续烧写,就能正常烧写了。

Fq8FNl-RO4A5DxAChJ7J83BNBmSA

4、心得体会
感谢funpack带来的这期活动。才发现nRF的芯片在日常的蓝牙鼠标中居然那么常见,低功耗蓝牙做的如此优秀。正好借此机会好好了解一下蓝牙协议,可以好好玩一下蓝牙设备啦!

附件下载
mouse_keyboard.zip
开发板选择nrf5340dk_nrf5340_cpuapp
团队介绍
折腾小能手
团队成员
aramy
单片机业余爱好者,瞎捣鼓小能手。
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号