- 新增混流解析函数 `parse_mixed_stream()`,支持标准包与光泵包交替出现的二进制文件 - 新增光泵磁力仪模拟器 `OpticMagSimulatorThread`,以可配置频率发送BCD帧 - 新增光泵磁场显示标签页,支持文件模式与实时模式下的磁场曲线绘制 - 新增 `flash_counter` 模块,用于NOFX模式下文件序号持久化 ♻️ refactor(data_storage): 重构数据存储目录与文件命名策略 - 会话目录改为按GPS日期命名(YYYYMMDD),无GPS时使用NOFX目录 - 文件命名改为基于GPS时间(HHMMSS)或Flash计数器(C开头),消除序号文件依赖 - 移除 `config_manager` 依赖和 `PARAM.TXT` 序号文件读写逻辑 - 新增 `GPS_ParseGPRMC()` 解析函数,提取日期字段用于目录命名 🐛 fix(ltc2508_driver): 修复DMA启动失败时资源泄漏问题 - SPI2/SPI3 DMA启动失败时主动停止已启动的DMA,防止资源泄漏 - 将SPI1的dummy发送缓冲区改为static,确保DMA传输期间数据有效 🐛 fix(rs485_driver): 修复DMA发送缓冲区可能被覆盖的问题 - 引入静态发送缓冲区 `s_tx_buf`,隔离调用方数据与DMA传输,防止TIM2 ISR覆盖 ♻️ refactor(ATEMParse): 重构数据结构定义与解析逻辑 - 移除硬编码的V1/V2 dtype,改为动态构建 `make_dtype()` 函数 - 新增数据格式选项(GPS经纬度/光泵),支持灵活配置 - 新增文件模式下的分页浏览功能(滑块/上一页/下一页) - 优化实时模式下的光泵数据显示与表格列过滤
514 lines
15 KiB
C
514 lines
15 KiB
C
#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;
|
||
|
||
// 读取Flash计数器,计算本次会话起始值并立即写回
|
||
// 策略:上电读取存储值+1000作为本次起始,立即写入,防止异常断电重复使用同一区间
|
||
uint32_t flash_base = 0;
|
||
FlashCounter_Init(&flash_base);
|
||
handle->file_counter = flash_base + 1000;
|
||
FlashCounter_Write(handle->file_counter);
|
||
|
||
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 通用字节流写入,支持任意包长度(方案Y混流存储)
|
||
* @param handle: 数据存储句柄指针
|
||
* @param data: 数据缓冲区指针
|
||
* @param size: 写入字节数
|
||
* @retval HAL_StatusTypeDef
|
||
*/
|
||
HAL_StatusTypeDef DataStorage_WriteRawBytes(DataStorageHandle_t *handle, const uint8_t *data, uint16_t size)
|
||
{
|
||
if (handle == NULL || data == NULL || !handle->initialized || size == 0) {
|
||
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 + size > 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], data, size);
|
||
active_buf->index += size;
|
||
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;
|
||
}
|
||
|
||
GPS_Data_t gps;
|
||
GPS_GetData(&gps);
|
||
|
||
if (gps.data_valid) {
|
||
// GPS有效:用 HHMMSS 命名,同秒冲突时追加 _1 _2 ...
|
||
char base[DATA_STORAGE_MAX_PATH_LEN];
|
||
snprintf(base, sizeof(base), "%s/%02u%02u%02u",
|
||
handle->current_session_path,
|
||
gps.time.hour, gps.time.minute, gps.time.second);
|
||
|
||
FILINFO fno;
|
||
snprintf(handle->stats.current_filename,
|
||
sizeof(handle->stats.current_filename), "%s.dat", base);
|
||
|
||
if (f_stat(handle->stats.current_filename, &fno) == FR_OK) {
|
||
for (int i = 1; i <= 99; i++) {
|
||
snprintf(handle->stats.current_filename,
|
||
sizeof(handle->stats.current_filename),
|
||
"%s_%d.dat", base, i);
|
||
if (f_stat(handle->stats.current_filename, &fno) != FR_OK) break;
|
||
}
|
||
}
|
||
} else {
|
||
// 无GPS:用Flash计数器命名 C0001000.dat
|
||
snprintf(handle->stats.current_filename,
|
||
sizeof(handle->stats.current_filename),
|
||
"%s/C%08lu.dat",
|
||
handle->current_session_path, handle->file_counter++);
|
||
}
|
||
|
||
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; // 已经在记录中
|
||
}
|
||
|
||
// 每次开始录制时确定会话目录(此时GPS可能已定位)
|
||
if (DataStorage_CreateSessionFolder(handle) != HAL_OK) {
|
||
handle->stats.state = DATA_STORAGE_ERROR;
|
||
return HAL_ERROR;
|
||
}
|
||
|
||
// 创建新文件
|
||
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;
|
||
}
|
||
|
||
// 创建基础数据目录
|
||
FRESULT res = f_mkdir(DATA_STORAGE_BASE_PATH);
|
||
if (res != FR_OK && res != FR_EXIST) {
|
||
return HAL_ERROR;
|
||
}
|
||
|
||
GPS_Data_t gps;
|
||
GPS_GetData(&gps);
|
||
|
||
if (gps.date_valid) {
|
||
// GPS日期有效:目录格式 0:/DATA/YYYYMMDD
|
||
snprintf(handle->current_session_path, sizeof(handle->current_session_path),
|
||
"%s/%04u%02u%02u",
|
||
DATA_STORAGE_BASE_PATH,
|
||
gps.date.year, gps.date.month, gps.date.day);
|
||
} else {
|
||
// 无GPS日期:统一放入 NOFX 目录
|
||
snprintf(handle->current_session_path, sizeof(handle->current_session_path),
|
||
"%s", DATA_STORAGE_NOFX_DIR);
|
||
}
|
||
|
||
res = f_mkdir(handle->current_session_path);
|
||
if (res != FR_OK && res != FR_EXIST) {
|
||
return HAL_ERROR;
|
||
}
|
||
|
||
return HAL_OK;
|
||
}
|
||
|