天天看點

STM32學習筆記:gps兩種解碼的方式

做為現在的物聯網行業,手持裝置中,缺少不了的就是GPS定位功能。GPS子產品和STM32的序列槽進行通信,将GPS的資料發送給M3的序列槽,由M3進行GPS協定的解碼。解析出來後儲存在響應的結構體中。在進行顯示。

這裡分别介紹2中解析協定的方法,第一種就是自己寫解析協定函數,第二種便是采用别人寫好的GPS解析協定庫:NMEALIB庫,将這個庫移植到M3中,直接調用API函數,就可以解析出GPS資訊,同樣的也儲存在一個結構體中。

下面分析一下這兩種解析協定的算法,第一種,采用的是正點原子寫的GPS解析算法(感謝原子哥)

//從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 XFF;//遇到'*'或者非法字元,則不存在第cx個逗号
        if(*buf==',')cx--;
        buf++;
    }
    return buf-p;   //傳回內插補點,
}
           

從GPS中得到的一串資料是這樣的: GPRMC,083559.00,A,4717.11437,N,00833.91522,E,0.004,77.52,091202,,,A∗57是以,我們可以調用這個函數,得到第幾個逗号所距離第一個字元的位置,例如:NMEACommaPos(buf,2),我們的到的是,第二個逗号距離 的位置,也就是17

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

這個就不用多說了,都看的懂,

//str轉換為數字,以','或者'*'結束
//buf:數字存儲區
//dx:小數點位數,傳回給調用函數
//傳回值:轉換後的數值
int NMEA_Str2num(u8 *buf,u8*dx)
{
    u8 *p=buf;
    u32 ires=,fres=;
    u8 ilen=,flen=,i;
    u8 mask=;
    int res;
    while() //得到整數和小數的長度
    {
        if(*p=='-'){mask|=X02;p++;}//是負數
        if(*p==','||(*p=='*'))break;//遇到結束了
        if(*p=='.'){mask|=X01;p++;}//遇到小數點了
        else if(*p>'9'||(*p<'0'))   //有非法字元
        {   
            ilen=;
            flen=;
            break;
        }   
        if(mask&X01)flen++;
        else ilen++;
        p++;
    }
    if(mask&X02)buf++; //去掉負号
    for(i=;i<ilen;i++) //得到整數部分資料
    {  
        ires+=NMEA_Pow(,ilen--i)*(buf[i]-'0');
    }
    if(flen>)flen=;   //最多取5位小數
    *dx=flen;           //小數點位數
    for(i=;i<flen;i++) //得到小數部分資料
    {  
        fres+=NMEA_Pow(,flen--i)*(buf[ilen++i]-'0');
    } 
    res=ires*NMEA_Pow(,flen)+fres;
    if(mask&X02)res=-res;         
    return res;
}   
           

這個函數便是将兩個逗号之間的字元串數字,變成整數,既将字元串“235”變成int(整型)數字,235

//分析GPGSV資訊
//gpsx:nmea資訊結構體
//buf:接收到的GPS資料緩沖區首位址
void NMEA_GPGSV_Analysis(nmea_msg *gpsx,u8 *buf)
{
    u8 *p,*p1,dx;
    u8 len,i,j,slx=;
    u8 posx;     
    p=buf;
    p1=(u8*)strstr((const char *)p,"$GPGSV");//strstr判斷$GPGSV是否是p數組的子串,是則傳回$GPGSV中首先出現的位址,
    len=p1[]-'0';                              //得到GPGSV的條數,p1[7]表示,後面的第一個字元。
    posx=NMEA_Comma_Pos(p1,);                  //得到可見衛星總數,既将‘,’後面的字元裡第一個字元的內插補點的到。
    if(posx!=XFF)gpsx->svnum=NMEA_Str2num(p1+posx,&dx);//p1+posx 得到可見衛星總數的指針,
    for(i=;i<len;i++)
    {    
        p1=(u8*)strstr((const char *)p,"$GPGSV");  
        for(j=;j<;j++)
        {     
            posx=NMEA_Comma_Pos(p1,+j*);
            if(posx!=XFF)gpsx->slmsg[slx].num=NMEA_Str2num(p1+posx,&dx);   //得到衛星編号
            else break; 
            posx=NMEA_Comma_Pos(p1,+j*);
            if(posx!=XFF)gpsx->slmsg[slx].eledeg=NMEA_Str2num(p1+posx,&dx);//得到衛星仰角 
            else break;
            posx=NMEA_Comma_Pos(p1,+j*);
            if(posx!=XFF)gpsx->slmsg[slx].azideg=NMEA_Str2num(p1+posx,&dx);//得到衛星方位角
            else break; 
            posx=NMEA_Comma_Pos(p1,+j*);
            if(posx!=XFF)gpsx->slmsg[slx].sn=NMEA_Str2num(p1+posx,&dx);    //得到衛星信噪比
            else break;
            slx++;     
        }   
        p=p1+;//切換到下一個GPGSV資訊
    }   
}
           

