ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

STM32G4 ADC 10位分辨率下偏移补偿与增益补偿顺序解析及LL库配置

STM32G4 ADC 10位分辨率下偏移补偿与增益补偿顺序解析及LL库配置 在EE论坛上刷到一个高频问题STM32G4的ADC在10位分辨率下偏移补偿和增益补偿到底谁先谁后这个问题看起来简单但很多人栽在这里因为答案往往跟直觉相反。我不是第一次被问到这个问题了干脆把自己的验证过程和结论完整写出来包括手动配置寄存器的方式以及LL库代码怎么落方便需要的朋友直接抄。先说结论在STM32G4的ADC数据通路上先执行偏移补偿Offset Compensation减去OFFSETx寄存器里的偏移值再执行增益补偿Gain Compensation即内部校准因子调整。最终结果对应公式ADC转换最终结果 增益因子 × (ADC原始转换数值 - 偏移值)这个顺序在所有分辨率下都成立10位分辨率也不例外。但10位模式下有一个隐藏的坑校准因子的表达方式、偏移寄存器的有效位宽、以及“先偏移后增益”在数值上带来的误差影响跟12位模式有很大区别。下面我把整个逻辑链路、寄存器行为、配置步骤和实际坑位一次说完。1. 问题背景与结论先行1.1 为什么“执行顺序”会被反复讨论STM32G4系列的ADC精度在主流MCU里算是很能打的内部集成了可编程增益补偿和偏移补偿但官方参考手册RM0440里对这两者的描述是分段出现的偏移补偿在ADC章节的某个小节增益补偿写入在另一个小节如果不把寄存器级的数据流串起来很容易理解成两个互不相关的独立功能。真正让“顺序”这个问题变得重要的场景是你同时使用偏移补偿和增益补偿希望得到一个线性校正结果。举个实际例子我在做电流采样时霍尔传感器在零电流时会有几十毫伏的失调电压这让ADC在0A时不是输出0而是输出一个固定码值同时采样电阻的精度、运放的增益误差又导致整体斜率和理想值不符。这时候就同时需要“清零”和“调斜率”两个动作。如果在固件里把两个补偿写反了结果会非常反直觉零点调准了满量程却歪了或者满量程调准了零点又跑了。从数学上看ADC实际输出有线性误差时可以写成D_raw k × V_in b其中k是实际增益b是偏移也就是零点偏置。要用软件校正到理想的D_corrected k_ideal × V_in如果先做增益补偿乘以G再做偏移补偿减去O那么结果是D_out G×D_raw - O G×k×V_in G×b - O。你会发现偏移b先被放大了G倍再减去O。如果G不是精确的1那么“减掉的O”和“实际需要减掉的偏移”永远对不齐调节G会影响零点调节O会影响斜率两个旋钮互相打架。反过来如果先用偏移补偿把b消掉再对纯比例部分做增益补偿那么两个旋钮就完全解耦了先调零再调斜率互不影响。STM32G4内部正是按这个顺序做的——先减偏移后调增益。这个设计不是拍脑袋而是线性校正的标准做法。1.2 10位分辨率下的结论适用性有朋友会问12位是这么做的那10位分辨率的模式下会不会因为内部数据通路做了截断顺序发生变化答案是不会。STM32G4的ADC核心是12位的SAR架构10位、8位、6位分辨率是对转换结果做右移截断得到的。数据通路上的补偿逻辑位于“原始结果”之后、寄存器输出之前的同一段链路所以偏移补偿永远先于增益补偿。但这里引出了10位模式最关键的区别补偿值本身会被“截断适配”。也就是说偏移寄存器的有效位宽从12位变成了10位校准因子也需要按分辨率做缩放。这就是很多人配置10位分辨率时结果不对、但又找不出原因的地方。后面第3章我会专门展开讲。2. 补偿链路中的执行顺序解析2.1 从寄存器看补偿数据流要真正理解执行顺序最直接的方式是看寄存器行为而不是只看应用层描述。STM32G4的每个ADC都有独立的偏移寄存器OFFSET1R到OFFSET4R以及校准因子相关的CALFACT寄存器。数据流大致是这样模拟输入电压经过内部采样电容进入SAR比较器得到12位原始二进制码。如果使能了某个OFFSETx寄存器且当前通道匹配该偏移配置硬件从原始码中减去OFFSETx[11:0]实际有效位取决于当前分辨率得到第一次校正后的码值。如果ADC已经加载了增益校准因子通过CALFACT GAINCALR机制硬件再将上一步的结果按比例缩放得到最终结果。最终结果写入ADCx_DR数据寄存器同时触发EOC事件和DMA请求。所以从寄存器读到的数据永远是“已经过两步补偿”的最终数据。如果你想验证顺序最粗暴的方法是故意设一个很大的偏移值和明显不等于1的增益因子观察DR寄存器里的输出。我在某次调试里把偏移设为51210位模式下按3.225mV/LSB计算相当于1.65V增益因子设为1.2然后给一个0.5V输入读到的值精确符合“先减512再乘1.2”的路径而不是反过来的结果。这个实验可以在没有示波器的情况下稳定复现推荐有疑问的朋友自己试一次。2.2 偏移补偿的前置条件与使能时机虽然顺序是硬件固定的但软件上有一个容易踩的坑OFFSETx寄存器在写入之后不会立刻生效必须设置对应的OFFSETx_EN位。更准确地说G4参考手册RM0440在偏移补偿章节明确标注必须在ADC转换开始前配置好OFFSET值并使能偏移功能否则结果不确定。而且每个OFFSETx寄存器还有独立的通道匹配机制。比如OFFSET1R既可以作用于规则通道也可以作用于注入通道由OFFSETx_CH[4:0]字段指定目标通道。这里有个细节在10位分辨率下偏移值最大只能写到1023如果写超过当前分辨率的范围硬件会按截断处理而不是报错。很多人在10位模式下直接复制12位模式的偏移配置偏移量超过1023结果低端数据全部被“垫”到0附近还以为是自己传感器坏了。实操上我的建议是偏移补偿基于当前分辨率的最小量化单位LSB来设置。如果使用一个固定的物理电压值比如要补偿100mV的失调10位模式下VDDA为3.3V时每LSB约等于3.225mV那么偏移值应取100 / 3.225 ≈ 31。这里的运算注意四舍五入而不是直接取整截断可以有效减少零点残余误差。2.3 增益补偿的真实身份校准因子的加载很多网友对STM32G4的“增益补偿”有误解以为像某些MCU一样有一个独立的GCOMP开关位。实际上G4的增益补偿是靠着校准因子CALFACT实现的它没有独立的使能开关只要CALFACT里加载了有效的非零值增益补偿就一直在起作用。G4的校准机制和F0/F3系列有较大区别。G4的ADC校准分三步内部模拟校准、数字校准、校准结束。校准完成后代表增益误差补偿的9位校准因子会自动写入CALFACT寄存器。正常工作时这个值不需要修改。但如果你在低功耗模式后重新初始化ADC或者想把产线标定得到的校准因子重新加载就需要手动写CALFACT并设置CFGR2寄存器中的GAINCALR位触发硬件重新加载。触发加载的流程必须这么来先向CALFACT寄存器写入目标校准因子。将CFGR2寄存器中的GAINCALR位置1。硬件自动完成加载加载完成后GAINCALR位硬件清0。在GAINCALR为1期间不能修改CALFACT否则加载结果不确定。我见过不少人顺序写反先置GAINCALR再写CALFACT结果校准因子压根没更新增益补偿还停留在上一个状态而他们还在抱怨“我明明改了校准值怎么没反应”。3. 10位分辨率下校准因子的特殊规则3.1 为什么10位模式校准因子要右移STM32G4的ADC核心是12位的但支持12位、10位、8位、6位四种分辨率。分辨率切换通过ADC_CFGR1寄存器中的RES[1:0]字段实现。问题就出在“校准因子”的标定基准上。ADC在校准过程中是参考12位满量程来测量和存储校准因子的因此保存下来的9位校准因子对应的是12位分辨率下的增益误差修正量。当你把分辨率切到10位一个12位LSB和一个10位LSB的大小关系是10位LSB 4 × 12位LSB因为分辨率降低2位量化步长扩大了4倍。校准因子要想继续表达同等的修正能力就必须相应缩小。硬件处理时不会去猜你到底怎么想的它只会把当前分辨率记为“目标分辨率”自动将校准因子右移12 - 当前分辨率位数位并截断。以10位为例校准因子右移2位。也就是说原本9位精度的校准因子到了10位分辨率下只剩7位有效精度了。这就是为什么很多人在10位模式下觉得ADC增益补偿“变粗了”的原因它不是错觉是设计如此。3.2 实际校准中的右移操作细节如果在初始化流程里手动写校准因子请务必按这个规则处理12位分辨率直接写入校准得到的完整CALFACT值。10位分辨率建议将12位模式下读取到的CALFACT右移2位后写入。8位分辨率右移4位。6位分辨率右移6位。但要特别注意这里的右移必须是无符号右移因为校准因子的表达里包含了符号信息增益修正方向有正有负。我在代码里一般这么处理uint32_t calfact_12b LL_ADC_GetCalibrationFactor(ADC1); uint32_t calfact_10b calfact_12b 2; LL_ADC_SetCalibrationFactor(ADC1, calfact_10b);有朋友会问如果我在10位分辨率下直接做一次完整的ADC校准硬件会自动把校准因子调整成10位分辨率对应的值吗答案是会但校准过程本身仍基于12位核心校准得到的CALFACT寄存器值依然以12位域存储使用时的缩放依旧遵循前面的规则。也就是说校准发生在12位域使用时在目标分辨率域。这个“标定域”和“应用域”的差异是G4系列最容易出bug的地方。3.3 偏移寄存器在10位模式下的行为偏移寄存器OFFSETx的位宽官方写的是OFFSETx[11:0]共12位。但在10位分辨率下有效位只有低10位高两位的写入会被忽略。这一点和校准因子类似都是“按目标分辨率适配”。不过偏移寄存器的适配方式和校准因子有一个本质区别偏移寄存器不需要你手动右移你直接按10位LSB为单位写入目标电压对应的码值即可。举例10位模式下VDDA 3.3V要补偿一个200mV的失调直接写200 / (3.3 / 1024) ≈ 62就行。而校准因子则需要手动右移因为校准因子的参考基准是12位。我在之前的项目里犯过一个错误在12位模式下测量并保存了当前通道的偏移值切到10位模式后直接把这个值写入OFFSET寄存器。由于偏移值恰好大于1024低端输出被整体钳位排查了很久才发现是分辨率切换导致的。所以最好的习惯是切换分辨率后把偏移值和校准因子一起重算不要走捷径复用旧值。4. 完整配置实操LL库代码直接抄4.1 初始化流程的分步实现下面给一份可以直接跑的初始化流程基于STM32G4系列、LL库场景是ADC1单通道10位分辨率使能偏移补偿随后加载手动校准因子。先说明整体顺序这个顺序不是随便排的每一步都依赖上一步的硬件状态使能ADC内部稳压器等待启动完成。启动ADC校准等待校准结束。保存校准因子。关闭ADC校准完成后重新配置需要先禁用。配置分辨率、通道、触发方式、采样周期。配置OFFSET寄存器并启用偏移补偿。写校准因子并触发GAINCALR加载。使能ADC并启动转换。为什么要先校准再关ADC因为G4的校准流程要求ADC处于关闭状态校准完成后硬件自动退出校准态但如果你紧接着改分辨率必须先执行一次关闭动作否则配置寄存器被锁定。这段时序我在调试时踩过坑直接在官方推荐顺序上补了关闭步骤实测稳定。4.2 LL库代码示例/* 1. 使能ADC内部稳压器 */ LL_ADC_EnableInternalRegulator(ADC1); /* 等待稳压器就绪参考手册建议最大延时 */ for (volatile int i 0; i 1000; i); /* 2. 启动ADC校准轮询等待完成 */ LL_ADC_StartCalibration(ADC1); while (LL_ADC_IsCalibrationOnGoing(ADC1)); /* 3. 保存校准因子12位分辨率基准 */ uint32_t calfact_saved LL_ADC_GetCalibrationFactor(ADC1); /* 4. 关闭ADC准备重新配置 */ LL_ADC_Disable(ADC1); /* 5. 配置分辨率为10位 */ LL_ADC_SetResolution(ADC1, LL_ADC_RESOLUTION_10B); /* 配置通道这里以ADC_CHANNEL_5为例采样周期按应用需求调整 */ LL_ADC_SetSamplingTimeCommon(ADC1, LL_ADC_SAMPLINGTIME_12CYCLES_5); LL_ADC_REG_SetTriggerSource(ADC1, LL_ADC_REG_TRIG_SOFTWARE); LL_ADC_REG_SetSequencerDiscont(ADC1, LL_ADC_REG_SEQ_DISCONT_DISABLE); LL_ADC_REG_SetSequencerChannels(ADC1, LL_ADC_CHANNEL_5); LL_ADC_SetChannelSamplingTime(ADC1, LL_ADC_CHANNEL_5, LL_ADC_SAMPLINGTIME_12CYCLES_5); /* 6. 配置偏移补偿10位模式下偏移值按10位LSB写入 */ LL_ADC_SetOffset(ADC1, LL_ADC_OFFSET_1, 62); /* 对应约200mV 3.3V */ LL_ADC_SetOffsetChannel(ADC1, LL_ADC_OFFSET_1, LL_ADC_CHANNEL_5); LL_ADC_SetOffsetState(ADC1, LL_ADC_OFFSET_1, LL_ADC_OFFSET_ENABLE); /* 7. 写校准因子10位模式下将12位因子右移2位 */ uint32_t calfact_10b calfact_saved 2; LL_ADC_SetCalibrationFactor(ADC1, calfact_10b); /* 触发增益校准因子加载 */ ADC1-CFGR2 | ADC_CFGR2_GAINCALR; /* 等待GAINCALR位硬件清0代表加载完成 */ while (ADC1-CFGR2 ADC_CFGR2_GAINCALR); /* 8. 使能ADC并启动软触发转换 */ LL_ADC_Enable(ADC1); LL_ADC_REG_StartConversion(ADC1);代码里第7步是重点。很多用HAL库的朋友会在这一步卡住因为HAL库里没有专门暴露GAINCALR的接口需要直接操作寄存器。这是正常的G4的校准因子加载机制本来就有寄存器操作的味道。4.3 代码中容易忽略的时序细节上面代码的第6步和第7步顺序有讲究如果先加载校准因子再配置偏移理论上也有效但从硬件的状态机来看偏移配置不会干扰增益加载过程而增益加载过程会短暂占用校准因子相关逻辑。为了避免任何边界情况我习惯先把偏移配好再去动增益。实测下来两种方式结果一致但先偏移后增益更稳妥。还要注意GAINCALR的轮询等待必须在使能ADC之前完成。如果在ADEN置位之后再清GAINCALR可能会触发意外的转换启动行为个别G4型号上表现为第一个转换结果异常。我在G474上遇到过两次把加载动作挪到ADC使能之前就消失了。还有一点“LL_ADC_SetCalibrationFactor”这个函数实际上直接写CALFACT寄存器在G4上没有额外的“使能”开关。校准因子的加载完全依赖GAINCALR位所以这两个动作必须配对执行先写值再置位后等待。5. 常见问题与排查技巧实录5.1 症状到原因的速查表下面这张表是我把社区里高频问题以及自己做项目时的实测记录整理出来的按“症状 → 可能原因 → 排查/解决”的格式梳理方便直接对号入座。症状可能原因排查/解决10位模式下偏移补偿后低端输出被钳位偏移值超过1023或复用了12位模式的偏移值按10位LSB重算偏移值确认小于1024切换10位后整体转换结果系统性偏大/偏小校准因子未按分辨率右移将12位校准因子右移2位后重新加载写了CALFACT但增益补偿好像没生效GAINCALR触发时机或顺序错误先写CALFACT再置GAINCALR等待硬件清0第一个转换结果偶发异常GAINCALR加载未完成就使能ADC将GAINCALR轮询放到LL_ADC_Enable之前温度变化后零点漂移明显缺少对OFFTRIM的微调使用OFFTRIM0/OFFTRIM1做精细偏移调整同时用偏移和增益补偿时调参相互干扰固件里自己实现了二次校正与硬件补偿叠加关闭软件补偿只保留硬件一条链路5.2 校准因子恢复时的常见翻车现场低功耗场景下保存和恢复校准因子是最容易出问题的。典型做法是上电初始化时校准一次记录校准因子到备份寄存器后续低功耗唤醒后直接恢复。恢复时如果分辨率变了一定要重新缩放。我在一个传感器节点项目里是这样处理的/* 唤醒后从备份寄存器读取12位校准因子 */ uint32_t calfact_restore restore_from_backup(); /* 根据当前分辨率重新缩放 */ uint32_t calfact_target 0; switch (current_resolution) { case RES_12B: calfact_target calfact_restore; break; case RES_10B: calfact_target calfact_restore 2; break; case RES_8B: calfact_target calfact_restore 4; break; case RES_6B: calfact_target calfact_restore 6; break; default: break; } LL_ADC_SetCalibrationFactor(ADC1, calfact_target); ADC1-CFGR2 | ADC_CFGR2_GAINCALR; while (ADC1-CFGR2 ADC_CFGR2_GAINCALR);也有工程师图省事在低功耗唤醒后直接重新跑一遍校准流程。这当然最保险但代价是ADC在校准期间不能转换唤醒响应时间变长。如果系统对唤醒时间敏感恢复加缩放的方案更实用。5.3 现场调试时的偏移微调技巧G4的ADC除了OFFSETx寄存器还有OFFTRIM0和OFFTRIM1两个微调寄存器用于对内部偏移做精细调整。虽然手册上主要描述为“应用于注入通道的偏移调整”但实测对规则通道也有一定的零点修正效果。OFFTRIM的有效调整步长约0.7LSB左右具体见参考手册电气特性章节所以在10位分辨率下如果你发现零点差1到2个LSB用OFFTRIM微调比改OFFSET值更平滑。我一般这样用先完成标准校准。给一个已知的零输入如VIN接地。连续采样100次计算平均值。根据平均值与0的偏差递增或递减OFFTRIM寄存器直到零点落在±1 LSB以内。这个方法在量产标定阶段特别好用可以弥补PCB板级零点偏置比在应用层做软件减法更符合硬件校准的思路。5.4 多通道连续采样时修改补偿参数的注意点不少用户会做多通道扫描加DMA连续采样比如用ADC1的注入组扫描几个通道。这种场景下如果系统运行中要动态修改偏移补偿值或校准因子务必在转换停止的状态下操作。我的建议是停止ADC转换清ADSTART。修改OFFSET或CALFACT并重新触发GAINCALR加载。丢弃转换停止前DMA缓冲区中的最后一批样本。重新启动转换后丢弃前2到3个新样本原因是补偿链路切换后首个样本可能仍受旧状态影响。这个“丢弃前几个样本”的习惯非常管用尤其是用于FOC电机控制等实时性要求高的场景。FOC里如果ADC采样数据突然跳变电流环会剧烈波动而补偿参数切换正是瞬间跳变的一个隐蔽来源。把这些样本主动丢掉比在DMA中断里做复杂滤波省心得多。6. 写在结尾的几个实操心得最后分享几条基于实测的经验供参考。第一不要把“ADC校准”和“偏移/增益补偿”混为一谈。ADC校准是生产环节的粗调解决的是芯片自身的制造偏差偏移/增益补偿是应用环节的微调解决的是系统级误差。两者配合使用但不能互相替代。第二10位分辨率下不要迷信“分辨率降低所以精度变差”这句话。真正会让精度变差的往往是校准因子被截断后有效位减少。如果你的系统对增益精度要求极高建议留在12位模式通过软件做均值滤波来换精度如果必须用10位做高速采样那么校准因子的右移处理一定要精确。第三遇到校准相关的问题第一反应应该去查参考手册的“Offset and gain compensation”小节而不是翻各种二手教程。RM0440里对GAINCALR的触发时序和校准因子的分辨率适配都有明确描述。我第一次踩坑时就是因为手册看漏了一句结果在代码里绕了一大圈。这段内容后续还可以往深处扩展比如结合DMA多通道循环采样的补偿参数管理、结合FOC场景下的中点采样对齐或者写一个产线标定工具把每个通道的偏移和增益补偿值自动算好烧录进Flash。如果大家有兴趣我下篇可以专门写写产线标定这一块。
返回列表