首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >MPS的SAR ADC谁用过?我!我用过!(附带开源信号分析仪)

MPS的SAR ADC谁用过?我!我用过!(附带开源信号分析仪)

作者头像
云深无际
发布2025-08-15 10:59:40
发布2025-08-15 10:59:40
21100
代码可运行
举报
文章被收录于专栏:云深之无迹云深之无迹
运行总次数:0
代码可运行

摘要:这篇文章按照芯片解读,硬件调试,数据分析仪三大部分来写。

目前发布的有两颗料,终于有厂家的 ADC 的产品我可以像追星一样的跟了
目前发布的有两颗料,终于有厂家的 ADC 的产品我可以像追星一样的跟了

目前发布的有两颗料,终于有厂家的 ADC 的产品我可以像追星一样的跟了

我们这次的主角是这颗 SAR 的,就是分辨率的区别:

三颗料是共用一个数据手册的
三颗料是共用一个数据手册的

三颗料是共用一个数据手册的

MPS好的一点就是有方便的小程序存数据手册:戳这个地方MDC9747X

型号

分辨率

SINAD (120kHz)

SNR

采样率

功耗(1MSPS@3.3V)

MDC97476

12-bit

72.06 dB

72.26

1 MSPS

4.9 mW

MDC97477

10-bit

61.76 dB

61.81

1 MSPS

4.9 mW

MDC97478

8-bit

50.03 dB

50.04

1 MSPS

4.9 mW

正好在吃葡萄,放上去看看大小

超小封装,引脚就6个

当然也有严谨一点的游标卡尺
当然也有严谨一点的游标卡尺

当然也有严谨一点的游标卡尺

因为长文怕大家没有动力看完,可以先看视频:

最早的视频

这是更新了上位机版本

正好 DHO804 有个简易的电压表:

非常的好用
非常的好用

非常的好用

看看背面

右下角看上去有个环岛
右下角看上去有个环岛

右下角看上去有个环岛

有颗 DAC,我对这个设计还比较疑惑,后面问了 MPS 的 FAE 解惑了,不得不说,速度和质量是真的高。

因为我看原理图的时候发现了很多的:

有 L 和 F 后缀的引脚
有 L 和 F 后缀的引脚

有 L 和 F 后缀的引脚

遇到问题直接论坛问

也就是说,为了兼容不同的电平:

跳线控制
跳线控制

跳线控制

MDC97476 的 IO 电平追踪 VDD 电源电压,即:

若 VDD = 5V → 输出 IO 电平为 5V 兼容;

若 VDD = 3.3V → IO 电平为 3.3V;

多数 MCU/FPGA IO 电平 仅支持最高 3.3V,若 VDD 配成 5V,直连会烧坏外部芯片 IO 口

EVB 板上添加了 电平转换器(Level Shifter) 来适配这个问题:

信号名(带 _F)

含义

ADC_CSn_F

原始 ADC 输出信号(高电平,跟随 VDD)

ADC_CSn_L_F

经电平转换后的信号(恒定 3.3V 兼容)

“_L_F” 后缀中的 “L” 表示 Level shifted 的版本。

那上面的 DAC 是什么意思?

是为了测量不同供电下的情况
是为了测量不同供电下的情况

是为了测量不同供电下的情况

怪不得 MPS 的片子卖的好,服务就是好。

继续说片子

架构:SAR(Successive Approximation Register)

接口:三线 SPI 兼容

电源电压:2.7V-5.2V

工作温度:**-40 到125**

封装:6-pin TSOT-23

参考电压:内部参考 = VDD

内部的架构也很清爽,就是输入加量化级到 SPI 输出
内部的架构也很清爽,就是输入加量化级到 SPI 输出

内部的架构也很清爽,就是输入加量化级到 SPI 输出

芯片内部模块包括:

采样保持电路(T/H)

电容阵列 DAC(Charge Redistribution DAC)

SAR 控制器

比较器

SPI 接口输出

这个芯片使用起来很简单,不需要寄存器什么的,直接就是三线 SPI

我拿到的是 76
我拿到的是 76

我拿到的是 76

时序:

CS 拉低:开始转换 + SDATA 输出低

