Compare commits

..
4 Commits
Author SHA1 Message Date
zhoujie 29cc814384 ✨ 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经纬度/光泵),支持灵活配置
- 新增文件模式下的分页浏览功能(滑块/上一页/下一页)
- 优化实时模式下的光泵数据显示与表格列过滤
2026-06-14 23:06:25 +08:00
zhoujie 047ef08318 🐛 fix(optic_mag_driver): 修正光泵磁力计数据单位
- 将数据单位从 0.0001 nT 改为 0.001 nT
- 更新注释中的示例值以匹配新的单位换算
2026-06-08 21:30:32 +08:00
zhoujie bc37e14fba ✨ feat(optic_mag): 集成光泵磁力仪驱动并重构数据包架构
- 新增光泵磁力仪驱动模块,通过 USART2 中断接收 BCD 编码数据,采样率 115200bps
- 重构数据包架构:引入标准包与扩展包(含光泵数据)两种类型,通过帧头魔数区分
- 新增 DataPacketWithOptic_t、CorrectedDataPacketWithOptic_t 两种扩展数据包类型
- 数据存储改为通用字节流写入(方案Y),支持任意包类型混流存储
- 将编译期配置集中到 app_config.h,包括 UART 输出、SD 存储、GPS 位置等开关
- 移除 ADC_SYNC GPIO 引脚配置,释放 PA2 用于 USART2_TX
- 主循环 ProcessAdcData 改为按需选择数据包类型,光泵数据快照在 ADC 中断前完成
- 新增 USART2 错误回调处理,支持接收异常时自动恢复
2026-06-07 22:50:54 +08:00
zhoujie 4f8feccc06 ✨ feat(filesystem): 增强SD卡文件系统挂载与格式化逻辑
- 添加工作缓冲区以支持带参数的格式化操作
- 重构挂载逻辑,在挂载成功后验证文件系统类型和簇大小是否符合目标格式(FAT32,簇大小32KB)
- 将格式化条件判断与执行分离,提高代码可读性
- 使用宏定义目标簇大小和扇区大小参数,便于维护

🔧 chore(scripts): 重命名脚本文件并添加压缩包

