第三章:软件算法实现——数字锁相放大器与I/Q解调

各位同学,欢迎来到实战课的核心环节。前面我们聊了硬件怎么搭、信号怎么采,但说实话,那些只是“骨架”。真正让美容仪能测准皮肤阻抗的,是今天要讲的这套软件算法——数字锁相放大器(DPSD)和I/Q解调。

我当年第一次接触这个课题时,也以为不就是算个幅值嘛,直接ADC采回来求个均方根不就行了?结果被噪声教做人。后来才明白,在微弱的皮肤阻抗信号面前,没有数字锁相放大,基本等于裸奔。

3.1 数字锁相放大器(DPSD)原理

先说说为什么需要锁相放大。你想想看,皮肤阻抗信号通常只有几毫伏到几十毫伏,而环境噪声、工频干扰、运动伪迹,哪个都比它大。传统滤波器?带宽一窄,相位就飘;带宽一宽,噪声又进来了。

数字锁相放大器的核心思路其实很朴素:我只关心和激励信号同频同相的那部分能量。其他频率的成分,统统视为噪声,给我滚蛋。

具体怎么做呢?我们发射一个已知频率的正弦波激励(比如50kHz),然后接收端把混入噪声的信号,和两路参考信号(同相和正交)分别相乘,再低通滤波。这个过程,说白了就是“相关检测”。

DPSD的数学本质:

输入信号 x(t) = A·sin(ωt + φ) + 噪声

参考信号1:r₁(t) = sin(ωt)

参考信号2:r₂(t) = cos(ωt)

相乘后低通,得到 I = (A/2)·cos(φ),Q = (A/2)·sin(φ)

嗯,这里要注意:参考信号必须和激励信号严格同步。我在项目中遇到过因为晶振漂移导致解调结果跳变的情况,后来改用同一颗晶振分频给发射和接收,问题才解决。

3.2 I/Q解调算法实现

I/Q解调,说白了就是把一个正弦波拆成两个正交分量。I是实部,Q是虚部。有了I和Q,幅值和相位就是手到擒来的事。

我习惯在嵌入式平台上用定点数来实现,毕竟浮点运算在低成本MCU上太慢了。下面给出一段C语言风格的伪代码,大家感受一下:

// 假设ADC采样率 fs = 1MHz,激励频率 f0 = 50kHz
// 每个周期采20个点

#define N 20  // 每个周期的采样点数
int16_t sin_table[N] = {0, 3090, 5878, 8090, 9511, 10000, 9511, 8090, 5878, 3090,
                        0, -3090, -5878, -8090, -9511, -10000, -9511, -8090, -5878, -3090};
int16_t cos_table[N] = {10000, 9511, 8090, 5878, 3090, 0, -3090, -5878, -8090, -9511,
                        -10000, -9511, -8090, -5878, -3090, 0, 3090, 5878, 8090, 9511};

int32_t I_sum = 0, Q_sum = 0;
for(int i = 0; i < N; i++) {
    I_sum += (int32_t)adc_buffer[i] * sin_table[i];
    Q_sum += (int32_t)adc_buffer[i] * cos_table[i];
}
// 平均后得到I和Q
int32_t I = I_sum / N;
int32_t Q = Q_sum / N;

这段代码里,我用了查表法生成正余弦参考信号,省去了实时计算三角函数的时间。你想想看,在中断里做sin运算?那中断延迟会高到让你怀疑人生。

我的经验:查表法的点数最好选4的倍数,这样sin和cos表可以共用,省一半存储空间。另外,表值用Q15格式(16位有符号整数,范围-32768~32767),乘积累加时注意扩展到32位防止溢出。

3.3 幅值与相位计算

有了I和Q,幅值和相位就是简单的数学运算了:

  • 幅值: Amplitude = 2 × sqrt(I² + Q²) / N
  • 相位: Phase = atan2(Q, I)

等等,这里有个坑。sqrt和atan2在嵌入式平台上都是重量级运算。我曾经在一个项目里直接调用了标准库的sqrt,结果一个通道算下来要200微秒,四个通道就是800微秒,控制周期直接崩了。

怎么办?两个思路:

  1. 查表法近似: 对于幅值,可以用max(|I|, |Q|) + 0.4 × min(|I|, |Q|) 来近似,误差在5%以内,但速度快了十倍。
  2. CORDIC算法: 只用移位和加法就能同时算出幅值和相位,适合没有硬件乘法器的低端MCU。

避坑指南:我曾经在相位计算上栽过跟头——atan2返回的是[-π, π]范围,但我们的激励信号和参考信号可能有固定的相位偏移。如果不做初始校准,算出来的相位值会一直偏大或偏小,导致后续的阻抗模型参数全错。

3.4 校准与归一化

算法写完了,直接上机?别急,还有最关键的一步——校准。

为什么需要校准?因为整个信号链路上有太多非理想因素:

误差来源 影响 校准方法
ADC偏移误差 I/Q直流偏置 短路测量,记录偏移量并扣除
放大器增益误差 幅值偏大或偏小 接已知电阻,计算增益系数
电缆寄生参数 相位偏移 开路/短路校准,矢量网络分析思想
温度漂移 增益和相位缓慢变化 定期插入参考测量通道

我个人的习惯是做一个“两点校准”:

  • 短路校准: 把测量电极短接,此时阻抗为0,理论上I=0, Q=0。实际测到的非零值就是系统偏移,后续每个测量值都要减去这个偏移。
  • 开路校准: 电极悬空,此时阻抗无穷大。记录此时的幅值作为参考上限,后续测量值归一化到[0, 1]区间。

归一化公式很简单:

// 归一化到0~1000的整数范围(方便显示)
normalized_value = (raw_value - short_offset) * 1000 / (open_value - short_offset);

嗯,这里要注意:开路测量时,由于没有负载,运放可能饱和。我建议在开路校准时,在电极间并联一个1MΩ的电阻,既模拟了高阻抗状态,又防止了运放输出轨到轨。

实战要点总结:

1. DPSD的核心是相关检测,只提取与激励同频的信号成分。

2. I/Q解调用查表法实现,注意定点数精度和溢出保护。

3. 幅值和相位计算尽量用近似算法或CORDIC,别直接调数学库。

4. 校准是算法落地的最后一道关,短路/开路校准必不可少。

好了,这一章的内容就到这里。下一章我们会把这些算法整合到实际的嵌入式系统中,看看怎么在中断服务程序里高效地跑完整个解调流程。到时候我会分享一个我踩过的坑——DMA双缓冲配置不当导致数据错位,那叫一个酸爽。

各位回去可以把今天讲的I/Q解调代码在开发板上跑一跑,用信号发生器输入一个已知幅值和相位的正弦波,看看解调出来的结果对不对。有问题随时在群里交流。