13 个 SCLK 上升沿后:进入采样状态

第 16 个 SCLK 下降沿:SDATA 输出完成,进入三态

SDATA 输出格式:前导 4 个零,后接 N 位数据(N = 12/10/8),剩余位补 0 或进入高阻态

根据时序可以写出来:

代码语言:javascript
代码运行次数:0
运行
复制
// GPIO 控制 CS 低电平,开始转换
HAL_GPIO_WritePin(GPIOx, CS_Pin, GPIO_PIN_RESET);

// SPI 接收 2 字节(16 位)
uint8_t adc_buf[2] = {0};
HAL_SPI_Receive(&hspi1, adc_buf, 2, HAL_MAX_DELAY);

// GPIO 拉高 CS,结束通信
HAL_GPIO_WritePin(GPIOx, CS_Pin, GPIO_PIN_SET);

// 解析数据
uint16_t raw_code = ((adc_buf[0] << 8) | adc_buf[1]) >> 4;  // 只取中间12位

上面的时序不好看,可以直接仿真一个:

Clock 默认是高电平,我就不改动了,以数据手册为准
Clock 默认是高电平,我就不改动了,以数据手册为准

Clock 默认是高电平,我就不改动了,以数据手册为准

CS(片选):从高拉低启动转换,持续低电平直到数据传输完成

SCLK(串行时钟):模拟连续 16 个 SCLK(20MHz);占空比 50%,芯片是下降沿吐数据,MCU测是上升沿还是下降沿要看MCU引脚处CLOCK 和 data的相位关系,确保setup/hold time 满足要求。

SDATA(串行输出):以 0000 + D11...D0 顺序输出,在 SCLK 的下降沿前稳定数据

手册写的很清晰
手册写的很清晰

手册写的很清晰

静态性能

参数

典型值

INL

±0.5 LSB

DNL

±0.4 LSB

偏移误差

±0.7 LSB

增益误差

±3.2 LSB

动态参数

ADC(MDC97476/7/8)的动态参数按官方手册汇总(统一测试条件:VDD=3.3 V,fSCLK=20 MHz,采样率 fSAMPLE=1 MSPS,正弦输入 fIN=120 kHz,TA=25 °C):

MDC97476(12-bit)

SNR:71–72.2 dBFS;SINAD:68–71.9 dBFS;THD:−84…−78 dB;SFDR:81–87 dB。

典型图示(1 MSPS)读数:SNR 72.26 dB、SINAD 72.06 dB、THD −84.67 dB、SFDR 87.9 dB、ENOB 11.68 bits。

补充说明

以上指标均在手册指定的条件下测得(VDD=3.3 V、fSCLK=20 MHz、1 MSPS、fIN=120 kHz),手册相应页脚注已标明测试条件;ENOB 可由公式 ENOB ≈ (SINAD−1.76)/6.02 验证;例如 12 位器件典型 SINAD≈72.06 dB 对应 ENOB≈11.7 bit,与典型曲线标注一致。

厂商测试时一般不是满量程输入去测,所以满量程测试时,ENOB还会更高。

可视化的对比
可视化的对比

可视化的对比

引脚功能

引脚

名称

类型

功能说明

1

VDD

电源

正电源输入,2.7v-5.2v

2

GND

接地

3

VIN

输入

模拟输入,0V 至 VDD

4

SCLK

输入

SPI 时钟,最大 20 MHz

5

SDATA

输出

SPI 输出数据(下降沿更新)

6

CS

输入

SPI 片选,下降沿启动转换

工作模式

Normal Mode

CS 保持低电平 > 10 个 SCLK 下降沿;可连续采样,保持最大吞吐率(1 MSPS)

SCLK 周期:50 ns,对应最高 20 MHz

SCLK 最大:20 MHz,对应最大采样率:1 MSPS,SPI要分频的,也就是 APB 上面要 40M

Sleep Mode

节能模式,适合低频采样;CS 在第二个和第10个clock期间拉高进入sleep → 进入 Sleep

输出格式

芯片型号

数据格式

MDC97476

4 个前导零 + 12 位有效数据 + 高阻

MDC97477