這個便是解析GPGSV資訊,GPGSV協定如下:

STM32學習筆記:gps兩種解碼的方式
//分析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,"$GPGGA");
    posx=NMEA_Comma_Pos(p1,);                              //得到GPS狀态
    if(posx!=XFF)gpsx->gpssta=NMEA_Str2num(p1+posx,&dx);   
    posx=NMEA_Comma_Pos(p1,);                              //得到用于定位的衛星數
    if(posx!=XFF)gpsx->posslnum=NMEA_Str2num(p1+posx,&dx); 
    posx=NMEA_Comma_Pos(p1,);                              //得到海拔高度
    if(posx!=XFF)gpsx->altitude=NMEA_Str2num(p1+posx,&dx);  
}
           

這個是解析GPGGA資訊,GPGGA協定如下:

STM32學習筆記:gps兩種解碼的方式
//分析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");
    posx=NMEA_Comma_Pos(p1,);                              //得到定位類型
    if(posx!=XFF)gpsx->fixmode=NMEA_Str2num(p1+posx,&dx);  
    for(i=;i<;i++)                                       //得到定位衛星編号
    {
        posx=NMEA_Comma_Pos(p1,+i);                     
        if(posx!=XFF)gpsx->possl[i]=NMEA_Str2num(p1+posx,&dx);
        else break; 
    }                 
    posx=NMEA_Comma_Pos(p1,);                             //得到PDOP位置精度因子
    if(posx!=XFF)gpsx->pdop=NMEA_Str2num(p1+posx,&dx);  
    posx=NMEA_Comma_Pos(p1,);                             //得到HDOP位置精度因子
    if(posx!=XFF)gpsx->hdop=NMEA_Str2num(p1+posx,&dx);  
    posx=NMEA_Comma_Pos(p1,);                             //得到VDOP位置精度因子
    if(posx!=XFF)gpsx->vdop=NMEA_Str2num(p1+posx,&dx);  
}
           

這個是解析GPGSA資訊,GPGSA協定定義如下:

STM32學習筆記:gps兩種解碼的方式
STM32學習筆記:gps兩種解碼的方式

接下來就是我們通常要用到的一個協定了:GPRMC資訊

//分析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,"GPRMC");//"$GPRMC",經常有&和GPRMC分開的情況,故隻判斷GPRMC.
    posx=NMEA_Comma_Pos(p1,);                              //得到UTC時間
    if(posx!=)
    {
        temp=NMEA_Str2num(p1+posx,&dx)/NMEA_Pow(,dx);     //得到UTC時間,去掉ms
        gpsx->utc.hour=temp/;
        gpsx->utc.min=(temp/)%;
        gpsx->utc.sec=temp%;      
    }   
    posx=NMEA_Comma_Pos(p1,);                              //得到緯度
    if(posx!=)
    {
        temp=NMEA_Str2num(p1+posx,&dx);          
        gpsx->latitude=temp/NMEA_Pow(,dx+);  //得到°
        rs=temp%NMEA_Pow(,dx+);              //得到'        
        gpsx->latitude=gpsx->latitude*NMEA_Pow(,)+(rs*NMEA_Pow(,-dx))/;//轉換為° 
    }
    posx=NMEA_Comma_Pos(p1,);                              //南緯還是北緯 
    if(posx!=)gpsx->nshemi=*(p1+posx);                   
    posx=NMEA_Comma_Pos(p1,);                              //得到經度
    if(posx!=)
    {                                                 
        temp=NMEA_Str2num(p1+posx,&dx);          
        gpsx->longitude=temp/NMEA_Pow(,dx+); //得到°
        rs=temp%NMEA_Pow(,dx+);              //得到'        
        gpsx->longitude=gpsx->longitude*NMEA_Pow(,)+(rs*NMEA_Pow(,-dx))/;//轉換為° 
    }
    posx=NMEA_Comma_Pos(p1,);                              //東經還是西經
    if(posx!=)gpsx->ewhemi=*(p1+posx);       
    posx=NMEA_Comma_Pos(p1,);                              //得到UTC日期
    if(posx!=)
    {
        temp=NMEA_Str2num(p1+posx,&dx);                     //得到UTC日期
        gpsx->utc.date=temp/;
        gpsx->utc.month=(temp/)%;
        gpsx->utc.year=+temp%;        
    } 
}
           

