任务介绍
本项目实现了Funpack第4-1期活动Qorvo板卡的任务一,使用了DWM3001CDK开发板实现了基于NFC控制的LED效果灯的设计,支持使用刷卡设备通过NFC协议控制LED实现不同灯效。
硬件平台
首先介绍本次用到的开发板:DWM3001CDK,它是射频方案商Qorvo推出的一块基于Nordic nRF52833微控制器的UWB开发板,它主要面向的是UWB定位应用的开发,但由于使用了nRF52833这一片低功耗蓝牙SoC,所以也同时支持蓝牙、NFC等常用功能。开发板分为底板和核心邮票板,底板引出了大量引脚,并集成了常用的外设,如按键、LED、串口、NFC、USB接口和调试器接口。在软件方面,Nordic官方和第三方平台已经适配好了Arduino、nRF5 SDK、NCS等多种开发框架,上手开发会很方便。本次我会使用这块开发板的NFC外设,做一个基于NFC控制的LED效果灯。
主控设备:Qorvo DWM3001CDK开发板
- 搭载Nordic nRF52833低功耗蓝牙SoC
- 集成NFC Type 2标签功能
- 板载4个可编程LED指示灯
- 支持多线程实时操作系统(Zephyr RTOS)
控制终端:
- 支持NFC-A协议的智能手机/读写器
- ISO/IEC 14443 Type A标准兼容
- 数据传输速率:106kbps
任务分析与实现
本项目实现了通过NFC协议控制LED灯效的智能交互系统,主要功能包括:
- 五种灯效模式:
- 模式0:全灭状态(低功耗模式)
- 模式1:顺序流水(LED2→LED3→LED4)
- 模式2:逆序流水(LED4→LED3→LED2)
- 模式3:三灯同步快闪(200ms周期)
- 模式4:三灯同步慢闪(1000ms周期)
- 双控制通道:
- NFC无线控制:通过NDEF消息'A'-'D'选择模式1-4
- 物理按钮控制:板载按键循环切换0-4模式
- 持久化存储:
- Flash存储最后生效的NDEF消息
- 长按按钮恢复默认灯效配置
方案框图:
代码详解
整体软件流程图:
本次项目涉及到NFC数据解析、按键与LED控制几个关键部分,接下来结合相关代码来进行讲解:
一、多线程架构
K_THREAD_DEFINE(led_tid, LED_STACK_SIZE, // LED控制线程(优先级-2)
led_control_thread, NULL, NULL, NULL,
LED_PRIORITY, 0, 0);
K_THREAD_DEFINE(button_tid, BUTTON_STACK_SIZE, // 按钮检测线程(优先级-1)
button_thread, NULL, NULL, NULL,
BUTTON_PRIORITY, 0, 0);
- 主线程:处理NFC协议栈和闪存操作
- 独立线程1:LED灯光效果控制
- 独立线程2:按钮状态检测
二、NFC数据解析
功能点
- 快速提取NDEF消息Payload
- HEX数据打印辅助调试
- 原子操作保证模式切换的线程安全
手机向nRF52833模拟的NFC设备写入数据,此时会触发 NFC_T4T_EVENT_NDEF_UPDATED 事件,我们在该事件中识别手机写入的数据,转化为本地控制指令。
1. 数据格式
我们先看一看上位机写入数据后,下位机得到的原始数据是怎样的。这里我们分四次向nRF52833写入了'A'、'B'
'C'、'D'四种字符。将它们打印出来:
printk("Raw Data (Hex): ");
for (int i = 0; i < data_length+2; i++) {
printk("%02X ", data[i]);
}
printk("\n");
可以初步得到如下的数据
NLEN | NDEF Record
00 08 | D1 01 06 54 02 7A 68 41
( 8 bytes) | ▲ ▲ ▲ ▲ ▲ ▲ ▲
│ │ │ │ │ │ └─ 数据只有'A'(0x41)
│ │ │ │ │ └─ 语言码"zh"
│ │ │ │ └─ 状态字节
│ │ │ └─ 类型'T'
│ │ └─ 标称载荷长度6字节
│ └─ 类型长度1字节
└─ NDEF Header
可知当写入单字符时,最后一个字符就是我们写入的Payload。nRF52的NFC库提供了完善的NDEF数据解析函数。但这里场景限定且单一,我们可以写一个最简单的解析方法:
int target_mode = -1;
/* 指令映射 */
switch (data[9]) {
case 'A': target_mode = 1; break; // LED_SEQ_FORWARD
case 'B': target_mode = 2; break; // LED_SEQ_BACKWARD
case 'C': target_mode = 3; break; // LED_SYNC_FAST
case 'D': target_mode = 4; break; // LED_SYNC_SLOW
default: target_mode = 0; break; // LED_OFF
}
/* 控制LED */
if (target_mode != -1) {
atomic_set(¤t_mode, target_mode);
printk("NFC command '%c' -> Mode %d\n", data[9], target_mode);
}
三、LED控制线程
1. 设备树设定
DWM3001CDK开发板上有一排4个可控的LED。
我们选择其中一个作为NFC传输指示灯,另外三个作为效果灯。
使用宏定义和枚举在程序中定义:
#define LED_NUM 3
#define LED1 DK_LED2
#define LED2 DK_LED3
#define LED3 DK_LED4
#define MODE_COUNT 5 // 五种模式(含关闭)
/* 全局变量 */
static enum {
LED_OFF, // 模式0:关闭
LED_SEQ_FORWARD, // 模式1:顺序流水
LED_SEQ_BACKWARD, // 模式2:逆序流水
LED_SYNC_FAST, // 模式3:快速同步
LED_SYNC_SLOW // 模式4:慢速同步
} led_mode = LED_OFF;
2. 灯效控制线程
按照题目要求,我们共设计了四种灯光效果,包含关闭,就是共5种状态:
void led_control_thread(void *arg1, void *arg2, void *arg3)
{
ARG_UNUSED(arg1);
ARG_UNUSED(arg2);
ARG_UNUSED(arg3);
int last_mode = -1;
bool led_state = false;
int64_t last_toggle_time = 0;
while (1) {
int current = atomic_get(¤t_mode);
// 模式变化时重置状态
if (current != last_mode) {
dk_set_leds(0);
last_toggle_time = 0;
last_mode = current;
}
switch (current) {
case LED_OFF:
dk_set_leds(0);
k_sleep(K_MSEC(100)); // 低功耗等待
break;
case LED_SEQ_FORWARD:
dk_set_led(LED1, (current_led == 0));
dk_set_led(LED2, (current_led == 1));
dk_set_led(LED3, (current_led == 2));
current_led = (current_led + 1) % 3;
k_sleep(K_MSEC(200));
break;
case LED_SEQ_BACKWARD:
dk_set_led(LED3, (current_led == 0));
dk_set_led(LED2, (current_led == 1));
dk_set_led(LED1, (current_led == 2));
current_led = (current_led + 1) % 3;
k_sleep(K_MSEC(200));
break;
case LED_SYNC_FAST:
if (k_uptime_get() - last_toggle_time >= 200) {
led_state = !led_state;
dk_set_leds(led_state ? (BIT(LED1)|BIT(LED2)|BIT(LED3)) : 0);
last_toggle_time = k_uptime_get();
}
k_sleep(K_MSEC(10));
break;
case LED_SYNC_SLOW:
if (k_uptime_get() - last_toggle_time >= 1000) {
led_state = !led_state;
dk_set_leds(led_state ? (BIT(LED1)|BIT(LED2)|BIT(LED3)) : 0);
last_toggle_time = k_uptime_get();
}
k_sleep(K_MSEC(10));
break;
}
}
}
三、按键控制线程
1、状态流转
- 初始状态:LED_OFF(全灭)
- 转换条件:每次按钮按下触发模式切换
- 状态特征:
- LED_SEQ_FORWARD:顺序流水(LED2→3→4)
- LED_SEQ_BACKWARD:逆序流水(LED4→3→2)
- LED_SYNC_FAST:三灯同步快闪(200ms)
- LED_SYNC_SLOW:三灯同步慢闪(1000ms)
- 循环机制:模式4后回到模式0(LED_OFF)
2、按键扫描与控制
nRF52的SDK为我们实现好了按键读取的函数 dk_read_buttons,可以直接使用,为了不影响主线程代码,我们可以单独起一个按键线程,读取按键并控制LED。
void button_thread(void *arg1, void *arg2, void *arg3)
{
ARG_UNUSED(arg1);
ARG_UNUSED(arg2);
ARG_UNUSED(arg3);
uint32_t last_btn_state = 0;
while (1) {
uint32_t btn_state;
dk_read_buttons(&btn_state, NULL);
if ((btn_state & DK_BTN1_MSK) &&
!(last_btn_state & DK_BTN1_MSK)) {
int new_mode = (atomic_get(¤t_mode) + 1) % MODE_COUNT;
atomic_set(¤t_mode, new_mode);
printk("Mode changed to: %d\n", new_mode);
}
last_btn_state = btn_state;
k_sleep(K_MSEC(50));
}
}
效果展示
手机控制页面
灯效1:顺序流水灯
灯效2:逆序流水灯
灯效3:同步快闪
灯效4:同步慢闪
遇到的难题与解决办法
问题:NDEF数据包解析
我们获取到的NDEF数据是经过协议编码的,Payload只是其中的一部分。nRF SDK提供了一个库来提供解析功能,但示例很少。
解法
编写一套完善的、稳定的NDEF协议解析代码是不必要的,因为我们的使用场景限定且单一,我们可以写一个最简单的解析方法,直接从RAW数据的特定位置中提取我们想要的字节,这样的解析效率也是最高。
活动感想
通过本项目实践,进一步掌握了Zephyr多线程开发、NFC协议栈集成等关键技术。Qorvo的DWM3001CDK开发套件使用了成熟的nRF52主控方案,使用Nordic完善的NFC库支持与简洁易用的开发环境极大简化了开发流程。期待未来能在物联网设备交互设计领域继续深耕,开发出更多创新应用。
感谢硬禾学堂和得捷电子联合举办的Funpack活动,祝硬禾的活动越办越好!