42 KiB
方案B DDS注入式检测器——AD8302接口电路与IQ解调算法详解
作者: 小德 分类: 线圈车检 · DDS注入 · AD8302 · IQ解调算法 版本: V1.0 日期: 2026-07-05 关联文档: 铝制车架有效感应方法_v2.0.md(方案B概述)
前言
方案B(DDS注入 + 阻抗分析)的核心是实现复阻抗 Z = R + jX 的实时测量。常见的芯片实现路径有两条:
| 路径 | 芯片 | 原理 | 精度 | 成本 |
|---|---|---|---|---|
| 路径A:幅度-相位检测 | AD8302 | 测两路信号的幅度比和相位差 | ★★★★ | 低 |
| 路径B:正交IQ解调 | AD8333 / LTC5599 | 真正正交混频输出 I/Q | ★★★★★ | 中高 |
| 路径C:数字IQ解调(纯MCU) | MCU ADC + 软件 | 直接采样后数字混频 | ★★★ | 零 |
本文以**路径A(AD8302)**为主、**路径C(纯MCU)**为辅展开,因为这是性价比最高、最适合产品化的路线。
一、AD8302 芯片核心特性
1.1 引脚功能
AD8302(14引脚 TSSOP)
┌─────────────┐
│ INPA COMM │← 通道A输入、地
│ OFSA OFSB │← 输入偏置/耦合方式选择
│ INPB VREF │← 通道B输入、1.8V参考电压输出
│ VREF VPOS │← 参考电压、正电源(2.7~5.5V)
│ V_MAG V_PHS │← 幅度比输出、相位差输出
│ FLTR MSET │← 幅度测量滤波器设置
│ PHSD PSET │← 相位测量滤波器设置
└─────────────┘
1.2 输入输出特性
输入通道特性:
| 参数 | 值 | 说明 |
|---|---|---|
| 输入频率范围 | DC ~ 2.7GHz | 远超车检需求(80~200kHz) |
| 输入幅度范围 | -60dBm ~ 0dBm | (1mVpp ~ 630mVpp @ 50Ω) |
| 输入阻抗 | 3kΩ // 2pF | 高阻抗,可直接耦合 |
| 输入耦合方式 | AC/DC 可选 | 通过 OFSA/OFSB 引脚配置 |
输出特性:
V_MAG 引脚(幅度比):
V_MAG = 0.9V + 0.03V × (PA - PB) [dB]
其中 PA、PB 为 INPA、INPB 的输入功率(dBm)
测量范围:-30dB ~ +30dB(即 0.0316x ~ 31.6x 倍幅度比)
输出范围:0 ~ 1.8V
灵敏度:30mV/dB
比例因子:约 0.26mV/0.01dB
V_PHS 引脚(相位差):
V_PHS = 0.9V + 0.01V × (ΦA - ΦB) [度]
其中 ΦA、ΦB 为 INPA、INPB 的相位
测量范围:-180° ~ +180°
输出范围:0 ~ 1.8V
灵敏度:10mV/度
比例因子:约 0.1mV/0.01°
1.3 传递函数曲线
V_MAG 输出 V_PHS 输出
│ │
1.8V ┌──────── 1.8V ┌────────
│ │ │ │
│ │ │ │
0.9V ┼─────────── 0.9V ┼───────────
│ │ │ │
│ │ │ │
0V └─────────── 0V └───────────
+30dB 0dB -30dB +180° 0° -180°
(INPA > INPB) (INPA 超前 INPB)
车检场景下的工作区间:
- 车辆覆盖时,幅度变化约 ±0.01~±0.5dB(对应 V_MAG 变化 ±0.3~±15mV)
- 相位变化约 ±0.01°
±2°(对应 V_PHS 变化 ±0.1±20mV) - 关键看 ADC 分辨率能否分辨这几个 mV 级别的变化量
二、AD8302 与 MCU 的接口电路设计
2.1 系统整体框图
┌─────────────────────┐
│ DDS │
│ AD9834 │
│ f₀ = 120kHz │
└──────┬──────────────┘
│ 正弦波 V_dds
▼
┌─────────────────────┐
│ 功放/缓冲 │
│ OPA2188 │
│ Gain = 2~5 │
└──────┬──────────────┘
│
┌──────────────┼──────────────┐
│ │ │
▼ ▼ ▼
┌───────────┐ ┌───────────┐ ┌───────────┐
│ 衰减器 │ │ 变压器 │ │ 阻抗匹配 │
│ 电阻分压 │ │ 1:N升压 │ │ 网络 │
│ -20~-30dB │ │ │ │ │
└─────┬─────┘ └─────┬─────┘ └─────┬─────┘
│ │ │
│ ┌────┴────┐ │
│ │ 线圈 │ │
│ │ (100~ │ │
│ │ 300μH) │ │
│ └─────────┘ │
│ │ │
│ ┌────┴────┐ │
│ │ 变压器 │ │
│ │ N:1降压 │ │
│ └────┬────┘ │
│ │ │
▼ ▼ ▼
┌───────────────────────────────────────┐
│ 信号调理 │
│ ┌─────────┐ ┌─────────┐ │
│ │ 缓冲器 │ │ 缓冲器 │ │
│ │ OPA2188 │ │ OPA2188 │ │
│ └────┬────┘ └────┬────┘ │
│ │ │ │
│ ┌────┴────┐ ┌────┴────┐ │
│ │ LPF │ │ LPF │ │
│ │ 300kHz │ │ 300kHz │ │
│ └────┬────┘ └────┬────┘ │
│ │ │ │
│ Ref_IN Meas_IN │
└───────────────────────────────────────┘
│ │
▼ ▼
┌──────────────────────────┐
│ AD8302 │
│ INPA(参考) INPB(测量) │
│ │
│ V_MAG V_PHS │
└──────┬──────────┬────────┘
│ │
▼ ▼
┌──────────────────────────┐
│ MCU (STM32G474) │
│ ADC1_IN1 ADC1_IN2 │
│ (12bit, 过采样至16bit) │
│ │
│ 定时器触发 ADC 采样 │
│ f_sample = 1kHz │
│ │
│ 软件 IQ 解算: │
│ → ΔR, ΔL 提取 │
│ → 基线跟踪 │
│ → 触发判决 │
└──────────────────────────┘
2.2 参考通道设计(INPA)
参考通道从 DDS 输出端直接取信号,经过固定衰减后送入 AD8302 的 INPA。
DDS 输出 ──→ 电阻分压衰减器 ──→ 缓冲器 ──→ LPF ──→ INPA
衰减器设计(典型值 -26dB ≈ 1/20):
R1=1kΩ
IN ──┬─/\/\/\/──┬── OUT
│ │
│ R2=51Ω │
│ │
GND GND
衰减比 = R2/(R1+R2) ≈ 51/1051 ≈ 1/20 ≈ -26dB
注意: 衰减器必须使用低温度系数电阻(±25ppm/°C 或更好),因为它是整个系统的幅度参考基准,直接影响 ΔR 的测量精度。
2.3 测量通道设计(INPB)
测量通道从线圈响应端取信号,经缓冲后的送入 AD8302 的 INPB。
线圈 ──→ 变压器 N:1 ──→ 缓冲器 ──→ LPF ──→ INPB
变压器匝比:
- 激励侧升压:1:3 ~ 1:5(提升线圈激励电流,增加 SNR)
- 接收侧降压:5:1 ~ 3:1(恢复至 ADC/AD8302 可接受电平)
2.4 低通滤波器(LPF)设计
目的是滤除带外噪声和高次谐波,同时保持激励频率(120kHz)信号不衰减。
二阶巴特沃斯 LPF,截止频率 300kHz:
R1=10k R2=10k
IN ──┬─/\/\/\/──┬─/\/\/\/──┬── OUT
│ │ │
│ C1=47pF │ │ C2=100pF
│ │ │
GND GND GND
f_c = 1 / (2π × √(R1×R2×C1×C2))
= 1 / (2π × √(10k×10k×47p×100p))
= 1 / (2π × 6.86e-6)
≈ 23.2kHz ❌ 太低了!
调整元件值使截止频率为 300kHz:
重新计算:
R1=2k R2=2k
IN ──┬─/\/\/\/──┬─/\/\/\/──┬── OUT
│ │ │
│ C1=470pF │ │ C2=220pF
│ │ │
GND GND GND
f_c ≈ 1 / (2π × √(2k×2k×470p×220p))
≈ 1 / (2π × 4.55e-7)
≈ 350kHz ✅
120kHz 处衰减 < 0.5dB
1MHz 处衰减 > 20dB
2.5 输入电平匹配
AD8302 的输入幅度范围是 -60dBm ~ 0dBm(50Ω参考)。为保证最佳线性度,推荐工作点在 -30dBm ~ -10dBm。
电平计算(以 120kHz, 3.3V DDS 输出 Vpp 为例):
DDS 输出(AD9834):3.3Vpp (50% 占空比时 ≈ 1.65V 振幅)
参考通道:
- 经 -26dB 衰减器:1.65V × 1/20 = 82.5mVpp
- 82.5mVpp 在 50Ω 上对应功率:P = Vrms²/50
- Vrms = 82.5mVpp / 2.828 = 29.2mVrms
- P = 0.0292²/50 = 17μW = -17.7dBm ✅
测量通道(线圈空载时):
- 变压器匝比 1:3(激励升压),接收 3:1(降压):总增益 ≈ 0dB
- 但线圈有 1~2Ω 直流电阻 + 辐射损耗
- 信号幅度约 DDS 的 0.7~0.9 倍
- 预期:60~75mVpp → -20dBm ~ -18dBm ✅
2.6 MCU ADC 接口
AD8302 输出:
V_MAG ──┬─ 100Ω ──┬── ADC1_IN1 (PA0)
│ │
│ 1nF === 10nF (去耦)
│ │
GND GND
V_PHS ──┬─ 100Ω ──┬── ADC1_IN2 (PA1)
│ │
│ 1nF === 10nF
│ │
GND GND
100Ω + 1nF 构成简单 RC 低通:
f_c = 1/(2π × 100 × 1nF) ≈ 1.6MHz(去噪,不影响信号)
ADC 配置(以 STM32G474 为例):
ADC 参数:
分辨率:12bit(过采样至 16bit,OSR=16)
采样率:1ksps(每个通道,两个通道)
采样时间:47.5 个 ADC 时钟周期
ADC 时钟:60MHz(APB2)
参考电压:3.3V(VDDA)
过采样(硬件过采样):
OVFS=4 → 过采样率 16 → 有效分辨率 ~16bit
等效 LSB = 3.3V / 65536 ≈ 50μV
V_MAG 信号灵敏度评估:
铝车架 ΔR = 3% → V_MAG 变化约 1.5~2mV
50μV LSB → 约 30~40 个 LSB 的变化
SNR ≈ 36dB → 足够可靠(需求 SNR > 20dB)
DMA 配置:
双通道 DMA 循环采样:
ADC1 → DMA1_CH1 → buffer[128](每通道 64 点双缓冲)
半满中断 / 满中断 → 触发数据处理
定时器触发:
TIM6(1kHz)→ TRGO → ADC1 启动转换
2.7 PCB 布局关键要点
┌────────────────────────────────────┐
│ AD8302 布局注意事项 │
├────────────────────────────────────┤
│ │
│ ① 模拟地与数字地单点连接 │
│ AD8302下方开槽隔离 │
│ │
│ ② 参考通道和测量通道走线等长 │
│ 减少路径引入的额外相位差 │
│ │
│ ③ V_MAG 和 V_PHS 输出走线远离 │
│ DDS 高频信号线 │
│ │
│ ④ 电源去耦: │
│ VPOS 引脚:100nF + 10μF 就近放置 │
│ 每颗运放:100nF │
│ │
│ ⑤ DDS 时钟源远离模拟前端 │
│ 晶振/时钟走线包地 │
│ │
│ ⑥ 变压器靠近 AD8302 输入引脚 │
│ LPF 元件靠近 AD8302 │
│ │
└────────────────────────────────────┘
三、AD8302 标定与校准
3.1 出厂/上电校准
在已知空载条件下(无车、已知环境),测量 V_MAG 和 V_PHS 作为空载基准:
步骤:
1. 确认线圈已连接,周围 2m 内无金属
2. 等待 5s(让 AD8302 和 DDS 温漂稳定)
3. 采集 100 个 V_MAG 和 V_PHS 样本
4. 计算中值作为 V_MAG₀ 和 V_PHS₀
5. 计算噪声标准差 σ_MAG 和 σ_PHS
存储校准参数:
Cal = {V_MAG₀, V_PHS₀, σ_MAG, σ_PHS, T_cal}
3.2 增益/相位系数标定
虽然 AD8302 的 datasheet 给出了 30mV/dB 和 10mV/° 的标称值,实际每颗芯片有细微差异,建议在产线标定:
标定方法(使用标准阻抗盒):
① 接入已知 R 和 L 的标准负载:
R_load = R₀ + ΔR_cal(如 0Ω、0.5Ω、1Ω、2Ω)
L_load = L₀ + ΔL_cal(如 +1μH、+2μH、+5μH)
② 记录每种负载下的 V_MAG、V_PHS
③ 线性回归得到实际系数:
k_mag = ΔV_MAG / Δ(dB) [V/dB]
k_phs = ΔV_PHS / Δ(deg) [V/deg]
④ 每台设备存储 k_mag、k_phs 到 EEPROM/Flash
3.3 温漂校准
在工作温度范围(-20°C ~ +70°C)内采集:
ΔV_MAG_T(T) = V_MAG(T) - V_MAG(25°C)
ΔV_PHS_T(T) = V_PHS(T) - V_PHS(25°C)
拟合为二阶多项式:
ΔV_MAG_T(T) = a₂T² + a₁T + a₀
ΔV_PHS_T(T) = b₂T² + b₁T + b₀
运行时补偿:
V_MAG_comp = V_MAG_raw - ΔV_MAG_T(T_current)
V_PHS_comp = V_PHS_raw - ΔV_PHS_T(T_current)
四、IQ 解调算法——从 AD8302 输出到 ΔR、ΔL
4.1 算法概述
AD8302 直接给出的是幅度比 G和相位差 Φ,我们需要将其转换为电阻变化 ΔR和电感变化 ΔL。
AD8302 输出 ──→ 幅度比 G (dB) ──→ |Z|/|Z₀| ──→ ΔR, ΔL
──→ 相位差 Φ (deg) ──→ ∠Z - ∠Z₀ ──┘
4.2 第一步:原始量采样
每 1ms(1kHz 采样率):
[0] 读取 V_MAG_raw(ADC 码值)
[1] 读取 V_PHS_raw(ADC 码值)
[2] 转换为电压:
V_MAG = V_MAG_raw × (3.3V / 65536) [16bit ADC]
V_PHS = V_PHS_raw × (3.3V / 65536)
[3] 温度补偿:
V_MAG = V_MAG - ΔV_MAG_T(T_sensor)
V_PHS = V_PHS - ΔV_PHS_T(T_sensor)
[4] 转换为幅度比和相位差:
G_dB = (V_MAG - 0.9V) / k_mag [dB]
Φ_deg = (V_PHS - 0.9V) / k_phs [度]
注意:k_mag ≈ 0.03V/dB, k_phs ≈ 0.01V/°
4.3 第二步:相对于空载基线的变化量
ΔG_dB = G_dB - G₀_dB (幅度比变化,单位 dB)
ΔΦ_deg = Φ_deg - Φ₀_deg (相位差变化,单位 度)
其中 G₀_dB、Φ₀_deg 为空载基准值(从校准参数读取)
4.4 第三步:从幅度和相位提取 ΔR 和 ΔL
关键数学推导:
设空载时线圈复阻抗为 Z₀ = R₀ + jωL₀,模和相角为:
|Z₀| = √(R₀² + (ωL₀)²)
∠Z₀ = arctan(ωL₀ / R₀)
车辆覆盖后阻抗变为 Z = R₀ + ΔR + jω(L₀ + ΔL):
|Z| = √((R₀+ΔR)² + (ωL₀+ωΔL)²)
∠Z = arctan((ωL₀+ωΔL) / (R₀+ΔR))
AD8302 输出反映了这些变化:
幅度比变化:ΔG_dB = 20 × log₁₀(|Z| / |Z₀|)
相位差变化:ΔΦ_deg = (∠Z - ∠Z₀) × 180/π × sign_correction
但由于 Z 是 R 和 L 的耦合函数,从 ΔG 和 ΔΦ 反推 ΔR 和 ΔL 需要对非线性方程组求解。
对小信号(ΔR ≪ R₀, ΔL ≪ L₀)可用一阶线性近似:
定义阻抗矩阵 J:
[ ΔG_linear ] = J × [ ΔR ]
[ ΔΦ_rad ] [ ωΔL ]
其中:
ΔG_linear = ln(10)/20 × ΔG_dB ≈ 0.1151 × ΔG_dB
ΔΦ_rad = ΔΦ_deg × π/180
J = [ ∂G/∂R ∂G/∂(ωL) ] (在 R₀, ωL₀ 处计算)
[ ∂Φ/∂R ∂Φ/∂(ωL) ]
解析结果(非常重要):
设 α = R₀ / |Z₀| = cos(∠Z₀)
β = ωL₀ / |Z₀| = sin(∠Z₀)
∂G/∂R = α / |Z₀|
∂G/∂(ωL) = β / |Z₀|
∂Φ/∂R = -β / |Z₀|
∂Φ/∂(ωL) = α / |Z₀|
即:
ΔG_linear ≈ (α × ΔR + β × ωΔL) / |Z₀|
ΔΦ_rad ≈ (-β × ΔR + α × ωΔL) / |Z₀|
反解 ΔR 和 ΔL:
ΔR = |Z₀| × (α × ΔG_linear - β × ΔΦ_rad)
ωΔL = |Z₀| × (β × ΔG_linear + α × ΔΦ_rad)
注意 α² + β² = 1(因为 α = cosθ₀, β = sinθ₀),该变换是正交的!
即 ΔR 和 ΔL 的解耦计算公式:
ΔR = |Z₀| × (cos(∠Z₀) × ΔG_linear - sin(∠Z₀) × ΔΦ_rad)
ΔL = |Z₀| / ω × (sin(∠Z₀) × ΔG_linear + cos(∠Z₀) × ΔΦ_rad)
这个公式是方案B从 AD8302 原始输出到 ΔR、ΔL 的核心算法,务必正确实现。
4.5 算法伪代码
// ============================================================
// 核心算法:AD8302 → ΔR, ΔL
// 运行频率:1kHz(每 1ms 调用一次)
// ============================================================
// 校准参数(上电从 EEPROM 读取)
typedef struct {
float V_MAG_0; // 空载 V_MAG 基准电压
float V_PHS_0; // 空载 V_PHS 基准电压
float k_mag; // 幅度比系数(V/dB)
float k_phs; // 相位差系数(V/deg)
float R0; // 空载线圈电阻(Ω)
float L0; // 空载线圈电感(H)
float omega; // 激励角频率 = 2π×f₀
// 温度补偿系数
float a2, a1, a0; // V_MAG 温漂二阶系数
float b2, b1, b0; // V_PHS 温漂二阶系数
} CalParam_t;
CalParam_t cal; // 全局校准参数
// 由校准参数计算的中间量
float Z0; // |Z₀| = sqrt(R0² + (ωL0)²)
float cos_theta; // cos(∠Z₀) = R0 / Z0
float sin_theta; // sin(∠Z₀) = ωL0 / Z0
void Cal_Init(void) {
cal.omega = 2.0f * 3.14159265f * 120000.0f; // 120kHz
Z0 = sqrtf(cal.R0 * cal.R0 + powf(cal.omega * cal.L0, 2));
cos_theta = cal.R0 / Z0;
sin_theta = (cal.omega * cal.L0) / Z0;
}
// 温度补偿
float Compensate_MAG(float V_mag_raw, float temp) {
float dt = temp - 25.0f;
return V_mag_raw - (cal.a2*dt*dt + cal.a1*dt + cal.a0);
}
float Compensate_PHS(float V_phs_raw, float temp) {
float dt = temp - 25.0f;
return V_phs_raw - (cal.b2*dt*dt + cal.b1*dt + cal.b0);
}
// 核心:从 AD8302 输出计算 ΔR 和 ΔL
void AD8302_To_DeltaRL(
uint16_t adc_mag, // V_MAG ADC 码值
uint16_t adc_phs, // V_PHS ADC 码值
float temp, // 当前温度(°C)
float *delta_R, // 输出:ΔR(Ω)
float *delta_L // 输出:ΔL(H)
) {
// Step 1: ADC 码值 → 电压
float V_mag_raw = (float)adc_mag * 3.3f / 65536.0f;
float V_phs_raw = (float)adc_phs * 3.3f / 65536.0f;
// Step 2: 温度补偿
float V_mag = Compensate_MAG(V_mag_raw, temp);
float V_phs = Compensate_PHS(V_phs_raw, temp);
// Step 3: 电压 → 幅度比(dB)和相位差(度)
float G_dB = (V_mag - 0.9f) / cal.k_mag;
float Phi_deg = (V_phs - 0.9f) / cal.k_phs;
// Step 4: 相对于空载基线的变化量
float dG_dB = G_dB - (cal.V_MAG_0 - 0.9f) / cal.k_mag;
float dPhi_deg = Phi_deg - (cal.V_PHS_0 - 0.9f) / cal.k_phs;
// Step 5: dB→线性, deg→rad
float dG_lin = 0.115129f * dG_dB; // ln(10)/20 ≈ 0.1151
float dPhi_rad = dPhi_deg * 0.0174533f; // π/180 ≈ 0.01745
// Step 6: 解耦提取 ΔR 和 ΔL(核心公式)
*delta_R = Z0 * (cos_theta * dG_lin - sin_theta * dPhi_rad);
*delta_L = (Z0 / cal.omega) * (sin_theta * dG_lin + cos_theta * dPhi_rad);
}
4.6 简化实现(MCU 资源受限时)
如果 MCU 不支持浮点运算或三角函数,可以用预计算表:
// 预计算表(在初始化时计算一次)
// 对于固定频率,cos_theta 和 sin_theta 是常数
// 简化公式(避免除法):
// ΔR ≈ K_RR × dG_dB + K_RP × dPhi_deg
// ΔL ≈ K_LR × dG_dB + K_LP × dPhi_deg
// 预计算常数(在 Cal_Init 中计算):
K_RR = Z0 * cos_theta * 0.115129f; // ΔR 对 dG_dB 的系数
K_RP = -Z0 * sin_theta * 0.0174533f; // ΔR 对 dPhi_deg 的系数
K_LR = Z0/cal.omega * sin_theta * 0.115129f; // ΔL 对 dG_dB
K_LP = Z0/cal.omega * cos_theta * 0.0174533f; // ΔL 对 dPhi_deg
// 运行时只需要 4 次乘法和 2 次加法:
void AD8302_To_DeltaRL_Fast(float dG_dB, float dPhi_deg,
float *dR, float *dL) {
*dR = K_RR * dG_dB + K_RP * dPhi_deg;
*dL = K_LR * dG_dB + K_LP * dPhi_deg;
}
五、数字滤波与信号处理流水线
5.1 处理流水线框架
ADC 采样 (1kHz) ──→ 预处理 ──→ ⅡR 低通 ──→ 基线跟踪 ──→ 触发判决 ──→ 输出
每个阶段的数据率:
1kHz → 1kHz → 100Hz → 100Hz → 事件驱动
↑
基线更新率:0.01~0.05Hz
5.2 ⅡR 低通滤波器设计
推荐二阶巴特沃斯低通,截止频率 20Hz(滤除电源纹波和噪声,保留车辆信号):
// 二阶 IIR 低通滤波器
// 设计参数:f_c = 20Hz, f_s = 1kHz, 巴特沃斯
// 传递函数:H(z) = (b₀ + b₁z⁻¹ + b₂z⁻²) / (1 + a₁z⁻¹ + a₂z⁻²)
typedef struct {
float b0, b1, b2; // 前馈系数
float a1, a2; // 反馈系数(注意符号约定:y = b0*x + b1*x1 + b2*x2 - a1*y1 - a2*y2)
float x1, x2; // 输入历史
float y1, y2; // 输出历史
} IIRFilter_t;
// 20Hz 低通 @ 1kHz 的系数(巴特沃斯)
// 使用双线性变换法计算得到:
void IIR_LPF20_Init(IIRFilter_t *f) {
f->b0 = 0.001837f;
f->b1 = 0.003674f;
f->b2 = 0.001837f;
f->a1 = -1.822623f;
f->a2 = 0.830472f;
f->x1 = f->x2 = f->y1 = f->y2 = 0.0f;
}
float IIR_Process(IIRFilter_t *f, float x) {
float y = f->b0 * x + f->b1 * f->x1 + f->b2 * f->x2
- f->a1 * f->y1 - f->a2 * f->y2;
f->x2 = f->x1;
f->x1 = x;
f->y2 = f->y1;
f->y1 = y;
return y;
}
// 实际使用时,对 ΔR 和 ΔL 各用一个 IIR 滤波器
IIRFilter_t iir_dR, iir_dL;
float filtered_dR = IIR_Process(&iir_dR, raw_dR);
float filtered_dL = IIR_Process(&iir_dL, raw_dL);
5.3 降采样
滤波后数据从 1kHz 降到 100Hz(每 10ms 处理一次):
// 每 10ms 执行一次(由定时器触发)
void ProcessLoop_100Hz(void) {
// AD8302 读取已经在 1kHz ISR 中完成
// 这里处理降采样后的数据
float dR_raw = latest_dR; // 最近一次 1kHz 计算的 ΔR
float dL_raw = latest_dL; // 最近一次 1kHz 计算的 ΔL
// IIR 滤波
float dR_filt = IIR_Process(&iir_dR, dR_raw);
float dL_filt = IIR_Process(&iir_dL, dL_raw);
// 基线跟踪
BaselineTrack(dR_filt, dL_filt);
// 触发判决
TriggerDecision(dR_filt - baseline_dR, dL_filt - baseline_dL);
}
六、基线跟踪算法(详细实现)
6.1 双基线结构
维护两组基线:快基线(快速恢复)和慢基线(温漂跟踪)。
typedef struct {
// 快基线(车辆离开后快速恢复)
float dR_fast; // ΔR 快基线值
float dL_fast; // ΔL 快基线值
uint32_t cooldown; // 离开计数器
// 慢基线(温度漂移跟踪)
float dR_slow; // ΔR 慢基线值
float dL_slow; // ΔL 慢基线值
// 参数
float alpha_fast; // 快基线学习率(0.01)
float alpha_slow; // 慢基线学习率(0.0005)
uint32_t cooldown_max; // 冷却周期数(100 = 1s @ 100Hz)
// 输出
float dR_baseline; // 当前使用的基线值
float dL_baseline;
} BaselineTracker_t;
void Baseline_Init(BaselineTracker_t *bt) {
bt->dR_fast = bt->dR_slow = bt->dR_baseline = 0.0f;
bt->dL_fast = bt->dL_slow = bt->dL_baseline = 0.0f;
bt->cooldown = 0;
bt->alpha_fast = 0.01f;
bt->alpha_slow = 0.0005f;
bt->cooldown_max = 100; // 1 秒冷却
}
void Baseline_Track(BaselineTracker_t *bt,
float dR, float dL,
uint8_t car_present) {
if (car_present) {
// 有车 → 冻结所有基线
bt->cooldown = 0;
return;
}
if (bt->cooldown < bt->cooldown_max) {
// 冷却期(车辆刚离开,可能有拖尾→基线冻结)
bt->cooldown++;
// 快基线快速恢复(每步 ~1% 向当前值靠近)
bt->dR_fast += bt->alpha_fast * (dR - bt->dR_fast);
bt->dL_fast += bt->alpha_fast * (dL - bt->dL_fast);
return;
}
// 冷却结束 → 确认无车
// 快基线保持与当前值一致,准备下次车辆到来时快速恢复
bt->dR_fast = dR;
bt->dL_fast = dL;
// 慢基线慢速跟踪温漂
bt->dR_slow += bt->alpha_slow * (dR - bt->dR_slow);
bt->dL_slow += bt->alpha_slow * (dL - bt->dL_slow);
// 输出基线 = 慢基线(温漂补偿后的真实空载值)
bt->dR_baseline = bt->dR_slow;
bt->dL_baseline = bt->dL_slow;
}
6.2 快速恢复机制(车辆离开后)
车辆离开瞬间,由于变压器磁芯剩磁、放大器偏置恢复等效应,信号可能需 10~50ms 才能回到真实空载值。
// 快速恢复状态机
// 车辆刚离开 → "预恢复"状态(50ms)
// → 使用快基线
// → "稳定"状态(500ms 后)
// → 切换到慢基线
typedef enum {
BASELINE_STABLE, // 稳态(使用慢基线)
BASELINE_RECOVER, // 预恢复(使用快基线)
} BaselineState_t;
// 在触发判决中:
// 检测到车辆离开事件:
if (just_left) {
bt->cooldown = 0;
bt->state = BASELINE_RECOVER;
bt->recover_timer = 50; // 50 个 10ms 周期 = 500ms
}
七、触发判决算法(详细实现)
7.1 多特征模糊触发
// ============================================================
// 触发判决参数
// ============================================================
typedef struct {
// 原始阈值
float dR_trigger; // ΔR 触发阈值(Ω) 推荐:0.005~0.015
float dL_trigger; // ΔL 触发阈值(μH) 推荐:0.1~0.3
float dR_release; // ΔR 释放阈值(Ω) 推荐:dR_trigger × 0.6
float dL_release; // ΔL 释放阈值(μH) 推荐:dL_trigger × 0.6
// 微分阈值
float dRdt_trigger; // ΔR 变化率阈值(Ω/s)
float dLdt_trigger; // ΔL 变化率阈值(μH/s)
// 模糊权重
float w_dR; // ΔR 权重 推荐:0.4~0.6
float w_dL; // ΔL 权重 推荐:0.2~0.3
float w_dRdt; // 微分权重 推荐:0.1~0.2
float w_noise; // 噪声自适应 推荐:0.05~0.1
// 时间参数
uint32_t debounce_ms; // 去抖时间(ms) 推荐:30~50
uint32_t hold_ms; // 存在保持时间(ms)推荐:2000~5000
// 自适应参数
float noise_floor_R; // ΔR 噪声本底(滚动 1s RMS)
float noise_floor_L; // ΔL 噪声本底
} TriggerParam_t;
// ============================================================
// 触发判决状态机
// ============================================================
typedef enum {
STATE_IDLE, // 空闲
STATE_PRE_TRIGGER, // 预触发(去抖确认中)
STATE_TRIGGERED, // 已触发(有车)
STATE_HOLD, // 保持(存在定时器)
} TriggerState_t;
typedef struct {
TriggerState_t state;
TriggerParam_t param;
// 去抖计数器
uint32_t debounce_count;
// 存在保持定时器
uint32_t hold_remaining; // 剩余保持时间(ms)
// 输出
uint8_t car_present; // 当前是否有车
uint32_t car_count; // 累计车辆计数
// 噪声估计窗口
float noise_buffer[100]; // 100 点循环缓冲
uint8_t noise_idx;
} TriggerEngine_t;
// ============================================================
// 核心触发判决(每 10ms 执行一次)
// ============================================================
void Trigger_Process(TriggerEngine_t *te,
float dR, // 相对基线的 ΔR
float dL, // 相对基线的 ΔL
float dRdt, // ΔR 变化率(Ω/s)
float dLdt // ΔL 变化率(μH/s)
) {
// Step 1: 更新噪声本底(仅在无车时)
if (te->state == STATE_IDLE) {
te->noise_buffer[te->noise_idx] = fmaxf(fabsf(dR), fabsf(dL));
te->noise_idx = (te->noise_idx + 1) % 100;
// 每 100 点更新一次 RMS 噪声
if (te->noise_idx == 0) {
float sum_sq = 0;
for (int i = 0; i < 100; i++)
sum_sq += te->noise_buffer[i] * te->noise_buffer[i];
te->param.noise_floor_R = sqrtf(sum_sq / 100.0f);
te->param.noise_floor_L = te->param.noise_floor_R; // 近似
}
}
// Step 2: 计算触发置信度
float conf;
switch (te->state) {
case STATE_IDLE:
// 模糊逻辑触发判决
conf = te->param.w_dR * (dR / te->param.dR_trigger)
+ te->param.w_dL * (dL / te->param.dL_trigger)
+ te->param.w_dRdt * (fmaxf(dRdt, dLdt) / te->param.dRdt_trigger)
- te->param.w_noise * (te->param.noise_floor_R / te->param.dR_trigger);
// 归一化触发条件
if (conf > 1.0f) {
te->debounce_count++;
if (te->debounce_count >= te->param.debounce_ms / 10) {
// 去抖确认 → 进入触发状态
te->state = STATE_TRIGGERED;
te->debounce_count = 0;
te->car_present = 1;
te->car_count++;
te->hold_remaining = te->param.hold_ms;
}
} else {
te->debounce_count = 0; // 重置去抖
}
break;
case STATE_TRIGGERED:
// 有车持续中
te->hold_remaining = te->param.hold_ms; // 持续刷新保持定时器
// 释放条件:两个特征都低于释放阈值
if (dR < te->param.dR_release && dL < te->param.dL_release) {
te->debounce_count++;
if (te->debounce_count >= te->param.debounce_ms / 10) {
// 确认释放 → 进入保持状态
te->state = STATE_HOLD;
te->debounce_count = 0;
}
} else {
te->debounce_count = 0;
}
break;
case STATE_HOLD:
// 存在保持状态(覆盖车底缝隙)
te->hold_remaining -= 10; // 每 10ms 递减
// 如果信号重新出现(车辆还在)
if (dR > te->param.dR_trigger || dL > te->param.dL_trigger) {
te->state = STATE_TRIGGERED;
te->hold_remaining = te->param.hold_ms;
break;
}
// 如果保持定时器超时
if (te->hold_remaining <= 0) {
te->state = STATE_IDLE;
te->car_present = 0;
}
break;
}
}
7.2 车速估算与谷底检测
// ============================================================
// 车速估算(基于同一线圈前/后沿时差)
// ============================================================
typedef struct {
float rise_time; // 上升沿 10%~90% 时间(ms)
float fall_time; // 下降沿 90%~10% 时间(ms)
float peak_dR; // 本次车辆 ΔR 峰值
float peak_dL; // 本次车辆 ΔL 峰值
float speed_est; // 估计车速(km/h)
} VehicleProfile_t;
VehicleProfile_t vprof;
// 在触发上升/下降沿时调用
void OnTriggerRising(void) {
vprof.rise_time_start = current_time_ms;
}
void OnTriggerFullyOn(void) {
vprof.rise_time = current_time_ms - vprof.rise_time_start;
}
void OnTriggerLeaving(void) {
vprof.fall_time_start = current_time_ms;
}
void OnTriggerFullyOff(void) {
vprof.fall_time = current_time_ms - vprof.fall_time_start;
// 车速估计(基于已知线圈长度和上升沿时间)
// 假设车辆有效耦合长度 ≈ 线圈长度 + 轴距/2
float effective_len = coil_length + wheelbase_est / 2.0f;
vprof.speed_est = effective_len / vprof.rise_time * 3.6f; // m/s → km/h
}
// ============================================================
// 谷底检测(前车/后车区分)
// ============================================================
typedef enum {
GAP_NONE, // 无间隙
GAP_SMALL, // 底盘间隙(属于同一辆车)
GAP_VALLEY, // 车缝(两车之间)
} GapType_t;
GapType_t DetectGap(float dR, float dL) {
static float dR_prev = 0, dL_prev = 0;
static uint32_t valley_ms = 0;
static uint8_t in_valley = 0;
// 微分判断是否下降
float dR_dot = dR - dR_prev;
float dL_dot = dL - dL_prev;
dR_prev = dR;
dL_prev = dL;
// 检测谷底:dR_dot 从负转正(由降到升)
if (!in_valley && dR_dot < 0 && dR < dR_trigger * 0.5f) {
in_valley = 1;
valley_ms = 0;
}
if (in_valley) {
valley_ms += 10; // 每 10ms
// 信号回升 → 谷底结束
if (dR_dot > 0 && dR > dR_trigger * 0.3f) {
in_valley = 0;
if (valley_ms > 200) { // 车缝 > 200ms
return GAP_VALLEY; // 判为前后两车
} else {
return GAP_SMALL; // 判为底盘间隙
}
}
// 谷底持续太深 → 车辆离开
if (dR < dR_release && valley_ms > 500) {
in_valley = 0;
return GAP_VALLEY;
}
}
return GAP_NONE;
}
7.3 IQ 平面可视化输出(调试用)
开发调试阶段,可将 IQ 轨迹输出到串口:
每 10ms 输出一行 CSV:
t_ms, dR_mOhm, dL_uH
用串口工具接收后可直接绘图:
Excel、Python matplotlib、SerialPlot 等
典型波形解读:
纯钢制车通过的 IQ 轨迹:
dR (mΩ) │ · ← 前轮
15 │ · ← 钢底盘主体
10 │ ·
5 │ ·
0 └──────────────────
0 0.5 1.0 1.5 2.0 dL (μH)
全铝制车通过的 IQ 轨迹:
dR (mΩ) │
30 │ · ← 铝底盘主体
20 │ ·
10 │
0 └──────────────────
0 0.2 0.4 0.6 0.8 dL (μH)
钢铝混合车通过的 IQ 轨迹(典型对角线分布):
dR (mΩ) │
25 │ · ← 铝蒙皮 + 钢骨架
20 │ ·
15 │ ·
10 │ ·
5 │
0 └──────────────────
0 0.5 1.0 1.5 2.0 dL (μH)
八、纯 MCU 数字 IQ 解调方案(备选,零硬件解调器成本)
如果不用 AD8302,可直接用 MCU 内置 ADC 采样信号,在软件中做数字 IQ 解调。
8.1 采样要求
系统参数:
激励频率:f₀ = 120kHz
ADC 采样率:f_s = 960kHz(8 倍过采样,每个周期 8 个点)
采样方式:同步触发(TIM 与 DDS 同步)
同时采集两个通道(参考 + 测量):
ADC1: 参考信号 v_ref(t)
ADC2: 测量信号 v_meas(t)
每个激励周期采集 8 点,连续采集 N 个周期:
数据块大小:8 × N 点(N 通常取 50~100)
8.2 数字 IQ 解调算法
// ============================================================
// 数字 IQ 解调
// 频率 f₀ = 120kHz, f_s = 960kHz
// 每个周期 8 个点,窗口长度 100 个周期 = 800 个采样点
// ============================================================
#define F0 120000.0f // 激励频率 (Hz)
#define FS 960000.0f // 采样率 (Hz)
#define N_CYCLES 100 // 窗口周期数
#define N_SAMPS (N_CYCLES * 8) // 总采样点数
// 预计算的正交参考表(在 init 中计算一次)
float cos_table[N_SAMPS];
float sin_table[N_SAMPS];
void Init_DigitalDemod(void) {
for (int i = 0; i < N_SAMPS; i++) {
float t = (float)i / FS;
cos_table[i] = cosf(2.0f * PI * F0 * t);
sin_table[i] = sinf(2.0f * PI * F0 * t);
}
}
// 数字 IQ 解调:输入两个通道的原始采样数据
void DigitalDemod(
float *v_ref, // 参考通道采样数组 [N_SAMPS]
float *v_meas, // 测量通道采样数组 [N_SAMPS]
float *I_out, // 输出:I 分量(同相)
float *Q_out // 输出:Q 分量(正交)
) {
float I_ref = 0, Q_ref = 0;
float I_meas = 0, Q_meas = 0;
for (int i = 0; i < N_SAMPS; i++) {
// 参考通道
I_ref += v_ref[i] * cos_table[i];
Q_ref += v_ref[i] * sin_table[i];
// 测量通道
I_meas += v_meas[i] * cos_table[i];
Q_meas += v_meas[i] * sin_table[i];
}
// 归一化(除以窗口长度)
float inv_N = 1.0f / (float)N_SAMPS;
I_ref *= inv_N; Q_ref *= inv_N;
I_meas *= inv_N; Q_meas *= inv_N;
// 计算相对幅度比和相位差
float mag_ref = sqrtf(I_ref * I_ref + Q_ref * Q_ref);
float mag_meas = sqrtf(I_meas * I_meas + Q_meas * Q_meas);
float phase_ref = atan2f(Q_ref, I_ref);
float phase_meas = atan2f(Q_meas, I_meas);
// 输出
*I_out = mag_meas / mag_ref; // 归一化幅度
*Q_out = phase_meas - phase_ref; // 相位差
}
// 注意:
// 数字 IQ 方法的刷新率 = f₀ / N_CYCLES = 120kHz / 100 = 1.2kHz
// 可调 N_CYCLES 平衡 SNR 和响应速度
// N_CYCLES = 100 → 1.2kHz, SNR 高, 响应慢
// N_CYCLES = 20 → 6kHz, SNR 低, 响应快
8.3 AD8302 vs 纯MCU 方案对比
| 对比项 | AD8302 方案 | 纯 MCU 数字 IQ |
|---|---|---|
| BOM 成本增量 | +¥8~12 | 零 |
| ΔR 分辨率 | 0.01~0.03Ω | 0.02~0.05Ω |
| 刷新率 | 1kHz | 0.2~6kHz(可调) |
| MCU 算力占用 | 低(~5% CPU) | 中高( |
| 抗混叠要求 | 外置 LPF | 外置抗混叠 LPF(更关键) |
| 开发难度 | 中(硬件+标定) | 高(采样时序+实时性) |
| 调试便利性 | 直观(示波器看 V_MAG/PHS) | 黑盒(全在代码里) |
推荐: 量产产品首选 AD8302 方案,开发验证阶段可用纯 MCU 方案快速原型。
九、调试与诊断接口
9.1 实时数据输出(UART)
// 调试输出格式(CSV,115200bps)
// 每 10ms 输出一行:
void DebugOutput(float dR_mOhm, float dL_uH,
float dR_baseline, float dL_baseline,
uint8_t car_state, float temp_C) {
printf("%lu,%0.2f,%0.3f,%0.2f,%0.3f,%d,%0.1f\n",
HAL_GetTick(),
dR_mOhm, // ΔR 单位 mΩ
dL_uH, // ΔL 单位 μH
dR_baseline, // 基线 ΔR 单位 mΩ
dL_baseline, // 基线 ΔL 单位 μH
car_state, // 0=无车 1=有车
temp_C // 温度 °C
);
}
9.2 AD8302 自检
上电后检查 AD8302 是否正常工作:
uint8_t AD8302_SelfTest(void) {
// 1. 检查 V_MAG 和 V_PHS 是否在 0~1.8V 范围内
float V_MAG = ReadAD8302_MAG();
float V_PHS = ReadAD8302_PHS();
if (V_MAG < 0.1f || V_MAG > 1.7f) return 0; // 异常
if (V_PHS < 0.1f || V_PHS > 1.7f) return 0; // 异常
// 2. 检查 VREF 输出是否为 1.8V ±5%
float VREF = ReadAD8302_VREF();
if (VREF < 1.71f || VREF > 1.89f) return 0;
// 3. 计算瞬间信噪比
float mag_stddev = EstimateNoise(V_MAG, 100); // 100 点
if (mag_stddev > 0.01f) return 0; // 噪声过大
return 1; // 自检通过
}
十、性能验证与调参指引
10.1 现场调试步骤
Step 1:空载标定
- 线圈周围无金属
- 采集 V_MAG₀, V_PHS₀
- 计算噪声本底 σ_MAG, σ_PHS
Step 2:模拟车辆(铁板测试)
- 1m×1m 钢板置于线圈正上方
- 记录 ΔR、ΔL 变化量
- 验证触发判决是否正常工作
Step 3:铝板测试
- 1m×1m 铝板(3~5mm厚)
- 验证 ΔR 信号是否足够大
- 调整模糊权重使铝板可靠触发
Step 4:实车测试
- 纯钢、钢铝混合、全铝车辆各 10 次通过
- 统计触发成功率和响应时间
Step 5:环境测试
- 高温/低温(恒温箱)
- 喷水模拟雨天
- 验证基线和补偿算法
10.2 参数调优速查
| 现象 | 可能原因 | 调整方向 |
|---|---|---|
| 铝车漏触发 | ΔR 阈值太高 | 降低 dR_trigger(如 0.015→0.008Ω) |
| 温漂误触发 | 基线跟踪太慢 | 增大 alpha_slow |
| 雨天后频繁误触发 | 积水漂移 | 增大上升沿速率阈值 |
| 车辆离开后长时间保持"有车" | 存在定时器太长 | 减小 hold_ms |
| 跟车计数不足 | 车缝谷底判断太严 | 减小 T_gap_min |
| 高海拔频繁异常触发 | 匝间放电 | 降低激励电压 / 增强绝缘 |