[APM32F4] APM32F407备份SRAM VBAT测试避坑指南:从误判到精准检测

[复制链接]
297|4
Peixu 发表于 2025-12-1 17:52 | 显示全部楼层 |阅读模式
本帖最后由 Peixu 于 2025-12-1 17:55 编辑

APM32F407备份SRAM VBAT测试避坑指南:从误判到精准检测
最近在做APM32F407的备份SRAM数据保持测试,踩了个关于VBAT状态检测的大坑——明明没接CR2032电池,串口却一直显示“供电正常”。折腾大半天终于搞定,把整个排查过程和最终方案整理出来,给同样用这款MCU的小伙伴避坑。

一、需求与初始问题
核心需求很简单:利用备份SRAM存储少量关键数据,通过VBAT电池保证MCU断电后数据不丢失。测试目标是验证两种场景:
  • 接VBAT电池:断电后备份SRAM数据完整
  • 未接VBAT电池:提示数据会丢失,断电后验证数据确实丢失

但初始代码运行后就出问题了:无论接不接VBAT电池
,串口都打印“VBAT状态:供电正常”,完全无法区分实际供电情况。这要是用到项目里,数据丢失了都不知道原因,太危险了

二、问题根源:错用标志位的血泪教训
先看初始代码里的VAT检测逻辑,当时想当然地用了PMU的标志位判断,代码是这么写的:
// 错误的VBAT状态检测(复用调压器就绪标志)
#define VBAT_OK()           (PMU_ReadStatusFlag(PMU_FLAG_VOSR) != RESET)
#define VBAT_ERR()          (PMU_ReadStatusFlag(PMU_FLAG_VOSR) == RESET)

后来翻了Geehy的官方PMU库源码才发现问题——APM32F407的PMU模块根本没有专门的VBAT供电标志位!我用的PMU_FLAG_VOSR是“调压器电压缩放就绪标志”,只表示调压器初始化完成,和VBAT电池接没接半毛钱关系。就算没接VBAT,只要VDD供电正常,调压器照样能就绪,自然一直返回“正常”。
再仔细看PMU库的PMU_ReadStatusFlag函数,支持的标志位只有5个:WUE(唤醒)、SB(待机)、PVDO(电压检测输出)、BKPR(备份调压器就绪)、VOSR(电压缩放就绪),确实没有VBAT专属标志。

三、破局思路:利用硬件特性间接判断
既然没有直接的标志位可用,就换个思路——利用备份SRAM的硬件特性:
接VBAT时,备份SRAM由电池独立供电,断电后数据不丢失;未接VBAT时,断电备份SRAM无供电,数据必然丢失。   
基于这个特性,设计了“标志位+数据校验”的双重判断逻辑:
  • 首次运行时,往备份SRAM写入测试数据和“首次运行标志”(0x55AA55AA)
  • 断电后,先检查“首次运行标志”:若存在,说明SRAM数据可能保留;若不存在,直接判定VBAT无效
  • 再校验测试数据的完整性:标志位存在且数据完整→VBAT有效;标志位存在但数据异常,或标志位丢失→VBAT无效

四、最终可用代码和验证
14797692d65de01ca8.png

