Files
JBao_Temp_Humi_Fan_FW/Doc/温湿度风扇_固件需求规格书.md
T
wuchuyuan 5cc7e9dbd6 接入温湿度风扇产品固件(仲裁/条件/定时/显示),并补齐规格书与外设驱动
新增 usr_app 产品逻辑:手动>定时>条件仲裁、条件控温湿、定时任务、数码管显示、
按键/LED、风扇档位、NVM 掉电恢复、配对与开关机,上电按规格恢复上次状态。
新增 usr_periph:AHT20、CT1642、板级引脚与硬件封装;见宝协议 cond_config
改为 7 字节编解码,并接入产品 DP。
补充《温湿度风扇_固件需求规格书》,工程加入对应源文件。
.gitignore 忽略 WorkBuddy/Cursor 本地会话
2026-08-31 15:17:27 +08:00

641 lines
28 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 |
| 文档版本 | V1.2.0 |
| 日期 | 2026.08.31 |
| 作者 | cyWu |
| 读者 | WorkBuddy / 固件开发 |
| 参考工程 | `D:\Jianbao\Project\产品\宠物\门夹弹力绳\门夹弹力绳_soc_JieLi-JL7016` |
> **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 直通开机 / 低电拒绝开机。** 本机 USB 常电、无电池。插上电 **不会** 自动工作,必须长按 3s。
4. **不要把调试 UART 放在 PC5。** 调试 UART TX = **PG6**`IO_PORTG_06`)。PC5 是 AHT20 SDA(独立使用)。
5. **DP0 `power` 预留,不当开关。** 真开关只有按键 3s 软开关机。运行中上报 `power=1`,写忽略。
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() ← 必须在开机等待之前
app_boot_wait_key() ← 3s/5s/12s,失败则 soft poweroff 不返回
jbInit() + userInit()
若 usr_goto_updata → OTA
app_nvm_load() ← 恢复 VM
app_fan_init()
app_disp_init()
app_th_init()
app_mode_init()
app_cond_init()
app_timer_init()
app_function_init()
user_key_init() + lock_if_held
usr_timer_rearm(20ms)
```
**20ms 业务节拍**`usr_timer_tick`,与门夹相同,禁止再开 1ms ISR):
1. `jbTimerIrq()` × dt
2. `user_key_scan` + `user_key_handle`
3. `app_th_poll`(内部 2s 才真正采;IO 分离后采集与显示互不干扰)
4. `app_cond_run` / `app_timer_run` / `app_mode_run`(仲裁见第 12 章)
5. `app_fan_run`
6. `app_disp_run`(闪烁、刷新)
7. `app_led_run`
8. `app_function_poll`(同步 DP
9. 重装定时器(按键按住 / 风扇转 / 关机流程中 → 不可睡)
`usr_timer_loop()`BLE 500ms)只做:`userHandle()` 填温湿度 DP + `jbProtocolHandle()`
可直接复用门夹源码(拷贝后改引脚/事件表,不要改框架语义):
| 复用 | 路径 | 改什么 |
|---|---|---|
| 按键 FSM | `key.c` / `key.h` / `key_manager.c` | 短按切档、长按关机 |
| LED 事件库 | `led.c` / `led.h` / `led_manager.c` | 单灯 PC2,删红绿双通道 |
| 开机判定 | `app_boot.c` | **删除 USB 直通、低电拒绝** |
| 配对会话 | `app_pair.c` | 120s 超时,灯效改单灯 |
| 软关机 | `app_power.c` 精简 | 无电池、无充电;只保留 request/finalize |
| 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 关机 / 开机
- 关机 = `power_set_soft_poweroff()`:风扇 PWM=0 并关 MCPWM,数码管锁存灭(全段灭),LED 灭,BLE 停。
- **插 USB / 冷启动不会自动进入工作态。** 必须按住 3s。
- DP0 不参与。不要用 APP 开关机。
- 无电池逻辑。
### 5.2 关机态按住(`app_boot.c`,用 `timer_get_ms()` 轮询,与运行态 FSM 分开)
| 松开时已按住 | 行为 |
|---|---|
| < 3000 ms | 继续软关机 |
| ≥ 3000 且 < 5000 ms | 正常开机,加载 VM,恢复上次 mode/gear 并从周期起点开跑 |
| ≥ 5000 且 < 12000 ms | 开机 + `usr_clear_pair_info` + 进配对(换手机用这条) |
| ≥ 12000 ms | 进 OTA(门夹 `usr_app_ota_init` 路径) |
按住过程 LED 提示(单灯):
- ≥3s:常亮(将开机)
- ≥5s100ms 闪(将配对)
- ≥12s200ms 闪(将 OTA
开机后若键仍按着:`user_key_lock_if_held()`,直到松开才接受短按/长按。
### 5.3 开机态按键
| 事件 | 行为 |
|---|---|
| 短按 | 风扇档位循环:1(50%) → 2(75%) → 3(100%) → 0(关风扇) → 1 … **立刻退出通风/强力**,改为手动档。设备仍开机,数码管继续刷 |
| 长按 3s | 请求软关机。VM **不清** |
配对进行中:长按关机可忽略(与门夹一致,避免配到一半睡死)。短按仍可切档。
---
## 6. LED(仅 1 颗,PC2 低亮)
| 事件 | 图案 | 优先级(高者覆盖) |
|---|---|---|
| 软关机 / 未开机 | 灭 | — |
| 正常开机 | 常亮 | 低 |
| 配对中 | 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` / `app_fan_run`
- DP2 必须等于 **当前真实档位**(通风停 5 分钟那段报 0)。
- 换档无需软爬坡(P0 不做)。若 100% 上电冲击过大,允许后续加 200ms 爬升,不作为 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`,出厂重置 **不清**
开机 3s 成功后:加载上述字段,**按上次 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** 清配对进绑定广播。
---
## 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 可不实现动作 |
OTA12s 开机走门夹 `usr_app_ota_init``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 # 拷贝门夹
led.c/.h, led_manager.c/.h # 改单灯
app_boot.c/.h # 拷贝后删 USB/电池
app_led.c/.h
app_power.c/.h # 精简
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
- [ ] 关机态 3s 开机、&lt;3s 仍睡;开机态 3s 关机
- [ ] 短按四态循环,示波器/听感确认 50/75/100/0%
- [ ] LED 开机常亮、模拟配对闪、模拟 OTA 闪(可用宏强制)
- [ ] CT1642 两管能稳定显示写死的 `25``60`,无鬼影
- [ ] AHT20 串口打印 ℃/%,CRC 正确;采数时数码管不闪、不乱码(IO 独立,无需锁存)
- [ ] `BOARD_TH_ENABLE=0` 时显示仍可刷;`BOARD_DISP_ENABLE=0` 时传感器仍可打印
- [ ] 12s 能进 OTA 广播(可先只打日志 + LED)
**P0 关闭:** `BOARD_MODE/COND/TIMER/NVM_ENABLE=0`
### P1 — 最小产品闭环
- [ ] 5s 配对,APP 能读温湿度,能写档位/模式
- [ ] 通风 25/5、强力 15min 后停;短按立即退模式
- [ ] 超限闪(可用调试宏灌假温度)、失败 `--`
- [ ] DP0 写 0 风扇不变、设备不关
- [ ] VM:改档/改模式/写下条件定时后断电,3s 开机从周期头恢复 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「枚举/值定义」列为准。