
1. 项目概述当低功耗蓝牙遇上环境感知最近在捣鼓一个无线温湿度监测的小项目核心想法很简单做一个功耗极低、体积小巧、能通过手机直接读取数据的传感器节点。这玩意儿可以贴在鱼缸旁边监测水温湿度放在花盆里看看植物生长环境或者塞进书柜里预防书籍受潮应用场景挺多的。市面上虽然有成品的蓝牙温湿度计但自己动手做灵活性高成本可控还能把玩一下最新的低功耗芯片技术。这次我选用的核心是InPlay IN100这颗SoC。可能有些朋友对InPlay这家公司还不太熟悉他们是近几年在超低功耗无线物联网领域冒头很快的一家IN100更是其拳头产品主打的就是一个“极致低功耗”和“极简开发”。它内置了蓝牙5.3的射频部分最关键的是它支持一种叫做NanoBeacon的广播模式。简单来说它不需要像传统蓝牙那样先配对再连接而是像个小广播站持续地、以极低的功耗向外发送封装好的数据包。手机上的App比如一些蓝牙扫描工具或专用的接收App扫到这些广播包就能直接解析出里面的温湿度数据。这种方式省去了复杂的连接握手过程功耗可以做到微安级别一颗纽扣电池撑个一两年都不是问题。传感器方面我用了Sensirion的SHT40这是一款精度和稳定性都相当不错的数字温湿度传感器通过I2C总线与IN100通信。整个项目的难点和乐趣就在于如何用IN100这颗主打Beacon的芯片去可靠地驱动I2C传感器并把数据打包进NanoBeacon的广播帧里。下面我就把从硬件搭棚、软件调试到功耗优化的全过程拆解一遍。2. 硬件设计与核心器件选型2.1 主角剖析InPlay IN100 SoCIN100是一颗高度集成的芯片它的架构就是为了免开发、低功耗数据上传而优化的。对我们这个项目而言它有几个关键特性决定了方案可行性超低功耗无线支持蓝牙5.3在NanoBeacon模式下平均电流可以低至几个微安。它内部有专用的硬件逻辑来处理广播帧的组包和定时发送CPU大部分时间都在深度睡眠只有需要更新传感器数据或到点发送时才被唤醒。集成可编程外设虽然它不像通用MCU那样有庞大的外设库但提供了必要的GPIO和硬件I2C控制器。这是驱动SHT40的基础。官方SDK也提供了I2C主机驱动的底层接口省去了我们自己模拟时序的麻烦。内置电源管理芯片内部集成了DCDC和LDO支持宽电压输入1.8V-3.6V非常适合直接由单节3V纽扣电池如CR2032供电。我们几乎不需要额外复杂的电源电路。小巧的封装采用QFN等小型封装有利于设计出非常迷你的PCB。选择IN100而不是更常见的nRF52832或TI的CC2640主要是看中它在单纯广播发射场景下的功耗优势和方案的简洁性。它把“无线传感器标签”这件事做得非常纯粹。2.2 传感器搭档Sensirion SHT40温湿度传感器的选择很多比如DHT11、DHT22、AHT20等。我选择SHT40是基于以下几点考量精度与可靠性Sensirion是温湿度传感器的行业标杆SHT40是其新一代产品精度高湿度±1.8%RH温度±0.2℃长期稳定性好抗冷凝能力也更强。低功耗在测量模式下典型电流只有0.4μA平均功耗极低。它支持“单次测量”模式即需要数据时发命令启动一次测量读完后传感器自动进入休眠非常契合我们间歇性工作的需求。数字接口与集成度采用I2C数字接口直接输出校准后的数字量省去了模拟信号调理和ADC的环节。芯片本身也集成了信号调理和ADC外围电路极其简单只需要一个上拉电阻和滤波电容。尺寸DFN封装小到只有1.5x1.5mm非常适合微型化设计。注意SHT40的I2C地址是0x44七位地址。务必在PCB布局时将它的地址选择引脚ADDR接到固定的电平GND或VDD以确定唯一的地址避免总线冲突。2.3 电路连接与电源考量整个系统的原理图非常简单核心是IN100与SHT40的I2C连接。I2C总线连接IN100的任意一组I2C引脚例如GPIO8/SCL, GPIO9/SDA分别连接到SHT40的SCL和SDA引脚。SCL和SDA线上各需要一个上拉电阻阻值通常在2.2kΩ到10kΩ之间具体取决于总线速度和布线长度。为了降低功耗我选择了10kΩ的电阻。在3V电压下高电平时每条线的静态电流只有0.3mA在可接受范围内。在SHT40的电源引脚VDD附近需要放置一个0.1μF的陶瓷电容进行去耦确保电源稳定这对传感器测量精度很重要。电源设计采用单节CR2032纽扣电池标称3V容量约220mAh供电。电池正极直接接入IN100的VBAT引脚。IN100内部电源管理单元会处理一切。关键点为了进一步降低整个系统的休眠电流除了选择低功耗器件还要检查所有IO口的配置。确保IN100上未使用的GPIO引脚设置为“输入下拉”或“模拟输入”模式避免浮空引起漏电。同时给SHT40供电的路径上不要有额外的、无法断电的LDO。天线部分IN100需要外接一颗蓝牙天线。对于这种小尺寸标签通常使用PCB倒F天线IFA或陶瓷天线。我为了简化设计和保证性能选择了一颗标准的2.4G陶瓷天线如2450AT18A100并严格按照数据手册的推荐布局进行PCB设计预留了π型匹配网络的位置以便调试。硬件搭好后看起来就是一块比硬币大不了多少的小板子上面只有芯片、几个阻容和天线。3. 软件驱动与I2C通信实现这是项目的核心代码部分主要任务有两个一是初始化并驱动I2C总线读取SHT40数据二是配置IN100的NanoBeacon功能将数据填入广播包。3.1 I2C总线初始化与SHT40驱动IN100的SDK基于一个轻量级的RTOS或事件驱动框架。我们需要在工程中正确配置硬件I2C控制器。// 示例I2C初始化代码片段基于IN100 SDK风格 int sensor_i2c_init(void) { i2c_master_config_t i2c_cfg; // 配置I2C主机参数 i2c_cfg.scl_pin BSP_IO_PORT_01_PIN_08; // GPIO8 作为 SCL i2c_cfg.sda_pin BSP_IO_PORT_01_PIN_09; // GPIO9 作为 SDA i2c_cfg.freq_hz 100000; // 标准模式100kHz。SHT40最高支持1MHz但100kHz更稳定且低功耗 i2c_cfg.addr_mode I2C_MASTER_ADDR_7BIT; // 7位地址模式 // 初始化I2C主机驱动 if (i2c_master_init(g_i2c_master, i2c_cfg) ! 0) { printf(I2C Master init failed!\r\n); return -1; } return 0; }驱动SHT40的关键在于理解其I2C通信序列。SHT40支持多种测量模式和命令。// 读取SHT40温湿度数据 int sht40_read_measurement(float *temperature, float *humidity) { uint8_t cmd[1] {0xFD}; // 高精度测量命令 uint8_t rx_buf[6] {0}; uint16_t raw_temp, raw_hum; int ret; // 1. 发送测量命令 ret i2c_master_write(g_i2c_master, SHT40_I2C_ADDR, cmd, sizeof(cmd), true); if (ret ! 0) { printf(Send measure command failed: %d\r\n, ret); return ret; } // 2. 等待测量完成。高精度模式典型耗时8.2ms这里延时10ms确保完成 delay_ms(10); // 3. 读取6字节数据温度高/低/CRC湿度高/低/CRC ret i2c_master_read(g_i2c_master, SHT40_I2C_ADDR, rx_buf, sizeof(rx_buf), true); if (ret ! 0) { printf(Read measurement data failed: %d\r\n, ret); return ret; } // 4. 校验CRC可选但推荐 if (!sht40_check_crc(rx_buf[0], 2, rx_buf[2]) || !sht40_check_crc(rx_buf[3], 2, rx_buf[5])) { printf(CRC check failed!\r\n); return -1; // 数据可能出错 } // 5. 转换原始数据为实际值 raw_temp (rx_buf[0] 8) | rx_buf[1]; raw_hum (rx_buf[3] 8) | rx_buf[4]; *temperature -45.0 175.0 * (raw_temp / 65535.0); *humidity -6.0 125.0 * (raw_hum / 65535.0); *humidity (*humidity 0) ? 0 : (*humidity 100) ? 100 : *humidity; // 钳制到0-100%范围 return 0; }实操心得I2C时序与稳定性在实际调试中我发现即使使用硬件I2C偶尔也会出现读取失败的情况。尤其是在IN100从深度睡眠唤醒后立即进行I2C操作时。这可能是因为电源或时钟尚未完全稳定。我的解决办法是在唤醒后、执行关键I2C操作前增加一个短暂的软件延时比如1-2ms并加入重试机制。例如如果第一次读取CRC校验失败自动重试1-2次大部分偶发错误都能被规避。3.2 NanoBeacon数据包配置IN100的NanoBeacon功能是其特色。我们需要配置广播间隔、发射功率并构造广播数据。广播包遵循蓝牙广播数据格式AD Structure。我们主要利用“制造商特定数据”Manufacturer Specific Data字段来携带我们的温湿度数据。// 定义Beacon数据包结构示例 typedef struct __attribute__((packed)) { uint16_t company_id; // 公司标识符例如0xFFFF可用于测试正式产品需申请 uint16_t device_type; // 自定义设备类型 int16_t temperature; // 温度放大10倍传输例如23.5度传为235 uint16_t humidity; // 湿度放大10倍传输例如45.6%传为456 uint8_t battery_level; // 电池电压等级0-100 uint8_t packet_counter; // 包计数器用于去重和丢失检测 } beacon_data_t; // 配置并启动Beacon void beacon_config_and_start(float temp, float humi) { beacon_data_t beacon_data; uint8_t adv_data[31]; // 广播数据最大31字节 uint8_t adv_len 0; // 填充自定义数据 beacon_data.company_id 0xFFFF; beacon_data.device_type 0x0001; // 温湿度传感器类型 beacon_data.temperature (int16_t)(temp * 10); // 放大10倍取整 beacon_data.humidity (uint16_t)(humi * 10); beacon_data.battery_level get_battery_level(); // 假设的函数读取电池电压并换算为百分比 beacon_data.packet_counter g_packet_counter; // 构造广播数据AD Structure adv_data[adv_len] sizeof(beacon_data) 1; // 长度 数据长度 类型字段长度(1) adv_data[adv_len] 0xFF; // 数据类型制造商特定数据 memcpy(adv_data[adv_len], beacon_data, sizeof(beacon_data)); adv_len sizeof(beacon_data); // 调用IN100 SDK API设置广播数据 inplay_ble_set_adv_data(adv_data, adv_len); // 设置广播参数间隔1000ms低功耗模式 inplay_ble_set_adv_interval(1600, 1600); // 单位0.625ms, 16001s inplay_ble_set_tx_power(TX_POWER_NEGATIVE_10_DBM); // 设置发射功率-10dBm平衡距离与功耗 // 开始广播 inplay_ble_adv_start(); }这样一个包含温湿度、电量和序列号的定制化Beacon就配置好了。手机端可以使用任何支持扫描自定义Beacon的App如nRF Connect、LightBlue来接收并解析这些数据。4. 低功耗系统设计与软件流程对于电池供电的设备功耗就是生命线。整个系统的软件流程围绕“睡眠-唤醒-工作-睡眠”这个核心循环来设计。4.1 主循环与状态机程序没有复杂的多任务主要是一个大循环大部分时间芯片处于深度睡眠Deep Sleep状态。int main(void) { hardware_init(); // 初始化时钟、GPIO等基础硬件 sensor_i2c_init(); // 初始化I2C beacon_init(); // 初始化蓝牙Beacon相关参数 while (1) { float temp, humi; // 1. 唤醒外设读取传感器 power_on_sensor(); // 如果需要控制给传感器的供电MOS管 delay_ms(2); // 等待传感器电源稳定 if (sht40_read_measurement(temp, humi) 0) { // 2. 数据有效更新Beacon包 beacon_config_and_start(temp, humi); // 3. 记录本次数据可存入内部Flash或直接发送 store_measurement(temp, humi); } power_off_sensor(); // 关闭传感器电源以省电 // 4. 进入深度睡眠。IN100的RTC定时器会在设定的时间后唤醒CPU。 // 假设我们设置每5分钟300秒测量一次 enter_deep_sleep(300 * 1000); // 参数为睡眠时长毫秒 // 执行到这里时系统已被RTC中断唤醒开始下一次循环 } }4.2 功耗实测与优化点搭建好原型后我用高精度万用表串联在电池端测量了平均电流。深度睡眠电流IN100内核、内存掉电仅RTC和唤醒逻辑工作。实测约1.5μA。这是功耗的基石。传感器测量电流SHT40单次测量约1ms峰值电流平均到整个周期可忽略。关键是I2C总线活动时上拉电阻10kΩ产生的电流3V / 10kΩ 0.3mA。每次测量约有几次I2C读写持续几个毫秒这部分平均电流也很小。无线发射电流这是耗电大户。IN100在0dBm发射功率下峰值电流约5mA。每次广播发射持续约1-2ms。整体平均电流计算假设每5分钟300秒工作一次。工作阶段传感器测量数据处理耗时约15ms电流约1mABeacon发射耗时2ms电流5mA。总电荷消耗 ≈ (15ms * 1mA 2ms * 5mA) 25 μAh微安时。睡眠阶段电荷消耗 ≈ (300s - 0.017s) * 1.5μA ≈ 450 μAh。单周期总消耗 ≈ 475 μAh。平均电流 475 μAh / 300s ≈1.58 μA。这个理论计算值与我的实测平均电流1.6 - 1.8 μA基本吻合。一颗CR2032电池220mAh的理论续航时间为220mAh / 1.7μA ≈14.7年当然实际中电池自放电、环境温度、连接器阻抗等因素会缩短寿命但稳定工作2-3年是完全可以期待的。功耗优化黄金法则睡眠即王道让系统尽可能长时间处于最深度的睡眠模式。慢速省电在满足需求的前提下使用最低的I2C总线速度、最低的MCU主频。射频精打细算使用能满足通信距离的最低发射功率。每降低3dBm发射电流几乎减半。同时在数据可靠的前提下尽量拉长广播间隔。斩断漏电之路仔细检查每个IO口的状态悬空的引脚必须配置为确定的电平上拉或下拉或模拟输入。断开所有不必要外设的电源。5. 手机端数据接收与解析硬件和固件只是半边天数据还需要被看见。手机端有多种方式接收我们的NanoBeacon。5.1 使用通用蓝牙工具对于开发和测试nRF Connect for Mobile是神器。打开App开始扫描很快就能看到名为“IN100”或自定义名称的设备其广播数据Manufacturer Specific Data里就包含了我们填充的温湿度原始字节。我们需要根据定义的数据结构beacon_data_t手动换算。这种方式适合验证数据是否正确发出。5.2 开发简易接收App如果想有更好的用户体验可以自己开发一个简单的App。无论是用Android StudioJava/Kotlin还是SwiftiOS流程都是类似的扫描蓝牙设备使用平台提供的蓝牙API扫描特定服务UUID或制造商ID我们的company_id如0xFFFF的设备。解析广播数据从扫描结果的广播数据包中提取出制造商特定数据字段。数据转换与显示按照约定的格式例如温度的前两个字节是放大10倍的int16_t将字节流解析成有意义的温度和湿度数值并显示在UI上。历史记录可选可以将解析到的数据连同时间戳一起存入手机本地数据库并绘制成趋势曲线图。这里以Android的Kotlin代码为例展示核心解析逻辑// 在扫描回调中处理广播数据 override fun onScanResult(callbackType: Int, result: ScanResult?) { result?.scanRecord?.manufacturerSpecificData?.forEach { (manufacturerId, data) - if (manufacturerId 0xFFFF) { // 匹配我们的公司ID if (data.size 8) { // 确保数据长度足够 val buffer ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN) // 按struct beacon_data_t的顺序解析 val deviceType buffer.short val tempRaw buffer.short val humiRaw buffer.short val battery buffer.get().toInt() and 0xFF val counter buffer.get().toInt() and 0xFF val temperature tempRaw / 10.0f val humidity humiRaw / 10.0f runOnUiThread { textViewTemp.text String.format(%.1f °C, temperature) textViewHumi.text String.format(%.1f %%, humidity) textViewBattery.text $battery% } } } } }5.3 数据上报到云端进阶如果想让数据在互联网上可见可以通过手机App作为网关将解析后的数据通过Wi-Fi或蜂窝网络上传到云平台如阿里云IoT、腾讯云IoT、ThingsBoard或自建服务器。这样即使人不在家也能远程查看各个传感器的状态。这需要手机端App增加网络通信和用户认证的逻辑。6. 常见问题与调试心得在项目开发过程中踩过一些坑也积累了些许经验。6.1 I2C通信失败这是最常遇到的问题现象是IN100读不到SHT40的数据或者读到的CRC校验失败。排查步骤硬件检查首先用万用表检查I2C总线的SCL和SDA线是否连通上拉电阻是否焊接正确电压是否正常不通信时应为高电平VDD。逻辑分析仪抓时序这是最直接的调试手段。用逻辑分析仪连接SCL和SDA抓取一次完整的通信波形。重点看起始条件S和停止条件P是否清晰。设备地址0x44是否正确以及ACK位。数据位是否稳定。时钟频率是否与配置相符100kHz波形周期应为10μs。软件排查检查I2C初始化代码的引脚配置、时钟频率是否正确。在发送命令和读取数据之间确保有足够的延时delay_ms(10)等待SHT40完成测量。检查代码中的I2C读写函数返回值确认每一步操作是否成功。我的教训有一次调试发现始终读不到数据逻辑分析仪一看发现SCL线上有严重的振铃 ringing。原因是我的杜邦线太长约20cm且没有上拉电阻我错误地以为芯片内部已上拉。加上10kΩ上拉电阻并缩短连线后波形立刻干净通信恢复正常。I2C总线对布线非常敏感在PCB上要尽量走线短粗并正确使用上拉电阻。6.2 广播信号弱或距离短手机只能在很近的距离1米扫描到设备。原因与对策发射功率过低检查代码中inplay_ble_set_tx_power()的设置。尝试提高到0dBm或4dBm如果芯片支持。注意功耗会增加。天线匹配不佳PCB天线或陶瓷天线的匹配网络π型电路参数需要根据实际PCB的阻抗进行调试。可以使用矢量网络分析仪VNA来调优。对于业余项目严格按照天线厂商提供的参考设计布局和参数通常能获得不错的效果。电源干扰在发射瞬间电池电压可能被拉低导致射频性能下降。确保电源回路足够“强壮”在IN100的电源引脚附近放置一个容量较大的储能电容如10μF钽电容0.1μF陶瓷电容并联。6.3 功耗高于预期实测平均电流达到几十甚至上百微安。排查清单睡眠模式确认确认代码确实调用了深度睡眠函数并且没有因为中断或看门狗等原因被频繁唤醒。可以在睡眠函数前后翻转一个GPIO用示波器观察其波形确认睡眠时长是否符合设定。外设漏电断开SHT40的电源如果通过MOS管控制再次测量电流。如果电流显著下降说明传感器或I2C上拉电阻是主要耗电源。考虑使用更小的上拉电阻如4.7kΩ或在睡眠时通过GPIO控制切断上拉电阻的电源需要额外电路。IO口配置这是隐形杀手。再次核对所有未使用的GPIO在初始化时都设置为带下拉的输入模式或模拟输入模式。调试接口如果SWD/JTAG调试接口的引脚如SWCLKSWDIO没有正确处理也可能导致漏电。在量产代码中可以考虑将这些引脚配置为模拟输入。6.4 数据跳变或不准确偶尔读到的温湿度值会出现明显错误。可能原因电源噪声在传感器测量期间电源上有毛刺。确保SHT40的VDD引脚有足够的去耦电容0.1μF陶瓷电容必须靠近引脚放置。I2C总线受干扰如果I2C走线靠近射频天线或其它高速信号线可能受到干扰。在PCB布局时应让I2C走线远离射频部分并尽量短。CRC校验失败但被忽略确保你的代码检查了SHT40返回的CRC校验码。如果校验失败应该丢弃这次数据进行重试而不是直接使用错误数据。传感器本身问题SHT40虽然可靠但也需避免凝露、长期暴露在化学气体等极端环境。确保其工作在规定的温湿度范围内。这个基于InPlay IN100和SHT40的无线温湿度传感器项目从芯片选型到功耗调优完整地走通了一个超低功耗物联网传感节点的开发流程。它没有复杂的网络协议栈凭借NanoBeacon这种简洁的广播机制实现了极致的能效比。对于想要入门低功耗蓝牙传感开发的朋友这是一个非常典型的案例。你可以在此基础上扩展比如增加光照传感器、加速度计或者修改广播间隔、数据格式来适应不同的场景。动手做一遍你会对物联网终端设备的“省电”艺术有更深刻的理解。