1. 程式人生 > 實用技巧 >從NMEA0183到GNSS定位資料獲取(二)軟體篇

從NMEA0183到GNSS定位資料獲取(二)軟體篇

作者:良知猶存

轉載授權以及圍觀:歡迎新增微信公眾號:Conscience_Remains

總述

GPS我們都知道,一種用來全球定位的系統,後來俄羅斯推出了格洛納斯定位系統,中國推出了北斗定位,歐盟有伽利略,印度與日本也有有發展。所以後來把覆蓋全球的自主地利空間定位的衛星系統成為GNSS。

現在衛星定位那麼熱,那麼作為一個嵌入式人怎麼獲取這些資料為我們所用呢?下面就聽作者一一道來。

上一篇文章的傳送門從NMEA0183到GNSS定位資料獲取(一)原理篇

需要資料和程式碼的朋友可以關注公眾號回覆GNSS解析獲得自動回覆的連結。

三、程式介紹

上一篇文章介紹了NMEA-0183的協議內容,當我們知道資料格式,那對於底層的開發人員來說就是如何把我們需要的資料解析出來。

話不多說上例項來看:

還是這張圖,上面可以看到SOC與模組只是串列埠相連

我們第一步就是先配置通訊IO,STM32和Linux大家自行選擇

外設配置好了,接下來就開始對”模組“發過來的資料動手了。

$GNGGA,032220.291,,,,,0,0,,,M,,M,,*5D

$GNRMC,032220.291,V,,,,,0.00,0.00,140716,,,N*5D

$GNVTG,0.00,T,,M,0.00,N,0.00,K,N*2C

$GPGSA,A,1,,,,,,,,,,,,,,,*1E

$BDGSA,A,1,,,,,,,,,,,,,,,*0F

$GPGSV,2,1,07,23,,,31,08,,,49,30,,,33,16,,,45*7E

$GPGSV,2,2,07,07,,,44,27,,,49,26,,,43*72

$BDGSV,1,1,03,10,,,47,04,,,40,07,,,48*62

$GNGLL,,,,,032220.291,V,N*6F

首先先定義一個結構體,用來對解析好的資料進行存放。

//GPS NMEA-0183協議重要引數結構體定義 
//衛星資訊
__packed typedef struct  
{                        
   u8  num;    //衛星編號
  u8  eledeg;  //衛星仰角
  u16 azideg;  //衛星方位角
  u8  sn;    //信噪比       
}nmea_slmsg;


//UTC時間資訊 __packed typedef struct { u16 year; //年份 u8 month; //月份 u8 date; //日期 u8 hour; //小時 u8 min; //分鐘 u8 sec; //秒鐘 }nmea_utc_time; //NMEA 0183 協議解析後資料存放結構體 __packed typedef struct { u8 svnum; //可見衛星數 nmea_slmsg slmsg[12]; //最多12顆衛星 nmea_utc_time utc; //UTC時間 u32 latitude; //緯度 分擴大100000倍,實際要除以100000 u8 nshemi; //北緯/南緯,N:北緯;S:南緯 u32 longitude; //經度 分擴大100000倍,實際要除以100000 u8 ewhemi; //東經/西經,E:東經;W:西經 u8 gpssta; //GPS狀態:0,未定位;1,非差分定位;2,差分定位;6,正在估算. u8 posslnum; //用於定位的衛星數,0~12. u8 possl[12]; //用於定位的衛星編號 u8 fixmode; //定位型別:1,沒有定位;2,2D定位;3,3D定位 u16 pdop; //位置精度因子 0~500,對應實際值0~50.0 u16 hdop; //水平精度因子 0~500,對應實際值0~50.0 u16 vdop; //垂直精度因子 0~500,對應實際值0~50.0 u16 course; //航向 int altitude; //海拔高度,放大了10倍,實際除以10.單位:0.1m u32 speed; //地面速率,放大了1000倍,實際除以10.單位:0.001公里/小時 }nmea_msg;

其次因為協議是以字串的形式發過來的,我們要把解析的資訊進行符號的定義以及字元的轉化,準備了以下兩個函式:

