乐于分享
好东西不私藏

从硬件设计到软件实现,完整复盘一个嵌入式产品

从硬件设计到软件实现,完整复盘一个嵌入式产品

从硬件设计到软件实现,完整复盘一个嵌入式产品

说一个我们做过的真实项目。

产品是一个工业环境监测节点——采集温湿度、CO₂浓度、PM2.5,通过LoRa无线上报到网关,电池供电,要求续航两年以上,部署在工厂的各个角落,不方便频繁更换电池。

需求听起来不复杂,但从零到量产,经历了不少值得复盘的东西。今天把整个过程完整说一遍,不只说怎么做的,也说哪些地方做错了、后来怎么改的。


第一步:需求拆解——在动任何东西之前

很多团队的习惯是需求一确认就开始选芯片、画原理图,这是最容易埋坑的做法。

我们的第一步,是把需求翻译成一张约束清单:

功能约束:
- 传感器:温湿度(SHT40)、CO₂(SCD41)、PM2.5(PMS5003)
- 通信:LoRa(SX1276),868MHz,最大发射功率14dBm
- 本地存储:掉线时缓存最多1000条数据,恢复连接后补发

性能约束:
- 采集周期:每5分钟采集一次
- LoRa上报:每次采集后发送,单次发射时间<2s

功耗约束(最关键):
- 电池容量:2×AA碱性电池,约2500mAh
- 续航目标:2年 = 8760小时
- 平均电流目标:2500mAh / 8760h ≈ 0.28mA = 280µA

环境约束:
- 工作温度:-10°C~+55°C(工厂室内,不是极端环境)
- 防护等级:IP30(防尘,不防水)
- 安装方式:壁挂,标准86盒面板

认证要求:
- CE认证(LoRa无线必须的,欧盟市场)
- 国内型式认可(暂不要求,后续版本考虑)

这张清单里,280µA的平均电流目标是整个设计的核心约束——它决定了MCU的选型、传感器的供电策略、LoRa发射功率的限制,以及后面软件里每一个低功耗配置的取舍。

这个数字,不是在硬件设计完了之后再考虑功耗,而是在第一天就把它钉在那里,所有后续决策都要绕着它转。


第二步:硬件架构——选型和踩坑

MCU选型

280µA的平均电流目标,意味着大部分时间MCU必须处于深度睡眠状态,睡眠电流要尽量低。

备选了三颗:STM32L476(睡眠电流约40nA)、Nordic nRF52840(睡眠电流约2µA,但我们不需要BLE)、意法半导体STM32U5(睡眠电流约300nA,支持SMPS)。

最终选了STM32L476,原因:

  • • 睡眠电流足够低
  • • SHT40和SCD41都用I2C,PMS5003用UART,L476的I2C和UART资源够用
  • • 有内部RTC,不需要外挂RTC芯片
  • • 团队对STM32生态熟悉,开发效率高

传感器供电设计——这里踩了一个大坑

PM2.5传感器(PMS5003)的工作电流是100mA,CO₂传感器(SCD41)的测量电流是17mA,而且两者都不支持真正意义上的休眠——最低功耗模式下,PMS5003还有大概5mA,SCD41还有几百µA。

如果传感器一直通电,别说280µA了,1mA都做不到。

解决方案:用GPIO控制传感器的供电,采集时上电,采集完立刻断电。

每5分钟一次的工作周期:
睡眠(4分50秒)→ 唤醒 → 给传感器上电 → 等传感器稳定 → 采集数据 → 关传感器 → 发LoRa → 睡眠

具体实现:用一个P沟道MOSFET,MCU GPIO控制其栅极,低电平时MOSFET导通给传感器供电,高电平截止断电。

踩坑:第一版PCB用的是一颗小封装的PMOS(AO3407),没有仔细看数据手册,实际调试时发现这颗MOSFET的导通电阻Rds(on)在低Vgs(AA电池末期电压约2.8V)下会增加到约0.5Ω,PMS5003的100mA电流流过去会有50mV压降,加上电池本身的内阻,PMS5003供电电压在电池接近耗尽时会低于其最低工作电压4.7V,导致传感器报错。

第二版换用了Rds(on)更小的型号(SI2301),问题解决。

LoRa模块和天线

用的是SX1276射频芯片+外挂天线,SX1276通过SPI与MCU通信。

天线是最容易被轻视的部分,结果也是问题最多的地方。

第一版PCB把天线焊盘设计得离金属外壳太近,实际组装之后,天线辐射受到外壳的影响,接收灵敏度下降了约5dBm,覆盖范围明显缩小。

测试方法:用频谱分析仪测天线辐射方向图,发现背向外壳方向的辐射严重削弱。

改版把天线焊盘移到PCB边缘,远离金属件,灵敏度恢复正常。

