STM_ATEM/User/data_storage.c
zhoujie 3c0acaa176 feat(config): 新增运行时配置管理功能
- 新增配置管理器模块,支持从SD卡加载运行时配置
- 将数据输出模式从编译时宏改为运行时配置,提高灵活性
- 新增配置文件 `CONFIG.TXT`,支持串口输出和存储功能的动态开关
- 集成配置管理器到主程序,在系统启动时自动加载配置
- 更新数据存储模块,使用配置管理器管理会话序号

🐛 fix(usart): 修复USART3中断配置并优化波特率

- 添加USART3中断处理函数声明和实现
- 将USART3波特率从921600提升至2000000以提高通信速率
- 调整USART1中断优先级从2改为11,优化中断响应
- 在USART3 MSP初始化和反初始化中添加中断配置

📝 docs(user): 新增功能说明文档

- 新增《配置管理功能说明》文档,详细说明运行时配置的使用方法
- 新增《串口发送监控功能说明》文档,说明新增的串口统计功能
- 文档包含配置项说明、文件格式、使用示例和注意事项

 feat(monitor): 增强系统监控功能

- 在系统监控统计中添加串口发送监控字段(发送次数、字节数、错误数)
- 新增串口发送统计报告函数,集成到RS485驱动中
- 优化监控状态保存格式,采用精简格式减少文件写入阻塞时间
- 监控数据现在包含串口统计信息,便于性能分析和故障排查

♻️ refactor(main): 重构主程序配置处理逻辑

- 移除编译时宏 `DATA_OUTPUT_MODE_UART` 和 `DATA_OUTPUT_MODE_STORAGE`
- 使用 `Config_IsUartOutputEnabled()` 和 `Config_IsStorageEnabled()` 函数替代
- 优化数据存储启动逻辑,仅在存储功能启用时开始记录
- 添加配置加载成功/失败的调试输出信息
2026-02-07 14:28:14 +08:00