24600692d65e527060.png

  1. /*!
  2. * [url=/u/file]@file[/url]        main.c
  3. * [url=/u/brief]@brief[/url]       APM32F407 Backup SRAM VBAT断电测试(精简版)
  4. * [url=/u/version]@version[/url]     V1.0.6
  5. * [url=/u/date]@date[/url]        2025-02-15
  6. * [url=/u/Attention]@Attention[/url]   Copyright (C) 2021-2025 Geehy Semiconductor
  7. */

  8. /* Includes ****************************************************************/
  9. #include "main.h"
  10. #include <stdio.h>
  11. #include <string.h>

  12. /* Private Macro ***********************************************************/
  13. #define DEBUG_USART         USART1
  14. #define BACKUP_SRAM_SIZE    4096          // 4KB容量
  15. #define BKPSRAM_TEST_OFFSET 0x0000        // 测试数据偏移
  16. #define BKPSRAM_FLAG_OFFSET 0x0100        // 标志位偏移
  17. #define BKPSRAM_FIRST_FLAG  0x55AA55AAUL  // 首次运行标志

  18. // 修复:改用“数据保持状态”判断VBAT(通过参数传递,避免全局变量)
  19. #define VBAT_OK(fr, do)     ((fr) == 0 && (do) == 1)  // VBAT有效(断电后数据完整)
  20. #define VBAT_ERR(fr, do)    (!(VBAT_OK(fr, do)))      // VBAT无效(首次运行或数据丢失)

  21. /* Private Variables *******************************************************/
  22. uint8_t g_test_buf[] = {0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88,0x99,0xAA};
  23. uint8_t g_read_buf[sizeof(g_test_buf)] = {0};

  24. /* Private Function Prototypes *********************************************/
  25. uint8_t BackupSRAM_Init(void);
  26. uint16_t BackupSRAM_Write(uint16_t off, uint8_t *buf, uint16_t len);
  27. uint16_t BackupSRAM_Read(uint16_t off, uint8_t *buf, uint16_t len);
  28. uint8_t IsFirstRun(void);
  29. void SetFirstRunFlag(void);
  30. // 修复:添加参数传递first_run和data_ok
  31. void VBAT_Check(uint8_t first_run, uint8_t data_ok);
  32. void Delay(uint32_t cnt);

  33. /* Function Definitions ****************************************************/
  34. /**
  35. * @brief  备份SRAM初始化
  36. * @retval 0:成功 1:失败
  37. */
  38. uint8_t BackupSRAM_Init(void)
  39. {
  40.     RCM_EnableAPB1PeriphClock(RCM_APB1_PERIPH_PMU);
  41.     PMU_EnableBackupAccess();
  42.     RCM_EnableAHB1PeriphClock(RCM_AHB1_PERIPH_BKPSRAM);
  43.     PMU_EnableBackupRegulator();

  44.     // 修复:等待调压器就绪(用正确的PMU_FLAG_BKPR,而非VOSR)
  45.     uint32_t timeout = 0xFFFF;
  46.     while((PMU_ReadStatusFlag(PMU_FLAG_BKPR) == RESET) && timeout--);
  47.     return timeout ? 0 : 1;
  48. }

  49. /**
  50. * @brief  备份SRAM写入
  51. * @param  off: 地址偏移 len: 数据长度
  52. * @retval 实际写入长度
  53. */
  54. uint16_t BackupSRAM_Write(uint16_t off, uint8_t *buf, uint16_t len)
  55. {
  56.     if(!buf || !len || off >= BACKUP_SRAM_SIZE) return 0;
  57.     uint16_t real_len = (off + len) > BACKUP_SRAM_SIZE ? (BACKUP_SRAM_SIZE - off) : len;
  58.     memcpy((uint8_t *)BKPSRAM_BASE + off, buf, real_len);
  59.     return real_len;
  60. }

  61. /**
  62. * @brief  备份SRAM读取
  63. * @param  off: 地址偏移 len: 读取长度
  64. * @retval 实际读取长度
  65. */
  66. uint16_t BackupSRAM_Read(uint16_t off, uint8_t *buf, uint16_t len)
  67. {
  68.     if(!buf || !len || off >= BACKUP_SRAM_SIZE) return 0;
  69.     uint16_t real_len = (off + len) > BACKUP_SRAM_SIZE ? (BACKUP_SRAM_SIZE - off) : len;
  70.     memcpy(buf, (uint8_t *)BKPSRAM_BASE + off, real_len);
  71.     return real_len;
  72. }

  73. /**
  74. * @brief  检查是否首次运行
  75. * @retval 1:首次 0:非首次
  76. */
  77. uint8_t IsFirstRun(void)
  78. {
  79.     return *(uint32_t *)(BKPSRAM_BASE + BKPSRAM_FLAG_OFFSET) != BKPSRAM_FIRST_FLAG;
  80. }

  81. /**
  82. * @brief  设置首次运行标志
  83. */
  84. void SetFirstRunFlag(void)
  85. {
  86.     uint32_t flag = BKPSRAM_FIRST_FLAG;
  87.     BackupSRAM_Write(BKPSRAM_FLAG_OFFSET, (uint8_t *)&flag, sizeof(flag));
  88. }

  89. /**
  90. * @brief  VBAT状态检测提示(修复参数传递和判断逻辑)
  91. * @param  first_run: 是否首次运行(1:是 0:否)
  92. * @param  data_ok: 数据是否完整(1:完整 0:丢失)
  93. */
  94. void VBAT_Check(uint8_t first_run, uint8_t data_ok)
  95. {
  96.     // 首次运行时无法判断,仅提示;断电后根据数据状态判断
  97.     if(first_run == 1)
  98.     {
  99.         printf("\r\nVBAT状态:暂无法判断(首次运行)\r\n");
  100.         printf("提示:未接VBAT时断电数据将丢失(正常特性)\r\n");
  101.     }
  102.     else
  103.     {
  104.         printf("\r\nVBAT状态:%s\r\n", VBAT_OK(first_run, data_ok) ? "供电正常" : "未检测到供电");
  105.     }
  106. }

  107. /**
  108. * @brief  简单延时
  109. */
  110. void Delay(uint32_t cnt)
  111. {
  112.     volatile uint32_t delay = cnt;
  113.     while(delay--);
  114. }

  115. /**
  116. * @brief  主函数
  117. */
  118. int main(void)
  119. {
  120.     uint8_t init_ok, first_run, data_ok = 1;
  121.        
  122.     USART_Config_T usartConfigStruct;
  123.        
  124.     /* 串口配置:115200 8N1 仅发送(测试无需接收) */
  125.     usartConfigStruct.baudRate = 115200;
  126.     usartConfigStruct.hardwareFlow = USART_HARDWARE_FLOW_NONE;
  127.     usartConfigStruct.mode = USART_MODE_TX;
  128.     usartConfigStruct.parity = USART_PARITY_NONE;
  129.     usartConfigStruct.stopBits = USART_STOP_BIT_1;
  130.     usartConfigStruct.wordLength = USART_WORD_LEN_8B;
  131.     BOARD_COMInit(COM1, &usartConfigStruct);

  132.     // 标题打印
  133.     printf("================================\r\n");
  134.     printf("APM32F407 备份SRAM VBAT测试\r\n");
  135.     printf("================================\r\n");

  136.     // 备份SRAM初始化
  137.     init_ok = BackupSRAM_Init();
  138.     printf("备份SRAM初始化:%s\r\n", init_ok ? "失败" : "成功");

  139.     // 首次运行判断(移到VBAT_Check前,供判断使用)
  140.     first_run = IsFirstRun();

  141.     // 修复:传递参数first_run和data_ok
  142.     VBAT_Check(first_run, data_ok);

  143.     if(first_run)
  144.     {
  145.         // 首次运行:写入测试数据
  146.         printf("\r\n【首次运行】写入测试数据...\r\n");
  147.         uint16_t wlen = BackupSRAM_Write(BKPSRAM_TEST_OFFSET, g_test_buf, sizeof(g_test_buf));
  148.         for(uint8_t i=0; i<wlen; i++) printf("0x%02X ", g_test_buf[i]);
  149.         printf("\r\n写入长度:%d Bytes\r\n", wlen);
  150.         SetFirstRunFlag();
  151.         printf("写入完成!按断电键测试数据保持性\r\n");
  152.     }
  153.     else
  154.     {
  155.         // 断电后:读取验证
  156.         printf("\r\n【断电后】读取备份数据...\r\n");
  157.         uint16_t rlen = BackupSRAM_Read(BKPSRAM_TEST_OFFSET, g_read_buf, sizeof(g_read_buf));
  158.         for(uint8_t i=0; i<rlen; i++) printf("0x%02X ", g_read_buf[i]);
  159.         printf("\r\n读取长度:%d Bytes\r\n", rlen);

  160.         // 数据校验
  161.         if(rlen != sizeof(g_test_buf)) data_ok = 0;
  162.         else for(uint8_t i=0; i<rlen; i++) if(g_read_buf[i] != g_test_buf[i]) data_ok = 0;

  163.         // 结果提示
  164.         if(data_ok)
  165.         {
  166.             printf("\r\n测试成功!数据保持完整\r\n");
  167.         }
  168.         else
  169.         {
  170.             printf("\r\n数据丢失!\r\n");
  171.             if(VBAT_ERR(first_run, data_ok))
  172.             {
  173.                 printf("原因:未接VBAT电池\r\n");
  174.             }
  175.             else
  176.             {
  177.                 printf("原因:VBAT供电但数据丢失(配置/硬件异常)\r\n");
  178.             }
  179.         }
  180.     }

  181.     while(1)
  182.     {
  183.                        
  184.     }
  185. }

  186. /* printf重定向 ************************************************************/
  187. #if defined (__CC_ARM) || defined (__ICCARM__) || (defined(__ARMCC_VERSION) && (__ARMCC_VERSION >= 6010050))
  188. int fputc(int ch, FILE* f)
  189. {
  190.     USART_TxData(DEBUG_USART, (uint8_t)ch);
  191.     while(USART_ReadStatusFlag(DEBUG_USART, USART_FLAG_TXBE) == RESET);
  192.     return ch;
  193. }
  194. #elif defined (__GNUC__)
  195. int __io_putchar(int ch)
  196. {
  197.     USART_TxData(DEBUG_USART, ch);
  198.     while(USART_ReadStatusFlag(DEBUG_USART, USART_FLAG_TXBE) == RESET);
  199.     return ch;
  200. }
  201. int _write(int file, char* ptr, int len)
  202. {
  203.     UNUSED(file);
  204.     for(int i=0; i<len; i++) __io_putchar(*ptr++);
  205.     return len;
  206. }
  207. #else
  208. #warning Not supported compiler type
  209. #endif



9485692d65a6bc876.png
29862692d65b6a5f67.png

评论

搞的不错  发表于 2025-12-2 09:03
空灵回声 发表于 2025-12-1 19:19 | 显示全部楼层
我们也是使用后备寄存器记录当前系统的状态。
Gfan 发表于 2025-12-3 09:11 | 显示全部楼层
好帖顶顶,不过发现楼主好像有一张图好像挂了
转瞬回声 发表于 2025-12-3 10:20 | 显示全部楼层
不安装钮扣电池数据丢失属于正常现象。
检测也没有意义啊!该丢还是丢!

就是运维人员的事~~
您需要登录后才可以回帖 登录 | 注册

本版积分规则

34

主题

63

帖子

0

粉丝
快速回复 在线客服 返回列表 返回顶部
0