/** ****************************************************************************** * @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; }