4 个前导零 + 10 位有效 + 2 个 0

MDC97478

4 个前导零 + 8 位有效 + 4 个 0

为什么要选一个参数和MCU内置的ADC呐?

一般的单片机内置 ADC 速度较慢,通常标称值可以到 1MSPS 以内,但是受单片机处理多任务时的资源限制,其实际连续采样速率远小于标称值。对于低速信号采集够用,但如果需要处理高速信号,如无线通信、音频或视频数据,会无法满足系统采样速率要求。

外部独立 ADC 通常可以达到更高的采样率,如 1MSPS 或以上,总之就是很灵活。

其次由于 ADC 内置在单片机内部,其抗干扰能力通常会受到单片机其他模块的影响,如 CPU、PWM、电源模块等。这些模块的切换和操作可能会在 ADC 工作时引入内部噪声干扰,且无法消除, 显著降低 ADC 的有效位数 (ENOB)。

外部 ADC由于其设计,制造过程更为专业,通常在芯片内部模拟和数字接口部分做了更好的隔离,抗干扰能力更强,特别是在恶劣外部干扰环境下工作时,独立 ADC 的性能优势非常明显。

好了我们继续学习:

Hold Phase(保持阶段)
Hold Phase(保持阶段)

Hold Phase(保持阶段)

这张图是 Figure 5,表示 ADC 正处于“保持”阶段,即采样完成,进入转换。

元件

说明

V_IN

模拟输入电压,来自外部信号源

SW1

控制开关,用于连接输入或断开后接地

SW2

控制开关,用于将采样电容负端接到 VDD/2

Sampling Capacitor

采样电容,负责保持输入电压信息(电荷)

Comparator

比较采样电压和 DAC 电压是否相等

Capacitive DAC

电容阵列 DAC,逐步逼近采样值

SAR + Control Logic

SAR 逻辑控制器,逐位控制 DAC 设置、判断电压是否逼近输入值

SW1 接地:使采样电容的一端(靠近 V_IN 端)断开输入,接地;

SW2 断开:此时 VDD/2 未连接进来;

电容两端保持电荷 → 等价于“维持电压”,此时:比较器输入端电压 = 采样电容维持的电压;控制逻辑开始逐步调整 DAC 输出,使之与采样电压匹配。

逐次逼近转换典型步骤如下:

  1. 采样阶段(Track): SW1 导通 → 电容两端电压跟随 V_IN;SW2 接 VDD/2,提供偏置;控制逻辑未工作。
  2. 保持阶段(Hold)(如图):SW1 接地、SW2 断开,电容电荷冻结;比较器与 DAC 开始逼近;DAC 每次切换一位 → 控制电容阵列改变输出电压;比较器判断 DAC 输出与保持电压的大小关系;最终得到数字输出(SAR)。

为什么用 VDD/2

SAR ADC 通常为单电源供电;中点电压 VDD/2 提供“零点参考”,DAC 输出是围绕它正负跳变;这有助于提升共模范围和线性度。

电容分布式 DAC 的优点:低功耗;可实现非常小芯片面积;快速转换速度;静态状态下无电流消耗。

等效模拟输入电路(Figure 8)
等效模拟输入电路(Figure 8)

等效模拟输入电路(Figure 8)

图中展示了 ADC 的 等效输入建模电路,用于理解采样瞬间信号的加载情况。

元件

描述

D_ESD1, D_ESD2

ESD 防护二极管,钳位输入在 GND−300mV 与 VDD+300mV 之间

C_PIN = 2pF

封装与管脚等效输入电容

SW1

Track-and-Hold 控制开关

R_ON = 125Ω

Track/Hold 开关闭合后的等效导通电阻

C_SAMPLING = 14pF

ADC 的内部采样保持电容,决定采样动态响应

→ Comparator

比较器输入端,与 DAC 输出比较

上面这个小电容有些优点:

对 ADC 模拟带宽的提升

输入电容越小 → RC 充放电时间常数越小 → 模拟带宽越高

输入端等效电路是 信号源阻抗 R_sourceC_PIN 构成低通滤波器,带宽大约为

