STM32从零到量产开发:四路继电器工业控制模块开发 - Flash 参数掉电保存模块(flash_param)设计说明
·
STM32从零到量产开发:四路继电器工业控制模块开发 - Flash 参数掉电保存模块(flash_param)设计说明
1. 模块概述
本模块负责工业控制模块核心运行参数的掉电保存与恢复。针对 STM32F103RC 内部 Flash 特性,实现了参数校验、磨损均衡、跨重启运行时间统计等工业级功能。
核心功能:
- 参数持久化:从机地址、波特率、通信使能、故障安全配置等掉电不丢失。
- 数据完整性:Magic 魔术字 + CRC16 双重校验,防止 Flash 损坏或误读。
- 寿命保护:磨损均衡策略,避免高频擦写导致 Flash 损坏。
- 运行统计:累计复位次数与运行秒数(跨重启连续累计)。
2. 存储架构
2.1 Flash 地址规划
- 目标芯片:STM32F103RC (256KB Flash, 2KB/Page)
- 存储位置:内部 Flash 最后一页
0x0803F800 - 页大小:2KB (
0x800)
2.2 参数结构体设计 (param_t)
采用 4 字节对齐设计,避免 Cortex-M 非对齐访问异常:
typedef struct {
uint16_t magic; // 校验魔术字 (0x5A5A)
uint8_t slave_addr; // 从机地址 1~247
uint8_t baud_index; // 波特率索引 0~4
uint8_t link_enable; // 通信使能 0/1
uint8_t fail_safe_en; // 故障安全使能 0/1
uint8_t fail_safe_sec; // 故障安全超时(秒)
uint8_t reserved; // 4字节对齐填充
uint32_t reset_count; // 累计复位次数
uint32_t run_sec_base; // 运行秒数基准
uint32_t crc; // CRC16 校验
} param_t;
3. 出厂默认参数
| 参数 | 默认值 | 说明 |
|---|---|---|
| 从机地址 | 0x01 | Modbus 从站地址 |
| 波特率索引 | 0x00 | 对应 9600 bps |
| 通信使能 | 0x01 | 默认启用通信 |
| 故障安全使能 | 0x01 | 默认启用(工业安全) |
| 故障安全超时 | 10秒 | 默认超时时间 |
4. 对外公共接口
| 函数 | 说明 |
|---|---|
FlashParam_Init() |
初始化:从 Flash 加载参数,校验失败则恢复出厂默认 |
FlashParam_Get() |
获取当前参数指针(RAM 副本) |
FlashParam_Save() |
修改并保存参数到 Flash(支持部分更新) |
FlashParam_SaveResetCount() |
仅更新复位次数(低频调用) |
FlashParam_ResetDefault() |
恢复出厂默认参数并保存 |
FlashParam_GetRunSeconds() |
获取累计运行秒数(基准 + 本次上电增量) |
5. 关键设计亮点
5.1 Magic + CRC16 双重校验
- Magic 校验:快速判断 Flash 是否已初始化(
0x5A5A) - CRC16 校验:确保参数数据完整性,防止位翻转或 Flash 损坏
5.2 磨损均衡策略
- 参数仅在用户主动修改(Modbus 06/10 功能码)时写入 Flash
- 复位次数缓存在 RAM,随其他参数一并写入,避免每次复位都擦写 Flash
5.3 运行秒跨重启连续累计
run_sec_base:掉电保存的运行秒数基准FlashParam_GetRunSeconds()=run_sec_base+ 本次上电增量- 确保设备运行时间统计准确且断电不丢失
5.4 恢复出厂保留设备履历
FlashParam_ResetDefault()恢复出厂参数时,保留reset_count和run_sec_base- 确保设备运维统计数据不丢失
5.5 Flash 擦写安全保护
- 写入前校验参数范围合法性(地址 1~247、波特率索引 0~4)
- 非法参数直接返回错误,不执行 Flash 擦写
5.6 参数范围钳位
FlashParam_Save()内部对参数进行合法性校验- 超出范围的参数自动钳位到合法值或拒绝写入
6. 工程化设计细节
6.1 结构体 4 字节对齐
reserved字段填充,确保后续uint32_t字段 4 字节对齐- 避免 Cortex-M 非对齐访问异常
6.2 与 Modbus 协议协作
FlashParam_Save()由 Modbus 06/10 功能码触发- 参数修改后立即生效,无需重启
6.3 与故障安全联动
fail_safe_en和fail_safe_sec参数直接影响Modbus_CheckFailSafe()行为- 默认启用故障安全,防止上位机死机导致设备失控
7. 设计总结
| 设计维度 | 具体措施 |
|---|---|
| 数据完整性 | Magic + CRC16 双重校验 |
| Flash 寿命 | 磨损均衡,避免高频擦写 |
| 运行统计 | 跨重启连续累计,断电不丢失 |
| 安全性 | 参数范围钳位,非法值拒绝写入 |
| 可维护性 | 恢复出厂保留履历,便于运维追溯 |
该模块设计充分考虑了工业环境的可靠性要求,兼顾了数据安全性、Flash 寿命保护和运维统计需求,是工业控制设备参数管理的标准实践。
/**
******************************************************************************
* @file flash_param.h
* @brief Flash 参数掉电保存模块 - 对应产品规格书默认出厂参数
*
* 存储位置 (规格书 2):
* 内部 Flash 最后一页 0x0803F800 (STM32F103RC 256KB, 2KB/页)
*
* 存储内容 (规格书 4.4 保持寄存器):
* - 从机地址 (1~247)
* - 波特率索引 (0~4)
* - 通信使能 (0/1)
* - 复位次数 (累计)
*
* 校验机制:
* 结构体含 magic 字段 + crc16 校验, 防止误读未初始化 Flash
*
* 磨损均衡策略:
* - 参数仅在用户修改(通过 Modbus 06/10)时才写入 Flash
* - 复位次数缓存在 RAM, 随参数一并写入, 避免每次复位都擦写 Flash
******************************************************************************
*/
#ifndef __FLASH_PARAM_H
#define __FLASH_PARAM_H
#ifdef __cplusplus
extern "C" {
#endif
#include "main.h"
#include "bsp_rs485.h"
/* Flash 参数页地址 (STM32F103RC 最后一页) */
#define PARAM_FLASH_ADDR 0x0803F800u
#define PARAM_FLASH_PAGE_SIZE 0x800u /* 2KB */
/* 参数校验魔术字 */
#define PARAM_MAGIC 0x5A5Au
/* 出厂默认值 (规格书第8章) */
#define DEFAULT_SLAVE_ADDR 0x01u
#define DEFAULT_BAUD_INDEX 0x00u /* 索引0 = 9600 bps (见规格书 4.4) */
#define DEFAULT_LINK_ENABLE 0x01u
/* #1 通信故障安全默认参数 (工业安全默认开启, 防止上位机死机导致设备失控) */
#define DEFAULT_FAILSAFE_EN 0x01u /* 默认启用故障安全 */
#define DEFAULT_FAILSAFE_SEC 0x0Au /* 默认超时 10 秒 */
/* 参数合法范围 */
#define SLAVE_ADDR_MIN 1u
#define SLAVE_ADDR_MAX 247u
#define FAILSAFE_EN_MAX 1u /* 仅 0/1 */
/* fail_safe_sec: 0~255, 0 表示禁用(配合 fail_safe_en=0) */
/**
* @brief 参数结构体 (存储在 Flash 中)
* @note 字段顺序经过对齐设计: magic(uint16)+slave/baud/link/fail_en/fail_sec(5字节)
* +reserved(1字节) 共 8 字节, 之后 reset_count/run_sec_base/crc 均为 uint32,
* 保证 4 字节对齐, 避免 Cortex-M 非对齐访问异常。
*/
typedef struct {
uint16_t magic; /* 校验魔术字 PARAM_MAGIC */
uint8_t slave_addr; /* 从机地址 1~247 */
uint8_t baud_index; /* 波特率索引 0~4 */
uint8_t link_enable; /* 通信使能 0/1 (0=静默模式不回复) */
uint8_t fail_safe_en; /* 通信故障安全使能 0/1 (#1 功能) */
uint8_t fail_safe_sec; /* 故障安全超时(秒), 0=禁用 (#1 功能) */
uint8_t reserved; /* 4字节对齐填充, 使后续 uint32 字段对齐 */
uint32_t reset_count; /* 累计复位次数 */
uint32_t run_sec_base; /* 累计运行秒数基准(掉电保存), 用于运行小时统计 (#4) */
uint32_t crc; /* CRC16 校验 (除本字段外) */
} param_t;
/**
* @brief 初始化参数模块: 从 Flash 加载, 校验失败则用出厂默认值
* @note 同时将复位次数 +1 (缓存在 RAM, 不立即写 Flash)
*/
void FlashParam_Init(void);
/**
* @brief 获取当前参数指针 (RAM 副本, 直接读取)
* @retval 参数结构体指针
*/
const param_t *FlashParam_Get(void);
/**
* @brief 修改并保存参数到 Flash
* @param slave_addr: 新从机地址 (0 表示不修改)
* @param baud_index: 新波特率索引 (0xFF 表示不修改)
* @param link_enable: 新通信使能 (0xFF 表示不修改)
* @param fail_safe_en: 新故障安全使能 (0xFF 表示不修改)
* @param fail_safe_sec: 新故障安全超时秒 (0xFF 表示不修改)
* @retval 0=成功, -1=参数非法, -2=Flash 写入失败
*/
int8_t FlashParam_Save(uint8_t slave_addr, uint8_t baud_index, uint8_t link_enable,
uint8_t fail_safe_en, uint8_t fail_safe_sec);
/**
* @brief 仅更新复位次数到 Flash (低频调用)
* @retval 0=成功, -2=失败
*/
int8_t FlashParam_SaveResetCount(void);
/**
* @brief 恢复出厂默认参数并保存
*/
void FlashParam_ResetDefault(void);
/**
* @brief 获取累计运行秒数 (#4 运行小时统计)
* @retval 自设备首次运行以来的累计秒数 (已保存基准 + 本次上电增量)
* @note 与 Param_UpdateRunSec 配套, 实时准确且断电不丢失。
*/
uint32_t FlashParam_GetRunSeconds(void);
#ifdef __cplusplus
}
#endif
#endif /* __FLASH_PARAM_H */
/**
******************************************************************************
* @file flash_param.c
* @brief Flash 参数掉电保存实现 (内部 Flash 最后一页 0x0803F800)
*
* 地址推导:
* STM32 片内程序 Flash 起始永远是 0x08000000,
* STM32F103RET6/RCT6(256KB Flash == 0x40000,每页 2KB = 0x800)
* Flash 末尾边界:0x08000000 + 0x40000 = 0x08040000
* 最后一页起始 = 0x08040000 - 0x800 = 0x0803F800
*
* 模块功能:
* 存储从机地址 / 波特率 / 链路使能 / 故障安全参数 / 复位次数 / 运行秒基准,
* 结构含 magic + CRC16 校验防止误读未初始化 Flash。
*
* 数据流:
* 上电: Flash 最后一页 -> memcpy 到 RAM 副本 s_param -> magic + CRC 校验
* -> 校验失败则 Param_LoadDefault 填出厂值并回写
* -> 范围合法性钳位 -> reset_count +1 -> 立即回写 Flash
* 运行: 业务层 FlashParam_Get() 直接读 RAM 副本 s_param (零 Flash 访问)
* 落盘: FlashParam_Save / SaveResetCount / ResetDefault
* -> 更新 RAM 副本 -> Param_UpdateRunSec 折算运行秒
* -> 重算 CRC -> Param_WriteFlash 擦除并按半字编程
* 复位次数与运行秒基准在 #4 运行小时统计中跨重启连续累计。
*
* 解耦说明:
* 1. 上层只通过 FlashParam_Get() 拿只读指针,不感知 Flash 地址与 CRC 细节。
* 2. 全部 Flash 擦写动作收敛在 Param_WriteFlash 一处,便于统一管控擦写寿命。
* 3. 运行秒折算只在保存时进行 (避免频繁擦写),实时值由 FlashParam_GetRunSeconds
* 用 "已保存基准 + 本次上电增量" 现算,两者共用基线 s_run_base_uptime。
******************************************************************************
*/
#include "bsp_storage.h"
#include "main.h"
#include <string.h>
/* -------------------------- API 快速导航 --------------------------
* void FlashParam_Init(void); 上电加载参数, 复位次数 +1 并回写
* int8_t FlashParam_Save(uint8_t slave_addr, uint8_t baud_index,
* uint8_t link_enable, uint8_t fail_safe_en,
* uint8_t fail_safe_sec);
* 修改并落盘 (0 成功 / -1 非法 / -2 写失败)
* int8_t FlashParam_SaveResetCount(void); 仅折算运行秒并回写 (0 / -2)
* void FlashParam_ResetDefault(void); 恢复出厂 (保留设备履历)
* const param_t *FlashParam_Get(void); 获取 RAM 参数副本只读指针
* uint32_t FlashParam_GetRunSeconds(void); 累计运行秒 (基准 + 本次上电增量)
* ------------------------------------------------------------------ */
/* -------------------------- 区块 2:模块内部静态全局变量 -------------------------- */
/* RAM 参数副本 (运行时使用, 所有 FlashParam_Get 返回其地址) */
static param_t s_param;
/* #4 运行秒基准对应的"上电秒数"基线: 用于增量折算运行小时,
* 避免每次 Param_UpdateRunSec 把当前上电时长整段重复累加 (修复虚高 bug) */
static uint32_t s_run_base_uptime = 0u;
/* -------------------------- 内部私有函数前置声明 -------------------------- */
static uint16_t Param_CRC16(const uint8_t *data, uint16_t len);
static uint16_t Param_CalcCRC(const param_t *p);
static uint8_t Param_WriteFlash(const param_t *p);
static void Param_LoadDefault(param_t *p);
static void Param_UpdateRunSec(void);
/* -------------------------- 区块 4:对外公共业务 API -------------------------- */
/**
* @brief 初始化参数模块:从 Flash 加载,校验失败则用出厂默认值
* @retval 无
* @note 同时将复位次数 +1 并立即写回 Flash;用当前上电秒数初始化运行小时折算基线。
* 【关联指引】必须在系统时钟/HAL 初始化之后调用;FlashParam_Get 返回的 s_param 在此填充
*/
void FlashParam_Init(void)
{
const param_t *flash_param = (const param_t *)PARAM_FLASH_ADDR;
/* 从 Flash 读取 */
memcpy(&s_param, flash_param, sizeof(param_t));
/* 校验 magic + CRC:magic 防止读到未编程的空白 Flash(全 0xFF),
CRC 防止掉电写坏导致的半截脏数据被当成合法参数 */
if ((s_param.magic != PARAM_MAGIC) ||
((uint16_t)s_param.crc != Param_CalcCRC(&s_param)))
{
/* Flash 未编程或损坏, 使用出厂默认值 */
Param_LoadDefault(&s_param);
/* 首次写入 (reset_count 此时为 0) */
Param_WriteFlash(&s_param);
}
/* 参数范围合法性检查 */
if (s_param.slave_addr < SLAVE_ADDR_MIN || s_param.slave_addr > SLAVE_ADDR_MAX)
{
s_param.slave_addr = DEFAULT_SLAVE_ADDR;
}
if (s_param.baud_index > BAUD_INDEX_MAX)
{
s_param.baud_index = DEFAULT_BAUD_INDEX;
}
if (s_param.link_enable > 1u)
{
s_param.link_enable = DEFAULT_LINK_ENABLE;
}
/* #1 故障安全参数合法性检查 */
if (s_param.fail_safe_en > FAILSAFE_EN_MAX)
{
s_param.fail_safe_en = DEFAULT_FAILSAFE_EN;
}
/* fail_safe_sec 为 0~255, 任意值均合法(0 表示禁用, 由 en 控制) */
/* 复位次数 +1 (本次上电) */
s_param.reset_count++;
/* 立即写回: STM32F103 Flash 擦写寿命 1 万次, 工业设备每天重启 ≤ 10 次可使用约 2.7 年 */
s_param.crc = (uint32_t)Param_CalcCRC(&s_param);
Param_WriteFlash(&s_param);
/* #4 用当前上电秒数初始化运行小时折算基线, 避免把本次上电时长重复计入 */
s_run_base_uptime = HAL_GetTick() / 1000u;
}
/**
* @brief 修改并保存参数到 Flash
* @param[in] slave_addr: 新从机地址 (0 表示不修改)
* @param[in] baud_index: 新波特率索引 (0xFF 表示不修改)
* @param[in] link_enable: 新通信使能 (0xFF 表示不修改)
* @param[in] fail_safe_en: 新故障安全使能 (0xFF 表示不修改)
* @param[in] fail_safe_sec:新故障安全超时秒 (0xFF 表示不修改)
* @retval 0=成功, -1=参数非法, -2=Flash 写入失败
* @note 先参数校验,再更新 RAM 副本,保存前折算运行时间(#4),最后擦写 Flash。
* 【关联指引】被 Modbus 06/10 写保持寄存器、RS232 Shell ADDR/BAUD/FS 命令调用
*/
int8_t FlashParam_Save(uint8_t slave_addr, uint8_t baud_index, uint8_t link_enable,
uint8_t fail_safe_en, uint8_t fail_safe_sec)
{
/* 参数校验 */
/* slave_addr 用 0 作"不修改"哨兵(Modbus 地址 0 是广播地址, 不可作从机地址) */
if (slave_addr != 0u && (slave_addr < SLAVE_ADDR_MIN || slave_addr > SLAVE_ADDR_MAX))
{
return -1;
}
if (baud_index != 0xFFu && baud_index > BAUD_INDEX_MAX)
{
return -1;
}
if (link_enable != 0xFFu && link_enable > 1u)
{
return -1;
}
if (fail_safe_en != 0xFFu && fail_safe_en > FAILSAFE_EN_MAX)
{
return -1;
}
/* 255(0xFF) 为"不修改"哨兵,合法取值 0..254,故上限为 0xFEu */
if (fail_safe_sec != 0xFFu && fail_safe_sec > 0xFEu)
{
return -1;
}
/* 更新 RAM 副本 (0/0xFF 表示不修改) */
if (slave_addr != 0u)
{
s_param.slave_addr = slave_addr;
}
if (baud_index != 0xFFu)
{
s_param.baud_index = baud_index;
}
if (link_enable != 0xFFu)
{
s_param.link_enable = link_enable;
}
if (fail_safe_en != 0xFFu)
{
s_param.fail_safe_en = fail_safe_en;
}
if (fail_safe_sec != 0xFFu)
{
s_param.fail_safe_sec = fail_safe_sec;
}
/* #4 保存前折算运行时间, 使运行小时跨重启连续 */
Param_UpdateRunSec();
s_param.crc = (uint32_t)Param_CalcCRC(&s_param);
/* 写入 Flash */
if (Param_WriteFlash(&s_param) != 0u)
{
return -2;
}
return 0;
}
/**
* @brief 仅更新复位次数到 Flash(低频调用)
* @retval 0=成功, -2=失败
* @note 保存前同样折算运行时间(#4),保持运行小时连续
*/
int8_t FlashParam_SaveResetCount(void)
{
/* #4 折算运行时间后写回 */
Param_UpdateRunSec();
s_param.crc = (uint32_t)Param_CalcCRC(&s_param);
if (Param_WriteFlash(&s_param) != 0u)
{
return -2;
}
return 0;
}
/**
* @brief 恢复出厂默认参数并保存
* @retval 无
* @note 保留 reset_count / run_sec_base(属"设备履历",不应因恢复出厂清零)。
* 【关联指引】被 RS232 Shell FACTORY 命令调用
*/
void FlashParam_ResetDefault(void)
{
/* 保存当前复位次数与运行时间基准, 恢复出厂后恢复
* (reset_count / run_sec_base 属于"设备履历", 不应因恢复出厂而清零) */
uint32_t saved_reset_count = s_param.reset_count;
uint32_t saved_run_sec = s_param.run_sec_base;
Param_LoadDefault(&s_param);
s_param.reset_count = saved_reset_count;
s_param.run_sec_base = saved_run_sec;
s_param.crc = (uint32_t)Param_CalcCRC(&s_param);
Param_WriteFlash(&s_param);
}
/**
* @brief 获取当前参数指针(RAM 副本,直接读取)
* @retval 参数结构体指针
* @note 【关联指引】所有业务模块(Modbus/RS232 Shell/RS485)均通过本接口读取从机地址、波特率等配置
*/
const param_t *FlashParam_Get(void)
{
return &s_param;
}
/**
* @brief 获取累计运行秒数 (#4 运行小时统计)
* @retval 自设备首次运行以来的累计秒数 (已保存基准 + 本次上电增量)
* @note 与 Param_UpdateRunSec 配套: 已保存基准 + 本次上电已流逝增量,
* 实时准确且断电后不丢失(断电前最后一次保存已折算)。
* 【关联指引】被 SystemApp_GetRunSeconds / Modbus 输入寄存器 IR_ADDR_RUNHOURS 调用
*/
uint32_t FlashParam_GetRunSeconds(void)
{
uint32_t now = HAL_GetTick() / 1000u;
/* now <= 基线说明 HAL_GetTick 已回绕,增量按 0 处理,保证运行秒不倒退 */
uint32_t delta = (now > s_run_base_uptime) ? (now - s_run_base_uptime) : 0u;
return s_param.run_sec_base + delta;
}
/* -------------------------- 区块 5:内部私有底层处理函数 -------------------------- */
/**
* @brief 软件 CRC16 计算(Modbus 多项式 0xA001,初始 0xFFFF)
* @param[in] data: 待计算数据指针
* @param[in] len: 数据长度
* @retval 16位 CRC 校验值
* @note 被 Param_CalcCRC 与 Param_WriteFlash 流程调用,为最底层原语
*/
static uint16_t Param_CRC16(const uint8_t *data, uint16_t len)
{
uint16_t crc = 0xFFFFu;
uint16_t i, j;
for (i = 0u; i < len; i++)
{
crc ^= (uint16_t)data[i];
for (j = 0u; j < 8u; j++)
{
/* 最低位为 1 才异或多项式,这是 Modbus CRC16 的标准逐位算法 */
if (crc & 0x0001u)
{
crc = (uint16_t)((crc >> 1u) ^ 0xA001u);
}
else
{
crc >>= 1u;
}
}
}
return crc;
}
/**
* @brief 计算参数结构体 CRC(排除末尾 crc 字段)
* @param[in] p: 参数结构体指针
* @retval 16位 CRC 校验值
* @note 长度用 sizeof 自动推导,新增参数字段时无需修改本函数
*/
static uint16_t Param_CalcCRC(const param_t *p)
{
/* CRC 覆盖 magic ~ reset_count (不含末尾 crc 字段) */
uint16_t crc_len = (uint16_t)(sizeof(param_t) - sizeof(uint32_t));
return Param_CRC16((const uint8_t *)p, crc_len);
}
/**
* @brief 写一页 Flash(擦除 + 按半字编程)
* @param[in] p: 待写入参数结构体指针
* @retval 0=成功, 1=擦除失败, 2=编程失败
* @note 全文件唯一的 Flash 擦写出口;任一步失败都会先 Lock 再返回,
* 保证不会把 Flash 控制器留在解锁状态
*/
static uint8_t Param_WriteFlash(const param_t *p)
{
FLASH_EraseInitTypeDef erase;
uint32_t page_error = 0u;
HAL_StatusTypeDef st;
uint16_t i;
const uint16_t *p16 = (const uint16_t *)p;
uint16_t words = (uint16_t)((sizeof(param_t) + 1u) / 2u); /* 半字个数 */
HAL_FLASH_Unlock();
/* 擦除一页 */
erase.TypeErase = FLASH_TYPEERASE_PAGES;
erase.Banks = FLASH_BANK_1;
erase.PageAddress = PARAM_FLASH_ADDR;
erase.NbPages = 1u;
st = HAL_FLASHEx_Erase(&erase, &page_error);
if (st != HAL_OK)
{
HAL_FLASH_Lock();
return 1u;
}
/* 按半字编程 (F1 最小编程单位) */
for (i = 0u; i < words; i++)
{
st = HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD,
PARAM_FLASH_ADDR + (uint32_t)(i * 2u),
p16[i]);
if (st != HAL_OK)
{
HAL_FLASH_Lock();
return 2u;
}
}
HAL_FLASH_Lock();
return 0u;
}
/**
* @brief 填充默认参数(出厂值 + 计算 CRC)
* @param[out] p: 待填充参数结构体指针
* @retval 无
* @note 【关联指引】故障安全默认开启(#1),运行小时基准清零(#4);
* 供首次上电与恢复出厂两处调用
*/
static void Param_LoadDefault(param_t *p)
{
/* 清零整个结构体 (消除编译器 padding 字节的不确定性) */
memset(p, 0, sizeof(param_t));
p->magic = PARAM_MAGIC;
p->slave_addr = DEFAULT_SLAVE_ADDR;
p->baud_index = DEFAULT_BAUD_INDEX;
p->link_enable = DEFAULT_LINK_ENABLE;
p->fail_safe_en = DEFAULT_FAILSAFE_EN; /* #1 故障安全默认开启 */
p->fail_safe_sec = DEFAULT_FAILSAFE_SEC; /* #1 默认 10 秒 */
p->reset_count = 0u;
p->run_sec_base = 0u; /* #4 运行秒基准(累计运行小时) */
p->crc = (uint32_t)Param_CalcCRC(p);
}
/**
* @brief 将本次上电运行时间折算进基准 (#4 运行小时)
* @retval 无
* @note 运行小时 = (run_sec_base + 本次上电已流逝增量) / 3600。
* 仅累加"距上次折算的增量" (now - s_run_base_uptime), 而非整段
* HAL_GetTick(), 否则每保存一次参数就把当前上电时长重复计入,
* 导致运行小时虚高。Flash 仅在参数保存时写入(避免频繁擦写),
* 故保存前把已流逝的增量累加进 run_sec_base, 跨重启后运行小时连续。
* 【关联指引】被 FlashParam_GetRunSeconds() 读取同一基线 s_run_base_uptime
*/
static void Param_UpdateRunSec(void)
{
uint32_t now = HAL_GetTick() / 1000u;
/* 仅在时间前进时累加,防止 HAL_GetTick 回绕导致基准被倒扣 */
if (now > s_run_base_uptime)
{
s_param.run_sec_base = s_param.run_sec_base + (now - s_run_base_uptime);
}
s_run_base_uptime = now;
}
更多推荐


所有评论(0)