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

674 lines
33 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 温湿度风扇固件需求规格书
| 项 | 内容 |
|---|---|
| 产品 | 温湿度风扇(爬宠) |
| 芯片 | JL7016 / AC7016CBR28 |
| 工程 | `温湿度风扇_soc_JieLi-JL7016` |
| 产品码 | ProdtCode=`CBR3`productCode=`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 = **PG6**`IO_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.h``BOARD_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`
```c
#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 显示器件
- 驱动:类 CT1642CLK+DATA 串行(原理图位号 S1)。
- 两块 2 位 7 段数码管:温度一块、湿度一块(含 DP 段,本产品 **不显示小数**DP 恒灭)。
- **帧协议(2026-08-29 实测定型,与 CSDN yitx 一致)**:一帧 **18bit**,CLK 上升沿采样 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=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_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 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` | 重生 7 字节条件 |
**不要拷贝:** `app_battery.c`、充电 GPIO、电机 `app_mode` 步进表(本产品 mode 是通风/强力,重写)。
---
## 4. 模块开关(独立使能 / 屏蔽调试)
全部放 `apps/usr_periph/board_pin.h``0` 时:对应 `init/run` 直接 return,GPIO 不配,日志打一次 `xxx disabled`
```c
#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=0`PG0/PG1 由显示独占,按 CT1642 时序刷;PC4/PC5 保持高阻不配。
- `DISP=0, TH=1`PG0/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(仍按住,到点即触发) | 进 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 闪(将配网,松手生效)
- ≥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 显示绝对值;&lt;0 显示 `-9``-1`(负号用 G 段或自定义,P0 扫段后定);&lt;−9 钳位 `-9`&gt;99 钳位 `99` |
| 湿度 2 位 | 整数 % | 0~99&gt;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 | 条件 | Bit75 温度比较,Bit42 湿度比较,Bit1–0 保留 |
比较编码(3 bit):
| 值 | 含义 |
|---|---|
| 000 | 无条件(忽略该侧) |
| 001 | &lt; |
| 010 | &lt;= |
| 011 | == |
| 100 | &gt;= |
| 101 | &gt; |
| 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=0,fan=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
存(冷启动 / 软关都保留):
- `mode``fan_gear`
- `cond_en``cond_config1~7`(各 7B
- `time_en``time_config1~7`(各 8B
- `fault_en``alarm_en`(若有写入)
不存:
- DP0(忽略)
- 条件作废标志、模式已运行秒数、AHT20 原始采样
- BLE 三元组(走现有 `CFG_USER_PAIR_INFO`,出厂重置 **不清**
上电(V2.0.0 起无需按键)后:加载上述字段,**按上次 mode/gear 从周期起点继续工作**。
首次上电(VM 无效):mode=0,gear=0,条件/定时全 0`cond_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 两管能稳定显示写死的 `25``60`,无鬼影
- [ ] 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` 时序宏(实现时抄这份数)
```c
#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.h`UART 改 PG6,关掉 PC3/4/5 上的 demo 外设。
3. 移植 key/led/boot/power/pair(删电池与 USB 直通)。
4. `bsp_hw` 风扇 PWM + LED + KEY。
5. `bsp_ct1642`PG0/PG1)写死 25/60。
6. `bsp_aht20` 串口打印,再接到显示。
7. P1`app_mode` + DP 同步 + NVM。
8. P2`app_cond` + `app_timer_task` + 仲裁用例。
每一步都保持对应 `BOARD_xxx_ENABLE` 可关。
---
**文档结束。** 行为争议以本文第 5~12 章为准;字节布局以第 10.2 / 11.1 节与 Excel「枚举/值定义」列为准。