基于TinyML与XIAO nRF52840 Sense的智能心音分类系统开发实践

📅 2026/8/19 4:37:51
基于TinyML与XIAO nRF52840 Sense的智能心音分类系统开发实践
1. 从听诊器到智能节点一次TinyML驱动的硬件探索作为一名长期在嵌入式边缘计算和物联网领域折腾的开发者我最近完成了一个让我自己都感到兴奋的项目一个基于TinyML的智能听诊器原型我把它叫做VitalSense。这个项目的核心想法很简单但实现过程充满了挑战和乐趣——把传统的听诊器变成一个能实时分析心音、识别异常并且完全在本地运行的智能设备。听起来像是医疗设备公司才会做的事没错但借助现在唾手可得的开源硬件和TinyML工具链个人开发者完全有能力去探索这个交叉领域。为什么是听诊器因为它是一个经典、非侵入式的诊断工具其核心信号——心音和呼吸音本质上是声音信号。而声音信号的分析正是机器学习特别是深度学习模型的拿手好戏。传统的数字听诊器可能只是录音和回放但结合TinyML我们能让它在“听”的同时就完成初步的“诊断”比如识别正常心音、异常心音如杂音甚至区分不同的呼吸音。这对于远程医疗、基层筛查或者个人健康监测都有巨大的想象空间。这个项目不是要做一个医疗级的认证产品而是一个纯粹的技术探索和原型验证。它验证了从传感器数据采集、信号预处理、模型训练到在超低功耗微控制器上部署和推理的完整TinyML流程。我选择了Seeed Studio的XIAO nRF52840 Sense作为核心它集成了麦克风、IMU和蓝牙是绝佳的边缘AI开发板使用Edge Impulse这个强大的在线平台来构建和训练我的机器学习模型最后通过Arduino IDE将模型部署到硬件上运行。整个过程就像是在给一块小小的芯片“传授”听诊的技艺。2. 硬件选型与核心组件拆解为什么是XIAO nRF52840 Sense在启动任何嵌入式项目时硬件选型是决定项目成败和复杂度的第一步。对于VitalSense这样一个需要采集高质量音频、进行实时机器学习推理并且最好能无线通信的原型我的需求清单非常明确一个足够强大的MCU、一个内置或易于连接的高质量麦克风、低功耗特性以及蓝牙连接能力。经过一番对比Seeed Studio的XIAO nRF52840 Sense几乎是为这个场景量身定制的。2.1 主控芯片nRF52840的强大与均衡XIAO nRF52840 Sense的核心是Nordic Semiconductor的nRF52840 SoC。这颗芯片有几个关键特性让它脱颖而出。首先它基于ARM Cortex-M4F内核主频64MHz拥有1MB Flash和256KB RAM。对于TinyML应用来说RAM大小直接决定了你能运行多复杂的模型。256KB的RAM使得部署经过适当优化的神经网络模型成为可能而不是仅限于简单的决策树或线性回归。其次Cortex-M4F支持单精度浮点单元FPU这对于某些需要浮点运算的预处理或模型层是宝贵的加速器。最后nRF52840以其超低功耗和强大的蓝牙5.0/蓝牙低功耗BLE堆栈闻名这为设备未来实现无线数据传输和手机App连接铺平了道路完全符合可穿戴或便携式医疗设备的功耗要求。2.2 集成传感器PMOD接口与IMU的妙用这块开发板最吸引我的点是其高度集成。它直接板载了一个数字麦克风MP34DT05这省去了额外连接麦克风模块的麻烦保证了信号路径的简洁和稳定。这个麦克风性能足以捕捉心音的主要频率范围通常20-600Hz。除了麦克风板载的6轴IMULSM6DS3TR-C在最初看来似乎与听诊无关但实际上它提供了额外的可能性。例如我们可以用加速度计来检测听诊器是否被正确放置在胸壁上或者识别使用者的手势如敲击切换模式甚至可以通过姿态数据对信号进行补偿尽管在这个原型阶段我主要聚焦于音频。板载的PDM转I2S芯片STM32MP157C将麦克风的脉冲密度调制PDM数据转换为更通用的I2S格式方便MCU读取。此外它还有一个板载的Flash芯片用于存储模型或数据以及一个兼容Seeed Studio Grove生态系统的接口。这个Grove接口极大地扩展了可能性未来可以轻松连接心率、血氧等更多传感器构建一个多参数的生命体征监测平台。2.3 对比其他选项为何不选ESP32或Arduino Uno你可能会问ESP32系列也有麦克风开发板且更便宜为什么不用ESP32确实强大双核、Wi-Fi/蓝牙生态丰富。但对于纯粹的、对实时性要求高的音频信号处理TinyML推理nRF52840的架构和内存管理有时更“清爽”。而且在超低功耗场景下nRF52840通常表现更优。至于经典的Arduino Uno其ATmega328P的2KB RAM和16MHz主频完全无法承载任何有意义的机器学习模型它更适合作为逻辑控制器而非边缘AI节点。选择XIAO nRF52840 Sense意味着我可以在一个邮票大小的硬件上获得一个功能相对完备的TinyML开发平台将更多精力集中在算法和应用逻辑上而不是繁琐的硬件调试。3. 数据采集的挑战如何“听”到清晰的心音有了硬件下一步就是让设备“听到”声音并且是听到我们想要的声音——心音。这远非接上麦克风那么简单是整个项目中最具挑战性的环节之一。环境噪音、身体移动的摩擦音、电路底噪都会严重干扰脆弱的心音信号。3.1 从模拟世界到数字信号PDM麦克风与采样率板载的MP34DT05是一个PDM麦克风。PDM是一种单比特流的数据格式需要通过芯片内置的PDM转I2S模块或外部解码器转换为多比特的PCM数据。幸运的是XIAO板子已经帮我们做好了转换我们通过I2S接口直接读取到的就是PCM数据。这里第一个关键决策是采样率。根据奈奎斯特采样定理要无失真地还原信号采样率必须至少是信号最高频率的两倍。心音的主要能量集中在600Hz以下但为了捕捉一些高频的杂音成分通常需要更高的带宽。我最终选择了16000Hz的采样率。这提供了8kHz的带宽足以覆盖心音的主要频谱同时数据量又不会过大给后续的实时处理留出计算余量。在Arduino IDE中使用I2S库进行配置时需要特别注意设置正确的I2S时钟和通道模式。一个常见的坑是通道对齐问题如果设置不当读取到的数据会是全零或杂乱无章。我的配置代码如下其中I2S_MODE_PDM模式是专门用于对接板载PDM转换器的#include I2S.h void setup() { // 初始化I2S以从板载麦克风接收数据 if (!I2S.begin(I2S_PHILIPS_MODE, 16000, 32)) { Serial.println(Failed to initialize I2S!); while (1); // 停在这里 } }3.2 信号预处理滤波与放大原始的心音信号非常微弱信噪比低。直接从I2S读出的数据几乎被噪声淹没。因此必须在数字域进行预处理。高通滤波是第一步用于去除人体移动、呼吸产生的低频噪声通常低于20Hz。我使用了一个一阶IIR高通滤波器截止频率设在20Hz。带通滤波紧随其后将频带限制在20-600Hz的心音主要范围内进一步抑制高频环境噪声。这些滤波操作可以在采集时实时进行也可以在将数据发送到Edge Impulse训练时作为DSP预处理块的一部分。注意滤波器的设计需要权衡。过于陡峭的滤波器高阶会引入相位失真和更大的计算开销。对于实时系统通常选择计算效率高的二阶或一阶滤波器。我最初尝试了四阶巴特沃斯滤波器虽然频响曲线漂亮但在nRF52840上实时运行导致推理帧率下降后来换成了二阶滤波器在性能和效果上取得了更好的平衡。另一个重要环节是增益控制。心音的幅度因人、因听诊位置而异。我实现了一个简单的自动增益控制AGC算法计算一个时间窗口内信号的平均绝对值如果低于阈值则按比例放大整个窗口的信号。这能保证输入到机器学习模型的数据幅度相对稳定有利于模型收敛和性能稳定。3.3 构建数据集从开源数据到自制采集训练一个模型需要大量高质量的数据。对于心音分类幸运的是有一些开源数据集可用如PhysioNet的CirCor DigiScope Phonocardiogram数据集。我下载了其中部分正常和异常心音的数据将其重采样到16kHz并切割成2-3秒的片段作为我的初始训练集。然而开源数据集的录音环境和设备与我的XIAO板差异巨大。为了提升模型在实际硬件上的表现制作自己的数据集至关重要。我用XIAO板录制了自己和几位志愿者的心音在静室环境中。这个过程需要耐心找准听诊位置二尖瓣区、主动脉瓣区等保持探头稳定尽量减少呼吸和移动。我将录制的WAV文件导入Edge Impulse与开源数据混合打上“正常”、“异常”模拟某些杂音的标签。一个高质量的、贴合实际硬件的数据集是模型成功的一半。4. 在Edge Impulse中构建心音分类模型Edge Impulse是一个将机器学习 democratize 的神器。它提供了一个完整的云端工作流让开发者无需深厚的ML背景也能完成数据标注、特征提取、模型训练、测试和部署。对于VitalSense项目它是我将想法快速转化为可运行模型的核心工具。4.1 创建Impulse设计处理流水线在Edge Impulse中一个“Impulse”定义了从原始数据到推理结果的整体处理流水线。我的设计如下输入块时间序列数据长度1000毫秒即1秒的音频16000个采样点。处理块我选择了MFCC梅尔频率倒谱系数。MFCC是语音和音频识别领域的经典特征它模拟人耳听觉特性能很好地表征声音的频谱形状。相比于原始的波形或简单的FFT频谱MFCC特征更紧凑且对与内容无关的变量如音量更鲁棒。Edge Impulse会自动计算一段音频的MFCC特征图例如13个系数乘上多个时间帧。学习块我尝试了两种神经网络架构。首先是全连接神经网络DNN它简单快速作为基线。然后是卷积神经网络CNN。CNN特别擅长从图像或像MFCC这样的二维特征图中提取空间局部模式。将MFCC特征图视为一个单通道的图像CNN可以学习到心音中不同频率成分随时间变化的模式这对于识别特定的杂音如收缩期杂音非常有效。4.2 特征生成与模型训练上传数据并完成标注后在“MFCC”处理块点击“生成特征”。这一步会遍历所有数据样本计算它们的MFCC并投影到一个三维空间进行可视化。你可以直观地看到不同类别正常/异常的数据点是否能够被区分开。如果它们混在一起说明当前特征可能不足以区分需要重新检查数据质量或考虑其他特征如频谱图、过零率等。接下来是模型训练。我使用CNN作为主要模型。关键参数包括网络结构我采用了两个卷积层配合池化层提取特征后接一个全连接层进行分类。卷积核大小设置为3x3以捕捉MFCC图中的局部相关性。训练周期Epochs从30开始观察损失曲线避免过拟合。最终使用了50个周期配合早停Early Stopping回调。学习率使用自适应学习率调度器初始值设为0.001。数据增强为了增加模型的鲁棒性我启用了音频数据增强如添加轻微的背景噪声、随机时间偏移和幅度缩放。这能模拟真实世界中不完美的录音环境。训练完成后Edge Impulse会给出模型在测试集上的准确率、混淆矩阵等指标。我的CNN模型在测试集上达到了约92%的准确率。更重要的是我要查看“模型验证”页面这里会显示模型在未见过的数据验证集上的表现这是评估模型泛化能力的关键。4.3 模型测试与性能分析Edge Impulse提供了一个“实时分类”测试功能你可以通过电脑麦克风录制新的心音或上传文件进行实时分类。这是一个非常直观的验证方式。我播放了一些开源数据集中未用于训练的心音片段模型大部分能正确分类。然而对于嵌入式部署光有准确率不够我们更关心模型大小和推理延迟。Edge Impulse的“部署”页面提供了详细的性能分析。我的CNN模型被量化Quantization为int8精度后大小约为45KB完全能放入nRF52840的1MB Flash中。RAM占用Tensor Arena约为60KB也在256KB的预算内。在Edge Impulse的模拟器中单次推理时间约为120ms这对于1秒的音频窗口来说是完全可以接受的实时性能。实操心得不要盲目追求复杂的模型。我最初尝试了一个更深的CNN准确率提升了1%但模型大小和推理时间翻倍。在资源受限的边缘设备上这种边际效益的提升往往不值得。TinyML的精髓是在性能、精度和资源消耗之间找到最佳平衡点。5. 从云端到边缘模型部署与Arduino集成模型在云端训练达标后下一步就是将它“塞进”小小的XIAO nRF52840 Sense里并编写固件让它活起来。Edge Impulse为这一步提供了无缝的支持。5.1 生成Arduino库与项目集成在Edge Impulse项目的“部署”页面选择“Arduino库”作为输出格式。平台会生成一个包含了你的完整Impulse预处理代码模型的.zip文件以及一个示例Arduino项目.ino文件。下载这个库在Arduino IDE中通过“项目” - “加载库” - “添加.ZIP库…”将其安装。接下来创建一个新的Arduino项目。关键步骤包括包含必要的头文件主要是VitalSense_inferencing.h你的项目名这个头文件定义了模型和预处理函数。初始化I2S麦克风如前面所述配置I2S以正确的采样率读取音频数据。实现数据采集循环你需要一个缓冲区来存储一帧音频数据例如对应1000ms的16000个采样点。通过I2S连续读取数据填充这个缓冲区。调用推理引擎当缓冲区满后将其传递给Edge Impulse生成的推理函数run_classifier()。这个函数内部会执行MFCC特征提取和神经网络推理。处理结果run_classifier()函数返回一个结构体包含每个类别的得分概率。根据最高得分来判断当前音频片段属于“正常”还是“异常”。示例代码框架如下#include I2S.h #include VitalSense_inferencing.h // 你的Edge Impulse项目库 static signed short sampleBuffer[EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE]; // 音频缓冲区 static bool debug_nn false; // 设置为true以查看详细的推理信息 void setup() { Serial.begin(115200); // 初始化I2S... I2S.begin(I2S_PHILIPS_MODE, 16000, 32); } void loop() { // 1. 填充音频缓冲区 int bytesRead readAudioBuffer(sampleBuffer, EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE); if (bytesRead ! EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE * 2) { // *2因为每个采样点是16位2字节 Serial.println(Failed to read enough audio data); return; } // 2. 将原始缓冲区转换为Edge Impulse所需的信号结构 signal_t signal; numpy::int16_to_float(sampleBuffer, signal, EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE); // 3. 运行推理 ei_impulse_result_t result {0}; EI_IMPULSE_ERROR err run_classifier(signal, result, debug_nn); if (err ! EI_IMPULSE_OK) { Serial.printf(ERR: Failed to run classifier (%d)\n, err); return; } // 4. 输出结果 Serial.printf(Prediction: ); for (size_t ix 0; ix EI_CLASSIFIER_LABEL_COUNT; ix) { Serial.printf(%s: %.5f, result.classification[ix].label, result.classification[ix].value); if (ix ! EI_CLASSIFIER_LABEL_COUNT - 1) { Serial.printf(, ); } } Serial.println(); // 5. 根据结果做出反应例如点亮不同颜色的LED // if (result.classification[0].value 0.8) { // 正常 // digitalWrite(LED_G, HIGH); // } else if (result.classification[1].value 0.8) { // 异常 // digitalWrite(LED_R, HIGH); // } }5.2 优化推理性能与内存管理直接将生成的库拿来用可能会发现推理速度比Edge Impulse模拟的慢或者内存不足。这就需要一些优化技巧。启用EIMEdge Impulse Manager在ei_classifier_config.h文件中确保EI_CLASSIFIER_ALLOCATION_STATIC被定义。这意味着所有中间张量tensor的内存都在编译时静态分配而不是在堆上动态分配这能提高速度和避免内存碎片。调整Tensor Arena大小这是推理时用于存储中间计算结果的内存池。在ei_classifier_config.h中EI_CLASSIFIER_TFLITE_ARENA_SIZE定义了其大小。如果推理时崩溃可能是这个区域不够大。你可以根据Edge Impulse部署页面给出的“最大内存使用量”建议值来调整并留出一些余量。降低采样率或帧长如果性能仍然吃紧可以考虑降低采样率如从16kHz降到8kHz或缩短推理的帧长度如从1000ms降到500ms。但这会牺牲一些信息量需要重新训练模型。使用DSP加速nRF52840的CMSIS-DSP库提供了优化的数学函数。确保你的Arduino项目包含了这个库通常通过#include arm_math.h并且Edge Impulse生成的代码中已经使用了这些优化函数通常默认是。5.3 添加输出与交互LED、蜂鸣器与蓝牙一个只会默默推理的设备是枯燥的。我添加了简单的输出和交互。视觉反馈通过板载的RGB LEDWS2812B我用绿色表示“正常心音”检测红色表示“异常心音”检测蓝色表示设备正在采集/处理中。听觉反馈连接了一个小型有源蜂鸣器。当连续检测到多次“异常”时蜂鸣器会发出间歇性提示音注意音量要小避免干扰。蓝牙串口BLE UART这是将设备升级为“智能”节点的关键一步。我使用了Arduino的ArduinoBLE库创建了一个BLE服务包含一个用于传输分类结果和原始波形数据可选的特征值。这样我就可以用手机App例如nRF Connect或自制的App实时接收和显示心音分类结果甚至绘制简单的波形图。添加BLE后功耗管理变得更重要。我实现了简单的休眠机制当一段时间没有检测到有效声音通过信号能量判断时MCU进入低功耗模式仅保留BLE广播由手机App连接来唤醒进行详细测量。6. 实测、调试与未来展望将代码烧录到XIAO板后真正的挑战才开始。实验室环境下的成功不代表在相对复杂的真实场景下也能工作。6.1 现场测试与遇到的典型问题我拿着原型设备在相对安静的室内进行测试。将板载麦克风区域轻轻贴在胸口模拟听诊器头。一开始效果并不理想问题1误触发频繁。环境中的突然声响如咳嗽、关门会被当作“心音”并触发推理结果自然是混乱的。解决我增加了一个能量门限Energy Threshold检测。只有当一个音频帧的能量超过预设阈值通过实验校准才将其送入模型推理。这滤除了大部分无意义的背景噪声。问题2分类结果不稳定。即使是连续的心跳模型的输出也在“正常”和“异常”之间跳动。解决我引入了滑动窗口与投票机制。不再对每一帧独立判断而是维护一个最近N帧如5帧的结果队列。最终输出是这N帧中出现次数最多的类别。这平滑了输出提高了稳定性。问题3不同听诊位置信号差异大。主动脉瓣区的声音和二尖瓣区的声音幅度和频谱都有差异。解决这更多是数据层面的问题。我回到Edge Impulse补充了从不同位置采集的数据即使都标记为“正常”让模型学习到正常心音本身的多样性。同时强化了AGC自动增益控制模块使输入信号的幅度范围更归一化。6.2 性能瓶颈分析与优化通过串口打印推理时间我发现平均推理时间在150ms左右略高于模拟值。使用Arduino的micros()函数进行更细粒度的 profiling 后发现MFCC特征计算占据了超过70%的时间。这是典型的DSP运算瓶颈。优化措施检查编译器优化等级在Arduino IDE的“工具”菜单中将“优化”等级从“调试”改为“优化速度-Os”或“优化更多-O3”这能带来显著的性能提升。探索CMSIS-DSP的利用确保MFCC计算中使用的FFT、向量点乘等操作都链接到了arm_math.h中的优化函数。有时需要检查Edge Impulse生成的DSP代码是否确实调用了这些硬件加速函数。降低特征维度回到Edge Impulse尝试减少MFCC系数的数量例如从13个降到10个或者减少FFT的长度。这需要重新训练模型并评估精度损失是否在可接受范围内。经过一轮优化单次推理时间稳定在了100ms以内满足了实时性的基本要求。6.3 项目的边界与未来可能的延伸VitalSense目前只是一个原型它清晰地展示了TinyML在便携式医疗传感设备上的可行性。但它距离一个真正的“智能听诊器”还有很长的路也存在着明确的边界非医疗设备它绝不能用于任何实际的医疗诊断。其准确率、鲁棒性远未达到临床要求。它只是一个技术演示和教育工具。算法局限性当前模型只能做简单的二分类正常/异常。真实的心音异常有数十种需要更精细的分类如收缩期杂音、舒张期杂音、心包摩擦音等这需要大量专业标注的数据和更复杂的模型。硬件局限性板载麦克风并非医用级频率响应和灵敏度可能不适合捕捉所有心音成分。专业的电子听诊器通常使用特殊的接触式麦克风或压电传感器。尽管有这些边界这个项目为许多有趣的延伸打开了大门多模态传感结合板载的IMU可以检测听诊器的放置压力和角度甚至实现自动定位瓣膜听诊区。更复杂的模型可以探索更高效的神经网络架构如MobileNetV1/V2的1D版本或专为音频设计的模型如TCNN Temporal Convolutional Neural Networks。云端协同设备端进行实时筛查和异常检测当发现疑似异常时通过BLE将高保真度的音频片段上传到手机再同步到云端进行更深入的分析如由更复杂的AI模型或医生复核实现“云边协同”。产品化探索设计3D打印外壳集成电池管理电路开发专用的手机App形成一个完整的用户体验闭环。通过VitalSense这个项目我深刻体会到TinyML正在极大地降低智能硬件创新的门槛。它让每一个嵌入式开发者都有能力为物理世界注入“智能”。从心脏的跳动声中提取信息只是无数可能性中的一个。当你掌握了数据采集、预处理、模型训练和部署这套组合拳后你会发现让设备“看见”、“听见”并“理解”周围的世界不再是大公司的专利而是你我可以亲手实现的创造。