硬件介绍: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嗅探)功能。
任务选择:本次活动我选择了任务一:使用板卡的蓝牙连接,设计一个蓝牙鼠标+键盘复合设备,按键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
任务实现:
先做了个流程图,需要使用的外设不多,两个LED灯、两个开关按钮。板子上的wifi、nfc功能都没有涉及。
学习开发板最方便的途径当然就是实现例程。官方提供的例程中有蓝牙鼠标、蓝牙键盘的例子。所以先将蓝牙键盘的例子拷贝出来,先进行编译。这里经过群里老师的提醒,蓝牙键盘的例子中栈设置的过小,需要修改。需要给项目单独的配置文件:prj.conf。开发板的选择,先点中“Nordic boards”然后就可以输入这个开发板“nrf7002dk_nrf5340_cpuapp”。然后就可以进行编译了。
第一次烧写后,会报一个错误“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
修改后:
// #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
最后烧录:
nrfjprog --verify --program ./build/zephyr/zephyr.hex --chiperase -r
接下来就可以愉快地玩耍啦!
心得体会: 非常开心参加funpack举办的活动,nRF7002DK是一个非常优秀的开发板。尚有很多好玩的功能待去探索。开发过程中得到了交流群里很多老师的帮助,在此表示非常感谢!期待学习各位老师的作品!