Files
JBao_Temp_Humi_Fan_FW/Doc/温湿度风扇_固件需求规格书.md
T
wuchuyuan e324ebd99b 精简 usr_app 架构:去掉关机/休眠死代码,按键复用 key.c 状态机,ISR 重活统一延后到任务上下文
Type-C 长供电、上电即工作、无软关机,删除 app_boot/app_power 及 DP0 开关相关逻辑,
去掉空占位 app_fan_run 和无效的 usr_timer_pick_mode/rearm(数码管 4ms 扫描已 priority=1,
系统本来就不会进低功耗)。按键 5s 配对 / 12s OTA 改走 key.c 长按分级,去掉自计时与 1~5s 死区。
新增 app_defer,NVM/配对/OTA 只在 ISR 置位、在 500ms 任务上下文落地,避免再在 usr_timer 里写 flash。
规格书与注释同步精简。
2026-08-31 17:57:52 +08:00

33 KiB
Raw Blame History

温湿度风扇固件需求规格书

内容
产品 温湿度风扇(爬宠)
芯片 JL7016 / AC7016CBR28
工程 温湿度风扇_soc_JieLi-JL7016
产品码 ProdtCode=CBR3productCode=PRCBR3
协议 见宝 SOC 55AAV1.0.0
文档版本 V2.2.0
日期 2026.08.31
作者 cyWu
读者 WorkBuddy / 固件开发
参考工程 D:\Jianbao\Project\产品\宠物\门夹弹力绳\门夹弹力绳_soc_JieLi-JL7016

V2.2.0 变更(2026-08-31): 顺着 V2.1.0 删 app_power 的思路,继续清掉同一类"保留但无调用者"的死代码:删除 app_function_soft_off() / app_function_is_power_on() / app_function_power_on_silent()gDevData.power 恒为 1、写死在 app_function_init()(不再需要 s_func.app_power 状态字段);usr_on_key_short() 里对应的"软关态唤醒"死分支一并删除。

V2.1.0 变更(2026-08-31): 关机功能确认彻底不存在(关机=拔电),整模块删除 app_power.c/.h(原 request/finalize 深睡钩子已无任何触发源,纯累赘代码);usr_jb_main.c/app_function.c/app_disp.c 里所有 app_power_poweroff_pending() 判断一并清理。同时把配对/OTA/NVM 落盘的"ISR 打标志"统一收进新模块 app_defer.c/.h(见 16 章)。

V2.0.0 变更(2026-08-31): 上电即自动开机,删除 app_boot(原阻塞式 3s/5s/12s 开机判定整模块移除)。按键语义改为运行态按住分级:短按切档;按住 5s 松手清绑定进配对;按住 12s 到点即进 OTA;取消长按 3s 软关机(拔电即关)。详见 5.1/5.2/5.3。

V1.2.0 变更(2026-08-31): 风扇档位占空比按用户拍板改为 1档 50%5000/10000)、2档 75%7500/10000(原 35%/65%,第 5.3/7/17/18 章同步)。

V1.1.0 变更(2026-08-31): IO 分离。CT1642 改走 PG0(CLK)/PG1(DATA)AHT20 独占 PC4(SCL)/PC5(SDA),两路物理隔离,删除 bsp_bus 共线分时机制(原 8.3 节整体替换为"IO 独立")。同时把 2.2 节显示协议修正为实测 18bit 帧

本文是 已拍板的共同理解,不是草稿。实现时以本文 + Doc\温湿度风扇DP配置.xlsx 为准。Excel 与工程代码冲突时:Excel 字节布局优先,本文行为优先


0. WorkBuddy 开干前必读(硬约束)

  1. 先改协议再写业务。 Excel 里 cond_config1~7 已是 7 字节,当前工程 jb_protocol.* / dp_definition.json 仍是 6 字节。必须按 Excel 重新生成协议,禁止继续按 6 字节解析。
  2. 不要把业务算法写进 jb_product.c 只把 DP 写入 gDevData 并转调 app_*。组合根是 usr_jb_main.c(照门夹)。
  3. 不要抄门夹的电池 / 低电拒绝开机。 本机 USB 常电、无电池。上电即开机(V2.0.0 起,不再长按 3s 才开机);拔电即关,无软关机深睡。
  4. 不要把调试 UART 放在 PC5。 调试 UART TX = PG6IO_PORTG_06)。PC5 是 AHT20 SDA(独立使用)。
  5. DP0 power 预留,不当开关。 上电恒为开机态(power=1),运行中上报 power=1,写忽略;停机靠短按切到 0 档或拔电。
  6. CT1642 与 AHT20 已 IO 分离(V1.1.0)。 CT1642 走 PG0(CLK)/PG1(DATA)AHT20 走 PC4(SCL)/PC5(SDA),两路独立、无总线竞争,不存在分时/总线锁(见 8.3)。
  7. 每个模块必须能用 board_pin.hBOARD_xxx_ENABLE 单独关掉 仍能编过、能配网上报(关掉的模块返回空操作)。
  8. 日志全部 英文。中文只出现在注释和本文。
  9. 编码风格对齐门夹 + 工程师规范:文件小写+下划线,g_ / s_ 前缀,枚举 Pascal_t,接口 xxx_init / xxx_run 或 tick / xxx_get_status