当 C_PIN 从 10 pF 降到 2 pF 时,带宽提升 5 倍,可以更好地响应快速变化的信号;对高速 ADC(特别是采样频率在 MHz 级)很重要,否则高频分量会被输入 RC 滤掉。

降低采样瞬间的kickback 电压

SAR ADC 在采样瞬间,SW1 闭合 → C_sampling(14 pF)瞬时充电,会把输入信号拉动一个小幅度的电压跳变,这就是 kickback

C_PIN 越大,这个跳变幅度越大,因为它与 C_sampling 之间存在电荷共享;小的 C_PIN 意味着前级驱动器不需要快速补偿大电流,减轻前级运放带宽与驱动能力的要求,尤其在多通道复用时好处更明显。

静态功耗更低

小电容在静态时几乎没有额外漏电消耗,结合电容分布式 DAC 架构的 SAR ADC,在静态状态下功耗非常低。

设计上的权衡

虽然小 C_PIN 带来高带宽与低 kickback,但它也减少了对高频干扰的自然滤波作用,可能增加 EMI/噪声的进入机会,因此 PCB 端仍需合适的 RC 滤波器或前端缓冲;对高阻抗信号源来说,小电容意味着采样瞬间电压会被拉动得更明显,所以通常搭配低输出阻抗的缓冲运放。

仿真是数据比较夸张的,为了好看:

好处
好处

好处

这个仿真直观看出了小输入电容的好处:

小 C_PIN(2 pF)

带宽高(≈ 796 MHz);Kickback 电压小(≈ 125 mV);恢复速度快,对前级运放带宽要求低

大 C_PIN(10 pF)

带宽低(≈ 159 MHz);Kickback 电压大(≈ 417 mV);恢复慢,对前级运放瞬态响应要求高

这解释了为什么高速 ADC 会尽量把 C_PIN 设计得很小,从而减少前端驱动负担并提升高频性能。

Track Phase(采样)

SW1 闭合VIN → C_SAMPLING;信号通过 R_ONC_SAMPLING 充电;形成 **RC 延迟 τ = R_ON × (C_PIN + C_SAMPLING)**;

影响采样带宽。

Hold Phase(保持)

SW1 断开,电容保持采样电压;SAR 开始逐位逼近。

对 SNR / THD / 谐波失真 敏感的应用中,建议注意以下事项:

输入阻抗匹配:

因为存在 R_ON = 125ΩC_TOTAL ≈ 16pF,所以:输入源阻抗必须远低于 1kΩ,最好 < 100Ω;否则 RC 充电不完全,采样误差增大。

等效带宽限制:

前级:使用低阻抗缓冲驱动器,如:运放缓冲器。

加了 buffer
加了 buffer

加了 buffer

幅频响应
幅频响应

幅频响应

上图展示了 ADC 前端输入通路中由开关电阻 和采样电容 组成的一阶 RC 低通滤波器的幅频响应:

截止频率(-3 dB 点)为约 90.95 MHz,见红色虚线标注;在高于该频率的信号将显著衰减,这有助于抑制高频噪声和混叠(aliasing)信号。

我太讨厌这个接口了,数的真麻烦,下次连在一起
我太讨厌这个接口了,数的真麻烦,下次连在一起

我太讨厌这个接口了,数的真麻烦,下次连在一起

官方的原理图非常的简单
官方的原理图非常的简单

官方的原理图非常的简单

调试

搞过硬件的都知道,这个事情就没有你们简单的,即使是数据手册里面写的那么简单:

脆弱瞬间
脆弱瞬间

脆弱瞬间

就是这个 STM32 的 三线 SPI 绝对有问题(Receive Only Master),打死不要用,软件拉也拉不下来,后来是 4 线 SPI。

main() 中加入 ADC 上电后稳定延时(建议 ≥ 200us):

代码语言:javascript
代码运行次数:0
运行
复制
HAL_Delay(1);  // 上电后延时 ≥ 200us,确保 ADC 准备好

main()MX_GPIO_Init() 之后加入:

代码语言:javascript
代码运行次数:0
运行
复制
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_7, GPIO_PIN_SET);  // 确保 CS 默认高电平

对,这个就是 bug,先拉高,

