成功和失败,都给这一轮一个结论
从S01持续串口温湿度计改成周期节点时,不能在采样while后只插入sleep就算完成。Deep-sleep每次重新启动,普通I²C对象和局部变量都要重建;失败也必须有退出策略。
本例只做一轮FD高精度采样,不开Wi-Fi、不上传、不驱动屏幕;本轮双CRC通过才打印数值,否则输出NA,随后同样安排60秒睡眠。没有本站实板;连续30次唤醒、活动/睡眠电流均给可执行方法。
参考:[1] IDF5.5.5 S3 Sleep Modes/定时与RTC内存[4] SHT4x v6.4 §4:测量/转换/CRC/时间
指定模块的VIN与I²C,仍沿S01
官方ESP32-S3-DevKitC-1 v1.1 / ESP32-S3-WROOM-1-N8R8,8MB QIO Flash / 8MB OPI PSRAM;UART0桥接115200(GPIO43/44)。使用PID4885 SHT40-AD1B,默认0x44;可靠焊接排针、3.3V逻辑、短线≤20cm,模块自带10k上拉,不再添加未知并联电阻。3Vo是输出,不能当另一供电输入。
功能测试USB-to-UART单电源;测流必须切换为L02的断两USB/5V单点供电。传感器在睡眠中仍接3.3V,本例不是外设电源门控。
| DevKit v1.1 | PID4885 | 说明 |
|---|---|---|
| J1-1 3V3 | VIN | 同3.3V逻辑供电 |
| J1-22 GND | GND | 公共地 |
| J1-12 GPIO8 | SDA | 100kHz/0x44 |
| J1-15 GPIO9 | SCL | 短线 |
| 不连接 | 3Vo | 输出留空 |
不能用一个RTC计数掩盖普通重启
继续会话的条件同时包含reset=DEEPSLEEP、wake=TIMER与RTC记录magic/version/校验/计数范围。RESET、软件重启、掉电、结构损坏或uint32计数饱和都从NEW/cycle1开始;不能仅因RTC某个数字非0就算上次成功。
RTC_DATA_ATTR只服务同次供电内的深睡续接,保存cycle和累计success;不是NVS,不保证掉电保持。success并不证明连续成功,因为错误轮也会增加cycle。记录校验是完整性检查,不是安全认证。
首次NEW等待5秒方便维护;正常定时CONTINUED没有该等待。计时源esp_timer从启动重新开始,active_app_us只覆盖app_main内到END前,不能当含ROM/bootloader的完整活动时长。
本版本日志枚举:reset=8为DEEPSLEEP,wake=4为TIMER;其他数字按固定esp_system.h/esp_sleep.h解释,不把RESET按钮必然叫作EXT。VALID只指本轮I²C与CRC协议检查通过,不代表已校准或超出额定环境仍有精度保证。
参考:[1] IDF5.5.5 S3 Sleep Modes/定时与RTC内存[2] 固定sleep API与返回原因[3] 固定reset原因枚举[8] IDF固定RTC_DATA_ATTR定义
完整固件:事务关闭后再入睡
原生I²C创建bus、添加0x44设备、发送FD、等待10ms、收6字节并检查两个CRC。API每步最多指定100ms超时,不以无限重试拖住活动;数值转换保留原始RH和0–100%裁剪RH,不能把裁剪叫校准。
正常与错误都更新RTC本轮记录,移除device/删除bus,再配置timer。外围总线失败并不被伪装成正常样本。此工程没有外部库、没有传感器加热器。
// ---- main/ShtSample.h ----
// SPDX-License-Identifier: MIT
#pragma once
#include <stdint.h>
struct ShtSample { bool valid=false;int t_mC=0;int rh_raw_mPct=0;int rh_mPct=0; };
inline uint8_t shtCrc(const uint8_t* b){uint8_t c=0xff;for(int k=0;k<2;++k){c^=b[k];for(int j=0;j<8;++j)c=(c&0x80)?uint8_t((c<<1)^0x31):uint8_t(c<<1);}return c;}
inline ShtSample decodeSht(const uint8_t b[6]){ShtSample s;if(shtCrc(b)!=b[2]||shtCrc(b+3)!=b[5])return s;uint16_t t=(uint16_t(b[0])<<8)|b[1],h=(uint16_t(b[3])<<8)|b[4];s.valid=true;s.t_mC=-45000+int(175000LL*t/65535);s.rh_raw_mPct=-6000+int(125000LL*h/65535);s.rh_mPct=s.rh_raw_mPct<0?0:(s.rh_raw_mPct>100000?100000:s.rh_raw_mPct);return s;}
// ---- main/SensorBus.h ----
// SPDX-License-Identifier: MIT
#pragma once
#include "driver/i2c_master.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "ShtSample.h"
inline esp_err_t createBus(i2c_master_bus_handle_t* bus){i2c_master_bus_config_t c{};c.i2c_port=I2C_NUM_0;c.sda_io_num=GPIO_NUM_8;c.scl_io_num=GPIO_NUM_9;c.clk_source=I2C_CLK_SRC_DEFAULT;c.glitch_ignore_cnt=7;c.flags.enable_internal_pullup=false;return i2c_new_master_bus(&c,bus);}
inline esp_err_t addDevice(i2c_master_bus_handle_t bus,uint8_t addr,i2c_master_dev_handle_t* dev){i2c_device_config_t c{};c.dev_addr_length=I2C_ADDR_BIT_LEN_7;c.device_address=addr;c.scl_speed_hz=100000;return i2c_master_bus_add_device(bus,&c,dev);}
inline esp_err_t takeSample(i2c_master_dev_handle_t dev,ShtSample& sample){sample={};uint8_t cmd=0xfd,b[6];esp_err_t e=i2c_master_transmit(dev,&cmd,1,100);if(e!=ESP_OK)return e;vTaskDelay(pdMS_TO_TICKS(10));e=i2c_master_receive(dev,b,6,100);if(e==ESP_OK)sample=decodeSht(b);return e;}
// ---- main/CycleRecord.h ----
// SPDX-License-Identifier: MIT
#pragma once
#include <stdint.h>
struct CycleRecord { uint32_t magic,version,cycle,success,check; };
inline uint32_t recordCheck(const CycleRecord& r){uint32_t c=0x811c9dc5U;const uint32_t v[]={r.magic,r.version,r.cycle,r.success};for(auto w:v)for(int i=0;i<4;++i){c^=uint8_t(w>>(8*i));c*=16777619U;}return c;}
inline bool recordValid(const CycleRecord& r){return r.magic==0x4c503033U&&r.version==1&&r.success<=r.cycle&&r.check==recordCheck(r);}
inline bool beginCycle(CycleRecord& r,bool expectedDeepWake){bool continued=expectedDeepWake&&recordValid(r)&&r.cycle!=UINT32_MAX;if(!continued)r={0x4c503033U,1,0,0,0};++r.cycle;r.check=recordCheck(r);return continued;}
inline void finishCycle(CycleRecord& r,bool success){if(success&&r.success<r.cycle)++r.success;r.check=recordCheck(r);}
// ---- main/main.cpp ----
// SPDX-License-Identifier: MIT
#include <cstdio>
#include <inttypes.h>
#include "esp_sleep.h"
#include "esp_system.h"
#include "esp_timer.h"
#include "driver/uart.h"
#include "SensorBus.h"
#include "CycleRecord.h"
RTC_DATA_ATTR static CycleRecord record{};
extern "C" void app_main(){
int64_t start=esp_timer_get_time();auto reset=esp_reset_reason();auto wake=esp_sleep_get_wakeup_cause();
bool continued=beginCycle(record,reset==ESP_RST_DEEPSLEEP&&wake==ESP_SLEEP_WAKEUP_TIMER);
printf("L03 reset=%d wake=%d session=%s cycle=%" PRIu32 " prior_success=%" PRIu32 "\n",int(reset),int(wake),continued?"CONTINUED":"NEW",record.cycle,record.success);
if(!continued)vTaskDelay(pdMS_TO_TICKS(5000)); // first boot/RESET recovery window, not on normal timer wakes
i2c_master_bus_handle_t bus=nullptr;i2c_master_dev_handle_t dev=nullptr;ShtSample sample;
esp_err_t e=createBus(&bus);if(e==ESP_OK)e=addDevice(bus,0x44,&dev);if(e==ESP_OK)e=takeSample(dev,sample);
finishCycle(record,e==ESP_OK&&sample.valid);
if(e==ESP_OK&&sample.valid)printf("VALID T_mC=%d RH_raw_mPct=%d RH_display_mPct=%d\n",sample.t_mC,sample.rh_raw_mPct,sample.rh_mPct);
else printf("ERROR I2C=%s CRC_valid=%u T=NA RH=NA; sleep still scheduled\n",esp_err_to_name(e),sample.valid);
if(dev)ESP_ERROR_CHECK(i2c_master_bus_rm_device(dev));
if(bus)ESP_ERROR_CHECK(i2c_del_master_bus(bus));
ESP_ERROR_CHECK(esp_sleep_enable_timer_wakeup(60000000ULL));
printf("END cycle=%" PRIu32 " success=%" PRIu32 " active_app_us=%" PRId64 " sleep_request_us=60000000; period includes boot+active+sleep\n",record.cycle,record.success,esp_timer_get_time()-start);
fflush(stdout);ESP_ERROR_CHECK(uart_wait_tx_idle_polling(UART_NUM_0));esp_deep_sleep_start();
}
参考:[2] 固定sleep API与返回原因[4] SHT4x v6.4 §4:测量/转换/CRC/时间[6] IDF5.5.5原生I²C主机[8] IDF固定RTC_DATA_ATTR定义
60秒睡眠不等于每分钟固定时刻采样
esp_sleep_enable_timer_wakeup(60000000)定义睡眠计时请求,实际精度受RTC_SLOW_CLK影响。两轮之间还包含ROM/bootloader、I²C初始化/测量、串口排空与应用处理。若要整分钟对齐,应另设计可信绝对时间与补偿,不能用本例日志宣布已经实现。
活动时间日志不包含前段启动或END后的UART排空;用L02方法捕获全周期面积并记录板端电压。功能串口与独立供电测流是不同条件,不把一份CSV的电流塞进另一份USB条件。
先完成一个真实周期,再累计30次
下载进入工程,以现有IDF v5.5.5构建并刷USB-to-UART端口。第一轮上电NEW不是一次成功timer wake;它只证明开始了一次会话。
- 保存首次NEW、cycle1、实际VALID或ERROR及END日志,不删启动信息。
- 保持电源,后续记录应是DEEPSLEEP/TIMER、CONTINUED且cycle相邻;本轮VALID才能记valid=1。
- 保存至少30条后续连续成功timer记录;转录cycles.csv时run_id一致,reset/wake按原日志解释填写DEEPSLEEP/TIMER。
- 运行verify_cycles.py;缺行、错误轮、普通重启必须使这一连续段不通过。修复后另开一段,不从累计success挑30条。
- RESET、完全断电后上电分别确认NEW;另按L02至少3完整测流周期,活动与电流两维记录清楚。
idf.py --version
idf.py -B build -D IDF_TARGET=esp32s3 build
idf.py -B build -p PORT flash monitor- 做到这里,你应该看到
- 30次是待执行硬件验收,不因为下面的30次合成循环而勾选。
验收脚本只检查记录结构,不创造实测
脚本拒绝空表、混会话、不在1..UINT32_MAX内或非相邻cycle、非DEEPSLEEP/TIMER、valid非1和缺活动时间。它只能检验输入是否满足声明条件,不能证明串口记录真实、仪器已测或芯片确实睡过。
tests中的30条标SYNTHETIC,CRC/RTC同源测试也是纯内存输入;无I²C或RTC硬件仿真。实际cycles.csv和observations.csv保持空白。
# ---- tools/verify_cycles.py ----
# SPDX-License-Identifier: MIT
import argparse,csv
def verify(rows,required):
if len(rows)<required:raise ValueError('not enough actual rows')
run=rows[0]['run_id']
if not run:raise ValueError('run_id required')
previous=None
for row in rows:
if row['run_id']!=run:raise ValueError('mixed sessions')
n=int(row['cycle'])
if not 1<=n<=0xFFFFFFFF:raise ValueError('cycle must be firmware uint32 range 1..4294967295')
if previous is not None and n!=previous+1:raise ValueError('nonconsecutive cycle')
previous=n
if row['reset']!='DEEPSLEEP' or row['wake']!='TIMER' or row['valid']!='1':raise ValueError('not a successful timer wake')
if int(row['active_app_us'])<=0:raise ValueError('actual active time required')
return len(rows)
def main():
ap=argparse.ArgumentParser();ap.add_argument('csv');ap.add_argument('--required',type=int,default=30);a=ap.parse_args()
if a.required<1:ap.error('required >=1')
try:
with open(a.csv,newline='') as f:n=verify(list(csv.DictReader(f)),a.required)
except (KeyError,ValueError) as e:ap.error(str(e))
print('PASS input records:',n,'consecutive valid TIMER/DEEPSLEEP; authenticity and current measurement require original logs')
if __name__=='__main__':main()
让断线、松开和恢复也走完一个周期
断电预装SDA串联SPST;先读一轮有效,再只操作信号开关断开,下一轮应ERROR/NA但仍睡;闭合后续周期应重新有效,记录实际错误与恢复周期。不要在带电时挪VIN/GND或杜邦线。
每轮NEW先看reset/brownout,排供电/RESET线/RTC校验;每轮ERROR先回S01确认0x44/脚位/CRC,再读实际I²C错误,不靠保留上次值掩盖。没有串口时用供电波形与桥接供电条件区分,不能直接断言芯片没醒。
恢复重刷回单USB,BOOT+RESET进下载。不要改eFuse或关欠压检测,也不先擦Flash。若测得周期不稳定,报告实际范围和RTC时钟条件,不补一张理想值表。
参考:[1] IDF5.5.5 S3 Sleep Modes/定时与RTC内存[4] SHT4x v6.4 §4:测量/转换/CRC/时间[5] PID4885模块脚位
从定时醒来,进入事件醒来
本篇完成的是有原因、有周期、有本轮有效性的节点。下一篇保留这种启动证据,但把RTC timer换成有释放协议的外部电平;测流记录仍沿L02边界。
资料来源与版本说明
已核固定版本API与模块/仪器手册;实际构建和主机逻辑单独列证据,硬件仍待执行。资料核对日期:。资料核对、程序构建与实板测试分别记录,不互相替代。