这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » DIY与开源设计 » 电子DIY » 【NRF54Lxx专题】5、EdgeAI语音控制

共1条 1/1 1 跳转至

【NRF54Lxx专题】5、EdgeAI语音控制

高工
2026-06-17 22:32:36     打赏

【前言】

Axon NPU 是 Nordic 自研片上集成 AI 硬件加速器,仅搭载在 nRF54LM20B(开发板 nRF54LM20-DK 配套芯片),为超低功耗无线 IoT 设备提供硬件神经网络推理加速;区别于仅靠 Cortex-M33 跑 Neuton 小模型,Axon 专门承载TensorFlow Lite Micro中高负载音频、图像、多通道传感器 AI 任务Nordic Semiconductor。

硬件基础参数

运行主频:128 MHz,与主 M33、RISC-V 协处理器同频

架构来源:收购 Atlazo 获得 Axon 专用神经网络加速内核,原生适配 8bit 量化 TFLite 模型

配套存储:2MB Flash + 512KB RAM,满足音频 / 唤醒词 / 图像模型缓存需求

供电体系:和 SoC 共用超低功耗电源域,支持深度休眠断电,无 AI 任务时完全关闭 NPU 零功耗。

image.png

【硬件】

1、NRF54LM20-DK

2、PDM MEMS Microphone

【实现步骤】

1、安装ncs的edgi-ai包

# 克隆对应版本仓库
git clone -b v2.0.0 https://github.com/nrfconnect/sdk-edge-ai edge-ai
# 切换manifest指向edge-ai
west config manifest.path edge-ai
# 更新所有依赖
west update

2、复制一份edgi工程到D:\ncs\v3.3.0\applications\ww_kws

编译下后打开串终端可以实现语音唤醒:

Waiting for wakeword  //对着麦克风喊出:Okay Nordic
Wakeword detected   //唤醒成功

3、进入https://ai.lab.nordicsemi.com/ 登录后下载已有模型:

image.png

4、下载后解压,将文件放到src目录下面:

image.png

5、整理后好工程目录如下:

image.png

6、修改prj.conf

# nRF Edge AI
CONFIG_NRF_EDGEAI=y

# nRF Edge AI dependencies
CONFIG_NRF_AXON=y
CONFIG_NEWLIB_LIBC=y
CONFIG_FPU=y

# Size of interlayer buffer to fit model
CONFIG_NRF_AXON_INTERLAYER_BUFFER_SIZE=6657

# Size of psum buffer to fit model
CONFIG_NRF_AXON_PSUM_BUFFER_SIZE=0

# Enable sensor, button and LED libraries
CONFIG_GPIO=y
CONFIG_AUDIO=y
CONFIG_AUDIO_DMIC=y

# Enable logging and assert
CONFIG_LOG=y
CONFIG_CONSOLE=y
CONFIG_SERIAL=y
CONFIG_ASSERT=y

CONFIG_UART_ASYNC_API=y

# Wakeword detection sensitivity
CONFIG_WW_PROBABILITY_THRESHOLD=700
CONFIG_WW_HISTORY_SIZE=15
CONFIG_WW_COUNT_THRESHOLD=8

7、main.c内容如下:

#include <stdio.h>
/*
 * Copyright (c) 2026 Nordic Semiconductor ASA
 *
 * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
 */

#include <string.h>
#include <stddef.h>
#include <stdint.h>

#include <zephyr/audio/dmic.h>
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/logging/log.h>

#include "control_output.h"
#include "dmic.h"
#include "kws/kws.h"
#include "leds.h"
#include "ww/wakeword.h"

LOG_MODULE_REGISTER(main);

#define DMIC_READ_TIMEOUT 100

static const struct device *const dmic_dev = DEVICE_DT_GET(DT_NODELABEL(dmic_dev));

static int ww_loop(void)
{
    int err;
    void *audio_buffer;
    size_t audio_buffer_size;
    bool ww_detected;

    ww_reset();

    print_control_output((struct control_message){CONTROL_MESSAGE_WAITING_WW});

    while (true) {
        err = dmic_read(dmic_dev, 0, &audio_buffer, &audio_buffer_size, DMIC_READ_TIMEOUT);
        if (err < 0) {
            print_control_output((struct control_message){CONTROL_MESSAGE_WW_DETECTED});
            print_control_output((struct control_message){.type = CONTROL_MESSAGE_ERROR, .name = "DMIC read"});
            return err;
        }

        err = ww_process(audio_buffer, DMIC_SAMPLES_IN_BLOCK, &ww_detected);
        if (err == -EBUSY) {
            /* More data is needed. */
            continue;
        } else if (err < 0) {
            print_control_output((struct control_message){.type = CONTROL_MESSAGE_ERROR, .name = "WW proc"});
            return err;
        }

        if (ww_detected) {
            print_control_output((struct control_message){CONTROL_MESSAGE_WW_DETECTED});
            if (IS_ENABLED(CONFIG_APP_MODE_WW_ONLY)) {
                leds_blink_led0();
            } else {
                return 0;
            }
        }
    }
}