教训:天线设计不是画一个焊盘就完事,周围的金属、地平面开槽、天线方向都要在布局阶段就考虑进去,改版成本很高。


第三步:PCB设计——几个关键决策

模拟和数字地的处理

SHT40温湿度传感器对PCB设计有具体要求——传感器下方不能有铺铜(热量会影响测量精度),传感器周围要有物理隔离,防止PCB上其他热源(MCU、MOSFET)的热量影响读数。

我们把SHT40放在PCB边缘,下方开槽,周围没有大功率元器件,验证下来误差在0.1°C以内,满足要求。

电源走线

电池到MOSFET、MOSFET到PMS5003这段路,过的是100mA电流,必须用足够宽的走线(1mm以上)。

第一版有一段走线只有0.3mm,在低温下(内阻增大+MOSFET导通电阻增大)会出现传感器供电不稳定的情况。改版加宽了这段走线。

测试点的预留

量产阶段,这一点后来被反复感谢:

  • • 电源轨(3.3V、电池直接)各留一个测试点
  • • LoRa天线馈线留测试点(方便测驻波比)
  • • SWD调试接口留排针焊盘
  • • UART日志口留焊盘(量产固件可以通过这里读日志)

没有测试点,量产不良品的定位效率差距是十倍不止。


第四步:软件架构——低功耗是主线

整体架构

这个产品不需要RTOS——任务足够简单,一个超级循环(Super Loop)加上RTC唤醒就够了:

intmain(void)
{
    SystemInit();
    Sensor_Init();
    LoRa_Init();
    Storage_Init();

while (1) {
// 唤醒后立刻执行工作任务
        Sensor_PowerOn();
        HAL_Delay(SCD41_WARMUP_MS);  // 等CO₂传感器预热

        SensorData_t data;
        Sensor_ReadAll(&data);

        Sensor_PowerOff();

// 有网络就发送,没有就存本地
if (LoRa_IsNetworkAvailable()) {
            LoRa_Send(&data);
            Storage_FlushCache();  // 发送积压的缓存数据
        } else {
            Storage_Cache(&data);
        }

// 进入深度睡眠,等RTC唤醒
        RTC_SetAlarm(SAMPLE_INTERVAL_SEC);
        System_EnterDeepSleep();
// 唤醒后从这里继续
    }
}

低功耗的每一个细节

睡眠期间

voidSystem_EnterDeepSleep(void)
{
// 关掉所有未用的外设时钟
    __HAL_RCC_SPI1_CLK_DISABLE();  // LoRa用完了,关SPI时钟
    __HAL_RCC_USART1_CLK_DISABLE(); // 日志串口关掉
    __HAL_RCC_I2C1_CLK_DISABLE();  // 传感器用完了,关I2C时钟

// 把所有GPIO配成模拟输入(最低漏电)
// 除了必要的唤醒引脚和RTC引脚
    GPIO_ConfigAllAnalog();

// 进入Stop2模式(STM32L4最深的停止模式之一)
// SRAM内容保留,RTC继续运行,唤醒后可以从中断处继续
    HAL_PWREx_EnterSTOP2Mode(PWR_STOPENTRY_WFI);
}

Stop2模式下的实测睡眠电流:约3µA(MCU)+ 约1µA(RTC和32kHz晶振)= 约4µA。

传感器已经断电,LoRa模块处于深度睡眠模式(SX1276的Sleep模式电流<200nA)。

整体睡眠电流:约4.5µA。

工作期间的功耗

SCD41预热(9秒):MCU约3mA + SCD41约17mA = 约20mA × 9s
PMS5003+SCD41采集(3秒):约120mA × 3s
LoRa发送(1秒,14dBm):约100mA × 1s

工作阶段总电荷消耗:
20mA × 9s + 120mA × 3s + 100mA × 1s = 180 + 360 + 100 = 640mAs

平均电流计算

睡眠时间:300秒 - 13秒 = 287秒
睡眠电荷:4.5µA × 287s ≈ 1.3mAs

工作电荷:640mAs

每个周期总电荷:641.3mAs
平均电流:641.3mAs / 300s ≈ 2.14mA

2.14mA,远超280µA的目标。

这里出了一个严重的估算错误——早期没有认真算过PM2.5传感器的功耗,想当然地认为"传感器小,功耗不大",实际上PMS5003的工作电流是100mA,而且需要稳定工作3秒才能出稳定数据。


第五步:功耗优化——逼出来的改动

问题一:PM2.5传感器是最大的功耗怪兽

重新评估需求:客户实际上不需要每5分钟都测PM2.5,PM2.5的变化相对缓慢,每小时测一次足够。

把PM2.5的采集频率改成每小时一次,其余时间只采集温湿度和CO₂:

