[MM32软件] NMEA(xxGGA)报文解析(FPGA实现)

[复制链接]
1027|10
 楼主| gaonaiweng 发表于 2022-6-29 23:22 | 显示全部楼层 |阅读模式
最近接触GPS,需要使用FPGA进行NMEA报文的解析,以获得经纬度和时间信息,我选用的报文是xxGGA,包含GPGGA(GPS系统的)、GBGGA(北斗系统的)、GLGGA(GLONASS系统的)、GAGGA(伽利略系统的),GNGGA(任意GNSS系统组合)。他们的格式完全相同,不同之处仅在于报文头,xxGGA报文格式如下。
  1. $xxGGA,time,lat,NS,lon,EW,quality,numSV,HDOP,alt,altUnit,sep,sepUnit,diffAge,diffStation*cs<CR><LF>


评论

版权声明:本文为博主原创文章,遵循 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  发表于 2022-6-29 23:33
 楼主| 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 | 显示全部楼层
例如:
  1. $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 米
 楼主| gaonaiweng 发表于 2022-6-29 23:40 | 显示全部楼层
本xxGGA解析模块使用UART串口数据,以UART模块发出的rx_done信号驱动。

xxGGA报文解析模块:
 楼主| gaonaiweng 发表于 2022-6-29 23:41 | 显示全部楼层
  1. /******************************FILE HEAD**********************************
  2. * file_name         : parseGGA.v
  3. * function          : 解析xxGGA报文,获取UTC时间、经纬度、海拔
  4. * author            : 今朝无言
  5. * version & date    : 2021/10/14 & v1.0
  6. *************************************************************************/
  7. module parseGGA(
  8.         input                                rx_done_toUart,        //整个模块由rx_done_toUart驱动
  9.         input                [7:0]        rddat_toUart,
  10.        
  11.         output        reg                 rx_done,                //接收指令结束,上升沿对齐'\n'字符出现时刻,下降沿对齐$xxGGA后面的','的出现时刻的下一时刻
  12.        
  13.         output        reg        [4:0]        hh,                                //UTC时间,整数,时  0~24
  14.         output        reg        [5:0]        mm,                                //UTC时间,整数,分  0~59
  15.         output        reg        [5:0]        ss,                                //UTC时间,整数,秒  0~59
  16.         output        reg        [6:0]        ss2,                        //UTC时间,小数,秒  2位小数,0~99
  17.        
  18.         output        reg        [6:0]        lat,                        //纬度  整数部分,度  0~90
  19.         output        reg        [5:0]        lat2,                        //纬度  整数部分,分  0~59
  20.         output        reg        [16:0]        lat3,                        //纬度  小数部分,分  5位小数,0~99999
  21.         output        reg                        NS,                                //区分南北纬,北纬标为1,南纬标为0
  22.        
  23.         output        reg        [7:0]        lon,                        //经度  整数部分,度  0~180
  24.         output        reg        [5:0]        lon2,                        //经度  整数部分,分  0~59
  25.         output        reg        [16:0]        lon3,                        //经度  小数部分,分  5位小数,0~99999
  26.         output        reg                        EW,                                //区分东西经,东经标为1,西经标为0
  27.        
  28.         output        reg [13:0]        alt,                        //海拔  整数部分,m
  29.         output        reg [3:0]        alt2                        //海拔  小数部分,一位小数  0~9
  30. );
  31. //xxGGA格式: $xxGGA,time,lat,NS,lon,EW,quality,numSV,HDOP,alt,altUnit,sep,sepUnit,diffAge,diffStation*cs<CR><LF>
  32. //time格式: hhmmss.ss
  33. //lat格式: ddmm.mmmmm
  34. //lon格式: dddmm.mmmmm
  35. //alt格式: numeric,一位小数

  36. reg                [4:0]        cntField;                //当前读取第几个域,以','分隔
  37. reg         [3:0]        cntChar;                //当前读取域中的第几个字符

  38. reg                                start        = 1'b0;        //NMEA报文的接收标志,以$开始,到\n结束
  39. reg                [7:0]        charBuffer;
  40. reg                [1:0]        corrNum        = 2'd0;        //比对是否为xxGGA,当corrNum=3时,表示"GGA"字符通过测试,该条报文即xxGGA

  41. wire        [3:0]        num;                        //若charBuffer为字符0~9,则将之转换为数字0~9
  42. wire                        isnum;

  43. reg                                afterDot;                //判断是否是"."后面的数字,在解析海拔时用到

  44. //将字符0~9转换为数字0~9
  45. Char2Num Char2Num_inst(
  46.         .Char(charBuffer),
  47.         .Num(num),
  48.         .isNum(isnum)
  49. );

  50. always @(posedge rx_done_toUart) begin
  51.         charBuffer        <= rddat_toUart;

  52.         //-----------------------接收NMEA报文数据-----------------------------
  53.         if(rddat_toUart == "$") begin                        //接收到$,标志着NMEA数据的起始
  54.                 start                <= 1'b1;
  55.                 cntField        <= 5'd0;
  56.                 cntChar                <= 4'd0;
  57.                 corrNum                <= 2'd0;
  58.         end
  59.         else if(start) begin
  60.                 if(rddat_toUart == "\n") begin                //收到\n,标志NMEA报文结束
  61.                         start                <= 1'b0;
  62.                         rx_done                <= 1'b1;
  63.                 end
  64.                 else if(rddat_toUart == "," ||
  65.                                 rddat_toUart == "*") begin        //收到','或'*',为域的分隔符
  66.                         cntField        <= cntField + 1'b1;
  67.                         cntChar                <= 4'd0;
  68.                 end
  69.                 else begin                                                        //收到其他字符
  70.                         cntChar                <= cntChar + 1'b1;
  71.                 end
  72.         end
  73.         else begin
  74.                 start                <= 1'b0;
  75.                 cntField        <= 5'd0;
  76.                 cntChar                <= 4'd0;
  77.                 corrNum                <= 2'd0;
  78.         end
  79.        
  80.         //------------------------判断是否为xxGGA----------------------------
  81.         if(cntField == 5'd0) begin
  82.                 if(cntChar == 4'd3 && charBuffer == "G") begin
  83.                         corrNum <= corrNum + 1'b1;
  84.                 end
  85.                 else if(cntChar == 4'd4 && charBuffer == "G") begin
  86.                         corrNum <= corrNum + 1'b1;
  87.                 end
  88.                 else if(cntChar == 4'd5 && charBuffer == "A") begin
  89.                         corrNum <= corrNum + 1'b1;
  90.                 end
  91.         end
  92.         if(corrNum == 2'd3) begin        //检测到是"xxGGA",开启解析
  93.                 rx_done <= 1'b0;
  94.                 corrNum        <= 2'b0;
  95.         end
  96.        
  97.         //---------------------------解析xxGGA------------------------------
  98.         if(rx_done == 1'b0) begin
  99.                 //解析UTC时间
  100.                 if(cntField == 5'd1) begin
  101.                         if(cntChar == 4'd1) begin                //UTC-hh
  102.                                 hh        <= num*4'd10;
  103.                         end
  104.                         else if(cntChar == 4'd2) begin
  105.                                 hh        <= hh + num;
  106.                         end
  107.                         else if(cntChar == 4'd3) begin        //UTC-mm
  108.                                 mm        <= num*4'd10;
  109.                         end
  110.                         else if(cntChar == 4'd4) begin
  111.                                 mm        <= mm + num;
  112.                         end
  113.                         else if(cntChar == 4'd5) begin        //UTC-ss
  114.                                 ss        <= num*4'd10;
  115.                         end
  116.                         else if(cntChar == 4'd6) begin
  117.                                 ss        <= ss + num;
  118.                         end
  119.                         else if(cntChar == 4'd8) begin        //UTC-.ss
  120.                                 ss2        <= num*4'd10;
  121.                         end
  122.                         else if(cntChar == 4'd9) begin
  123.                                 ss2        <= ss2 + num;
  124.                         end
  125.                 end
  126.                
  127.                 //解析纬度
  128.                 if(cntField == 5'd2) begin
  129.                         if(cntChar == 4'd1) begin                //lat-dd
  130.                                 lat                <= num*4'd10;
  131.                         end
  132.                         else if(cntChar == 4'd2) begin
  133.                                 lat                <= lat + num;
  134.                         end
  135.                         else if(cntChar == 4'd3) begin        //lat-mm
  136.                                 lat2        <= num*4'd10;
  137.                         end
  138.                         else if(cntChar == 4'd4) begin
  139.                                 lat2        <= lat2 + num;
  140.                         end
  141.                         else if(cntChar == 4'd6) begin        //lat-.mmmmm
  142.                                 lat3        <= num;
  143.                         end
  144.                         else if(cntChar == 4'd7 || cntChar == 4'd8 ||
  145.                                         cntChar == 4'd9 || cntChar == 4'd10) begin
  146.                                 lat3        <= lat3*4'd10 + num;
  147.                         end
  148.                 end
  149.                 if(cntField == 5'd3 && cntChar == 4'd1) begin        //NS
  150.                         if(charBuffer == "N") begin
  151.                                 NS        <= 1'b1;
  152.                         end
  153.                         else begin
  154.                                 NS        <= 1'b0;
  155.                         end
  156.                 end
  157.                
  158.                 //解析经度
  159.                 if(cntField == 5'd4) begin
  160.                         if(cntChar == 4'd1) begin                //lon-ddd
  161.                                 lon                <= num;
  162.                         end
  163.                         else if(cntChar == 4'd2 || cntChar == 4'd3) begin
  164.                                 lon                <= lon*4'd10 + num;
  165.                         end
  166.                         else if(cntChar == 4'd4) begin        //lon-mm
  167.                                 lon2        <= num*4'd10;
  168.                         end
  169.                         else if(cntChar == 4'd5) begin
  170.                                 lon2        <= lon2 + num;
  171.                         end
  172.                         else if(cntChar == 4'd7) begin        //lon-.mmmmm
  173.                                 lon3        <= num;
  174.                         end
  175.                         else if(cntChar == 4'd8 || cntChar == 4'd9 ||
  176.                                         cntChar == 4'd10 || cntChar == 4'd11) begin
  177.                                 lon3        <= lon3*4'd10 + num;
  178.                         end
  179.                 end
  180.                 if(cntField == 5'd5 && cntChar == 4'd1) begin        //EW
  181.                         if(charBuffer == "E") begin
  182.                                 EW        <= 1'b1;
  183.                         end
  184.                         else begin
  185.                                 EW        <= 1'b0;
  186.                         end
  187.                 end
  188.                
  189.                 //解析海拔
  190.                 if(cntField == 5'd9) begin
  191.                         if(cntChar == 4'd1) begin
  192.                                 alt                        <= num;
  193.                                 afterDot        <= 1'b0;
  194.                         end
  195.                         else if(charBuffer==".") begin
  196.                                 afterDot        <= 1'b1;
  197.                                 alt2                <= 4'd0;
  198.                         end
  199.                         else begin
  200.                                 if(~afterDot) begin
  201.                                         alt                <= alt*4'd10 + num;                //alt-MMM
  202.                                 end
  203.                                 else begin
  204.                                         alt2        <= alt2*4'd10 +num;                //alt-.M
  205.                                 end
  206.                         end
  207.                 end
  208.         end
  209. end

  210. endmodule
  211. //END OF parseGGA.v FILE***************************************************

 楼主| gaonaiweng 发表于 2022-6-29 23:42 | 显示全部楼层
字符-数字转换模块:
  1. /******************************FILE HEAD**********************************
  2. * file_name         : Char2Num.v
  3. * function          : 若Char为字符0~9,将之转化为数字0~9
  4. * author            : 今朝无言
  5. * version & date    : 2021/10/14 & v1.0
  6. *************************************************************************/
  7. module Char2Num(
  8.         input                 [7:0]        Char,
  9.         output                [3:0]        Num,
  10.         output        reg                        isNum
  11. );

  12. always@(*)begin
  13.         case(Char)
  14.                 "0": isNum <= 1;
  15.                 "1": isNum <= 1;
  16.                 "2": isNum <= 1;
  17.                 "3": isNum <= 1;
  18.                 "4": isNum <= 1;
  19.                 "5": isNum <= 1;
  20.                 "6": isNum <= 1;
  21.                 "7": isNum <= 1;
  22.                 "8": isNum <= 1;
  23.                 "9": isNum <= 1;
  24.                 default: isNum <= 0;
  25.         endcase
  26. end

  27. assign Num = isNum? Char - "0" : 4'hff;

  28. endmodule
  29. //END OF Char2Num.v FILE***************************************************

 楼主| gaonaiweng 发表于 2022-6-29 23:42 | 显示全部楼层
testbench:
  1. /******************************FILE HEAD**********************************
  2. * file_name         : parseGGA_tb.v
  3. * function          : 解析xxGGA报文,获取UTC时间、经纬度、海拔
  4. * author            : 今朝无言
  5. * version & date    : 2021/10/14 & v1.0
  6. *************************************************************************/
  7. `default_nettype none
  8. `timescale 1ns/1ps

  9. module parseGGA_tb;

  10. 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

  11. reg                                rx_done_toUart;        //整个模块由rx_done_toUart驱动
  12. reg                [7:0]        rddat_toUart;

  13. wire                         rx_done;                //接收指令结束,上升沿对齐'\n'字符出现时刻,下降沿对齐$xxGGA后面的','的出现时刻

  14. wire        [4:0]        hh;                                //UTC时间,整数,时  0~24
  15. wire        [5:0]        mm;                                //UTC时间,整数,分  0~59
  16. wire        [5:0]        ss;                                //UTC时间,整数,秒  0~59
  17. wire        [6:0]        ss2;                        //UTC时间,小数,秒  2位小数,0~99

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

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

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

  28. reg                [9:0]        i;
  29. initial begin
  30.         rx_done_toUart        <= 0;
  31.         #50;
  32.        
  33.         for(i=0;i<=74*8;i=i+8)begin
  34.                 rddat_toUart        <= {data[i],data[i+1],data[i+2],data[i+3],
  35.                                                         data[i+4],data[i+5],data[i+6],data[i+7]};
  36.                 #5;
  37.                 rx_done_toUart        <= 1;
  38.                 #50;
  39.                 rx_done_toUart        <=0;
  40.                 #50;
  41.         end
  42.        
  43.         #200;
  44.         $stop;
  45. end

  46. //解析xxGGA报文
  47. parseGGA parseGGA_inst(
  48.         .rx_done_toUart        (rx_done_toUart),
  49.         .rddat_toUart        (rddat_toUart),
  50.         .rx_done                (rx_done),
  51.         .hh                                (hh),
  52.         .mm                                (mm),
  53.         .ss                                (ss),
  54.         .ss2                        (ss2),
  55.         .lat                        (lat),
  56.         .lat2                        (lat2),
  57.         .lat3                        (lat3),
  58.         .NS                                (NS),
  59.         .lon                        (lon),
  60.         .lon2                        (lon2),
  61.         .lon3                        (lon3),
  62.         .EW                                (EW),
  63.         .alt                        (alt),
  64.         .alt2                        (alt2)
  65. );

  66. endmodule
  67. //END OF parseGGA_tb.v FILE***************************************************

 楼主| gaonaiweng 发表于 2022-6-29 23:43 | 显示全部楼层
ModelSim仿真结果:
 楼主| gaonaiweng 发表于 2022-6-29 23:45 | 显示全部楼层
您需要登录后才可以回帖 登录 | 注册

本版积分规则

80

主题

875

帖子

3

粉丝
快速回复 在线客服 返回列表 返回顶部