GPRMC協定如下:

STM32學習筆記:gps兩種解碼的方式
STM32學習筆記:gps兩種解碼的方式
//分析GPVTG資訊
//gpsx:nmea資訊結構體
//buf:接收到的GPS資料緩沖區首位址
void NMEA_GPVTG_Analysis(nmea_msg *gpsx,u8 *buf)
{
    u8 *p1,dx;           
    u8 posx;    
    p1=(u8*)strstr((const char *)buf,"$GPVTG");                             
    posx=NMEA_Comma_Pos(p1,);                              //得到地面速率
    if(posx!=XFF)
    {
        gpsx->speed=NMEA_Str2num(p1+posx,&dx);
        if(dx<)gpsx->speed*=NMEA_Pow(,-dx);             //確定擴大1000倍
    }
}  
           

這個是GPVTG資訊解析,協定如下:

STM32學習筆記:gps兩種解碼的方式

到這裡,一些常用的,和我們需要的都解析出來了,

注意:這裡并不是每條協定都解析,解析的是我們需要什麼解析什麼,,當然在實際項目中要根據自己的需求解析。

GPS資訊我們是通過序列槽3中斷接收,将接收到的資料放在一個BUF中,

//通過判斷接收連續2個字元之間的時間差不大于10ms來決定是不是一次連續的資料.
//如果2個字元接收間隔超過10ms,則認為不是1次連續資料.也就是超過10ms沒有接收到
//任何資料,則表示此次接收完畢.
//接收到的資料狀态
//[15]:0,沒有接收到資料;1,接收到了一批資料.
//[14:0]:接收到的資料長度
vu16 USART3_RX_STA=;       


void USART3_IRQHandler(void)
{
    u8 res;       
    if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET)//接收到資料
    {    
        res =USART_ReceiveData(USART3);      
        if((USART3_RX_STA&(<<))==)//接收完的一批資料,還沒有被處理,則不再接收其他資料
        { 
            if(USART3_RX_STA<USART3_MAX_RECV_LEN)   //還可以接收資料  USART3_MAX_RECV_LEN = 600    最大接收緩存位元組數
            {
                TIM_SetCounter(TIM7,);//計數器清空                          //計數器清空
                if(USART3_RX_STA==)                //使能定時器7的中斷 
                {
                    TIM_Cmd(TIM7,ENABLE);//使能定時器7
                }
                USART3_RX_BUF[USART3_RX_STA++]=res; //記錄接收到的值    
            }else 
            {
                USART3_RX_STA|=<<;               //強制标記接收完成
            } 
        }
    }                                                            
}   
           

USART3_RX_STA是原子自己定義的一個最高位标志位,當資料接收完成時,USART3_RX_STA|=1<<15

将最高位标志位置1,

這裡便是定義了解析後資料儲存的結構體:

//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顆衛星
    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[];               //用于定位的衛星編号
    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 

    int altitude;               //海拔高度,放大了10倍,實際除以10.機關:0.1m     
    u16 speed;                  //地面速率,放大了1000倍,實際除以10.機關:0.001公裡/小時     
}nmea_msg; 
           

到這裡,采用第一種方式解析協定已經分析完了,接下來就是采用NMEALIB庫解析協定,

了解了NMEA格式有之後,我們就可以編寫相應的解碼程式了,而程式員Tim ([email protected])提供了一個非常完善的NMEA解碼庫,在以下網址可以下載下傳到:http://nmea.sourceforge.net/ ,直接使用該解碼庫,可以避免重複發明輪子的工作。在野火提供的GPS子產品資料的“NMEA0183解碼庫源碼”檔案夾中也包含了該解碼庫的源碼,野火提供的STM32程式就是使用該庫來解碼NMEA語句的。

