基于XIAO BLE与Edge Impulse的紫外线风险监测智能手表开发全流程
1. 项目概述与核心价值作为一名长期混迹于硬件创客圈和嵌入式开发领域的“老鸟”我经手过不少智能穿戴和物联网项目。今天想和大家深入聊聊一个我个人觉得特别有“温度”的项目——一个能实时监测紫外线风险、并给出预警的智能手表。这玩意儿不是什么商业级产品而是一个基于XIAO BLE nRF52840开发板融合了边缘人工智能Edge AI和蓝牙低功耗BLE技术的原型。它的核心目标很简单让你在户外活动时能直观地了解当前的紫外线强度对你可能造成的风险等级是“可耐受”、“有风险”还是“危险”从而提前采取防护措施。为什么说它有“温度”因为紫外线伤害是个“沉默的杀手”。短期看可能只是晒伤长期累积却可能引发皮肤老化、免疫抑制甚至皮肤癌。传统的天气预报APP给的紫外线指数UVI是个宏观区域值而每个人所处的具体微环境比如高楼阴影下、水边反射差异巨大。这个项目的价值就在于它通过本地传感器获取你此时此刻、此情此景下的精确环境数据紫外线指数、温度、气压、海拔并利用一个部署在手表本地的轻量化AI模型进行实时分析直接在设备端做出判断再通过BLE将结果推送到你手机上的专属APP。整个过程无需连接云端响应速度快通常30秒内更新一次功耗极低而且你的所有敏感环境数据都不会离开你的设备隐私性得到了最大程度的保障。简单来说这就是嵌入式机器学习Embedded ML或边缘AI的一个典型落地案例把智能从“云”搬到了“端”。对于开发者而言这个项目涵盖了从传感器数据采集、嵌入式系统编程、机器学习模型训练与部署到移动端应用开发的完整链路技术栈非常全面是个绝佳的学习和实践样板。对于终端用户它则是一个个性化、高隐私保护的健康监测小助手。2. 硬件选型与系统架构解析2.1 核心控制器为什么是XIAO BLE nRF52840在这个项目中主控芯片的选择是成败的关键。我最终锁定了Seeed Studio的XIAO BLEnRF52840主要基于以下几点考量性能与能效的平衡nRF52840是Nordic Semiconductor的一款经典蓝牙5.0/低功耗蓝牙SoC内置Cortex-M4F内核主频64MHz拥有1MB Flash和256KB RAM。这个配置对于运行轻量级神经网络模型和复杂的BLE协议栈游刃有余同时其低功耗特性非常适合电池供电的可穿戴设备。极致的尺寸XIAO BLE的尺寸仅有21 x 17.5mm比一枚硬币还小这为将其集成到手表形态的设备中扫清了物理障碍。小巧的体积是穿戴设备舒适性的前提。丰富的接口与生态它提供了足够的GPIO、ADC、I2C等接口来连接各类传感器。更重要的是其Arduino兼容性意味着有海量的库和社区支持能极大降低开发门槛。配套的**XIAO Expansion Board扩展板**更是“神器”它集成了OLED屏幕、MicroSD卡槽、按键和锂电池充电管理让原型搭建变得异常简洁。实操心得在选择类似项目的主控时不要只看重绝对性能。对于边缘AI设备需要综合评估计算能力是否支持浮点运算、内存大小、功耗、尺寸、开发便利性和成本。nRF52840系列在这个平衡点上做得非常好是很多消费级蓝牙穿戴设备的“芯脏”。2.2 传感器套件环境数据的“五官”为了评估紫外线风险我们需要多维度环境数据作为AI模型的输入特征Grove - UV传感器这是一个模拟输出传感器用于测量环境中的紫外线强度。它输出的电压值与紫外线强度成正比。需要注意的是它给出的不是标准的紫外线指数UVI而是一个模拟量我们需要通过一个经验公式将其换算成近似的UVI值。公式大致为UVI ≈ (传感器读数平均值 * 1000 / 4.3 - 83) / 21。虽然存在误差但对于风险等级分类这个任务来说其变化趋势和相对大小已经足够。BMP180气压/温度/海拔传感器这是一个通过I2C通信的数字传感器。它提供精确的温度和气压读数并能根据气压计算出近似海拔。温度直接影响人体对紫外线的敏感度和体感而气压和海拔则是重要的环境校正因子例如高海拔地区紫外线更强。BMP180精度高、体积小、功耗低是环境监测项目的常客。其他外围设备SSD1306 OLED显示屏128x64集成在扩展板上用于本地显示传感器读数、风险等级和设备状态。选择OLED是因为其自发光、高对比度、可视角度好且功耗比LCD更低。MicroSD卡模块用于离线存储采集的原始数据这是后续构建训练数据集的关键。扩展板内置此模块省去了额外接线的麻烦。RGB LED用于通过颜色绿/黄/红直观指示当前风险等级提供一种无需看屏幕的快速状态反馈。按键用于在数据采集阶段手动标注数据类别风险等级。整个系统的架构非常清晰传感器采集数据 - XIAO BLE主控进行预处理和模型推理 - 结果通过OLED和RGB LED本地显示同时通过BLE广播 - 手机APP接收并展示。数据流和决策流完全在本地闭环。3. 数据采集与数据集构建实战3.1 固件开发稳定可靠的数据“记录员”数据是AI模型的“粮食”质量决定模型的上限。我编写了第一个核心固件BLE_smartwatch_data_collect.ino其核心任务就是稳定、准确地采集并存储数据。代码核心逻辑拆解初始化与自检程序启动后首先初始化I2C用于BMP180和OLED、SPI用于SD卡并检测BMP180传感器和SD卡是否连接正常。任何一环失败都会在OLED上显示错误信息并将RGB LED设为红色。这个“开机自检”机制对于野外使用的设备至关重要能快速定位硬件问题。数据采集循环紫外线数据通过get_UV_radiation()函数连续读取1024次ADC值并求平均以此消除随机噪声去抖动然后使用前述公式估算UVI。环境数据通过collect_BMP180_data()函数读取温度、气压并计算标准气压下的海拔。这里有个细节bmp.readAltitude()默认使用101325帕斯卡作为海平面标准气压。为了更精确可以传入实时测量的海平面气压值bmp.readSealevelPressure()但这需要网络获取当地气压数据对于离线设备使用标准值是可接受的简化。数据标注与存储这是构建监督学习数据集的关键。我通过扩展板上的按键实现了一个简单的交互逻辑短按在“可耐受0”、“有风险1”、“危险2”三个风险等级间循环选择。选中的等级会实时显示在OLED上。长按约2秒将当前时刻的传感器读数UVI、温度、气压、海拔以及手动选择的等级标签作为一条完整的数据记录追加写入到SD卡中名为UV_DATA.csv的CSV文件里。避坑指南在户外采集数据时手动标注的准确性是最大挑战。我的经验是参考世界卫生组织WHO和环保署EPA的UVI风险等级表并结合当时的体感阳光灼热感、皮肤反应和云层情况综合判断。最好在一天中的不同时段清晨、正午、傍晚、不同天气晴、多云和不同地点开阔地、树荫下进行采集以增加数据集的多样性和代表性。我前后用了20多天积累了数百条有效数据。3.2 数据预处理为模型训练做准备从SD卡导出的CSV数据不能直接喂给Edge Impulse。因为Edge Impulse的“CSV上传”功能要求数据是时间序列格式或者每个样本是一个独立的CSV文件且文件名要包含标签。为此我写了一个Python脚本process_dataset.py进行数据预处理和格式化数据归一化缩放这是机器学习中非常关键的一步。不同特征如UVI值在0-15气压在90000-110000帕的量纲和数值范围差异巨大直接输入模型会导致收敛慢或精度低。我将每个特征缩放到0-1之间scaled_uv_index uv_index / 10(假设UVI最大为10留有裕量)scaled_temperature temperature / 100scaled_pressure pressure / 100000scaled_altitude altitude / 100这个缩放系数需要根据你数据实际的最大最小值进行调整这里使用的是经验值。样本文件生成脚本读取原始的UV_DATA.csv根据risk_level字段0,1,2将数据按类别拆分。对于每一条数据记录生成一个独立的CSV文件文件名格式为{类别名}.sample_{序号}.csv例如Tolerable.sample_1.csv。每个文件的内容就是一行4个缩放后的特征值。这样Edge Impulse就能自动从文件名中提取标签Tolerable,Risky,Perilous。# 示例代码片段数据缩放与样本生成 import pandas as pd class ProcessDataset: def __init__(self, csv_path): self.df pd.read_csv(csv_path) self.class_names [Tolerable, Risky, Perilous] def scale_data_elements(self): self.df[scaled_uv_index] self.df[uv_index] / 10 self.df[scaled_temperature] self.df[temperature] / 100 self.df[scaled_pressure] self.df[pressure] / 100000 self.df[scaled_altitude] self.df[altitude] / 100 def split_dataset_by_labels(self, class_num): # ... 遍历df为每个属于class_num的数据行创建独立CSV文件 ... pass # 使用 dataset ProcessDataset(UV_DATA.csv) dataset.scale_data_elements() for i in range(3): dataset.split_dataset_by_labels(i)4. 边缘AI模型训练与部署从数据到智能4.1 Edge Impulse平台实战Edge Impulse是一个对开发者极其友好的在线机器学习平台它抽象了模型训练和部署的复杂性让我们能专注于数据和业务逻辑。创建项目与上传数据在Edge Impulse上新建一个项目选择“分类”任务。在“数据采集”页面使用“上传已有数据”功能分别上传我们预处理好的训练集和测试集样本文件。关键一步是在标签选项中选择“从文件名推断”平台会自动根据我们之前设定的文件名如Tolerable.sample_1.csv来打标签非常方便。设计脉冲Impulse脉冲是Edge Impulse的工作流。我们的数据是4个数值特征不是图像或音频时间序列因此选择“原始数据”作为处理块它不对数据做额外变换直接传递。学习块选择“分类Keras”。生成特征与模型训练在“脉冲设计”保存后进入“原始数据”页面直接保存参数因为我们不需要特别的窗长或滤波。然后点击“生成特征”这一步会为我们的数值数据创建特征图虽然简单但流程需要。接下来进入“NN分类器”页面。对于这种小规模数值分类问题我直接使用了默认的神经网络架构一个包含多个Dense层的全连接网络。点击“开始训练”平台会自动将数据分为训练集和验证集进行训练。模型测试与验证训练完成后平台会给出在验证集上的准确率。这里我遇到了一个典型问题准确率显示100%。这通常不是好事很可能意味着过拟合——模型完美“记住”了训练数据但泛化到新数据上会表现不佳。原因可能是我的训练数据量还不够大或者多样性不足。解决方法是收集更多、更广泛的数据重新训练。作为原型验证我暂时接受了这个模型并用预留的独立测试集test_UV_DATA.csv进行测试得到了约90.91%的准确率这个结果更为可靠。部署为Arduino库训练和验证满意后进入“部署”页面选择“Arduino库”。为了保持精度我选择了“未优化float32”选项如果对模型大小极其敏感可以选择量化版本int8但可能会损失一些精度。点击“构建”后下载得到一个ZIP格式的库文件。4.2 在XIAO BLE上集成与运行模型将训练好的模型集成到嵌入式设备中是边缘AI的“临门一脚”。导入库在Arduino IDE中通过项目-加载库-添加.ZIP库...将刚才下载的ZIP文件导入。这会在你的代码中生成一个头文件例如#include BLE_Smartwatch_Detecting_Potential_Sun_Damage_inferencing.h。这个头文件封装了模型的所有信息输入输出维度、推理函数等。编写推理固件第二个核心固件BLE_smartwatch_run_model.ino登场。它的工作流程如下初始化与数据采集同数据采集固件初始化传感器和BLE。填充特征缓冲区模型要求一次输入一个“帧”的数据。我们的模型输入是4个特征。在代码中我定义了一个float features[EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE]数组。每次采集到新数据并缩放后就将其填入这个数组。EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE这个常量是由Edge Impulse库自动定义的代表模型期望的输入大小。运行推理当特征缓冲区填满后调用run_classifier(signal, result, false)函数进行推理。结果存储在result结构体中其中result.classification[i].value表示模型对第i个类别的置信度分数0-1之间。结果解析与阈值过滤我设置了一个置信度阈值例如0.60。遍历三个类别的置信度选择第一个超过阈值的类别作为最终预测结果。如果多个类别超过阈值则取置信度最高的一个。本地显示与BLE广播将预测结果0,1,2映射到对应的图标显示在OLED上和颜色控制RGB LED。同时通过BLE服务将预测结果和原始的传感器数据温度、海拔、UVI、气压封装成特征值Characteristics并开始广播。// 关键代码片段运行推理与结果处理 float threshold 0.60; int predicted_class -1; // -1表示尚未预测 void run_inference() { // ... 填充features数组 ... if (feature_ix EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE) { ei_impulse_result_t result; signal_t signal; // 从features数组创建信号 numpy::signal_from_buffer(features, EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE, signal); // 运行分类器 EI_IMPULSE_ERROR res run_classifier(signal, result, false); if (res ! 0) return; // 解析结果应用阈值 for (size_t ix 0; ix EI_CLASSIFIER_LABEL_COUNT; ix) { if (result.classification[ix].value threshold) { predicted_class ix; break; // 取第一个超过阈值的类别 } } // 清空缓冲区准备下一次推理 feature_ix 0; } }BLE通信设置使用ArduinoBLE库。设备作为外围设备Peripheral创建一个自定义的BLE服务Service并为每个要传输的数据温度、海拔等创建一个特征值。然后设置设备名并开始广播。手机APP作为中央设备Central扫描并连接后可以订阅Subscribe这些特征值当手表端数据更新时手机会自动收到通知。5. 配套Android应用开发与系统联调5.1 使用MIT App Inventor快速开发为了快速验证概念我选择了MIT App Inventor这个图形化编程工具来开发Android客户端。它的优势是拖拽组件和积木式编程无需深厚的Java/Android开发经验非常适合创客和快速原型开发。界面设计设计一个简洁的界面包含一个设备列表ListPicker、扫描/停止/连接/断开按钮、用于显示传感器数据和风险等级的多个Label组件以及一个用于显示风险状态图标或颜色的Canvas或Image组件。集成BLE功能MIT App Inventor默认不支持BLE需要导入一个第三方扩展组件通常叫BluetoothLE。导入后就可以使用其提供的积木块来实现BLE功能。核心逻辑积木扫描点击扫描按钮调用BluetoothLE.StartScanning。连接在设备列表中选择手表设备识别设备名如“BLE UV Smartwatch”调用BluetoothLE.Connect并传入设备的MAC地址。发现服务与特征值连接成功后调用BluetoothLE.GetServices和BluetoothLE.GetCharacteristics来获取手表广播的服务和特征值UUID。订阅通知对关注的特征值如风险等级特征值调用BluetoothLE.RegisterForChanges或BluetoothLE.Read。这里更推荐使用“通知”模式这样当手表数据更新时APP会自动收到回调无需轮询。数据处理与显示在“特征值改变”的事件处理积木中解析收到的字节数据通常是float或int格式更新UI上对应的Label和状态图标。注意事项MIT App Inventor处理BLE数据时通常收到的是十六进制字符串。你需要根据手表端发送数据的格式例如一个float是4个字节编写相应的解析逻辑将其转换回可读的数字。同时确保APP请求了必要的Android权限如位置权限在Android 6.0以上扫描BLE设备需要位置权限。5.2 系统集成与现场测试将所有部分组装起来将运行BLE_smartwatch_run_model.ino固件的XIAO BLE、传感器、电池装入3D打印的表壳手机安装好APP。测试流程与问题排查上电与自检手表开机观察OLED是否显示传感器数据RGB LED是否变为默认的洋红色。如果卡在错误界面检查串口监视器输出常见问题有I2C地址错误BMP180通常是0x77、SD卡初始化失败可能是卡格式或接触问题。BLE连接打开手机APP点击扫描应该能看到名为“BLE UV Smartwatch”的设备。点击连接。如果连接失败检查手机蓝牙是否开启。检查手表固件中BLE初始化和广播代码是否执行串口会打印“Bluetooth device active”。确保没有其他设备已经连接了手表BLE外围设备通常只允许一个连接。数据接收与显示连接成功后APP界面应开始更新数据。如果数据不更新检查APP是否成功订阅注册通知了对应的特征值。检查手表端update_characteristics()函数是否被定期调用每30秒并且characteristic.writeValue()执行成功。在APP端添加调试输出打印收到的原始数据检查格式是否正确。模型推理验证将手表置于不同光照环境下室内、窗边、户外阴凉、阳光直射观察OLED和APP上显示的风险等级是否与预期相符。如果一直显示某个固定等级或频繁跳变检查传感器数据是否正常UVI值是否随光照变化。检查特征缓冲区填充逻辑是否正确推理函数是否被触发。考虑调整模型输出的置信度阈值threshold0.6可能偏高或偏低可根据测试情况微调。最根本的回顾训练数据集检查数据质量和标注是否准确。6. 结构设计与3D打印让想法变成实物6.1 设计思路与建模一个可靠的原型不仅需要“软实力”也需要“硬外壳”。我设计了一款受《Ben 10》中“Ultimatrix”启发的表壳主要考虑以下几点防护性外壳需要完全包裹脆弱的电子元件抵御日常使用中的磕碰并具备一定的防尘防泼溅能力非防水。我设计了主体外壳和可滑动的顶盖顶盖紧密扣合保护内部的扩展板和屏幕。可维护性顶盖设计为可滑动拆卸这样无需工具就能轻松打开进行SD卡存取、电池更换或调试。这是原型开发阶段非常实用的设计。佩戴舒适性底部留有凹槽用于粘贴15mm宽的黄色尼龙搭扣Velcro。这种表带固定方式成本低、可调节、透气性好并且方便快速穿脱。固定与散热使用4个10mm的M3铜柱和螺丝螺母将XIAO扩展板稳固地抬升并固定在壳体内部既保证了结构强度也在电路板和壳体之间留出了空气流通的空间有助于散热。使用Autodesk Fusion 360进行建模它对于参数化设计和生成适合3D打印的模型非常友好。设计完成后导出为STL文件。6.2 3D打印与后处理我使用的是Creality CR-6 SE FDM 3D打印机和彩虹炫彩PLA材料。打印参数根据材料推荐设置层高0.2mm填充率20%。实操心得支撑结构对于有悬垂结构的表壳如内部卡扣、顶盖滑轨需要生成支撑。在Ultimaker Cura等切片软件中仔细检查预览确保支撑容易拆除且不损坏模型表面。打印方向将表壳的开口面朝下打印可以减少支撑的使用并且让朝外的主要表面获得最好的打印质量。公差配合对于滑动配合、螺丝孔等需要精确尺寸的地方在设计时就要预留公差。我通常会给孔位留出0.2-0.3mm的间隙对于滑轨配合留0.1-0.2mm的间隙。第一次打印可以先打一个小样测试配合度。后期处理打印完成后小心去除支撑用砂纸打磨毛刺。对于PLA材料可以使用丙烯酸颜料或喷漆进行上色让外观更个性化。打印完成并处理好后用热熔胶将UV传感器、BMP180传感器、RGB LED固定在外壳预留的孔位中。将XIAO BLE插到扩展板上再用铜柱和螺丝将扩展板固定到壳体底座上。最后将电池连接好粘贴上尼龙搭扣表带一个功能完整、外观酷炫的智能手表原型就诞生了。7. 项目总结与未来优化方向这个项目从构思到实现完整地走通了“传感器数据采集 - 边缘AI模型训练与部署 - 低功耗无线通信 - 移动端交互”的全链路。它不仅仅是一个技术Demo更是一个展示如何用低成本、易获取的开源硬件和工具解决一个实际健康监测问题的范例。我个人在实际操作中的体会是数据质量至上在边缘AI项目中模型的性能天花板在数据采集阶段就已经被决定了。花在数据收集、清洗和标注上的时间远比后期调参更有价值。务必保证数据的准确性、代表性和多样性。功耗是穿戴设备的生命线虽然这个原型为了开发方便没有做极致的功耗优化比如频繁使用OLED和SD卡但在产品化时必须考虑使用深度睡眠模式、降低传感器采样频率、仅在需要时启动BLE广播、选用更节能的显示屏如电子墨水屏等。用户体验在于细节RGB LED的颜色反馈、OLED上清晰的图标、手机APP简洁的界面、30秒一次的更新频率这些细节共同构成了可用的用户体验。在技术可行的基础上多从用户角度思考交互逻辑。这个项目后续还可以从以下几个方向进行扩展和深化模型优化收集更大量、更多样化的数据不同季节、不同地理区域重新训练模型以提升泛化能力。可以尝试更复杂的网络结构或者使用Edge Impulse的EON Tuner自动搜索最优模型。增加传感器集成心率、血氧传感器结合紫外线数据可以更综合地评估户外活动时的身体负荷和风险。加入GPS模块可以记录风险地理位置。云端同步与分析在用户授权的前提下可以定期如回家连接Wi-Fi后将脱敏后的数据和风险记录同步到云端进行长期趋势分析为用户生成个性化的防晒报告和建议。产品化设计设计更小巧、更时尚的定制PCB替换掉开发板和扩展板使用纽扣电池或更小体积的锂电池并做好防水处理向真正的可穿戴产品迈进。通过这个项目我希望展示的是随着Edge Impulse这类工具的出现嵌入式AI的门槛正在迅速降低。任何一个有嵌入式基础和好奇心的开发者都有能力将AI赋能到身边的设备中创造出有实际价值的智能产品。从想法到实物中间的关键就是动手去做并在每一步中思考“为什么”和“如何更好”。