使用USB CDC 输出替代串口

打开 STM32CubeMX

在 Peripherals 栏中启用:

代码语言:javascript
代码运行次数:0
运行
复制
Connectivity > USB_DEVICE → Communication Device Class (Virtual Port Com)

设置参数:

Mode: Device_Only

Class For FS IP: Communication Device Class (CDC)

会自动启用:USB_OTG_FS,并使用 PA11 (DM), PA12 (DP)

修改 main.c:替代 fputc

代码语言:javascript
代码运行次数:0
运行
复制
int _write(int file, char *ptr, int len)
{
  while (CDC_Transmit_FS((uint8_t *)ptr, len) == USBD_BUSY)
  {
    HAL_Delay(1);
  }
  return len;
}

批量发送整串字符串,而不是一字节一字节发。你可以这样(最终的代码使用了这个):

代码语言:javascript
代码运行次数:0
运行
复制
char usb_buf[64];

while (1)
{
    uint16_t adc_raw = MDC97476_ReadRaw();
    float voltage = MDC97476_ConvertToVoltage(adc_raw, 3.3f);

    int len = sprintf(usb_buf, "ADC = %u, Voltage = %.3f V\r\n", adc_raw, voltage);
    CDC_Transmit_FS((uint8_t*)usb_buf, len);

    HAL_Delay(1000);
}

波特率可随意设置(无效,USB CDC 不依赖波特率);F411 不支持 USB 全速自带 PHY 直接供电,需要外接 USB 供电(5V via VBUS)

代码语言:javascript
代码运行次数:0
运行
复制
MX_USB_DEVICE_Init();

即可初始化 USB CDC 设备,HAL 会默认设置虚拟串口的参数为:

波特率:115200(或其他默认值)

数据位:8

停止位:1

校验位:无

如果需要修改,可以修改:

代码语言:javascript
代码运行次数:0
运行
复制
USBD_CDC_LineCodingTypeDef LineCoding = {
    .bitrate = 115200,  // 可改为 921600 等
    .format = 0x00,     // 停止位:1
    .paritytype = 0x00, // 无奇偶校验
    .datatype = 0x08,   // 8位数据
};

因为 USB 的输出和串口不一样,需要加入一个检测:

代码语言:javascript
代码运行次数:0
运行
复制
 // 等待 USB 枚举完成
  while (hUsbDeviceFS.dev_state != USBD_STATE_CONFIGURED)
  {
    HAL_Delay(10);
  }

实际传输速率

USB FS(全速):

实际最大传输速率约为 1 MB/s(8 Mbps);一帧最多 64 字节,每毫秒最多传输一次.

USB HS(高速):

实际最大传输速率高达 30-40 MB/s;512 字节/帧,1ms 多帧传输.

所以比串口高得多,即使你设置 "波特率" 为 9600,USB CDC 实际仍然以几百 KB/s 的速率传输,只是软件兼容层设置为了 9600。

供电 VDD = 3.3V,存在以下几种模式:

模式

输入类型

范围估计

单端输入

VIN 对 AGND

0 ~ VDD(即 0~3.3V)

差分输入

VIN+ - VIN-

±VREF/2 或 0~VREF 范围

输入钳位电路

内部钳位/外部保护

输入必须在 AGND - 0.3V ~ VDD + 0.3V 内

这里没有后面的钳位电路,而从实际原理图看:使用了 单端输入VIN 是单一引脚,没有 VIN+ / VIN-);前级缓冲运放供电为 +7V / -2V,输出可能为 负电压

自动读取帧格式:

代码语言:javascript
代码运行次数:0
运行
复制
0xAA + ADC低字节 + ADC高字节 + 电压低字节 + 电压高字节 + 0x55

实时绘图:

电压范围 0~3.3V;图像自动刷新

键盘控制:

按下空格 space:开始 / 暂停采集

按下 ESC:退出并关闭文件与串口

自动保存 CSV 日志:

包括原始 ADC 值和电压值(单位:V);保存为 adc_log_年月日_时分秒.csv

这是第一次的数据
这是第一次的数据

这是第一次的数据

