本文主要是介绍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 [7:0] rddat_toUart,output reg rx_done, //接收指令结束,上升沿对齐'\n'字符出现时刻,下降沿对齐$xxGGA后面的','的出现时刻的下一时刻output reg [4:0] hh, //UTC时间,整数,时 0~24output reg [5:0] mm, //UTC时间,整数,分 0~59output reg [5:0] ss, //UTC时间,整数,秒 0~59output reg [6:0] ss2, //UTC时间,小数,秒 2位小数,0~99output reg [6:0] lat, //纬度 整数部分,度 0~90output reg [5:0] lat2, //纬度 整数部分,分 0~59output reg [16:0] lat3, //纬度 小数部分,分 5位小数,0~99999output reg NS, //区分南北纬,北纬标为1,南纬标为0output reg [7:0] lon, //经度 整数部分,度 0~180output reg [5:0] lon2, //经度 整数部分,分 0~59output reg [16:0] lon3, //经度 小数部分,分 5位小数,0~99999output reg EW, //区分东西经,东经标为1,西经标为0output reg [13:0] alt, //海拔 整数部分,moutput reg [3:0] 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 [4:0] cntField; //当前读取第几个域,以','分隔
reg [3:0] cntChar; //当前读取域中的第几个字符reg start = 1'b0; //NMEA报文的接收标志,以$开始,到\n结束
reg [7:0] charBuffer;
reg [1:0] corrNum = 2'd0; //比对是否为xxGGA,当corrNum=3时,表示"GGA"字符通过测试,该条报文即xxGGAwire [3:0] 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) begincharBuffer <= rddat_toUart;//-----------------------接收NMEA报文数据-----------------------------if(rddat_toUart == "$") begin //接收到$,标志着NMEA数据的起始start <= 1'b1;cntField <= 5'd0;cntChar <= 4'd0;corrNum <= 2'd0;endelse if(start) beginif(rddat_toUart == "\n") begin //收到\n,标志NMEA报文结束start <= 1'b0;rx_done <= 1'b1;endelse if(rddat_toUart == "," || rddat_toUart == "*") begin //收到','或'*',为域的分隔符cntField <= cntField + 1'b1;cntChar <= 4'd0;endelse begin //收到其他字符cntChar <= cntChar + 1'b1;endendelse beginstart <= 1'b0;cntField <= 5'd0;cntChar <= 4'd0;corrNum <= 2'd0;end//------------------------判断是否为xxGGA----------------------------if(cntField == 5'd0) beginif(cntChar == 4'd3 && charBuffer == "G") begincorrNum <= corrNum + 1'b1;endelse if(cntChar == 4'd4 && charBuffer == "G") begincorrNum <= corrNum + 1'b1;endelse if(cntChar == 4'd5 && charBuffer == "A") begincorrNum <= corrNum + 1'b1;endendif(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) beginif(cntChar == 4'd1) begin //UTC-hhhh <= num*4'd10;endelse if(cntChar == 4'd2) beginhh <= hh + num;endelse if(cntChar == 4'd3) begin //UTC-mmmm <= num*4'd10;endelse if(cntChar == 4'd4) beginmm <= mm + num;endelse if(cntChar == 4'd5) begin //UTC-ssss <= num*4'd10;endelse if(cntChar == 4'd6) beginss <= ss + num;endelse if(cntChar == 4'd8) begin //UTC-.ssss2 <= num*4'd10;endelse if(cntChar == 4'd9) beginss2 <= ss2 + num;endend//解析纬度if(cntField == 5'd2) beginif(cntChar == 4'd1) begin //lat-ddlat <= num*4'd10;endelse if(cntChar == 4'd2) beginlat <= lat + num;endelse if(cntChar == 4'd3) begin //lat-mmlat2 <= num*4'd10;endelse if(cntChar == 4'd4) beginlat2 <= lat2 + num;endelse if(cntChar == 4'd6) begin //lat-.mmmmmlat3 <= num;endelse if(cntChar == 4'd7 || cntChar == 4'd8 ||cntChar == 4'd9 || cntChar == 4'd10) beginlat3 <= lat3*4'd10 + num;endendif(cntField == 5'd3 && cntChar == 4'd1) begin //NSif(charBuffer == "N") beginNS <= 1'b1;endelse beginNS <= 1'b0;endend//解析经度if(cntField == 5'd4) beginif(cntChar == 4'd1) begin //lon-dddlon <= num;endelse if(cntChar == 4'd2 || cntChar == 4'd3) beginlon <= lon*4'd10 + num;endelse if(cntChar == 4'd4) begin //lon-mmlon2 <= num*4'd10;endelse if(cntChar == 4'd5) beginlon2 <= lon2 + num;endelse if(cntChar == 4'd7) begin //lon-.mmmmmlon3 <= num;endelse if(cntChar == 4'd8 || cntChar == 4'd9 ||cntChar == 4'd10 || cntChar == 4'd11) beginlon3 <= lon3*4'd10 + num;endendif(cntField == 5'd5 && cntChar == 4'd1) begin //EWif(charBuffer == "E") beginEW <= 1'b1;endelse beginEW <= 1'b0;endend//解析海拔if(cntField == 5'd9) beginif(cntChar == 4'd1) beginalt <= num;afterDot <= 1'b0;endelse if(charBuffer==".") beginafterDot <= 1'b1;alt2 <= 4'd0;endelse beginif(~afterDot) beginalt <= alt*4'd10 + num; //alt-MMMendelse beginalt2 <= alt2*4'd10 +num; //alt-.Mendendendend
endendmodule
//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 [7:0] Char,output [3:0] Num,output reg isNum
);always@(*)begincase(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
endassign 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/1psmodule parseGGA_tb;reg [0:75*8-1]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=10reg rx_done_toUart; //整个模块由rx_done_toUart驱动
reg [7:0] rddat_toUart;wire rx_done; //接收指令结束,上升沿对齐'\n'字符出现时刻,下降沿对齐$xxGGA后面的','的出现时刻wire [4:0] hh; //UTC时间,整数,时 0~24
wire [5:0] mm; //UTC时间,整数,分 0~59
wire [5:0] ss; //UTC时间,整数,秒 0~59
wire [6:0] ss2; //UTC时间,小数,秒 2位小数,0~99wire [6:0] lat; //纬度 整数部分,度 0~90
wire [5:0] lat2; //纬度 整数部分,分 0~59
wire [16:0] lat3; //纬度 小数部分,分 5位小数,0~99999
wire NS; //区分南北纬,北纬标为1,南纬标为0wire [7:0] lon; //经度 整数部分,度 0~180
wire [5:0] lon2; //经度 整数部分,分 0~59
wire [16:0] lon3; //经度 小数部分,分 5位小数,0~99999
wire EW; //区分东西经,东经标为1,西经标为0wire [13:0] alt; //海拔 整数部分,m
wire [3:0] alt2; //海拔 小数部分,一位小数 0~9reg [9:0] i;
initial beginrx_done_toUart <= 0;#50;for(i=0;i<=74*8;i=i+8)beginrddat_toUart <= {data[i],data[i+1],data[i+2],data[i+3],data[i+4],data[i+5],data[i+6],data[i+7]};#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仿真结果:
这篇关于NMEA(xxGGA)报文解析(FPGA实现)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!