Funpack4-1: 基于DWM3001CDK开发板的NFC点灯功能开发
本项目使用Funpack第四季第1期活动的板卡,Qorvo 的 DWM3001CDK,实现了任务一。具体要求是:点灯!使用导线缠绕成NFC线圈连接在板上,通过刷卡控制板卡上的RGB LED,并实现切换至少4种不同灯效.
标签
Funpack活动
PWM
NFC
DWM3001CDK
chinaking
更新2025-04-02
27

一、     项目概述

本项目使用Funpack第四季第1期活动的板卡,Qorvo DWM3001CDK,实现了任务一。

具体要求是:点灯!使用导线缠绕成NFC线圈连接在板上,通过刷卡控制板卡上的RGB LED,并实现切换至少4种不同灯效,(如色彩渐变,呼吸/流水效果等)。

二、     硬件介绍

开发板实物图

Qorvo DWM3001CDK 是为基于 Qorvo DW3110 IC DWM3001C 全集成 (UWB) 模块开发的设计套件。

开发板使用了DWM3001C模组,且板载 J-Link 调试器为 DWM3001C 提供 SWD UART 接口,具有复位和用户定义按钮和LED

模组支持NFCBLEUWB等无线功能。

DWM3001C模组内部框图如下图所示,

DWM3001C模组内部的处理器是Nodic公司的nRF52833,通过SPI口连接了型号为DW3110UWB芯片。

本项目实现的NFC点灯功能,只需将NFC天线引出即可,NFC天线接口为板卡的J8口。控制灯效时,使用板载的D9-D124LED

三、     搜集资料的过程

搜集资料,主要通过以下途径。

 硬禾学堂网站。下面网址提供了DWM3001C DK开发板的主要介绍、使用教程及常用链接。https://www.eetree.cn/platform/3617

Qorvo官网。官网https://www.qorvo.com/products/p/DWM3001CDK 提供了DWM3001CDK详细的介绍及开发包,开发包里包含了使用SEGGER Embedded Studio实现的例程,注意开发包需要使用企业邮箱下载。

Segger网站。https://www.segger.com/ 通过Segger网站可下载SEGGER Embedded Studio开发软件及JLink软件包。

  Github网站。https://github.com/qqice/Work-on-DWM3001CDK 提供了Arduino环境的构建方法,可根据相关文档,实现ArduinoDWM3001C DK编程。

四、     软件开发

本项目在Windows环境下,使用SEGGER Embedded Studio进行开发。

4.1 实现过程介绍

第一步,熟悉开发板硬件。了解按钮和LED控制的接口函数。

先实现按钮控制LED效果。

获取按钮状态bButtonState=bsp_board_button_state_get(BSP_BOARD_BUTTON_0);

控制LED

bsp_board_led_on(BSP_BOARD_LED_0);

bsp_board_led_off(BSP_BOARD_LED_1);

bsp_board_leds_on();

bsp_board_leds_off();

第二步,熟悉SDK_BSP里的NFC例程。

SDK_BSP\Nordic\NORDIC_SDK_17_1_0\examples\nfc\record_text\pca10040目录下,有一个nfc刷卡的例程,手机靠近开发板NFC天线时,会有NFC弹窗。这个例程适用的是nRF52832的开发板,稍加改动后,即可适用于nRF52833,同时也适用于DWM3001C模组及其开发板。

具体改动为,

在项目option菜单下,修改Target Device类型,由nRF52832改成nRF52833

编译时,如果报错。将工程里的SEGGER_RTT_Syscalls_SES.c文件删除。

同时,需要修改flash_placement.xml文件,将“”里面的0x4删除。

然后,下载测试NFC,手机靠近开发板NFC天线时,会有弹窗显示。

第三步,测试PWM功能。

SDK_BSP\Nordic\NORDIC_SDK_17_1_0\examples\peripheral\pwm_driver下,提供了一个PWM控制LED的例程,同样,对程序按照上一步的过程,使其适配nRF52833

为了匹配开发板的引脚及端口,还需要修改板卡类型,由PCA10040板卡改成CUSTOM定制板卡。这一步,板卡类型设置,无法在文件中修改,在option菜单下也无法修改。

image.png

经过查找资料,可以在pwm_driver_pca10040.emProject工程文件上,右键用记事本或VScode打开,直接将BOARD_PCA10040改成BOARD_CUSTOM即可。

然后,将custom_board.h文件,

