- 新增混流解析函数 `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经纬度/光泵),支持灵活配置 - 新增文件模式下的分页浏览功能(滑块/上一页/下一页) - 优化实时模式下的光泵数据显示与表格列过滤
325 lines
9.7 KiB
C
325 lines
9.7 KiB
C
#include "ltc2508_driver.h"
|
||
#include <string.h> // For memset
|
||
|
||
// 全局变量定义 - 双缓冲区实现
|
||
volatile LTC2508_BufferTypeDef g_adc_buffers[LTC2508_BUFFER_COUNT] = {0};
|
||
volatile uint8_t g_adc_data_ready_flag = 0;
|
||
volatile uint8_t g_dma_complete_count = 0;
|
||
volatile uint8_t g_current_write_buffer = 0;
|
||
volatile uint8_t g_current_read_buffer = 0;
|
||
|
||
// 错误统计信息
|
||
LTC2508_StatsTypeDef g_ltc2508_stats = {0};
|
||
|
||
// SPI 句柄指针
|
||
static SPI_HandleTypeDef *g_hspi1 = NULL;
|
||
static SPI_HandleTypeDef *g_hspi2 = NULL;
|
||
static SPI_HandleTypeDef *g_hspi3 = NULL;
|
||
|
||
|
||
/**
|
||
* @brief LTC2508 是否初始化
|
||
* @retval 0-未初始化 1-已初始化
|
||
*/
|
||
uint32_t LTC2508_IsInited()
|
||
{
|
||
return (g_hspi1 != NULL && g_hspi2 != NULL && g_hspi3 != NULL);
|
||
}
|
||
|
||
|
||
/**
|
||
* @brief 初始化 LTC2508 驱动
|
||
* @param hspi1: SPI1 句柄指针
|
||
* @param hspi2: SPI2 句柄指针
|
||
* @param hspi3: SPI3 句柄指针
|
||
* @retval LTC2508_StatusTypeDef
|
||
*/
|
||
LTC2508_StatusTypeDef LTC2508_Init(SPI_HandleTypeDef *hspi1, SPI_HandleTypeDef *hspi2, SPI_HandleTypeDef *hspi3)
|
||
{
|
||
// 参数检查
|
||
if (hspi1 == NULL || hspi2 == NULL || hspi3 == NULL) {
|
||
g_ltc2508_stats.error_count++;
|
||
g_ltc2508_stats.last_error = LTC2508_ERROR_INIT;
|
||
return LTC2508_ERROR_INIT;
|
||
}
|
||
|
||
g_hspi1 = hspi1;
|
||
g_hspi2 = hspi2;
|
||
g_hspi3 = hspi3;
|
||
g_adc_data_ready_flag = 0;
|
||
g_dma_complete_count = 0;
|
||
g_current_write_buffer = 0;
|
||
g_current_read_buffer = 0;
|
||
|
||
// 初始化双缓冲区
|
||
for (int i = 0; i < LTC2508_BUFFER_COUNT; i++) {
|
||
memset((void*)&g_adc_buffers[i], 0, sizeof(LTC2508_BufferTypeDef));
|
||
g_adc_buffers[i].state = LTC2508_BUFFER_EMPTY;
|
||
g_adc_buffers[i].dma_complete_count = 0;
|
||
}
|
||
|
||
// 重置统计信息
|
||
memset(&g_ltc2508_stats, 0, sizeof(g_ltc2508_stats));
|
||
|
||
// 可以在这里添加对 LTC2508 的配置,如果需要通过 SPI 发送配置字
|
||
// 例如:HAL_SPI_Transmit(g_hspi1, &config_word, 1, 100);
|
||
|
||
return LTC2508_OK;
|
||
}
|
||
|
||
/**
|
||
* @brief 触发三路 SPI 通过 DMA 读取数据
|
||
* @param None
|
||
* @retval LTC2508_StatusTypeDef
|
||
*/
|
||
LTC2508_StatusTypeDef LTC2508_TriggerDmaRead(void)
|
||
{
|
||
// 检查SPI句柄是否有效
|
||
if (g_hspi1 == NULL || g_hspi2 == NULL || g_hspi3 == NULL) {
|
||
g_ltc2508_stats.error_count++;
|
||
g_ltc2508_stats.last_error = LTC2508_ERROR_INIT;
|
||
return LTC2508_ERROR_INIT;
|
||
}
|
||
|
||
// 检查当前写入缓冲区是否可用
|
||
volatile LTC2508_BufferTypeDef *current_buffer = &g_adc_buffers[g_current_write_buffer];
|
||
|
||
if (current_buffer->state == LTC2508_BUFFER_EMPTY)
|
||
{
|
||
// 设置缓冲区状态为填充中
|
||
current_buffer->state = LTC2508_BUFFER_FILLING;
|
||
current_buffer->dma_complete_count = 0;
|
||
current_buffer->timestamp = HAL_GetTick();
|
||
|
||
// SPI2 和 SPI3 作为从机只接收;必须在 SPI1(主机)启动时钟前挂好 DMA,否则从机会丢失前几个时钟
|
||
if (HAL_SPI_Receive_DMA(g_hspi2, (uint8_t*)current_buffer->data[1], LTC2508_DATA_LEN) != HAL_OK)
|
||
{
|
||
current_buffer->state = LTC2508_BUFFER_EMPTY;
|
||
g_ltc2508_stats.dma_error_count++;
|
||
g_ltc2508_stats.error_count++;
|
||
g_ltc2508_stats.last_error = LTC2508_ERROR_DMA;
|
||
return LTC2508_ERROR_DMA;
|
||
}
|
||
|
||
if (HAL_SPI_Receive_DMA(g_hspi3, (uint8_t*)current_buffer->data[2], LTC2508_DATA_LEN) != HAL_OK)
|
||
{
|
||
current_buffer->state = LTC2508_BUFFER_EMPTY;
|
||
HAL_SPI_DMAStop(g_hspi2);
|
||
g_ltc2508_stats.dma_error_count++;
|
||
g_ltc2508_stats.error_count++;
|
||
g_ltc2508_stats.last_error = LTC2508_ERROR_DMA;
|
||
return LTC2508_ERROR_DMA;
|
||
}
|
||
|
||
// static:DMA 传输异步完成,必须保证缓冲区生命周期覆盖整个传输过程
|
||
static uint16_t s_dummy_tx[LTC2508_DATA_LEN] = {0};
|
||
if (HAL_SPI_TransmitReceive_DMA(g_hspi1, (uint8_t*)s_dummy_tx, (uint8_t*)current_buffer->data[0], LTC2508_DATA_LEN) != HAL_OK)
|
||
{
|
||
current_buffer->state = LTC2508_BUFFER_EMPTY;
|
||
HAL_SPI_DMAStop(g_hspi2);
|
||
HAL_SPI_DMAStop(g_hspi3);
|
||
g_ltc2508_stats.dma_error_count++;
|
||
g_ltc2508_stats.error_count++;
|
||
g_ltc2508_stats.last_error = LTC2508_ERROR_DMA;
|
||
return LTC2508_ERROR_DMA;
|
||
}
|
||
|
||
return LTC2508_OK;
|
||
}
|
||
|
||
return LTC2508_ERROR_TIMEOUT; // 缓冲区不可用
|
||
}
|
||
|
||
/**
|
||
* @brief SPI DMA 传输完成回调函数
|
||
* @param hspi: SPI 句柄指针
|
||
* @retval None
|
||
*/
|
||
void LTC2508_DmaComplete_Callback(SPI_HandleTypeDef *hspi)
|
||
{
|
||
if (hspi == g_hspi1 || hspi == g_hspi2 || hspi == g_hspi3)
|
||
{
|
||
volatile LTC2508_BufferTypeDef *current_buffer = &g_adc_buffers[g_current_write_buffer];
|
||
|
||
// 增加当前缓冲区的DMA完成计数
|
||
current_buffer->dma_complete_count++;
|
||
|
||
// 检查是否所有SPI的DMA传输都完成
|
||
if (current_buffer->dma_complete_count >= NUM_LTC2508)
|
||
{
|
||
// 标记当前缓冲区为准备就绪
|
||
current_buffer->state = LTC2508_BUFFER_READY;
|
||
g_ltc2508_stats.total_samples++;
|
||
|
||
// 自动切换到下一个缓冲区
|
||
LTC2508_SwapBuffers();
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief SPI DMA 传输错误回调函数
|
||
* @param hspi: SPI 句柄指针
|
||
* @retval None
|
||
*/
|
||
void LTC2508_ErrorCallback(SPI_HandleTypeDef *hspi)
|
||
{
|
||
if (hspi == g_hspi1 || hspi == g_hspi2 || hspi == g_hspi3)
|
||
{
|
||
volatile LTC2508_BufferTypeDef *current_buffer = &g_adc_buffers[g_current_write_buffer];
|
||
|
||
g_ltc2508_stats.dma_error_count++;
|
||
g_ltc2508_stats.error_count++;
|
||
g_ltc2508_stats.last_error = LTC2508_ERROR_DMA;
|
||
|
||
// 重置当前缓冲区状态
|
||
current_buffer->state = LTC2508_BUFFER_EMPTY;
|
||
current_buffer->dma_complete_count = 0;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief 验证ADC数据有效性
|
||
* @param buffer: 缓冲区指针
|
||
* @param channel: ADC通道 (0-2)
|
||
* @retval LTC2508_StatusTypeDef
|
||
*/
|
||
LTC2508_StatusTypeDef LTC2508_ValidateData(LTC2508_BufferTypeDef *buffer, uint8_t channel)
|
||
{
|
||
if (buffer == NULL || channel >= NUM_LTC2508) {
|
||
return LTC2508_ERROR_DATA_INVALID;
|
||
}
|
||
|
||
// 检查数据是否为全0或全1 (可能的错误状态)
|
||
uint32_t combined_data = ((uint32_t)buffer->data[channel][0] << 16) | buffer->data[channel][1];
|
||
|
||
if (combined_data == 0x00000000 || combined_data == 0xFFFFFFFF) {
|
||
g_ltc2508_stats.error_count++;
|
||
g_ltc2508_stats.last_error = LTC2508_ERROR_DATA_INVALID;
|
||
return LTC2508_ERROR_DATA_INVALID;
|
||
}
|
||
|
||
return LTC2508_OK;
|
||
}
|
||
|
||
/**
|
||
* @brief 获取统计信息
|
||
* @param stats: 统计信息结构体指针
|
||
* @retval None
|
||
*/
|
||
void LTC2508_GetStats(LTC2508_StatsTypeDef *stats)
|
||
{
|
||
if (stats != NULL) {
|
||
memcpy(stats, &g_ltc2508_stats, sizeof(LTC2508_StatsTypeDef));
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief 重置统计信息
|
||
* @param None
|
||
* @retval None
|
||
*/
|
||
void LTC2508_ResetStats(void)
|
||
{
|
||
memset(&g_ltc2508_stats, 0, sizeof(LTC2508_StatsTypeDef));
|
||
}
|
||
|
||
/**
|
||
* @brief 缓冲区切换函数
|
||
* @param None
|
||
* @retval LTC2508_StatusTypeDef
|
||
*/
|
||
LTC2508_StatusTypeDef LTC2508_SwapBuffers(void)
|
||
{
|
||
uint8_t next_write_buffer = (g_current_write_buffer + 1) % LTC2508_BUFFER_COUNT;
|
||
|
||
// 检查下一个写入缓冲区是否可用
|
||
if (g_adc_buffers[next_write_buffer].state == LTC2508_BUFFER_EMPTY) {
|
||
g_current_write_buffer = next_write_buffer;
|
||
return LTC2508_OK;
|
||
}
|
||
|
||
// 如果下一个缓冲区不可用,说明缓冲区溢出
|
||
g_ltc2508_stats.buffer_overflow_count++;
|
||
g_ltc2508_stats.error_count++;
|
||
g_ltc2508_stats.last_error = LTC2508_ERROR_BUFFER_OVERFLOW;
|
||
return LTC2508_ERROR_BUFFER_OVERFLOW;
|
||
}
|
||
|
||
/**
|
||
* @brief 获取当前写入缓冲区索引
|
||
* @param None
|
||
* @retval uint8_t 当前写入缓冲区索引
|
||
*/
|
||
uint8_t LTC2508_GetCurrentWriteBuffer(void)
|
||
{
|
||
return g_current_write_buffer;
|
||
}
|
||
|
||
/**
|
||
* @brief 获取当前读取缓冲区索引
|
||
* @param None
|
||
* @retval uint8_t 当前读取缓冲区索引
|
||
*/
|
||
uint8_t LTC2508_GetCurrentReadBuffer(void)
|
||
{
|
||
return g_current_read_buffer;
|
||
}
|
||
|
||
/**
|
||
* @brief 检查指定缓冲区是否准备就绪
|
||
* @param buffer_index: 缓冲区索引
|
||
* @retval LTC2508_StatusTypeDef
|
||
*/
|
||
LTC2508_StatusTypeDef LTC2508_IsBufferReady(uint8_t buffer_index)
|
||
{
|
||
if (buffer_index >= LTC2508_BUFFER_COUNT) {
|
||
return LTC2508_ERROR_DATA_INVALID;
|
||
}
|
||
|
||
if (g_adc_buffers[buffer_index].state == LTC2508_BUFFER_READY) {
|
||
return LTC2508_OK;
|
||
}
|
||
|
||
return LTC2508_ERROR_TIMEOUT;
|
||
}
|
||
|
||
/**
|
||
* @brief 获取准备好的数据缓冲区
|
||
* @param buffer: 返回缓冲区指针
|
||
* @retval LTC2508_StatusTypeDef
|
||
*/
|
||
LTC2508_StatusTypeDef LTC2508_GetReadyBuffer(LTC2508_BufferTypeDef **buffer)
|
||
{
|
||
if (buffer == NULL) {
|
||
return LTC2508_ERROR_DATA_INVALID;
|
||
}
|
||
|
||
// 检查读取缓冲区是否有准备好的数据
|
||
if (g_adc_buffers[g_current_read_buffer].state == LTC2508_BUFFER_READY) {
|
||
g_adc_buffers[g_current_read_buffer].state = LTC2508_BUFFER_PROCESSING;
|
||
*buffer = (LTC2508_BufferTypeDef*)&g_adc_buffers[g_current_read_buffer];
|
||
return LTC2508_OK;
|
||
}
|
||
|
||
return LTC2508_ERROR_TIMEOUT; // 没有准备好的数据
|
||
}
|
||
|
||
/**
|
||
* @brief 释放缓冲区
|
||
* @param buffer_index: 缓冲区索引
|
||
* @retval None
|
||
*/
|
||
void LTC2508_ReleaseBuffer(uint8_t buffer_index)
|
||
{
|
||
if (buffer_index < LTC2508_BUFFER_COUNT) {
|
||
g_adc_buffers[buffer_index].state = LTC2508_BUFFER_EMPTY;
|
||
g_adc_buffers[buffer_index].dma_complete_count = 0;
|
||
|
||
// 如果释放的是当前读取缓冲区,切换到下一个
|
||
if (buffer_index == g_current_read_buffer) {
|
||
g_current_read_buffer = (g_current_read_buffer + 1) % LTC2508_BUFFER_COUNT;
|
||
}
|
||
}
|
||
}
|