39 KiB
温湿度风扇固件需求规格书
| 项 | 内容 |
|---|---|
| 产品 | 温湿度风扇(爬宠) |
| 芯片 | JL7016 / AC7016C(BR28) |
| 工程 | 温湿度风扇_soc_JieLi-JL7016 |
| 产品码 | ProdtCode=CBR3,productCode=PRCBR3 |
| 协议 | 见宝 SOC 55AA,V1.0.0(2026-09-02 重生:删条件 DP,加联动 / 运行记录 / 心跳预留) |
| 文档版本 | V3.0.1 |
| 日期 | 2026.09.04 |
| 作者 | cyWu |
| 读者 | WorkBuddy / 固件开发 |
| 参考工程 | D:\Jianbao\Project\产品\宠物\门夹弹力绳\门夹弹力绳_soc_JieLi-JL7016 |
| 新协议生成工程 | D:\Jianbao\Project\产品\爬宠\温湿度风扇\温湿度风扇_soc_JieLi-JL7016_新协议 |
V3.0.1 变更(2026-09-04): DP 号收紧为连续 0
24。20→8time_en13→7,time_config1~71414,30→22~24。7–12 不再空置。以run_record21→15,heartbeat22→16,fault_en/code23/24→17/18,alarm_en/code25/26→19/20,factory_reset27→21,reserva_dp1~328jb_protocol.h/dp_definition.json为准。V3.0.0 变更(2026-09-02): 协议 DP 重生。删除
cond_en/cond_config1~7(7 组条件引擎整段作废)。新增 DP5linkage_delete、DP6linkage_config(20B,缸体 + 温湿度 min/max)、当时号为 DP21run_record(现 DP15)、DP22heartbeat(现 DP16,本版不做)。故障/告警/复位号后移,当时factory_reset为 DP27(现 DP21)。联动按实时温湿度回差开 1 档 / 关风扇,不改 mode;仲裁改为手动 > 定时 > 联动。运行记录按风扇实际在转累加秒,满 10 分钟写 VM。生成协议MAX_PACKAGE_LEN必须改回 256(124B 的run_record装不进 128)。旧条件 VM 不迁移。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 帧。
本文是 已拍板的共同理解,不是草稿。实现时以本文为准;DP 表以 dp_definition.json + 本文第 10.2 / 11.1 / 15 章为准。Excel / JSON 描述与本文冲突时:多字节字段的偏移与开停行为以本文为准(JSON 对 DP6 只写了类型摘要,min/max 顺序以 10.2 为准)。
0. WorkBuddy 开干前必读(硬约束)
- 先改协议再写业务。 从
_新协议工程拷贝已生成的jb_protocol.*/dp_definition.json/jb_product骨架,再把现有jb_product.c业务回调迁过去(禁止用空壳 TODO 覆盖业务)。拷贝后立刻把MAX_PACKAGE_LEN从生成值 128 改回 256,否则 DP15run_record(124B)组不出完整帧。 - 不要把业务算法写进
jb_product.c。 只把 DP 写入gDevData并转调app_*。组合根是usr_jb_main.c(照门夹)。 - 不要抄门夹的电池 / 低电拒绝开机。 本机 USB 常电、无电池。上电即开机(V2.0.0 起,不再长按 3s 才开机);拔电即关,无软关机深睡。
- 不要把调试 UART 放在 PC5。 调试 UART TX = PG6(
IO_PORTG_06)。PC5 是 AHT20 SDA(独立使用)。 - DP0
power预留,不当开关。 上电恒为开机态(power=1),运行中上报power=1,写忽略;停机靠短按切到 0 档或拔电。 - CT1642 与 AHT20 已 IO 分离(V1.1.0)。 CT1642 走 PG0(CLK)/PG1(DATA),AHT20 走 PC4(SCL)/PC5(SDA),两路独立、无总线竞争,不存在分时/总线锁(见 8.3)。
- 每个模块必须能用
board_pin.h的BOARD_xxx_ENABLE单独关掉 仍能编过、能配网上报(关掉的模块返回空操作)。 - 日志全部 英文。中文只出现在注释和本文。
- 编码风格对齐门夹 + 工程师规范:文件小写+下划线,
g_/s_前缀,枚举Pascal_t,接口xxx_init/xxx_run或 tick /xxx_get_status。 - 禁止继续实现 7 组条件引擎。
app_cond整模块替换为联动(app_linkage);旧CFG_USER_CBR3_COND作废,不读旧 49B。 - DP16 心跳本版忽略。 温湿度仍按协议 2s 变化防抖 / 10min 全量上报,不因 12s 无心跳停报。
1. 产品一句话
USB 供电的爬宠温湿度风扇:本地按键调档,双两位一体数码管显示整数温湿度,BLE 连 APP;APP 可下发通风/强力模式、1 条缸体联动配置、7 组定时。联动根据实时温湿度与 min/max 回差开 1 档或关风扇;定时还必须 RTC 已对时。风扇实际在转的秒数记 31 天运行记录。
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.c 的 io_ext_interrupt_test() 是 while(1) 演示函数,产品不调用),可直接作 CT1642 引脚。
2.2 显示器件
- 驱动:类 CT1642,CLK+DATA 串行(原理图位号 S1)。
- 两块 2 位 7 段数码管:温度一块、湿度一块(含 DP 段,本产品 不显示小数,DP 恒灭)。
- 帧协议(2026-08-29 实测定型,与 CSDN yitx 一致):一帧 18bit,CLK 上升沿采样 DATA,顺序
C1-C4 位选(4bit,低有效,入参 ~com 先发 C1)→ 6bit 保留(固定填 1)→ Q2-Q9 段码(8bit,LSB 先,seg bit0 → Q2)。 - DATA 只允许在 CLK=低 时改变。
- 发完 18bit:CLK=高时 DATA 上升沿 = 锁存;CLK=低时 DATA 上升沿 = 输出刷新(一次只点亮锁存的那 1 位)。
- CT1642 内部只有 1 个 18 位移位 + 1 个 12 位锁存,无按位显示 RAM、不自动扫描 4 位,必须 MCU 持续快节拍轮询(
BOARD_DISP_SCAN_MS=4)4 位才能同亮不闪。
段码与 COM 的 PCB 网络为 SET1~8、CAN1~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_linkage_init() / app_timer_init() / app_runrec_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):
jbTimerIrq()× dtuser_key_scan+user_key_handleapp_th_poll(内部 2s 才真正采;IO 分离后采集与显示互不干扰)app_mode_run(通风/强力状态机;联动/定时引擎在 500ms 任务上下文,见下)app_disp_run(闪烁、刷新)app_function_poll(同步 DP)app_runrec_tick(风扇在转则累加秒;满 10 分钟只打脏,不写 flash)app_nvm_tick(变化检测 + 500ms 防抖计时,不写 flash)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_linkage_run()/app_timer_run()(联动/定时引擎,
仲裁见第 12 章)、app_runrec_rotate_if_needed()、app_function_loop()
(VM 落盘 + 配对/OTA 落地)。
可直接复用门夹源码(拷贝后改引脚/事件表,不要改框架语义):
| 复用 | 路径 | 改什么 |
|---|---|---|
| 按键 FSM | key.c / key.h / key_manager.c |
短按切档;按住分级 5s 配对 / 12s OTA(V2.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 |
从 _新协议 工程拷贝后改 MAX_PACKAGE_LEN=256,迁回业务回调 |
不要拷贝: app_battery.c、充电 GPIO、电机 app_mode 步进表(本产品 mode 是通风/强力,重写)。
4. 模块开关(独立使能 / 屏蔽调试)
全部放 apps/usr_periph/board_pin.h。0 时:对应 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_LINKAGE_ENABLE 1 /* 联动引擎;替换原 BOARD_COND_ENABLE */
#define BOARD_TIMER_ENABLE 1 /* 定时引擎;P0/P1 可先 0 */
#define BOARD_RUNREC_ENABLE 1 /* 31 天运行记录 */
#define BOARD_NVM_ENABLE 1 /* VM 持久化;P0 可 0 */
总线约定(V1.1.0:IO 独立,无共享总线):
DISP=1, TH=0:PG0/PG1 由显示独占,按 CT1642 时序刷;PC4/PC5 保持高阻不配。DISP=0, TH=1:PG0/PG1 不配;PC4/PC5 当软件 I2C(AHT20)。- 都为 1:两路各走各的,无锁、无分时(见 8.3)。
- 都为 0:两组脚保持输入/高阻,可单独调 KEY/LED/FAN/BLE。
P0 建议先 MODE/LINKAGE/TIMER/RUNREC/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(仍按住,到点即触发) | 进 OTA(usr_app_ota_init),不等松手;同时撤销本次配对 |
设计理由:配对刻意不在 5s 到点提交 —— 用户可能想继续按到 12s 进 OTA, 提前清绑定会误伤已配对的手机。
⚠ 上下文约束:这几个回调跑在 20ms 硬件定时器中断上下文,只准置标志 +
切灯效(纯 RAM)。清绑定(写 VM)与 usr_app_ota_init(BLE 广播切换)
一律延后到 app_function_loop()(500ms 任务上下文)执行 —— 违反会栈溢出软复位
(2026-08-31 事故定论)。因此配对/OTA 的实际生效有最多 500ms 延迟。
按住过程 LED 提示(单灯):
- ≥5s:100ms 闪(将配网,松手生效)
- ≥12s:200ms 闪(已进入 OTA)
5.3 开机态按键
| 事件 | 行为 |
|---|---|
| 短按(<1s 松开) | 风扇档位循环:1(50%) → 2(75%) → 3(100%) → 0(关风扇) → 1 … 立刻退出通风/强力,改为手动档;联动本轮作废(第 12 章)。设备仍开机,数码管继续刷 |
| 按住 5s 后松开 | 清绑定 + 进配对(见 5.2) |
| 按住 12s | 进 OTA(见 5.2) |
| 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. 数码管 + AHT20(IO 独立)
8.1 显示内容
| 管子 | 内容 | 范围 |
|---|---|---|
| 温度 2 位 | 整数 ℃ | 0~99 显示绝对值;<0 显示 -9~-1(负号用 G 段或自定义,P0 扫段后定);<−9 钳位 -9;>99 钳位 99 |
| 湿度 2 位 | 整数 % | 0~99,>99 钳位 99 |
| 读失败 | 双管 -- |
连续失败 3 次后;fault_code=1 |
APP DP3 仍是 0.1℃ 精度 float;显示不跟小数。
8.2 告警闪烁(与吹风解耦)
有 有效联动配置(第 10.1 节)时,按 是否落在 [min, max] 闭区间内 闪,不等风扇真的开:
- 温度管:
T > Tmax或T < Tmin→ 500ms 亮 / 500ms 灭。Tmin ≤ T ≤ Tmax(含等于)→ 常亮。 - 湿度管:
H > Hmax或H < Hmin→ 同样 500ms 闪。Hmin ≤ H ≤ Hmax→ 常亮。 - 两路可同时闪。过冷、过干 只闪不开风扇(风扇开停见 10.3)。
- 跟联动是否被手动作废无关:超范围仍闪,方便看见超标。
- 无有效联动:不因温湿度闪。
- 温湿度出范围只驱动对应管子闪烁,不上报
fault_code。 - 传感器连续失败 3 次:显示
--,同步fault_code=1。fault_code=2预留。 alarm_code预留,不上报具体码(保持 0)。BOARD_DISP_ENABLE=0时不闪、不驱动管子,fault 仍可上报。
8.3 IO 独立(V1.1.0,2026-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 次失败改
--。 --期间联动视为不成立:若当前是联动占着风扇 → 关掉(fan_gear=0),避免盲吹。- 过冷、过干可以闪管子,不会因为过冷/过干去开风扇。
9. 工作模式(DP1)
| 值 | 名称 | 行为 |
|---|---|---|
| 0 | 关闭模式 | 不按模式表驱动风扇;风扇跟手动档 / 定时 / 联动 |
| 1 | 通风 | 强制 1 档,开 25min,停 5min,循环,直到退出 |
| 2 | 强力 | 强制 3 档,连续 15min 后:风扇 0,mode 回到 0 |
退出方式(任一即退):
- 短按切档(含切到 0)
- APP 写
fan_gear - APP 写
mode=0 - 定时窗结束(第 11 章)
联动 不改 mode:开只把 fan_gear 写成 1,关只写成 0。通风/强力仍只由手动和定时启动。
开机恢复(不存相位): VM 里 mode=1 则从 25min 开段 从头 计;mode=2 则从 15min 从头 计。
DP 同步:mode = 逻辑模式;fan_gear = 实际 PWM 档(通风停段为 0)。
10. 联动引擎(替换原 7 组条件)
无 cond_en。设备只有 1 条 20 字节联动配置(DP6)。不切通风/强力,不恢复档位快照。
10.1 有效配置
同时满足才跑联动开停(闪烁仍见 8.2,只要有效配置就判闪):
- 缸体编号 ≠ 0(Byte0–1,uint16 大端)
Tmin ≤ Tmax且Hmin ≤ HmaxBOARD_LINKAGE_ENABLE=1
否则视为无联动:不自动开 1 档;若原先是联动占着风扇则关掉。
缸体编号只存、不参与开停判断(本机一路 AHT20)。配置版本(Byte6)只存不解释。Byte7–19 预留:整包 20B 原样落盘,控制逻辑忽略。
10.2 20 字节布局(DP6 linkage_config)
| 偏移 | 字段 | 编码 |
|---|---|---|
| Byte0–1 | 缸体编号 | uint16 大端 |
| Byte2 | 最低温度 Tmin |
有符号 int8,单位 ℃ |
| Byte3 | 最高温度 Tmax |
有符号 int8,单位 ℃ |
| Byte4 | 最低湿度 Hmin |
uint8,单位 % |
| Byte5 | 最高湿度 Hmax |
uint8,单位 % |
| Byte6 | 配置版本 | uint8,只存不用 |
| Byte7–19 | 预留 | 只存不用 |
温度比较用设备内部 0.1℃(temp_x10):T > Tmax 等价 temp_x10 > Tmax * 10。湿度用整数 %。没有等于比较:T == Tmax 不算超最高,也不算低于最低。
10.3 开停(回差 + 超最高优先)
在有效配置、未被手动作废、且仲裁允许联动占风扇时,每个 500ms 任务 tick 判一次:
开(一侧满足即可): T > Tmax 或 H > Hmax → fan_gear = 1,不改 mode。一直保持到关条件成立。
关(一侧满足即可,但超最高优先):
- 若仍有一侧
> max→ 保持开(例:33℃ 且湿度已低于Hmin→ 继续吹)。 - 否则,若至少一侧
< min→fan_gear = 0。 - 否则(两侧都落在
[min, max]闭区间,含等于)→ 保持上一态。
过冷、过干 不开风扇(T < Tmin 或 H < Hmin 只参与「可以关」和数码管闪)。
不恢复触发前档位:联动开 = 写 1,联动关 = 写 0。
10.4 联动删除(DP5)
| 写入 | 行为 |
|---|---|
| 0 | 无操作 |
| 1 | 清 RAM + VM 里 20 字节配置(全 0);若当前是联动占着风扇 → fan_gear=0;然后把 linkage_delete 清回 0 再上报(脉冲) |
10.5 APP 重写 DP6
memcpy 20 字节 → 存 VM → 清手动作废标志 → 立刻按新 min/max 重判(该开开、该关关)。缸体编号写成 0:按无效配置处理,等同删除(联动占着则关)。
11. 定时引擎(P2,7 组)
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 门闩(强制)
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=0,fan=0),等到结束时刻再做一次收口(已经停则保持停)。窗内不循环强力。
- 通风:窗内一直 25/5,直到结束时刻。
- 结束时刻:风扇 0,mode=0。然后 立刻按第 10.3 节判一次联动(不是手动作废):仍超最高 → 开 1 档。
12. 仲裁(唯一决策表)
优先级 高 → 低:
- 手动:短按、APP 写
fan_gear、APP 写mode - 定时窗(RTC 有效且
time_en=1且当前落在窗内) - 联动(有效配置且已因超最高占着风扇,或本 tick 满足开条件)
规则:
| 冲突 | 胜者 | 副作用 |
|---|---|---|
| 手动 vs 正在跑的模式 / 联动 | 手动 | 立刻退模式;若原是联动占着 → 本轮联动作废(RAM,不写 VM) |
| 手动 vs 定时窗 | 手动 | 本窗剩余时间不再自动抢回;等到下一窗开始沿或下次对时补窗 |
| 定时窗 vs 联动 | 定时 | 窗内联动不开、不改档;窗结束收口后立刻按 10.3 重判(见 11.3) |
| 联动 vs 空闲 | 联动 | 开则写 fan_gear=1,关则写 0;不改 mode、不快照 |
作废标志不存 VM。
手动作废后的再武装: 必须先走到第 10.3 节的「关」条件(没有一侧还超最高,且至少一侧 < min),再重新出现 T > Tmax 或 H > Hmax,才允许再开 1 档。作废期间数码管仍按 8.2 闪。
冷启动: 作废标志是 RAM,上电为空。VM 里即使恢复的是手动 2/3 档,只要已超最高且无定时窗占用,允许联动立刻改成 1 档。用户不想吹再短按,那一次才作废。
time_en 运行中被写成 0:立刻停定时(窗结束语义)。无联动总开关;无效配置 / DP5 删除即停联动。
13. 持久化(VM)
存(冷启动保留):
mode、fan_gearlinkage_config(20B)time_en、time_config1~7(各 8B)fault_en、alarm_en(若有写入)run_record:31 × uint32 秒 + slot[0] 对应的本地日期(对时后才有意义)
不存:
- DP0(忽略)
- 联动作废标志、模式已运行秒数、AHT20 原始采样
linkage_delete(脉冲,RAM 瞬态)- BLE 三元组(走现有
CFG_USER_PAIR_INFO,出厂重置 不清)
上电后:加载上述字段,按上次 mode/gear 从周期起点继续工作,再让联动按第 12 章冷启动规则判一次。
首次上电(VM 无效):mode=0,gear=0,联动 20B 全 0,定时全 0,time_en=0,31 天运行记录全 0。
旧 CFG_USER_CBR3_COND(49B 条件)作废不读。联动 / 运行记录换新 VM key 或升 ver,当新设备。
写 VM 时机:
- APP 改联动/定时、单次定时自毁、档位/模式稳定变化:500ms 防抖(避免短按循环打爆 Flash)
- 运行记录:风扇累计转满 10 分钟 写一次;跨天旋转时立刻写(见 13.1)
13.1 运行记录(DP15 run_record,RO,124B)
31 天 × 4 字节,每格 uint32 大端,单位秒:
| 偏移 | 含义 |
|---|---|
| Byte0–3 | 今天风扇运行总秒数 |
| Byte4–7 | 昨天 |
| … | … |
| Byte120–123 | 前 30 天 |
什么叫在运行: 风扇实际在转(PWM 占空比 > 0 / fan_gear != 0 的真实输出)。手动、通风开段、强力、定时、联动都算。通风停 5 分钟那段 不算。
日期轴:
- 未对时:只往 slot[0] 累加,不旋转。
- 对时成功:按本地日历把数组左移 N 天(跨了几天就移几天),新今天从 0 或当天已累加值继续。
- 掉电再上电:先把 VM 里 31 格灌进
gDevData.run_record;未对时继续往 slot[0] 加。
上报: 今天累计秒数相对上次 0x07 上报满 60 秒 才进差分(JB_RUNREC_DIFF_SEC);跨天旋转立刻带 DP15。APP 0x05 读、10 分钟全量仍带完整 124B。其它 DP 上报时不刷新运行记录基准。
落盘: 每累计运行满 600 秒写一次 VM。跨天立刻写。拔电没有软关机钩子,接受最多丢不满 10 分钟的秒数。
BOARD_RUNREC_ENABLE=0:不累加、DP15 报全 0。
14. 出厂重置(DP21 写 1)
- 清全部业务 VM(第 13 章列表,含联动 20B 与 31 天运行记录)。
- 保留 BLE 配对,不断开、不软复位。
- RAM 中 mode=0,gear=0,风扇停,联动/定时停止,作废标志清掉。
- 换机:运行中 按住 5s 后松手 清配对进绑定广播(见 5.2,无需关机)。
15. BLE / DP 行为摘要
身份:CBR3 / PRCBR3。帧格式见已生成 jb_protocol.h(以 _新协议 拷贝后、MAX_PACKAGE_LEN=256 的头文件为准)。DP 号 0–24 连续,不要填回条件配置。
| DP | 处理 |
|---|---|
| 0 power | 预留。运行中上报 1,写忽略 |
| 1 mode | 写则手动抢权,切通风/强力/关 |
| 2 fan_gear | 写则手动抢权,退模式,联动作废 |
| 3/4 温湿度 | 仅上报。变化防抖 2s,全量 10min(协议已有) |
| 5 linkage_delete | 第 10.4 节,脉冲 |
| 6 linkage_config | 20 字节 memcpy + 存 VM + 第 10.5 节立刻重判 |
| 7 time_en | 见第 11 章 |
| 8~14 time_config | 8 字节 + 存 VM |
| 15 run_record | RO,由 app_runrec 填 124B,禁止 APP 写。差分满 1 分钟或跨天才 0x07 |
| 16 heartbeat | 本版忽略(写了也不停温湿度上报) |
| 17 fault_en | 原样存,P0 不做位级过滤 |
| 18 fault_code | 0 无 / 1 传感器故障 / 2 预留 |
| 19 alarm_en | 原样存 |
| 20 alarm_code | 保持 0 |
| 21 factory_reset | 第 14 章 |
| 22~24 reserva | 存 RAM,可不实现动作 |
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_linkage.c/.h # 替换 app_cond;DP5/DP6 + 回差开停
app_runrec.c/.h # 31 天秒数、10 分钟落盘、跨天旋转
app_timer_task.c/.h # P2,避免与 usr_rtc 文件名混
app_nvm.c/.h # 换掉 cond 49B,改为 linkage 20B + run_record
app_function.c/.h # DP 与按键总装
app_arbiter.c/.h # 第 12 章:COND owner 改为 LINKAGE
usr_jb_main.c 只编排,不写算法。删除或抽空 app_cond.c/.h,禁止两套引擎并存。
17. 分阶段交付与验收
P0 — 台上 Bring-up(不依赖 APP)
目标: 两路 IO(CT1642 显示 / 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 两管能稳定显示写死的
25和60,无鬼影 - AHT20 串口打印 ℃/%,CRC 正确;采数时数码管不闪、不乱码(IO 独立,无需锁存)
BOARD_TH_ENABLE=0时显示仍可刷;BOARD_DISP_ENABLE=0时传感器仍可打印- 12s 能进 OTA 广播(可先只打日志 + LED)
- 长按进配对/OTA 后不复位(验证动作确实走的任务上下文)
P0 关闭: BOARD_MODE/LINKAGE/TIMER/RUNREC/NVM_ENABLE=0
P1 — 最小产品闭环
- 5s 配对,APP 能读温湿度,能写档位/模式
- 通风 25/5、强力 15min 后停;短按立即退模式
- 失败
--;有联动配置时超范围闪(可用调试宏灌假温度) - DP0 写 0 风扇不变、设备不关
- VM:改档/改模式/写下联动与定时后断电,重新上电从周期头恢复 mode
- 定时引擎可只存不跑(
BOARD_TIMER_ENABLE=0仍能保存 DP) - 新协议 DP 号:读不到旧的 cond_config;能读写 DP6 20B、能读 DP15
P2 — 自动化
- DP6 按 10.2 解析;缸体号 0 / min>max 不跑联动
- 超最高开 1 档;回差保持;超最高优先(热着湿已低于最低仍吹)
- 过冷过干不吹,但出范围闪对应管子
- 手动作废后须先到「关」条件再超最高才再开
--时联动占着则关风扇- 定时 RTC 门闩:未对时绝不跑;对时后补窗
- 跨午夜、单次自毁使能
- 定时强力只一轮;窗结束收口后立刻把联动判一次
- DP5 脉冲删除;DP6 重写立刻重判
- 运行记录:转 10 分钟落盘;对时后跨天左移;通风停段不计入
- 出厂重置清业务(含运行记录)留配对
MAX_PACKAGE_LEN=256,APP 能读完整 124B 运行记录- 仲裁表第 12 章逐条用例(见下)
P2 仲裁最小用例(必须写进测试笔记)
- 空闲超最高 → 风扇 1 档;短按到 0 → 保持停,即使仍超最高。
- 用例 1 之后先到「关」条件(至少一侧
< min且没有一侧超最高),再超最高,才再次 1 档。 - 定时窗启动强力 → 15min 停 → 窗未结束前保持停 → 结束时刻仍停;若此时仍超最高 → 开 1 档(联动接手)。
- 定时窗内再短按切 2 档 → 保持 2 档到窗结束也不要被定时抢回;窗结束若仍超最高且已被手动作废 → 不要被联动立刻改回 1 档。
- 未对时,定时配置使能也不转。对时后若在窗内立即进模式。
- 上电 VM 为 2 档且已超最高、无定时窗 → 联动立刻改 1 档。
- 33℃ + 湿度已低于
Hmin→ 风扇保持 1 档(超最高优先)。 - 20℃、湿度在带内、风扇已关 → 保持关(过冷不吹),温度管闪。
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)
#define BOARD_RUN_RECORD_DAYS 31
#define BOARD_RUN_RECORD_SAVE_SEC 600 /* 累计运行 10 分钟写一次 VM */
19. 风险与不要擅自发挥的点
| 风险 | 要求 |
|---|---|
| JSON 对 DP6 只写了「温度 int8、湿度/版本 uint8」 | 以本文第 10.2 节 先 min 后 max 为准,不要猜成单阈值 |
生成协议 MAX_PACKAGE_LEN=128 |
必须改 256,否则 DP15 组帧失败 |
| 旧条件 49B VM | 不迁移、不解析;当无联动 |
| CT1642 鬼影/异显微亮 | IO 独立不解决:单锁存 + 移位实时输出的固有特性,软件靠"值不变不刷新"/提速分时压;残余微亮需 Q2-Q9 加 1~10k 硬件下拉 |
| 10k 上拉偏弱 | 软件 I2C 放慢;不行再提硬件改 4.7k(SDA 建议外部 4.7k) |
| 联动开 1 档可能一直吹 | 已拍板回差 + 超最高优先,不要擅自加时长或改切通风 |
| 门夹 USB 插入直通开机 | 禁止 移植 |
| 协议 OTA stub 返回 OK | 注释标明未升级;P2 不要装作已刷写 |
Makefile 引用缺失 board 文件 |
产品只编实际存在的 board_jl701n_demo.c |
| 负温度段码 | P0 扫段后在 app_disp 注释画真值表 |
| 心跳 12s 停报 | 本版明确不做,不要「顺手实现」 |
20. 给 WorkBuddy 的推荐开工顺序
- 从
_新协议拷贝jb_protocol.*/dp_definition.json,MAX_PACKAGE_LEN改 256;把现有jb_product.c业务迁到新 DP 号(不要用空壳覆盖)。 app_nvm:丢掉 cond 49B,改为 linkage 20B + run_record;升版本。- 新建
app_linkage(替换app_cond),仲裁 owner 改为 LINKAGE。 - 新建
app_runrec:20ms 累加、10 分钟脏、对时旋转、灌 DP15。 app_disp闪烁改 8.2(出 [min,max] 就闪)。jbEventProcess:DP5 脉冲删除、DP6 重写重判、DP16 忽略、DP21 出厂重置。- 按第 17 章 P2 用例逐条验。
每一步都保持对应 BOARD_xxx_ENABLE 可关。
文档结束。 行为争议以本文第 5~13 章为准;字节布局以第 10.2 / 11.1 / 13.1 节为准。