放到SDK_BSP\Nordic\NORDIC_SDK_17_1_0\components\boards文件夹内。

这个例程里有5demo,对于不同的pwm效果。

第四步,通过按钮,控制PWM功能。

将第一步和第三步的程序进行整合。

第五步,通过NFC,控制PWM功能。

将第二步和第三步的程序进行整合。

NFC的操作,和按钮操作是相似的,手机靠近和离开,可以看成是按钮的动作。

第六步,将上面的工程整合成综合实例。

整合后的实例,可以通过按钮或手机NFC贴卡的方式,控制PWM点灯效果。

4.2程序介绍

有两种方式,可以实现不同灯效的切换,分别是状态机方式和事件触发方式。

状态机和事件触发是两种常见的程序结构设计方法,它们在设计理念、适用场景和实现方式上都有显著的区别。

以下是它们的详细对比:

• 状态机:

核心思想:将程序的行为划分为多个状态,每个状态对应一种特定的行为模式。程序的行为变化通过状态之间的转换来实现。

设计重点:定义状态集合、状态之间的转换条件以及每个状态下的行为。

适用场景:适用于具有明确状态划分和状态转换逻辑的系统,例如协议解析、用户界面状态管理、有限状态自动机等

• 事件触发:

核心思想:程序的行为由事件驱动,事件的产生和处理是程序运行的核心。事件可以是用户操作、系统消息、定时器触发等。

设计重点:定义事件类型、事件的产生机制以及事件的处理逻辑。

适用场景:适用于需要对多种事件进行响应的系统,例如图形用户界面(GUI)、实时系统、消息驱动的中间件等。

下面是主要代码介绍,重点介绍两种程序结构。

点灯子函数,主要来自官方SDK包,可查看代码后用AI分析,此处不再详细介绍。

4.2.1状态机

定义1iLED_State表示当前LED状态。当按钮动作或手机NFC靠近时,iLED_State值自加一次,主程序根据iLED_State值,切换不同的子程序。

状态机变量

bool bButtonState;  //按钮状态
bool bButtonLastState;

bool bNFC_WakeState;//NFC 状态 1-贴卡激活 0-卡拿开
bool bNFC_LastWakeState;


#define MAX_REC_COUNT      3  
#define LED_STATE_NUM      6     /*LED状态数 */
bool bStateRunOnce[LED_STATE_NUM + 1] = {false}; // 子程序只执行一次标志位
int iLED_State; //状态机当前状态

NFC回调函数

当手机贴近开发板线圈或离开时,有状态标志变化

static void nfc_callback(void * p_context, nfc_t2t_event_t event, const uint8_t * p_data, size_t data_length)
{
    (void)p_context;


    switch (event)
    {
        case NFC_T2T_EVENT_FIELD_ON:
            //bsp_board_led_on(BSP_BOARD_LED_0);//D9 绿灯
            bNFC_WakeState=true;
            break;
        case NFC_T2T_EVENT_FIELD_OFF:
           // bsp_board_led_off(BSP_BOARD_LED_0);
            bNFC_WakeState=false;
            break;
        default:
            break;
    }
}

main函数

首先初始化板卡和NFC,然后在while循环里获取按钮或NFC动作,当检测到动作后,改变LED灯的状态变量iLED_State,根据iLED_State的状态,去执行不同的子程序。

需要注意的是,对子程序要进行区分,有的是只执行一次,有的是需要循环执行。