//從buf裡面得到第cx個逗號所在的位置
//返回值:0~0XFE,代表逗號所在位置的偏移.
//       0XFF,代表不存在第cx個逗號                
u8 NMEA_Comma_Pos(u8 *buf,u8 cx)
{           
  u8 *p=buf;
  while(cx)
  {     
    if(*buf=='*'||*buf<' '||*buf>'z')return 0XFF;//遇到'*'或者非法字元,則不存在第cx個逗號
    if(*buf==',')cx--;
    buf++;
  }
  return buf-p;   
}
//str轉換為數字,以','或者'*'結束
//buf:數字儲存區
//dx:小數點位數,返回給呼叫函式
//返回值:轉換後的數值
/*遇到冒號以及豎槓、註釋的斜槓的時候進行返回*/
int NMEA_Str2num(u8 *buf,u8*dx)
{
  u8 *p=buf;
  u32 ires=0,fres=0;
  u8 ilen=0,flen=0,i;
  u8 mask=0;
  int res;
  while(1) //得到整數和小數的長度
  {
    if(*p=='-'){mask|=0X02;p++;}//是負數
    if(*p==','||(*p=='*')||(*p=='|')||(*p==':')\
    ||(*p=='!')  ||(*p=='/'))break;//遇到結束了
    if(*p=='.'){mask|=0X01;p++;}//遇到小數點了
    else if(*p == 0)//截至符 0
    {
      break;
    }
    else if(*p>'9'||(*p<'0'))  //有非法字元
    {  
      ilen=0;
      flen=0;
      break;
    }  
    if(mask&0X01)flen++;
    else ilen++;
    p++;
  }
  if(mask&0X02)buf++;  //去掉負號
  for(i=0;i<ilen;i++)  //得到整數部分資料
  {  
    ires+=NMEA_Pow(10,ilen-1-i)*(buf[i]-'0');//
  }
  if(flen>5)flen=5;  //最多取5位小數
  *dx=flen;       //小數點位數
  for(i=0;i<flen;i++)  //得到小數部分資料
  {  
    fres+=NMEA_Pow(10,flen-1-i)*(buf[ilen+1+i]-'0');
  } 
  res=ires*NMEA_Pow(10,flen)+fres;
  if(mask&0X02)res=-res;       
  return res;
}    

還有一個是在例如經緯度的轉化的時候需要用到的次方的函式:

//m^n函式
//返回值:m^n次方.
u32 NMEA_Pow(u8 m,u8 n)
{
  u32 result=1;   
  while(n--)result*=m;    
  return result;
}

因為專案的需求沒有對協議全部的解析,只是針對性的進行了解析。其餘大家想要解析的資料也是類似:

下面是以GPGGA 解析為例:其他標誌頭的資料大家以此類推。

從上面圖片可以看到本條資訊帶有14條資訊,但是我只解析了第六個、第七個和第九個位元組的資料。雖然這條資訊中也有UTC時間,但是一般都是建議在*RMC中獲得。

//分析GPGGA資訊
//gpsx:nmea資訊結構體
//buf:接收到的GPS資料緩衝區首地址
void NMEA_GPGGA_Analysis(nmea_msg *gpsx,u8 *buf)
{
  u8 *p1,dx;       
  u8 posx;    
  
  p1 = (u8*)strstr((const char *)buf,"$GNGGA");//GN 標誌開頭
  if(p1 == NULL)
  {
    p1=(u8*)strstr((const char *)buf,"$GPGGA");//或者GP開頭的標誌 你也可以用BD開頭的標誌
  }
  
  posx=NMEA_Comma_Pos(p1,6);                //得到GPS狀態
  if(posx!=0XFF)
    gpsx->gpssta=NMEA_Str2num(p1+posx,&dx);  
  
  posx=NMEA_Comma_Pos(p1,7);                //得到用於定位的衛星數
  if(posx!=0XFF)
    gpsx->posslnum=NMEA_Str2num(p1+posx,&dx); 
  
  posx=NMEA_Comma_Pos(p1,9);                //得到海拔高度
  if(posx!=0XFF)
    gpsx->altitude=NMEA_Str2num(p1+posx,&dx);  
}

