Files
xdeSpace/方案B_AD8302接口与IQ解调算法详解.md
T

1316 lines
42 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.
# 方案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 引脚功能
```
AD830214引脚 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(过采样至 16bitOSR=16
采样率:1ksps(每个通道,两个通道)
采样时间:47.5 个 ADC 时钟周期
ADC 时钟:60MHzAPB2
参考电压:3.3VVDDA
过采样(硬件过采样):
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 点双缓冲)
半满中断 / 满中断 → 触发数据处理
定时器触发:
TIM61kHz)→ 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 第一步:原始量采样
```
每 1ms1kHz 采样率):
[0] 读取 V_MAG_rawADC 码值)
[1] 读取 V_PHS_rawADC 码值)
[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 算法伪代码
```c
// ============================================================
// 核心算法: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 // 输出:ΔLH
) {
// 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 不支持浮点运算或三角函数,可以用预计算表:
```c
// 预计算表(在初始化时计算一次)
// 对于固定频率,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(滤除电源纹波和噪声,保留车辆信号):
```c
// 二阶 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 处理一次):
```c
// 每 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 双基线结构
维护两组基线:**快基线**(快速恢复)和**慢基线**(温漂跟踪)。
```c
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 才能回到真实空载值。
```c
// 快速恢复状态机
// 车辆刚离开 → "预恢复"状态(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 多特征模糊触发
```c
// ============================================================
// 触发判决参数
// ============================================================
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 车速估算与谷底检测
```c
// ============================================================
// 车速估算(基于同一线圈前/后沿时差)
// ============================================================
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 解调算法
```c
// ============================================================
// 数字 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 | 中高(~20~40% CPU |
| 抗混叠要求 | 外置 LPF | 外置抗混叠 LPF(更关键) |
| 开发难度 | 中(硬件+标定) | 高(采样时序+实时性) |
| 调试便利性 | 直观(示波器看 V_MAG/PHS) | 黑盒(全在代码里) |
**推荐:** 量产产品首选 AD8302 方案,开发验证阶段可用纯 MCU 方案快速原型。
---
## 九、调试与诊断接口
### 9.1 实时数据输出(UART
```c
// 调试输出格式(CSV115200bps
// 每 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 是否正常工作:
```c
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 |
| 高海拔频繁异常触发 | 匝间放电 | 降低激励电压 / 增强绝缘 |
---
## 文档结束