STM_ATEM/User/gps_driver.c
zhoujie 5b245f89c1 feat(gps): 集成GPS模块支持数据采集系统
- 新增GPS驱动模块,支持NMEA GPGGA/GNGGA语句解析
- 修改USART3配置,波特率从2000000调整为115200用于GPS数据接收
- 新增带GPS信息的校正数据包结构`CorrectedDataPacketWithGPS_t`
- 在ADC数据处理流程中集成GPS数据获取和打包
- 更新数据包处理函数,支持GPS时间戳和经纬度信息
- 新增GPS驱动使用指南和集成说明文档
- 修改主循环,添加GPS数据处理调用
- 更新中断处理,添加GPS UART接收回调支持

📝 docs(gps): 添加GPS驱动和集成说明文档

- 新增`GPS_Driver_Guide.md`详细说明GPS驱动API和使用方法
- 新增`GPS_Integration_Guide.md`说明GPS数据集成到采集系统的实现细节
- 文档包含硬件连接、数据格式、使用示例和故障排除等内容

♻️ refactor(data): 重构数据包结构以支持GPS信息

- 修改`DataPacket_t`和`CorrectedDataPacket_t`结构,添加GPS时间戳和经纬度字段
- 新增`CorrectedDataPacketWithGPS_t`结构用于带完整GPS信息的数据包
- 更新数据打包函数,支持GPS参数传递
- 简化数据包验证逻辑,移除校验和检查以提高处理速度

🔧 chore(config): 更新硬件配置文件

- 更新STM32CubeMX项目文件,修改USART3波特率配置
- 在中断处理文件中添加GPS驱动头文件包含
2026-02-07 19:34:48 +08:00

