简介这份资料围绕STM32F373的SDADC逐次逼近型模数转换器展开面向嵌入式开发者和工业控制方向的学习者帮助理解16位高精度模拟采集的配置方法以及双SDADC同步采样、多通道扫描等实际用法。包内共159个文件以C源文件stm32f37x_sdadc.c等、头文件为主配合hex/axf固件输出、uvproj工程文件和map映射文件便于直接打开工程查看初始化流程与中断处理逻辑约3.16MB结构紧凑。已有1800余人学习适用于传感器数据采集、环境监测、医疗设备等需要同时读取多路模拟量的场景。通过示例工程可以掌握单次/连续转换触发方式、采样时间设置、校准与噪声抑制功能以及双SDADC并行采集时如何保证采样率一致为实际项目中的多通道同步测量提供可直接借鉴的代码框架与调试思路。1. 为什么STM32F373的SDADC值得单独写一篇标称16位上电却读不出16位STM32F373 这颗芯片最吸引人的不是 72MHz 主频而是集成了两个独立的 Σ-Δ 型 ADCSDADC1 / SDADC2标称分辨率 16 位输出寄存器却是 24 位专门用来吃高精度传感器这类场景称重、桥式压力、电流检测、热电偶冷端补偿、电池电压监测。但几乎每一个第一次接触 F373 SDADC 的人都会遇到同一个现象按普通 ADC 那套「使能、启动、读寄存器」流程操作读回来的数据跳动很大有效分辨率也就 12 位左右。问题几乎都不在芯片本身而在参考电压选择、增益搭配、滤波阶数和偏移校准这四件事上没配对。这篇文章就是把这四件事逐一拆开给出一套能在开发板上直接复现的配置方法和参数边界。2. 看懂F373的SDADC数据通路从差分输入到24位原始码要过几道关SDADC 不是逐次逼近型 ADC它是 Σ-Δ 调制器加数字抽取滤波器的组合。要调好它得先知道数据在芯片内部走哪条路否则出了问题你连该查哪一段都不知道。2.1 两条独立转换链SDADC1与SDADC2的通道差异F373 的 SDADC 一共有两个独立的单元简称为 SDADC1 和 SDADC2。每个单元前面有一个多路输入开关可以从一组外部输入中选一个通道做转换。外部输入既可以配成单端输入也可以配成差分输入对差分输入在低噪声场景下几乎是必选因为共模噪声会被调制器直接抑制掉。每个 SDADC 单元内部还带两个特殊通道一个接内部温度传感器一个接内部参考电压。这意味着你不需要额外占用外部引脚就能做粗略的温度监测和参考电压自检。两路 SDADC 在寄存器层面完全独立可以分别设置不同的增益、不同的滤波阶数这就给了你「一路跑快、一路跑准」的搭配空间。需要特别提醒的是SDADC 的输入通道编号并不一定等于引脚编号。外部通道对应的具体引脚取决于封装和该器件的复用功能映射表。同一个通道号在不同封装上可能落在不同引脚上所以拿到一块新板子第一件事是按型号和封装查数据手册里的 AF 映射而不是凭经验接。2.2 参考电压、增益与时钟决定分辨率的三组关键变量SDADC 的输出满量程由参考电压 VREF 和输入增益共同决定可选的参考源有三类各有适用场景。我在项目里最常见的做法是优先用内部参考原因后面避坑章会细说。参考源满量程范围适用情况VDDA±VDDA / 增益3.3V 电源较干净、对精度要求不极端时内部参考电压±内部VREF / 增益对 VDDA 噪声敏感追求稳定有效分辨率时推荐外部 VREF 引脚±外部基准 / 增益需要与外部基准源保持一致时增益档位常见有 0.5、1、2、4 四档。增益越高满量程越小对应同样一位 LSB 代表的电压越小。但增益高不意味着精度一定高因为输入信号的直流偏置和噪声同样会被放大。小信号传感器可以先选 4 倍增益而像电池电压这种本身就接近满量程的信号选 1 倍甚至 0.5 倍更合适。时钟这块是最容易被忽略的。SDADC 的调制器时钟 fMOD 不是随便取值的芯片要求 fMOD 落在 1.28MHz 到 6.4MHz 之间。以 72MHz 系统时钟为例APB2 也是 72MHz经 SDADC 时钟分频后只有÷16 得到 4.5MHz、÷32 得到 2.25MHz 两个选项是合法的。这个约束直接决定了你最终能拿到的输出数据率。数据率的上限就是 fMOD 除以滤波器的抽取倍数抽取得越多数据越干净但每秒能取到的样本数越少。所以「高精度」和「高速度」在 SDADC 上是同一个时钟资源上的取舍不是两个独立可调的东西。2.3 数字滤波器与输出码制FORD 参数到底是什么Σ-Δ 调制器输出的是一路高速 1bit 流必须经过数字抽取滤波器才能变成可读的二进制码。F373 内部集成的是一组 sinc 滤波器阶数由 FORD 字段控制可选 1 到 4 阶。FORD 选得越高阻带衰减越陡对带内噪声压制越狠但建立时间也更长。输出数据率的计算公式可以写成fDATA fMOD /128 × FORD以 fMOD 2.25MHz 为例FORD1 时约 17.6kSPSFORD2 时约 8.8kSPSFORD3 时约 5.9kSPSFORD4 时约 4.4kSPS。同一个公式翻过来用就是你评估系统吞吐量的依据。输出数据寄存器是 24 位差分输入下采用二进制补码。0 附近代表差分输入接近共模电平正满量程和负满量程分别对应正向和反向的最大差分电压。很多人第一次读回数据发现是「负数」其实不是采样错误而是补码本来就有符号。后面换算物理量时一定要把这个符号保留下来。3. 用HAL把STM32F373 SDADC跑起来初始化、通道配置与读取的最小代码这一章直接给可编译的 HAL 风格代码。F373 在 Cube 生态里对应的是 STM32CubeF3 固件包SDADC 外设驱动位于stm32f3xx_hal_sdadc.h/.c。不同小版本的 HAL 头文件字段名可能略有差异编译报错时以你本机的头文件为准。3.1 初始化前的四步准备时钟、GPIO复用、参考电压与使能顺序SDADC 是模拟外设初始化顺序比普通数字外设讲究。我习惯把它拆成四步。第一步打开 SDADC 外设的时钟。SDADC1 挂在 APB2 总线上使能后还建议做一次外设复位确保寄存器回到干净状态。第二步把实际用到的输入引脚配置为模拟输入模式。具体引脚号查数据手册的 AF 映射表不同封装不一样。第三步预留 2 到 5 毫秒的延时让模拟部分供电稳定。第四步再配置参考电压和时钟分频最后使能 SDADC。顺序上先复位再延时再配置能避开不少上电瞬间的偶发异常。有些人图省事把所有初始化写在一起结果就是同一份代码有的板子正常有的板子首次读数明显偏大重启又好了。这类「玄学」问题多半是模拟域还没稳定就开始转换。3.2 最小驱动代码启动单通道差分转换并读取24位原始码下面这段代码是我在一个模拟项目里用的最小模板初始化 SDADC1配置 0 号外部通道为差分模式读取一次原始码。它不包含 DMA适合先验证硬件通路。#include stm32f3xx_hal.h static SDADC_HandleTypeDef hsdadc1; /* 初始化SDADC1使用内部参考时钟分频32 */ static void SDADC_InitHardware(void) { __HAL_RCC_SDADC1_CLK_ENABLE(); /* 打开SDADC1的APB2时钟 */ hsdadc1.Instance SDADC1; hsdadc1.Init.ClockDiv SDADC_CLOCK_DIV32; /* 72MHz/32 2.25MHz */ hsdadc1.Init.VrefMux SDADC_VREF_INTERNAL; /* 使用内部参考电压 */ hsdadc1.Init.FastMode SDADC_FAST_MODE_DISABLE; hsdadc1.Init.LowPowerMode SDADC_LOW_POWER_DISABLE; /* 如果当前CubeF3版本的Init结构体里没有ScanMode字段 就把这一行删掉扫描模式在ConfigChannel阶段配置 */ hsdadc1.Init.ScanMode SDADC_SCAN_MODE_SINGLE; if (HAL_SDADC_Init(hsdadc1) ! HAL_OK) { while (1); /* 初始化失败查时钟和GPIO */ } } /* 读取指定SDADC通道一次返回24位原始码 */ static int32_t SDADC_ReadOnce(uint32_t channel) { SDADC_ConfTypeDef conf {0}; conf.InputMode SDADC_INPUT_MODE_DIFF; /* 差分输入 */ conf.Gain SDADC_GAIN_1; /* 1倍增益满量程±内部参考 */ conf.Offset 0; /* 校准后回填 */ conf.CommonMode SDADC_COMMON_MODE_NONE; /* 差分模式无需共模设置 */ if (HAL_SDADC_ConfigChannel(hsdadc1, conf, channel) ! HAL_OK) { return -1; /* 通道参数非法 */ } if (HAL_SDADC_Start(hsdadc1, channel) ! HAL_OK) { return -1; } if (HAL_SDADC_PollForConversion(hsdadc1, 100) ! HAL_OK) { HAL_SDADC_Stop(hsdadc1); return -1; /* 超时未完成 */ } int32_t raw HAL_SDADC_GetValue(hsdadc1); HAL_SDADC_Stop(hsdadc1); return raw; }代码的逻辑并不复杂但有几个参数要特别说明。ClockDiv选择 ÷32fMOD 为 2.25MHz这是前面算过的最稳妥档位。VrefMux选内部参考目的是绕开 VDDA 上的数字噪声。Gain选 1 倍适合先确认链路是否通之后再根据信号幅度调整。PollForConversion的超时参数我习惯给 100 毫秒。这个值远大于单次转换时间超时基本只会在硬件没接好或者时钟配置错误时出现不会误触发。GetValue返回的是 24 位有符号数直接赋值给 int32_t 即可。3.3 偏移校准与电压换算从原始码到能用的物理量SDADC 最容易被忽略的是偏移误差。哪怕输入差分对短接在一起读出来的数也不是 0可能偏几十甚至几百个 LSB。这个误差来自调制器的直流偏置、输入开关的导通压差和 PCB 布线不对称。F373 内部提供了硬件校准触发位可以按参考手册流程启动。但我实际项目里更常用的是软件标定法把正负输入端在外部短接读若干次数取平均把这个平均值回填到通道配置的Offset字段。这样做的好处是它连 PCB 上的失配一起补偿了。/* 外部短接差分输入端后采集16次取均值作为偏移补偿 */ static int32_t SDADC_CalibrateOffset(uint32_t channel) { SDADC_ConfTypeDef conf {0}; int64_t sum 0; int32_t sample; conf.InputMode SDADC_INPUT_MODE_DIFF; conf.Gain SDADC_GAIN_1; conf.CommonMode SDADC_COMMON_MODE_NONE; conf.Offset 0; HAL_SDADC_ConfigChannel(hsdadc1, conf, channel); for (int i 0; i 16; i) { sample SDADC_ReadOnce(channel); if (sample 0) sample 0; /* 防止异常值混入 */ sum sample; } conf.Offset (int16_t)(sum / 16); HAL_SDADC_ConfigChannel(hsdadc1, conf, channel); return conf.Offset; }校准完成后电压换算要用带符号的原始码。差分输入下满量程对应正负参考电压除以增益。最可靠的做法不是背公式而是用两点标定短接输入测偏移再接一个已知精密电压测斜率。斜率单位是伏每 LSB之后任意读数乘斜率加偏移就是实际电压。提示如果Offset字段在你当前的 HAL 头文件里是 uint16_t注意强制转换时保留补码符号位不要把负数变成很大的正数。4. SDADC 可调参数怎么选采样率、滤波阶数与扫描模式初始化代码能跑通之后下一步就是针对实际需求调参数。这里最容易犯的错是把 SDADC 当成普通 ADC什么参数都往最大精度调最后发现采样率不够用或者数据周期性地带上一个毛刺。4.1 采样率与有效分辨率的权衡FORD 应该怎么选以 fMOD 2.25MHz 为例FORD 与输出数据率的关系如下表。看这张表的时候要把「数据率」理解为最终每秒能拿到多少个转换结果而不是调制器速度。FORD 阶数输出数据率 fMOD2.25MHz典型场景1 阶约 17.6 kSPS动态信号监测、快速巡检2 阶约 8.8 kSPS通用传感器采样3 阶约 5.9 kSPS称重、压力、温度采集4 阶约 4.4 kSPS需要最干净输出的精密测量工程上的实际选择逻辑是这样的如果信号带宽只有几赫兹比如称重传感器的输出经过了一级低通滤波那直接选 4 阶把噪声压到最低。如果还要监测 100Hz 级别的振动信号选 4 阶就明显不够1 阶或 2 阶更实际。这里有个常见的误解FORD 选 4 阶等效分辨率一定比 1 阶高吗不完全是。4 阶 sinc 滤波器对带外噪声抑制更好但建立时间更长。如果系统在频繁切换通道或者信号本身带有快速变化的阶跃高阶滤波器反而会把阶跃响应拉长造成动态误差。所以选阶数要看整个链路的信号特征不能只看静态噪声指标。4.2 通道切换与扫描模式时序比参数更值得花时间当系统需要采集多个通道时SDADC 支持扫描模式也就是按通道序号依次转换。扫描模式本身不复杂复杂的是切换通道后的数据时序。sinc 滤波器有建立时间切换到一个新通道后滤波器的内部状态还残留着上一通道的信息前几个转换结果是不准的。这个现象在 FORD 越高时越明显。我常用的做法是每次切换通道后丢弃前 2 到 4 个样本再从后续数据里取值。如果用的是中断方式就在断点里数样本个数前几个只读不用。扫描模式的周期要这样估算总时间等于通道数乘以单通道转换时间再乘以每个通道需要丢弃的样本数。比如 4 个通道、FORD3、每个通道丢弃 2 个样本实际有效数据率会明显低于单通道的理论值。设计系统吞吐量时一定要把这部分冗余算进去否则留给上层算法的数据量会不够。4.3 DMA搬运与缓冲设计连续采集不丢数据的关键多通道连续采集时CPU 逐个读取会占用大量时间而且容易漏掉转换完成事件。DMA 是标准解但缓冲区和传输长度的设计有讲究。/* 连续扫描4个通道每个通道采集8次DMA搬运 */ #define SDADC_CH_COUNT 4 #define SDADC_SAMPLE_COUNT 8 static uint32_t scan_buf[SDADC_CH_COUNT * SDADC_SAMPLE_COUNT]; /* 配置SDADC进入连续扫描模式后启动DMA */ HAL_SDADC_Start_DMA(hsdadc1, scan_buf, SDADC_CH_COUNT * SDADC_SAMPLE_COUNT);DMA 的传输单位必须是 32 位因为 SDADC 的数据寄存器是 24 位右对齐按 16 位搬运会把数据截断。缓冲区长度是通道数乘以每通道样本数这一点看起来简单但实际项目里经常出现只按通道数配了缓冲区导致 DMA 写越界的情况。启动 DMA 后我一般会在 DMA 传输完成中断里取数据然后立即启动下一轮。如果系统里中断负载高改用 DMA 半传输中断也可以但要注意半传输中断的边界正好落在哪个通道上否则数据处理逻辑会把两组不完整的数据拼在一起。注意连续扫描模式下每一轮扫描结束后立即开始下一轮中间没有任何空闲间隔。如果你的应用需要精确等间隔采样这种模式反而合适如果需要每轮之间留出处理时间就要用单次扫描模式手动触发。5. SDADC 避坑指南我从 F373 上踩过的五个实测问题以下问题都来自实际调试过程每条按「现象、原因、解决」三要素写。如果你在 F373 上读出异常数据对照这一章排查通常能省下大半天时间。5.1 数据噪声大、有效分辨率上不去查参考电压和引脚状态现象同一通道重复采集结果跳动几百个 LSB有效分辨率连 14 位都不到。把增益调低、滤波阶数调高改善也有限。原因有两类。第一类是参考电压选了 VDDA而 VDDA 上有数字电路耦合过来的噪声等于把电源噪声直接当作基准引入测量链路。第二类是外部引脚没有接任何信号源处于悬空状态差分输入本身没有确定电平读出来的完全是噪声。解决参考电压优先切换为内部参考。引脚方面未使用的 SDADC 输入引脚不要悬空要么接地要么在代码里不使能对应通道。测量时先用一个干净信号源接入差分输入确认噪声水平正常后再接真实传感器。5.2 首次上电读数明显偏移偏移校准没做或做晚了现象同一套硬件、同一份代码第一次上电读出来的数值比正常值偏大复位之后又恢复正常。或者冷启动和热启动的读数对不上。原因SDADC 的模拟偏置在上电后需要一段时间才能稳定而且内部偏移误差是逐芯片不同的没做校准就直接使用等于把这个误差原样带进了测量结果。解决每次上电初始化完成、模拟域稳定后先执行一次偏移校准再开始正式采集。外部短接条件允许的话就用上一章的软件标定法不允许的话也要触发一次内部校准流程。校准值最好能存到非易失区避免每次上电都重复同样的校准操作省时间。5.3 通道切换后第一笔数据周期性异常滤波器还没建立完成现象扫描模式下每个通道的第一个样本总是明显偏离真实值后面的样本正常。如果用 DMA 连续搬运看起来就像数据里混进了周期性的毛刺。原因sinc 滤波器在切换通道后需要几个采样周期才能冲刷掉上一通道的状态。FORD 越高需要的周期越多。DMA 拿到的是滤波器当前输出不会替你做这个清洗动作。解决在软件里丢弃每个通道的前几个样本再取有效值。样本数按 FORD 调整FORD 越高多丢几个。这个坑最容易在「扫描模式 DMA 只看最终结果」的组合里被忽略因为在单通道模式下不容易复现。5.4 读回数据恒为 0 或满码查差分接法和增益配置现象某个通道读回来不是 0 就是接近满量程的固定值调整输入电压没有响应。原因差分输入接反了或者输入共模电压超出允许范围。接反时正负信号颠倒输出变成负满量程看起来像满码。另一个可能原因是增益档位和参考电压搭配后满量程远小于实际输入信号输入早就饱和了。解决先用示波器确认两个差分引脚的电压关系区分是接反还是共模超限。然后把增益调到最低档重新测试排除饱和问题。代码里把所有通道配置的增益统一打印出来检查有没有哪个通道用了之前残留的配置。5.5 扫描 DMA 丢数据缓冲区长度和触发时机没对齐现象DMA 传输完成中断触发后解析缓冲区的数据发现通道顺序错乱或者某一个通道的数据偶尔缺失。加大超时时间没有用。原因扫描模式的通道数量和 DMA 传输长度不一致。DMA 是按固定长度搬运的如果长度不是通道数的整数倍一轮扫描的数据就会截断在中间下一轮从错误的位置开始表现为通道错位。解决缓冲区长度严格等于通道数乘以每通道采集次数并且这个乘积保持不变。每次修改扫描通道数同步修改 DMA 长度。调试时在缓冲区头部放一个起始标志用第一通道的已知特征值校验帧对齐能快速确认是不是这个问题。6. 把 SDADC 用得更稳两点标定与比率测量的实战技巧6.1 两点标定消除增益误差让读数在高温下也准确SDADC 的增益误差和内部参考电压的温度系数单靠校准偏移补偿不掉。我习惯在硬件调试阶段做一次两点标定差分输入端短接记下偏移再输入一个已知精密电压记下读数。两点就能算出斜率和截距标定结果存到片上 Flash上电后读出来修正每个转换结果。计算公式就是最简单的线性修正实际值 (原始码 - 偏移) × 斜率。斜率单位是伏每 LSB这一步比查手册理论公式可靠得多因为它把 PCB 走线、接插件接触电阻的误差一起吸收掉了。做完标定整个链路就是一个可以量化的测量系统后面换传感器或者改增益只需要重做一次标定。6.2 比率测量消除参考漂移把 VREF 的波动从结果里抹掉如果系统里参考电压本身会随温度或负载漂移最有效的办法是比率测量用同一个 SDADC 轮流采参考电压通道和信号通道计算比值而不是绝对值。这样参考漂移在分子分母上同时出现被约掉。F373 的 SDADC 自带内部参考通道正好可以拿来当这个比值参考。采样时序上我会先采参考通道再采信号通道两者间隔控制在几十毫秒内保证漂移量小到可以忽略。这个方法实现成本极低但对长期稳定性的提升非常明显。它也是我每次做精密采集项目时的默认手段比单纯堆滤波阶数更管用。一个曾经的教训我在某个模拟项目里因为没在通道切换后丢弃前两个样本产线测试数据每隔固定周期就出现一次偏大排查了整整两天才定位到是 sinc 滤波器建立时间的问题。从那以后凡是 SDADC 做多通道扫描我都会先把丢弃逻辑写好再去调其他参数。希望这篇 STM32F373 SDADC 的落地笔记能帮你绕开这些我已经踩过的坑把时间花在真正影响结果的地方。本文还有配套的精品资源点击获取