代码语言:javascript
代码运行次数:0
运行
复制
Mean Voltage (V)  Min Voltage (V)  Max Voltage (V)  RMS Voltage (V)  \
Value          1.594536              0.0            3.096         1.812197   

       Std Deviation (V)  
Value           0.861112  

时域分析结果:

数据在 0~3.3V 范围内波动(最大值约 3.096V,最小值为 0V)。

波形在 1.6V 附近居中,有一定周期性,可能是某种低频信号(如慢变化电压或模拟波形)。

频域分析结果:

主频率成分集中在低频部分(如 < 10 Hz),整体频谱没有明显尖锐的高频成分,符合慢变电压的特征。

统计结果(基于 1ms 采样间隔):

指标

数值

平均电压

1.594 V

最小电压

0.000 V

最大电压

3.096 V

RMS(有效值)

1.812 V

标准差(波动性)

0.861 V

此时我还没有意识到这里没接,文档里面也没有写
此时我还没有意识到这里没接,文档里面也没有写

此时我还没有意识到这里没接,文档里面也没有写

那就顺便写一下即使信号源未连接或无信号输出,ADC仍然会采集到“漂浮的”随机数据或电平噪声有哪些原因:

原因 1:ADC 输入悬空导致输入端“漂浮”

若 ADC 输入引脚悬空(未接信号源或信号源为高阻态),其电压处于不确定状态,称为“漂浮”。

浮空输入会导致 ADC 采样到环境电磁干扰(EMI)、电源纹波、邻近 IO 的耦合信号、电容充放电残留电压等。

电压值随机波动;数值不稳定、无规律;偶尔出现 0 或满量程,更多是中间电压。

原因 2:模拟前端存在内部偏置或泄漏路径

某些 ADC(MDC97476)内部带有采样保持电路、偏置电流源、电荷注入结构,即使没有外部信号,也可能在输入端形成一定偏置电压或干扰响应;如果使用的是电容耦合、未加下拉电阻,也会导致浮空电压残留。

原因 3:输入引脚高阻抗,容易感应噪声

ADC 通常具有 >1MΩ 的输入阻抗,在未连接低阻抗源时,相当于“天线”,极易感应周围 50Hz / 60Hz 电源噪声、开关电源纹波等;即使加了低通滤波,也不能完全去除这种低频干扰。

原因 4:电源纹波通过内部耦合影响 ADC

ADC 的基准电压或供电电压如果不稳定,特别是在模拟地与数字地耦合较差时,也可能在无信号下采集到虚假的非零电压值

问题

建议

悬空导致不稳定

用 电阻下拉(如10kΩ~100kΩ)至 GND,或者连接一个稳定的信号源/参考电压

电源纹波引起干扰

使用 LDO 稳压 + 适当的 旁路电容(0.1μF + 10μF)

浮空电容注入影响

增加前端 RC 滤波器 或缓冲运放

不希望采集浮值

固件中可判断采样值是否连续低于阈值,做“无信号”判断

这是调试的一个图,被干扰的样子
这是调试的一个图,被干扰的样子

这是调试的一个图,被干扰的样子

信号分析仪设计

我这里就用第一开始的稿子了:对于这个上位机,我首先想要丰富的数据分析呈现,所以时域,频域,时频分析是有的,而且 ADC 本身缺失了滤波器的功能,所以我加入了数字滤波器。

在开始采集后,会把所有的数据保存为 CSV,做后处理工作。

然后整体流程是这样的:

代码语言:javascript
代码运行次数:0
运行
复制
Start program
   │
   ▼
串口初始化,创建图形界面
   │
   ▼
定义 update() 函数:读取数据 → 更新图像
   │
   ▼
FuncAnimation() 注册 update 函数
   │
   ▼
plt.show() 启动事件循环(每隔 interval ms 调用一次 update)
   │
   ▼
 ┌────────────┐
 │每次刷新过程│
 └────────────┘
    └─> update()
         └─> 读取串口帧
         └─> 滤波处理
         └─> FFT 分析
         └─> 更新图形
         └─> 返回更新的 Line 对象
这是最终的效果
这是最终的效果

这是最终的效果

也经过了 UI 的调整
也经过了 UI 的调整

