NMEA(xxGGA)报文解析(FPGA实现)
最近接触GPS,需要使用FPGA进行NMEA报文的解析,以获得经纬度和时间信息,我选用的报文是xxGGA,包含GPGGA(GPS系统的)、GBGGA(北斗系统的)、GLGGA(GLONASS系统的)、GAGGA(伽利略系统的),GNGGA(任意GNSS系统组合)。他们的格式完全相同,不同之处仅在于报文头,xxGGA报文格式如下。$xxGGA,time,lat,NS,lon,EW,quality,numSV,HDOP,alt,altUnit,sep,sepUnit,diffAge,diffStation*cs<CR><LF>
我们需要关注的数据域如下
time:UTC时间,hhmmss.ss格式,如132253.27表示UTC时间 13时22分53.27秒,需要注意的是.ss表示秒的小数域(2位小数),而非毫秒
lat:纬度,ddmm.mmmm格式,如3124.73251表示 31度24.73251分,1度=60分
NS:指示南北半球,北半球为‘N’,南半球为‘S’
lon:经度,dddmm.mmmmm格式,如13424.73251表示 134度24.73251分
EW:指示东西半球,东半球为‘E’,西半球为‘W’
alt:海拔,-9999.9~9999.9
altUnit:海拔单位,‘M’表示以 米 为单位 例如:
$GPGGA,092725.00,4717.11399,N,00833.91590,E,1,08,1.01,499.6,M,48.0,M,,*5B
表示 UTC时间 09 时 27 分 25.00 秒,北纬 47 度 17.11399 分,东经 8 度 33.91590 分,海拔 499.6 米 本xxGGA解析模块使用UART串口数据,以UART模块发出的rx_done信号驱动。
xxGGA报文解析模块: /******************************FILE HEAD**********************************
* file_name : parseGGA.v
* function : 解析xxGGA报文,获取UTC时间、经纬度、海拔
* author : 今朝无言
* version & date : 2021/10/14 & v1.0
*************************************************************************/
module parseGGA(
input rx_done_toUart, //整个模块由rx_done_toUart驱动
input rddat_toUart,
output reg rx_done, //接收指令结束,上升沿对齐'\n'字符出现时刻,下降沿对齐$xxGGA后面的','的出现时刻的下一时刻
output reg hh, //UTC时间,整数,时0~24
output reg mm, //UTC时间,整数,分0~59
output reg ss, //UTC时间,整数,秒0~59
output reg ss2, //UTC时间,小数,秒2位小数,0~99
output reg lat, //纬度整数部分,度0~90
output reg lat2, //纬度整数部分,分0~59
output reg lat3, //纬度小数部分,分5位小数,0~99999
output reg NS, //区分南北纬,北纬标为1,南纬标为0
output reg lon, //经度整数部分,度0~180
output reg lon2, //经度整数部分,分0~59
output reg lon3, //经度小数部分,分5位小数,0~99999
output reg EW, //区分东西经,东经标为1,西经标为0
output reg alt, //海拔整数部分,m
output reg alt2 //海拔小数部分,一位小数0~9
);
//xxGGA格式: $xxGGA,time,lat,NS,lon,EW,quality,numSV,HDOP,alt,altUnit,sep,sepUnit,diffAge,diffStation*cs<CR><LF>
//time格式: hhmmss.ss
//lat格式: ddmm.mmmmm
//lon格式: dddmm.mmmmm
//alt格式: numeric,一位小数
reg cntField; //当前读取第几个域,以','分隔
reg cntChar; //当前读取域中的第几个字符
reg start = 1'b0; //NMEA报文的接收标志,以$开始,到\n结束
reg charBuffer;
reg corrNum = 2'd0; //比对是否为xxGGA,当corrNum=3时,表示"GGA"字符通过测试,该条报文即xxGGA
wire num; //若charBuffer为字符0~9,则将之转换为数字0~9
wire isnum;
reg afterDot; //判断是否是"."后面的数字,在解析海拔时用到
//将字符0~9转换为数字0~9
Char2Num Char2Num_inst(
.Char(charBuffer),
.Num(num),
.isNum(isnum)
);
always @(posedge rx_done_toUart) begin
charBuffer <= rddat_toUart;
//-----------------------接收NMEA报文数据-----------------------------
if(rddat_toUart == "$") begin //接收到$,标志着NMEA数据的起始
start <= 1'b1;
cntField <= 5'd0;
cntChar <= 4'd0;
corrNum <= 2'd0;
end
else if(start) begin
if(rddat_toUart == "\n") begin //收到\n,标志NMEA报文结束
start <= 1'b0;
rx_done <= 1'b1;
end
else if(rddat_toUart == "," ||
rddat_toUart == "*") begin //收到','或'*',为域的分隔符
cntField <= cntField + 1'b1;
cntChar <= 4'd0;
end
else begin //收到其他字符
cntChar <= cntChar + 1'b1;
end
end
else begin
start <= 1'b0;
cntField <= 5'd0;
cntChar <= 4'd0;
corrNum <= 2'd0;
end
//------------------------判断是否为xxGGA----------------------------
if(cntField == 5'd0) begin
if(cntChar == 4'd3 && charBuffer == "G") begin
corrNum <= corrNum + 1'b1;
end
else if(cntChar == 4'd4 && charBuffer == "G") begin
corrNum <= corrNum + 1'b1;
end
else if(cntChar == 4'd5 && charBuffer == "A") begin
corrNum <= corrNum + 1'b1;
end
end
if(corrNum == 2'd3) begin //检测到是"xxGGA",开启解析
rx_done <= 1'b0;
corrNum <= 2'b0;
end
//---------------------------解析xxGGA------------------------------
if(rx_done == 1'b0) begin
//解析UTC时间
if(cntField == 5'd1) begin
if(cntChar == 4'd1) begin //UTC-hh
hh <= num*4'd10;
end
else if(cntChar == 4'd2) begin
hh <= hh + num;
end
else if(cntChar == 4'd3) begin //UTC-mm
mm <= num*4'd10;
end
else if(cntChar == 4'd4) begin
mm <= mm + num;
end
else if(cntChar == 4'd5) begin //UTC-ss
ss <= num*4'd10;
end
else if(cntChar == 4'd6) begin
ss <= ss + num;
end
else if(cntChar == 4'd8) begin //UTC-.ss
ss2 <= num*4'd10;
end
else if(cntChar == 4'd9) begin
ss2 <= ss2 + num;
end
end
//解析纬度
if(cntField == 5'd2) begin
if(cntChar == 4'd1) begin //lat-dd
lat <= num*4'd10;
end
else if(cntChar == 4'd2) begin
lat <= lat + num;
end
else if(cntChar == 4'd3) begin //lat-mm
lat2 <= num*4'd10;
end
else if(cntChar == 4'd4) begin
lat2 <= lat2 + num;
end
else if(cntChar == 4'd6) begin //lat-.mmmmm
lat3 <= num;
end
else if(cntChar == 4'd7 || cntChar == 4'd8 ||
cntChar == 4'd9 || cntChar == 4'd10) begin
lat3 <= lat3*4'd10 + num;
end
end
if(cntField == 5'd3 && cntChar == 4'd1) begin //NS
if(charBuffer == "N") begin
NS <= 1'b1;
end
else begin
NS <= 1'b0;
end
end
//解析经度
if(cntField == 5'd4) begin
if(cntChar == 4'd1) begin //lon-ddd
lon <= num;
end
else if(cntChar == 4'd2 || cntChar == 4'd3) begin
lon <= lon*4'd10 + num;
end
else if(cntChar == 4'd4) begin //lon-mm
lon2 <= num*4'd10;
end
else if(cntChar == 4'd5) begin
lon2 <= lon2 + num;
end
else if(cntChar == 4'd7) begin //lon-.mmmmm
lon3 <= num;
end
else if(cntChar == 4'd8 || cntChar == 4'd9 ||
cntChar == 4'd10 || cntChar == 4'd11) begin
lon3 <= lon3*4'd10 + num;
end
end
if(cntField == 5'd5 && cntChar == 4'd1) begin //EW
if(charBuffer == "E") begin
EW <= 1'b1;
end
else begin
EW <= 1'b0;
end
end
//解析海拔
if(cntField == 5'd9) begin
if(cntChar == 4'd1) begin
alt <= num;
afterDot <= 1'b0;
end
else if(charBuffer==".") begin
afterDot <= 1'b1;
alt2 <= 4'd0;
end
else begin
if(~afterDot) begin
alt <= alt*4'd10 + num; //alt-MMM
end
else begin
alt2 <= alt2*4'd10 +num; //alt-.M
end
end
end
end
end
endmodule
//END OF parseGGA.v FILE***************************************************
字符-数字转换模块:
/******************************FILE HEAD**********************************
* file_name : Char2Num.v
* function : 若Char为字符0~9,将之转化为数字0~9
* author : 今朝无言
* version & date : 2021/10/14 & v1.0
*************************************************************************/
module Char2Num(
input Char,
output Num,
output reg isNum
);
always@(*)begin
case(Char)
"0": isNum <= 1;
"1": isNum <= 1;
"2": isNum <= 1;
"3": isNum <= 1;
"4": isNum <= 1;
"5": isNum <= 1;
"6": isNum <= 1;
"7": isNum <= 1;
"8": isNum <= 1;
"9": isNum <= 1;
default: isNum <= 0;
endcase
end
assign Num = isNum? Char - "0" : 4'hff;
endmodule
//END OF Char2Num.v FILE***************************************************
testbench:
/******************************FILE HEAD**********************************
* file_name : parseGGA_tb.v
* function : 解析xxGGA报文,获取UTC时间、经纬度、海拔
* author : 今朝无言
* version & date : 2021/10/14 & v1.0
*************************************************************************/
`default_nettype none
`timescale 1ns/1ps
module parseGGA_tb;
reg data = {"$GNGGA,092725.00,4717.11399,N,00833.91590,E,1,08,1.01,499.6,M,48.0,M,,*5B",8'd13,8'd10}; //\r\n, \r=13,\n=10
reg rx_done_toUart; //整个模块由rx_done_toUart驱动
reg rddat_toUart;
wire rx_done; //接收指令结束,上升沿对齐'\n'字符出现时刻,下降沿对齐$xxGGA后面的','的出现时刻
wire hh; //UTC时间,整数,时0~24
wire mm; //UTC时间,整数,分0~59
wire ss; //UTC时间,整数,秒0~59
wire ss2; //UTC时间,小数,秒2位小数,0~99
wire lat; //纬度整数部分,度0~90
wire lat2; //纬度整数部分,分0~59
wire lat3; //纬度小数部分,分5位小数,0~99999
wire NS; //区分南北纬,北纬标为1,南纬标为0
wire lon; //经度整数部分,度0~180
wire lon2; //经度整数部分,分0~59
wire lon3; //经度小数部分,分5位小数,0~99999
wire EW; //区分东西经,东经标为1,西经标为0
wire alt; //海拔整数部分,m
wire alt2; //海拔小数部分,一位小数0~9
reg i;
initial begin
rx_done_toUart <= 0;
#50;
for(i=0;i<=74*8;i=i+8)begin
rddat_toUart <= {data,data,data,data,
data,data,data,data};
#5;
rx_done_toUart <= 1;
#50;
rx_done_toUart <=0;
#50;
end
#200;
$stop;
end
//解析xxGGA报文
parseGGA parseGGA_inst(
.rx_done_toUart (rx_done_toUart),
.rddat_toUart (rddat_toUart),
.rx_done (rx_done),
.hh (hh),
.mm (mm),
.ss (ss),
.ss2 (ss2),
.lat (lat),
.lat2 (lat2),
.lat3 (lat3),
.NS (NS),
.lon (lon),
.lon2 (lon2),
.lon3 (lon3),
.EW (EW),
.alt (alt),
.alt2 (alt2)
);
endmodule
//END OF parseGGA_tb.v FILE***************************************************
ModelSim仿真结果:
页:
[1]