385 lines
12 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
******************************************************************************
* @file gps_driver.c
* @brief GPS NMEA数据接收和解析驱动实现
* @author Your Name
* @date 2026-02-07
******************************************************************************
*/
/* Includes ------------------------------------------------------------------*/
#include "gps_driver.h"
/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
static uint8_t gps_rx_buffer[GPS_RX_BUFFER_SIZE]; // DMA接收缓冲区
static uint8_t gps_rx_byte; // 单字节接收缓冲
static char gps_nmea_buffer[GPS_NMEA_MAX_LENGTH]; // NMEA语句缓冲区
static uint16_t gps_nmea_index = 0; // NMEA缓冲区索引
static GPS_Data_t gps_data; // GPS数据
/* Private function prototypes -----------------------------------------------*/
static void GPS_ParseNMEA(char *nmea);
static void GPS_ParseGPGGA(char *nmea);
static double GPS_ConvertToDecimal(const char *coord, char direction);
/* Exported functions --------------------------------------------------------*/
/**
* @brief 初始化GPS驱动
* @retval HAL状态
*/
HAL_StatusTypeDef GPS_Init(void)
{
HAL_StatusTypeDef status;
// 清空GPS数据
memset(&gps_data, 0, sizeof(GPS_Data_t));
gps_data.data_valid = 0;
// 清空缓冲区
memset(gps_rx_buffer, 0, GPS_RX_BUFFER_SIZE);
memset(gps_nmea_buffer, 0, GPS_NMEA_MAX_LENGTH);
gps_nmea_index = 0;
// 启动UART接收单字节中断接收
status = HAL_UART_Receive_IT(&GPS_UART_HANDLE, &gps_rx_byte, 1);
if (status == HAL_OK) {
// 可选启用UART空闲中断
__HAL_UART_ENABLE_IT(&GPS_UART_HANDLE, UART_IT_IDLE);
}
return status;
}
/**
* @brief GPS数据接收处理在主循环中调用
* @retval None
*/
void GPS_Process(void)
{
// 检查数据是否超时
if (gps_data.data_valid) {
uint32_t current_tick = HAL_GetTick();
if ((current_tick - gps_data.last_update_tick) > GPS_DATA_TIMEOUT_MS) {
gps_data.data_valid = 0; // 数据超时,标记为无效
}
}
}
/**
* @brief 获取GPS数据
* @param gps_data_out: 指向GPS数据结构体的指针
* @retval 1=数据有效, 0=数据无效或超时
*/
uint8_t GPS_GetData(GPS_Data_t *gps_data_out)
{
if (gps_data_out == NULL) {
return 0;
}
// 复制GPS数据
memcpy(gps_data_out, &gps_data, sizeof(GPS_Data_t));
return gps_data.data_valid;
}
/**
* @brief 检查GPS数据是否有效
* @retval 1=有效, 0=无效
*/
uint8_t GPS_IsDataValid(void)
{
return gps_data.data_valid;
}
/**
* @brief 获取GPS时间字符串
* @param buffer: 输出缓冲区
* @param size: 缓冲区大小
* @retval None
*/
void GPS_GetTimeString(char *buffer, uint16_t size)
{
if (buffer == NULL || size == 0) {
return;
}
if (gps_data.data_valid) {
snprintf(buffer, size, "%02d:%02d:%02d.%03d UTC",
gps_data.time.hour,
gps_data.time.minute,
gps_data.time.second,
gps_data.time.millisec);
} else {
snprintf(buffer, size, "No GPS Time");
}
}
/**
* @brief 获取GPS位置字符串
* @param buffer: 输出缓冲区
* @param size: 缓冲区大小
* @retval None
*/
void GPS_GetPositionString(char *buffer, uint16_t size)
{
if (buffer == NULL || size == 0) {
return;
}
if (gps_data.data_valid) {
snprintf(buffer, size, "Lat: %.6f%c, Lon: %.6f%c, Alt: %.1fm, Sats: %d",
gps_data.position.latitude,
gps_data.position.lat_direction,
gps_data.position.longitude,
gps_data.position.lon_direction,
gps_data.position.altitude,
gps_data.position.satellites);
} else {
snprintf(buffer, size, "No GPS Position");
}
}
/**
* @brief UART接收完成回调函数
* @param huart: UART句柄
* @retval None
*/
void GPS_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == GPS_UART_HANDLE.Instance) {
// 处理接收到的字节
char received_char = (char)gps_rx_byte;
// 检测NMEA语句开始标志 '$'
if (received_char == '$') {
gps_nmea_index = 0;
gps_nmea_buffer[gps_nmea_index++] = received_char;
}
// 检测NMEA语句结束标志 '\n'
else if (received_char == '\n') {
if (gps_nmea_index > 0 && gps_nmea_index < GPS_NMEA_MAX_LENGTH) {
gps_nmea_buffer[gps_nmea_index] = '\0'; // 字符串结束符
GPS_ParseNMEA(gps_nmea_buffer); // 解析NMEA语句
gps_nmea_index = 0; // 重置索引
}
}
// 累积NMEA语句字符
else if (gps_nmea_index > 0 && gps_nmea_index < (GPS_NMEA_MAX_LENGTH - 1)) {
if (received_char != '\r') { // 忽略回车符
gps_nmea_buffer[gps_nmea_index++] = received_char;
}
}
// 继续接收下一个字节
HAL_UART_Receive_IT(&GPS_UART_HANDLE, &gps_rx_byte, 1);
}
}
/**
* @brief UART空闲中断回调函数
* @param huart: UART句柄
* @retval None
*/
void GPS_UART_IdleCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == GPS_UART_HANDLE.Instance) {
// 可以在这里处理空闲中断
// 目前使用单字节接收,不需要特殊处理
}
}
/* Private functions ---------------------------------------------------------*/
/**
* @brief 解析NMEA语句
* @param nmea: NMEA语句字符串
* @retval None
*/
static void GPS_ParseNMEA(char *nmea)
{
if (nmea == NULL || nmea[0] != '$') {
return;
}
// 检查是否为GPGGA或GNGGA语句
if (strncmp(nmea, "$GPGGA", 6) == 0 || strncmp(nmea, "$GNGGA", 6) == 0) {
GPS_ParseGPGGA(nmea);
}
// 可以添加其他NMEA语句的解析如GPRMC等
}
/**
* @brief 解析GPGGA语句
* @param nmea: GPGGA语句字符串
* @retval None
*
* GPGGA格式示例:
* $GPGGA,123519.00,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47
*
* 字段说明:
* 0: $GPGGA
* 1: UTC时间 (hhmmss.ss)
* 2: 纬度 (ddmm.mmmm)
* 3: 纬度方向 (N/S)
* 4: 经度 (dddmm.mmmm)
* 5: 经度方向 (E/W)
* 6: 定位质量 (0=无效, 1=GPS, 2=DGPS, etc.)
* 7: 使用的卫星数量
* 8: HDOP水平精度因子
* 9: 海拔高度
* 10: 高度单位 (M)
* 11: 大地水准面高度
* 12: 高度单位 (M)
* 13: 差分GPS数据年龄
* 14: 差分参考站ID
* 15: 校验和
*/
static void GPS_ParseGPGGA(char *nmea)
{
char *token;
char *saveptr;
int field_index = 0;
char temp_buffer[32];
// 使用strtok_r进行字符串分割线程安全
token = strtok_r(nmea, ",", &saveptr);
while (token != NULL) {
switch (field_index) {
case 1: // UTC时间
if (strlen(token) >= 6) {
// 解析时间 hhmmss.ss
char hour_str[3] = {token[0], token[1], '\0'};
char min_str[3] = {token[2], token[3], '\0'};
char sec_str[3] = {token[4], token[5], '\0'};
gps_data.time.hour = (uint8_t)atoi(hour_str);
gps_data.time.minute = (uint8_t)atoi(min_str);
gps_data.time.second = (uint8_t)atoi(sec_str);
// 解析毫秒(如果有)
if (strlen(token) > 7 && token[6] == '.') {
char ms_str[4] = {0};
strncpy(ms_str, &token[7], 3);
gps_data.time.millisec = (uint16_t)atoi(ms_str);
} else {
gps_data.time.millisec = 0;
}
}
break;
case 2: // 纬度
if (strlen(token) > 0) {
strncpy(temp_buffer, token, sizeof(temp_buffer) - 1);
temp_buffer[sizeof(temp_buffer) - 1] = '\0';
}
break;
case 3: // 纬度方向
if (strlen(token) > 0) {
gps_data.position.lat_direction = token[0];
// 转换纬度为十进制度
gps_data.position.latitude = GPS_ConvertToDecimal(temp_buffer, token[0]);
}
break;
case 4: // 经度
if (strlen(token) > 0) {
strncpy(temp_buffer, token, sizeof(temp_buffer) - 1);
temp_buffer[sizeof(temp_buffer) - 1] = '\0';
}
break;
case 5: // 经度方向
if (strlen(token) > 0) {
gps_data.position.lon_direction = token[0];
// 转换经度为十进制度
gps_data.position.longitude = GPS_ConvertToDecimal(temp_buffer, token[0]);
}
break;
case 6: // 定位质量
if (strlen(token) > 0) {
gps_data.position.fix_status = (GPS_FixStatus_t)atoi(token);
}
break;
case 7: // 卫星数量
if (strlen(token) > 0) {
gps_data.position.satellites = (uint8_t)atoi(token);
}
break;
case 8: // HDOP
if (strlen(token) > 0) {
gps_data.position.hdop = (float)atof(token);
}
break;
case 9: // 海拔高度
if (strlen(token) > 0) {
gps_data.position.altitude = atof(token);
}
break;
default:
break;
}
token = strtok_r(NULL, ",", &saveptr);
field_index++;
}
// 更新数据有效标志和时间戳
if (gps_data.position.fix_status > GPS_FIX_INVALID) {
gps_data.data_valid = 1;
gps_data.last_update_tick = HAL_GetTick();
} else {
gps_data.data_valid = 0;
}
}
/**
* @brief 将GPS坐标格式转换为十进制度
* @param coord: GPS坐标字符串 (ddmm.mmmm 或 dddmm.mmmm)
* @param direction: 方向字符 (N/S/E/W)
* @retval 十进制度数
*
* 示例:
* 输入: "4807.038", 'N'
* 输出: 48.1173 (度)
*/
static double GPS_ConvertToDecimal(const char *coord, char direction)
{
if (coord == NULL || strlen(coord) < 4) {
return 0.0;
}
double value = atof(coord);
// 判断是纬度还是经度纬度2位度数经度3位度数
int degree_digits = (strlen(coord) >= 5 && coord[4] == '.') ? 2 : 3;
// 提取度数和分钟
double degrees = (int)(value / 100.0);
double minutes = value - (degrees * 100.0);
// 转换为十进制度
double decimal = degrees + (minutes / 60.0);
// 根据方向调整符号
if (direction == 'S' || direction == 'W') {
decimal = -decimal;
}
return decimal;
}