int main(void)
{
    uint32_t  len = sizeof(m_ndef_msg_buf);
    uint32_t  err_code;


    log_init();
    bsp_board_init(BSP_INIT_LEDS|BSP_INIT_BUTTONS);


    APP_ERROR_CHECK(nrf_drv_clock_init());
    nrf_drv_clock_lfclk_request(NULL);
    APP_ERROR_CHECK(app_timer_init());


    err_code = nfc_t2t_setup(nfc_callback, NULL);
    APP_ERROR_CHECK(err_code);


    err_code = welcome_msg_encode(m_ndef_msg_buf, &len);
    APP_ERROR_CHECK(err_code);


    err_code = nfc_t2t_payload_set(m_ndef_msg_buf, len);
    APP_ERROR_CHECK(err_code);


    err_code = nfc_t2t_emulation_start();
    APP_ERROR_CHECK(err_code);
   
    while (1)
    {
    bButtonState=bsp_board_button_state_get(BSP_BOARD_BUTTON_0);//Botton0对应开发板的SW2


    if  ((bButtonState)||((bNFC_WakeState==true) && (bNFC_LastWakeState==false)))
    {
    iLED_State=iLED_State+1;
    nrf_delay_ms(300);
    }
    bNFC_LastWakeState=bNFC_WakeState;


     if (iLED_State>LED_STATE_NUM)
     {
     iLED_State=0;
     }


       switch (iLED_State) {
        case 0:
           if (bStateRunOnce[0]==false)
            {
            pwm_uninit();
            }
            update_state_run_once(iLED_State);
            bsp_board_leds_off();//全灭
            break;
        case 1:
           if (bStateRunOnce[1]==false)
            {
            pwm_uninit();
            demo1(); //轮流呼吸,只执行1次
            }
            update_state_run_once(iLED_State);
            break;
        case 2:
            if (bStateRunOnce[2]==false)
            {
            pwm_uninit();
            demo2(); //同时呼吸后闪烁,只执行1次
            }
            update_state_run_once(iLED_State);
            break;
        case 3:
             if (bStateRunOnce[3]==false)
            {
            pwm_uninit();
            demo3();//绿灯闪烁三次后,熄灭
            }
            update_state_run_once(iLED_State);
            break;
        case 4:
             if (bStateRunOnce[4]==false)
            {
             pwm_uninit();
             }
            running_lights(200);//流水灯 快
            update_state_run_once(iLED_State);
            break;  
        case 5:
             if (bStateRunOnce[5]==false)
            {
             pwm_uninit();
             }
            running_lights(500);//流水灯 慢
            update_state_run_once(iLED_State);
            break;
        case 6:
            if (bStateRunOnce[6]==false)
            {
             pwm_uninit();
             }
            bsp_board_leds_on();
            update_state_run_once(iLED_State);
            break;


        default:
       
            break;
       }      
    }
}

4.2.2事件触发

程序结构:主程序里一直执行检测事件和处理事件两个子函数,根据事件的变化,去执行不同的子程序。

定义事件枚举变量

typedef enum {
    EVENT_NONE = 0,   // 无事件
    EVENT_BUTTON_PRESS,  // 按键按下
    EVENT_NFC_WAKE,   // NFC 激活
    EVENT_MAX         // 事件类型数量
} event_type_t;

事件入队

//事件队列    入队:将一个事件添加到事件队列中。如果队列已满,则不添加事件。
void event_enqueue(event_type_t event) {
    if ((event_queue_head + 1) % EVENT_QUEUE_SIZE != event_queue_tail) {  // 检查队列是否已满
        event_queue[event_queue_head] = event;
        event_queue_head = (event_queue_head + 1) % EVENT_QUEUE_SIZE;
    }
}

事件出队

//出队:从事件队列中取出一个事件。如果队列为空,则返回  EVENT_NONE  。
event_type_t event_dequeue(void) {
    if (event_queue_head != event_queue_tail) {
        event_type_t event = event_queue[event_queue_tail];
        event_queue_tail = (event_queue_tail + 1) % EVENT_QUEUE_SIZE;
        return event;
    }
    return EVENT_NONE;  // 如果队列为空,返回 EVENT_NONE
}

检测事件

void detect_events(void) {
    bool button_state = bsp_board_button_state_get(BSP_BOARD_BUTTON_0);
    if (button_state && !bButtonLastState) {  // 检测按钮按下
        nrf_delay_ms(200);
        event_enqueue(EVENT_BUTTON_PRESS);//按钮按下
    }
    bButtonLastState = button_state;  // 更新按钮状态


    if (bNFC_WakeState && !bNFC_LastWakeState) {
        nrf_delay_ms(200);
        event_enqueue(EVENT_NFC_WAKE);//NFC贴过卡
    }
    bNFC_LastWakeState = bNFC_WakeState;
}

处理事件 

