STM_ATEM/User/data_storage.c
zhoujie 5419e33397 feat(monitor): 增强系统监控与数据存储功能
- 新增数据存储缓冲区可用性检查,防止缓冲区满时数据丢失
- 新增会话文件夹管理功能,每次上电自动创建新的数据存储文件夹
- 新增监控状态定期保存功能,将系统统计信息写入MONITOR.TXT文件
- 新增数据丢弃统计,记录因缓冲区满而未存储的数据包数量
- 优化数据输出模式配置,支持串口输出和存储到卡的独立控制
- 优化USB连接处理逻辑,增加系统稳定性检查

🐛 fix(interrupt): 调整中断优先级配置

- 提高USART1中断优先级(从6调整为2),确保串口通信及时响应
- 调整DMA2_Stream5中断优先级(从0调整为5),优化数据传输调度
- 修复RS485驱动中的忙标志逻辑,改为阻塞式传输以提高可靠性

♻️ refactor(config): 优化系统配置和存储设置

- 重构宏定义配置,统一系统监控开关,分离数据输出模式控制
- 将SD卡最大扇区大小从512调整为4096,优化大文件存储性能
- 增加堆栈大小配置(从0x800调整为0x1000),提高系统稳定性
- 优化USB存储读写超时设置,使用最大超时值确保操作完成

📝 docs(comments): 更新代码注释和文档

- 更新数据存储模块的注释,说明新的会话文件夹管理机制
- 在main.c中添加数据输出模式选择的详细说明注释
- 更新系统监控统计输出格式,包含新增的数据丢弃统计项
2026-02-07 13:02:59 +08:00

528 lines
15 KiB
C
Raw 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 <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;
}
// 从PARAM.TXT加载当前序号
uint32_t session_number = 0;
DataStorage_LoadSessionNumber(&session_number);
// 递增序号
session_number++;
// 生成会话文件夹名(基于序号)
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;
}
// 保存更新后的序号到PARAM.TXT
if (DataStorage_SaveSessionNumber(session_number) != 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;
}