打印
[MM32软件]

NMEA(xxGGA)报文解析(FPGA实现)

[复制链接]
346|10
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
最近接触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>


使用特权

评论回复
评论
gaonaiweng 2022-6-29 23:33 回复TA
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://blog.csdn.net/qq_43557686/article/details/123893672 ———————————————— 版权声明:本文为CSDN博主「今朝无言」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/qq_43557686/article/details/123893672 
沙发
gaonaiweng|  楼主 | 2022-6-29 23:33 | 只看该作者
我们需要关注的数据域如下

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’表示以 米 为单位

使用特权

评论回复
板凳
gaonaiweng|  楼主 | 2022-6-29 23:35 | 只看该作者
例如:
$GPGGA,092725.00,4717.11399,N,00833.91590,E,1,08,1.01,499.6,M,48.0,M,,*5B

使用特权

评论回复
地板
gaonaiweng|  楼主 | 2022-6-29 23:39 | 只看该作者
表示 UTC时间 09 时 27 分 25.00 秒,北纬 47 度 17.11399 分,东经 8 度 33.91590 分,海拔 499.6 米

使用特权

评论回复
5
gaonaiweng|  楼主 | 2022-6-29 23:40 | 只看该作者
本xxGGA解析模块使用UART串口数据,以UART模块发出的rx_done信号驱动。

xxGGA报文解析模块:

使用特权

评论回复
6
gaonaiweng|  楼主 | 2022-6-29 23:41 | 只看该作者
/******************************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~24
        output        reg        [5:0]        mm,                                //UTC时间,整数,分  0~59
        output        reg        [5:0]        ss,                                //UTC时间,整数,秒  0~59
        output        reg        [6:0]        ss2,                        //UTC时间,小数,秒  2位小数,0~99
       
        output        reg        [6:0]        lat,                        //纬度  整数部分,度  0~90
        output        reg        [5:0]        lat2,                        //纬度  整数部分,分  0~59
        output        reg        [16:0]        lat3,                        //纬度  小数部分,分  5位小数,0~99999
        output        reg                        NS,                                //区分南北纬,北纬标为1,南纬标为0
       
        output        reg        [7:0]        lon,                        //经度  整数部分,度  0~180
        output        reg        [5:0]        lon2,                        //经度  整数部分,分  0~59
        output        reg        [16:0]        lon3,                        //经度  小数部分,分  5位小数,0~99999
        output        reg                        EW,                                //区分东西经,东经标为1,西经标为0
       
        output        reg [13:0]        alt,                        //海拔  整数部分,m
        output        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"字符通过测试,该条报文即xxGGA

wire        [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) 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***************************************************

使用特权

评论回复
7
gaonaiweng|  楼主 | 2022-6-29 23:42 | 只看该作者
字符-数字转换模块:
/******************************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@(*)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***************************************************

使用特权

评论回复
8
gaonaiweng|  楼主 | 2022-6-29 23:42 | 只看该作者
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        [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=10

reg                                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~99

wire        [6:0]        lat;                        //纬度  整数部分,度  0~90
wire        [5:0]        lat2;                        //纬度  整数部分,分  0~59
wire        [16:0]        lat3;                        //纬度  小数部分,分  5位小数,0~99999
wire                        NS;                                //区分南北纬,北纬标为1,南纬标为0

wire        [7:0]        lon;                        //经度  整数部分,度  0~180
wire        [5:0]        lon2;                        //经度  整数部分,分  0~59
wire        [16:0]        lon3;                        //经度  小数部分,分  5位小数,0~99999
wire                        EW;                                //区分东西经,东经标为1,西经标为0

wire        [13:0]        alt;                        //海拔  整数部分,m
wire        [3:0]        alt2;                        //海拔  小数部分,一位小数  0~9

reg                [9:0]        i;
initial begin
        rx_done_toUart        <= 0;
        #50;
       
        for(i=0;i<=74*8;i=i+8)begin
                rddat_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***************************************************

使用特权

评论回复
9
gaonaiweng|  楼主 | 2022-6-29 23:43 | 只看该作者
ModelSim仿真结果:

使用特权

评论回复
10
gaonaiweng|  楼主 | 2022-6-29 23:45 | 只看该作者

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

59

主题

591

帖子

1

粉丝