//处理事件
void process_events(void) {
    event_type_t event = event_dequeue();  // 获取第一个事件
    void (* const demos[])(void) = {demo1, demo2, demo3, demo4, demo5,demo6};
    uint8_t const demo_idx_max = (sizeof(demos) / sizeof(demos[0])) - 1;
    static uint8_t demo_idx = 0;


    while (event != EVENT_NONE) {  // 如果事件不为空
        switch (event) {
            case EVENT_BUTTON_PRESS:
               //按钮事件
            if (demo_idx < demo_idx_max)
            {
                ++demo_idx;
            }
            else
            {
                demo_idx = 0;
            }
            break;
             

            case EVENT_NFC_WAKE:
                // NFC 激活事件的处理逻辑
            if (demo_idx < demo_idx_max)
            {
                ++demo_idx;
            }
            else
            {
                demo_idx = 0;
            }
            break;  
             
            default:
                break;
        }

        // 停止当前使用的 PWM 实例
        if (m_used & USED_PWM(0)) {
            nrf_drv_pwm_uninit(&m_pwm0);
        }
        if (m_used & USED_PWM(1)) {
            nrf_drv_pwm_uninit(&m_pwm1);
        }
        if (m_used & USED_PWM(2)) {
            nrf_drv_pwm_uninit(&m_pwm2);
        }
        m_used = 0;


        // 启动新的演示
        demos[demo_idx]();


        // 获取下一个事件
        event = event_dequeue();
    }
}

主程序

主程序里只要循环检测和处理事件即可。

int main(void) 
{
    uint32_t  len = sizeof(m_ndef_msg_buf);
    uint32_t  err_code;
   
    log_init();
    bsp_board_init(BSP_INIT_LEDS | BSP_INIT_BUTTONS);
    APP_ERROR_CHECK(nrf_drv_clock_init());
    nrf_drv_clock_lfclk_request(NULL);
    APP_ERROR_CHECK(app_timer_init());


    // NFC 初始化
     err_code = nfc_t2t_setup(nfc_callback, NULL);
    APP_ERROR_CHECK(err_code);


    err_code = welcome_msg_encode(m_ndef_msg_buf, &len);
    APP_ERROR_CHECK(err_code);


    err_code = nfc_t2t_payload_set(m_ndef_msg_buf, len);
    APP_ERROR_CHECK(err_code);


    err_code = nfc_t2t_emulation_start();
    APP_ERROR_CHECK(err_code);


    demo1();
    // 主循环
    while (1) {
        detect_events();  // 检测事件
        process_events(); // 处理事件  
    }
}

4.2.3总结:

优缺点

• 状态机:

• 优点:

逻辑清晰:状态和状态转换逻辑明确,易于理解和维护。

可预测性高:状态转换是基于明确的条件,程序行为可预测。

适合复杂逻辑:对于具有复杂状态转换的系统,状态机能够很好地组织代码。

• 缺点:

状态爆炸:随着状态和转换条件的增加,状态机可能会变得复杂,难以管理。

灵活性差:状态机的逻辑相对固定,难以动态调整。

• 事件触发:

• 优点:

响应性强:能够快速响应各种事件,适合实时系统。

灵活性高:事件处理逻辑可以动态调整,适合动态变化的系统。

解耦性好:事件的产生和处理可以分离,便于模块化设计。

• 缺点:

逻辑分散:事件处理逻辑分散在多个处理函数中,难以集中管理。

复杂度高:对于复杂的事件依赖关系,代码可能会变得难以维护。

性能问题:事件队列和分发机制可能会引入额外的性能开销。

五、     效果演示

通过操作按钮或手机NFC贴近开发板线圈后离开,两种方式,实现了不同的点灯效果。可以实现依次呼吸灯、同时呼吸后闪烁、流水灯、跑马灯等多种效果。

具体演示效果无法通过图片描述,详见视频。

六、     心得体会

本次使用的硬禾学堂与得捷电子举办的Funpack第四季第1期的活动,收获很大。

本期的板卡Qorvo DWM3001CDK开发板,具有比较丰富的无线功能,可以开发NFC、蓝牙、UWB等多种应用。

除了使用SEGGER Embedded Studio进行开发之外,也支持Nordic的NCS系统,以及Arduino等多种开发平台。

在开发过程中,也可以选择FreeRTOS、Zephyr等嵌入式操作系统或使用BSP进行裸机开发。

当然,开发过程中也遇到一些问题,

比如:安装jLink软件后,无法直接支持 DWM3001CDK开发板,需要手动更新驱动。

使用SEGGER Embedded Studio进行开发时,工程的文件目录不能太深,否则可能出现编译错误。

另外,对SEGGER Embedded Studio软件的使用还不太熟悉,没有经过系统的学习。

不过,随着AI助手的普及推广,很多问题都得到了快速的解决。

感谢组织者及技术交流群的小伙伴!



软硬件
电路图
附件下载
nfc_wake_led4 ztj ok.zip
状态机例程
nfc_wake_led6 sjcf ok.zip
事件触发例程
团队介绍
老胡,自动化工程师,嵌入式爱好者。
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号