static int kws_loop(void)
{
    int err;
    void *audio_buffer;
    size_t audio_buffer_size;
    struct kws_prediction prediction;

    uint32_t spotting_timeout = k_uptime_get_32() + CONFIG_KWS_PERIOD_MS;

    kws_reset();

    print_control_output((struct control_message){.type = CONTROL_MESSAGE_WAITING_KW});

    while (IS_ENABLED(CONFIG_APP_MODE_KWS_ONLY) || spotting_timeout > k_uptime_get_32()) {
        err = dmic_read(dmic_dev, 0, &audio_buffer, &audio_buffer_size, DMIC_READ_TIMEOUT);
        if (err < 0) {
            print_control_output((struct control_message){.type = CONTROL_MESSAGE_ERROR, .name = "DMIC read"});
            return err;
        }

        err = kws_process(audio_buffer, DMIC_SAMPLES_IN_BLOCK, &prediction);
        if (err == -EBUSY) {
            /* More data is needed. */
            continue;
        } else if (err) {
            print_control_output((struct control_message){.type = CONTROL_MESSAGE_ERROR, .name = "KWS proc"});
            return err;
        }

        if (prediction.valid) {
            spotting_timeout = k_uptime_get_32() + CONFIG_KWS_PERIOD_MS;
            print_control_output(
                (struct control_message){.type = CONTROL_MESSAGE_KW_SPOTTED,
                             .kw_class = prediction.class,
                             .name = prediction.name});

            if (strcmp(prediction.name, "On") == 0) {
                leds_on_led1();
                printk("LED1 ON\n");
            } else if (strcmp(prediction.name, "Off") == 0) {
                leds_off_led1();
                printk("LED1 OFF\n");
            } else {
                leds_blink_led1();
            }
        }
    }

    print_control_output((struct control_message){.type = CONTROL_MESSAGE_TIMEOUT_KWS});

    return 0;
}

int main(void)
{
    int err;

    err = dmic_init();
    if (err) {
        return err;
    }

    err = leds_init();
    if (err) {
        return err;
    }

    err = control_output_init();
    if (err) {
        return err;
    }

    err = ww_init();
    if (err) {
        return err;
    }

    err = kws_init();
    if (err) {
        return err;
    }

    LOG_INF("Initialization completed, check output on VCOM0");

    err = dmic_trigger(dmic_dev, DMIC_TRIGGER_START);
    if (err < 0) {
        LOG_ERR("Failed to start DMIC (err %d)", err);
        return err;
    }

    while (true) {
        if (IS_ENABLED(CONFIG_APP_MODE_WW_GATED_KWS) ||
            IS_ENABLED(CONFIG_APP_MODE_WW_ONLY)) {
            err = ww_loop();
            if (err) {
                return err;
            }
        }

        if (IS_ENABLED(CONFIG_APP_MODE_WW_GATED_KWS)) {
            leds_on_led0();
        }

        if (IS_ENABLED(CONFIG_APP_MODE_WW_GATED_KWS) ||
            IS_ENABLED(CONFIG_APP_MODE_KWS_ONLY)) {
            err = kws_loop();
            if (err) {
                return err;
            }
        }

        if (IS_ENABLED(CONFIG_APP_MODE_WW_GATED_KWS)) {
            leds_off_led0();
        }
    }

    return 0;
}

识别的流程如下:

image.png

平时只跑唤醒词,唤醒后才开 3 秒窗口跑关键词

8、灵敏度的调节

可以在kws.c中来调节识别阈值,比如从 0.9/22(太严)改成 0.5/5(更灵敏),加 300ms 防抖 SKIP_DETECTIONS_COUNT=30。

【实验的效果】

打开串口终端依次说出唤醒词,然后发出指令,打印效果如下:

Waiting for wakeword
Wakeword detected
Waiting for keywords
Keyword spotted: On
Keyword spotted: Up
Keyword spotted: Up
Keyword spotted: On
Keyword spotted: Up
Keyword spotted: On
Keyword spotted: Stop
Keyword spotted: On
Keyword spotting window timeout
Waiting for wakeword

相应的开发板的LED也可以通on与off来实现开关控制。




关键词: NRF54Lxx     语音     edgiAI    

共1条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]