如何基于Itsy Bitsy nRF52840实现BLE低功耗蓝牙下125Hz速率的PPG传感器实时数据传输?
如何基于Itsy Bitsy nRF52840实现BLE低功耗蓝牙下125Hz速率的PPG传感器实时数据传输?
首先得揪出问题的核心:你现在用bleuart.println()把每个样本转成带换行的字符串发送,这简直是带宽杀手!一个16位的整数转成字符串再加上换行,至少要5-6个字节,还得单独发一个BLE包——每个BLE包本身还有几十字节的协议开销,1250个样本就要发1250个小包,这效率能不低吗?4分钟的传输时间就是这么来的。另外,之前实时传输出现信号间隙,大概率是因为你每次只读一个样本就立刻发BLE,I2C读取和BLE发送的时序冲突,导致传感器的下一个样本没及时读取而丢失了。
下面给你一套完整的优化方案,既能实现实时125Hz传输,又能保证信号无间隙,还能把传输速度拉满:
核心优化思路
- 抛弃字符串,用二进制传输:直接发送16位整数的原始字节,每个样本只占2字节,数据量直接砍到原来的1/3甚至更少。
- 利用传感器FIFO批量读样本:MAX30105自带32级FIFO缓冲区,传感器会自动把采样数据存在FIFO里,哪怕MCU暂时在处理BLE,也不会丢样本,我们可以一次读取多个样本再批量发送,彻底解决信号间隙问题。
- 优化BLE连接参数:调小连接间隔,让BLE链路的响应更及时,适配实时传输的需求。
- 用批量发送代替单样本发送:一次发多个样本的二进制数据,减少BLE包的数量,大幅提升传输效率。
修改后的完整代码
#include <bluefruit.h> #include <Wire.h> #include "MAX30105.h" MAX30105 particleSensor; BLEUart bleuart; // 定义二进制传输的临时缓冲区(适配BLE MTU,默认23字节,这里设32字节存16个样本) #define TX_BUF_SIZE 32 uint8_t tx_buf[TX_BUF_SIZE] = {0}; uint8_t tx_buf_idx = 0; void setup() { Serial.begin(230400); Serial.println("Initializing..."); Wire.begin(); // 初始化传感器 if (!particleSensor.begin(Wire, I2C_SPEED_FAST)) { Serial.println("ERROR: MAX30102 not found."); while (1); } Serial.println("MAX30105 initialized."); // 优化传感器配置:开启FIFO,设置采样率125Hz,配置FIFO阈值 particleSensor.setup(0x1F, 8, 2, 3200, 69, 4096); // 维持你的采样参数 particleSensor.enableFIFO(); // 开启FIFO功能 particleSensor.setFIFOAlmostFull(28); // FIFO存到28个样本时可以触发读取(可选) particleSensor.setFIFOOverflowEnabled(true); // 溢出时标记状态 particleSensor.clearFIFO(); // 初始化清空FIFO // 优化BLE参数 Bluefruit.begin(); Bluefruit.setTxPower(4); // 缩小连接间隔:7.5ms - 15ms(单位1.25ms), slave latency设0,保证实时响应 Bluefruit.Periph.setConnInterval(6, 12); Bluefruit.Periph.setConnSlaveLatency(0); Bluefruit.Periph.setConnSupervisionTimeout(400); // 超时时间400ms Bluefruit.setTxMTU(256); // 协商更大的MTU,提升批量发送效率 bleuart.begin(); startAdv(); Serial.println("Setup complete, ready for real-time transmission!"); } void startAdv(void) { Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); Bluefruit.Advertising.addTxPower(); Bluefruit.Advertising.addService(bleuart); Bluefruit.Advertising.addName(); Bluefruit.Advertising.restartOnDisconnect(true); Bluefruit.Advertising.setInterval(32, 244); Bluefruit.Advertising.setFastTimeout(30); Bluefruit.Advertising.start(0); } void loop() { // 读取FIFO中所有可用的样本 int available_samples = particleSensor.getFIFOAvailableSamples(); if (available_samples > 0) { for (int i = 0; i < available_samples; i++) { // 读取传感器IR值 uint32_t irValue = particleSensor.getIR(); // 你的压缩逻辑不变 uint16_t compressedValue = (irValue % 100000) / 10; // 把16位整数转成大端字节序(和iOS端约定好即可,小端也可以) tx_buf[tx_buf_idx++] = (compressedValue >> 8) & 0xFF; // 高字节 tx_buf[tx_buf_idx++] = compressedValue & 0xFF; // 低字节 // 如果缓冲区满了,立刻发送 if (tx_buf_idx >= TX_BUF_SIZE) { bleuart.write(tx_buf, tx_buf_idx); tx_buf_idx = 0; // 重置缓冲区索引 } } // 发送缓冲区中剩余的未发数据 if (tx_buf_idx > 0) { bleuart.write(tx_buf, tx_buf_idx); tx_buf_idx = 0; } } // 可选:处理BLE连接状态,比如调试用 if (Bluefruit.connected()) { // 可以在这里加一些连接状态的调试输出,比如每1秒打印一次传输统计 static unsigned long last_stats = 0; if (millis() - last_stats > 1000) { Serial.println("Real-time transmission active, connected."); last_stats = millis(); } } }
关键优化点详解
1. 二进制传输的优势
原来每个样本用println()发送,比如12345\n要6字节,现在直接发2字节的原始数据,数据量减少了70%以上。而且BLE可以一次打包多个样本,比如用256字节的MTU,一次能发128个样本,传输效率直接拉满。
2. 传感器FIFO的作用
MAX30105的FIFO会自动把采样到的样本存起来,哪怕MCU在处理BLE发送的瞬间,传感器也在持续采样存FIFO,这样就不会因为时序冲突导致样本丢失,彻底解决了实时传输的信号间隙问题。我们只需要定时读取FIFO里的所有可用样本即可。
3. BLE连接参数优化
- 连接间隔调小到7.5ms-15ms:让BLE从设备(你的nRF52840)能更频繁地和主机(iOS设备)通信,保证数据能及时发出去。
- Slave latency设为0:禁止从设备跳过连接事件,确保每一次连接事件都能传输数据。
- 更大的MTU:协商256字节的MTU,一次能发更多样本,减少包数量。
4. iOS端适配
你的iOS app需要改成解析二进制数据:每次读取2字节,拼接成一个16位整数(注意字节序要和代码里的一致,这里用的是大端),然后按原来的逻辑分10秒块喂给神经网络就行,比解析字符串还简单高效。
额外注意事项
- 如果还是担心传输延迟,可以开启BLE的“增强型连接”或者用BLE Notify的批量发送(不过BLE UART已经足够用了)。
- 可以在代码里加一些错误处理,比如检查FIFO溢出状态,如果溢出了就清空FIFO并打印警告,排查是否采样率和读取速度不匹配。
- 测试时可以用Serial打印每秒读取的样本数,确认是不是稳定在125个左右。
备注:内容来源于stack exchange,提问作者AceKijani
相关产品推荐
相关产品推荐

