Compare commits
4
Commits
973dfad5c7
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
29cc814384 | ||
|
|
047ef08318 | ||
|
|
bc37e14fba | ||
|
|
4f8feccc06 |
@@ -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()` 即可
|
||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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 */
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
+151
-132
@@ -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;
|
||||||
CorrectedDataPacket_t g_corrected_packet;
|
DataPacketWithOptic_t g_data_packet_with_optic;
|
||||||
CorrectedDataPacketWithGPS_t g_corrected_packet_with_gps; // 带GPS信息的数据包
|
CorrectedDataPacket_t g_corrected_packet;
|
||||||
|
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 can_store_data = 0;
|
uint8_t optic_fresh = g_optic_mag_fresh;
|
||||||
if (g_recording_enabled) {
|
uint32_t optic_val = g_optic_mag_value;
|
||||||
uint32_t max_packet_size = sizeof(CorrectedDataPacketWithGPS_t); // 使用带GPS的数据包大小
|
if (optic_fresh) g_optic_mag_fresh = 0;
|
||||||
can_store_data = DataStorage_IsBufferAvailable(&g_data_storage, max_packet_size);
|
|
||||||
}
|
// 检查存储缓冲区是否可用(取最大包长为基准)
|
||||||
|
uint8_t can_store_data = 0;
|
||||||
|
if (g_recording_enabled) {
|
||||||
|
can_store_data = DataStorage_IsBufferAvailable(&g_data_storage,
|
||||||
|
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数据(时间戳始终使用,位置字段受ENABLE_GPS_POSITION控制)
|
||||||
|
GPS_Data_t current_gps_data;
|
||||||
|
uint8_t gps_valid = GPS_GetData(¤t_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;
|
||||||
|
|
||||||
|
CorrectionResult_t correction_result;
|
||||||
|
if (g_correction_params.params_valid &&
|
||||||
|
Apply_Correction(raw_adc[0], raw_adc[1], raw_adc[2],
|
||||||
|
&correction_result, &g_correction_params) == HAL_OK) {
|
||||||
|
|
||||||
|
if (optic_fresh) {
|
||||||
|
PackCorrectedDataWithOptic(&g_corrected_packet_with_optic,
|
||||||
|
correction_result.corrected_x, correction_result.corrected_y,
|
||||||
|
correction_result.corrected_z, gps_time, lat, lon, alt, optic_val);
|
||||||
|
send_ptr = (const uint8_t *)&g_corrected_packet_with_optic;
|
||||||
|
send_size = sizeof(CorrectedDataPacketWithOptic_t);
|
||||||
|
} else {
|
||||||
|
PackCorrectedDataWithGPS(&g_corrected_packet_with_gps,
|
||||||
|
correction_result.corrected_x, correction_result.corrected_y,
|
||||||
|
correction_result.corrected_z, gps_time, lat, lon, alt);
|
||||||
|
send_ptr = (const uint8_t *)&g_corrected_packet_with_gps;
|
||||||
|
send_size = sizeof(CorrectedDataPacketWithGPS_t);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 获取当前GPS数据
|
} else {
|
||||||
GPS_Data_t current_gps_data;
|
|
||||||
uint8_t gps_valid = GPS_GetData(¤t_gps_data);
|
|
||||||
|
|
||||||
// 3. 应用校正算法
|
if (optic_fresh) {
|
||||||
CorrectionResult_t correction_result;
|
PackDataWithOptic(&g_data_packet_with_optic,
|
||||||
uint8_t correction_applied = 0;
|
raw_adc[0], raw_adc[1], raw_adc[2],
|
||||||
|
gps_time, lat, lon, alt, optic_val);
|
||||||
if (g_correction_params.params_valid &&
|
send_ptr = (const uint8_t *)&g_data_packet_with_optic;
|
||||||
Apply_Correction(raw_adc[0], raw_adc[1], raw_adc[2],
|
send_size = sizeof(DataPacketWithOptic_t);
|
||||||
&correction_result, &g_correction_params) == HAL_OK) {
|
} else {
|
||||||
|
PackData(&g_data_packet,
|
||||||
// 4a. 打包校正后的数据(带GPS关键信息:仅经纬度)
|
raw_adc[0], raw_adc[1], raw_adc[2],
|
||||||
// float lat = gps_valid ? (float)current_gps_data.position.latitude : 0.0f;
|
gps_time, lat, lon, alt);
|
||||||
// float lon = gps_valid ? (float)current_gps_data.position.longitude : 0.0f;
|
send_ptr = (const uint8_t *)&g_data_packet;
|
||||||
// float alt = gps_valid ? (float)current_gps_data.position.altitude : 0.0f;
|
send_size = sizeof(DataPacket_t);
|
||||||
float lat = (float)current_gps_data.position.latitude;
|
|
||||||
float lon = (float)current_gps_data.position.longitude;
|
|
||||||
float alt = (float)current_gps_data.position.altitude;
|
|
||||||
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,
|
|
||||||
correction_result.corrected_x,
|
|
||||||
correction_result.corrected_y,
|
|
||||||
correction_result.corrected_z,
|
|
||||||
gps_time,
|
|
||||||
lat,
|
|
||||||
lon,
|
|
||||||
alt);
|
|
||||||
|
|
||||||
correction_applied = 1;
|
|
||||||
|
|
||||||
// 发送校正后的数据包到串口(运行时配置)
|
|
||||||
if (Config_IsUartOutputEnabled()) {
|
|
||||||
RS485_SendData((uint8_t*)&g_corrected_packet_with_gps, sizeof(CorrectedDataPacketWithGPS_t));
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
// 4b. 校正失败或未启用,使用原始数据
|
|
||||||
// float lat = gps_valid ? (float)current_gps_data.position.latitude : 0.0f;
|
|
||||||
// float lon = gps_valid ? (float)current_gps_data.position.longitude : 0.0f;
|
|
||||||
// float alt = gps_valid ? (float)current_gps_data.position.altitude : 0.0f;
|
|
||||||
float lat = (float)current_gps_data.position.latitude;
|
|
||||||
float lon = (float)current_gps_data.position.longitude;
|
|
||||||
float alt = (float)current_gps_data.position.altitude;
|
|
||||||
uint32_t gps_time = gps_valid ? (current_gps_data.time.hour * 10000 +
|
|
||||||
current_gps_data.time.minute * 100 +
|
|
||||||
current_gps_data.time.second) : 0;
|
|
||||||
|
|
||||||
PackData(&g_data_packet, raw_adc[0], raw_adc[1], raw_adc[2], gps_time, lat, lon, alt);
|
|
||||||
|
|
||||||
// 发送原始数据包到串口(运行时配置)
|
|
||||||
if (Config_IsUartOutputEnabled()) {
|
|
||||||
RS485_SendData((uint8_t*)&g_data_packet, sizeof(DataPacket_t));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 6. 存储数据到SD卡 (如果启用记录且缓冲区可用,运行时配置)
|
// 4. 发送到RS485串口
|
||||||
if (Config_IsStorageEnabled() && g_recording_enabled) {
|
if (Config_IsUartOutputEnabled()) {
|
||||||
if (can_store_data) {
|
RS485_SendData((uint8_t *)send_ptr, send_size);
|
||||||
if (correction_applied) {
|
}
|
||||||
// 存储校正后的数据(带GPS信息)
|
|
||||||
DataStorage_WriteCorrectedData(&g_data_storage, (CorrectedDataPacket_t*)&g_corrected_packet_with_gps);
|
// 5. 存储到SD卡(方案Y:直接写原始字节流,与串口格式一致)
|
||||||
} else {
|
if (Config_IsStorageEnabled() && g_recording_enabled) {
|
||||||
// 存储原始数据
|
if (can_store_data) {
|
||||||
DataStorage_WriteData(&g_data_storage, &g_data_packet);
|
DataStorage_WriteRawBytes(&g_data_storage, send_ptr, send_size);
|
||||||
}
|
} else {
|
||||||
} else {
|
|
||||||
// 缓冲区满,数据被丢弃
|
|
||||||
#if ENABLE_SYSTEM_MONITOR
|
#if ENABLE_SYSTEM_MONITOR
|
||||||
SystemMonitor_ReportDataDropped();
|
SystemMonitor_ReportDataDropped();
|
||||||
#endif
|
#endif
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 7. 释放已处理的缓冲区
|
|
||||||
LTC2508_ReleaseBuffer(LTC2508_GetCurrentReadBuffer());
|
|
||||||
} else {
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 6. 释放已处理的缓冲区
|
||||||
|
LTC2508_ReleaseBuffer(LTC2508_GetCurrentReadBuffer());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -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,32 +411,48 @@ 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)
|
|
||||||
{
|
if (mount_result == FR_OK) {
|
||||||
DebugOutput_SendString("No filesystem found, formatting...\r\n");
|
// 挂载成功,验证文件系统类型和簇大小是否符合目标格式
|
||||||
|
if (SDFatFS.fs_type != FS_FAT32) {
|
||||||
// 格式化为FAT32
|
DebugOutput_SendString("Not FAT32, reformatting...\r\n");
|
||||||
FRESULT format_result = f_mkfs(SDPath, FM_FAT32, 0, NULL, 0);
|
f_mount(NULL, SDPath, 0);
|
||||||
|
need_format = 1;
|
||||||
if (format_result == FR_OK) {
|
} else if (SDFatFS.csize != TARGET_SECTORS_PER_CLUSTER) {
|
||||||
DebugOutput_SendString("Format successful, remounting...\r\n");
|
DebugOutput_SendString("Cluster size mismatch, reformatting...\r\n");
|
||||||
mount_result = f_mount(&SDFatFS, SDPath, 1);
|
f_mount(NULL, SDPath, 0);
|
||||||
if (mount_result != FR_OK) {
|
need_format = 1;
|
||||||
DebugOutput_SendString("Remount after format failed\r\n");
|
}
|
||||||
return HAL_ERROR;
|
} else if (mount_result == FR_NO_FILESYSTEM) {
|
||||||
}
|
DebugOutput_SendString("No filesystem found, formatting...\r\n");
|
||||||
} else {
|
need_format = 1;
|
||||||
DebugOutput_SendString("Format failed\r\n");
|
} 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) {
|
||||||
|
DebugOutput_SendString("Format successful, remounting...\r\n");
|
||||||
|
mount_result = f_mount(&SDFatFS, SDPath, 1);
|
||||||
|
|
||||||
|
if (mount_result != FR_OK) {
|
||||||
|
DebugOutput_SendString("Remount after format failed\r\n");
|
||||||
return HAL_ERROR;
|
return HAL_ERROR;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
DebugOutput_SendString("Mount failed with other error\r\n");
|
DebugOutput_SendString("Format failed\r\n");
|
||||||
return HAL_ERROR;
|
return HAL_ERROR;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -519,15 +524,19 @@ 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 */
|
||||||
|
|
||||||
// 初始化系统监控
|
// 初始化系统监控
|
||||||
#if ENABLE_SYSTEM_MONITOR
|
#if ENABLE_SYSTEM_MONITOR
|
||||||
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
@@ -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.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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 |
@@ -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.
File diff suppressed because it is too large
Load Diff
@@ -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.
@@ -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
|
||||||
@@ -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
|
||||||
|
|
||||||
// 函数声明
|
// 函数声明
|
||||||
|
|||||||
+76
-92
@@ -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->adc_data1 = adc1;
|
||||||
packet->start_byte = PACKET_START_BYTE;
|
packet->adc_data2 = adc2;
|
||||||
|
packet->adc_data3 = adc3;
|
||||||
// 设置时间戳
|
packet->gps_time = gps_time;
|
||||||
// packet->timestamp = HAL_GetTick();
|
FILL_GPS_POSITION(packet, latitude, longitude, altitude);
|
||||||
|
|
||||||
// 设置ADC数据
|
|
||||||
packet->adc_data1 = adc1;
|
|
||||||
packet->adc_data2 = adc2;
|
|
||||||
packet->adc_data3 = adc3;
|
|
||||||
|
|
||||||
// 设置GPS数据
|
|
||||||
packet->gps_time = gps_time;
|
|
||||||
packet->gps_latitude = latitude;
|
|
||||||
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
@@ -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
|
||||||
|
|||||||
+104
-115
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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: 数据存储句柄指针
|
||||||
@@ -177,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,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;
|
||||||
@@ -416,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;
|
|
||||||
}
|
|
||||||
|
|||||||
+11
-10
@@ -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
|
||||||
|
|||||||
@@ -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, §or_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;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
+65
-3
@@ -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 (度)
|
||||||
|
|||||||
+21
-2
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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
|
||||||
+11
-2
@@ -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)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user