1. 产品一句话

USB 供电的爬宠温湿度风扇:本地按键开关机和调档,双两位一体数码管显示整数温湿度,BLE 连 APP;APP 可下发通风/强力模式、7 组温湿度条件、7 组定时。条件与定时仅在设备处于开机态执行;定时还必须 RTC 已对时。


2. 硬件资源

2.1 引脚(以用户原理图说明 + 本文为准)

功能 MCU 电平 / 说明
CT1642 DATA PG1 IO_PORTG_01 推挽输出(V1.1.0 改板飞线,独立)
CT1642 CLK PG0 IO_PORTG_00 推挽输出(同上)
AHT20 SDA PC5 IO_PORTC_05 开漏+内部上拉(外部 4.7~10k 双保险),独立
AHT20 SCLK PC4 IO_PORTC_04 推挽驱动,独立
风扇 PWM PC3 IO_PORTC_03 高电平导通MCPWM
状态 LED PC2 IO_PORTC_02 低电平亮
按键 PB1 IO_PORTB_01 与门夹相同,低电平按下(若实测相反只改 BOARD_KEY_ACTIVE_LEVEL
调试 UART TX PG6 IO_PORTG_06 只发打印,不占用 PC5
USB Type-C → LDO 3.3V 无电池、无充电管理

板级文件仍指向 CONFIG_BOARD_JL701N_DEMO。产品 GPIO 全部集中到新建 apps/usr_periph/board_pin.h,并改 board_jl701n_demo_cfg.h

#define TCFG_UART0_TX_PORT    IO_PORTG_06

关掉会与 PC3/PC4/PC5 冲突的 demo 外设(SD0、HW SPI1 等)。PWM LED 走产品 app_led,保持 TCFG_PWMLED_ENABLE=DISABLE。PG0/PG1 无 demo 外设占用(cpu/br28/mcpwm.cio_ext_interrupt_test() 是 while(1) 演示函数,产品不调用),可直接作 CT1642 引脚。

2.2 显示器件

  • 驱动:类 CT1642CLK+DATA 串行(原理图位号 S1)。
  • 两块 2 位 7 段数码管:温度一块、湿度一块(含 DP 段,本产品 不显示小数DP 恒灭)。
  • 帧协议(2026-08-29 实测定型,与 CSDN yitx 一致):一帧 18bitCLK 上升沿采样 DATA,顺序
    C1-C4 位选(4bit,低有效,入参 ~com 先发 C1)→ 6bit 保留(固定填 1)→ Q2-Q9 段码(8bitLSB 先,seg bit0 → Q2
  • DATA 只允许在 CLK=低 时改变。
  • 发完 18bitCLK=高时 DATA 上升沿 = 锁存CLK=低时 DATA 上升沿 = 输出刷新(一次只点亮锁存的那 1 位)。
  • CT1642 内部只有 1 个 18 位移位 + 1 个 12 位锁存,无按位显示 RAM、不自动扫描 4 位,必须 MCU 持续快节拍轮询(BOARD_DISP_SCAN_MS=44 位才能同亮不闪。

段码与 COM 的 PCB 网络为 SET1~8CAN1~4。P0 必须用写死数字扫清 哪几 bit 对应温度十位/个位、湿度十位/个位、A~G。扫不清之前禁止把随机码当产品逻辑。

2.3 AHT20

  • I2C 7 位地址 0x38(写 0x70 / 读 0x71)。
  • 上电后 ≥5ms 再操作总线。
  • 测量:0xAC 0x33 0x00,等 ≥80ms,读 Status + 20bit 湿度 + 20bit 温度 + CRC8。
  • T = (ST / 1048576) * 200 - 50(℃);湿度按手册 20bit 公式,显示与 DP 用 0~100 整数 %。
  • 建议 I2C 100kHz;采集周期 2s(手册要求 >1s)。
  • CRC 多项式 0x31,初值 0xFF。CRC 失败当本次采样失败。

3. 软件架构(对齐门夹,删电池,加风扇/显示/传感)

apps/earphone/app_main.c
  └─ usr_rtc_init()
  └─ usr_jb_init()                    ← 唯一组合根
        bsp_hw_init()
        app_led_init()                ← 灯效事件表最先装载
        jbInit() + userInit()
        (上电即开机:无按键等待,不再有 app_boot
        app_fan_init() / app_disp_init()
        usr_timer_add(app_disp_scan, 4ms, priority=1)   ← CT1642 扫描,常驻
        app_th_init() / app_mode_init() / app_arbiter_init()
        app_cond_init() / app_timer_init()
        app_nvm_init() + app_nvm_load()                 ← 恢复 VM
        app_function_init()
        user_key_init() + lock_if_held
        usr_timer_add(usr_timer_tick, 20ms, priority=1) ← 常驻,永不重挂

20ms 业务节拍usr_timer_tick,硬件定时器中断上下文,禁止再开 1ms ISR):

  1. jbTimerIrq() × dt
  2. user_key_scan + user_key_handle
  3. app_th_poll(内部 2s 才真正采;IO 分离后采集与显示互不干扰)
  4. app_mode_run(通风/强力状态机;条件/定时引擎在 500ms 任务上下文,见下)
  5. app_disp_run(闪烁、刷新)
  6. app_function_poll(同步 DP
  7. app_nvm_tick(变化检测 + 500ms 防抖计时,不写 flash
  8. app_led_run

priority 固定 1、只注册一次,不动态重挂:本产品 Type-C 长供电,不需要低功耗; 且 app_disp_scan 本身就以 priority=1 常驻,系统本来就永远无法进入低功耗—— 按业务状态动态切换 usr_timer_tick 自己的 priority 没有任何实际效果,已删除 (原 usr_timer_pick_mode/usr_timer_rearm,规格书 V2.3.0 起废弃)。

usr_timer_loop()BLE 500ms 任务上下文):userHandle() 填温湿度 DP、 jbProtocolHandle()app_cond_run()/app_timer_run()(条件/定时引擎, 仲裁见第 12 章)、app_function_loop()(VM 落盘 + 配对/OTA 落地)。

可直接复用门夹源码(拷贝后改引脚/事件表,不要改框架语义):

复用 路径 改什么
按键 FSM key.c / key.h / key_manager.c 短按切档;按住分级 5s 配对 / 12s OTAV2.0.0,无长按关机)
LED 事件库 led.c / led.h / led_manager.c 单灯 PC2,删红绿双通道
开机判定 app_boot.c V2.0.0 已整模块删除(上电即开机,判定下沉到 key_manager
配对会话 app_pair.c 120s 超时,灯效改单灯
软关机 app_power.c V2.1.0 已整模块删除:无电池、无充电,关机=拔电,无软件关机路径
RTC usr_rtc.c 原样
协议骨架 usr_jb_proto 重生 7 字节条件

不要拷贝: app_battery.c、充电 GPIO、电机 app_mode 步进表(本产品 mode 是通风/强力,重写)。


4. 模块开关(独立使能 / 屏蔽调试)

全部放 apps/usr_periph/board_pin.h0 时:对应 init/run 直接 return,GPIO 不配,日志打一次 xxx disabled

#define BOARD_LED_ENABLE          1
#define BOARD_KEY_ENABLE          1
#define BOARD_FAN_ENABLE          1
#define BOARD_DISP_ENABLE         1
#define BOARD_TH_ENABLE           1
#define BOARD_MODE_ENABLE         1   /* 通风/强力状态机 */
#define BOARD_COND_ENABLE         1   /* 条件引擎;P0/P1 可先 0 */
#define BOARD_TIMER_ENABLE        1   /* 定时引擎;P0/P1 可先 0 */
#define BOARD_NVM_ENABLE          1   /* VM 持久化;P0 可 0 */

总线约定(V1.1.0:IO 独立,无共享总线):

  • DISP=1, TH=0PG0/PG1 由显示独占,按 CT1642 时序刷;PC4/PC5 保持高阻不配。
  • DISP=0, TH=1PG0/PG1 不配;PC4/PC5 当软件 I2CAHT20)。
  • 都为 1两路各走各的,无锁、无分时(见 8.3)。
  • 都为 0:两组脚保持输入/高阻,可单独调 KEY/LED/FAN/BLE。

P0 建议先 MODE/COND/TIMER/NVM=0,台上只验按键、灯、PWM、数码管、传感器。


5. 电源与按键

5.1 上电 / 断电(V2.0.0 改:上电即开机)

  • 插 USB / 冷启动直接进入工作态,无需按键,无 3s 等待。上电流程: bsp_hw_init()app_led_init()jbInit()/userInit() → 各 app_*_init() app_function_init() 里写死 gDevData.power=1)→ app_nvm_load()user_key_init() + user_key_lock_if_held() → 起 20ms 业务节拍。
  • 关机 = 拔电。 已取消按键长按 3s 软关机(原 3s 与新的 5s/12s 时间轴直接冲突)。 app_power.c 的软关机能力(request/finalize + 关机前落盘)V2.1.0 已整模块删除 (无任何触发源,纯累赘代码);V2.2.0 进一步删除 app_function_soft_off / app_function_is_power_on / app_function_power_on_silent(同样无调用者的 死代码,power 恒 1 不再需要状态字段)。
  • 上电若键仍按着:user_key_lock_if_held() 只锁短按(防一上电就切档), 不锁长按分级 —— 所以"插电时就按住 5s"照样进配对。
  • DP0 不参与开关机,运行中恒上报 power=1(写死,非每 tick 同步),写忽略。
  • 无电池、无充电逻辑。

5.2 按住分级(运行态,key_manager.c 直接复用 key.c 长按/连按/释放事件)

上电后任意时刻按住 PB1 都按同一套时序判定(不再有"关机态/开机态"两套):

按住时长 行为
< 5000 ms 松开 短按:切档(见 5.3
满 5000 ms(仍按住) 切配对提示灯(100ms 快闪),不清绑定
满 5s 后、12s 前松开 提交配对:usr_goto_pair_mode()(断连+清绑定+重发绑定广播+ app_pair_start() 起 120s 会话(换手机用这条)
满 12000 ms(仍按住,到点即触发) 进 OTAusr_app_ota_init),不等松手;同时撤销本次配对

设计理由:配对刻意不在 5s 到点提交 —— 用户可能想继续按到 12s 进 OTA, 提前清绑定会误伤已配对的手机。

上下文约束:这几个回调跑在 20ms 硬件定时器中断上下文,只准置标志 + 切灯效(纯 RAM)。清绑定(写 VM)与 usr_app_ota_initBLE 广播切换) 一律延后到 app_function_loop()(500ms 任务上下文)执行 —— 违反会栈溢出软复位 (2026-08-31 事故定论)。因此配对/OTA 的实际生效有最多 500ms 延迟。

按住过程 LED 提示(单灯):

  • ≥5s:100ms 闪(将配网,松手生效)
  • ≥12s200ms 闪(已进入 OTA

5.3 开机态按键

事件 行为
短按(<1s 松开) 风扇档位循环:1(50%) → 2(75%) → 3(100%) → 0(关风扇) → 1 … 立刻退出通风/强力,改为手动档。设备仍开机,数码管继续刷
按住 5s 后松开 清绑定 + 进配对(见 5.2
按住 12s 进 OTA(见 5.2
长按 3s 软关机 V2.0.0 已取消,拔电即关

OTA 进行中:忽略配对/短按之外的重复分级(app_led_is_ota() 为 1 时不再重入)。 配对进行中:短按仍可切档。


6. LED(仅 1 颗,PC2 低亮)

事件 图案 优先级(高者覆盖)
软关机 / 未开机
正常开机 常亮
按住满 5s(待配网,未松手) 100ms 亮 / 100ms 灭
配对中 100ms 亮 / 100ms 灭,直到成功或 120s 失败
配对成功 常亮(可保持 3s 再回到常亮,允许直接常亮)
配对失败 200ms 闪 3s,然后常亮
OTA 200ms 亮 / 200ms 灭,无限 最高

LED 表示风扇档位, 表示温湿度告警(告警走数码管闪)。


7. 风扇 PWM

  • 引脚 PC3高有效,频率 20 kHz,占空比单位 0~10000。
  • 档位:
fan_gear 占空比 说明
0 0% 关风扇,MCPWM 可关
1 50% 5000/10000(用户拍板 2026-08-31
2 75% 7500/10000(用户拍板 2026-08-31
3 100% 10000/10000
  • 模块接口:app_fan_init / app_fan_set_gear(uint8_t) / app_fan_get_gear
  • DP2 必须等于 当前真实档位(通风停 5 分钟那段报 0)。
  • 换档无需软爬坡(P0 不做,也不做占位接口)。若 100% 上电冲击过大,后续要加 200ms 爬升时再新增 app_fan_run 接口即可,不作为 P0 验收。

8. 数码管 + AHT20IO 独立)

8.1 显示内容

管子 内容 范围
温度 2 位 整数 ℃ 0~99 显示绝对值;<0 显示 -9-1(负号用 G 段或自定义,P0 扫段后定);<−9 钳位 -9>99 钳位 99
湿度 2 位 整数 % 0~99>99 钳位 99
读失败 双管 -- 连续失败 3 次后;fault_code=2

APP DP3 仍是 0.1℃ 精度 float;显示不跟小数。

8.2 告警闪烁(与吹风解耦)

  • 任一 已使能cond_en=1 的条件组,其温度比较 当前已成立不等待 Byte4 时长)→ 温度管 500ms 亮 / 500ms 灭
  • 湿度同理。两路可同时闪。
  • 比较不再成立 → 该侧停闪,常亮显示数值。
  • 同步:fault_code=1(温湿度异常)。传感器故障优先于 1:失败达 3 次则 fault_code=2--
  • alarm_code 预留,不上报具体码(保持 0)。
  • BOARD_DISP_ENABLE=0 时不闪、不驱动管子,fault 仍可上报。

8.3 IO 独立(V1.1.02026-08-31 改板分离)

CT1642 用 PG0(CLK)/PG1(DATA)(推挽,bsp_ct1642_init() 配置),AHT20 用 PC4(SCL)/PC5(SDA)bsp_aht20_init() 配置:PC5 内部上拉 + 外部 4.7~10k 双保险)。两路物理隔离、无总线竞争,删除 bsp_bus 模块,不存在 lock/unlock/模式切换。

运行时序:

需要刷显示(每 4ms 扫描回调):
  直接发帧(CT1642 单锁存,一次只亮 1 位,靠快节拍轮询 4 位同亮)
  鬼影靠"值不变不刷新"或提速分时压(见 board_pin.h 注释与 bsp_ct1642.c

需要采 AHT20(每 2s,一次完成):
  发 0xAC 0x33 0x00 → 等 ≥80ms(期间 I2C 空闲,显示照常刷新,互不影响)
  → 读 7 字节 + CRC8

规则(V1.1.0 起):

  • 无总线锁:显示扫描(priority=1 定时器 ISR)与 AHT20 采样可同时进行,不再有"采样期冻结显示"(原共线时代每 2s 闪一下的根因已物理消除)。
  • bsp_aht20_measure_start/finish 不再返回 -2(总线忙),上层无 busy 重试。
  • 等待 AHT20 的 80ms 由 app_th_poll 状态机跨 tick 完成(期间 clr_wdt),不阻塞 20ms 主节拍。
  • 共线时代遗留约束(已作废,勿再套用):原"锁只包裹真实硬件交互窗口、长等待必放锁"适用于 PC4/PC5 共线方案,IO 分离后不需要。

8.4 采样失败与条件计时

  • 条件「持续时长」只在 本次采样成功且比较成立 时累加。
  • 采样失败:不累加、不清零已累计时长;显示保持上次有效值,直到连续 3 次失败改 --
  • -- 期间条件比较视为不成立(不误触发吹风)。

9. 工作模式(DP1

名称 行为
0 关闭模式 不按模式表驱动风扇;风扇跟手动档 / 其它仲裁胜者
1 通风 强制 1 档,开 25min,停 5min,循环,直到退出
2 强力 强制 3 档,连续 15min 后:风扇 0mode 回到 0

退出方式(任一即退):

  • 短按切档(含切到 0
  • APP 写 fan_gear
  • APP 写 mode=0
  • 定时窗结束(第 11 章)
  • 条件恢复快照(第 10 章,若当前模式是条件开的)

开机恢复(不存相位): VM 里 mode=1 则从 25min 开段 从头 计;mode=2 则从 15min 从头 计。

DP 同步:mode = 逻辑模式;fan_gear = 实际 PWM 档(通风停段为 0)。


10. 条件引擎(P27 组)

10.1 总开关

  • cond_en=0:不启动新条件;若正在因条件跑模式,视为条件结束,按 Q9 A3 恢复触发前快照
  • cond_en=1:扫描 config1~7。

10.2 7 字节布局(Excel 枚举列;备注里 Byte5 位域是笔误,以本表 Byte6 为准)

偏移 字段 编码
Byte0 使能 0=关,非 0=开
Byte12 温度阈值 有符号 int16 大端,传输值 = ℃ × 10。例 30.5℃ → 0x01 0x31;−5.0℃ → 0xFF 0xCE
Byte3 湿度阈值 0~100,单位 %
Byte4 持续分钟 1~255 按分钟计;0 = 比较一成立立即触发
Byte5 动作 模式0=关闭模式,1=通风,2=强力。不是档位
Byte6 条件 Bit7–5 温度比较,Bit4–2 湿度比较,Bit1–0 保留

比较编码(3 bit):

含义
000 无条件(忽略该侧)
001 <
010 <=
011 ==
100 >=
101 >
110/111 视为无条件

两侧都「无条件」的组视为无效,不触发。
两侧都有效时 AND。一侧无条件则只判另一侧。
无回差,比较式怎么写怎么判。

10.3 触发与保持

  1. 组使能且 cond_en=1,比较成立,开始累加持续时间(采样周期 2s,满 Byte4 分钟)。
  2. 未满时长时比较变不成立 → 该组计时清零。
  3. 满时长 → 触发:快照当前 mode+fan_gear(若尚无「条件占用」),然后切到 Byte5 模式。
  4. 触发后比较仍成立 → 保持(C2)。强力 15min 到点若仍成立 → 立即再开一轮 15min 强力Q16 A1),不要恢复快照。
  5. 比较变不成立 → 本轮条件结束,恢复快照(Q9 A3),该组重新进入「需满时长」。
  6. 另一组后触发 → 覆盖 当前条件模式,不回退条件栈D1)。快照仍是 第一次条件抢权之前 的用户/定时状态(后覆盖不改快照)。后者失效且比较不成立 → 恢复这份原始快照,即使前者比较仍成立也不自动回去(D1)。前者若要再开,必须重新满时长。
  7. 手动/APP 介入:见第 12 章,本轮条件作废(RAM 标志,不写 VM)。必须先比较不成立,再成立并满时长,才允许再次触发。

通风由条件启动时:按 25/5 循环,直到第 5/6/7 条退出。


11. 定时引擎(P27 组)

11.1 8 字节(Excel 已定义)

偏移 字段
Byte0 使能
Byte1 模式 0/1/2
Byte2 类型:0=单次,1=每天,2=每周
Byte3 星期位图 Bit0=周一 … Bit6=周日(仅类型 2)
Byte4 开始小时 0~23
Byte5 开始分钟 0~59
Byte6 结束小时
Byte7 结束分钟
  • 支持跨午夜:开始 22:00 结束 06:00 = 当晚 22 点到次日 6 点。
  • 类型 1:每天该窗。
  • 类型 2:仅位图置位的星期(跨午夜的窗,以 开始时刻所在的本地星期 判断是否该跑)。
  • 类型 0 单次:结束时刻过后把 Byte0 清 0 并写 VM,不再跑。

11.2 RTC 门闩(Q20,强制)

usr_rtc_get_time() 失败  →  全部定时不执行(等同 time_en=0)
仅 BLE 已连接但从未对时 →  同上
对时成功瞬间            →  立刻补判:若当前落在某条有效窗内,进入该窗模式

无电池,拔电后 RTC 丢失。开机后必须等 APP/网关 usr_rtc_set_from_unix 成功。

time_en=0:所有窗立刻视为结束(停风扇、mode=0),直到再置 1 并重新到开始沿或补窗。

11.3 窗内行为

  • 进入窗:切到 Byte1 模式(通风循环或强力一轮)。
  • 强力:只跑 一轮 15min,自行停下(mode=0fan=0),等到结束时刻再做一次收口(已经停则保持停)。窗内不循环强力(Q16 B1)。
  • 通风:窗内一直 25/5,直到结束时刻。
  • 结束时刻:风扇 0mode=0。
  • 结束时若条件比较仍成立:不要 立刻让条件接手,必须重新满时长(第 12 章)。

12. 仲裁(唯一决策表)

优先级 高 → 低

  1. 手动:短按、APP 写 fan_gear、APP 写 mode
  2. 定时窗RTC 有效且 time_en=1 且当前落在窗内)
  3. 条件cond_en=1 且已触发保持中)

规则:

冲突 胜者 副作用
手动 vs 正在跑的模式(无论谁开的) 手动 立刻退模式;若原是条件开的 → 条件本轮作废(RAM)
手动 vs 定时窗 手动 本窗剩余时间不再自动抢回;等到下一窗开始沿或下次对时补窗
定时窗 vs 条件 定时 条件即使已满时长也不开;窗结束后条件要重新计时长
条件 vs 空闲 条件 快照空闲态的 mode/gear
两路条件 后触发覆盖 不回栈

作废标志不存 VM。冷启动后条件从 0 重新计时(即使拔电前用户刚按停)。

cond_en/time_en 运行中被写成 0:立刻停对应自动化;条件停 → 恢复快照;定时停 → 窗结束语义。


13. 持久化(VM

存(冷启动 / 软关都保留):

  • modefan_gear
  • cond_encond_config1~7(各 7B
  • time_entime_config1~7(各 8B
  • fault_enalarm_en(若有写入)

不存:

  • DP0(忽略)
  • 条件作废标志、模式已运行秒数、AHT20 原始采样
  • BLE 三元组(走现有 CFG_USER_PAIR_INFO,出厂重置 不清

上电(V2.0.0 起无需按键)后:加载上述字段,按上次 mode/gear 从周期起点继续工作

首次上电(VM 无效):mode=0,gear=0,条件/定时全 0cond_en=time_en=0

写 VM 时机:APP 改配置后、单次定时自毁使能时、档位/模式稳定变化时(可做 500ms 防抖,避免短按循环打爆 Flash)。


14. 出厂重置(DP25 写 1

  • 清全部业务 VM(第 13 章列表)。
  • 保留 BLE 配对,不断开、不软复位。
  • RAM 中 mode=0gear=0,风扇停,条件/定时停止。
  • 换机:运行中 按住 5s 后松手 清配对进绑定广播(见 5.2,无需关机)。

15. BLE / DP 行为摘要

身份:CBR3 / PRCBR3。帧格式见已生成 jb_protocol.h(重生后以新头文件为准)。

DP 处理
0 power 预留。运行中上报 1,写忽略
1 mode 写则手动抢权,切通风/强力/关
2 fan_gear 写则手动抢权,退模式
3/4 温湿度 仅上报。变化防抖 2s,全量 10min(协议已有)
5 cond_en 见第 10/12 章
6~12 cond_config 7 字节 memcpy + 存 VM + 条件状态机复位该组计时
13 time_en 见第 11 章
14~20 time_config 8 字节 + 存 VM
21/23 en 原样存,P0 不做位级过滤
22 fault_code 0 无 / 1 温湿度比较异常 / 2 传感器故障
24 alarm_code 保持 0
25 factory_reset 第 14 章
26~28 存 RAM/VM 可不实现动作

OTA:运行态按住 12s 走门夹 usr_app_ota_init(在 app_function_loop 任务上下文调用,灯效 200ms 闪)。jbOnOtaNotify 等协议 OTA P2 可继续 stub,注释写明未实现,禁止假装升级成功后擦用户区。

配对超时 120s,失败保持开机。


16. 建议文件清单(新建,门夹已有的拷贝改)

apps/usr_periph/
  board_pin.h          # 引脚、时序、BOARD_xxx_ENABLE
  bsp_hw.c/.h          # LED/KEY/FAN GPIO+MCPWM、sleep IO
  bsp_ct1642.c/.h      # PG0/PG1,移位、锁存、送出
  bsp_aht20.c/.h       # PC4/PC5,软件 I2C + 测量
  usr_rtc.c/.h         # 已有则保留

apps/usr_app/
  key.c/.h, key_manager.c/.h     # 拷贝门夹;短按切档 + 按住 5s/12s 分级
  led.c/.h, led_manager.c/.h     # 改单灯
  app_led.c/.h                   # 含长按 5s "将配网"提示灯
  app_defer.c/.h                 # ISR→任务上下文延后执行标志位(NVM/配对/OTA 统一收口)
  app_pair.c/.h
  app_fan.c/.h
  app_disp.c/.h                  # 数值、闪烁、--
  app_th.c/.h                    # 2s 采样、失败计数
  app_mode.c/.h                  # 通风/强力(重写,勿用门夹电机表)
  app_cond.c/.h                  # P2
  app_timer_task.c/.h            # P2,避免与 usr_rtc 文件名混
  app_nvm.c/.h
  app_function.c/.h              # DP 与按键总装
  app_arbiter.c/.h               # 可选:把第 12 章收成一处,避免 mode/cond/timer 互调成环

usr_jb_main.c 只编排,不写算法。


17. 分阶段交付与验收

P0 — 台上 Bring-up(不依赖 APP

目标: 两路 IOCT1642 显示 / AHT20 采集)、PWM、按键、灯可独立开关验证。

  • board_pin.h + UART TX=PG6,打印正常,且不影响 PC5
  • 插电即开机(不按键,直接进工作态、数码管亮、LED 常亮)
  • 长按 3s 不再关机(已取消软关机按键入口)
  • 短按(<5s 松手)四态循环,示波器/听感确认 50/75/100/0%
  • 按住满 5s → 100ms 快闪;此时松手 → 清绑定进配对(APP 可搜到)
  • 按住满 5s 不松手继续到 12s → 不进配对,直接进 OTA(200ms 闪)
  • LED 开机常亮、模拟配对闪、模拟 OTA 闪(可用宏强制)
  • CT1642 两管能稳定显示写死的 2560,无鬼影
  • AHT20 串口打印 ℃/%,CRC 正确;采数时数码管不闪、不乱码(IO 独立,无需锁存)
  • BOARD_TH_ENABLE=0 时显示仍可刷;BOARD_DISP_ENABLE=0 时传感器仍可打印
  • 12s 能进 OTA 广播(可先只打日志 + LED)
  • 长按进配对/OTA 后不复位(验证动作确实走的任务上下文)

P0 关闭: BOARD_MODE/COND/TIMER/NVM_ENABLE=0

P1 — 最小产品闭环

  • 5s 配对,APP 能读温湿度,能写档位/模式
  • 通风 25/5、强力 15min 后停;短按立即退模式
  • 超限闪(可用调试宏灌假温度)、失败 --
  • DP0 写 0 风扇不变、设备不关
  • VM:改档/改模式/写下条件定时后断电,重新上电从周期头恢复 mode
  • 定时引擎可只存不跑(BOARD_TIMER_ENABLE=0 仍能保存 DP

P2 — 自动化

  • 条件 7 字节解析与 AND、时长、快照恢复、后覆盖不回栈
  • 条件强力循环 15min(仍超限再来一轮)
  • 手动作废条件,须回安全再满时长
  • 定时 RTC 门闩:未对时绝不跑;对时后补窗
  • 跨午夜、单次自毁使能
  • 定时强力只一轮;窗结束收口;结束后条件重新计时
  • 出厂重置清业务留配对
  • 仲裁表第 12 章逐条用例(见下)

P2 仲裁最小用例(必须写进测试笔记)

  1. 手动 2 档 → 条件触发通风 → 应切通风;短按到 0 → 风扇停且保持停,即使仍超限。
  2. 用例 1 之后温湿度回到安全再超限,满时长才再次通风。
  3. 定时窗启动强力 → 15min 停 → 窗未结束前保持停 → 结束时刻仍停。
  4. 定时窗内再短按切 1 档 → 保持 1 档到窗结束也不要被定时抢回。
  5. 未对时,把设备时钟拨乱或不对时 → 定时配置使能也不转。对时后若在窗内立即进模式。
  6. 两组条件先后触发,后者强力覆盖通风;后者失效恢复 手动快照 而不是第一组通风。

18. board_pin.h 时序宏(实现时抄这份数)

#define BOARD_UART_TX_PIN         IO_PORTG_06
#define BOARD_DISP_CLK_PIN        IO_PORTG_00
#define BOARD_DISP_DATA_PIN       IO_PORTG_01
#define BOARD_TH_SCL_PIN          IO_PORTC_04
#define BOARD_TH_SDA_PIN          IO_PORTC_05
#define BOARD_FAN_PIN             IO_PORTC_03
#define BOARD_LED_PIN             IO_PORTC_02
#define BOARD_KEY_PIN             IO_PORTB_01

#define BOARD_LED_ON_LEVEL        0
#define BOARD_FAN_ON_LEVEL        1
#define BOARD_KEY_ACTIVE_LEVEL    0

#define BOARD_KEY_POWER_MS        3000
#define BOARD_KEY_PAIR_MS         5000
#define BOARD_KEY_OTA_MS          12000
#define BOARD_PAIR_TIMEOUT_MS     (120u * 1000u)

#define BOARD_LED_FAST_ON_MS      100
#define BOARD_LED_FAST_OFF_MS     100
#define BOARD_LED_OTA_ON_MS       200
#define BOARD_LED_OTA_OFF_MS      200
#define BOARD_DISP_BLINK_MS       500

#define BOARD_FAN_PWM_HZ          20000
#define BOARD_FAN_DUTY_GEAR1      5000
#define BOARD_FAN_DUTY_GEAR2      7500
#define BOARD_FAN_DUTY_GEAR3      10000

#define BOARD_TH_PERIOD_MS        2000
#define BOARD_TH_FAIL_MAX         3
#define BOARD_AHT20_WAIT_MS       80

#define BOARD_MODE_VENT_ON_MS     (25u * 60u * 1000u)
#define BOARD_MODE_VENT_OFF_MS    (5u  * 60u * 1000u)
#define BOARD_MODE_POWER_ON_MS    (15u * 60u * 1000u)

19. 风险与不要擅自发挥的点

风险 要求
Excel 备注 Byte5 vs 枚举 Byte5/6 以本文第 10.2 节为准
CT1642 鬼影/异显微亮 IO 独立不解决:单锁存 + 移位实时输出的固有特性,软件靠"值不变不刷新"/提速分时压;残余微亮需 Q2-Q9 加 1~10k 硬件下拉
10k 上拉偏弱 软件 I2C 放慢;不行再提硬件改 4.7k(SDA 建议外部 4.7k
条件强力循环可能把箱体吹干 已拍板(Q16 A1),不要擅自改成通风兜底
门夹 USB 插入直通开机 禁止 移植
协议 OTA stub 返回 OK 注释标明未升级;P2 不要装作已刷写
Makefile 引用缺失 board 文件 产品只编实际存在的 board_jl701n_demo.c
负温度段码 P0 扫段后在 app_disp 注释画真值表

20. 给 WorkBuddy 的推荐开工顺序

  1. 按 Excel 重生 7 字节 jb_protocol / dp_definition.json
  2. 新建 board_pin.hUART 改 PG6,关掉 PC3/4/5 上的 demo 外设。
  3. 移植 key/led/boot/power/pair(删电池与 USB 直通)。
  4. bsp_hw 风扇 PWM + LED + KEY。
  5. bsp_ct1642PG0/PG1)写死 25/60。
  6. bsp_aht20 串口打印,再接到显示。
  7. P1app_mode + DP 同步 + NVM。
  8. P2app_cond + app_timer_task + 仲裁用例。

每一步都保持对应 BOARD_xxx_ENABLE 可关。


文档结束。 行为争议以本文第 5~12 章为准;字节布局以第 10.2 / 11.1 节与 Excel「枚举/值定义」列为准。