# 方案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 算法伪代码 ```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 // 输出:Δ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 不支持浮点运算或三角函数,可以用预计算表: ```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 // 调试输出格式(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 是否正常工作: ```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 | | 高海拔频繁异常触发 | 匝间放电 | 降低激励电压 / 增强绝缘 | --- ## 文档结束