- 新增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驱动头文件包含
385 lines
12 KiB
C
385 lines
12 KiB
C
/**
|
||
******************************************************************************
|
||
* @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;
|
||
}
|