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

实物图连接如下


二,软件部分
由于我的gps模块波特率为38400,所以需要修改一下/dev/ttyS0的波特率
# 查看当前设置
stty -F /dev/ttyS0
# 修改波特率为38400
stty -F /dev/ttyS0 38400
验证模块是否正常工作
确保模块供电正常后,在终端输入以下命令,监听模块发来的信息。
cat /dev/ttyS0


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放置到室外,由于我这里有有源天线,在窗口就可以搜到卫星,成功解析到的内容如下:



我要赚赏金
