一、硬件连接

在这里插入图片描述

模块引脚 连接目标 说明
TX 串口助手接收端(RX) 交叉连接,用于模块发送数据到上位机
RX 不接 测试阶段无需发送指令,可悬空
VCC 5V/3.3V 根据模块版本选择:多数ATGM336H型号需5V供电(具体以规格书为准)
GND GND 必须与上位机共地,否则会导致数据乱码或通信失败
注意:
1、模块上的led灯闪烁时,相当于提示定位信息获取到了,常量表示定位失效。
2、断电重启模块,更新信息状态。
3、定位时,天线上的陶瓷片向上(陶瓷片上有金属小圆点一面朝向天空),朝向空旷的天空,高楼会遮挡信号导致定位不到。
4、模块的通信默认用9600波特率通信的。

二、代码部分

定义部分

#define DMA_BUF_SIZE       256   // DMA接收缓冲区大小
#define NMEA_QUEUE_LEN     8    // NMEA帧队列长度
#define GNGGA_HEAD         "$GNGGA"
#define LAT_LON_SCALE      60.0f // 度分转度的系数
#define GNGGA_MIN_FIELD_CNT 6
typedef struct {
    uint8_t  valid;       // 定位有效标志(1有效,0无效)
    char     time[7];     // UTC时间(HHMMSS)
    float    latitude;    // 纬度(度)
    char     lat_dir;     // 纬度方向(N/S)
    float    longitude;   // 经度(度)
    char     lon_dir;     // 经度方向(E/W)
    uint8_t  sat_num;     // 锁定卫星数(新增,适配GNGGA)
    uint8_t  parse_ok;    // 解析成功标志
} GnssInfo_t;
extern GnssInfo_t gnss_info ;
float nmea_degree_convert(char *str); // 度分转度
extern uint8_t dma_rx_buf[DMA_BUF_SIZE];
extern volatile uint16_t dma_rx_len;

  // 开启USART2空闲中断
     __HAL_UART_ENABLE_IT(&huart2, UART_IT_IDLE);
     // 启动DMA接收(循环模式)
     HAL_UART_Receive_DMA(&huart2, dma_rx_buf, DMA_BUF_SIZE);
     
void USART2_IRQHandler(void)
{
    HAL_UART_IRQHandler(&huart2);
    USART2_IDLE_IRQHandler(); // 处理空闲中断
}

// 打印定位信息
void USART2_IDLE_IRQHandler(void)
{
    if(__HAL_UART_GET_FLAG(&huart2, UART_FLAG_IDLE) != RESET)
    {
        __HAL_UART_CLEAR_IDLEFLAG(&huart2); // 清除空闲中断标志
        HAL_UART_DMAStop(&huart2);          // 停止DMA接收
        // 计算接收长度
        dma_rx_len = DMA_BUF_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart2_rx);
    }
}

float nmea_degree_convert(char *str)
{
    if (str == NULL || strlen(str) < 4 || strchr(str, '.') == NULL)
    {
        return 0.0f;
    }

    float degree = 0.0f;
    float minute = 0.0f;
    char temp[16] = {0};
    int dot_pos = strchr(str, '.') - str;

    // 纬度:DDMM.mmmm → 前2位度
    if (strlen(str) >= 4 && dot_pos >= 2)
    {
        strncpy(temp, str, 2);
        degree = atof(temp);
        minute = atof(str + 2);
    }
    // 经度:DDDMM.mmmm → 前3位度
    else if (strlen(str) >= 5 && dot_pos >= 3)
    {
        strncpy(temp, str, 3);
        degree = atof(temp);
        minute = atof(str + 3);
    }
    else
    {
        return 0.0f;
    }

    // 防止转换后为0(有效经纬度不可能为0)
    float res = degree + (minute / LAT_LON_SCALE);
    return (res > 0.0f) ? res : 0.0f;
}

任务调用部分