該解碼庫目前最新為0.5.3版本,它使用純C語言編寫,支援windows、winCE 、UNIX平台,支援解析GPGGA,GPGSA,GPGSV,GPRMC,GPVTG這五種語句(這五種語句已經提供足夠多的GPS資訊),解析得的GPS資料資訊以結構體存儲,附加了地理學相關功能,可支援導航等資料工作,除了解析NMEA語句,它還可以根據随機數産生NMEA語句,友善模拟。

将nmealib庫中的src和include這兩個檔案夾複制到工程,在添加進工程中,包含編譯的頭檔案,結果如下:

STM32學習筆記:gps兩種解碼的方式

(這裡采用的是野火所提供的例程,感謝fire)

利用nmealib解析GPS子產品的輸出結果大緻可以分為三步,

第一步定義和初始化GPS資訊結構體和解析載體結構體,

第二步調用nmea_parse函數完成解析工作,

第三步釋放解析載體所占用的記憶體空間。

具體的代碼如下注釋中包含了代碼的分析:

/**
  * @brief  nmea_decode_test 解碼GPS子產品資訊
  * @param  無
  * @retval 無
                            利用nmealib解析GPS子產品的輸出結果大緻可以分為三步,
                                第一步定義和初始化GPS資訊結構體和解析載體結構體,
                                第二步調用nmea_parse函數完成解析工作,
                                第三步釋放解析載體所占用的記憶體空間。
*/
int nmea_decode_test(void)
{

    nmeaINFO info;          //GPS解碼後得到的資訊
    nmeaPARSER parser;      //解碼時使用的資料結構 
                                                        //nmeaPARSER是解析nmea所需要的一個結構。
    uint8_t new_parse=;    //是否有新的解碼資料标志

    nmeaTIME beiJingTime;    //中原標準時間 

    /* 設定用于輸出調試資訊的函數 */
    nmea_property()->trace_func = &trace;
    nmea_property()->error_func = &error;

    /* 初始化GPS資料結構 */
    nmea_zero_INFO(&info);/*對nmeaINFO這個結構中資料進行清零操作,
                                                            使用nmea_time_now函數對其中utc時間賦一個初值,初值就是目前的系統時間,
                                                            如果沒有從nmea中解析出時間資訊,那麼最後的結果就是你目前的系統時間。
                                                                而nmeaINFO中的sig、fix分别是定位狀态和定位類型
                                                        */
        nmea_parser_init(&parser);//nmeaPARSER結構做初始化,以nmea_parser_init和nmea_parser_destroy需要成對出現。

    while()
    {
      if(GPS_HalfTransferEnd)     /* 設定半傳輸完成标志位
                                                                        接收到GPS_RBUFF_SIZE一半的資料 */
      {
        /* 進行nmea格式解碼 */
                /*
                調用nmea_parse函數對nmea語句進行解析
                原型:
                    int nmea_parse(      
                                nmeaPARSER *parser,  
                                const char *buff, 
                                int buff_sz,  
                                nmeaINFO *info  
                            )  
                這個函數有四個參數,分别是nmeaPARSER指針,buff對應需要解析的nmea語句,buff_sz為nmea語句的長度,nmeaINFO指針
                */
        nmea_parse(&parser, (const char*)&gps_rbuff[], HALF_GPS_RBUFF_SIZE, &info);
                                    //nmeaPARSER指針,需要解析的BUFF,      序列槽接收緩沖區一半512/2,nmeaINFO指針

        GPS_HalfTransferEnd = ;   //清空标志位
        new_parse = ;             //設定解碼消息标志 
      }
      else if(GPS_TransferEnd)    /* 接收到另一半資料 */
      {

        nmea_parse(&parser, (const char*)&gps_rbuff[HALF_GPS_RBUFF_SIZE], HALF_GPS_RBUFF_SIZE, &info);

        GPS_TransferEnd = ;
        new_parse =;
      }

      if(new_parse )                //有新的解碼消息   
      {    
        /* 對解碼後的時間進行轉換,轉換成中原標準時間 */
        GMTconvert(&info.utc,&beiJingTime,,);

        /* 輸出解碼得到的資訊 */
        printf("\r\n時間%d,%d,%d,%d,%d,%d\r\n", beiJingTime.year+, beiJingTime.mon+,beiJingTime.day,beiJingTime.hour,beiJingTime.min,beiJingTime.sec);
        printf("\r\n緯度:%f,經度%f\r\n",info.lat,info.lon);
        printf("\r\n正在使用的衛星:%d,可見衛星:%d",info.satinfo.inuse,info.satinfo.inview);
        printf("\r\n海拔高度:%f 米 ", info.elv);
        printf("\r\n速度:%f km/h ", info.speed);
        printf("\r\n航向:%f 度", info.direction);

        new_parse = ;
      }

    }

    /* 釋放GPS資料結構 */
    // nmea_parser_destroy(&parser);


    //  return 0;
}
           