// 每5分钟:采集温湿度+CO₂(不开PM2.5传感器)
// 每60分钟:采集全部三种传感器

if (sample_count % 12 == 0) {
// 每12次(60分钟)才采集一次PM2.5
    PM25_PowerOn();
    HAL_Delay(PM25_WARMUP_MS);
    PM25_Read(&data.pm25);
    PM25_PowerOff();
}

问题二:SCD41的预热时间太长

SCD41(CO₂传感器)进入测量模式后,需要等待约5秒才能输出稳定数据。但SCD41有一个"低功耗单次测量"(Low Power Single Shot)模式,可以在约9秒内完成一次测量并自动进入空闲状态,电流约17mA × 9s。

调整为单次测量模式,不使用持续测量模式,节省了大量等待时间。

优化后的平均电流

每5分钟(非PM2.5轮次):
工作时间:SCD41单次9秒 × 17mA + SHT40采集0.1秒 × 0.5mA + LoRa发送1秒 × 100mA
       ≈ 153mAs + 0.05mAs + 100mAs = 253.05mAs
睡眠时间:(300 - 10.1秒)× 4.5µA ≈ 1.3mAs
每次电荷:约254mAs
平均电流:254mAs / 300s ≈ 0.85mA

每60分钟(PM2.5轮次,每12轮一次):
额外增加PMS5003:100mA × 3s = 300mAs

12轮平均分摊:
(11 × 254mAs + (254 + 300)mAs) / 12 = (2794 + 554) / 12 ≈ 279mAs/轮
平均电流:279mAs / 300s ≈ 0.93mA

0.93mA,还是超过280µA目标。

继续优化:LoRa发射功率从14dBm降到10dBm(实际测试距离还够用),发射电流从100mA降到约60mA:

LoRa部分:60mA × 1s = 60mAs(原来100mAs)

最终平均电流约0.75mA,电池寿命约2500mAh / 0.75mA ≈ 3333小时 ≈ 4.6个月。

还是差很多。

决定性的改动:降低采集频率

最终和客户沟通,他们实际的需求是:工厂控制系统需要看趋势,不需要每5分钟就上报一次,15分钟一次足够。

这个需求澄清,是整个项目最重要的一步沟通。

把采集周期从5分钟改成15分钟,工作时间比例从10.1/300降到10.1/900:

每15分钟(非PM2.5轮次):
工作电荷:约253mAs(工作内容不变)
睡眠电荷:(900 - 10.1) × 4.5µA ≈ 4mAs
每次电荷:257mAs
平均电流:257mAs / 900s ≈ 0.285mA = 285µA

285µA,非常接近280µA目标。

考虑到PM2.5每小时(每4轮)采集一次的额外功耗,平均电流约310µA,电池寿命约2500mAh / 0.31mA ≈ 8064小时 ≈ 11个月。

离两年还差一大截。


最后一公里:换电池型号

AA碱性电池在低温下(-10°C)实际可用容量大幅下降,约只有室温容量的60%。

换用3.6V锂亚(Li-SOCl₂)D型电池,容量19000mAh,低温性能好,自放电率极低(可存储10年以上):

理论寿命:19000mAh / 0.31mA ≈ 61290小时 ≈ 7年

考虑电池自放电(约1%/年)、低温降容(约20%)、实际工况的各种偏差,工程估算寿命约4-5年,远超2年目标。


复盘总结

这个项目从立项到量产,经历了两次PCB改版、一次传感器方案调整、多次固件优化。

几个值得记录的教训:

第一:功耗要在第一天就算清楚,不是等到产品做出来再算。 我们早期没有把PM2.5传感器的功耗认真算进去,导致第一次估算严重低估,白白绕了一大圈。

第二:和客户沟通需求细节,往往能发现"需求的需求"。 把采集频率从5分钟改到15分钟,是和客户深度沟通之后才知道可以做的,早沟通早节省开发成本。

第三:硬件选型要看完整的电气特性,不只是主要参数。 MOSFET的Rds(on)随温度和Vgs的变化、ADC的参考电压精度、晶振的低温频偏——这些参数在台架上测不出问题,在低温或者电池末期才会暴露。

第四:测试点是量产阶段的生命线,在PCB设计阶段就要留够。 宁可多一个测试点,不要少一个——少了想要加,要重新改PCB。

第五:从需求到量产,每一个"先这样,后面再优化"都有可能变成一次改版。 改版的成本,远比一开始做对要高。


最后一句话

做过一个完整的产品,才真正理解什么叫做"系统工程"——不是某个模块做得好,而是所有模块在所有约束条件下同时做得够好,才能做出一个能量产的东西。

这个过程不浪漫,但踩过一遍,建立起来的是别的方式学不来的工程直觉。