- 将`atem_parse.py`重命名为`ATEMParse.py`以遵循命名规范
- 添加`ATEMParse.7z`压缩包文件
2026-03-07 13:54:52 +08:00
28 changed files with 2087 additions and 1142 deletions
+49
View File
@@ -0,0 +1,49 @@
# STM_ATEM_F405 项目说明
## 项目概述
STM32F405 航空电磁接收机固件,基于 STM32CubeIDE + HAL 库开发。
**核心功能:** ADC 采集(LTC2508)→ 数据校正 → SD 卡存储 / UART 输出
## 目录结构
- `Core/` — CubeMX 生成代码(main.c、外设初始化),**不要手动修改生成区域**
- `User/` — 业务逻辑(驱动、数据包、存储、GPS、配置管理等)
- `Drivers/` — HAL 驱动库(不修改)
- `Middlewares/` — FatFs、USB(不修改)
## 关键模块
| 文件 | 职责 |
|------|------|
| `ltc2508_driver` | SPI ADC 采集驱动 |
| `data_packet` | 数据包格式定义与封包 |
| `data_storage` | FatFs SD 卡文件写入 |
| `correction` | ADC 校正参数应用 |
| `app_config` | 编译期配置(UART 输出、SD 存储开关)|
| `config_manager` | 配置运行时访问层,默认值来自 `app_config.h` |
| `gps_driver` | GPS NMEA 解析(UART)|
| `rs485_driver` | RS485 通信 |
| `optic_mag_driver` | 光学磁传感器 |
| `system_monitor` | 系统状态统计与诊断 |
## 通信接口
| 接口 | 用途 | 波特率 | 备注 |
|------|------|--------|------|
| USART1 (PA9/PA10) | RS485 数据输出 | 2 000 000 | TX DMA;DE/RE 控制脚 PC7 |
| USART2 (PA3) | 光泵磁力仪接收 | 115 200 | 仅 RX,DMA 循环;PA2 已改为普通 GPIO |
| USART3 (PB10/PB11) | GPS NMEA 接收 | 115 200 | TX/RX,单字节中断接收 |
| SPI1/2/3 | LTC2508 ADC 采集 | — | 三路 SPI 同时驱动三片 ADC |
| SDIO | SD 卡(FatFs) | — | 与 USB MSC 共用,采样期间独占 |
| USB | MSC 大容量存储 | — | 供 PC 直接访问 SD 卡 |
## 开发规范
- 每次修改代码后,判断是否影响模块职责、接口用途、配置项或注意事项,若有则同步更新 CLAUDE.md 对应部分
- 业务代码只放 `User/` 目录;CubeMX 生成区域(`USER CODE BEGIN/END`)内写用户代码
- 函数命名:`模块名_功能()` 如 `DataStorage_StartRecording()`
- 全局变量加 `g_` 前缀,静态变量加 `s_` 前缀
- 不添加无意义注释;注释用中文说明 **为什么**,不说明是什么
## 注意事项
- 修改 `.ioc` 文件后重新生成代码会覆盖 `Core/` 文件,需手动恢复用户代码
- USB MSC 与 FatFs 共享 SD 卡,采样期间需卸载再挂载
- 配置在 `User/app_config.h` 中以 `#define` 编译期确定;若需恢复 CONFIG.TXT 运行时加载,在 `main.c` 挂载 SD 后重新调用 `Config_Load()` 即可
-2
View File
@@ -60,8 +60,6 @@ void Error_Handler(void);
#define ADC_DRY_Pin GPIO_PIN_1 #define ADC_DRY_Pin GPIO_PIN_1
#define ADC_DRY_GPIO_Port GPIOA #define ADC_DRY_GPIO_Port GPIOA
#define ADC_DRY_EXTI_IRQn EXTI1_IRQn #define ADC_DRY_EXTI_IRQn EXTI1_IRQn
#define ADC_SYNC_Pin GPIO_PIN_2
#define ADC_SYNC_GPIO_Port GPIOA
#define RS485_CTL_Pin GPIO_PIN_7 #define RS485_CTL_Pin GPIO_PIN_7
#define RS485_CTL_GPIO_Port GPIOC #define RS485_CTL_GPIO_Port GPIOC
+1
View File
@@ -60,6 +60,7 @@ void DMA1_Stream0_IRQHandler(void);
void DMA1_Stream3_IRQHandler(void); void DMA1_Stream3_IRQHandler(void);
void TIM2_IRQHandler(void); void TIM2_IRQHandler(void);
void USART1_IRQHandler(void); void USART1_IRQHandler(void);
void USART2_IRQHandler(void);
void USART3_IRQHandler(void); void USART3_IRQHandler(void);
void SDIO_IRQHandler(void); void SDIO_IRQHandler(void);
void DMA2_Stream0_IRQHandler(void); void DMA2_Stream0_IRQHandler(void);
+3
View File
@@ -34,6 +34,8 @@ extern "C" {
extern UART_HandleTypeDef huart1; extern UART_HandleTypeDef huart1;
extern UART_HandleTypeDef huart2;
extern UART_HandleTypeDef huart3; extern UART_HandleTypeDef huart3;
/* USER CODE BEGIN Private defines */ /* USER CODE BEGIN Private defines */
@@ -41,6 +43,7 @@ extern UART_HandleTypeDef huart3;
/* USER CODE END Private defines */ /* USER CODE END Private defines */
void MX_USART1_UART_Init(void); void MX_USART1_UART_Init(void);
void MX_USART2_UART_Init(void);
void MX_USART3_UART_Init(void); void MX_USART3_UART_Init(void);
/* USER CODE BEGIN Prototypes */ /* USER CODE BEGIN Prototypes */
-10
View File
@@ -51,9 +51,6 @@ void MX_GPIO_Init(void)
__HAL_RCC_GPIOB_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_GPIOD_CLK_ENABLE(); __HAL_RCC_GPIOD_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(ADC_SYNC_GPIO_Port, ADC_SYNC_Pin, GPIO_PIN_RESET);
/*Configure GPIO pin Output Level */ /*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(RS485_CTL_GPIO_Port, RS485_CTL_Pin, GPIO_PIN_RESET); HAL_GPIO_WritePin(RS485_CTL_GPIO_Port, RS485_CTL_Pin, GPIO_PIN_RESET);
@@ -69,13 +66,6 @@ void MX_GPIO_Init(void)
GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(ADC_DRY_GPIO_Port, &GPIO_InitStruct); HAL_GPIO_Init(ADC_DRY_GPIO_Port, &GPIO_InitStruct);
/*Configure GPIO pin : ADC_SYNC_Pin */
GPIO_InitStruct.Pin = ADC_SYNC_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(ADC_SYNC_GPIO_Port, &GPIO_InitStruct);
/*Configure GPIO pin : RS485_CTL_Pin */ /*Configure GPIO pin : RS485_CTL_Pin */
GPIO_InitStruct.Pin = RS485_CTL_Pin; GPIO_InitStruct.Pin = RS485_CTL_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
+114 -95
View File
@@ -37,6 +37,7 @@
#include "system_monitor.h" #include "system_monitor.h"
#include "config_manager.h" #include "config_manager.h"
#include "gps_driver.h" #include "gps_driver.h"
#include "optic_mag_driver.h"
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
/* USER CODE END Includes */ /* USER CODE END Includes */
@@ -48,15 +49,10 @@
/* Private define ------------------------------------------------------------*/ /* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */ /* USER CODE BEGIN PD */
// 监控功能宏开关(统一控制串口输出和文件存储) // 以下宏从 app_config.h 引入,此处仅做别名映射,保持内部代码不变
#define ENABLE_SYSTEM_MONITOR 1 // 系统监控开关 #define ENABLE_SYSTEM_MONITOR CFG_ENABLE_SYSTEM_MONITOR
#define DEBUG_OUTPUT_INTERVAL_MS 30000 // 调试输出间隔(毫秒) #define DEBUG_OUTPUT_INTERVAL_MS CFG_DEBUG_OUTPUT_INTERVAL_MS
#define MONITOR_SAVE_INTERVAL_MS 30000 // 监控状态保存间隔(毫秒) - 30秒 #define MONITOR_SAVE_INTERVAL_MS CFG_MONITOR_SAVE_INTERVAL_MS
// 数据输出模式选择(运行时配置,从SD卡加载)
// 注意:DATA_OUTPUT_MODE_UART 和 DATA_OUTPUT_MODE_STORAGE 已改为运行时配置
// 使用 Config_IsUartOutputEnabled() 和 Config_IsStorageEnabled() 来检查状态
// 配置文件:0:/CONFIG.TXT
/* USER CODE END PD */ /* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/ /* Private macro -------------------------------------------------------------*/
@@ -73,14 +69,17 @@ extern SPI_HandleTypeDef hspi2;
extern SPI_HandleTypeDef hspi3; extern SPI_HandleTypeDef hspi3;
extern TIM_HandleTypeDef htim2; extern TIM_HandleTypeDef htim2;
extern UART_HandleTypeDef huart1; extern UART_HandleTypeDef huart1;
extern UART_HandleTypeDef huart2;
extern UART_HandleTypeDef huart3; extern UART_HandleTypeDef huart3;
// 校正参数 // 校正参数
CorrectionParams_t g_correction_params; CorrectionParams_t g_correction_params;
// 数据包 // 数据包(标准包 / 扩展包各一份,ProcessAdcData按需选用)
DataPacket_t g_data_packet; DataPacket_t g_data_packet;
DataPacketWithOptic_t g_data_packet_with_optic;
CorrectedDataPacket_t g_corrected_packet; CorrectedDataPacket_t g_corrected_packet;
CorrectedDataPacketWithGPS_t g_corrected_packet_with_gps; // 带GPS信息的数据包 CorrectedDataPacketWithGPS_t g_corrected_packet_with_gps;
CorrectedDataPacketWithOptic_t g_corrected_packet_with_optic;
// 数据存储句柄 // 数据存储句柄
DataStorageHandle_t g_data_storage; DataStorageHandle_t g_data_storage;
// 系统状态 // 系统状态
@@ -151,110 +150,99 @@ static void StopRecording(void)
*/ */
static void ProcessAdcData(void) static void ProcessAdcData(void)
{ {
// 检查ADC数据是否准备就绪
LTC2508_BufferTypeDef *ready_buffer = NULL; LTC2508_BufferTypeDef *ready_buffer = NULL;
if (LTC2508_GetReadyBuffer(&ready_buffer) == LTC2508_OK && ready_buffer != NULL) if (LTC2508_GetReadyBuffer(&ready_buffer) != LTC2508_OK || ready_buffer == NULL) return;
{
// 检查存储缓冲区是否可用(用于决定是否存储数据) // 快照光泵数据:USART2中断优先级(10) < TIM2优先级(3),此处不会被抢占,无需关中断
uint8_t optic_fresh = g_optic_mag_fresh;
uint32_t optic_val = g_optic_mag_value;
if (optic_fresh) g_optic_mag_fresh = 0;
// 检查存储缓冲区是否可用(取最大包长为基准)
uint8_t can_store_data = 0; uint8_t can_store_data = 0;
if (g_recording_enabled) { if (g_recording_enabled) {
uint32_t max_packet_size = sizeof(CorrectedDataPacketWithGPS_t); // 使用带GPS的数据包大小 can_store_data = DataStorage_IsBufferAvailable(&g_data_storage,
can_store_data = DataStorage_IsBufferAvailable(&g_data_storage, max_packet_size); sizeof(CorrectedDataPacketWithOptic_t));
} }
#if ENABLE_SYSTEM_MONITOR #if ENABLE_SYSTEM_MONITOR
SystemMonitor_IncrementSampleCount(); SystemMonitor_IncrementSampleCount();
#endif #endif
// 1. 从双缓冲区获取数据并合并 (高位16位在前) // 1. 合并三路ADC原始数据(高16位在前)
int32_t raw_adc[NUM_LTC2508]; int32_t raw_adc[NUM_LTC2508];
for (uint8_t i = 0; i < NUM_LTC2508; i++) { for (uint8_t i = 0; i < NUM_LTC2508; i++) {
raw_adc[i] = (int32_t)(((uint32_t)ready_buffer->data[i][0] << 16) | ready_buffer->data[i][1]); raw_adc[i] = (int32_t)(((uint32_t)ready_buffer->data[i][0] << 16) | ready_buffer->data[i][1]);
} }
// 2. 获取当前GPS数据 // 2. 获取GPS数据(时间戳始终使用,位置字段受ENABLE_GPS_POSITION控制)
GPS_Data_t current_gps_data; GPS_Data_t current_gps_data;
uint8_t gps_valid = GPS_GetData(&current_gps_data); uint8_t gps_valid = GPS_GetData(&current_gps_data);
uint32_t gps_time = gps_valid ? (current_gps_data.time.hour * 10000u +
current_gps_data.time.minute * 100u +
current_gps_data.time.second) : 0u;
float lat = (float)current_gps_data.position.latitude;
float lon = (float)current_gps_data.position.longitude;
float alt = (float)current_gps_data.position.altitude;
// 3. 决定发送哪种包(校正×是否有光泵 = 4种组合)
const uint8_t *send_ptr;
uint16_t send_size;
// 3. 应用校正算法
CorrectionResult_t correction_result; CorrectionResult_t correction_result;
uint8_t correction_applied = 0;
if (g_correction_params.params_valid && if (g_correction_params.params_valid &&
Apply_Correction(raw_adc[0], raw_adc[1], raw_adc[2], Apply_Correction(raw_adc[0], raw_adc[1], raw_adc[2],
&correction_result, &g_correction_params) == HAL_OK) { &correction_result, &g_correction_params) == HAL_OK) {
// 4a. 打包校正后的数据(带GPS关键信息:仅经纬度) if (optic_fresh) {
// float lat = gps_valid ? (float)current_gps_data.position.latitude : 0.0f; PackCorrectedDataWithOptic(&g_corrected_packet_with_optic,
// float lon = gps_valid ? (float)current_gps_data.position.longitude : 0.0f; correction_result.corrected_x, correction_result.corrected_y,
// float alt = gps_valid ? (float)current_gps_data.position.altitude : 0.0f; correction_result.corrected_z, gps_time, lat, lon, alt, optic_val);
float lat = (float)current_gps_data.position.latitude; send_ptr = (const uint8_t *)&g_corrected_packet_with_optic;
float lon = (float)current_gps_data.position.longitude; send_size = sizeof(CorrectedDataPacketWithOptic_t);
float alt = (float)current_gps_data.position.altitude; } else {
uint32_t gps_time = gps_valid ? (current_gps_data.time.hour * 10000 +
current_gps_data.time.minute * 100 +
current_gps_data.time.second) : 0;
PackCorrectedDataWithGPS(&g_corrected_packet_with_gps, PackCorrectedDataWithGPS(&g_corrected_packet_with_gps,
correction_result.corrected_x, correction_result.corrected_x, correction_result.corrected_y,
correction_result.corrected_y, correction_result.corrected_z, gps_time, lat, lon, alt);
correction_result.corrected_z, send_ptr = (const uint8_t *)&g_corrected_packet_with_gps;
gps_time, send_size = sizeof(CorrectedDataPacketWithGPS_t);
lat,
lon,
alt);
correction_applied = 1;
// 发送校正后的数据包到串口(运行时配置)
if (Config_IsUartOutputEnabled()) {
RS485_SendData((uint8_t*)&g_corrected_packet_with_gps, sizeof(CorrectedDataPacketWithGPS_t));
} }
} else { } else {
// 4b. 校正失败或未启用,使用原始数据 if (optic_fresh) {
// float lat = gps_valid ? (float)current_gps_data.position.latitude : 0.0f; PackDataWithOptic(&g_data_packet_with_optic,
// float lon = gps_valid ? (float)current_gps_data.position.longitude : 0.0f; raw_adc[0], raw_adc[1], raw_adc[2],
// float alt = gps_valid ? (float)current_gps_data.position.altitude : 0.0f; gps_time, lat, lon, alt, optic_val);
float lat = (float)current_gps_data.position.latitude; send_ptr = (const uint8_t *)&g_data_packet_with_optic;
float lon = (float)current_gps_data.position.longitude; send_size = sizeof(DataPacketWithOptic_t);
float alt = (float)current_gps_data.position.altitude; } else {
uint32_t gps_time = gps_valid ? (current_gps_data.time.hour * 10000 + PackData(&g_data_packet,
current_gps_data.time.minute * 100 + raw_adc[0], raw_adc[1], raw_adc[2],
current_gps_data.time.second) : 0; gps_time, lat, lon, alt);
send_ptr = (const uint8_t *)&g_data_packet;
send_size = sizeof(DataPacket_t);
}
}
PackData(&g_data_packet, raw_adc[0], raw_adc[1], raw_adc[2], gps_time, lat, lon, alt); // 4. 发送到RS485串口
// 发送原始数据包到串口(运行时配置)
if (Config_IsUartOutputEnabled()) { if (Config_IsUartOutputEnabled()) {
RS485_SendData((uint8_t*)&g_data_packet, sizeof(DataPacket_t)); RS485_SendData((uint8_t *)send_ptr, send_size);
}
} }
// 6. 存储数据到SD卡 (如果启用记录且缓冲区可用,运行时配置) // 5. 存储到SD卡(方案Y:直接写原始字节流,与串口格式一致)
if (Config_IsStorageEnabled() && g_recording_enabled) { if (Config_IsStorageEnabled() && g_recording_enabled) {
if (can_store_data) { if (can_store_data) {
if (correction_applied) { DataStorage_WriteRawBytes(&g_data_storage, send_ptr, send_size);
// 存储校正后的数据(带GPS信息)
DataStorage_WriteCorrectedData(&g_data_storage, (CorrectedDataPacket_t*)&g_corrected_packet_with_gps);
} else { } else {
// 存储原始数据
DataStorage_WriteData(&g_data_storage, &g_data_packet);
}
} else {
// 缓冲区满,数据被丢弃
#if ENABLE_SYSTEM_MONITOR #if ENABLE_SYSTEM_MONITOR
SystemMonitor_ReportDataDropped(); SystemMonitor_ReportDataDropped();
#endif #endif
} }
} }
// 7. 释放已处理的缓冲区 // 6. 释放已处理的缓冲区
LTC2508_ReleaseBuffer(LTC2508_GetCurrentReadBuffer()); LTC2508_ReleaseBuffer(LTC2508_GetCurrentReadBuffer());
} else {
}
} }
/** /**
@@ -411,6 +399,7 @@ static HAL_StatusTypeDef MountFileSystemForSampling(void)
extern FATFS SDFatFS; extern FATFS SDFatFS;
extern char SDPath[4]; extern char SDPath[4];
extern SD_HandleTypeDef hsd; extern SD_HandleTypeDef hsd;
BYTE work[_MAX_SS];
if (g_fatfs_mounted_for_sampling) { if (g_fatfs_mounted_for_sampling) {
return HAL_OK; // 已经挂载 return HAL_OK; // 已经挂载
@@ -422,22 +411,42 @@ static HAL_StatusTypeDef MountFileSystemForSampling(void)
return HAL_ERROR; return HAL_ERROR;
} }
// FRESULT format_result = f_mkfs(SDPath, FM_FAT32, 0, NULL, 0); // 目标格式参数:FAT32,簇大小32768字节(512字节/扇区 × 64扇区/簇)
#define TARGET_CLUSTER_SIZE_BYTES 32768U
#define TARGET_SECTOR_SIZE_BYTES 512U
#define TARGET_SECTORS_PER_CLUSTER (TARGET_CLUSTER_SIZE_BYTES / TARGET_SECTOR_SIZE_BYTES) // 64
// 尝试挂载文件系统 // 尝试挂载文件系统
FRESULT mount_result = f_mount(&SDFatFS, SDPath, 1); FRESULT mount_result = f_mount(&SDFatFS, SDPath, 1);
if (mount_result != FR_OK) { uint8_t need_format = 0;
if (mount_result == FR_NO_FILESYSTEM)
{
DebugOutput_SendString("No filesystem found, formatting...\r\n");
// 格式化为FAT32 if (mount_result == FR_OK) {
FRESULT format_result = f_mkfs(SDPath, FM_FAT32, 0, NULL, 0); // 挂载成功,验证文件系统类型和簇大小是否符合目标格式
if (SDFatFS.fs_type != FS_FAT32) {
DebugOutput_SendString("Not FAT32, reformatting...\r\n");
f_mount(NULL, SDPath, 0);
need_format = 1;
} else if (SDFatFS.csize != TARGET_SECTORS_PER_CLUSTER) {
DebugOutput_SendString("Cluster size mismatch, reformatting...\r\n");
f_mount(NULL, SDPath, 0);
need_format = 1;
}
} else if (mount_result == FR_NO_FILESYSTEM) {
DebugOutput_SendString("No filesystem found, formatting...\r\n");
need_format = 1;
} else {
DebugOutput_SendString("Mount failed with other error\r\n");
return HAL_ERROR;
}
if (need_format) {
FRESULT format_result = f_mkfs(SDPath, FM_FAT32, TARGET_CLUSTER_SIZE_BYTES, work, sizeof(work));
if (format_result == FR_OK) { if (format_result == FR_OK) {
DebugOutput_SendString("Format successful, remounting...\r\n"); DebugOutput_SendString("Format successful, remounting...\r\n");
mount_result = f_mount(&SDFatFS, SDPath, 1); mount_result = f_mount(&SDFatFS, SDPath, 1);
if (mount_result != FR_OK) { if (mount_result != FR_OK) {
DebugOutput_SendString("Remount after format failed\r\n"); DebugOutput_SendString("Remount after format failed\r\n");
return HAL_ERROR; return HAL_ERROR;
@@ -446,10 +455,6 @@ static HAL_StatusTypeDef MountFileSystemForSampling(void)
DebugOutput_SendString("Format failed\r\n"); DebugOutput_SendString("Format failed\r\n");
return HAL_ERROR; return HAL_ERROR;
} }
} else {
DebugOutput_SendString("Mount failed with other error\r\n");
return HAL_ERROR;
}
} }
g_fatfs_mounted_for_sampling = 1; g_fatfs_mounted_for_sampling = 1;
@@ -519,6 +524,7 @@ int main(void)
MX_USART3_UART_Init(); MX_USART3_UART_Init();
MX_TIM2_Init(); MX_TIM2_Init();
MX_TIM1_Init(); MX_TIM1_Init();
MX_USART2_UART_Init();
/* USER CODE BEGIN 2 */ /* USER CODE BEGIN 2 */
// 初始化系统监控 // 初始化系统监控
@@ -526,9 +532,12 @@ int main(void)
SystemMonitor_Init(); SystemMonitor_Init();
#endif #endif
// 初始化调试输出 // 初始化调试输出(含GPS)
DebugOutput_Init(); DebugOutput_Init();
// 初始化光泵磁力仪接收(USART2 RX,115200)
OpticMag_Init(&huart2);
// 初始化配置管理器(设置默认值) // 初始化配置管理器(设置默认值)
Config_Init(); Config_Init();
@@ -545,13 +554,9 @@ int main(void)
if (!g_usb_connected) { if (!g_usb_connected) {
// USB未连接,挂载文件系统用于数据采集 // USB未连接,挂载文件系统用于数据采集
if (MountFileSystemForSampling() == HAL_OK) { if (MountFileSystemForSampling() == HAL_OK) {
// 从SD卡加载配置 #if CFG_LOAD_FROM_FILE
if (Config_Load() == HAL_OK) { Config_Load();
DebugOutput_SendString("Config loaded from SD card\r\n"); #endif
} else {
DebugOutput_SendString("Using default config\r\n");
}
// 初始化数据存储 // 初始化数据存储
if (DataStorage_Init(&g_data_storage) == HAL_OK) { if (DataStorage_Init(&g_data_storage) == HAL_OK) {
// 开始数据记录(如果存储功能已启用) // 开始数据记录(如果存储功能已启用)
@@ -570,9 +575,10 @@ int main(void)
} }
} }
// #define NEED_FORMAT_SD
#ifdef NEED_FORMAT_SD #ifdef NEED_FORMAT_SD
// Raw_Hardware_Test(); // Raw_Hardware_Test();
// SDNAND_ForceFormat_and_Mount(); SDNAND_ForceFormat_and_Mount();
Run_SDNAND_SpeedTest_V2(); Run_SDNAND_SpeedTest_V2();
while(1) while(1)
{ {
@@ -794,8 +800,21 @@ void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{ {
if (huart == &huart3) { if (huart == &huart3) {
// GPS数据接收回调
GPS_UART_RxCpltCallback(huart); GPS_UART_RxCpltCallback(huart);
} else if (huart == &huart2) {
OpticMag_UART_RxCpltCallback(huart);
}
}
/**
* @brief UART错误回调函数
* @param huart: UART句柄指针
* @retval None
*/
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
{
if (huart == &huart2) {
OpticMag_UART_ErrorCallback(huart);
} }
} }
+16 -1
View File
@@ -25,6 +25,7 @@
#include "ltc2508_driver.h" #include "ltc2508_driver.h"
#include "rs485_driver.h" #include "rs485_driver.h"
#include "gps_driver.h" #include "gps_driver.h"
#include "optic_mag_driver.h"
/* USER CODE END Includes */ /* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/ /* Private typedef -----------------------------------------------------------*/
@@ -69,12 +70,12 @@ extern DMA_HandleTypeDef hdma_spi3_rx;
extern TIM_HandleTypeDef htim2; extern TIM_HandleTypeDef htim2;
extern DMA_HandleTypeDef hdma_usart1_tx; extern DMA_HandleTypeDef hdma_usart1_tx;
extern UART_HandleTypeDef huart1; extern UART_HandleTypeDef huart1;
extern UART_HandleTypeDef huart2;
extern UART_HandleTypeDef huart3; extern UART_HandleTypeDef huart3;
/* USER CODE BEGIN EV */ /* USER CODE BEGIN EV */
extern SPI_HandleTypeDef hspi1; extern SPI_HandleTypeDef hspi1;
extern SPI_HandleTypeDef hspi2; extern SPI_HandleTypeDef hspi2;
extern SPI_HandleTypeDef hspi3; extern SPI_HandleTypeDef hspi3;
extern UART_HandleTypeDef huart1;
/* USER CODE END EV */ /* USER CODE END EV */
/******************************************************************************/ /******************************************************************************/
@@ -285,6 +286,20 @@ void USART1_IRQHandler(void)
/* USER CODE END USART1_IRQn 1 */ /* USER CODE END USART1_IRQn 1 */
} }
/**
* @brief This function handles USART2 global interrupt.
*/
void USART2_IRQHandler(void)
{
/* USER CODE BEGIN USART2_IRQn 0 */
/* USER CODE END USART2_IRQn 0 */
HAL_UART_IRQHandler(&huart2);
/* USER CODE BEGIN USART2_IRQn 1 */
/* USER CODE END USART2_IRQn 1 */
}
/** /**
* @brief This function handles USART3 global interrupt. * @brief This function handles USART3 global interrupt.
*/ */
+84
View File
@@ -25,6 +25,7 @@
/* USER CODE END 0 */ /* USER CODE END 0 */
UART_HandleTypeDef huart1; UART_HandleTypeDef huart1;
UART_HandleTypeDef huart2;
UART_HandleTypeDef huart3; UART_HandleTypeDef huart3;
DMA_HandleTypeDef hdma_usart1_tx; DMA_HandleTypeDef hdma_usart1_tx;
@@ -56,6 +57,35 @@ void MX_USART1_UART_Init(void)
/* USER CODE END USART1_Init 2 */ /* USER CODE END USART1_Init 2 */
}
/* USART2 init function */
void MX_USART2_UART_Init(void)
{
/* USER CODE BEGIN USART2_Init 0 */
/* USER CODE END USART2_Init 0 */
/* USER CODE BEGIN USART2_Init 1 */
/* USER CODE END USART2_Init 1 */
huart2.Instance = USART2;
huart2.Init.BaudRate = 115200;
huart2.Init.WordLength = UART_WORDLENGTH_8B;
huart2.Init.StopBits = UART_STOPBITS_1;
huart2.Init.Parity = UART_PARITY_NONE;
huart2.Init.Mode = UART_MODE_RX;
huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart2.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart2) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN USART2_Init 2 */
/* USER CODE END USART2_Init 2 */
} }
/* USART3 init function */ /* USART3 init function */
@@ -137,6 +167,40 @@ void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
/* USER CODE END USART1_MspInit 1 */ /* USER CODE END USART1_MspInit 1 */
} }
else if(uartHandle->Instance==USART2)
{
/* USER CODE BEGIN USART2_MspInit 0 */
/* USER CODE END USART2_MspInit 0 */
/* USART2 clock enable */
__HAL_RCC_USART2_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/**USART2 GPIO Configuration
PA2 ------> USART2_TX
PA3 ------> USART2_RX
*/
GPIO_InitStruct.Pin = GPIO_PIN_2|GPIO_PIN_3;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF7_USART2;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* USART2 interrupt Init */
HAL_NVIC_SetPriority(USART2_IRQn, 7, 0);
HAL_NVIC_EnableIRQ(USART2_IRQn);
/* USER CODE BEGIN USART2_MspInit 1 */
/* 覆盖PA2为普通GPIO输出:CubeMX将其初始化为USART2_TX(AF7),
此处重新配置以供其他功能使用,PA3(RX)保持AF7不变 */
GPIO_InitStruct.Pin = GPIO_PIN_2;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.Alternate = 0;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* USER CODE END USART2_MspInit 1 */
}
else if(uartHandle->Instance==USART3) else if(uartHandle->Instance==USART3)
{ {
/* USER CODE BEGIN USART3_MspInit 0 */ /* USER CODE BEGIN USART3_MspInit 0 */
@@ -192,6 +256,26 @@ void HAL_UART_MspDeInit(UART_HandleTypeDef* uartHandle)
/* USER CODE END USART1_MspDeInit 1 */ /* USER CODE END USART1_MspDeInit 1 */
} }
else if(uartHandle->Instance==USART2)
{
/* USER CODE BEGIN USART2_MspDeInit 0 */
/* USER CODE END USART2_MspDeInit 0 */
/* Peripheral clock disable */
__HAL_RCC_USART2_CLK_DISABLE();
/**USART2 GPIO Configuration
PA2 ------> USART2_TX
PA3 ------> USART2_RX
*/
HAL_GPIO_DeInit(GPIOA, GPIO_PIN_2|GPIO_PIN_3);
/* USART2 interrupt Deinit */
HAL_NVIC_DisableIRQ(USART2_IRQn);
/* USER CODE BEGIN USART2_MspDeInit 1 */
/* USER CODE END USART2_MspDeInit 1 */
}
else if(uartHandle->Instance==USART3) else if(uartHandle->Instance==USART3)
{ {
/* USER CODE BEGIN USART3_MspDeInit 0 */ /* USER CODE BEGIN USART3_MspDeInit 0 */
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

+46 -40
View File
@@ -111,9 +111,10 @@ Mcu.IP0=DMA
Mcu.IP1=FATFS Mcu.IP1=FATFS
Mcu.IP10=TIM2 Mcu.IP10=TIM2
Mcu.IP11=USART1 Mcu.IP11=USART1
Mcu.IP12=USART3 Mcu.IP12=USART2
Mcu.IP13=USB_DEVICE Mcu.IP13=USART3
Mcu.IP14=USB_OTG_FS Mcu.IP14=USB_DEVICE
Mcu.IP15=USB_OTG_FS
Mcu.IP2=NVIC Mcu.IP2=NVIC
Mcu.IP3=RCC Mcu.IP3=RCC
Mcu.IP4=SDIO Mcu.IP4=SDIO
@@ -122,42 +123,43 @@ Mcu.IP6=SPI2
Mcu.IP7=SPI3 Mcu.IP7=SPI3
Mcu.IP8=SYS Mcu.IP8=SYS
Mcu.IP9=TIM1 Mcu.IP9=TIM1
Mcu.IPNb=15 Mcu.IPNb=16
Mcu.Name=STM32F405RGTx Mcu.Name=STM32F405RGTx
Mcu.Package=LQFP64 Mcu.Package=LQFP64
Mcu.Pin0=PH0-OSC_IN Mcu.Pin0=PH0-OSC_IN
Mcu.Pin1=PH1-OSC_OUT Mcu.Pin1=PH1-OSC_OUT
Mcu.Pin10=PB13 Mcu.Pin10=PB11
Mcu.Pin11=PB15 Mcu.Pin11=PB13
Mcu.Pin12=PC7 Mcu.Pin12=PB15
Mcu.Pin13=PC8 Mcu.Pin13=PC7
Mcu.Pin14=PC9 Mcu.Pin14=PC8
Mcu.Pin15=PA8 Mcu.Pin15=PC9
Mcu.Pin16=PA9 Mcu.Pin16=PA8
Mcu.Pin17=PA10 Mcu.Pin17=PA9
Mcu.Pin18=PA11 Mcu.Pin18=PA10
Mcu.Pin19=PA12 Mcu.Pin19=PA11
Mcu.Pin2=PC3 Mcu.Pin2=PC3
Mcu.Pin20=PA13 Mcu.Pin20=PA12
Mcu.Pin21=PA14 Mcu.Pin21=PA13
Mcu.Pin22=PC10 Mcu.Pin22=PA14
Mcu.Pin23=PC11 Mcu.Pin23=PC10
Mcu.Pin24=PC12 Mcu.Pin24=PC11
Mcu.Pin25=PD2 Mcu.Pin25=PC12
Mcu.Pin26=PB3 Mcu.Pin26=PD2
Mcu.Pin27=PB5 Mcu.Pin27=PB3
Mcu.Pin28=VP_FATFS_VS_SDIO Mcu.Pin28=PB5
Mcu.Pin29=VP_SYS_VS_Systick Mcu.Pin29=VP_FATFS_VS_SDIO
Mcu.Pin3=PA1 Mcu.Pin3=PA1
Mcu.Pin30=VP_TIM2_VS_ClockSourceINT Mcu.Pin30=VP_SYS_VS_Systick
Mcu.Pin31=VP_USB_DEVICE_VS_USB_DEVICE_MSC_FS Mcu.Pin31=VP_TIM2_VS_ClockSourceINT
Mcu.Pin32=VP_USB_DEVICE_VS_USB_DEVICE_MSC_FS
Mcu.Pin4=PA2 Mcu.Pin4=PA2
Mcu.Pin5=PA5 Mcu.Pin5=PA3
Mcu.Pin6=PA6 Mcu.Pin6=PA5
Mcu.Pin7=PA7 Mcu.Pin7=PA6
Mcu.Pin8=PB10 Mcu.Pin8=PA7
Mcu.Pin9=PB11 Mcu.Pin9=PB10
Mcu.PinsNb=32 Mcu.PinsNb=33
Mcu.ThirdPartyNb=0 Mcu.ThirdPartyNb=0
Mcu.UserConstants= Mcu.UserConstants=
Mcu.UserName=STM32F405RGTx Mcu.UserName=STM32F405RGTx
@@ -175,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
@@ -185,6 +187,7 @@ NVIC.SVCall_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false
NVIC.SysTick_IRQn=true\:0\:0\:true\:false\:true\:false\:true\:false NVIC.SysTick_IRQn=true\:0\:0\:true\:false\:true\:false\:true\:false
NVIC.TIM2_IRQn=true\:3\:0\:true\:false\:true\:true\:true\:true NVIC.TIM2_IRQn=true\:3\:0\:true\:false\:true\:true\:true\:true
NVIC.USART1_IRQn=true\:11\:0\:true\:false\:true\:true\:true\:true NVIC.USART1_IRQn=true\:11\:0\:true\:false\:true\:true\:true\:true
NVIC.USART2_IRQn=true\:7\:0\:true\:false\:true\:true\:true\:true
NVIC.USART3_IRQn=true\:15\:0\:true\:false\:true\:true\:true\:true NVIC.USART3_IRQn=true\:15\:0\:true\:false\:true\:true\:true\:true
NVIC.UsageFault_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false NVIC.UsageFault_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false
PA1.GPIOParameters=GPIO_Label PA1.GPIOParameters=GPIO_Label
@@ -201,10 +204,10 @@ PA13.Mode=Serial_Wire
PA13.Signal=SYS_JTMS-SWDIO PA13.Signal=SYS_JTMS-SWDIO
PA14.Mode=Serial_Wire PA14.Mode=Serial_Wire
PA14.Signal=SYS_JTCK-SWCLK PA14.Signal=SYS_JTCK-SWCLK
PA2.GPIOParameters=GPIO_Label PA2.Mode=Asynchronous
PA2.GPIO_Label=ADC_SYNC PA2.Signal=USART2_TX
PA2.Locked=true PA3.Mode=Asynchronous
PA2.Signal=GPIO_Output PA3.Signal=USART2_RX
PA5.Mode=Full_Duplex_Master PA5.Mode=Full_Duplex_Master
PA5.Signal=SPI1_SCK PA5.Signal=SPI1_SCK
PA6.Mode=Full_Duplex_Master PA6.Mode=Full_Duplex_Master
@@ -289,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
@@ -299,7 +302,7 @@ ProjectManager.ToolChainLocation=
ProjectManager.UAScriptAfterPath= ProjectManager.UAScriptAfterPath=
ProjectManager.UAScriptBeforePath= ProjectManager.UAScriptBeforePath=
ProjectManager.UnderRoot=true ProjectManager.UnderRoot=true
ProjectManager.functionlistsort=1-SystemClock_Config-RCC-false-HAL-false,2-MX_GPIO_Init-GPIO-false-HAL-true,3-MX_DMA_Init-DMA-false-HAL-true,4-MX_SDIO_SD_Init-SDIO-false-HAL-true,5-MX_SPI1_Init-SPI1-false-HAL-true,6-MX_SPI2_Init-SPI2-false-HAL-true,7-MX_SPI3_Init-SPI3-false-HAL-true,8-MX_USART1_UART_Init-USART1-false-HAL-true,9-MX_FATFS_Init-FATFS-false-HAL-false,10-MX_USB_DEVICE_Init-USB_DEVICE-false-HAL-false,11-MX_USART3_UART_Init-USART3-false-HAL-true,12-MX_TIM2_Init-TIM2-false-HAL-true ProjectManager.functionlistsort=1-SystemClock_Config-RCC-false-HAL-false,2-MX_GPIO_Init-GPIO-false-HAL-true,3-MX_DMA_Init-DMA-false-HAL-true,4-MX_SDIO_SD_Init-SDIO-false-HAL-true,5-MX_SPI1_Init-SPI1-false-HAL-true,6-MX_SPI2_Init-SPI2-false-HAL-true,7-MX_SPI3_Init-SPI3-false-HAL-true,8-MX_USART1_UART_Init-USART1-false-HAL-true,9-MX_FATFS_Init-FATFS-false-HAL-false,10-MX_USB_DEVICE_Init-USB_DEVICE-false-HAL-false,11-MX_USART3_UART_Init-USART3-false-HAL-true,12-MX_TIM2_Init-TIM2-false-HAL-true,13-MX_TIM1_Init-TIM1-false-HAL-true,14-MX_USART2_UART_Init-USART2-false-HAL-true
RCC.48MHZClocksFreq_Value=48000000 RCC.48MHZClocksFreq_Value=48000000
RCC.AHBFreq_Value=168000000 RCC.AHBFreq_Value=168000000
RCC.APB1CLKDivider=RCC_HCLK_DIV4 RCC.APB1CLKDivider=RCC_HCLK_DIV4
@@ -371,6 +374,9 @@ TIM2.Prescaler=83
USART1.BaudRate=2000000 USART1.BaudRate=2000000
USART1.IPParameters=VirtualMode,BaudRate USART1.IPParameters=VirtualMode,BaudRate
USART1.VirtualMode=VM_ASYNC USART1.VirtualMode=VM_ASYNC
USART2.IPParameters=VirtualMode,Mode
USART2.Mode=MODE_RX
USART2.VirtualMode=VM_ASYNC
USART3.BaudRate=115200 USART3.BaudRate=115200
USART3.IPParameters=VirtualMode,BaudRate USART3.IPParameters=VirtualMode,BaudRate
USART3.VirtualMode=VM_ASYNC USART3.VirtualMode=VM_ASYNC
Binary file not shown.
+1095
View File
File diff suppressed because it is too large Load Diff
-686
View File
@@ -1,686 +0,0 @@
import sys
import time
import numpy as np
import pandas as pd
import serial
import serial.tools.list_ports
from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
QHBoxLayout, QPushButton, QFileDialog, QTableView,
QLabel, QRadioButton, QButtonGroup, QTabWidget,
QMessageBox, QHeaderView, QCheckBox, QComboBox, QSplitter)
from PyQt6.QtCore import Qt, QAbstractTableModel, QThread, pyqtSignal, QTimer
import pyqtgraph as pg
# ==========================================
# 1. 数据结构定义 (移除了 timestamp)
# ==========================================
RAW_DTYPE_V1 = np.dtype([
('start_byte', '<u4'),
('adc1', '<i4'), ('adc2', '<i4'), ('adc3', '<i4'),
('checksum', '<u2'), ('end_byte', '<u2')
])
CORRECTED_DTYPE_V1 = np.dtype([
('start_byte', '<u4'),
('corr_x', '<f4'), ('corr_y', '<f4'), ('corr_z', '<f4'),
('checksum', '<u2'), ('end_byte', '<u2')
])
# ⚠️ 注意: 移除了 timestamp,保留了 gps_altitude
RAW_DTYPE_V2 = np.dtype([
('start_byte', '<u4'),
('adc1', '<i4'), ('adc2', '<i4'), ('adc3', '<i4'),
('gps_time', '<u4'), ('gps_latitude', '<f4'), ('gps_longitude', '<f4'),
('gps_altitude', '<f4')
])
CORRECTED_DTYPE_V2 = np.dtype([
('start_byte', '<u4'),
('corr_x', '<f4'), ('corr_y', '<f4'), ('corr_z', '<f4'),
('gps_time', '<u4'), ('gps_latitude', '<f4'), ('gps_longitude', '<f4'),
('gps_altitude', '<f4')
])
# ==========================================
# 2. 高性能表格模型
# ==========================================
class BigDataModel(QAbstractTableModel):
def __init__(self, data):
super(BigDataModel, self).__init__()
self._data = data
def rowCount(self, parent=None):
return self._data.shape[0]
def columnCount(self, parent=None):
return self._data.shape[1]
def data(self, index, role=Qt.ItemDataRole.DisplayRole):
if index.isValid() and role == Qt.ItemDataRole.DisplayRole:
val = self._data.iloc[index.row(), index.column()]
if isinstance(val, (float, np.floating)):
return f"{val:.6f}"
return str(val)
return None
def headerData(self, col, orientation, role):
if orientation == Qt.Orientation.Horizontal and role == Qt.ItemDataRole.DisplayRole:
return self._data.columns[col]
return None
# ==========================================
# 3. 后台线程:串口数据接收 (带异常安全关闭)
# ==========================================
class SerialReaderThread(QThread):
data_received = pyqtSignal(np.ndarray)
error_occurred = pyqtSignal(str)
def __init__(self, port, baudrate, dtype):
super().__init__()
self.port = port
self.baudrate = baudrate
self.dtype = dtype
self.is_running = False
self.serial_port = None
def run(self):
self.is_running = True
buffer = bytearray()
packet_size = self.dtype.itemsize
start_marker = b'\xff\xff\xff\xff'
try:
self.serial_port = serial.Serial(self.port, self.baudrate, timeout=1)
while self.is_running:
if self.serial_port and self.serial_port.in_waiting > 0:
data = self.serial_port.read(self.serial_port.in_waiting)
buffer.extend(data)
packets = []
while True:
idx = buffer.find(start_marker)
if idx == -1:
buffer = buffer[-3:] if len(buffer) >= 3 else buffer
break
if len(buffer) >= idx + packet_size:
packet_bytes = buffer[idx : idx + packet_size]
packets.append(packet_bytes)
buffer = buffer[idx + packet_size :]
else:
buffer = buffer[idx:]
break
if packets:
combined_bytes = b''.join(packets)
parsed_arr = np.frombuffer(combined_bytes, dtype=self.dtype)
self.data_received.emit(parsed_arr)
except Exception as e:
if self.is_running:
self.error_occurred.emit(str(e))
finally:
self.stop()
def stop(self):
self.is_running = False
if self.serial_port:
try:
self.serial_port.cancel_read()
except Exception:
pass
try:
if self.serial_port.is_open:
self.serial_port.close()
except Exception:
pass
finally:
self.serial_port = None
# ==========================================
# 4. 后台线程:GPS 动态模拟输出 (10Hz)
# ==========================================
class GPSSimulatorThread(QThread):
error_occurred = pyqtSignal(str)
def __init__(self, port, baudrate):
super().__init__()
self.port = port
self.baudrate = baudrate
self.is_running = False
self.serial_port = None
def get_nmea_checksum(self, sentence):
calc_cksum = 0
for char in sentence:
calc_cksum ^= ord(char)
return f"{calc_cksum:02X}"
def run(self):
self.is_running = True
try:
self.serial_port = serial.Serial(self.port, self.baudrate, timeout=1)
current_lat = 39.9042
current_lon = 116.3972
current_alt = 43.0
lat_step = 0.00001
lon_step = 0.00001
alt_step = 0.05
while self.is_running:
start_time = time.perf_counter()
now = time.time()
gm_time = time.gmtime(now)
ms = int((now % 1) * 1000)
time_str = time.strftime("%H%M%S", gm_time) + f".{ms:03d}"
date_str = time.strftime("%d%m%y", gm_time)
current_lat += lat_step
current_lon += lon_step
current_alt += alt_step
lat_deg = int(abs(current_lat))
lat_min = (abs(current_lat) - lat_deg) * 60
lat_str = f"{lat_deg:02d}{lat_min:07.4f}"
lat_dir = "N" if current_lat >= 0 else "S"
lon_deg = int(abs(current_lon))
lon_min = (abs(current_lon) - lon_deg) * 60
lon_str = f"{lon_deg:03d}{lon_min:07.4f}"
lon_dir = "E" if current_lon >= 0 else "W"
speed_knots = "19.4"
course_true = "45.0"
gga_core = f"GPGGA,{time_str},{lat_str},{lat_dir},{lon_str},{lon_dir},1,08,1.0,{current_alt:.1f},M,0.0,M,,"
rmc_core = f"GPRMC,{time_str},A,{lat_str},{lat_dir},{lon_str},{lon_dir},{speed_knots},{course_true},{date_str},0.0,E"
gga_sentence = f"${gga_core}*{self.get_nmea_checksum(gga_core)}\r\n"
rmc_sentence = f"${rmc_core}*{self.get_nmea_checksum(rmc_core)}\r\n"
if self.serial_port and self.serial_port.is_open:
self.serial_port.write(gga_sentence.encode('ascii'))
self.serial_port.write(rmc_sentence.encode('ascii'))
elapsed = time.perf_counter() - start_time
sleep_time = 0.1 - elapsed
if sleep_time > 0:
time.sleep(sleep_time)
except Exception as e:
if self.is_running:
self.error_occurred.emit(str(e))
finally:
self.stop()
def stop(self):
self.is_running = False
if self.serial_port:
try:
self.serial_port.cancel_write()
except Exception:
pass
try:
if self.serial_port.is_open:
self.serial_port.close()
except Exception:
pass
finally:
self.serial_port = None
# ==========================================
# 5. 主窗口
# ==========================================
class DataAnalyzerUI(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("🚀 高性能数据分析工具 (含经纬度地图与海拔曲线)")
self.resize(1300, 900)
self.df = pd.DataFrame()
self.live_data_list = []
self.is_live_mode = False
self.curves = []
self.last_fps_time = 0
self.frame_count = 0
self.last_packet_count = 0
self.current_packet_count = 0
self.is_sim_running = False
self.sim_thread = None
pg.setConfigOptions(antialias=False)
self.init_ui()
self.plot_timer = QTimer()
self.plot_timer.timeout.connect(self.update_live_plot)
def init_ui(self):
main_widget = QWidget()
self.setCentralWidget(main_widget)
layout = QVBoxLayout(main_widget)
# --- 第一排工具栏 ---
top_bar1 = QHBoxLayout()
self.ver_group = QButtonGroup(self)
self.rb_v2 = QRadioButton("V2 (带GPS与海拔)")
self.rb_v2.setChecked(True)
self.ver_group.addButton(self.rb_v2)
self.mode_group = QButtonGroup(self)
self.rb_raw = QRadioButton("原始数据 (Raw)")
self.rb_corr = QRadioButton("校正数据 (Corr)")
self.rb_raw.setChecked(True)
self.mode_group.addButton(self.rb_raw)
self.mode_group.addButton(self.rb_corr)
btn_load = QPushButton("📂 打开文件")
btn_load.clicked.connect(self.load_file)
btn_export = QPushButton("💾 导出CSV")
btn_export.clicked.connect(self.export_csv)
self.chk_mouse_mode = QCheckBox("🔍 鼠标框选放大")
self.chk_mouse_mode.stateChanged.connect(self.toggle_mouse_mode)
btn_autoscale = QPushButton("⟲ 复位视图")
btn_autoscale.clicked.connect(self.reset_view)
top_bar1.addWidget(QLabel("版本:"))
top_bar1.addWidget(self.rb_v2)
top_bar1.addSpacing(15)
top_bar1.addWidget(QLabel("模式:"))
top_bar1.addWidget(self.rb_raw)
top_bar1.addWidget(self.rb_corr)
top_bar1.addSpacing(15)
top_bar1.addWidget(btn_load)
top_bar1.addWidget(btn_export)
top_bar1.addStretch()
top_bar1.addWidget(self.chk_mouse_mode)
top_bar1.addWidget(btn_autoscale)
# --- 第二排工具栏 ---
top_bar2 = QHBoxLayout()
self.cb_ports = QComboBox()
self.cb_baudrate = QComboBox()
self.cb_baudrate.addItems(["9600", "115200", "230400", "460800", "921600", "2000000"])
self.cb_baudrate.setCurrentText("115200")
btn_refresh_ports = QPushButton("🔄 刷新端口")
btn_refresh_ports.clicked.connect(self.refresh_ports)
self.btn_toggle_serial = QPushButton("▶ 打开接收串口")
self.btn_toggle_serial.setStyleSheet("background-color: #4CAF50; color: white; font-weight: bold;")
self.btn_toggle_serial.clicked.connect(self.toggle_serial)
top_bar2.addWidget(QLabel("🔌 接收串口:"))
top_bar2.addWidget(self.cb_ports)
top_bar2.addWidget(btn_refresh_ports)
top_bar2.addWidget(QLabel("波特率:"))
top_bar2.addWidget(self.cb_baudrate)
top_bar2.addWidget(self.btn_toggle_serial)
top_bar2.addStretch()
# --- 第三排工具栏 (GPS 模拟器) ---
top_bar3 = QHBoxLayout()
self.cb_sim_ports = QComboBox()
self.cb_sim_baudrate = QComboBox()
self.cb_sim_baudrate.addItems(["9600", "115200", "230400", "460800", "921600"])
self.cb_sim_baudrate.setCurrentText("115200")
self.btn_toggle_sim = QPushButton("🛰 开启GPS动态模拟 (10Hz)")
self.btn_toggle_sim.setStyleSheet("background-color: #FF9800; color: white; font-weight: bold;")
self.btn_toggle_sim.clicked.connect(self.toggle_gps_sim)
top_bar3.addWidget(QLabel("📡 输出串口:"))
top_bar3.addWidget(self.cb_sim_ports)
top_bar3.addWidget(QLabel("波特率:"))
top_bar3.addWidget(self.cb_sim_baudrate)
top_bar3.addWidget(self.btn_toggle_sim)
top_bar3.addStretch()
layout.addLayout(top_bar1)
layout.addLayout(top_bar2)
layout.addLayout(top_bar3)
self.refresh_ports()
# --- 监控栏 ---
info_layout = QHBoxLayout()
self.lbl_info = QLabel("请加载文件或打开串口接收数据...")
self.lbl_info.setStyleSheet("color: blue; font-weight: bold;")
self.lbl_fps = QLabel("📈 绘图帧率: -- FPS | 📥 接收率: -- 包/秒")
self.lbl_fps.setStyleSheet("color: #E91E63; font-weight: bold;")
info_layout.addWidget(self.lbl_info)
info_layout.addStretch()
info_layout.addWidget(self.lbl_fps)
layout.addLayout(info_layout)
# ==========================================
# 多标签内容区域设置
# ==========================================
self.tabs = QTabWidget()
layout.addWidget(self.tabs)
# [Tab 1] 主波形图
self.plot_widget = pg.PlotWidget()
self.plot_widget.setBackground('w')
self.plot_widget.showGrid(x=True, y=True, alpha=0.3)
self.plot_widget.addLegend()
self.plot_widget.setLabel('bottom', 'Data Points (Index)') # 更新了标签
self.plot_widget.setLabel('left', 'Value')
self.vb = self.plot_widget.plotItem.vb
self.tabs.addTab(self.plot_widget, "📈 波形图 (Plot)")
# [Tab 2] GPS 轨迹与海拔视图
traj_container = QWidget()
traj_layout = QVBoxLayout(traj_container)
traj_splitter = QSplitter(Qt.Orientation.Vertical)
self.traj_plot = pg.PlotWidget(title="🗺️ 实时轨迹 (经度 vs 纬度)")
self.traj_plot.setBackground('w')
self.traj_plot.showGrid(x=True, y=True, alpha=0.5)
self.traj_plot.setLabel('bottom', 'Longitude (经度)')
self.traj_plot.setLabel('left', 'Latitude (纬度)')
self.traj_curve = self.traj_plot.plot(pen=pg.mkPen('b', width=2), symbol='o', symbolSize=3, symbolBrush='b')
self.alt_plot = pg.PlotWidget(title="⛰️ 海拔高度 (Altitude)")
self.alt_plot.setBackground('w')
self.alt_plot.showGrid(x=True, y=True, alpha=0.5)
self.alt_plot.setLabel('bottom', 'Data Points (Index)') # 更新了标签
self.alt_plot.setLabel('left', 'Altitude (m)')
self.alt_curve = self.alt_plot.plot(pen=pg.mkPen('g', width=2))
traj_splitter.addWidget(self.traj_plot)
traj_splitter.addWidget(self.alt_plot)
traj_layout.addWidget(traj_splitter)
self.tabs.addTab(traj_container, "🗺️ 轨迹与海拔 (Trajectory)")
# [Tab 3] 数据表格
self.table_view = QTableView()
self.table_view.setAlternatingRowColors(True)
self.table_view.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Interactive)
self.tabs.addTab(self.table_view, "🔢 数据表 (Table)")
def get_current_dtype(self):
is_v2 = self.rb_v2.isChecked()
is_raw = self.rb_raw.isChecked()
if is_v2:
return RAW_DTYPE_V2 if is_raw else CORRECTED_DTYPE_V2
return RAW_DTYPE_V1 if is_raw else CORRECTED_DTYPE_V1
def get_column_config(self):
is_v2 = self.rb_v2.isChecked()
is_raw = self.rb_raw.isChecked()
# 这里移除了 timestamp
if is_raw:
base_cols = ['adc1', 'adc2', 'adc3']
labels = ['ADC 1', 'ADC 2', 'ADC 3']
data_cols = ['adc1', 'adc2', 'adc3']
else:
base_cols = ['corr_x', 'corr_y', 'corr_z']
labels = ['X Axis', 'Y Axis', 'Z Axis']
data_cols = ['corr_x', 'corr_y', 'corr_z']
cols = base_cols + ['gps_time', 'gps_latitude', 'gps_longitude', 'gps_altitude'] if is_v2 else base_cols + ['checksum']
return cols, data_cols, labels
def load_file(self):
if self.is_live_mode:
QMessageBox.warning(self, "警告", "请先关闭串口后再加载文件。")
return
file_name, _ = QFileDialog.getOpenFileName(self, "选择文件", "", "Data (*.dat);;All (*)")
if not file_name: return
try:
dtype = self.get_current_dtype()
raw_data = np.fromfile(file_name, dtype=dtype)
if len(raw_data) == 0: return
self.df = pd.DataFrame(raw_data)
cols, data_cols, labels = self.get_column_config()
self.lbl_info.setText(f"文件加载成功 | {len(self.df)} 行 | 版本: {'V2' if self.rb_v2.isChecked() else 'V1'}")
self.lbl_fps.setText("📈 绘图帧率: -- FPS | 📥 接收率: -- 包/秒")
self.refresh_table(cols)
self.plot_static_data(data_cols, labels)
except Exception as e:
QMessageBox.critical(self, "解析错误", str(e))
def refresh_table(self, cols):
display_df = self.df[cols] if not self.df.empty else pd.DataFrame(columns=cols)
self.model = BigDataModel(display_df)
self.table_view.setModel(self.model)
self.table_view.resizeColumnsToContents()
def plot_static_data(self, data_cols, labels):
# 1. 主波形图静态渲染
self.plot_widget.clear()
self.curves.clear()
colors = ['#FF0000', '#00AA00', '#0000FF']
# 移除了 timestamp,改为使用数据点索引
x_data = np.arange(len(self.df))
for i, col in enumerate(data_cols):
y_data = self.df[col].values
curve = pg.PlotCurveItem(x=x_data, y=y_data, pen=pg.mkPen(color=colors[i], width=1.5),
name=labels[i], skipFiniteCheck=True, autoDownsample=True, clipToView=True)
self.plot_widget.addItem(curve)
self.plot_widget.setLabel('bottom', 'Data Points (Index)')
self.reset_view()
# 2. 轨迹和海拔静态渲染
if self.rb_v2.isChecked() and 'gps_longitude' in self.df.columns:
lons = self.df['gps_longitude'].values
lats = self.df['gps_latitude'].values
alts = self.df['gps_altitude'].values if 'gps_altitude' in self.df.columns else np.zeros_like(lons)
valid_idx = (lons != 0.0) & (lats != 0.0)
if np.any(valid_idx):
self.traj_curve.setData(lons[valid_idx], lats[valid_idx])
else:
self.traj_curve.setData(lons, lats)
self.alt_curve.setData(alts)
self.traj_plot.autoRange()
self.alt_plot.autoRange()
else:
self.traj_curve.setData([], [])
self.alt_curve.setData([])
def refresh_ports(self):
self.cb_ports.clear()
self.cb_sim_ports.clear()
ports = serial.tools.list_ports.comports()
for p in ports:
port_name = f"{p.device} - {p.description}"
self.cb_ports.addItem(port_name, p.device)
self.cb_sim_ports.addItem(port_name, p.device)
def toggle_serial(self):
if not self.is_live_mode:
port = self.cb_ports.currentData()
if not port:
QMessageBox.warning(self, "提示", "未找到有效串口")
return
baud = int(self.cb_baudrate.currentText())
dtype = self.get_current_dtype()
self.df = pd.DataFrame()
self.live_data_list = []
self.plot_widget.clear()
self.curves.clear()
self.traj_curve.setData([], [])
self.alt_curve.setData([])
self.plot_widget.setLabel('bottom', 'Latest Points (Index)')
colors = ['#FF0000', '#00AA00', '#0000FF']
_, _, labels = self.get_column_config()
for i in range(len(labels)):
curve = pg.PlotCurveItem(pen=pg.mkPen(color=colors[i], width=1.5), name=labels[i])
self.plot_widget.addItem(curve)
self.curves.append(curve)
self.current_packet_count = 0
self.last_packet_count = 0
self.frame_count = 0
self.last_fps_time = time.perf_counter()
self.lbl_fps.setText("📈 绘图帧率: 计算中... | 📥 接收率: 计算中...")
self.serial_thread = SerialReaderThread(port, baud, dtype)
self.serial_thread.data_received.connect(self.on_live_data_received)
self.serial_thread.error_occurred.connect(self.on_serial_error)
self.serial_thread.start()
self.plot_timer.start(50)
self.is_live_mode = True
self.btn_toggle_serial.setText("⏹ 关闭串口停止接收")
self.btn_toggle_serial.setStyleSheet("background-color: #f44336; color: white; font-weight: bold;")
self.rb_v2.setEnabled(False)
self.rb_raw.setEnabled(False)
self.rb_corr.setEnabled(False)
else:
self.serial_thread.stop()
self.serial_thread.wait()
self.plot_timer.stop()
self.is_live_mode = False
self.btn_toggle_serial.setText("▶ 打开接收串口")
self.btn_toggle_serial.setStyleSheet("background-color: #4CAF50; color: white; font-weight: bold;")
self.lbl_fps.setText("📈 绘图帧率: -- FPS | 📥 接收率: -- 包/秒")
self.rb_v2.setEnabled(True)
self.rb_raw.setEnabled(True)
self.rb_corr.setEnabled(True)
if self.live_data_list:
full_array = np.concatenate(self.live_data_list)
self.df = pd.DataFrame(full_array)
cols, _, _ = self.get_column_config()
self.refresh_table(cols)
self.lbl_info.setText(f"串口采集完毕。总计收集 {len(self.df)} 行数据。")
def on_live_data_received(self, data_array):
self.live_data_list.append(data_array)
self.current_packet_count += len(data_array)
self.lbl_info.setText(f"🟢 正在接收数据... 已接收: {self.current_packet_count} 帧")
def update_live_plot(self):
self.frame_count += 1
current_time = time.perf_counter()
elapsed = current_time - self.last_fps_time
if elapsed >= 1.0:
fps = self.frame_count / elapsed
pps = (self.current_packet_count - self.last_packet_count) / elapsed
self.lbl_fps.setText(f"📈 绘图帧率: {fps:.1f} FPS | 📥 接收率: {pps:.0f} 包/秒")
self.last_fps_time = current_time
self.frame_count = 0
self.last_packet_count = self.current_packet_count
if not self.live_data_list:
return
MAX_POINTS = 5000
recent_chunks = []
point_count = 0
for arr in reversed(self.live_data_list):
recent_chunks.append(arr)
point_count += len(arr)
if point_count >= MAX_POINTS:
break
recent_data = np.concatenate(recent_chunks[::-1])
if len(recent_data) > MAX_POINTS:
recent_data = recent_data[-MAX_POINTS:]
# 1. 更新主波形图 (自动以接收点索引作为 X 轴)
_, data_cols, _ = self.get_column_config()
for i, col in enumerate(data_cols):
y_data = recent_data[col]# / 429496729.0
self.curves[i].setData(y_data)
# 2. 更新轨迹和海拔
if self.rb_v2.isChecked() and 'gps_longitude' in recent_data.dtype.names:
lons = recent_data['gps_longitude']
lats = recent_data['gps_latitude']
alts = recent_data['gps_altitude'] if 'gps_altitude' in recent_data.dtype.names else np.zeros_like(lons)
valid_idx = (lons != 0.0) & (lats != 0.0)
if np.any(valid_idx):
self.traj_curve.setData(lons[valid_idx], lats[valid_idx])
else:
self.traj_curve.setData(lons, lats)
self.alt_curve.setData(alts)
def on_serial_error(self, err_msg):
self.toggle_serial()
QMessageBox.critical(self, "接收串口错误", f"发生错误:\n{err_msg}")
def toggle_gps_sim(self):
if not self.is_sim_running:
port = self.cb_sim_ports.currentData()
if not port:
QMessageBox.warning(self, "提示", "未找到有效的输出串口")
return
if self.is_live_mode and port == self.cb_ports.currentData():
reply = QMessageBox.question(self, "警告",
"模拟输出端口与当前接收端口相同,可能会导致端口冲突。确定要继续吗?",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
if reply == QMessageBox.StandardButton.No: return
baud = int(self.cb_sim_baudrate.currentText())
self.sim_thread = GPSSimulatorThread(port, baud)
self.sim_thread.error_occurred.connect(self.on_sim_error)
self.sim_thread.start()
self.is_sim_running = True
self.btn_toggle_sim.setText("⏹ 关闭GPS动态模拟")
self.btn_toggle_sim.setStyleSheet("background-color: #f44336; color: white; font-weight: bold;")
else:
self.sim_thread.stop()
self.sim_thread.wait()
self.is_sim_running = False
self.btn_toggle_sim.setText("🛰 开启GPS动态模拟 (10Hz)")
self.btn_toggle_sim.setStyleSheet("background-color: #FF9800; color: white; font-weight: bold;")
def on_sim_error(self, err_msg):
self.toggle_gps_sim()
QMessageBox.critical(self, "输出串口错误", f"模拟器串口发生错误:\n{err_msg}")
def toggle_mouse_mode(self, state):
mode = self.vb.RectMode if state == 2 else self.vb.PanMode
self.vb.setMouseMode(mode)
self.traj_plot.plotItem.vb.setMouseMode(mode)
self.alt_plot.plotItem.vb.setMouseMode(mode)
def reset_view(self):
self.plot_widget.autoRange()
self.traj_plot.autoRange()
self.alt_plot.autoRange()
def export_csv(self):
if self.df.empty: return
path, _ = QFileDialog.getSaveFileName(self, "保存", "export.csv", "CSV (*.csv)")
if path:
self.df.to_csv(path, index=False)
QMessageBox.information(self, "完成", "导出成功")
if __name__ == "__main__":
app = QApplication(sys.argv)
w = DataAnalyzerUI()
w.show()
sys.exit(app.exec())
Binary file not shown.
+84
View File
@@ -0,0 +1,84 @@
#ifndef APP_CONFIG_H
#define APP_CONFIG_H
// ============================================================
// 系统编译期配置总入口
// 所有可调参数集中在此文件,修改后重新编译生效。
// 若需恢复 SD 卡运行时加载,将 CFG_LOAD_FROM_FILE 置 1,
// 其余编译期值将作为加载失败时的 fallback 默认值。
// ============================================================
// ------------------------------------------------------------
// 配置加载方式
// ------------------------------------------------------------
// 是否在启动时从 SD 卡 CONFIG.TXT 加载配置
// 1 = 运行时加载(文件内容覆盖以下编译期值)
// 0 = 仅使用以下编译期值,不读取文件(安全,可重复)
#define CFG_LOAD_FROM_FILE 0
// ------------------------------------------------------------
// 数据输出控制
// ------------------------------------------------------------
// RS485 串口输出开关
// 1 = 每帧 ADC 数据通过 USART1 以 2Mbps 发送
// 0 = 禁用串口输出,降低功耗和 CPU 占用
#define CFG_UART_OUTPUT_ENABLED 0
// SD 卡数据存储开关
// 1 = 每帧数据写入 SD 卡 DATA/SESSION_xxx/ 目录下的文件
// 0 = 禁用存储,SD 卡仅用于 USB MSC 访问
#define CFG_STORAGE_ENABLED 1
// ------------------------------------------------------------
// 功能模块开关
// ------------------------------------------------------------
// 系统监控统计模块
// 1 = 启用:统计采样次数、丢帧数、SD 写入量、串口发送量,
// 并定期输出到调试串口 / 写入 LOG.TXT
// 0 = 禁用:相关代码被条件编译剔除,节省 Flash 和运行时开销
#define CFG_ENABLE_SYSTEM_MONITOR 0
// 数据包中包含 GPS 经纬度和海拔字段
// 1 = 每帧包含 latitude(float) + longitude(float) + altitude(float),包体增大 12 字节
// 0 = 仅保留 GPS 时间戳(gps_time),包体更小,适合不需要位置信息的场景
#define CFG_ENABLE_GPS_POSITION 0
// ------------------------------------------------------------
// 调试与监控定时参数
// ------------------------------------------------------------
// 调试信息通过 RS485 输出的时间间隔(毫秒)
// 控制 DebugOutput_PrintSystemStats() 的调用频率
// 建议范围:5000(5秒)~ 60000(1分钟)
#define CFG_DEBUG_OUTPUT_INTERVAL_MS 30000
// 监控统计数据写入 SD 卡 LOG.TXT 的时间间隔(毫秒)
// 间隔越短日志越详细,但 SD 卡写入操作越频繁
// 建议范围:10000(10秒)~ 300000(5分钟)
#define CFG_MONITOR_SAVE_INTERVAL_MS 30000
// ------------------------------------------------------------
// SD 卡存储参数
// ------------------------------------------------------------
// SD 卡写入双缓冲区单个缓冲区大小(字节)
// 更大的缓冲区可减少写入次数,提升吞吐量,但占用更多 RAM
// STM32F405 RAM 为 192KB,建议不超过 65536(64KB)
// 典型值:32768(32KB) / 65536(64KB)
#define CFG_STORAGE_BUFFER_SIZE 32768
// 单个数据文件的最大大小(字节),超过后自动创建新文件
// 较小的文件便于传输和分析,较大的文件减少文件切换开销
// 典型值:100MB / 500MB / 1024MB
#define CFG_STORAGE_FILE_MAX_SIZE (100 * 1024 * 1024)
#endif // APP_CONFIG_H
+3 -2
View File
@@ -2,6 +2,7 @@
#define CONFIG_MANAGER_H #define CONFIG_MANAGER_H
#include "main.h" #include "main.h"
#include "app_config.h"
#include <stdint.h> #include <stdint.h>
// 配置文件路径 // 配置文件路径
@@ -17,8 +18,8 @@ typedef struct {
} SystemConfig_t; } SystemConfig_t;
// 默认配置值 // 默认配置值
#define DEFAULT_UART_OUTPUT_ENABLED 1 #define DEFAULT_UART_OUTPUT_ENABLED CFG_UART_OUTPUT_ENABLED
#define DEFAULT_STORAGE_ENABLED 0 #define DEFAULT_STORAGE_ENABLED CFG_STORAGE_ENABLED
#define CONFIG_VERSION 0x00010000 // 版本 1.0.0 #define CONFIG_VERSION 0x00010000 // 版本 1.0.0
// 函数声明 // 函数声明
+72 -88
View File
@@ -2,8 +2,8 @@
#include "stddef.h" #include "stddef.h"
#include "stm32f4xx_hal.h" #include "stm32f4xx_hal.h"
// CRC16-MODBUS算法实现 uint16_t Calculate_CRC16(const uint8_t *data, uint16_t len)
uint16_t Calculate_CRC16(const uint8_t *data, uint16_t len) { {
uint16_t crc = 0xFFFF; uint16_t crc = 0xFFFF;
for (uint16_t i = 0; i < len; i++) { for (uint16_t i = 0; i < len; i++) {
crc ^= data[i]; crc ^= data[i];
@@ -18,107 +18,91 @@ uint16_t Calculate_CRC16(const uint8_t *data, uint16_t len) {
return crc; return crc;
} }
void PackData(DataPacket_t *packet, int32_t adc1, int32_t adc2, int32_t adc3, // ─── 内部辅助宏:填充GPS位置字段(仅在宏开启时) ─────────────────────────────
#ifdef ENABLE_GPS_POSITION
#define FILL_GPS_POSITION(pkt, lat, lon, alt) \
do { (pkt)->gps_latitude = (lat); (pkt)->gps_longitude = (lon); (pkt)->gps_altitude = (alt); } while(0)
#else
#define FILL_GPS_POSITION(pkt, lat, lon, alt) // 空操作
#endif
// ─── 原始ADC数据包 ────────────────────────────────────────────────────────────
void PackData(DataPacket_t *packet,
int32_t adc1, int32_t adc2, int32_t adc3,
uint32_t gps_time, float latitude, float longitude, float altitude) uint32_t gps_time, float latitude, float longitude, float altitude)
{ {
if (packet == NULL) return; if (packet == NULL) return;
packet->start_byte = PACKET_TYPE_STANDARD;
// 设置包头
packet->start_byte = PACKET_START_BYTE;
// 设置时间戳
// packet->timestamp = HAL_GetTick();
// 设置ADC数据
packet->adc_data1 = adc1; packet->adc_data1 = adc1;
packet->adc_data2 = adc2; packet->adc_data2 = adc2;
packet->adc_data3 = adc3; packet->adc_data3 = adc3;
// 设置GPS数据
packet->gps_time = gps_time; packet->gps_time = gps_time;
packet->gps_latitude = latitude; FILL_GPS_POSITION(packet, latitude, longitude, altitude);
packet->gps_longitude = longitude;
packet->gps_altitude = altitude;
} }
void PackDataWithOptic(DataPacketWithOptic_t *packet,
int32_t adc1, int32_t adc2, int32_t adc3,
uint32_t gps_time, float latitude, float longitude, float altitude,
uint32_t optical_mag)
{
if (packet == NULL) return;
packet->start_byte = PACKET_TYPE_WITH_OPTIC;
packet->adc_data1 = adc1;
packet->adc_data2 = adc2;
packet->adc_data3 = adc3;
packet->gps_time = gps_time;
FILL_GPS_POSITION(packet, latitude, longitude, altitude);
packet->optical_mag = optical_mag;
}
// ─── 校正数据包 ───────────────────────────────────────────────────────────────
void PackCorrectedData(CorrectedDataPacket_t *packet,
float x, float y, float z,
uint32_t gps_time, float latitude, float longitude, float altitude)
{
PackCorrectedDataWithGPS(packet, x, y, z, gps_time, latitude, longitude, altitude);
}
void PackCorrectedDataWithGPS(CorrectedDataPacketWithGPS_t *packet,
float x, float y, float z,
uint32_t gps_time, float latitude, float longitude, float altitude)
{
if (packet == NULL) return;
packet->start_byte = PACKET_TYPE_STANDARD;
packet->corrected_x = x;
packet->corrected_y = y;
packet->corrected_z = z;
packet->gps_time = gps_time;
FILL_GPS_POSITION(packet, latitude, longitude, altitude);
}
void PackCorrectedDataWithOptic(CorrectedDataPacketWithOptic_t *packet,
float x, float y, float z,
uint32_t gps_time, float latitude, float longitude, float altitude,
uint32_t optical_mag)
{
if (packet == NULL) return;
packet->start_byte = PACKET_TYPE_WITH_OPTIC;
packet->corrected_x = x;
packet->corrected_y = y;
packet->corrected_z = z;
packet->gps_time = gps_time;
FILL_GPS_POSITION(packet, latitude, longitude, altitude);
packet->optical_mag = optical_mag;
}
// ─── 验证函数 ─────────────────────────────────────────────────────────────────
uint8_t ValidatePacket(const DataPacket_t *packet) uint8_t ValidatePacket(const DataPacket_t *packet)
{ {
if (packet == NULL) return 0; if (packet == NULL) return 0;
return (packet->start_byte == PACKET_TYPE_STANDARD) ? 1 : 0;
// 检查包头
if (packet->start_byte != PACKET_START_BYTE) return 0;
// 精简版数据包无校验和,仅检查包头
return 1; // 包头正确,认为有效
} }
uint8_t ValidateCorrectedPacket(const CorrectedDataPacket_t *packet) uint8_t ValidateCorrectedPacket(const CorrectedDataPacket_t *packet)
{ {
if (packet == NULL) return 0; if (packet == NULL) return 0;
return (packet->start_byte == PACKET_TYPE_STANDARD) ? 1 : 0;
// 检查包头
if (packet->start_byte != PACKET_START_BYTE) return 0;
// 精简版数据包无校验和,仅检查包头
return 1; // 包头正确,认为有效
}
void PackCorrectedData(CorrectedDataPacket_t *packet, float x, float y, float z,
uint32_t gps_time, float latitude, float longitude, float altitude)
{
if (packet == NULL) return;
// 设置包头
packet->start_byte = PACKET_START_BYTE;
// 设置时间戳
// packet->timestamp = HAL_GetTick();
// 设置校正后数据
packet->corrected_x = x;
packet->corrected_y = y;
packet->corrected_z = z;
// 设置GPS数据
packet->gps_time = gps_time;
packet->gps_latitude = latitude;
packet->gps_longitude = longitude;
packet->gps_altitude = altitude;
}
void PackCorrectedDataWithGPS(CorrectedDataPacketWithGPS_t *packet, float x, float y, float z,
uint32_t gps_time, float latitude, float longitude, float altitude)
{
if (packet == NULL) return;
// 设置包头
packet->start_byte = PACKET_START_BYTE;
// 设置时间戳
// packet->timestamp = HAL_GetTick();
// 设置校正后数据
packet->corrected_x = x;
packet->corrected_y = y;
packet->corrected_z = z;
// 设置GPS数据
packet->gps_time = gps_time;
packet->gps_latitude = latitude;
packet->gps_longitude = longitude;
packet->gps_altitude = altitude;
}
uint8_t ValidateCorrectedPacketWithGPS(const CorrectedDataPacketWithGPS_t *packet)
{
if (packet == NULL) return 0;
// 检查包头
if (packet->start_byte != PACKET_START_BYTE) return 0;
// 精简版数据包无校验和,仅检查包头
// 可以添加简单的数据合理性检查
// 例如:检查GPS坐标是否在有效范围内等
return 1; // 包头正确,认为有效
} }
+89 -39
View File
@@ -2,59 +2,109 @@
#define DATA_PACKET_H #define DATA_PACKET_H
#include <stdint.h> #include <stdint.h>
#include "app_config.h"
#define PACKET_START_BYTE 0xFFFFFFFF // 帧同步魔数(高3字节固定),低字节为类型标志
#define PACKET_END_BYTE 0x0000 #define PACKET_TYPE_STANDARD 0xFFFFFFFFUL // 标准包(无光泵)
#define PACKET_TYPE_WITH_OPTIC 0xFFFFFFFEUL // 扩展包(含光泵,末尾追加 optical_mag)
#define PACKET_SYNC_MASK 0xFFFFFF00UL // 同步检测掩码
// 兼容旧代码
#define PACKET_START_BYTE PACKET_TYPE_STANDARD
// GPS位置字段由 app_config.h 中 CFG_ENABLE_GPS_POSITION 控制
#if CFG_ENABLE_GPS_POSITION
#define ENABLE_GPS_POSITION
#endif
// ─── 原始ADC数据包 ────────────────────────────────────────────────────────────
// 数据包结构(精简版 - 有包头无校验和,含GPS)
typedef struct __attribute__((packed)) { typedef struct __attribute__((packed)) {
uint32_t start_byte; // 包头 (4字节) = 0xFFFFFFFF uint32_t start_byte; // PACKET_TYPE_STANDARD
// uint32_t timestamp; // 系统时间戳 (4字节) int32_t adc_data1;
int32_t adc_data1; // ADC1 数据 (4字节) int32_t adc_data2;
int32_t adc_data2; // ADC2 数据 (4字节) int32_t adc_data3;
int32_t adc_data3; // ADC3 数据 (4字节) uint32_t gps_time; // HHMMSS格式,始终存在
uint32_t gps_time; // GPS时间戳 (4字节) HHMMSS格式 #ifdef ENABLE_GPS_POSITION
float gps_latitude; // GPS纬度 (4字节) float gps_latitude;
float gps_longitude; // GPS经度 (4字节) float gps_longitude;
float gps_altitude; // GPS海拔 (4字节) float gps_altitude;
#endif
} DataPacket_t; } DataPacket_t;
// 校正后数据包结构(精简版 - 有包头无校验和,含GPS)
typedef struct __attribute__((packed)) { typedef struct __attribute__((packed)) {
uint32_t start_byte; // 包头 (4字节) = 0xFFFFFFFF uint32_t start_byte; // PACKET_TYPE_WITH_OPTIC
// uint32_t timestamp; // 系统时间戳 (4字节) int32_t adc_data1;
float corrected_x; // 校正后X轴数据 (4字节) int32_t adc_data2;
float corrected_y; // 校正后Y轴数据 (4字节) int32_t adc_data3;
float corrected_z; // 校正后Z轴数据 (4字节) uint32_t gps_time;
uint32_t gps_time; // GPS时间戳 (4字节) HHMMSS格式 #ifdef ENABLE_GPS_POSITION
float gps_latitude; // GPS纬度 (4字节) float gps_latitude;
float gps_longitude; // GPS经度 (4字节) float gps_longitude;
float gps_altitude; // GPS海拔 (4字节) float gps_altitude;
#endif
uint32_t optical_mag; // 单位 0.001 nT,例如 123456789 = 123456.789 nT
} DataPacketWithOptic_t;
// ─── 校正数据包 ───────────────────────────────────────────────────────────────
typedef struct __attribute__((packed)) {
uint32_t start_byte; // PACKET_TYPE_STANDARD
float corrected_x;
float corrected_y;
float corrected_z;
uint32_t gps_time;
#ifdef ENABLE_GPS_POSITION
float gps_latitude;
float gps_longitude;
float gps_altitude;
#endif
} CorrectedDataPacket_t; } CorrectedDataPacket_t;
// 带GPS信息的校正数据包结构(精简版 - 有包头无校验和) // 与 CorrectedDataPacket_t 相同,保留别名兼容旧代码
typedef CorrectedDataPacket_t CorrectedDataPacketWithGPS_t;
typedef struct __attribute__((packed)) { typedef struct __attribute__((packed)) {
uint32_t start_byte; // 包头 (4字节) = 0xFFFFFFFF uint32_t start_byte; // PACKET_TYPE_WITH_OPTIC
// uint32_t timestamp; // 系统时间戳 (4字节) float corrected_x;
float corrected_x; // 校正后X轴数据 (4字节) float corrected_y;
float corrected_y; // 校正后Y轴数据 (4字节) float corrected_z;
float corrected_z; // 校正后Z轴数据 (4字节) uint32_t gps_time;
uint32_t gps_time; // GPS时间戳 (4字节) HHMMSS格式 #ifdef ENABLE_GPS_POSITION
float gps_latitude; // GPS纬度 (4字节) float gps_latitude;
float gps_longitude; // GPS经度 (4字节) float gps_longitude;
float gps_altitude; // GPS海拔 (4字节) float gps_altitude;
} CorrectedDataPacketWithGPS_t; #endif
uint32_t optical_mag; // 单位 0.001 nT
} CorrectedDataPacketWithOptic_t;
// ─── 函数声明 ─────────────────────────────────────────────────────────────────
// 函数声明
uint16_t Calculate_CRC16(const uint8_t *data, uint16_t len); uint16_t Calculate_CRC16(const uint8_t *data, uint16_t len);
void PackData(DataPacket_t *packet, int32_t adc1, int32_t adc2, int32_t adc3,
void PackData(DataPacket_t *packet,
int32_t adc1, int32_t adc2, int32_t adc3,
uint32_t gps_time, float latitude, float longitude, float altitude); uint32_t gps_time, float latitude, float longitude, float altitude);
void PackCorrectedData(CorrectedDataPacket_t *packet, float x, float y, float z,
void PackDataWithOptic(DataPacketWithOptic_t *packet,
int32_t adc1, int32_t adc2, int32_t adc3,
uint32_t gps_time, float latitude, float longitude, float altitude,
uint32_t optical_mag);
void PackCorrectedData(CorrectedDataPacket_t *packet,
float x, float y, float z,
uint32_t gps_time, float latitude, float longitude, float altitude); uint32_t gps_time, float latitude, float longitude, float altitude);
void PackCorrectedDataWithGPS(CorrectedDataPacketWithGPS_t *packet, float x, float y, float z,
uint32_t gps_time, float latitude, float , float altitude); void PackCorrectedDataWithGPS(CorrectedDataPacketWithGPS_t *packet,
float x, float y, float z,
uint32_t gps_time, float latitude, float longitude, float altitude);
void PackCorrectedDataWithOptic(CorrectedDataPacketWithOptic_t *packet,
float x, float y, float z,
uint32_t gps_time, float latitude, float longitude, float altitude,
uint32_t optical_mag);
uint8_t ValidatePacket(const DataPacket_t *packet); uint8_t ValidatePacket(const DataPacket_t *packet);
uint8_t ValidateCorrectedPacket(const CorrectedDataPacket_t *packet); uint8_t ValidateCorrectedPacket(const CorrectedDataPacket_t *packet);
uint8_t ValidateCorrectedPacketWithGPS(const CorrectedDataPacketWithGPS_t *packet);
#endif // DATA_PACKET_H #endif // DATA_PACKET_H
+94 -105
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,10 +30,12 @@ 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;
@@ -113,6 +114,41 @@ HAL_StatusTypeDef DataStorage_WriteData(DataStorageHandle_t *handle, const DataP
return HAL_OK; 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 写入校正后的数据包到存储 * @brief 写入校正后的数据包到存储
* @param handle: 数据存储句柄指针 * @param handle: 数据存储句柄指针
@@ -178,24 +214,47 @@ HAL_StatusTypeDef DataStorage_CreateNewFile(DataStorageHandle_t *handle)
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;
} }
@@ -230,6 +289,12 @@ HAL_StatusTypeDef DataStorage_StartRecording(DataStorageHandle_t *handle)
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;
@@ -417,108 +482,32 @@ HAL_StatusTypeDef DataStorage_CreateSessionFolder(DataStorageHandle_t *handle)
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;
}
+11 -10
View File
@@ -2,19 +2,20 @@
#define DATA_STORAGE_H #define DATA_STORAGE_H
#include "main.h" #include "main.h"
#include "app_config.h"
#include "fatfs.h" #include "fatfs.h"
#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_* 统一管理)
#define DATA_STORAGE_BUFFER_SIZE 32768 // 缓冲区大小(字节) #define DATA_STORAGE_BUFFER_SIZE CFG_STORAGE_BUFFER_SIZE
#define DATA_STORAGE_FILE_MAX_SIZE (100*1024*1024) // 单个文件最大100MB #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 // 最大路径长度
// 缓冲区状态 // 缓冲区状态
@@ -60,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;
// 函数声明 // 函数声明
@@ -75,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);
@@ -87,4 +85,7 @@ void DataStorage_ProcessBackgroundTasks(DataStorageHandle_t *handle);
// 缓冲区可用性检查函数 // 缓冲区可用性检查函数
uint8_t DataStorage_IsBufferAvailable(DataStorageHandle_t *handle, uint32_t required_size); uint8_t DataStorage_IsBufferAvailable(DataStorageHandle_t *handle, uint32_t required_size);
// 通用字节流写入(支持任意包类型,方案Y混流存储)
HAL_StatusTypeDef DataStorage_WriteRawBytes(DataStorageHandle_t *handle, const uint8_t *data, uint16_t size);
#endif // DATA_STORAGE_H #endif // DATA_STORAGE_H
+59
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
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
+64 -2
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,6 +365,50 @@ 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)
+21 -2
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
+9 -6
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 数据触发时钟 // static:DMA 传输异步完成,必须保证缓冲区生命周期覆盖整个传输过程
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;
+70
View File
@@ -0,0 +1,70 @@
#include "optic_mag_driver.h"
volatile uint32_t g_optic_mag_value = 0;
volatile uint8_t g_optic_mag_fresh = 0;
typedef enum {
OPTIC_STATE_IDLE = 0,
OPTIC_STATE_COLLECTING,
OPTIC_STATE_WAIT_END,
} OpticState_t;
static UART_HandleTypeDef *s_huart;
static uint8_t s_rx_byte;
static uint8_t s_bcd_buf[OPTIC_MAG_DATA_BYTES];
static uint8_t s_data_count;
static OpticState_t s_state;
void OpticMag_Init(UART_HandleTypeDef *huart)
{
s_huart = huart;
s_state = OPTIC_STATE_IDLE;
s_data_count = 0;
HAL_UART_Receive_IT(s_huart, &s_rx_byte, 1);
}
void OpticMag_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart != s_huart) return;
uint8_t byte = s_rx_byte;
switch (s_state) {
case OPTIC_STATE_IDLE:
if (byte == OPTIC_MAG_FRAME_START) {
s_data_count = 0;
s_state = OPTIC_STATE_COLLECTING;
}
break;
case OPTIC_STATE_COLLECTING:
s_bcd_buf[s_data_count++] = byte;
if (s_data_count == OPTIC_MAG_DATA_BYTES) {
s_state = OPTIC_STATE_WAIT_END;
}
break;
case OPTIC_STATE_WAIT_END:
if (byte == OPTIC_MAG_FRAME_END) {
// 取前9位BCD数字(忽略第10位),每字节低nibble为有效数字
uint32_t value = 0;
for (uint8_t i = 0; i < 9; i++) {
value = value * 10u + (s_bcd_buf[i] & 0x0Fu);
}
g_optic_mag_value = value;
g_optic_mag_fresh = 1;
}
// 无论结束字节是否合法,均回到IDLE(非法帧直接丢弃)
s_state = OPTIC_STATE_IDLE;
break;
}
HAL_UART_Receive_IT(s_huart, &s_rx_byte, 1);
}
void OpticMag_UART_ErrorCallback(UART_HandleTypeDef *huart)
{
if (huart != s_huart) return;
s_state = OPTIC_STATE_IDLE;
HAL_UART_Receive_IT(s_huart, &s_rx_byte, 1);
}
+20
View File
@@ -0,0 +1,20 @@
#ifndef OPTIC_MAG_DRIVER_H
#define OPTIC_MAG_DRIVER_H
#include "main.h"
#include <stdint.h>
#define OPTIC_MAG_FRAME_START 0x5A
#define OPTIC_MAG_FRAME_END 0xA5
#define OPTIC_MAG_DATA_BYTES 10
// 单位 0.001 nT,忽略末位BCD数字,取前9位
// 例如 123456789 表示 123456.789 nT
extern volatile uint32_t g_optic_mag_value;
extern volatile uint8_t g_optic_mag_fresh;
void OpticMag_Init(UART_HandleTypeDef *huart);
void OpticMag_UART_RxCpltCallback(UART_HandleTypeDef *huart);
void OpticMag_UART_ErrorCallback(UART_HandleTypeDef *huart);
#endif // OPTIC_MAG_DRIVER_H
+10 -1
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)
{ {