也经过了 UI 的调整

但是以下是最开始的程序设计,所以大体思路按照如下设计。

数据协议

MCU → 上位机 发送帧格式(总长 6 字节):

字节序号

内容

说明

0

0xAA

帧头(同步)

1

ADC 低字节

原始 ADC 值低 8 位

2

ADC 高字节

原始 ADC 值高 8 位

3

电压低字节

毫伏值低 8 位

4

电压高字节

毫伏值高 8 位

5

0x55

帧尾

安装依赖项(如果尚未安装):

代码语言:javascript
代码运行次数:0
运行
复制
pip install pyserial matplotlib scipy numpy

运行环境必须包含串口 COM16,并有设备按照帧结构:

代码语言:javascript
代码运行次数:0
运行
复制
帧头(0xAA) + 原始数据(2字节) + 电压mv(2字节) + 帧尾(0x55)

串口与数据采集配置

代码语言:javascript
代码运行次数:0
运行
复制
ser = serial.Serial("COM16", 115200, timeout=1)

打开串口 COM16,波特率 115200,超时时间 1 秒。

用于接收来自 STM32 的数据包(帧结构:0xAA + 原始数据(2B) + 电压mV(2B) + 0x55)

采样与滤波参数设置

代码语言:javascript
代码运行次数:0
运行
复制
BUFFER_SIZE = 512
SAMPLE_RATE = 1000  # Hz

使用一个环形缓冲区(deque)维护最近 512 个样本点。

采样率设为 1000Hz,决定 FFT 横坐标频率轴最大为 500Hz。

代码语言:javascript
代码运行次数:0
运行
复制
FILTER_LOW = 30
FILTER_HIGH = 200

默认带通滤波器的截止频率设置为 30Hz~200Hz。

CSV 数据保存初始化

代码语言:javascript
代码运行次数:0
运行
复制
csv_file = open(f"adc_log_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.csv", ...)
csv_writer = csv.writer(csv_file)
csv_writer.writerow(["Sample", "Voltage (V)"])

创建一个时间戳命名的 CSV 文件;每帧数据记录一行(ADC 原始值,电压值)。

Matplotlib 界面搭建(两个子图)

代码语言:javascript
代码运行次数:0
运行
复制
fig, (ax_time, ax_freq) = plt.subplots(2, 1, figsize=(10, 6))

上图 ax_time:显示 时域波形(原始+滤波)

下图 ax_freq:显示 频域波形(FFT)

代码语言:javascript
代码运行次数:0
运行
复制
line_raw, = ax_time.plot(data, label="Raw")
line_filtered, = ax_time.plot(filtered_data, label="Filtered", linestyle='--')

蓝色实线:原始采样数据

虚线:带通滤波器输出