//获取信息
void StartTask01(void *argument)
{
  uint8_t nmea_buf[DMA_BUF_SIZE] = {0};
  for (;;)
 {

    if (dma_rx_len > 0)
	{
	  // 拷贝DMA接收的数据到临时缓冲区
	  memcpy (nmea_buf, dma_rx_buf, dma_rx_len);
	  nmea_buf[dma_rx_len] = '\0'; // 字符串结束符
	  //输出串口接收信息
	  HAL_UART_Transmit (&huart1, (uint8_t*) &dma_rx_buf, dma_rx_len, 8000);
	  printf ("\r\n");
	  // 发送到NMEA解析队列
	  osMessageQueuePut (myQueue01Handle, nmea_buf, 0, 100);
	  // 重置DMA接收
	  dma_rx_len = 0;
	  memset (dma_rx_buf, 0, DMA_BUF_SIZE);
	  HAL_UART_Receive_DMA (&huart2, dma_rx_buf, DMA_BUF_SIZE);
	}
   osDelay (5);
  }

}
//解析部分
void StartTask02 (void *argument)
{
  uint8_t nmea_buf[DMA_BUF_SIZE] ={ 0 };
  for (;;)
    {
      // 解析任务核心逻辑
      if (osMessageQueueGet(myQueue01Handle, nmea_buf, NULL, 100) == osOK)
      {
          gnss_info.parse_ok = 0;
          char *gngga_frame = strstr((char*)nmea_buf, GNGGA_HEAD);
          if (gngga_frame != NULL && strlen(gngga_frame) > 20)
          {
              char *token = NULL;
              char *save_ptr = NULL;
              int field_idx = 0;
              int field_cnt = 0;
              // 临时变量:先存储经纬度字符串,等定位质量解析后再转换
              char lat_str[16] = {0};
              char lon_str[16] = {0};
              token = strtok_r(gngga_frame, ",", &save_ptr);
              while (token != NULL)
              {
                  field_cnt++;
                  if (strlen(token) == 0)
                  {
                      token = strtok_r(NULL, ",", &save_ptr);
                      field_idx++;
                      continue;
                  }
                  switch (field_idx)
                  {
                      case 0: // $GNGGA:帧头校验
                          if (strcmp(token, GNGGA_HEAD) != 0)
                          {
                              field_idx = -1;
                              break;
                          }
                          break;
                      case 1: // UTC时间(HHMMSS.ss)
                          if (strlen(token) >= 6)
                          {
                              strncpy(gnss_info.time, token, 6);
                              gnss_info.time[6] = '\0';
                          }
                          else
                          {
                              memset(gnss_info.time, 0, sizeof(gnss_info.time));
                          }
                          break;
                      // 先存储经纬度字符串(不立即转换)
                      case 2: // 纬度字符串(DDMM.mmmm)
                          strncpy(lat_str, token, sizeof(lat_str)-1);
                          break;
                      case 3: // 纬度方向(N/S)
                          if (token[0] == 'N' || token[0] == 'S')
                          {
                              gnss_info.lat_dir = token[0];
                          }
                          else
                          {
                              gnss_info.lat_dir = '\0';
                          }
                          break;
                      case 4: // 经度字符串(DDDMM.mmmm)
                          strncpy(lon_str, token, sizeof(lon_str)-1);
                          break;
                      case 5: // 经度方向(E/W)
                          if (token[0] == 'E' || token[0] == 'W')
                          {
                              gnss_info.lon_dir = token[0];
                          }
                          else
                          {
                              gnss_info.lon_dir = '\0';
                          }
                          break;
                      case 6: // 定位质量(核心:先解析!)
                          gnss_info.valid = (atoi(token) == 1) ? 1 : 0;
                          // 定位有效时,再转换经纬度(关键修复!)
                          if (gnss_info.valid)
                          {
                              if (strlen(lat_str) >= 4)
                                  gnss_info.latitude = nmea_degree_convert(lat_str);
                              if (strlen(lon_str) >= 5)
                                  gnss_info.longitude = nmea_degree_convert(lon_str);
                          }
                          break;
                      case 7: // 卫星数
                          gnss_info.sat_num = atoi(token);
                          break;
                      default:
                          if (field_idx >= 8) // 解析到卫星数后停止
                          {
                              token = NULL;
                              break;
                          }
                          break;
                  }
                  if (field_idx == -1)
                  {
                      token = NULL;
                      break;
                  }
                  token = strtok_r(NULL, ",", &save_ptr);
                  field_idx++;
              }
              // 放宽最终校验条件(适配实际场景)
              if (field_cnt >= 7  // 至少7个字段(核心字段)
                  && gnss_info.valid == 1                // 定位质量=1
                  && gnss_info.lat_dir != '\0'           // 纬度方向有效
                  && gnss_info.lon_dir != '\0'           // 经度方向有效
                  && gnss_info.latitude > 0.0f           // 纬度>0(有效范围)
                  && gnss_info.longitude > 0.0f          // 经度>0(有效范围)
                  && gnss_info.sat_num >= 1)             // 卫星数≥1(放宽)
              {
                  gnss_info.parse_ok = 1;
                  // 【调试打印】解析成功提示
                  printf("解析成功!纬度:%.6f%c,经度:%.6f%c,utc时间:%s,定位质量=%d,卫星数=%d\r\n",
                         gnss_info.latitude, gnss_info.lat_dir,
                         gnss_info.longitude, gnss_info.lon_dir,gnss_info.time,gnss_info.valid, gnss_info.sat_num);
              }
              else
              {
                  memset(&gnss_info, 0, sizeof(GnssInfo_t));
                  printf("解析失败:字段数=%d,定位质量=%d,卫星数=%d\r\n",
                         field_cnt, gnss_info.valid, gnss_info.sat_num);
              }
          }
      }
      osDelay (10);
    }
}

测试效果示意图:

在这里插入图片描述
源码:https://download.csdn.net/download/weixin_44386927/92460926?spm=1001.2014.3001.5501

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