17 KiB
DLD154V4B 开发日志
MCU: AT32F421F8P7 (Cortex-M4, 120MHz) | 线圈通道: 1路 | 调试口: TTL Tx
2026-06-23 — 跳出框框:去掉 >>6 精度浪费
背景
DLD154V4B 的检测算法继承自 M1H(STC12C5202, 2008)和 TLD-110(P87LPC762, 2003),但有几个关键硬件差异:
| 特性 | M1H / TLD-110 | DLD154V4B |
|---|---|---|
| 主频 | ~12MHz (8051) | 120MHz (Cortex-M4) |
| 分频芯片 | CD4060 (Pin5, ÷32) | 无,直连 PA7 |
| MCU 内分频 | 无 | TIM3 DIV_4 → DIV_2 |
| 有效分频比 | ÷32 | ÷2 |
| 捕获方式 | PCA 周期捕获 / 门控计数 | TIM3_CH2 硬件输入捕获 |
问题:<<6 和 >>6 的精度浪费
原始代码的数据流存在一个"膨胀再压缩"的精度陷阱:
TMR3 ISR:
Xn = 周期差值 (4800 ticks @ DIV_4)
LPCNT = (32768 << 6) / Xn = 437 ← 窗口开大 64×,降噪
Value = Σ 437次 Xn ≈ 2,097,600 ← 437 样本精密累加
vd1_task:
tmp = Value >> 6 = 32,775 ← 6bit 精度全部丢弃!
CAPVD = IIR(tmp...) ← IIR 只看到 ~6 样本有效信息
<<6 和 >>6 在数学上互相抵消——437 个样本累加,然后右移 6 位,等效仅用 ~7 个样本(437/64 ≈ 6.8)。窗口开大降噪了,但精度被自己扔掉了。
方案:去掉 <<6/>>6,用 MEASUREMENT_BASE
TMR3 ISR:
LPCNT = 131072 / Xn = 27~54 ← 自适应窗口,不丢精度
Value = Σ Xn ≈ 131072 ← 直接作为检测输入
vd1_task:
CAPVD = IIR(Value) ← 全精度参与滤波
| 参数 | 旧方案 | 新方案 |
|---|---|---|
| LPCNT (100kHz, DIV_2) | — | 54 |
| 测量窗口 | 17.5ms (DIV_4) | ~1ms |
| 有效精度/窗口 | ~6 样本 (>>6 后) | 54 样本 (全保留) |
| Origin 范围 | ~32K | ~131K |
| 灵敏度阈值 | 百分比公式,自动按比例缩放 ✓ | 同 ✓ |
| 进入响应 | ~50ms | ~3ms |
同步改进
| # | 改动 | 原因 |
|---|---|---|
| 1 | TIM3 分频 DIV_4 → DIV_2 | 提高采样率,中断率 2× 仍安全 (CPU<3%) |
| 2 | MEASUREMENT_BASE = 131072 (2^17) | 平衡精度和 Origin 范围 |
| 3 | 去除所有 >>6 | 保留全采样精度 |
| 4 | 离开增加 cnt_release >= 3 防抖 |
防瞬间噪声导致误落杆 |
| 5 | 基线冻结 + 100 窗口慢跟踪 | 仿 TLD-110,有车不更新基线 |
为什么不需要 CD4060 外部分频
AT32F421 的 TIM3 有内置输入分频器,配合 120MHz 主频:
| 线圈频率 | DIV_2 中断率 | CPU 占用 (50周期/ISR) |
|---|---|---|
| 100 kHz | 50k/s | 2.1% |
| 150 kHz | 75k/s | 3.1% |
| 200 kHz | 100k/s | 4.2% |
均在安全范围内,无需外部分频芯片。
对比 M1H/TLD-110
| 指标 | M1H (CD4060÷32) | DLD154V4B (新) |
|---|---|---|
| 测量间隔 | ~50ms (固定) | ~1ms (自适应) |
| 进入防抖 | 500ms IN_DELAY | 500ms IN_DELAY |
| 离开防抖 | 1.9s OUT_DELAY | 3次连续确认 + 1.9s OUT_DELAY |
| 基线跟踪 | 100次 (有车也跟) | 100次 (有车冻结) |
2026-06-23 — 精简重构,对齐参考实现
- 删除二阶差分滤波(计算但从未参与判决)
- 删除 FltHistoryManager 死代码(20+ 未用字段)
- 删除 StageRangeConfig(区间约束未引用)
- 删除动态窗口切换(LOOP_WINDOW_SIZE_LOW→FAST)
- 时序状态机简化:去掉 PLUSE_IN_F/PLUSE_IN 中间层
- 拨码去抖 10→5(对齐 M1H)
- 代码量:1177→706 行(-40%)
2026-06-22 — 参考分析
见 reference_analysis.md:M1H + TLD-110 完整算法分析。
2026-06-23 — 指示灯行为规范化
LED 硬件对应
| 硬件 | 引脚 | 宏 | 行为 |
|---|---|---|---|
| 红灯 | PB1 | (无宏, TMR14 PWM) | 始终呼吸,不干预 |
| 绿灯 (LEDA) | PA9 | LEDA_ON/LEDA_OFF |
自检慢闪 / 有车亮 / 无车灭 |
| 黄灯 (LEDC) | PA10 | LED_YELLOW_ON/OFF |
故障快闪 / 断开次数编码 |
注:
LEDA宏在 BSP 遗留代码中指向 PB1(红灯),已修正为 PA9(绿灯)。LEDB宏无实际 IO,已删除。
绿灯行为
| 状态 | 绿灯 |
|---|---|
| 上电自检 (Origin 未确立) | 慢闪 200ms |
| 数值稳定期 (128 样本 ≈ 128ms) | 慢闪 200ms |
| 正常工作,无车 | 灭 |
| 正常工作,有车 | 亮 |
| 线圈断开中 | 灭(黄灯快闪负责故障指示) |
黄灯行为
| 条件 | 黄灯 |
|---|---|
| 上电后从未接线圈 | 快闪 200ms |
| 线圈当前断开中 | 快闪 200ms |
| 重连后,断开过 1 次 | 1 短闪 (80ms 亮) + 1.2s 间隔 |
| 重连后,断开过 2 次 | 2 短闪 + 1.2s 间隔 |
| 重连后,断开过 3+ 次 | 3 短闪 + 1.2s 间隔 |
| 正常,无断开记录 | 灭 |
"不接线圈上电,上电后再接线圈"不计入断开次数。
2026-06-23 — 上电稳定期 & 线圈重连
上电稳定期
Origin 首次确立后,线圈振荡需要时间稳定。新增 g_loop_stable 标志:
INIT_VD()→g_loop_stable = 0vd1_task(): 稳定期内只做 IIR + 基线跟踪,跳过进入检测- 128 样本 (~128ms) 后
g_loop_stable = 1,正式启用检测 - 安全复位时重置
g_loop_stable = 0
线圈重连状态保持
断开时不丢 VD_FLAG,重连后快速收敛 IIR:
断开: 保留 loop1_VD_FLAG,仅关断继电器
重连: loop1_CAPVD = 0 → 首个样本直锁 Value → IIR 后续正常跟踪
| 断开前 | 断开期间 | 重连后 | 检测结果 |
|---|---|---|---|
| 有车 | 车还在,绿灯灭 | 车还在 | CAPVD < Origin-dlt → VD_FLAG=1 → 绿灯亮 |
| 有车 | 车离开 | 车离开 | CAPVD ≈ Origin → cnt_release→3 → VD_FLAG=0 |
| 无车 | 车进入 | 车进入 | CAPVD < Origin-dlt → VD_FLAG=1 → 绿灯亮 |
2026-06-23 — 移植平坦性离开判定(CN200910309382)
来源
专利 CN200910309382(中山大学,张辉/黄永强/陈古典)提出平坦性三条件判定法, 解决大车通行时频率曲线的负波峰和近零波谷导致单一阈值法多次误触发的问题。
算法
车辆到达 → 单一阈值法 f(i) - f_b > Δ1 (沿用 SensTable)
车辆离开 → 平坦性三条件 同时满足:
① |f - f_b| < Δ1 频率回归基频 (SensTable_1)
② |f'| < Δ2 一阶导数近零
③ |f''| < Δ3 二阶导数近零
Phase 1 (g_exit_state=0): 车辆进入线圈后,追踪第一上升坡面,
记录最大 |f'| 和 |f''|。当斜率连续 3 次降到 SLOPE_FLAT_THRESH 以下,
计算动态阈值:
Δ2 = max_slope / K1 (K1=8)
Δ3 = max_slope_rate / K2 (K2=8)
Phase 2 (g_exit_state=1): 每帧检查三条件。连续 3 次全部满足 → 车辆离开。
与传统方法对比
| 方法 | 离开判据 | 大车防误检 |
|---|---|---|
| M1H/TLD-110 | 单一滞回阈值 | ❌ 多峰可能多次触发 |
| 原 cnt_release | 连续 3 次超阈值 | ⚠️ 固定阈值,大车波谷可能误判 |
| 平坦性判定 | f-f_b |
参数说明
| 常量 | 值 | 含义 |
|---|---|---|
| K1, K2 | 8 | 动态阈值除数(专利推荐值) |
| SLOPE_FLAT_THRESH | 100 | 斜率趋零判断阈值 |
| MIN_DELTA2 | 5 | Δ2 下限(防除数过小) |
| MIN_DELTA3 | 2 | Δ3 下限 |
| FLAT_CONFIRM_CNT | 3 | 平坦连续确认次数 |
整数化适配
专利原实现使用 float(Hz 频率值),DLD154V4B 用 uint32 定点(Origin≈131K)。 导数计算同样用 int32 整数差分,阈值 /K1、/K2 做整数除法,精度足够。
2026-06-23 — Origin 污染保护 & 宏开关
USE_FLATNESS_EXIT 宏
新增编译开关,一行切换离开判据:
#define USE_FLATNESS_EXIT 1 // 1=平坦性, 0=cnt_release防抖
所有平坦性状态变量和逻辑用 #if USE_FLATNESS_EXIT 包裹。
Origin 基线污染保护
问题:实测发现车辆驶入时 Xn 偶尔先增大再减小。无车状态下 Origin 跟踪上升,
被污染到虚高值。进入检测虽触发,但离开时 Origin 冻在虚高处,
|f-f_b| 远超 SensTable_1 阈值 → 绿灯常亮、永远不释放。
根因:CAPVD 异常上升期间,moving average 持续将虚高值写入 Origin。
修复:在基线跟踪前增加保护条件:
dev = CAPVD - Origin;
if (dev < dlt_ORG × 4)
update_moving_average(...); // 安全跟踪
else
freeze; // 冻结 + 重置累计
冻结阈值 = 进入阈值的 4 倍,随灵敏度自动缩放。
| 场景 | CAPVD | Origin | dev | 阈值(×4) | 结果 |
|---|---|---|---|---|---|
| 正常无车 | 127081 | 127080 | +1 | 276 | 跟踪 ✓ |
| 异常上升 | 127884 | 127085 | +799 | 276 | 冻结 ✓ |
2026-06-24 — 时序参数修正 & 接口更正
时序参数修正
OUT_DELAY 和 PULSE_DELAY 之前沿用 M1H 的值(1.9s / 950ms),现修正为 500ms:
| 参数 | M1H 旧值 | DLD154V4B 新值 |
|---|---|---|
| OUT_DELAY | 1.9s (38 tick) | 500ms (10 tick),SW_4 控制开关 |
| PULSE_DELAY | 950ms (19 tick) | 500ms (10 tick),固定 |
SW_4 语义:0 = 无离开延时(立即脉冲),1 = 离开延时 500ms。删除 OUT_DELAY_FAST / PULSE_DELAY_FAST 宏,因只有一组值。
// TaskLoop.h
#define OUT_DELAY 10 // 离开防抖 500ms(仅 SW_4=ON 时生效)
#define PULSE_DELAY 10 // 脉冲宽度 500ms
// TaskLoop.c
if (SET_DLY) {
loop1_OUTCNT++;
if (loop1_OUTCNT > OUT_DELAY) { ... } // 500ms 延时
} else {
// 无离开延时:立即 FLAG_PLUSE
}
接口修正
- 去除所有 RS485 相关描述:本产品只有 TTL 电平 UART Tx 调试口
- 产品手册端子定义:RS485-A/B → Tx TTL 调试输出
- 技术规格书 §9:整节从 RS485 协议改为 TTL Tx 调试接口说明
2026-06-26 — M4 核心优化:双路 IIR + 进入确认 + 斜率限幅
背景
DLD154V4B 的 8051 时代设计在 M4 上可以做得更好。8051 的 50ms tick 是 CPU 限制,不是最优选择。
三项改进
1. 10ms tick + 双路 IIR
| 滤波器 | α | τ | 用途 |
|---|---|---|---|
| CAPVD (慢速) | 18/256 ≈ 0.07 | 135ms | 基线跟踪,等效原 50ms 的 79/256 |
| CAPVD_fast (快速) | 128/256 = 0.5 | 28ms | 进入/离开检测判定 |
tick 提升到 10ms,但通过调整 α 保持与 50ms 设计相同的滤波强度。快速 IIR 用于检测,慢速 IIR 用于基线,两路各司其职。
2. 斜率限幅
EMI/闪电等瞬态干扰会造成 CAPVD 瞬间跳变。物理车辆不可能让线圈频率瞬间改变 >5%:
max_step = CAPVD × 5 / 100; // 5% 限幅
if (|delta| > max_step) delta = clamp(delta, -max_step, max_step);
尖峰被截断,真实车辆信号(缓慢的频率漂移)不受影响。
3. 进入确认
原设计:单次 CAPVD < Origin-dlt → 立即 VD_FLAG=1(同 8051)
新设计:CAPVD_fast 连续 3 次低于阈值 → 才判定有车
if (CAPVD_fast < Origin - dlt)
entry_cnt++;
if (entry_cnt >= 3) { VD_FLAG = 1; ... }
else
entry_cnt = 0; // 一旦恢复就重置
对真实车辆:CAPVD_fast τ=28ms,3 次确认 = 30ms,加上 IN_DELAY 500ms = 总响应 ~530ms,比原来的 550ms 还快。
对瞬态干扰:单个尖峰无法连续 3 次 → 被过滤。
对比
| 指标 | 8051 原设计 | M4 优化 |
|---|---|---|
| tick 周期 | 50ms | 10ms |
| IIR τ (检测) | 135ms | 28ms (5× 快) |
| IIR τ (基线) | 135ms | 135ms (相同) |
| 进入判定 | 单次阈值 | 3 次连续确认 |
| 瞬态抑制 | 无 | 斜率限幅 + 确认 |
| 进入响应 | ~550ms | ~530ms |
| 误触发风险 | 中 | 低 |
兼容性
- 离开检测路径不变(cnt_release 平坦性均已带确认)
- 灵敏度表、IN_DELAY、OUT_DELAY 等参数不变
- 可通过
ENTRY_CONFIRM宏调整确认次数,MAX_SLOPE_RATE调整限幅强度
2026-06-26 — CAPVD_fast 初始化修复
问题
M4 V2.0 引入双路 IIR 后,进入检测使用 CAPVD_fast(快速 IIR,α=0.5)。但 CAPVD_fast 始终为 0,导致首次进入判定就触发有车。
根因
// 原代码(错误):
if (loop1_CAPVD_fast != 0) {
loop1_CAPVD_fast = (loop1_CAPVD_fast + loop1_CAPVD) / 2;
}
TMR3 ISR 首次捕获时直接设置 loop1_CAPVD(不为 0),INIT_VD 将 CAPVD_fast 初始化为 0。vd1_task 进入时 CAPVD != 0 → if (CAPVD_fast != 0) 永远为 false → CAPVD_fast 保持 0 不更新。
修复
条件反转:== 0 时首次锁定当前 CAPVD 值,之后正常执行快速 IIR。
// 修复后:
if (loop1_CAPVD_fast == 0) {
loop1_CAPVD_fast = loop1_CAPVD; // 首次直接锁定
} else {
loop1_CAPVD_fast = (loop1_CAPVD_fast + loop1_CAPVD) / 2;
}
2026-06-26 — 稳定期绕过 IIR 和斜率限幅
问题
上电后很快输出有车状态。日志显示:
First_capSum:177406, Origin:177406 ← 首测为瞬态高值
Loop stable, Origin:149755 ← 稳定期后 Origin 仍偏高
Car_In, Value:128654, Origin:149755 ← Origin - Value = 21067 >> dlt=82
根因
V2.0 引入的 5% 斜率限幅 在稳定期内仍然生效。首测 CAPVD=177406 是瞬态高值(~38% 偏高),斜率限幅让 CAPVD 在 128 个采样周期内只能缓慢下降到 149755 — 无法在稳定期内充分收敛。100 窗口滑动平均被前半段的高值污染,导致 Origin 停在 149755,远高于真实值 ~128688。
修复
稳定期内不走 IIR 和斜率限幅——直接用 raw Value 建立基线:
if (!g_loop_stable) {
/* 稳定期内不做斜率限幅和 IIR — 直接用 Value 快速收敛到真实基线 */
loop1_CAPVD = loop1_Value;
loop1_CAPVD_fast = loop1_Value;
// ... 滑动平均跟踪 ...
return;
}
稳定期结束后恢复正常 IIR + 斜率限幅用于检测。
| 场景 | 修复前 | 修复后 |
|---|---|---|
| 首测瞬态 | 177406 被限幅缓慢下降 | 直接被 128688 覆盖 |
| Origin 收敛 | 149755 (偏离 +16%) | ~128688 (正确) |
| 稳定期结束 | Origin 偏离 → 误判有车 | Origin 准确 → 正常检测 |
2026-06-29 — 基线冻结超时自动恢复
问题
V1.5 引入的 Origin 污染保护机制在 CAPVD 异常偏高时永久冻结基线。如果线圈因环境变化(温度漂移、器件老化、更换线圈)稳定在新的频率值,Origin 永远不会更新 → 绿灯常亮、永久误判有车。只有 MCU 复位才能恢复。
方案
在冻结路径上增加超时自动恢复机制:
#define FREEZE_TIMEOUT 3000 // ~30s @ 10ms/tick
if (dev >= dlt_ORG × 4) {
loop1_freeze_cnt++;
if (loop1_freeze_cnt >= FREEZE_TIMEOUT) {
// 超时: 持续偏高但稳定 → 环境变化,接受新基线
loop1_Origin = loop1_CAPVD; // 直接更新为新常态值
loop1_freeze_cnt = 0;
// 重置累计,后续正常滑动平均跟踪
} else {
// 未超时: 继续冻结(防车辆驶入时的 Xn 先增尖峰)
}
}
行为表格
| 场景 | CAPVD 行为 | 冻结计数 | 结果 |
|---|---|---|---|
| 车辆驶入(Xn 先增) | 偏高 2~3s 后下降 | < 3000 → 清零 | 正常冻结保护 ✓ |
| 温度漂移 | 缓慢爬升到新值并稳定 | > 3000 → 超时 | Origin 更新到新值 ✓ |
| 更换线圈 | 瞬间跳到新频率 | > 3000 → 超时 | Origin 更新到新值 ✓ |
| EMI 间歇干扰 | 偏高后恢复 | 恢复时清零 | 正常冻结保护 ✓ |
冻结计数重置时机
| 触发条件 | 操作 |
|---|---|
dev < dlt_ORG × 4(CAPVD 回到正常范围) |
freeze_cnt = 0,恢复跟踪 |
| 超时触发(Origin 强制更新后) | freeze_cnt = 0 |
车辆进入(loop1_VD_FLAG = 1) |
freeze_cnt = 0 |
INIT_VD()(上电/安全复位) |
freeze_cnt = 0 |
修订记录
| 版本 | 时间 | 说明 |
|---|---|---|
| V2.3 | 2026-06-29 | 基线冻结超时: 持续偏高30s后强制更新 Origin,防止环境变化死锁 |
| ------ | ------ | ------ |
| V2.2 | 2026-06-26 | 稳定期绕过 IIR/斜率限幅,用 Value 直接收敛基线 |
| V2.1 | 2026-06-26 | CAPVD_fast 初始化修复(条件反转为 ==0 首次锁定) |
| V2.0 | 2026-06-26 | M4 优化: 双路 IIR + 斜率限幅 + 进入确认;tick 10ms |
| V1.7 | 2026-06-26 | vTaskDelay 10→50ms 修正(对齐原始 TMR15 5ms×10 设计);基线更新 1s→5s |
| V1.6 | 2026-06-24 | 时序修正:OUT_DELAY/PULSE_DELAY→500ms;RS485→TTL Tx |
| V1.5 | 2026-06-23 | Origin 污染保护、USE_FLATNESS_EXIT 开关 |
| V1.4 | 2026-06-23 | 移植 CN200910309382 平坦性离开判定 |
| V1.3 | 2026-06-23 | 指示灯行为、稳定期、重连状态保持 |
| V1.2 | 2026-06-23 | <<6/>>6 精度浪费分析与改进 |
| V1.1 | 2026-06-23 | 精简重构,对齐 M1H/TLD-110 |
| V1.0 | 2026-06-22 | 参考分析文档 |