【前言】
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 零功耗。

【硬件】
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/ 登录后下载已有模型:

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

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

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;
}识别的流程如下:

平时只跑唤醒词,唤醒后才开 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来实现开关控制。
我要赚赏金