儲存解析後的結構體:

NMEA解碼庫良好的封裝特性使我們無需關注更深入的内部實作,隻需要再了解一下nmeaINFO資料結構即可,所有GPS解碼得到的結果都存儲在這個結構中

typedef struct _nmeaTIME
{
    int     year;       /**< Years since 1900 */
    int     mon;        /**< Months since January - [0,11] */
    int     day;        /**< Day of the month - [1,31] */
    int     hour;       /**< Hours since midnight - [0,23] */
    int     min;        /**< Minutes after the hour - [0,59] */
    int     sec;        /**< Seconds after the minute - [0,59] */
    int     hsec;       /**< Hundredth part of second - [0,99] */

} nmeaTIME;

typedef struct _nmeaINFO
{
    int     smask;      /**< Mask specifying types of packages from which data have been obtained */

    nmeaTIME utc;       /**< UTC of position */

    int     sig;        /**< GPS quality indicator (0 = Invalid; 1 = Fix; 2 = Differential, 3 = Sensitive) */
    int     fix;        /**< Operating mode, used for navigation (1 = Fix not available; 2 = 2D; 3 = 3D) */

    double  PDOP;       /**< Position Dilution Of Precision */
    double  HDOP;       /**< Horizontal Dilution Of Precision */
    double  VDOP;       /**< Vertical Dilution Of Precision */

    double  lat;        /**< Latitude in NDEG - +/-[degree][min].[sec/60] */
    double  lon;        /**< Longitude in NDEG - +/-[degree][min].[sec/60] */
    double  elv;        /**< Antenna altitude above/below mean sea level (geoid) in meters */
    double  speed;      /**< Speed over the ground in kilometers/hour */
    double  direction;  /**< Track angle in degrees True */
    double  declination; /**< Magnetic variation degrees (Easterly var. subtracts from true course) */

    nmeaSATINFO satinfo; /**< Satellites information */

} nmeaINFO;
           

結構體的具體含義,

STM32學習筆記:gps兩種解碼的方式
typedef struct _nmeaPARSER
{
    void *top_node;
    void *end_node;
    unsigned char *buffer;
    int buff_size;
    int buff_use;

} nmeaPARSER;
           

可以看到,nmeaPARSER是一個連結清單,在解碼時,NMEA庫會把輸入的GPS原始資料壓入到nmeaPARSER結構的連結清單中,便于對資料管理及解碼。在使用該結構前,我們調用了nmea_parser_init函數配置設定動态空間,而解碼結束時,調用了nmea_parser_destroy函數釋放配置設定的空間

當然最重要的還是要:配置設定堆棧空間

由于NMEA解碼庫在進行解碼時需要動态配置設定較大的堆空間,是以我們需要在STM32的啟動檔案startup_stm32f10x_hd.s檔案中對堆空間進行修改,本工程中設定的堆空間大小設定為0x0000 1000,

Stack_Size      EQU     0x00000400

                AREA    STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem       SPACE   Stack_Size
__initial_sp

; <h> Heap Configuration
;   <o>  Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>

Heap_Size       EQU     0x00001000

                AREA    HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem        SPACE   Heap_Size
__heap_limit

                PRESERVE8
                THUMB
           

當然,這裡也是通過序列槽接收資料儲存在這個數組中,

/* DMA接收緩沖  */
uint8_t gps_rbuff[GPS_RBUFF_SIZE];//接收緩存區512
           

詳情了解nmealib庫的可以參考這個部落格:

http://blog.csdn.net/xukai871105/article/details/12834421

到這裡,GPS協定的解析相信你應該懂了不少,

繼續閱讀