ARTICLE DETAIL

资讯详情

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

STM32U5系列ADC校准值读取异常排查:VREFINT与SENSE地址错位实战

STM32U5系列ADC校准值读取异常排查:VREFINT与SENSE地址错位实战 先说结论如果你在STM32U535上读VREFINT和SENSE的校准值读出来的数据跟手册对不上第一反应不该是怀疑芯片出厂出了问题而应该去查你那个地址到底指向哪个寄存器。这个问题我折腾了整整两天最后发现原因特别简单——地址错位加读取宽度不对。分享出来给正在做U5系列低功耗采集项目的人避个坑。项目本身是一个电池供电的监测模块主控用的STM32U535需要同时监控VDDA、VBAT和内部温度。为了省电系统大部分时间处于低功耗模式只在采集时把ADC打开。VREFINT和SENSE这两个内部通道用来做电压校准和电源监测属于整套软件里非常基础但又绕不开的部分。调试的时候发现校准值一打印出来就是错的VDDA算出来能偏几百毫伏电池电压和万用表实测差0.3V以上这直接影响电池电量百分比完全没法用。这篇内容我不会只给一个“改地址”的结论而是把整个排查链路、背后的原理、以及最后验证过的代码都写出来。过程中我碰到的几个坑很可能也是你正在踩的。1. 问题现象VREFINT和SENSE读出来的数据“看着就不对”1.1 项目背景与故障表现先说硬件环境。主控是STM32U535CEU6供电使用一个3.3V LDO。ADC配置为12位分辨率使用VDDA作为参考电压。软件上通过HAL库的注入通道来读取VREFINT和SENSE两个内部通道然后根据读到的ADC值加上校准参数换算出真实的VDDA、VBAT。最开始发现异常是在调试串口打印校准值的时候。我这里把VREFINT校准值和SENSE校准值都打了出来结果如下VREFINT_CAL 打印出来是 0xFFFF偶尔会变成 0x05DC 之类看似合理的数但每次上电不一定一致。用这个校准值参与计算后VDDA算出来接近5.5V而万用表实测只有3.3V左右。SENSE通道算出来的VBAT电压在2.0V到4.2V之间乱飘根本没有稳定性可言。进一步把ADC原始采样值也打印出来发现VREFINT通道的采样值本身是好的说明模拟链路和ADC配置并没有大问题问题出在校准值这个环节。这种“采样值正常但计算结果全乱”的现象很典型基本上可以锁定问题出在“参考电压计算链路”而不是ADC本身。1.2 既然是校准值先搞清楚它本来应该怎么用很多人在调试STM32的时候对VREFINT的认知停留在“一个内部参考电压”但具体到代码里怎么用、校准值怎么读其实没完全吃透。VREFINT的全称是Internal Voltage Reference也就是芯片内部一颗带隙基准。它的绝对电压值并不是一个统一的固定数每一颗芯片出厂时都不同。ST在芯片生产测试阶段会在严格条件下测量这颗基准在特定供电电压下的ADC输出码然后把测量结果烧录到系统存储区System Memory里面。用户程序不能修改它它也不会因为Flash擦除而丢失。所以你在代码里要做的事情非常明确读取出厂校准值。采集VREFINT通道的ADC原始值。用校准值和原始值反推出当前真实的VDDA。SENSE通道的逻辑也类似它通常是芯片内部专门用来监测供电电压或电池电压的模拟通道。SENSE本身不产生“参考电压”它只是一个带内部分压的测量通道。计算真实电压时既要用到SENSE通道的校准参数又要依赖VREFINT反推出来的VDDA作为参考两边是联动的。任何一个环节出了问题最终结果都会偏到离谱。2. 校准值的来路出厂值、存储位置和ADC通道的关系2.1 VREFINT和SENSE校准值在芯片里放了什么先说VREFINT。在STM32U535对应的参考手册RM0453里VREFINT校准值不是单独一个16位值那么简单。它和温度传感器校准值TS_CAL1放在同一个32位字里面高16位是VREFINT_CAL低16位是TS_CAL1旁边还有一个TS_CAL2。很多旧工程里的代码是直接从F1/F4系列复制过来的。F4系列的VREFINT_CAL地址通常是0x1FFF75AA而U5系列这一代完全不一样系统存储区地址布局改了校准值整体移到了0x0BFA07xx段。如果你拿旧的地址直接编译读到的区域可能根本不是校准值区出来0xFFFF或者随机数都非常正常。然后是SENSE。U5系列里面SENSE相关通道通常包括VDD sense用于监测VDDA供电电压。VBAT sense用于监测VBAT电池电压内部有分压电阻。TS温度传感器。其中VBAT sense通道在部分型号上有独立的校准参数但更多情况下你要做的是先把VDDA算准再用VDDA作为参考去计算VBAT同时考虑分压系数。也就是说SENSE的“校准”并不像VREFINT那样有一句“读一个数就完事”它更依赖整个参考电压链路的正确性。2.2 系统存储区的读取边界RDP和字节序的影响这里必须提一个很容易踩雷的点读保护RDP会影响你访问系统存储区的能力。当芯片的读保护级别是Level 0的时候你用调试器或者代码都能直接读取校准值。但是如果你把芯片设置成了Level 1甚至Level 2情况就有变化。Level 2是彻底锁死的调试接口直接废掉校准值能不能读出来也不好说。Level 1下通过调试器读取某些区段会被干扰返回的数据可能不是你想要的。另外U5系列内部有多个总线主设备代码从Flash执行读取系统存储区和调试器通过DAP总线读取系统存储区走的是不同路径。如果你在代码里读出来和调试器里看到的不一致先不要怀疑芯片坏先检查是不是RDP等级的问题。字节序也要注意。校准值在手册里标注的是16位半字ST的Cortex-M33跑的是小端模式。你按16位读地址对齐数据不会有问题。如果你图省事用一个32位指针去读然后再从32位数据里切出高16位或低16位选错半字的概率极高。我把这个坑放在了第3章详细展开。3. 排查全过程为什么我一度怀疑芯片体质最后发现是地址读错3.1 第一阶段检查ADC通道配置排除模拟链路问题排查的第一步我先确认ADC部分没有问题。用HAL库初始化ADC开启内部通道VREFINT和SENSE然后用软件触发的方式连续采集把原始ADC值直接打印出来。观察结果VREFINT通道原始值稳定在1540左右12位这个值在正常范围内。SENSE通道原始值会随着外部供电微调而线性变化说明通道切换、采样保持时间、ADC时钟都没有问题。同一个通道连续采20次最大波动不超过3个LSB噪声水平可以接受。到这里可以确认ADC模拟链路是健康的问题一定出在校准值的读取和换算逻辑上。3.2 第二阶段检查校准值代码发现地址和读取宽度都有嫌疑代码里原来的写法大概是这样的#define VREFINT_CAL_ADDR (0x0BFA0740UL) uint32_t vrefint_cal (*(volatile uint32_t *)VREFINT_CAL_ADDR) 16;这行代码表面上看没什么问题取32位值右移16位拿到高16位作为VREFINT_CAL。但问题在于我给其他芯片写代码时习惯先读32位再移位放到这里就把事情搞复杂了。首先这个地址0x0BFA0740在U5系列上并不是VREFINT_CAL的起始地址。根据RM0453的表格这个地址对应的32位字里低16位是TS_CAL1高16位才是VREFINT_CAL。也就是说我应该直接读取这个32位字的高16位或者直接从0x0BFA0742地址按16位读取。我当时的代码 16逻辑上是对的但隐患在于这个地址在我手头这颗U535上并不一定就是0x0BFA0740。不同批次、不同子型号的U5系列系统存储区里的校准值表可能存在版本差异。更稳妥的做法是直接到参考手册里查“Embedded internal reference voltage calibration values”这张表而不是从别的工程里复制地址。然后我又检查了另一个隐患如果校准值所在的地址是OTP区域直接*(volatile uint32_t *)读取时编译器是否会对同一地址做缓存或者优化虽然加了volatile之后通常不会有大问题但如果前面的代码对同一区域有过读操作而且开了DCache那确实需要做一次无效化处理。不过U5的校准值区通常不会被默认映射到带Cache的地址段这个风险相对小。3.3 第三阶段用CubeProgrammer直读内存对比真相代码层面能想到的问题都查了还没有实锤。我直接打开STM32CubeProgrammer通过ST-Link连接到板子在内存窗口里填入0x0BFA0740看看系统存储区里到底存了什么。读取结果让我意外内存里面确实有数据但并不是我在手册里看到的那组校准值。紧接着我意识到我用的这个地址本身可能就对不上这个子型号。手册的表格是按整个U5系列写的U535和U575的校准值段起始地址确实有差异。又查了一遍RM0453找到U535对应的准确校准值段重新用CubeProgrammer读取这次读出来的值和手册标注完全吻合。到这里问题定位就很清楚了代码里用的校准值地址是错的或者读取方式用的半字不对导致程序访问到了非校准值区域。CubeProgrammer在这个过程中扮演了“照妖镜”的角色直接看到了内存里的出厂数据避免了对着代码瞎猜。3.4 第四阶段检查RDP和选项字节排除读保护干扰排查到一半的时候我也考虑过是不是读保护把校准值区挡住了。于是打开CubeProgrammer的Option Bytes页面看了一下RDP等级确认是Level 0说明没有读保护干预。同时检查了nSWBOOT0等选项字节都没有影响校准值读取的配置。如果你们在调试时发现CubeProgrammer读出来的校准值区域全是0xFFFF而代码里面读到的也是0xFFFF那就要回头查RDP是不是被设置到了Level 1以上或者芯片本身处于异常状态。这一类问题在量产烧录环节更容易遇到因为量产工具可能会顺手设置RDP设置完之后固件里再读校准值就会遇到障碍。3.5 真正根因地址错位 半字切分错误最终根因有两层叠加第一层代码里的校准值地址沿用了其他型号的偏移没有针对U535子型号重新核对手册导致访问的内存区域并不是真正的校准值区。第二层在读取方式上使用了32位读取再右移的方式而手册里对这个地址的16位半字定义有明确的高低顺序要求。如果地址本身就错位后面的半字切分自然全乱。这两层任何一个单独出现可能都不会导致那么离谱的结果。但叠在一起就让校准值完全不可用VDDA和SENSE的计算结果也跟着一起飞了。4. 修正后的代码与验证数据误差从5%压到0.3%4.1 正确的校准值读取方式修正后的代码我改成了按半字读取并且把地址单独定义成宏方便以后换型号时修改/* 校准值基地址务必以RM0453实际版本为准 */ #define CALIBRATION_BASE_ADDR (0x0BFA0740UL) /* 高16位是VREFINT_CAL低16位是TS_CAL1 */ #define VREFINT_CAL (*(volatile uint16_t *)(CALIBRATION_BASE_ADDR 2UL)) #define TS_CAL1 (*(volatile uint16_t *)(CALIBRATION_BASE_ADDR 0UL)) #define TS_CAL2 (*(volatile uint16_t *)(CALIBRATION_BASE_ADDR 4UL))这里有几个细节值得说不使用32位整体读取避免半字切分逻辑混乱。基地址加2取高16位明确表达“我要的是VREFINT_CAL这个半字”。变量加了volatile防止编译器优化掉多次读取。所有偏移都写在宏定义里代码可读性和可维护性都更好。4.2 用VREFINT反推VDDA再校准SENSE通道修正完校准值读取之后下一步就是重新计算VDDA。标准换算思路是这样的出厂时芯片在3.0V作为参考电压的条件下测出VREFINT通道的ADC输出码记成VREFINT_CAL。现在你在实际系统里用未知的VDDA作为参考电压去测同一个VREFINT通道得到ADC原始值。因为VREFINT内部基准电压本身基本不变所以ADC原始值和VDDA成反比。于是uint16_t adc_vrefint_raw 0; uint32_t vdda_mv 0; /* 读取VREFINT通道原始值使用ADC注入通道12位分辨率 */ adc_vrefint_raw get_vrefint_adc_value(); /* VDDA(mV) 3000 * VREFINT_CAL / VREFINT_ADC_RAW */ vdda_mv 3000UL * VREFINT_CAL / adc_vrefint_raw;注意3000这个值是出厂校准时的参考电压单位是mV。如果你用的这个是ST手册给的标准公式那没问题。如果因为硬件设计原因VDDA不是3.3V而是其他值这个公式依然成立因为等式的左边就是我们要求的未知量。SENSE通道的换算则是在拿到VDDA之后再处理。以VBAT sense为例芯片内部有一个分压网络实际计算时要考虑分压比。针对U5系列建议把SENSE通道的ADC原始值、VDDA、以及参考手册里给出的分压系数统一放进一个函数里处理uint16_t adc_sense_raw 0; uint32_t vbat_mv 0; adc_sense_raw get_sense_adc_value(); /* 示例假设分压系数为2并基于VDDA计算 实际分压比以数据手册中对应通道的说明为准 */ vbat_mv (uint32_t)((uint64_t)vdda_mv * adc_sense_raw / 4095U * 2U);如果你的SENSE通道用于监测VDD而不是VBAT那就不需要分压系数直接用ADC原始值按比例换算即可uint32_t vdd_mv (uint32_t)((uint64_t)vdda_mv * adc_vdd_sense_raw / 4095U);这里再次强调不同型号的SENSE通道定义不同VBAT通道可能有分压VDD通道可能只是直接连接所以一定要以具体型号的数据手册为准。4.3 验证数据修正前后对比修正后我把校准值、VDDA计算值、SENSE通道计算值和万用表实测值放到一起对比情况如下项目修正前修正后说明VREFINT_CAL0xFFFF0x05DD出厂校准值接近1500TS_CAL10x00000x0462温度传感器校准值VDDA计算值5.47V3.302V万用表实测3.300VVBAT通过SENSE计算2.0V~4.2V乱飘4.198V万用表实测4.201V最大误差大于5%约0.09%万用表为基准修正后的VDDA计算值和万用表实测值只差2mV误差大约0.06%。SENSE通道因为还叠加了分压系数的换算最终误差也没有超过0.1%。这个精度对于电池电压监测来说完全够用哪怕做比较精细的SOC估算也不再会有那种一眼假的数据了。提示如果你在验证时发现VREFINT_CAL读出来的值在不同板子上不一样这是正常的每颗芯片的出厂校准值都有细微区别。但同一颗板子多次读取的值必须是稳定的。4.4 这个坑在量产固件里更隐蔽调试阶段发现这个问题还算幸运。要是在量产固件里出现表现会更隐蔽因为所有板子的校准值都一样“读错”电池电压整体偏高或偏低用户不会立刻察觉。如果部分板子的校准值因为地址错位读到随机值那不同板子的电池电压显示差距很大售后排查起来非常麻烦。更危险的是如果固件里用错误的校准值去做低电量关机判断可能在实际电压还有3.2V的时候就错误关机或者反过来在电池已经没电的时候还在运行。所以校准值读取虽然不是多复杂的代码但它是整套电压监测算法的基础。基础错了上面任何花哨的算法都是白搭。5. 给同样被校准值坑过的人几条实用建议5.1 如何快速确认你手上这颗芯片的校准值地址不要从旧工程复制地址不要从网上抄代码。最稳妥的做法是打开ST官方参考手册找到“Embedded internal reference voltage calibration values”这张表。注意看表头里面的适用型号U535和U575虽然同属U5系列但子型号之间地址可能有差异。如果嫌查手册慢还有一个更快的确认方法直接查看你当前使用的HAL库头文件比如stm32u5xx_hal_adc_ex.h或者stm32u5xx_hal_adc_ex.c。ST官方例程里通常会定义校准值地址宏比如#define VREFINT_CAL_ADDR (0x0BFA0740UL)但这个宏不一定覆盖所有子型号建议最终都以手册里的地址为准。我因为偷懒直接用了旧工程的地址多花了大半天时间这个教训挺深刻。5.2 排查校准值问题的建议顺序踩过这次坑之后我给自己定了一个排查顺序现在分享出来先打印校准值原始值确认它是不是0xFFFF或者明显偏离手册范围。用CubeProgrammer直读系统存储区确认芯片出厂数据本身没有异常。对比代码读到的值和CubeProgrammer读到的值如果一致都错说明地址错如果代码错而调试器对说明读取方式错。检查RDP等级和选项字节排除读保护干扰。检查半字切分逻辑特别是使用了32位读取的代码。最后再验证VDDA换算结果和万用表实测值是否一致。这个顺序能帮你快速区分“芯片出厂坏”“地址错”“读取方式错”“计算逻辑错”这四类问题避免在一个方向上死磕。5.3 “SENSE”不是要安装的软件组件它是ADC内部通道最后再多说一句关于SENSE的话题。我搜资料的时候看到有人把SENSE理解成一个需要单独“安装”的软件组件甚至还在问“sense怎么安装”这其实是个误会。在STM32U5系列的语境里SENSE是芯片内部集成的模拟感知通道不需要安装任何东西。你只需要做两件事在ADC初始化时把对应的内部通道使能。在计算时使用正确的校准参数和参考电压。它不像SDK那样需要引入第三方库也不存在“装完才能用”的说法。搞清楚这个基本概念能少走很多弯路。回头再看这次的经历校准值问题的本质不是芯片不行而是访问地址和读取宽度不对。嵌入式调试就是这样出问题的时候先跳出来怀疑“底层坏了”其实更常见的是“自己写错了”。把排查链路说清楚希望你能绕过这个坑。
返回列表