这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 活动中心 » 板卡试用 » 【PocketBeagle2】九,GPS

共1条 1/1 1 跳转至

【PocketBeagle2】九,GPS

菜鸟
2025-11-03 09:41:08     打赏

【PocketBeagle2】九,GPS

一,硬件部分

本次我们选择USART0.TX和USART0.RX作为与GPS的通讯口,原理图如下

image.png

实物图连接如下

image.pngimage-20251102234333042

二,软件部分

由于我的gps模块波特率为38400,所以需要修改一下/dev/ttyS0的波特率

# 查看当前设置
stty -F /dev/ttyS0
# 修改波特率为38400
stty -F /dev/ttyS0 38400

验证模块是否正常工作

确保模块供电正常后,在终端输入以下命令,监听模块发来的信息。

cat /dev/ttyS0

image-20251102234542195image.png

NMEA0183协议解析

我这里以GNGGA举例做一下简单的介绍,其标准协议为:

标准格式:$GPGGA,<1>,<2>,<3>,<4>,<5>,<6>,<7>,<8>,<9>,M,<10>,M,<11>,<12>*hh<CR><LF>

格式解析:
  <1> UTC 时间,hhmmss(时分秒)格式
  <2> 纬度ddmm.mmmm(度分)格式(前面的0 也将被传输)
  <3> 纬度半球N(北半球)或S(南半球
  <4> 经度dddmm.mmmm(度分)格式(前面的0 也将被传输)
  <5> 经度半球E(东经)或W(西经)
  <6> GPS 状态:0=未定位,1=非差分定位,2=差分定位,6=正在估算
  <7> 正在使用解算位置的卫星数量(00~12)(前面的0 也将被传输)
  <8> HDOP 水平精度因子(0.5~99.9)
  <9> 海拔高度(-9999.9~99999.9)
  <10> 地球椭球面相对大地水准面的高度
  <11> 差分时间(从最近一次接收到差分信号开始的秒数,如果不是差分定位将为空)
  <12> 差分站ID 号0000~1023(前面的0 也将被传输,如果不是差分定位将为空

代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
#include <math.h>
#include <ctype.h>

// 初始化串口
int init_serial(const char *port) {
   int fd = open(port, O_RDWR | O_NOCTTY);
   if (fd < 0) {
       perror("打开串口失败");
       return -1;
   }
   
   struct termios options;
   tcgetattr(fd, &options);
   
   // 配置串口参数
   cfsetispeed(&options, B38400);
   cfsetospeed(&options, B38400);
   
   options.c_cflag &= ~PARENB;     // 无奇偶校验
   options.c_cflag &= ~CSTOPB;     // 1位停止位
   options.c_cflag &= ~CSIZE;      // 清除数据位掩码
   options.c_cflag |= CS8;         // 8位数据位
   options.c_cflag |= CREAD;       // 启用接收
   
   // 原始模式输入
   options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
   options.c_iflag &= ~(IXON | IXOFF | IXANY); // 禁用软件流控
   options.c_oflag &= ~OPOST;      // 原始输出
   
   // 设置超时
   options.c_cc[VMIN] = 0;
   options.c_cc[VTIME] = 5; // 0.5秒超时
   
   tcsetattr(fd, TCSANOW, &options);
   return fd;
}

// 将度分格式转换为十进制度
double nmea_to_decimal(double nmea_coord) {
   double deg = floor(nmea_coord / 100);
   double min = nmea_coord - (deg * 100);
   return deg + (min / 60.0);
}

// 解析GGA语句 - 基础位置信息
void parse_gga(const char *sentence) {
   char time[11] = {0}, ns = '\0', ew = '\0'; // 增加时间字段宽度
   double lat = 0, lon = 0, alt = 0;
   int fix = 0, sat = 0;
   
   // 示例: $GNGGA,100806.800,2246.2334,N,11349.7316,E,1,11,2.5,48.1,M,-2.6,M,,0000*6F
   if (sscanf(sentence, "$%*2sGGA,%10[^,],%lf,%c,%lf,%c,%d,%d,%*f,M,%*f,M,,%*s",
             time, &lat, &ns, &lon, &ew, &fix, &sat) >= 7) {
       double latitude = nmea_to_decimal(lat);
       double longitude = nmea_to_decimal(lon);
       if (ns == 'S') latitude = -latitude;
       if (ew == 'W') longitude = -longitude;
       
       // 从原始字符串中提取高度值(更可靠的方式)
       char* alt_ptr = strstr(sentence, ",M,");
       if (alt_ptr) {
           alt_ptr += 3; // 跳过 ",M,"
           if (sscanf(alt_ptr, "%lf", &alt) != 1) {
               alt = 0.0; // 如果解析失败则使用默认值
           }
       }
       
       printf("\n=== GGA 基础信息 ===\n");
       printf("UTC时间: %s\n", time);
       printf("纬度: %.6f° (%c)\n", fabs(latitude), ns);
       printf("经度: %.6f° (%c)\n", fabs(longitude), ew);
       printf("海拔高度: %.1f米\n", alt);
       printf("定位质量: %d (1=有效定位)\n", fix);
       printf("使用卫星数: %d\n", sat);
       printf("===================\n");
   } else {
       printf("GGA解析失败: %s\n", sentence);
   }
}

// 解析RMC语句 - 推荐最小定位信息
void parse_rmc(const char *sentence) {
   char time[11] = {0}, status, ns, ew, date[7] = {0}; // 增加时间字段宽度
   double lat = 0, lon = 0, speed_knots = 0, course_deg = 0;
   
   // 示例: $GNRMC,100806.600,A,2246.2334,N,11349.7316,E,000.0,000.0,090825,,,A*76
   if (sscanf(sentence, "$%*2sRMC,%10[^,],%c,%lf,%c,%lf,%c,%lf,%lf,%6s",
             time, &status, &lat, &ns, &lon, &ew, &speed_knots, &course_deg, date) >= 8) {
       double latitude = nmea_to_decimal(lat);
       double longitude = nmea_to_decimal(lon);
       if (ns == 'S') latitude = -latitude;
       if (ew == 'W') longitude = -longitude;
       
       printf("\n=== RMC 定位信息 ===\n");
       printf("UTC时间: %s\n", time);
       printf("UTC日期: %s\n", date);
       printf("定位状态: %c (A=有效, V=无效)\n", status);
       printf("纬度: %.6f° (%c)\n", fabs(latitude), ns);
       printf("经度: %.6f° (%c)\n", fabs(longitude), ew);
       printf("地面速度: %.1f km/h (%.1f 节)\n", speed_knots * 1.852, speed_knots);
       printf("航向: %.1f°\n", course_deg);
       printf("===================\n");
   } else {
       printf("RMC解析失败: %s\n", sentence);
   }
}

// 解析GSA语句 - 精度因子(使用更健壮的解析方法)
void parse_gsa(const char *sentence) {
   char copy[128];
   strncpy(copy, sentence, sizeof(copy) - 1);
   copy[sizeof(copy) - 1] = '\0';
   
   char *tokens[20] = {0};
   int count = 0;
   
   // 分割字符串
   char *token = strtok(copy, ",");
   while (token && count < 20) {
       tokens[count++] = token;
       token = strtok(NULL, ",");
   }
   
   // 验证字段数并提取DOP值
   if (count >= 15) {
       char mode = tokens[1][0]; // 模式字段
       int fix_type = atoi(tokens[2]); // 定位类型
       
       // 最后三个字段是PDOP, HDOP, VDOP
       float pdop = atof(tokens[count-3]);
       float hdop = atof(tokens[count-2]);
       float vdop = atof(tokens[count-1]);
       
       printf("\n=== GSA 精度因子 ===\n");
       printf("模式: %c (M=手动, A=自动)\n", mode);
       printf("定位类型: %d (1=无定位, 2=2D, 3=3D)\n", fix_type);
       printf("位置精度因子(PDOP): %.1f\n", pdop);
       printf("水平精度因子(HDOP): %.1f\n", hdop);
       printf("垂直精度因子(VDOP): %.1f\n", vdop);
       printf("===================\n");
   } else {
       printf("GSA解析失败: %s\n", sentence);
   }
}

// 解析GSV语句 - 卫星信息
void parse_gsv(const char *sentence) {
   int total_msgs = 0, msg_num = 0, total_sats = 0;
   char system[3] = {0};
   
   // 示例: $GPGSV,3,1,12,10,60,181,40,32,58,027,30,28,55,328,21,31,42,282,06*73
   if (sscanf(sentence, "$%2sGSV,%d,%d,%d", system, &total_msgs, &msg_num, &total_sats) >= 4) {
       printf("\n=== %sGSV 卫星信息 (%d/%d) ===\n", system, msg_num, total_msgs);
       printf("可见卫星总数: %d\n", total_sats);
       
       // 提取卫星信息
       const char *p = strchr(sentence, ',');
       for (int i = 0; i < 4; i++) p = strchr(p + 1, ','); // 跳过前4个字段
       
       int sat_count = 0;
       for (int i = 0; i < 4; i++) {
           int prn = 0, elevation = 0, azimuth = 0, snr = 0;
           if (p && sscanf(p, ",%d,%d,%d,%d", &prn, &elevation, &azimuth, &snr) >= 4) {
               printf("卫星: PRN=%d, 仰角=%d°, 方位角=%d°, 信噪比=%ddB\n",
                      prn, elevation, azimuth, snr);
               sat_count++;
               
               // 移动到下一组
               for (int j = 0; j < 4 && p; j++) {
                   p = strchr(p + 1, ',');
                   if (!p) break;
               }
           } else {
               break;
           }
       }
       printf("=======================\n");
   } else {
       printf("GSV解析失败: %s\n", sentence);
   }
}

int main(int argc, char *argv[]) {
   if (argc < 2) {
       printf("用法: %s <串口设备>\n", argv[0]);
       return 1;
   }
   
   int fd = init_serial(argv[1]);
   if (fd < 0) return 1;
   
   char buffer[256];
   int buffer_pos = 0;
   
   printf("开始接收GPS数据,解析基础信息...\n");
   printf("正在等待数据,请确保GPS天线位置良好...\n");
   
   while (1) {
       char c;
       ssize_t n = read(fd, &c, 1);
       
       if (n > 0) {
           // 如果接收到换行符,处理完整的一行
           if (c == '\n') {
               if (buffer_pos > 0) {
                   // 去除行尾的回车和换行符
                   while (buffer_pos > 0 &&
                         (buffer[buffer_pos-1] == '\r' ||
                          buffer[buffer_pos-1] == '\n')) {
                       buffer_pos--;
                   }
                   buffer[buffer_pos] = '\0';
                   
                   // 打印原始数据用于调试
                   printf("原始数据: %s\n", buffer);
                   
                   // 根据语句类型调用解析函数
                   if (strstr(buffer, "GGA") != NULL) {
                       parse_gga(buffer);
                   }
                   else if (strstr(buffer, "RMC") != NULL) {
                       parse_rmc(buffer);
                   }
                   else if (strstr(buffer, "GSA") != NULL) {
                       parse_gsa(buffer);
                   }
                   else if (strstr(buffer, "GSV") != NULL) {
                       parse_gsv(buffer);
                   }
                   
                   buffer_pos = 0;
               }
           }
           // 否则添加到行缓冲区
           else if (c == '$') {
               buffer_pos = 0;
               buffer[buffer_pos++] = c;
           }
           else if (buffer_pos < sizeof(buffer) - 1) {
               buffer[buffer_pos++] = c;
           }
       } else if (n == 0) {
           // 没有数据时短暂休眠
           sleep(1); // 1s
       }
   }
   
   close(fd);
   return 0;
}


三,演示

将文件编译copy到开发板上运行

# 编译
gcc gps.c -lm
# 运行
./a.out /dev/tty


将gps放置到室外,由于我这里有有源天线,在窗口就可以搜到卫星,成功解析到的内容如下:

95caad76c8c0be37c6e06f662bc8ad38image.png


image.png


共1条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]