//分析GPGSA資訊
//gpsx:nmea資訊結構體
//buf:接收到的GPS資料緩衝區首地址
void NMEA_GPGSA_Analysis(nmea_msg *gpsx,u8 *buf)
{
  u8 *p1,dx;       
  u8 posx; 
  u8 i;   
  p1=(u8*)strstr((const char *)buf,"$GPGSA");
  if(p1 == NULL)
    p1 = (u8*)strstr((const char *)buf,"$GNGSA");
  posx=NMEA_Comma_Pos(p1,2);                //得到定位型別
  if(posx!=0XFF)gpsx->fixmode=NMEA_Str2num(p1+posx,&dx);  
  for(i=0;i<12;i++)                    //得到定位衛星編號
  {
    posx=NMEA_Comma_Pos(p1,3+i);           
    if(posx!=0XFF)gpsx->possl[i]=NMEA_Str2num(p1+posx,&dx);
    else break; 
  }          
  posx=NMEA_Comma_Pos(p1,15);                //得到PDOP位置精度因子
  if(posx!=0XFF)gpsx->pdop=NMEA_Str2num(p1+posx,&dx);  
  posx=NMEA_Comma_Pos(p1,16);                //得到HDOP位置精度因子
  if(posx!=0XFF)gpsx->hdop=NMEA_Str2num(p1+posx,&dx);  
  posx=NMEA_Comma_Pos(p1,17);                //得到VDOP位置精度因子
  if(posx!=0XFF)gpsx->vdop=NMEA_Str2num(p1+posx,&dx);  
}

//分析GPRMC資訊
//gpsx:nmea資訊結構體
//buf:接收到的GPS資料緩衝區首地址
void NMEA_GPRMC_Analysis(nmea_msg *gpsx,u8 *buf)
{
  u8 *p1,dx;       
  u8 posx;     
  u32 temp;     
  float rs;  
  
  p1 = (u8*)strstr((const char *)buf,"GNRMC"); //GNSS
  if(p1 == NULL)
  {
    p1=(u8*)strstr((const char *)buf,"GPRMC");//"$GPRMC",經常有&和GPRMC分開的情況,故只判斷GPRMC.
  }
  
  posx=NMEA_Comma_Pos(p1, 1);                //得到UTC時間  hhmmss.ss
  if(posx!=0XFF)
  {
    temp=NMEA_Str2num(p1+posx,&dx)/NMEA_Pow(10,dx);     //得到UTC時間,去掉ms
    gpsx->utc.hour=temp/10000;
    gpsx->utc.min=(temp/100)%100;
    gpsx->utc.sec=temp%100;      
  }
  
  posx=NMEA_Comma_Pos(p1,2);/*判斷RMC資料狀態,A=資料有效 V=資料無效*/  
  if(posx!=0XFF)
  {
    u8* p2=(u8*)strstr((const char *)(p1+posx), "A");
    if(p2 == NULL)
    {
      posx = 0;  //資料無效 TODO
    }
  }
  
  posx=NMEA_Comma_Pos(p1,3);                //得到緯度 ddmm.mmmm
  if(posx!=0XFF)
  {
    temp=NMEA_Str2num(p1+posx,&dx);        
    gpsx->latitude=temp/NMEA_Pow(10,dx+2);  //得到°
    rs=temp%NMEA_Pow(10,dx+2);        //得到'     
    gpsx->latitude=gpsx->latitude*NMEA_Pow(10,5)+(rs*NMEA_Pow(10,5-dx))/60;//轉換為° 
  }
  posx=NMEA_Comma_Pos(p1,4);                //南緯還是北緯 
  if(posx!=0XFF)
    gpsx->nshemi=*(p1+posx);           
   
  posx=NMEA_Comma_Pos(p1,5);                //得到經度 dddmm.mmmm
  if(posx!=0XFF)
  {                          
    temp=NMEA_Str2num(p1+posx,&dx);        
    gpsx->longitude=temp/NMEA_Pow(10,dx+2);  //得到°
    rs=temp%NMEA_Pow(10,dx+2);        //得到'     
    gpsx->longitude=gpsx->longitude*NMEA_Pow(10,5)+(rs*NMEA_Pow(10,5-dx))/60;//轉換為° 
  }
  posx=NMEA_Comma_Pos(p1,6);                //東經還是西經
  if(posx!=0XFF)
    gpsx->ewhemi=*(p1+posx);  
   
  posx=NMEA_Comma_Pos(p1,8);                //得到方位 度
  if(posx!=0XFF)
  {                          
    temp=NMEA_Str2num(p1+posx,&dx);        
    gpsx->course = temp*10;                
  }
  
  posx=NMEA_Comma_Pos(p1, 9);                //得到UTC日期 ddmmyy
  if(posx!=0XFF)
  {
    temp=NMEA_Str2num(p1+posx, &dx);         
    gpsx->utc.date  = temp/10000;
    gpsx->utc.month = (temp/100)%100;
    gpsx->utc.year  = 2000+temp%100;      
  } 
}

這就是我分享的第二篇NMEA-0183到GNSS資料的文章,裡面程式碼都是實踐過的。如果大家有什麼更好的思路,歡迎分享交流哈。