Funpack2-6 基于nRF7002-DK制作的蓝牙键盘鼠标
Funpack2-6 nRF7002-DK nRF5340 蓝牙 键盘鼠标 docker
标签
嵌入式系统
Funpack活动
开发板
happy
更新2023-10-07
648

硬件介绍:funpack第二季第6期活动带来的这款nRF7002-DK开发板是用于nRF7002 Wi-Fi 6协同IC的开发套件,该开发套件采用nRF5340多协议片上系统 (SoC) 作为nRF7002的主处理器,在单一的电路板上包含了开发工作所需的一切,可让开发人员轻松开启基于nRF7002 的物联网项目。该 DK 包括 Arduino 连接器、两个可编程按钮、一个 Wi-Fi 双频段天线和一个低功耗蓝牙天线,以及电流测量引脚。这款DK支持低功耗 Wi-Fi 应用开发,并实现了多项 Wi-Fi 6 功能,比如 OFDMA、波束成型和 TWT。nRF7002 Wi-Fi 6配套IC为另一个主机添加了低功耗Wi-Fi 6功能,提供无缝连接和基于Wi-Fi的定位(本地Wi-Fi集线器的SSID嗅探)功能。
FpNIyJ3SUwZyUUlwiZxMyrxq54wP

任务选择:本次活动我选择了任务一:使用板卡的蓝牙连接,设计一个蓝牙鼠标+键盘复合设备,按键1作为鼠标点击,按键2作为键盘输入按下时输入“eetree”字符,电脑开启大写锁定时,板卡的LED亮起。选择这个任务的原因是听直播课老师介绍,才留意到,很多蓝牙鼠标主控都是使用nRF系列的芯片制成的,这个系列芯片在低功耗、无线通讯方面做得非常的优秀。

开发环境搭建:首先是官方提供的开发环境安装说明,但是很多服务器在国外,连接很不稳定,几乎没有成功的可能。还有网上一些老师的关于环境搭建的帖子,尝试了一轮,还是没能把环境搭建起来。最后群里一位老师提供了一个在docker下构建环境的帖子,并且非常贴心地将需要的软件包放在了百度云上,成功地在Ubuntu下将docker的环境实现了。

(.venv) mcudev@Lenovo:~/nRF7002DK$ ls
开发文档  命令行刷机工具  图形界面刷机工具  Docker环境源码包  sdk-nrf-2.4.1.zip  zephyr-3.4.0.zip
(.venv) mcudev@Lenovo:~/nRF7002DK$ sudo docker images
[sudo] mcudev 的密码: 
REPOSITORY                        TAG           IMAGE ID       CREATED         SIZE
nordicplayground/nrfconnect-sdk   v2.4-branch   f61807723f9d   2 weeks ago     6.04GB

任务实现:
FjIBxnVk82f-RMdTHWPIXkXBlI-P
先做了个流程图,需要使用的外设不多,两个LED灯、两个开关按钮。板子上的wifi、nfc功能都没有涉及。

学习开发板最方便的途径当然就是实现例程。官方提供的例程中有蓝牙鼠标、蓝牙键盘的例子。所以先将蓝牙键盘的例子拷贝出来,先进行编译。这里经过群里老师的提醒,蓝牙键盘的例子中栈设置的过小,需要修改。需要给项目单独的配置文件:prj.conf。开发板的选择,先点中“Nordic boards”然后就可以输入这个开发板“nrf7002dk_nrf5340_cpuapp”。然后就可以进行编译了。
FvoC21PYyzCVglmFv73VwiAkztfm

 

第一次烧写后,会报一个错误“LED index out of the range”检查一下项目对应的外设。这个开发板上有两颗LED灯,但是在代码中设置的有LED1~LED4,这里做一下屏蔽即可。

#define ADV_STATUS_LED DK_LED1
#define CON_STATUS_LED DK_LED2
#define LED_CAPS_LOCK  DK_LED3
#define NFC_LED	       DK_LED4
#define KEY_TEXT_MASK  DK_BTN1_MSK
#define KEY_SHIFT_MASK DK_BTN2_MSK
#define KEY_ADV_MASK   DK_BTN4_MSK


FsGH9buUCIpd1mDonQJfKXSiPdEn修改后:

// #define ADV_STATUS_LED DK_LED1
#define CON_STATUS_LED DK_LED2
#define LED_CAPS_LOCK  DK_LED1
// #define NFC_LED	       DK_LED4
#define KEY_TEXT_MASK  DK_BTN2_MSK
#define KEY_SHIFT_MASK DK_BTN1_MSK
// #define KEY_ADV_MASK   DK_BTN4_MSK

/* Key used to accept or reject passkey value */
#define KEY_PAIRING_ACCEPT DK_BTN1_MSK
#define KEY_PAIRING_REJECT DK_BTN2_MSK

解决了这些小问题,能够正常编译运行了,任务差不多就完成了一半了。这个官方的例程,实现了两个按键的输入,一个按键是依次输入“hello_world_str”字符串中的字符。另一个按键模拟的shift按键,同时按下,可以切换大小写。还实现了大小写切换的LED提醒。当切换大小写时,LED灯会对应地亮/灭。接下来对这个例程进行改造,增加鼠标的功能。
首先修改配置文件prj.conf。需要修改的地方有“
CONFIG_BT_DEVICE_NAME”,这个是设备的蓝牙名称,一会电脑上寻找蓝牙设备就会找到这个名字。“CONFIG_BT_DEVICE_APPEARANCE”这个是HID设备对应的图标,960对应的是0x03C0,是个鼠标+键盘的图标,不过这个不修改貌似也没啥影响。最后5行是增加栈的大小,必须修改。

