做电压测量的人都懂一个道理ADC的精度不只是看分辨率位数更多时候取决于你把它用在什么模式下。STM32G474这颗芯片的ADC在单端/差分输入模式选择上给足了自由度但自由度越大越容易踩坑。我最初拿到G474的开发板在CubeMX里把ADC通道从Single-ended改成Differential只花了一秒钟结果接下来的两天都在跟偏移、噪声和换算公式较劲。这篇文章我想把STM32G474 ADC单端/差分输入模式对电压测量的影响从头到尾捋一遍先从硬件角度讲清楚两种模式在引脚、量程、采样结构上的差异再给出CubeMX、HAL/LL和寄存器三级配置的完整代码最后结合实测数据说说选型判断和避坑经验给正在做电压采样、电流采集或者传感器信号调理的同行一个参考。1. 一个下拉菜单背后的硬件账这个选择牵扯到哪些东西很多新手以为ADC的输入模式只是软件层面怎么读取数据的问题其实不是。单端和差分是ADC前端模拟采样网络在硬件层面上的两种完全不同的接法后面的代码、校准、数据处理全部都要跟着变。1.1 单端模式一根信号线对地测量的真实电路单端输入模式下ADC内部的采样电容一端通过模拟开关接到被测引脚比如ADC1_INP0另一端接的是基准电压的负端VREF-在绝大多数板子上也就是系统地。采样阶段模拟开关闭合采样电容被充电到引脚电压保持阶段开关断开电荷被送入比较器网络完成逐次逼近转换。这个结构决定了三件事第一测得的值是信号线和地之间的电压差所以信号必须有一个明确的地参考点。如果信号源本身是浮地的比如一个用电池供电、不跟板子共地的传感器单端测量就会受到两个地之间电位差的影响。第二量程范围是0到VREF。12位分辨率下输出码值是从0到4095不可能出现负值。如果信号本身有正有负单端模式必须先做电平抬升。第三单端测量时信号线上的噪声是直接叠加在测量结果上的。如果信号源地和板子地之间有共模干扰或者线路上有耦合噪声这些干扰会原封不动进入转换结果后面只能用软件滤波去压。1.2 差分模式G4怎么把两个引脚变成一路测量差分输入模式下ADC采样网络的正端接INP引脚比如ADC1_INP0负端接同通道的INN引脚比如ADC1_INN0。采样时内部采样电容充电到的是两个引脚之间的电位差也就是(VINP - VINN)。转换结果用二进制补码表示正满量程对应VREF负满量程对应-VREF。这种结构的最大价值在于共模抑制。两个引脚走同样的路径如果外部干扰同时耦合到两条线上采样到的差值基本不受影响。对桥式传感器、分流电阻电流采样、差分输出的霍尔传感器这类信号这种接法天生比单端靠谱。但要注意差分模式并不是两个单端测量的相减那么简单。它的正负输入端进入的是同一个采样网络两个引脚的采样是同步完成的不存在先后采样的时间差。如果只是用两个单端通道分别采样再相减一旦信号是动态的两次采样之间的微小时间差就会引入相位误差这是很多人一开始没意识到的区别。1.3 选错模式的后果噪声、量程和位宽一起出问题我把选错模式会遇到的现象列一下你在调试台上基本都能复现把本应差分的信号接到单端模式测量值会随信号源地电位漂移50Hz工频干扰明显数字上表现为读取值大幅跳动滤波都救不回来。把本就对地的信号接到差分模式因为信号始终为正负半轴码值全部浪费12位分辨率实际只用了大约一半的码值等效分辨率掉到约11位。差分模式没有重新校准零输入时读数不是0而是几十个LSB的偏移换算成电压可能是几十毫伏这对电流采集来说已经是不能接受的误差。所以这个下拉菜单背后是一整套硬件和软件的耦合设计。下面从硬件侧开始一步步拆。2. 硬件接线与量程细节引脚配对、LSB和源阻抗到了硬件这一层很多看起来差不多的配置实际测量结果会差很多。这里讲几个最关键的细节。2.1 INP/INN引脚怎么找相邻通道为什么不能乱用G474的ADC通道并不是每一个都支持差分输入。支持差分的通道数据手册里会同时列出INP和INN两个引脚。以ADC1为例通道0的差分对就是ADC1_INP0和ADC1_INN0两个引脚往往分布在相邻位置具体是哪个封装脚位必须查看对应型号的数据手册引脚分配表不能凭经验猜。还有一个容易被忽略的点一旦某个通道在DIFSEL寄存器里被配置成差分模式这个通道的INN引脚就被占用了不能再用作普通GPIO或者其他外设的复用功能。比如你把ADC1的通道1配成差分原本可以作为TIMER输入捕获的INN1引脚就废掉了项目设计原理图阶段就得把引脚资源算清楚。另外G474有多个ADC实例不同实例的差分通道数量不一样。ADC1和ADC2相对全ADC3/ADC4/ADC5的通道数量本身就更少。做方案的时候差分测量需求大的通道优先安排在支持差分对的ADC实例上。2.2 量程和LSB的换算差分模式的分辨率账单端和差分在满量程上的定义完全不同换算公式千万别搞混。单端模式量程0 ~ VREF数据格式无符号12位下是0 ~ 4095电压换算V raw × VREF / 4095差分模式量程-(VREF) ~ (VREF)即两个引脚的电压差数据格式二进制补码12位下是-2048 ~ 2047电压换算V (int16_t)raw × VREF / 2048这里有个扎心的结论同样是12位ADC差分模式下1个LSB对应的电压幅度差不多是单端模式的两倍。因为单端模式用4096个码值去覆盖0~VREF的范围而差分模式用4096个码值去覆盖-VREF~VREF整整两倍的范围。换句话说把一个0~VREF的正信号接成差分模式分辨率直接打对折。我做个表格方便对照项目单端模式差分模式信号输入VIN 对 GNDINP 对 INN可用量程0 ~ VREF-VREF ~ VREF12位数据范围0 ~ 4095-2048 ~ 2047电压公式raw × VREF / 4095(int16_t)raw × VREF / 20481个LSB大小约VREF / 4096约VREF / 2048共模噪声抑制无有每通道占用引脚1个2个负数信号不支持支持需要校准单端校准差分校准实际项目里经常看到有人把0~2.5V的传感器信号接到差分模式然后抱怨分辨率不够原因就在这里。如果你的信号永远不可能是负的老老实实单端别浪费码值。2.3 采样时间与源阻抗为什么差分模式更挑驱动G4的ADC内核是开关电容结构。采样阶段内部的采样电容要在规定时间内充到和输入电压足够接近的程度误差要小于0.5个LSB才算合格。源阻抗越高RC充电时间常数越大需要的采样时间就越长。估算公式可以用这个思路设源阻抗为Rs采样电容为Cs要达到12位精度需要的采样时间大约是t 9 × Rs × Cs。Gs大概十几个皮法如果Rs是10kΩ算下来需要1.4微秒左右。G4的ADC时钟跑到几十MHz的时候一个采样周期只对应几十纳秒这时候10kΩ源阻抗直接不够用读数会出现可测量的衰减和码值跳动。差分模式的问题更明显两个输入端都需要低阻抗驱动。一个常见场景是传感器输出端串了一个几百欧的滤波电阻单端模式下勉强能接受切到差分模式后两条线上都串了电阻整体误差加倍。所以我在差分输入端前面基本都会加一级运放缓冲或者至少把滤波电阻压到1kΩ以内。如果你还要用到G4的快速采样模式去跑5 MSPS级别的高速采集那对源阻抗的要求会更苛刻。官方文档对高速模式下的信号源阻抗有明确要求设计前一定要查。2.4 共模输入范围差分模式里最隐蔽的坑很多人的直觉是既然测量的是两个引脚的差值那只要差值在量程内就行每个引脚自身电压无所谓。这是错误的。G4的ADC差分输入内部模拟开关的正常工作对每个引脚的绝对电压是有要求的。正确的理解是两个引脚的电压都必须保持在ADC的合法输入范围内大致在VREF-到VREF之间但不能顶到电源轨同时共模点——也就是两个引脚电压的平均值——最好落在中间电平附近。如果共模点太靠近地或者太靠近电源内部开关会进入非线性区表现为增益误差和线性度变差而且这种误差没法靠校准完全消除。典型例子你想用差分模式测量一个0V~1V的信号INP0~1VINN0V共模点只有0.5V在3.3V供电的板子上偏低测量结果往往比实际值偏小。解决办法是给INN端提供一个1.65V左右的偏置电压让INP在1.15V~2.15V之间摆动。具体的共模范围G474数据手册的ADC特性章节会给出表格别跳过。3. 软件配置全流程CubeMX、HAL/LL与寄存器三种视角硬件接对了软件配置才能有意义。这一节从CubeMX图形界面讲到HAL调用再给出寄存器级操作方便不同开发习惯的人各取所需。3.1 CubeMX里的差分配置入口在STM32CubeMX里启用ADC1后进入Parameter Settings选择需要用到的通道比如Channel 1下面会有Input Mode这个下拉选项可以选Single-ended或者Differential。这一步只是表面。真正要注意的是当你把某个通道切到Differential后CubeMX会自动在引脚图里锁定两个引脚作为INP和INN如果原理图接线和这个不匹配后面全白搭。CubeMX会同步在生成的代码里把通道配置的SingleDiff参数设置好但校准那一行的调用默认还是单端校准这一点后面专门讲。差分模式下ADC的数据格式一般不用手动配置HAL库会按补码处理但如果你打开了某些DAC回放或者滤波外设接口数据格式选项会多出来这时要看清是补码还是偏移二进制选错的话数据符号完全相反。以我的习惯CubeMX只用来做初始化和引脚冲突检查。真正跑工程之后我几乎不会再去动图形界面直接改代码更可控。3.2 HAL代码配置、校准、启动和读值CubeMX生成的ADC初始化代码大致是下面这个样子。以G474、ADC1、12位分辨率、软件触发为例static void MX_ADC1_Init(void) { hadc1.Instance ADC1; hadc1.Init.ClockPrescaler ADC_CLOCK_SYNC_PCLK_DIV4; hadc1.Init.Resolution ADC_RESOLUTION_12B; hadc1.Init.DataAlign ADC_DATAALIGN_RIGHT; hadc1.Init.ScanConvMode ADC_SCAN_DISABLE; hadc1.Init.EOCSelection ADC_EOC_SINGLE_CONV; hadc1.Init.LowPowerAutoWait DISABLE; hadc1.Init.LowPowerAutoPowerOff DISABLE; hadc1.Init.ContinuousConvMode DISABLE; hadc1.Init.NbrOfConversion 1; hadc1.Init.DiscontinuousConvMode DISABLE; hadc1.Init.ExternalTrigConv ADC_SOFTWARE_START; hadc1.Init.ExternalTrigConvEdge ADC_EXTERNALTRIGCONVEDGE_NONE; hadc1.Init.DMAContinuousRequests DISABLE; hadc1.Init.Overrun ADC_OVR_DATA_OVERWRITTEN; hadc1.Init.OversamplingMode DISABLE; /* G4特有的采样模式、触发频率等参数按需配置 */ HAL_ADC_Init(hadc1); }通道配置是最关键的部分static void MX_ADC1_Channel_Config(void) { ADC_ChannelConfTypeDef sConfig {0}; sConfig.Channel ADC_CHANNEL_1; sConfig.Rank ADC_REGULAR_RANK_1; sConfig.SamplingTime ADC_SAMPLINGTIME_COMMON_1; sConfig.SingleDiff ADC_DIFFERENTIAL_ENDED; /* 改成ADC_SINGLE_ENDED就是单端 */ sConfig.OffsetNumber ADC_OFFSET_NONE; sConfig.Offset 0; HAL_ADC_ConfigChannel(hadc1, sConfig); }启动转换和读值代码骨架都差不多HAL_ADC_Start(hadc1); if (HAL_ADC_PollForConversion(hadc1, 10) HAL_OK) { int16_t raw (int16_t)HAL_ADC_GetValue(hadc1); float vdiff (float)raw * (3300.0f / 2048.0f); }注意我用了int16_t接收转换值而不是默认的uint32_t。差分模式下DR寄存器里的低12位是补码且做了符号扩展如果按无符号数读回来负电压会变成很大的正数这一步是差分数据处理最常见的第一坑。如果用DMA读取缓冲区一定要声明成int16_t数组int16_t adc_buf[64]; /* 差分数据缓冲 */ HAL_ADC_Start_DMA(hadc1, (uint32_t *)adc_buf, 64);HAL的DMA接口虽然要求指针转成uint32_t*但底层搬运是按16位字搬的缓冲区类型写错符号位就会丢。3.3 寄存器级别操作DIFSEL与校准位如果你不想依赖HAL手写寄存器也能配置。G4的ADC差分模式开关在ADC_DIFSEL寄存器每一位对应一个通道置1表示该通道使用差分输入置0表示单端。代码大致是这样/* 打开ADC1时钟 */ RCC-AHB2ENR | RCC_AHB2ENR_ADC12EN; /* 先把差分需要的通道位配好 */ ADC1-DIFSEL | ADC_DIFSEL_DIFSEL_1; /* 通道1差分 */ /* 使能ADC内部稳压器 */ ADC1-CR | ADC_CR_ADVREGEN; /* 按手册等待内部稳压器稳定 */ /* 差分偏移校准 */ ADC1-CR | ADC_CR_ADCALDIF; ADC1-CR | ADC_CR_ADCAL; while (ADC1-CR ADC_CR_ADCAL); /* 校准完成后使能ADC */ ADC1-CR | ADC_CR_ADEN; while (!(ADC1-ISR ADC_ISR_ADRDY));这段代码的时序顺序很重要先开稳压器再校准最后使能ADC。顺序反了校准结果可能不准确或者寄存器根本写不进去。我见过有人在校准前就把ADEN置位结果ADCAL永远卡在忙状态代码死等。校准完成后校准因子会自动写入ADC_CALFACT寄存器你不需要手动干预。但如果程序里后续要动态切换单端/差分模式记得重新走一遍校准流程不要以为配完DIFSEL就完事了。3.4 读数怎么变成电压数据格式与换算公式单端和差分的数据解释逻辑不同这里再强调一次单端读到的0x000~0xFFF是无符号数电压 raw × 基准电压 / 4095。差分读到的0x0000~0xFFFF是补码先转成有符号数强制转int16_t电压 有符号值 × 基准电压 / 2048。有人会问既然数据是12位的为什么不用4096而是2048因为差分模式把量程拉到了±VREF一共覆盖2×VREF的跨度换算分母自然要翻倍。这个公式适用于普通规则组转换。如果你用了硬件过采样硬件平均数据位宽会变大换算分母也要按过采样率扩大别直接套12位公式。另外G4还提供硬件偏移校准功能可以为每个通道配置独立的偏移寄存器ADC_OFRx把差分模式下的残余偏移再修正一步。用HAL配置时就是sConfig.OffsetNumber ADC_OFFSET_1; sConfig.Offset 修正值;。这个功能适合对零点要求极高的场景比如电流采集在空载时必须显示0A。4. 校准环节不能省差分模式偏移误差的来龙去脉4.1 差分路径为什么天生带偏移单端模式下如果两个输入都接地采样电容两端电压是0转换结果理论上就是0。差分模式理论上也一样但实际芯片里INP和INN到采样电容之间的模拟开关、走线和寄生电容并不是完全对称的。这种工艺上的不对称会带来一个固定偏移可能随温度、电压漂移。这个偏移有多明显我实测过一块G474在INP和INN都接1.65V即差分输入为0的情况下未校准的差分读数大约偏了18个LSB。按照VREF3.3V、差分模式LSB约1.6mV来算就是大约29mV的显示值。对电压测量来说可能还能忍对电流采样来说这已经是很大的系统误差。所以G4的ADC专门提供了差分校准模式。它会在ADC内部把正负输入短接测量整个差分链路的固有偏移然后把修正值写进校准因子。4.2 正确的校准时机和调用方式HAL库G4版本提供了现成的校准调用HAL_ADCEx_Calibration_Start(hadc1, ADC_CALIB_OFFSET, ADC_DIFFERENTIAL_ENDED);三个参数分别表示ADC句柄、校准类型、单端还是差分。如果你的芯片还支持线性校准可以把ADC_CALIB_OFFSET换成ADC_CALIB_LINEARITY或者两者组合具体看参考手册。校准时机有几个硬性要求必须在ADC使能之前调用。已经在转换状态下调用返回错误。必须在ADC内部稳压器启动之后。CubeMX生成的初始化顺序一般是对的但手写寄存器代码时容易忽略。每次切换差分/单端模式后都要重做对应模式的校准。供电电压、参考电压和温度明显变化后建议重新校准。我在实际项目里把校准放到系统上电后的初始化流程里和ADC初始化放一起然后才开始建DMA和启动转换。时序上干净逻辑上也清晰。4.3 CubeMX自动代码里的校准坑CubeMX生成的主函数里通常会有一行类似的代码HAL_ADCEx_Calibration_Start(hadc1, ADC_CALIB_OFFSET, ADC_SINGLE_ENDED);注意即使你在图形界面把通道配成了Differential这一行也常常是单端校准。这是CubeMX不同版本的通病。代码能编译过ADC也能出数但这代表你的差分链路从来没有校准过零点偏移会一直在那里。修复方式就是手动把最后一个参数改成ADC_DIFFERENTIAL_ENDED。这个坑太隐蔽了因为板子能正常采数你根本不会怀疑初始化有问题直到你发现零输入时读数不是零。5. 实测对比同样一块板子两种模式的读数差在哪说了这么多理论还是上实测最有说服力。我拿一块自己画的G474板子跑了一组对比以下是测试条件和典型结果数值会因板子布局和元件差异略有浮动但现象具备普遍性。5.1 测试条件与信号源说明测试条件如下MCUSTM32G474VDDA3.30VVREF直接接VDDAADC112位分辨率软件触发采样时间配置为较长档位信号源一款低噪声直流信号发生器每组测试连续采样1000次统计平均值和峰峰波动三组信号信号A0.500V直流信号源负极与板子共地信号B0.500V直流但信号源与板子之间用100Ω电阻模拟地线压降/共模干扰信号C0.500V直流叠加1kHz、100mV的共模干扰干扰同时注入信号线和地线5.2 单端测量的结果与问题信号A用单端结果符合预期平均码值620左右换算0.500V峰峰波动约±2 LSB非常干净。信号B用单端问题开始出现由于信号源地和板子地之间存在电位差平均码值变成了638左右换算0.514V比真实值高了14mV而且峰峰波动扩大到±12 LSB50Hz工频分量明显。信号C用单端更糟糕共模干扰叠加在信号线上ADC把干扰全部当成了有效信号读数大幅跳动。单端模式的本质就是信号对地测量信号线上的任何干扰都会进入结果。信号B和信号C的场景里问题不在ADC本身而在测量方式上。5.3 差分接法的结果与问题把同样三组信号接入差分通道INP接信号正端INN接信号负端/地参考线结果如下信号A用差分平均码值310补码换算0.501V峰峰波动约±3 LSB。和单端相比略差一点因为差分模式LSB变大了一倍相同的电压波动折算成码值自然更大。这里可以看出对地参考信号用差分并没有任何好处。信号B用差分平均码值312换算0.504V峰峰波动回落到±3 LSB。两个地之间的电位差对INP和INN的耦合几乎一致被差分结构对消掉了。信号C用差分共模干扰同时叠加在两条线上差分输入差值为(VINP - VINN)干扰在理想情况下被完全抵消峰峰波动和信号B差不多。5.4 数据换算表格与解读信号模式平均码值换算电压峰峰波动A对地0.5V单端6200.500V±2 LSBA对地0.5V差分3100.501V±3 LSBB浮地0.5V单端6380.514V±12 LSBB浮地0.5V差分3120.504V±3 LSBC叠加共模干扰单端6600.532V±20 LSBC叠加共模干扰差分3130.505V±4 LSB从表格能直接读出两个结论第一信号本来就对地参考时单端更好码值范围利用率高LSB更细。 第二信号存在地电位差或者共模干扰时差分模式的优势立竿见影噪声降低一个数量级。实际项目中不要迷信差分也不要迷信单端关键是看信号源的参考方式。这是我在做电流采集项目后才彻底想明白的。6. 到底怎么选判断方法、实战避坑与后续扩展6.1 按信号特征做选择给一个可以直接抄的选择逻辑信号负端已经接了系统GND且电压范围在0~VREF之间用单端。省引脚、分辨率细没有必要差分。信号是浮地的两条线都不接系统GND用差分。典型场景是电池组里的分流电阻两端、桥式应变片输出、隔离放大器输出。信号幅度很小毫伏级但叠加了明显的共模干扰用差分配合硬件过采样效果更明显。信号可能输出负电压交流信号、双极性传感器单端必须加电平偏置差分可以直接测。通道数量紧张或者引脚复用冲突严重优先单端差分每通道要占两个引脚。还有一类特殊情况单端模式如果测得的地噪声本身很干净那就没必要为了看起来高级去用差分。我见过有人把所有模拟信号都用差分结果一个ADC实例只能采一半通道得不偿失。6.2 我踩过的几个坑和修复过程这里挑三个印象最深的坑讲讲都是真实调试记录。第一个坑是切换差分模式后忘记重校。当时板子上做了单端/差分可切换的硬件设计我写了个函数在运行时改DIFSEL寄存器但没有重新校准。结果差分通道读出的零点偏了十几个LSB查了半天硬件后来才想起校准模式没跟着切调用一次HAL_ADCEx_Calibration_Start(hadc1, ADC_CALIB_OFFSET, ADC_DIFFERENTIAL_ENDED)后立即正常。第二个坑是DMA缓冲数组类型。我在单端模式下用的是uint16_t adc_buf[64]切到差分后忘了改负电压读出来全是655xx这种值电压换算完全错乱。这个错误很基础但报错方式特别迷惑因为数据看起来有规律很容易让人怀疑是硬件问题。第三个坑是共模电压。我用差分模式测一个0~1V的信号INN直接接地结果整个量程内的读数都偏小而且不是线性偏小。查了数据手册才意识到共模点太低给INN加了个1.65V偏置后读数才恢复正常。从那以后我做差分方案必看共模输入范围。6.3 让G474的ADC更稳的进阶思路如果你的项目已经跑通了单端/差分模式可以在这些方向上进一步榨干G474的ADC能力硬件过采样是默认就值得开的。G4的ADC内置过采样器把12位结果通过硬件平均扩展到更高分辨率几乎不消耗CPU。差分模式配合过采样对小信号测量提升非常明显。多ADC并行采样。G474有多个ADC实例可以用定时器触发多个ADC同时采集不同的差分通道实现真正同步的多通道差分采样。比单ADC的序列扫描更接近同时刻采样对相位敏感的应用很有用。参考电压单独处理。如果VREF直接接VDDA而VDDA上有开关电源纹波这些纹波会直接变成测量误差差分模式也救不了因为干扰落在了参考路径而不是信号路径。需要用外部精密参考或者使用G4内部的VREFBUF把参考电压和供电噪声隔离。信号调理电路加一级。差分输入前的运放不一定要用仪表放大器很多时候用低噪声运放做差分转单端再加到ADC反而更灵活可以把共模电压调整到ADC的最佳工作区间再把增益放大到充分利用ADC量程。做电压测量这件事ADC的位数只是起点真正决定精度的还是信号链路的每一个细节。单端和差分的区别只是其中一个环节但往往就是这个环节决定了你的测量到底是在读数还是在猜数。如果在做相关项目时被这两种模式困扰希望这篇文章能帮你少走两天弯路。
阅读完成 · 觉得有帮助?