From 2402d323f3b0c62ef3fbad940f14e689cdcffbe1 Mon Sep 17 00:00:00 2001 From: admin Date: Sun, 5 Jul 2026 21:20:52 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=84=20=E6=96=B9=E6=A1=88B=20AD8302?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E7=94=B5=E8=B7=AF=E4=B8=8EIQ=E8=A7=A3?= =?UTF-8?q?=E8=B0=83=E7=AE=97=E6=B3=95=E8=AF=A6=E8=A7=A3=EF=BC=88=E7=A1=AC?= =?UTF-8?q?=E4=BB=B6=E6=8E=A5=E5=8F=A3+=E8=BD=AF=E4=BB=B6=E7=AE=97?= =?UTF-8?q?=E6=B3=95=E5=AE=8C=E6=95=B4=E5=AE=9E=E7=8E=B0=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 方案B_AD8302接口与IQ解调算法详解.md | 1315 +++++++++++++++++++++++++++ 1 file changed, 1315 insertions(+) create mode 100644 方案B_AD8302接口与IQ解调算法详解.md diff --git a/方案B_AD8302接口与IQ解调算法详解.md b/方案B_AD8302接口与IQ解调算法详解.md new file mode 100644 index 0000000..110bd78 --- /dev/null +++ b/方案B_AD8302接口与IQ解调算法详解.md @@ -0,0 +1,1315 @@ +# 方案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 | +| 高海拔频繁异常触发 | 匝间放电 | 降低激励电压 / 增强绝缘 | + +--- + +## 文档结束