#
# 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="nrf_mousekeyboard"
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

然后需要修改report map。HID设备被连接上后,HOST会获取hid report map来确定这个设备具有哪些功能(比如每次上报数据有几个字节,有什么按键,每个按键对应上报数据的哪个字节和实现什么功能等等)。这个地方有点看不懂,参考了网上的一篇文章。试着修改。这里始终有个疑惑,鼠标的report map分三个部分,第一部分是鼠标按键的输入,第二部分是鼠标的移动,第三部分是鼠标的高级按键功能。仅仅添加第一部分的report map,蓝牙连接就会提示“蓝牙协议错误”。所以这里对report map还是不太明白。但是对应鼠标的事件,就只添加了一个鼠标按键的事件,毕竟不需要用到鼠标的移动和高级按键功能。

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 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) */
		
		//添加鼠标的reportmap,这里只需要用到鼠标按键,所以只添加部分功能----------------------------------
		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 */
	};

	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_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_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_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");
}

static void auth_passkey_display(struct bt_conn *conn, unsigned int passkey)
{
	char addr[BT_ADDR_LE_STR_LEN];

	bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));

	printk("Passkey for %s: %06u\n", addr, passkey);
}

static void auth_passkey_confirm(struct bt_conn *conn, unsigned int passkey)
{
	int err;

	struct pairing_data_mitm pairing_data;

	pairing_data.conn    = bt_conn_ref(conn);
	pairing_data.passkey = passkey;

	err = k_msgq_put(&mitm_queue, &pairing_data, K_NO_WAIT);
	if (err) {
		printk("Pairing queue is full. Purge previous data.\n");
	}

	/* In the case of multiple pairing requests, trigger
	 * pairing confirmation which needed user interaction only
	 * once to avoid display information about all devices at
	 * the same time. Passkey confirmation for next devices will
	 * be proccess from queue after handling the earlier ones.
	 */
	if (k_msgq_num_used_get(&mitm_queue) == 1) {
		k_work_submit(&pairing_work);
	}
}

接下来调整按键,每次按下按键对应着两个动作:按键按下、按键弹起。对应鼠标发送蓝牙数据,也是发送两次,否则就会变成压着右键一直不松开的效果了。

//鼠标按键按下后的事件
static void mouse_button_send(bool down)
{
	uint8_t buf_down[INPUT_REP_BUTTONS_LEN]={2,0,0};
	uint8_t buf_up[INPUT_REP_BUTTONS_LEN]={0,0,0};
	
	if (down) {
		bt_hids_inp_rep_send(&hids_obj, conn_mode[0].conn,
		  INPUT_REP_BUTTONS_INDEX,
		  buf_down, sizeof(buf_down), NULL);
	} else {
		bt_hids_inp_rep_send(&hids_obj, conn_mode[0].conn,
		  INPUT_REP_BUTTONS_INDEX,
		  buf_up, sizeof(buf_up), NULL);
	}
}

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

	// printk("button:[ %d , %d , %d ]\n",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);		//输入字符		
	}
	if (has_changed & KEY_SHIFT_MASK) {
		// button_shift_changed((button_state & KEY_SHIFT_MASK) != 0);		//功能按键
		mouse_button_send((button_state & KEY_SHIFT_MASK) != 0);
	}
}

修改好代码后就是编译烧写了。编译烧写都在docker下进行。按照docker环境搭建的帖子中,编译命令是:

docker run -it --rm -v ${PWD}:/workdir/project nordicplayground/nrfconnect-sdk:$docker_nrf_revision west build -d project/build -b nrf7002dk_nrf5340_cpuapp zephyr/samples/basic/blinky

这里有点疑惑,命令中将当前的路径映射到docker环境中的"/workdir/project”,然后编译后的build文件夹,是存放在默认路径下“/workdir/build”。这样就导致了,编译完成后看不到build文件夹。所以这里修改了一下这个编译命令:将编译后的build文件夹存放在了本地文件夹内,这样编译后build文件夹就不会消失了。

sudo docker run -it --rm -v ${PWD}:/workdir/project nordicplayground/nrfconnect-sdk:$docker_nrf_revision west build -d project/build -b nrf7002dk_nrf5340_cpuapp project/nrf_mousekeyboard

Fut0zqONJ_7Cp5q3j-wSkgjam4Rd

最后烧录:

nrfjprog --verify --program ./build/zephyr/zephyr.hex --chiperase -r

FpnAzTMVsUWC4CNoU-Q8jsUf8XN1

接下来就可以愉快地玩耍啦!
Fu_TqU20wXK6h8_ZnhC0PGtt086R

FieaATdjVvjSdd_H92ybGMGBHUQI

Fsv-lBkdXxCetKAydxW3DmhOtX9B

心得体会: 非常开心参加funpack举办的活动,nRF7002DK是一个非常优秀的开发板。尚有很多好玩的功能待去探索。开发过程中得到了交流群里很多老师的帮助,在此表示非常感谢!期待学习各位老师的作品!

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