代码语言:javascript
代码运行次数:0
运行
复制
line_fft, = ax_freq.plot(np.linspace(0, SAMPLE_RATE/2, BUFFER_SIZE//2), np.zeros(BUFFER_SIZE//2))
peak_marker, = ax_freq.plot([], [], 'ro', label="Peak")

line_fft 是实时更新的频谱曲线

peak_marker 是频谱峰值的红色标记点

滤波器类型:二阶带通巴特沃斯滤波器

实时参数调节:

代码语言:javascript
代码运行次数:0
运行
复制
b, a = signal.butter(2, [low, high], btype='band', fs=SAMPLE_RATE)
filtered = signal.filtfilt(b, a, list(data))

低频和高频截止值由滑块 (Slider) 控制

串口帧读取函数

代码语言:javascript
代码运行次数:0
运行
复制
def read_frame():
    while ser.read(1) != b'\xAA':
        pass
    frame = ser.read(5)
    if len(frame) < 5 or frame[-1] != 0x55:
        return None
    raw = frame[0] | (frame[1] << 8)
    voltage_mv = frame[2] | (frame[3] << 8)
    return raw, voltage_mv / 1000.0

保证同步从帧头 0xAA 开始读取

提取两部分内容:

raw:原始 ADC 码

voltage: 计算后电压值(单位 V)

主循环 update(frame) 动画函数

读取数据并更新缓冲区

代码语言:javascript
代码运行次数:0
运行
复制
parsed = read_frame()
raw, voltage = parsed
data.append(voltage)
csv_writer.writerow([raw, voltage])

动态生成带通滤波器(Butterworth)

代码语言:javascript
代码运行次数:0
运行
复制
b, a = signal.butter(2, [low, high], btype='band', fs=SAMPLE_RATE)
filtered = signal.filtfilt(b, a, list(data))

butter 生成二阶滤波器系数

filtfilt 是前向后向滤波,避免相位偏移

时域图更新

代码语言:javascript
代码运行次数:0
运行
复制
line_raw.set_ydata(data)
line_filtered.set_ydata(filtered_data)

频谱 FFT 更新

代码语言:javascript
代码运行次数:0
运行
复制
fft_data = np.fft.rfft(filtered - np.mean(filtered))
fft_freq = np.fft.rfftfreq(len(filtered), 1/SAMPLE_RATE)
fft_mag = np.abs(fft_data)
fft_mag /= np.max(fft_mag + 1e-8)

rfft:仅计算实信号的正频部分

幅度归一化至 0~1

峰值频率注释

代码语言:javascript
代码运行次数:0
运行
复制
peak_idx = np.argmax(fft_mag)
peak_freq = fft_freq[peak_idx]
peak_amp = fft_mag[peak_idx]
peak_marker.set_data([peak_freq], [peak_amp])
ax_freq.set_title(f"FFT Spectrum - Peak: {peak_freq:.1f} Hz")

自动找出最大频率分量并在图中用红点标出;显示主频位置

按键事件绑定(ESC 退出)

代码语言:javascript
代码运行次数:0
运行
复制
def on_key(event):
    if event.key == 'escape':
        ser.close()
        csv_file.close()
        plt.close(fig)

实时动画渲染启动

代码语言:javascript
代码运行次数:0
运行
复制
ani = animation.FuncAnimation(fig, update, interval=10)
plt.show()

interval=10 毫秒刷新周期 ≈ 100Hz 界面更新速率

以上就是上位机的骨架,功能是在这个上面扩展的,可以给大家看一下现在的功能有哪些:

image-20250812111436597
image-20250812111436597

image-20250812111436597

FFT 的双游标测量功能

时域测量
时域测量

时域测量

包络,峰值保持和频点追踪等等
包络,峰值保持和频点追踪等等

包络,峰值保持和频点追踪等等

阅读原文戳进去是MPS 论坛ADC 版块,对这颗料感兴趣可以进去发帖逮住工程师一通问。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-08-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 云深之无迹 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 静态性能
  • 动态参数
  • 引脚功能
  • 工作模式
    • Normal Mode
    • Sleep Mode
  • 输出格式
  • 为什么要选一个参数和MCU内置的ADC呐?
    • 为什么用 VDD/2?
    • 对 ADC 模拟带宽的提升
    • 降低采样瞬间的kickback 电压
    • 静态功耗更低
    • 设计上的权衡
    • 输入阻抗匹配:
  • 调试
    • 在 main() 中加入 ADC 上电后稳定延时(建议 ≥ 200us):
    • 使用USB CDC 输出替代串口
    • 修改 main.c:替代 fputc
    • 实际传输速率
    • 时域分析结果:
    • 频域分析结果:
    • 统计结果(基于 1ms 采样间隔):
    • 原因 1:ADC 输入悬空导致输入端“漂浮”
    • 原因 2:模拟前端存在内部偏置或泄漏路径
    • 原因 3:输入引脚高阻抗,容易感应噪声
    • 原因 4:电源纹波通过内部耦合影响 ADC
  • 信号分析仪设计
  • 数据协议
  • 串口与数据采集配置
  • 采样与滤波参数设置
  • CSV 数据保存初始化
  • Matplotlib 界面搭建(两个子图)
  • 串口帧读取函数
  • 主循环 update(frame) 动画函数
    • 读取数据并更新缓冲区
    • 动态生成带通滤波器(Butterworth)
    • 时域图更新
    • 频谱 FFT 更新
    • 峰值频率注释
  • 按键事件绑定(ESC 退出)
  • 实时动画渲染启动
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档