feat(scripts): 新增光泵磁力仪数据支持与混流解析

- 新增混流解析函数 `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经纬度/光泵),支持灵活配置
- 新增文件模式下的分页浏览功能(滑块/上一页/下一页)
- 优化实时模式下的光泵数据显示与表格列过滤
This commit is contained in:
zhoujie 2026-06-14 23:06:25 +08:00
parent 047ef08318
commit 29cc814384
12 changed files with 912 additions and 380 deletions

View File

@ -177,7 +177,7 @@ NVIC.DebugMonitor_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false
NVIC.EXTI1_IRQn=true\:0\:0\:false\:false\:true\:true\:true\:true NVIC.EXTI1_IRQn=true\:0\:0\:false\:false\:true\:true\:true\:true
NVIC.ForceEnableDMAVector=true NVIC.ForceEnableDMAVector=true
NVIC.HardFault_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false NVIC.HardFault_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false
NVIC.MemoryManagement_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false NVIC.MemoryManagement_IRQn=true\:0\:0\:true\:false\:true\:false\:false\:false
NVIC.NonMaskableInt_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false NVIC.NonMaskableInt_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false
NVIC.OTG_FS_IRQn=true\:11\:0\:true\:false\:true\:false\:true\:true NVIC.OTG_FS_IRQn=true\:11\:0\:true\:false\:true\:false\:true\:true
NVIC.PendSV_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false NVIC.PendSV_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false
@ -292,8 +292,8 @@ ProjectManager.MainLocation=Core/Src
ProjectManager.NoMain=false ProjectManager.NoMain=false
ProjectManager.PreviousToolchain= ProjectManager.PreviousToolchain=
ProjectManager.ProjectBuild=false ProjectManager.ProjectBuild=false
ProjectManager.ProjectFileName=STM_ATEM_F405.ioc ProjectManager.ProjectFileName=STM_ATEM_F405_App.ioc
ProjectManager.ProjectName=STM_ATEM_F405 ProjectManager.ProjectName=STM_ATEM_F405_App
ProjectManager.ProjectStructure= ProjectManager.ProjectStructure=
ProjectManager.RegisterCallBack= ProjectManager.RegisterCallBack=
ProjectManager.StackSize=0x1000 ProjectManager.StackSize=0x1000

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@ -26,7 +26,7 @@
// RS485 串口输出开关 // RS485 串口输出开关
// 1 = 每帧 ADC 数据通过 USART1 以 2Mbps 发送 // 1 = 每帧 ADC 数据通过 USART1 以 2Mbps 发送
// 0 = 禁用串口输出,降低功耗和 CPU 占用 // 0 = 禁用串口输出,降低功耗和 CPU 占用
#define CFG_UART_OUTPUT_ENABLED 1 #define CFG_UART_OUTPUT_ENABLED 0
// SD 卡数据存储开关 // SD 卡数据存储开关
// 1 = 每帧数据写入 SD 卡 DATA/SESSION_xxx/ 目录下的文件 // 1 = 每帧数据写入 SD 卡 DATA/SESSION_xxx/ 目录下的文件

View File

@ -1,6 +1,5 @@
#include "data_storage.h" #include "data_storage.h"
#include "system_monitor.h" #include "system_monitor.h"
#include "config_manager.h"
#include <string.h> #include <string.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@ -31,14 +30,16 @@ HAL_StatusTypeDef DataStorage_Init(DataStorageHandle_t *handle)
handle->flush_buffer = 1; handle->flush_buffer = 1;
handle->flush_in_progress = 0; handle->flush_in_progress = 0;
// 创建新的会话文件夹(每次上电创建新文件夹) // 读取Flash计数器计算本次会话起始值并立即写回
if (DataStorage_CreateSessionFolder(handle) != HAL_OK) { // 策略:上电读取存储值+1000作为本次起始立即写入防止异常断电重复使用同一区间
return HAL_ERROR; 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->stats.state = DATA_STORAGE_IDLE;
handle->initialized = 1; handle->initialized = 1;
return HAL_OK; return HAL_OK;
} }
@ -212,26 +213,49 @@ HAL_StatusTypeDef DataStorage_CreateNewFile(DataStorageHandle_t *handle)
if (handle == NULL || !handle->initialized) { if (handle == NULL || !handle->initialized) {
return HAL_ERROR; return HAL_ERROR;
} }
// 生成文件名 (基于时间戳),文件存储在当前会话文件夹中 GPS_Data_t gps;
uint32_t timestamp = HAL_GetTick(); GPS_GetData(&gps);
snprintf(handle->stats.current_filename, sizeof(handle->stats.current_filename),
"%s%s%08lX.dat", handle->current_session_path, DATA_STORAGE_FILE_PREFIX, timestamp); 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, FRESULT res = f_open(&handle->file, handle->stats.current_filename,
FA_CREATE_ALWAYS | FA_WRITE); FA_CREATE_ALWAYS | FA_WRITE);
if (res != FR_OK) { if (res != FR_OK) {
handle->stats.error_count++; handle->stats.error_count++;
SystemMonitor_ReportSDWriteError(); // 报告文件创建错误 SystemMonitor_ReportSDWriteError();
return HAL_ERROR; return HAL_ERROR;
} }
handle->stats.file_count++; handle->stats.file_count++;
handle->stats.current_file_size = 0; handle->stats.current_file_size = 0;
SystemMonitor_ReportSDFileCreated(); // 报告文件创建成功 SystemMonitor_ReportSDFileCreated();
return HAL_OK; return HAL_OK;
} }
@ -264,7 +288,13 @@ HAL_StatusTypeDef DataStorage_StartRecording(DataStorageHandle_t *handle)
if (handle->stats.state == DATA_STORAGE_RECORDING) { if (handle->stats.state == DATA_STORAGE_RECORDING) {
return HAL_OK; // 已经在记录中 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) { if (DataStorage_CreateNewFile(handle) != HAL_OK) {
handle->stats.state = DATA_STORAGE_ERROR; handle->stats.state = DATA_STORAGE_ERROR;
@ -451,109 +481,33 @@ HAL_StatusTypeDef DataStorage_CreateSessionFolder(DataStorageHandle_t *handle)
if (handle == NULL) { if (handle == NULL) {
return HAL_ERROR; 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); FRESULT res = f_mkdir(DATA_STORAGE_BASE_PATH);
if (res != FR_OK && res != FR_EXIST) { if (res != FR_OK && res != FR_EXIST) {
return HAL_ERROR; 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); res = f_mkdir(handle->current_session_path);
if (res != FR_OK && res != FR_EXIST) { if (res != FR_OK && res != FR_EXIST) {
return HAL_ERROR; return HAL_ERROR;
} }
// 保存更新后的配置(包含新的会话序号)
if (Config_Save() != HAL_OK) {
// 即使保存失败,也继续使用该文件夹
// 这不是致命错误
}
return 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;
}

View File

@ -7,15 +7,15 @@
#include "ff.h" #include "ff.h"
#include "data_packet.h" #include "data_packet.h"
#include "correction.h" #include "correction.h"
#include "flash_counter.h"
#include "gps_driver.h"
#include <stdint.h> #include <stdint.h>
// 数据存储配置(数值由 app_config.h 中 CFG_* 统一管理) // 数据存储配置(数值由 app_config.h 中 CFG_* 统一管理)
#define DATA_STORAGE_BUFFER_SIZE CFG_STORAGE_BUFFER_SIZE #define DATA_STORAGE_BUFFER_SIZE CFG_STORAGE_BUFFER_SIZE
#define DATA_STORAGE_FILE_MAX_SIZE CFG_STORAGE_FILE_MAX_SIZE #define DATA_STORAGE_FILE_MAX_SIZE CFG_STORAGE_FILE_MAX_SIZE
#define DATA_STORAGE_BASE_PATH "0:/DATA" // 数据存储基础路径 #define DATA_STORAGE_BASE_PATH "0:/DATA" // 数据存储基础路径
#define DATA_STORAGE_FILE_PREFIX "/ADC_DATA_" // 文件名前缀 #define DATA_STORAGE_NOFX_DIR "0:/DATA/NOFX" // 无GPS定位时的目录
#define DATA_STORAGE_FOLDER_PREFIX "SESSION_" // 文件夹名前缀
#define DATA_STORAGE_PARAM_FILE "0:/PARAM.TXT" // 记录会话序号的文件
#define DATA_STORAGE_MAX_PATH_LEN 128 // 最大路径长度 #define DATA_STORAGE_MAX_PATH_LEN 128 // 最大路径长度
// 缓冲区状态 // 缓冲区状态
@ -61,6 +61,7 @@ typedef struct {
uint8_t initialized; uint8_t initialized;
uint8_t flush_in_progress; // 刷新进行中标志 uint8_t flush_in_progress; // 刷新进行中标志
char current_session_path[DATA_STORAGE_MAX_PATH_LEN]; // 当前会话文件夹路径 char current_session_path[DATA_STORAGE_MAX_PATH_LEN]; // 当前会话文件夹路径
uint32_t file_counter; // NOFX模式下的文件序号从Flash读取后+1000
} DataStorageHandle_t; } DataStorageHandle_t;
// 函数声明 // 函数声明
@ -76,10 +77,6 @@ HAL_StatusTypeDef DataStorage_CreateNewFile(DataStorageHandle_t *handle);
// 文件夹管理函数 // 文件夹管理函数
HAL_StatusTypeDef DataStorage_CreateSessionFolder(DataStorageHandle_t *handle); HAL_StatusTypeDef DataStorage_CreateSessionFolder(DataStorageHandle_t *handle);
// 序号管理函数
HAL_StatusTypeDef DataStorage_LoadSessionNumber(uint32_t *session_number);
HAL_StatusTypeDef DataStorage_SaveSessionNumber(uint32_t session_number);
// 双缓冲区管理函数 // 双缓冲区管理函数
HAL_StatusTypeDef DataStorage_SwitchBuffer(DataStorageHandle_t *handle); HAL_StatusTypeDef DataStorage_SwitchBuffer(DataStorageHandle_t *handle);
HAL_StatusTypeDef DataStorage_FlushBuffer(DataStorageHandle_t *handle, uint8_t buffer_index); HAL_StatusTypeDef DataStorage_FlushBuffer(DataStorageHandle_t *handle, uint8_t buffer_index);

59
User/flash_counter.c Normal file
View File

@ -0,0 +1,59 @@
#include "flash_counter.h"
typedef struct __attribute__((packed)) {
uint32_t magic;
uint32_t counter;
uint32_t checksum; // magic ^ counter用于检测 Flash 损坏
} FlashCounterData_t;
HAL_StatusTypeDef FlashCounter_Init(uint32_t *counter)
{
const FlashCounterData_t *p = (const FlashCounterData_t *)FLASH_COUNTER_ADDR;
if (p->magic == FLASH_COUNTER_MAGIC &&
(p->magic ^ p->counter) == p->checksum) {
*counter = p->counter;
} else {
// 未初始化或校验失败,视为 0
*counter = 0;
}
return HAL_OK;
}
HAL_StatusTypeDef FlashCounter_Write(uint32_t counter)
{
FLASH_EraseInitTypeDef erase = {
.TypeErase = FLASH_TYPEERASE_SECTORS,
.Sector = FLASH_COUNTER_SECTOR,
.NbSectors = 1,
.VoltageRange = FLASH_VOLTAGE_RANGE_3, // 2.7 ~ 3.6 V
};
uint32_t sector_error = 0;
HAL_FLASH_Unlock();
if (HAL_FLASHEx_Erase(&erase, &sector_error) != HAL_OK) {
HAL_FLASH_Lock();
return HAL_ERROR;
}
FlashCounterData_t data = {
.magic = FLASH_COUNTER_MAGIC,
.counter = counter,
.checksum = FLASH_COUNTER_MAGIC ^ counter,
};
uint32_t addr = FLASH_COUNTER_ADDR;
uint32_t *word = (uint32_t *)&data;
for (uint8_t i = 0; i < sizeof(FlashCounterData_t) / 4; i++) {
if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, addr, word[i]) != HAL_OK) {
HAL_FLASH_Lock();
return HAL_ERROR;
}
addr += 4;
}
HAL_FLASH_Lock();
return HAL_OK;
}

20
User/flash_counter.h Normal file
View File

@ -0,0 +1,20 @@
#ifndef FLASH_COUNTER_H
#define FLASH_COUNTER_H
#include "main.h"
#include <stdint.h>
// STM32F405 1MB Flash 最后一个扇区Sector 11, 128KB
// 地址范围 0x080E0000 ~ 0x080FFFFF
// 确保链接脚本 FLASH ORIGIN+LENGTH 不超过 0x080E0000
#define FLASH_COUNTER_SECTOR FLASH_SECTOR_11
#define FLASH_COUNTER_ADDR 0x080E0000UL
#define FLASH_COUNTER_MAGIC 0x5A5A1234UL
// 从 Flash 读取计数器。首次使用(未写入)时 *counter = 0返回 HAL_OK
HAL_StatusTypeDef FlashCounter_Init(uint32_t *counter);
// 将新计数器值擦除写入 Flash约 1~2 ms
HAL_StatusTypeDef FlashCounter_Write(uint32_t counter);
#endif // FLASH_COUNTER_H

View File

@ -26,6 +26,7 @@ static GPS_Data_t gps_data; // GPS数据
/* Private function prototypes -----------------------------------------------*/ /* Private function prototypes -----------------------------------------------*/
static void GPS_ParseNMEA(char *nmea); static void GPS_ParseNMEA(char *nmea);
static void GPS_ParseGPGGA(char *nmea); static void GPS_ParseGPGGA(char *nmea);
static void GPS_ParseGPRMC(char *nmea);
static double GPS_ConvertToDecimal(const char *coord, char direction); static double GPS_ConvertToDecimal(const char *coord, char direction);
/* Exported functions --------------------------------------------------------*/ /* Exported functions --------------------------------------------------------*/
@ -147,6 +148,23 @@ void GPS_GetPositionString(char *buffer, uint16_t size)
} }
} }
/**
* @brief GPS日期字符串 YYYYMMDD
*/
void GPS_GetDateString(char *buffer, uint16_t size)
{
if (buffer == NULL || size == 0) return;
if (gps_data.date_valid) {
snprintf(buffer, size, "%04u%02u%02u",
gps_data.date.year,
gps_data.date.month,
gps_data.date.day);
} else {
buffer[0] = '\0';
}
}
/** /**
* @brief UART接收完成回调函数 * @brief UART接收完成回调函数
* @param huart: UART句柄 * @param huart: UART句柄
@ -209,11 +227,11 @@ static void GPS_ParseNMEA(char *nmea)
return; return;
} }
// 检查是否为GPGGA或GNGGA语句
if (strncmp(nmea, "$GPGGA", 6) == 0 || strncmp(nmea, "$GNGGA", 6) == 0) { if (strncmp(nmea, "$GPGGA", 6) == 0 || strncmp(nmea, "$GNGGA", 6) == 0) {
GPS_ParseGPGGA(nmea); GPS_ParseGPGGA(nmea);
} else if (strncmp(nmea, "$GPRMC", 6) == 0 || strncmp(nmea, "$GNRMC", 6) == 0) {
GPS_ParseGPRMC(nmea);
} }
// 可以添加其他NMEA语句的解析如GPRMC等
} }
/** /**
@ -347,12 +365,56 @@ static void GPS_ParseGPGGA(char *nmea)
} }
} }
/**
* @brief GPRMC / GNRMC
*
* GPRMC :
* $GPRMC,HHMMSS.ss,A,lat,N,lon,E,speed,course,DDMMYY,mv,mvE*CS
* field 0: $GPRMC
* field 1: HHMMSS.ss
* field 2: A= V=
* field 9: DDMMYY
*/
static void GPS_ParseGPRMC(char *nmea)
{
char *token;
char *saveptr;
int field_index = 0;
uint8_t status_valid = 0;
token = strtok_r(nmea, ",", &saveptr);
while (token != NULL) {
switch (field_index) {
case 2: // 状态
status_valid = (token[0] == 'A') ? 1 : 0;
break;
case 9: // 日期 DDMMYY
if (status_valid && strlen(token) >= 6) {
char dd[3] = {token[0], token[1], '\0'};
char mm[3] = {token[2], token[3], '\0'};
char yy[3] = {token[4], token[5], '\0'};
gps_data.date.day = (uint8_t)atoi(dd);
gps_data.date.month = (uint8_t)atoi(mm);
gps_data.date.year = 2000u + (uint16_t)atoi(yy);
gps_data.date_valid = 1;
}
break;
default:
break;
}
token = strtok_r(NULL, ",", &saveptr);
field_index++;
}
}
/** /**
* @brief GPS坐标格式转换为十进制度 * @brief GPS坐标格式转换为十进制度
* @param coord: GPS坐标字符串 (ddmm.mmmm dddmm.mmmm) * @param coord: GPS坐标字符串 (ddmm.mmmm dddmm.mmmm)
* @param direction: (N/S/E/W) * @param direction: (N/S/E/W)
* @retval * @retval
* *
* : * :
* : "4807.038", 'N' * : "4807.038", 'N'
* : 48.1173 () * : 48.1173 ()

View File

@ -54,6 +54,15 @@ typedef struct {
uint16_t millisec; // 毫秒 uint16_t millisec; // 毫秒
} GPS_Time_t; } GPS_Time_t;
/**
* @brief GPS日期结构体 GPRMC/GNRMC
*/
typedef struct {
uint8_t day; // 日 (1~31)
uint8_t month; // 月 (1~12)
uint16_t year; // 年 (完整年份,如 2025)
} GPS_Date_t;
/** /**
* @brief GPS位置结构体 * @brief GPS位置结构体
*/ */
@ -72,9 +81,11 @@ typedef struct {
* @brief GPS数据结构体 * @brief GPS数据结构体
*/ */
typedef struct { typedef struct {
GPS_Time_t time; // GPS时间 GPS_Time_t time; // GPS时间来自GPGGA
GPS_Date_t date; // GPS日期来自GPRMC
GPS_Position_t position; // GPS位置 GPS_Position_t position; // GPS位置
uint8_t data_valid; // 数据有效标志 (1=有效, 0=无效) uint8_t data_valid; // 时间与位置有效GPGGA fix > 0
uint8_t date_valid; // 日期有效GPRMC 已收到且 status='A'
uint32_t last_update_tick; // 最后更新时间戳 uint32_t last_update_tick; // 最后更新时间戳
} GPS_Data_t; } GPS_Data_t;
@ -143,6 +154,14 @@ void GPS_UART_RxCpltCallback(UART_HandleTypeDef *huart);
*/ */
void GPS_UART_IdleCallback(UART_HandleTypeDef *huart); void GPS_UART_IdleCallback(UART_HandleTypeDef *huart);
/**
* @brief GPS日期字符串 YYYYMMDD
* @param buffer: 9
* @param size:
* @retval None
*/
void GPS_GetDateString(char *buffer, uint16_t size);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View File

@ -91,30 +91,33 @@ LTC2508_StatusTypeDef LTC2508_TriggerDmaRead(void)
current_buffer->dma_complete_count = 0; current_buffer->dma_complete_count = 0;
current_buffer->timestamp = HAL_GetTick(); current_buffer->timestamp = HAL_GetTick();
// SPI2 和 SPI3 作为从机只接收 // SPI2 和 SPI3 作为从机只接收;必须在 SPI1主机启动时钟前挂好 DMA否则从机会丢失前几个时钟
if (HAL_SPI_Receive_DMA(g_hspi2, (uint8_t*)current_buffer->data[1], LTC2508_DATA_LEN) != HAL_OK) if (HAL_SPI_Receive_DMA(g_hspi2, (uint8_t*)current_buffer->data[1], LTC2508_DATA_LEN) != HAL_OK)
{ {
current_buffer->state = LTC2508_BUFFER_EMPTY; current_buffer->state = LTC2508_BUFFER_EMPTY;
g_ltc2508_stats.dma_error_count++; g_ltc2508_stats.dma_error_count++;
g_ltc2508_stats.error_count++; g_ltc2508_stats.error_count++;
g_ltc2508_stats.last_error = LTC2508_ERROR_DMA; g_ltc2508_stats.last_error = LTC2508_ERROR_DMA;
// return 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) if (HAL_SPI_Receive_DMA(g_hspi3, (uint8_t*)current_buffer->data[2], LTC2508_DATA_LEN) != HAL_OK)
{ {
current_buffer->state = LTC2508_BUFFER_EMPTY; current_buffer->state = LTC2508_BUFFER_EMPTY;
HAL_SPI_DMAStop(g_hspi2);
g_ltc2508_stats.dma_error_count++; g_ltc2508_stats.dma_error_count++;
g_ltc2508_stats.error_count++; g_ltc2508_stats.error_count++;
g_ltc2508_stats.last_error = LTC2508_ERROR_DMA; g_ltc2508_stats.last_error = LTC2508_ERROR_DMA;
// return LTC2508_ERROR_DMA; return LTC2508_ERROR_DMA;
} }
// SPI1 作为主机收发,先发一个 dummy 数据触发时钟 // staticDMA 传输异步完成,必须保证缓冲区生命周期覆盖整个传输过程
uint16_t dummy_tx[LTC2508_DATA_LEN] = {0}; // 可以是任意值 static uint16_t s_dummy_tx[LTC2508_DATA_LEN] = {0};
if (HAL_SPI_TransmitReceive_DMA(g_hspi1, (uint8_t*)dummy_tx, (uint8_t*)current_buffer->data[0], LTC2508_DATA_LEN) != HAL_OK) 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; 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.dma_error_count++;
g_ltc2508_stats.error_count++; g_ltc2508_stats.error_count++;
g_ltc2508_stats.last_error = LTC2508_ERROR_DMA; g_ltc2508_stats.last_error = LTC2508_ERROR_DMA;

View File

@ -7,6 +7,11 @@ static GPIO_TypeDef* g_de_re_port = NULL;
static uint16_t g_de_re_pin = 0; static uint16_t g_de_re_pin = 0;
volatile uint8_t g_rs485_tx_busy = 0; volatile uint8_t g_rs485_tx_busy = 0;
/* DMA 异步读取期间此缓冲区必须保持有效;
TIM2 ISR DMA */
#define RS485_TX_BUF_SIZE 64
static uint8_t s_tx_buf[RS485_TX_BUF_SIZE];
void RS485_Init(UART_HandleTypeDef *huart, GPIO_TypeDef* de_re_port, uint16_t de_re_pin) void RS485_Init(UART_HandleTypeDef *huart, GPIO_TypeDef* de_re_port, uint16_t de_re_pin)
{ {
g_huart_485 = huart; g_huart_485 = huart;
@ -26,11 +31,15 @@ HAL_StatusTypeDef RS485_SendData(uint8_t *pData, uint16_t Size)
return HAL_BUSY; // 上一次传输未完成,返回忙状态 return HAL_BUSY; // 上一次传输未完成,返回忙状态
} }
if (Size > RS485_TX_BUF_SIZE) return HAL_ERROR;
memcpy(s_tx_buf, pData, Size);
g_rs485_tx_busy = 1; // 标记为忙状态 g_rs485_tx_busy = 1; // 标记为忙状态
HAL_GPIO_WritePin(g_de_re_port, g_de_re_pin, GPIO_PIN_SET); // 设置为发送模式 HAL_GPIO_WritePin(g_de_re_port, g_de_re_pin, GPIO_PIN_SET); // 设置为发送模式
// 使用DMA非阻塞发送 // 使用DMA非阻塞发送
ret = HAL_UART_Transmit_DMA(g_huart_485, pData, Size); ret = HAL_UART_Transmit_DMA(g_huart_485, s_tx_buf, Size);
if (ret != HAL_OK) if (ret != HAL_OK)
{ {