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

42 KiB
Raw Permalink Blame History

方案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.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 点双缓冲)
  半满中断 / 满中断 → 触发数据处理

定时器触发:
  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 算法伪代码

// ============================================================
// 核心算法: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 不支持浮点运算或三角函数,可以用预计算表:

// 预计算表(在初始化时计算一次)
// 对于固定频率,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 中高(2040% CPU
抗混叠要求 外置 LPF 外置抗混叠 LPF(更关键)
开发难度 中(硬件+标定) 高(采样时序+实时性)
调试便利性 直观(示波器看 V_MAG/PHS 黑盒(全在代码里)

推荐: 量产产品首选 AD8302 方案,开发验证阶段可用纯 MCU 方案快速原型。


九、调试与诊断接口

9.1 实时数据输出(UART

// 调试输出格式(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 是否正常工作:

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
高海拔频繁异常触发 匝间放电 降低激励电压 / 增强绝缘

文档结束