525 lines
15 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "data_storage.h"
#include "system_monitor.h"
#include "config_manager.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
/**
* @brief 初始化数据存储模块
* @param handle: 数据存储句柄指针
* @retval HAL_StatusTypeDef
*/
HAL_StatusTypeDef DataStorage_Init(DataStorageHandle_t *handle)
{
if (handle == NULL) {
return HAL_ERROR;
}
// 初始化句柄
memset(handle, 0, sizeof(DataStorageHandle_t));
// 初始化双缓冲区
for (int i = 0; i < 2; i++) {
handle->buffers[i].index = 0;
handle->buffers[i].state = BUFFER_IDLE;
memset(handle->buffers[i].data, 0, DATA_STORAGE_BUFFER_SIZE);
}
// 设置活动缓冲区为0
handle->active_buffer = 0;
handle->flush_buffer = 1;
handle->flush_in_progress = 0;
// 创建新的会话文件夹(每次上电创建新文件夹)
if (DataStorage_CreateSessionFolder(handle) != HAL_OK) {
return HAL_ERROR;
}
handle->stats.state = DATA_STORAGE_IDLE;
handle->initialized = 1;
return HAL_OK;
}
/**
* @brief 停止数据记录
* @param handle: 数据存储句柄指针
* @retval HAL_StatusTypeDef
*/
HAL_StatusTypeDef DataStorage_StopRecording(DataStorageHandle_t *handle)
{
if (handle == NULL || !handle->initialized) {
return HAL_ERROR;
}
if (handle->stats.state != DATA_STORAGE_RECORDING) {
return HAL_OK; // 没有在记录中
}
// 刷新所有缓冲区
for (int i = 0; i < 2; i++) {
if (handle->buffers[i].index > 0) {
handle->buffers[i].state = BUFFER_READY_TO_FLUSH;
DataStorage_FlushBuffer(handle, i);
}
}
// 强制同步所有数据到SD卡
f_sync(&handle->file);
// 关闭文件
f_close(&handle->file);
handle->stats.state = DATA_STORAGE_IDLE;
return HAL_OK;
}
/**
* @brief 写入数据包到存储
* @param handle: 数据存储句柄指针
* @param packet: 数据包指针
* @retval HAL_StatusTypeDef
*/
HAL_StatusTypeDef DataStorage_WriteData(DataStorageHandle_t *handle, const DataPacket_t *packet)
{
if (handle == NULL || packet == NULL || !handle->initialized) {
return HAL_ERROR;
}
if (handle->stats.state != DATA_STORAGE_RECORDING) {
return HAL_ERROR;
}
DataBuffer_t *active_buf = &handle->buffers[handle->active_buffer];
// 检查当前活动缓冲区空间
if (active_buf->index + sizeof(DataPacket_t) > DATA_STORAGE_BUFFER_SIZE) {
// 切换缓冲区
if (DataStorage_SwitchBuffer(handle) != HAL_OK) {
handle->stats.error_count++;
return HAL_ERROR;
}
active_buf = &handle->buffers[handle->active_buffer];
}
// 复制数据到活动缓冲区
memcpy(&active_buf->data[active_buf->index], packet, sizeof(DataPacket_t));
active_buf->index += sizeof(DataPacket_t);
active_buf->state = BUFFER_WRITING;
handle->stats.total_samples++;
return HAL_OK;
}
/**
* @brief 写入校正后的数据包到存储
* @param handle: 数据存储句柄指针
* @param packet: 数据包指针
* @retval HAL_StatusTypeDef
*/
HAL_StatusTypeDef DataStorage_WriteCorrectedData(DataStorageHandle_t *handle, const CorrectedDataPacket_t *packet)
{
if (handle == NULL || packet == NULL || !handle->initialized) {
return HAL_ERROR;
}
if (handle->stats.state != DATA_STORAGE_RECORDING) {
return HAL_ERROR;
}
DataBuffer_t *active_buf = &handle->buffers[handle->active_buffer];
// 检查当前活动缓冲区空间
if (active_buf->index + sizeof(CorrectedDataPacket_t) > DATA_STORAGE_BUFFER_SIZE) {
// 切换缓冲区
if (DataStorage_SwitchBuffer(handle) != HAL_OK) {
handle->stats.error_count++;
return HAL_ERROR;
}
active_buf = &handle->buffers[handle->active_buffer];
}
// 复制校正后的数据到活动缓冲区
memcpy(&active_buf->data[active_buf->index], packet, sizeof(CorrectedDataPacket_t));
active_buf->index += sizeof(CorrectedDataPacket_t);
active_buf->state = BUFFER_WRITING;
handle->stats.total_samples++;
return HAL_OK;
}
/**
* @brief 刷新缓冲区到文件
* @param handle: 数据存储句柄指针
* @retval HAL_StatusTypeDef
*/
HAL_StatusTypeDef DataStorage_Flush(DataStorageHandle_t *handle)
{
if (handle == NULL || !handle->initialized) {
return HAL_ERROR;
}
// 处理后台刷新任务
DataStorage_ProcessBackgroundTasks(handle);
return HAL_OK;
}
/**
* @brief 创建新的数据文件
* @param handle: 数据存储句柄指针
* @retval HAL_StatusTypeDef
*/
HAL_StatusTypeDef DataStorage_CreateNewFile(DataStorageHandle_t *handle)
{
if (handle == NULL || !handle->initialized) {
return HAL_ERROR;
}
// 生成文件名 (基于时间戳),文件存储在当前会话文件夹中
uint32_t timestamp = HAL_GetTick();
snprintf(handle->stats.current_filename, sizeof(handle->stats.current_filename),
"%s%s%08lX.dat", handle->current_session_path, DATA_STORAGE_FILE_PREFIX, timestamp);
// 创建并打开文件
FRESULT res = f_open(&handle->file, handle->stats.current_filename,
FA_CREATE_ALWAYS | FA_WRITE);
if (res != FR_OK) {
handle->stats.error_count++;
SystemMonitor_ReportSDWriteError(); // 报告文件创建错误
return HAL_ERROR;
}
handle->stats.file_count++;
handle->stats.current_file_size = 0;
SystemMonitor_ReportSDFileCreated(); // 报告文件创建成功
return HAL_OK;
}
/**
* @brief 获取数据存储统计信息
* @param handle: 数据存储句柄指针
* @param stats: 统计信息结构体指针
* @retval None
*/
void DataStorage_GetStats(DataStorageHandle_t *handle, DataStorageStats_t *stats)
{
if (handle == NULL || stats == NULL || !handle->initialized) {
return;
}
memcpy(stats, &handle->stats, sizeof(DataStorageStats_t));
}
/**
* @brief 开始数据记录
* @param handle: 数据存储句柄指针
* @retval HAL_StatusTypeDef
*/
HAL_StatusTypeDef DataStorage_StartRecording(DataStorageHandle_t *handle)
{
if (handle == NULL || !handle->initialized) {
return HAL_ERROR;
}
if (handle->stats.state == DATA_STORAGE_RECORDING) {
return HAL_OK; // 已经在记录中
}
// 创建新文件
if (DataStorage_CreateNewFile(handle) != HAL_OK) {
handle->stats.state = DATA_STORAGE_ERROR;
return HAL_ERROR;
}
// 重置双缓冲区状态
for (int i = 0; i < 2; i++) {
handle->buffers[i].index = 0;
handle->buffers[i].state = BUFFER_IDLE;
}
handle->active_buffer = 0;
handle->flush_buffer = 1;
handle->flush_in_progress = 0;
handle->stats.state = DATA_STORAGE_RECORDING;
return HAL_OK;
}
/**
* @brief 刷新指定缓冲区到文件
* @param handle: 数据存储句柄指针
* @param buffer_index: 缓冲区索引 (0 或 1)
* @retval HAL_StatusTypeDef
*/
HAL_StatusTypeDef DataStorage_FlushBuffer(DataStorageHandle_t *handle, uint8_t buffer_index)
{
if (handle == NULL || !handle->initialized || buffer_index > 1) {
return HAL_ERROR;
}
DataBuffer_t *buffer = &handle->buffers[buffer_index];
// 检查缓冲区状态和数据
if (buffer->state != BUFFER_READY_TO_FLUSH || buffer->index == 0) {
return HAL_OK; // 没有数据需要刷新
}
// 标记缓冲区正在刷新
buffer->state = BUFFER_FLUSHING;
UINT bytes_written;
FRESULT res = f_write(&handle->file, buffer->data, buffer->index, &bytes_written);
if (res != FR_OK || bytes_written != buffer->index) {
handle->stats.error_count++;
buffer->state = BUFFER_READY_TO_FLUSH; // 恢复状态以便重试
SystemMonitor_ReportSDWriteError(); // 报告SD卡写入错误
return HAL_ERROR;
}
// 优化:减少同步频率以提高性能
// 使用静态变量记录刷新次数
static uint32_t flush_count = 0;
flush_count++;
// 每10次刷新才同步一次或者文件即将达到最大大小时同步
if (flush_count % 10 == 0 ||
handle->stats.current_file_size + bytes_written >= DATA_STORAGE_FILE_MAX_SIZE) {
f_sync(&handle->file);
}
// 更新统计信息
handle->stats.current_file_size += bytes_written;
SystemMonitor_ReportSDWrite(bytes_written); // 报告SD卡写入成功
// 重置缓冲区
buffer->index = 0;
buffer->state = BUFFER_IDLE;
// 检查文件大小是否超过限制
if (handle->stats.current_file_size >= DATA_STORAGE_FILE_MAX_SIZE) {
f_close(&handle->file);
DataStorage_CreateNewFile(handle);
}
return HAL_OK;
}
/**
* @brief 处理后台任务(异步刷新缓冲区)
* @param handle: 数据存储句柄指针
* @retval None
*/
void DataStorage_ProcessBackgroundTasks(DataStorageHandle_t *handle)
{
if (handle == NULL || !handle->initialized) {
return;
}
// 检查是否有缓冲区需要刷新
for (int i = 0; i < 2; i++) {
if (handle->buffers[i].state == BUFFER_READY_TO_FLUSH) {
// 刷新缓冲区
DataStorage_FlushBuffer(handle, i);
break; // 一次只处理一个缓冲区,避免阻塞太久
}
}
}
/**
* @brief 切换活动缓冲区
* @param handle: 数据存储句柄指针
* @retval HAL_StatusTypeDef
*/
HAL_StatusTypeDef DataStorage_SwitchBuffer(DataStorageHandle_t *handle)
{
if (handle == NULL || !handle->initialized) {
return HAL_ERROR;
}
DataBuffer_t *current_buf = &handle->buffers[handle->active_buffer];
// 标记当前缓冲区准备刷新
if (current_buf->index > 0) {
current_buf->state = BUFFER_READY_TO_FLUSH;
}
// 切换到另一个缓冲区
uint8_t next_buffer = (handle->active_buffer == 0) ? 1 : 0;
DataBuffer_t *next_buf = &handle->buffers[next_buffer];
// 检查目标缓冲区是否可用
if (next_buf->state == BUFFER_FLUSHING) {
// 目标缓冲区正在刷新,等待完成
handle->stats.error_count++;
SystemMonitor_ReportSDBufferFull(); // 报告缓冲区满
return HAL_ERROR;
}
// 切换活动缓冲区
handle->active_buffer = next_buffer;
next_buf->index = 0;
next_buf->state = BUFFER_WRITING;
return HAL_OK;
}
/**
* @brief 检查缓冲区是否有足够空间可用
* @param handle: 数据存储句柄指针
* @param required_size: 需要的空间大小(字节)
* @retval 1: 缓冲区可用, 0: 缓冲区不可用
*/
uint8_t DataStorage_IsBufferAvailable(DataStorageHandle_t *handle, uint32_t required_size)
{
if (handle == NULL || !handle->initialized) {
return 0;
}
// 如果未在记录状态,返回不可用
if (handle->stats.state != DATA_STORAGE_RECORDING) {
return 0;
}
DataBuffer_t *active_buf = &handle->buffers[handle->active_buffer];
// 检查当前活动缓冲区是否有足够空间
if (active_buf->index + required_size <= DATA_STORAGE_BUFFER_SIZE) {
return 1; // 当前缓冲区有足够空间
}
// 当前缓冲区空间不足,检查是否可以切换到另一个缓冲区
uint8_t next_buffer = (handle->active_buffer == 0) ? 1 : 0;
DataBuffer_t *next_buf = &handle->buffers[next_buffer];
// 如果另一个缓冲区正在刷新或准备刷新,则不可用
if (next_buf->state == BUFFER_FLUSHING || next_buf->state == BUFFER_READY_TO_FLUSH) {
return 0; // 无法切换,缓冲区不可用
}
// 另一个缓冲区可用
return 1;
}
/**
* @brief 创建新的会话文件夹
* @param handle: 数据存储句柄指针
* @retval HAL_StatusTypeDef
*/
HAL_StatusTypeDef DataStorage_CreateSessionFolder(DataStorageHandle_t *handle)
{
if (handle == NULL) {
return HAL_ERROR;
}
// 从配置管理器获取并递增会话序号
uint32_t session_number = Config_IncrementSessionNumber();
// 生成会话文件夹名(基于序号)
snprintf(handle->current_session_path, sizeof(handle->current_session_path),
"%s/%s%06lu", DATA_STORAGE_BASE_PATH, DATA_STORAGE_FOLDER_PREFIX, session_number);
// 创建基础数据目录(如果不存在)
FRESULT res = f_mkdir(DATA_STORAGE_BASE_PATH);
if (res != FR_OK && res != FR_EXIST) {
return HAL_ERROR;
}
// 创建会话文件夹
res = f_mkdir(handle->current_session_path);
if (res != FR_OK && res != FR_EXIST) {
return HAL_ERROR;
}
// 保存更新后的配置(包含新的会话序号)
if (Config_Save() != HAL_OK) {
// 即使保存失败,也继续使用该文件夹
// 这不是致命错误
}
return HAL_OK;
}
/**
* @brief 从文件加载会话序号
* @param session_number: 用于存储序号的指针
* @retval HAL_StatusTypeDef
*/
HAL_StatusTypeDef DataStorage_LoadSessionNumber(uint32_t *session_number)
{
if (session_number == NULL) {
return HAL_ERROR;
}
FIL file;
FRESULT res;
UINT bytes_read;
char buffer[16];
// 打开PARAM.TXT文件
res = f_open(&file, DATA_STORAGE_PARAM_FILE, FA_READ);
if (res != FR_OK) {
// 文件不存在返回初始序号0
*session_number = 0;
return HAL_OK;
}
// 读取序号
res = f_read(&file, buffer, sizeof(buffer) - 1, &bytes_read);
if (res != FR_OK) {
f_close(&file);
*session_number = 0;
return HAL_OK;
}
// 添加字符串结束符
buffer[bytes_read] = '\0';
// 关闭文件
f_close(&file);
// 转换为数字
*session_number = (uint32_t)atoi(buffer);
return HAL_OK;
}
/**
* @brief 保存会话序号到文件
* @param session_number: 要保存的序号
* @retval HAL_StatusTypeDef
*/
HAL_StatusTypeDef DataStorage_SaveSessionNumber(uint32_t session_number)
{
FIL file;
FRESULT res;
UINT bytes_written;
char buffer[16];
// 创建或覆盖PARAM.TXT文件
res = f_open(&file, DATA_STORAGE_PARAM_FILE, FA_CREATE_ALWAYS | FA_WRITE);
if (res != FR_OK) {
return HAL_ERROR;
}
// 将序号转换为字符串
snprintf(buffer, sizeof(buffer), "%lu", session_number);
// 写入序号
res = f_write(&file, buffer, strlen(buffer), &bytes_written);
if (res != FR_OK || bytes_written != strlen(buffer)) {
f_close(&file);
return HAL_ERROR;
}
// 关闭文件
f_close(&file);
return HAL_OK;
}