基于XIAO ESP32C6与MR60BHA2毫米波雷达的非接触式生命体征监测实践

发布时间:2026/8/2 14:29:44
基于XIAO ESP32C6与MR60BHA2毫米波雷达的非接触式生命体征监测实践 1. 项目概述当毫米波雷达遇见嵌入式开发板最近在捣鼓一些非接触式的生命体征监测项目传统的方案要么是摄像头有隐私顾虑要么是压电或光电传感器需要接触且易受干扰。直到我发现了这个组合Seeed Studio的XIAO ESP32C6开发板配上他们家的MR60BHA2 60GHz毫米波雷达模块。这玩意儿听起来就挺“黑科技”的它不采集任何图像或声音只通过分析毫米波反射的微小变化就能隔空检测人的呼吸和心跳。这对于智能家居、健康监护、甚至是一些需要非侵入式监测的工业场景都提供了一个全新的、更优雅的解决方案。我手上这套就是XIAO ESP32C6和MR60BHA2的套件。XIAO ESP32C6是个非常小巧但功能全面的物联网开发板基于乐鑫ESP32-C6芯片支持Wi-Fi 6和蓝牙5.0性能足够处理雷达数据。MR60BHA2则是核心传感器它工作在60GHz频段这个频率的电磁波波长很短只有5毫米左右因此对微小的位移比如胸腔因呼吸的起伏、皮肤因心跳的微动极其敏感。更重要的是毫米波可以穿透衣物、薄毯等非金属介质实现“隔物检测”同时由于其波长特性它几乎不受环境光线、温度、声音的干扰稳定性比很多传统方案要好得多。这篇文章我就来分享一下如何从零开始让这套套件跑起来获取到呼吸和心跳数据。整个过程涉及硬件连接、固件烧录、串口数据解析以及一个简单的上位机数据显示。无论你是物联网开发者、嵌入式爱好者还是对生命体征监测技术感兴趣的研究人员这篇手把手的指南都能帮你快速上手。2. 硬件拆解与连接认识你的“雷达眼”和“大脑”在写代码之前我们得先搞清楚手头这两块板子都是干什么的以及如何正确地让它们“对话”。错误的连接是后续所有问题的万恶之源。2.1 XIAO ESP32C6物联网项目的“全能小钢炮”XIAO ESP32C6的尺寸只有21 x 17.5毫米比一枚硬币大不了多少但你别看它小该有的接口一个不少。对于这个项目我们需要关注以下几个关键引脚3V3和GND 为整个系统提供3.3V电源。非常重要MR60BHA2模块的工作电压就是3.3V严禁接入5V否则会永久损坏传感器。TX (GPIO21)和RX (GPIO20) 这是板子的UART1串口发送和接收引脚。我们将通过这个串口与雷达模块进行通信发送指令并接收数据。D6 (GPIO6)和D7 (GPIO7) 这是板子的UART0串口通常用于通过USB转串口芯片与电脑通信也就是我们烧录程序和打印调试信息Serial.print用的端口。板子通过Type-C接口供电和编程非常方便。它的ESP32-C6芯片主频高达160MHz内置RISC-V核心处理雷达的串口数据流绰绰有余并且预留了充足的性能用于Wi-Fi/蓝牙数据传输。2.2 MR60BHA2穿透介质的“生命感知器”MR60BHA2模块的核心是一颗60GHz毫米波雷达芯片。模块板载了天线、射频前端和信号处理单元。它通过发射调频连续波FMCW并接收从目标反射回来的波通过分析反射波的频率、相位变化可以精确计算出目标的距离、速度以及更关键的——微小的位移。模块上有一个关键的拨码开关用于设置模块的I2C地址但在我们使用UART通信的例程中通常保持默认即可所有拨码在OFF位置。模块的引脚定义清晰VCC 接3.3V电源。GND 接地。TX 模块的串口发送脚要连接到开发板的RX脚。RX 模块的串口接收脚要连接到开发板的TX脚。这里有一个经典的“交叉连接”原则A设备的TX要接B设备的RX反之亦然。很多新手会在这里接反导致通信失败。2.3 实战连接步骤与避坑指南准备杜邦线 建议使用4根母对母杜邦线连接更稳固。断电操作 在连接任何线缆之前确保XIAO ESP32C6没有通过USB连接到电脑。执行交叉连接将MR60BHA2的VCC连接到 XIAO ESP32C6的3V3。将MR60BHA2的GND连接到 XIAO ESP32C6的GND。关键一步将MR60BHA2的TX连接到 XIAO ESP32C6的RX (GPIO20)。将MR60BHA2的RX连接到 XIAO ESP32C6的TX (GPIO21)。检查与固定 仔细核对两遍连接关系。由于杜邦线容易松动可以用一点胶带或热熔胶轻轻固定连接处防止在测试过程中脱落。注意务必确认是3.3V对接。我曾有一次顺手将模块接到了另一块板子的5V引脚上模块瞬间发烫虽然没冒烟但之后再也无法正常工作只能报废。这是一个价值几十元的教训。连接好后硬件部分就准备好了。接下来我们要让“大脑”运行我们写的程序。3. 软件开发环境搭建与固件烧录要让ESP32C6工作我们需要搭建Arduino IDE环境并安装相应的板支持包。虽然也可以用ESP-IDF但对于快速上手和串口通信这类任务Arduino的简洁性更有优势。3.1 配置Arduino IDE与板管理安装Arduino IDE 从Arduino官网下载并安装最新版本的IDE1.8.x或2.0.x均可。添加ESP32板支持网址 打开Arduino IDE进入文件-首选项。在“附加开发板管理器网址”中填入以下网址https://espressif.github.io/arduino-esp32/package_esp32_index.json如果已有其他网址用逗号隔开即可。安装ESP32开发板 打开工具-开发板-开发板管理器。在搜索框中输入“esp32”找到由“Espressif Systems”发布的“esp32”开发板包点击安装。这个过程需要下载一些工具链和库耗时可能较长请保持网络通畅。选择正确的开发板 安装完成后在工具-开发板中选择“XIAO ESP32C6”。如果列表中没有可能需要选择“ESP32C6 Dev Module”然后手动修改Flash Size等参数但Seeed的板支持包通常会将XIAO系列直接集成进去。3.2 获取并理解示例代码Seeed Studio通常会在GitHub上提供相关产品的示例代码库。我们需要找到针对XIAO ESP32C6和MR60BHA2的示例。访问GitHub搜索“Seeed-Studio/Seeed_Arduino_MR60BHA1”或类似仓库注意型号可能为BHA1但其通信协议与BHA2通常兼容具体需查看传感器文档。更直接的方法是在Arduino IDE的库管理中搜索“MR60BHA”。安装相应的库。例如在项目-加载库-管理库中搜索“MR60BHA”找到并安装。安装后在文件-示例中你应该能找到类似Seeed_MR60BHA1-basic_reading的示例。不过为了更透彻地理解通信过程我建议我们先从一个“裸”的串口通信例程开始这能帮助我们厘清数据流的本质。3.3 编写并烧录基础通信固件下面是一个最基础的代码框架它只做两件事初始化与雷达模块通信的串口然后将雷达发来的所有原始数据原封不动地转发到电脑的串口监视器上。这相当于一个“透明传输”桥接。// MR60BHA2 与 XIAO ESP32C6 基础串口通信示例 // 雷达模块 UART1: TX-GPIO21, RX-GPIO20 // 电脑通信 UART0: USB Serial // 定义雷达模块连接的串口引脚 #define RADAR_RX_PIN 20 // ESP32C6 的 RX 连接 雷达的 TX #define RADAR_TX_PIN 21 // ESP32C6 的 TX 连接 雷达的 RX // 创建一个硬件串口对象我们将其命名为 RadarSerial HardwareSerial RadarSerial(1); // 使用 UART1 void setup() { // 初始化与电脑通信的串口波特率115200 Serial.begin(115200); while (!Serial); // 等待串口连接对于USB CDC可选 // 初始化与雷达模块通信的串口 // 波特率根据MR60BHA2手册设置常见有9600, 115200, 921600等需确认 RadarSerial.begin(115200, SERIAL_8N1, RADAR_RX_PIN, RADAR_TX_PIN); Serial.println(XIAO ESP32C6 MR60BHA2 串口桥接已启动); Serial.println(等待雷达模块数据...); } void loop() { // 检查雷达串口是否有数据到来 if (RadarSerial.available()) { // 读取一个字节 byte incomingByte RadarSerial.read(); // 以十六进制格式打印到电脑串口方便我们分析协议 Serial.print(incomingByte, HEX); Serial.print( ); } // 检查电脑串口是否有指令发送用于后续主动查询 if (Serial.available()) { byte cmd Serial.read(); RadarSerial.write(cmd); // 将指令转发给雷达 } }代码烧录步骤用Type-C数据线将XIAO ESP32C6连接到电脑。在Arduino IDE中选择正确的端口工具-端口通常显示为USB Serial Device或类似。点击上传按钮。首次上传可能需要按住XIAO上的“BOOT”按钮再点击上传直到出现上传进度条再松开。上传成功后打开串口监视器右上角放大镜图标将波特率设置为115200。如果一切正常你应该会在串口监视器里看到一串串连续的十六进制数例如AA BB 01 02 ...这样的数据。这证明硬件连接正确雷达模块正在持续输出数据。如果没有任何输出请立即返回检查硬件连接特别是TX/RX是否接反和电源电压。4. 解析毫米波数据协议从十六进制到生命体征看到原始数据只是第一步就像拿到了一本用密码写成的书。MR60BHA2模块通常遵循一个特定的串口通信协议我们需要根据其数据手册来破译这些十六进制码的含义。4.1 常见的数据帧结构毫米波雷达模块为了可靠传输数据通常被打包成“帧”。一个典型的数据帧可能包含以下部分具体格式需查阅MR60BHA2的官方数据手册以下是常见结构的举例帧头 1-2个固定的字节如0xAA、0x55或0xAA55用于标识一帧数据的开始。数据长度 1个字节指示后面“数据内容”部分的字节数。数据内容 核心数据区包含了我们关心的信息例如存在状态 1个字节0x01表示检测到目标0x00表示无目标。运动状态 1个字节区分静止、微动、运动等。距离数据 2个字节表示目标离雷达的直线距离单位可能是厘米。呼吸率 1个字节或2个字节表示每分钟呼吸次数。心率 1个字节或2个字节表示每分钟心跳次数。呼吸波形原始数据 一系列字节表示呼吸引起的位移随时间变化的相对值。心跳波形原始数据 一系列字节表示心跳引起的更微小的位移变化。校验和 1个字节用于验证数据在传输过程中没有出错。常见的算法是将帧头之后、校验和之前的所有字节相加取低8位或求反。例如一个简化的数据帧可能是AA 55 08 01 00 07 D0 0C 78 3C。AA 55 帧头。08 数据长度是8个字节。01 检测到目标。00 目标静止。07 D0 距离数据。0x07D0 2000表示距离2米。0C 呼吸率。0x0C 12表示12次/分钟。78 心率。0x78 120表示120次/分钟。3C 校验和假设为前面所有字节和的低8位。4.2 实现一个简单的数据解析器我们需要修改之前的代码从简单的转发升级为智能解析。思路是创建一个状态机在串口数据流中寻找帧头然后根据数据长度收取指定数量的字节最后验证校验和。如果校验通过就提取出关键信息。// MR60BHA2 数据解析示例 (假设协议) #define FRAME_HEADER_1 0xAA #define FRAME_HEADER_2 0x55 // 定义一个结构体来存放解析后的数据 struct RadarData { bool targetPresent; uint8_t motionStatus; uint16_t distanceCm; uint8_t breathingRate; uint8_t heartRate; bool dataValid; }; RadarData currentData; enum ParseState { WAIT_HEADER1, WAIT_HEADER2, WAIT_LENGTH, READING_DATA, WAIT_CHECKSUM }; ParseState state WAIT_HEADER1; uint8_t dataBuffer[20]; // 足够大的缓冲区 uint8_t dataIndex 0; uint8_t expectedLength 0; uint8_t calculatedChecksum 0; void parseRadarData(byte incomingByte) { switch (state) { case WAIT_HEADER1: if (incomingByte FRAME_HEADER_1) { state WAIT_HEADER2; calculatedChecksum incomingByte; // 开始计算校验和 } break; case WAIT_HEADER2: if (incomingByte FRAME_HEADER_2) { state WAIT_LENGTH; calculatedChecksum incomingByte; } else { state WAIT_HEADER1; // 同步失败重新开始 } break; case WAIT_LENGTH: expectedLength incomingByte; calculatedChecksum incomingByte; dataIndex 0; if (expectedLength 0 expectedLength sizeof(dataBuffer)) { state READING_DATA; } else { state WAIT_HEADER1; // 长度异常重置 } break; case READING_DATA: dataBuffer[dataIndex] incomingByte; calculatedChecksum incomingByte; if (dataIndex expectedLength) { state WAIT_CHECKSUM; } break; case WAIT_CHECKSUM: { uint8_t receivedChecksum incomingByte; // 常见的校验和是前面所有字节和的低8位可能取反 uint8_t ourChecksum calculatedChecksum 0xFF; // 取低8位 // 或者 ourChecksum ~calculatedChecksum 0xFF; // 如果协议是取反 if (ourChecksum receivedChecksum) { // 校验成功解析数据 if (expectedLength 6) { // 假设我们有至少6字节数据 currentData.targetPresent (dataBuffer[0] 0x01); currentData.motionStatus dataBuffer[1]; currentData.distanceCm (dataBuffer[2] 8) | dataBuffer[3]; currentData.breathingRate dataBuffer[4]; currentData.heartRate dataBuffer[5]; currentData.dataValid true; // 打印解析结果 Serial.print(Target: ); Serial.print(currentData.targetPresent ? Yes : No); Serial.print( | Dist: ); Serial.print(currentData.distanceCm); Serial.print(cm | Breath: ); Serial.print(currentData.breathingRate); Serial.print( bpm | Heart: ); Serial.print(currentData.heartRate); Serial.println( bpm); } } else { Serial.println(Checksum Error!); } // 无论成功与否都回到初始状态等待下一帧 state WAIT_HEADER1; calculatedChecksum 0; } break; } } void loop() { // 检查雷达串口是否有数据 while (RadarSerial.available()) { byte incomingByte RadarSerial.read(); parseRadarData(incomingByte); // 调用解析函数 } // 其他逻辑... }将这段代码替换之前的loop函数中的部分上传后串口监视器应该会显示格式清晰的解析结果。这里最大的坑在于协议的不确定性。以上代码是基于一个假设的通用协议编写的。你必须找到MR60BHA2的官方数据手册或通信协议文档根据实际的帧结构、字节顺序大端/小端、校验和算法来修改FRAME_HEADER、数据解析索引和校验和计算部分。没有正确的协议解析出的数据全是乱码。5. 构建简易上位机进行波形可视化看到数字变化还不够直观呼吸和心跳本质上是波形信号。我们可以让ESP32C6将原始的波形数据如果雷达模块输出的话或者处理后的呼吸/心率值发送到电脑用一个简单的Python上位机程序绘制成实时图表。5.1 修改固件发送格式化数据首先我们需要修改Arduino代码以固定的格式如CSV通过串口发送我们想绘制的数据。例如我们发送呼吸波形值和心率波形值假设从雷达数据中能提取出原始波形点。// 在成功解析一帧数据后发送波形数据假设dataBuffer[6]开始是波形数据 if (currentData.dataValid expectedLength 8) { // 发送一个简单的数据包例如W,breath_raw,heart_raw Serial.print(W,); Serial.print(dataBuffer[6]); // 假设第7字节是呼吸波形原始值 Serial.print(,); Serial.println(dataBuffer[7]); // 假设第8字节是心跳波形原始值 }5.2 使用Python与Matplotlib创建实时绘图在电脑上我们使用Python的pyserial库读取串口数据用matplotlib进行动画绘图。安装Python库pip install pyserial matplotlib编写Python上位机脚本import serial import matplotlib.pyplot as plt import matplotlib.animation as animation from collections import deque import time # 配置串口参数端口名请根据实际情况修改Windows: COM3, Linux: /dev/ttyUSB0 SERIAL_PORT /dev/cu.usbmodemXXXX # 替换为你的端口 BAUD_RATE 115200 # 初始化串口 ser serial.Serial(SERIAL_PORT, BAUD_RATE, timeout1) print(f连接到 {SERIAL_PORT}) # 设置绘图数据队列存储最近N个点 MAX_POINTS 200 breath_data deque([0]*MAX_POINTS, maxlenMAX_POINTS) heart_data deque([0]*MAX_POINTS, maxlenMAX_POINTS) time_axis deque(range(-MAX_POINTS1, 1), maxlenMAX_POINTS) # 时间轴 # 创建图形和子图 fig, (ax1, ax2) plt.subplots(2, 1, sharexTrue) fig.suptitle(MR60BHA2 呼吸与心跳波形实时监测) breath_line, ax1.plot([], [], b-, label呼吸波形, linewidth1) heart_line, ax2.plot([], [], r-, label心跳波形, linewidth1) # 设置坐标轴 ax1.set_ylabel(呼吸幅度) ax1.set_ylim(-10, 300) # 根据实际数据调整Y轴范围 ax1.legend() ax1.grid(True, linestyle--, alpha0.5) ax2.set_xlabel(时间 (采样点)) ax2.set_ylabel(心跳幅度) ax2.set_ylim(-10, 300) ax2.legend() ax2.grid(True, linestyle--, alpha0.5) def update(frame): 动画更新函数从串口读取数据并更新图表 global breath_data, heart_data, time_axis while ser.in_waiting: try: line ser.readline().decode(utf-8, errorsignore).strip() if line.startswith(W,): # 匹配我们发送的数据格式 parts line.split(,) if len(parts) 3: _, breath_val_str, heart_val_str parts breath_val int(breath_val_str) heart_val int(heart_val_str) # 将新数据添加到队列 breath_data.append(breath_val) heart_data.append(heart_val) time_axis.append(time_axis[-1] 1 if time_axis else 0) except (ValueError, UnicodeDecodeError) as e: # 忽略解析错误的数据行 pass # 更新图表数据 breath_line.set_data(list(time_axis), list(breath_data)) heart_line.set_data(list(time_axis), list(heart_data)) # 动态调整X轴范围实现滚动效果 ax1.set_xlim(time_axis[0], time_axis[-1]) ax2.set_xlim(time_axis[0], time_axis[-1]) return breath_line, heart_line, # 创建动画 ani animation.FuncAnimation(fig, update, interval50, blitTrue) # 每50ms更新一次 plt.tight_layout() plt.show() # 关闭串口图表关闭后执行 ser.close()运行这个Python脚本你应该能看到两个实时更新的波形图。呼吸波形通常频率较低0.1-0.5 Hz幅度较大心跳波形频率高1-2 Hz幅度更细微。如果波形杂乱或没有规律可能是雷达模块放置位置不佳距离太远、角度不对、前方有强反射物干扰或者数据解析仍有错误。6. 优化、调试与进阶应用思考让基础功能跑通只是第一步要让这个系统真正可靠、可用还需要大量的调试和优化工作。6.1 提升数据质量与系统稳定性传感器摆放至关重要 毫米波雷达有特定的辐射模式。应将传感器正对被测者的胸腔区域距离建议在0.5米到2米之间。避免传感器前方有正在摆动的风扇、窗帘或其它移动物体。将传感器固定在稳定的支架上减少自身振动。滤波算法是关键 直接从串口读出的呼吸/心率值或者原始波形都包含大量噪声。需要在ESP32C6端或上位机端进行数字滤波。对于呼吸信号一个低通滤波器截止频率约0.5Hz非常有效。对于心跳信号则需要一个带通滤波器例如0.8Hz到3Hz。你可以使用简单的滑动平均滤波开始进阶可以使用IIR或FIR滤波器。// 简单的滑动平均滤波示例用于呼吸率 #define FILTER_WINDOW 5 int breathRateBuffer[FILTER_WINDOW] {0}; int bufferIndex 0; int getFilteredBreathRate(int newRate) { breathRateBuffer[bufferIndex] newRate; bufferIndex (bufferIndex 1) % FILTER_WINDOW; long sum 0; for (int i0; iFILTER_WINDOW; i) sum breathRateBuffer[i]; return sum / FILTER_WINDOW; }处理多目标与动态场景 基本的固件可能只处理单个静止目标。在实际房间中可能存在多人或宠物走动。MR60BHA2可能具备多目标检测能力这需要解析更复杂的数据帧并实现目标跟踪算法如最近邻关联。对于动态场景你需要区分是人的生命体征还是无关的运动干扰。功耗优化 如果用于电池供电设备需要优化ESP32C6的功耗。可以在未检测到目标时让雷达模块进入低功耗查询模式或让ESP32C6本身进入深度睡眠定时唤醒检测。6.2 常见问题排查清单问题串口监视器无任何输出。检查1 USB线是否连接牢固电脑是否识别到串口检查2 代码中RadarSerial.begin()的波特率是否与雷达模块默认波特率一致尝试常见的9600, 115200, 921600。检查3 TX/RX连接线是否交叉用万用表通断档测量。检查4 雷达模块的VCC是否接3.3V接5V可能已损坏模块。问题有数据输出但全是乱码或固定值。检查1 数据解析协议是否正确这是最常见的原因。务必找到官方协议文档。检查2 校验和计算方式是否正确尝试不同的算法求和取低8位、取反、CRC8等。检查3 雷达模块前方是否有合适的目标让人静止坐在传感器前1米处试试。问题波形图噪声很大看不出呼吸心跳规律。检查1 环境是否安静避免强电磁干扰源如大功率路由器、电机。检查2 传感器是否稳固任何轻微晃动都会被放大。检查3 应用数字滤波器。原始毫米波数据噪声本就较高必须滤波。检查4 被测者是否保持相对静止轻微的肢体动作也会产生干扰。6.3 项目延伸与创意应用当你掌握了基础的数据获取和解析后这套系统的潜力才真正开始展现无线化与云端连接 利用ESP32C6内置的Wi-Fi将处理后的生命体征数据如心率、呼吸率、睡眠状态通过MQTT协议发送到Home Assistant、阿里云IoT或自建的服务器实现远程健康监护。非接触式睡眠监测仪 将设备安装在床头通过长时间监测呼吸节奏和身体微动判断睡眠阶段清醒、浅睡、深睡、REM统计入睡时间、醒来次数等。相比手环无佩戴负担。跌倒检测与告警 分析雷达信号中的运动速度、姿态变化模式可以用于老年人或病人的跌倒检测。一旦检测到疑似跌倒的快速下落和随后静止立即触发本地报警或通过网络发送告警信息。智能家居联动 检测到人长时间静止可能是在沙发上看电视睡着了自动调暗灯光、调低音量检测到夜间起床自动点亮夜灯。实现真正无感、隐私安全的智能感知。生物识别 高级的毫米波雷达甚至可以捕捉到每个人独特的心跳模式心搏图理论上可用于身份识别不过这需要更复杂的信号处理和机器学习模型。从点亮一个LED到隔空感知生命嵌入式开发的乐趣就在于此。XIAO ESP32C6和MR60BHA2这套组合为你打开了一扇通往60GHz毫米波感知世界的大门。剩下的就交给你的创意和代码了。在实际部署中耐心调试信号、反复验证算法可靠性将是项目成功的关键。祝你调试顺利