skype 发表于 2013-8-2 11:20:10

图解GPS解码

在论坛上潜水很久了,再不发点资料就....

下面我就以GPRMC解码为例,讲述我的解码方法,不对的地方,请大家指正,谢谢!
1。GPS串口接收部分的数据结构:
#define GPS_BUFFER_LEN127                      //0x7f,d6~d0都为1
typedef struct
{
    uint8_t star_$:1;                                    //注意,只有D0位有效
    int8_t GPS_data_Count;
    uint8_t GPS_Buffer;//二级缓冲
   
}Gps_Rx_T;

Gps_Rx_T Gps_data;

//串口中断接收GPS数据

void UART0_IRQHandler(void)
{
    uint8_t u8InChar=0xFF;
    uint32_t u32_IQR= UART0->ISR;
    uint32_t u32_status = UART0->FSR;
    UART0->ISR = u32_IQR;
    OSIntEnter();
   
    //串口接收数据
    if (((_UART_IS_RX_READY(UART0) > 0) || (_UART_IS_RX_TIMEOUT(UART0) > 0)) && (_UART_IS_RX_BUF_ERR(UART0) == 0))
    {
      while(_UART_IS_RX_EMPTY(UART0) == 0)
      {
            _UART_RECEIVEBYTE(UART0, u8InChar);
            switch(u8InChar)
            {   //NEMA0183 格式数据以$开始,\r\n结尾(0x0d 0x0a)
               case '$':   
                  Gps_data.star_$++;
                  Gps_data.GPS_data_Count=0;
               break;
               case 0x0a:
                  OS_IntSend_Msg(Task0_Prio, &Gps_data.GPS_Buffer);      //给任务0发消息
                break;
                default:    break;   
            }
            Gps_data.GPS_Buffer = u8InChar;
      }
    }
    else
    {//串口BUFFER数据溢出处理
      while(_UART_IS_RX_EMPTY(UART0) == 0)
      {
            _UART_FLUSH_FIFO(UART0, UART_FCR_TFR_Msk | UART_FCR_RFR_Msk);
            _UART_RECEIVEBYTE(UART0, u8InChar);
      }
    }
   
    OSIntExit();
}

从上面的代码中,我们可以看到 Gps_data.star_$ 变量是记录 '$' 次数的,Gps_data.star_$ 只能等于0 或 1, 因为存放GPS数据的数组 uint8_t GPS_Buffer;是二级缓冲.

当我们收到GPS输出 的第一个字节‘$’的时候,我们就这样处理:
               case '$':   
                  Gps_data.star_$++;                        //使用一个新的缓冲区
                  Gps_data.GPS_data_Count=0;         //数据个数从0记起
               break;


Gps_data.GPS_Buffer = u8InChar;   //就是把GPS输出的数据从'$'到0xda之前的数据存入数据缓冲区


看,我们第一步已做好了。

skype 发表于 2013-8-2 11:31:25

第二步就是把收到的一帧数据送去解码,在这里我模块了ucos的消息同步机制:
               case 0x0a:
                  OS_IntSend_Msg(Task0_Prio, &Gps_data.GPS_Buffer);      //给任务0发消息
                break;
//任务0是GPS解码任务.


现在我们进入解码函数里:


skynet 发表于 2013-8-2 11:32:54

楼主给个实例工程更好理解呀{:lol:}

skype 发表于 2013-8-2 11:34:24

GPMRC解码,太长了,我们先实现,再分段讲解!

void GprmcMessage(uint8_t *Buffer, uint8_t Len)
{
        uint8_t CommaNum; //逗号数
        uint8_t BufIndex; //数字量
        uint8_t Sbuf;
        uint8_t *Pstr;
   
    uint8_t hour, minute, sec, year, month, day, t;
   
        int i_tmp;
        double d_tmp;
    uint32_t w;

    GPS_GPRMC_T *pGPRMC_tmp;
   
   
    gRMC_info.POS_state = 'V';
   
   
    if ((sizeof(GPS_GPRMC_T) % 2) != 0)
    {
      pGPRMC_tmp = osal_mem_alloc(sizeof(GPS_GPRMC_T)+1);
    }
    else
    {
      pGPRMC_tmp = osal_mem_alloc(sizeof(GPS_GPRMC_T));
    }
   
    if (NULL != pGPRMC_tmp)
    {
      memset(pGPRMC_tmp, '\0', sizeof(GPS_GPRMC_T));
      //$GPRMC,<UTC时间>,<有效状态>,<纬度>,<纬度半球>,<经度>,<经度半球>,<地面速率>,<地面航向>,<UTC日期>,<磁偏角>,<磁偏角方向>,<模式指示>*<校验和><CR><LF>
      //NMEA0183语句(推荐定位信息(GPRMC))
      //$GPRMC,100259.00,A,3119.26216,N,12024.85164,E,2.447,,020613,,,A*7C
      //      1         2 3          4 5          6 7   8   9      ABC
      BufIndex=0;         //数字量
      CommaNum=0;         //逗号数
      Pstr=Buffer;          //找到GPRMC,后面的地址
      do
      {
            Sbuf=*Pstr++;       
         
            switch(Sbuf)
            {
                case ',':   CommaNum++;BufIndex=0;    break;//通过逗号的数目来进行状态分类
                case '*':                               break;
                default:
                        switch(CommaNum)
                        {
                            case 1: pGPRMC_tmp->UTC_Time      =Sbuf;      break;
                            case 2: pGPRMC_tmp->POS_state               =Sbuf;      break;
                            case 3: pGPRMC_tmp->WD_Lat          =Sbuf;      break;
                            case 4: pGPRMC_tmp->NS_WD                     =Sbuf;      break;
                            case 5: pGPRMC_tmp->JD_Long         =Sbuf;      break;
                            case 6: pGPRMC_tmp->EW_JD                     =Sbuf;      break;
                            case 7: pGPRMC_tmp->Speed         =Sbuf;      break;
                            case 8: pGPRMC_tmp->Azimuth         =Sbuf;      break;
                            case 9: pGPRMC_tmp->UTC_Date      =Sbuf;      break;
                            case 10: pGPRMC_tmp->MagneticDegrees=Sbuf;      break;
                            case 11: pGPRMC_tmp->MagneticDirection      =Sbuf;      break;
                            case 12: pGPRMC_tmp->Mode                     =Sbuf;      break;
                            default:break;
                        }
                        BufIndex++;        //
                        break;
            }
      }while(Sbuf!='*');//直到出现‘*’退出          

      gRMC_info.POS_state = 'V';
      if ('A' == pGPRMC_tmp->POS_state)
      {
            //时分秒
            hour    =   (pGPRMC_tmp->UTC_Time-0x30) * 10 + pGPRMC_tmp->UTC_Time-0x30;
            minute=   (pGPRMC_tmp->UTC_Time-0x30) * 10 + pGPRMC_tmp->UTC_Time-0x30;
            sec   =   (pGPRMC_tmp->UTC_Time-0x30) * 10 + pGPRMC_tmp->UTC_Time-0x30;
            //年月日
            year    =   (pGPRMC_tmp->UTC_Date-0x30) * 10 + pGPRMC_tmp->UTC_Date-0x30;
            month         =   (pGPRMC_tmp->UTC_Date-0x30) * 10 + pGPRMC_tmp->UTC_Date-0x30;
            day   =   (pGPRMC_tmp->UTC_Date-0x30) * 10 + pGPRMC_tmp->UTC_Date-0x30;
            
               
            //处理时间及日期
            t = GetDom(year+2000, month);                ////返回当月的最大天数
            hour = hour+8;
            if (hour >= 24)
            {
                hour -= 24;
                day += 1;
                if (day > t)
                {
                  day -= t;
                  month++;
                  if (month > 12)
                  {
                        month -= 12;
                        year++;
                  }
                }
            }
            //更新系统时间
            UpdateSystime(year, month, day, hour, minute, sec);
            
            gRMC_info.Y_M_D=0; gRMC_info.Y_M_D=0; gRMC_info.Y_M_D=0;
            gRMC_info.Y_M_D=0; gRMC_info.Y_M_D=0; gRMC_info.Y_M_D=0;
            // year
            gRMC_info.Y_M_D = (year/10)+0x30;
            gRMC_info.Y_M_D = (year%10)+0x30;
            //month
            gRMC_info.Y_M_D = (month/10)+0x30;
            gRMC_info.Y_M_D = (month%10)+0x30;      
            //day
            gRMC_info.Y_M_D = (day/10)+0x30;
            gRMC_info.Y_M_D = (day%10)+0x30;
            
            gRMC_info.H_M_S=0; gRMC_info.H_M_S=0; gRMC_info.H_M_S=0;
            gRMC_info.H_M_S=0; gRMC_info.H_M_S=0; gRMC_info.H_M_S=0;
            //hour
            gRMC_info.H_M_S = (hour/10)+0x30;
            gRMC_info.H_M_S = (hour%10)+0x30;
            //minute
            gRMC_info.H_M_S = (minute/10)+0x30;
            gRMC_info.H_M_S = (minute%10)+0x30;
            //sec
            gRMC_info.H_M_S = (sec/10)+0x30;
            gRMC_info.H_M_S = (sec%10)+0x30;
            //是否有效定位
            gRMC_info.POS_state = pGPRMC_tmp->POS_state;
            //数据有效才转换经纬度
            d_tmp         = Str_To_Double(&pGPRMC_tmp->WD_Lat);
            i_tmp                  = (int)d_tmp / 100;                                       //分离纬度
            d_tmp         = (d_tmp - i_tmp * 100)/60;                            //分
            d_tmp                 = (double)i_tmp +d_tmp;   
            
            gRMC_info.WD_string=0; gRMC_info.WD_string=0; gRMC_info.WD_string=0; gRMC_info.WD_string=0;
            gRMC_info.WD_string=0; gRMC_info.WD_string=0; gRMC_info.WD_string=0; gRMC_info.WD_string=0;
            gRMC_info.WD_string=0; gRMC_info.WD_string=0; gRMC_info.WD_string=0;
            //纬度的范围是南北纬0-90°
            w = (uint32_t)d_tmp;
            Myitoa(w, &(gRMC_info.WD_string));
            if (w <10)
            {   //1.123456
                w = (uint32_t)(((d_tmp - w)+1) * 10000000);                //w=1.xxxx
                Myitoa(w, &(gRMC_info.WD_string));
                gRMC_info.WD_string = '.';            
            }
            else
            {   //31.320145
                w = (uint32_t)(((d_tmp - w)+1) * 10000000);                //w=1.xxxx
                Myitoa(w, &(gRMC_info.WD_string));
                gRMC_info.WD_string = '.';
            }   

            gRMC_info.NS_WD = pGPRMC_tmp->NS_WD;
         
            d_tmp         = Str_To_Double(&pGPRMC_tmp->JD_Long);
            i_tmp                 = (int)d_tmp / 100;                                                    //分离经度
            d_tmp         = (d_tmp - i_tmp * 100)/60;
            d_tmp                 = (double)d_tmp + i_tmp;
            
            gRMC_info.JD_string=0; gRMC_info.JD_string=0; gRMC_info.JD_string=0; gRMC_info.JD_string=0;
            gRMC_info.JD_string=0; gRMC_info.JD_string=0; gRMC_info.JD_string=0; gRMC_info.JD_string=0;
            gRMC_info.JD_string=0; gRMC_info.JD_string=0; gRMC_info.JD_string=0;
                  
            //经度的范围是东西经0-180°
            w = (uint32_t)d_tmp;
            Myitoa(w, &(gRMC_info.JD_string));       
            if (w < 10)
            {   //1.123456
                w = (uint32_t)(((d_tmp - w)+1) * 10000000);
                Myitoa(w, &(gRMC_info.JD_string));
                gRMC_info.JD_string = '.';
            }
            else if (w < 100)
            {   //11.123456
                w = (uint32_t)(((d_tmp - w)+1) * 10000000);
                Myitoa(w, &(gRMC_info.JD_string));
                gRMC_info.JD_string = '.';
            }
            else if (w < 190)
            {
                w = (uint32_t)(((d_tmp - w)+1) * 10000000);
                Myitoa(w, &(gRMC_info.JD_string));
                gRMC_info.JD_string = '.';
            }

            gRMC_info.EW_JD=pGPRMC_tmp->EW_JD;

            //地面速率(000.0~999.9节,前面的0也将被传输)
            d_tmp = Str_To_Double(pGPRMC_tmp->Speed) * 1.85;        //1海里=1.85公里
            
            w = (uint32_t)d_tmp;
            Myitoa(w, &(gRMC_info.Speed));       
            if (w < 10)
            {   //1.123456
                w = (uint32_t)(((d_tmp - w)+1) * 10000000);
                Myitoa(w, &(gRMC_info.Speed));
                gRMC_info.Speed = '.';
            }
            else if (w < 100)
            {   //11.123456
                w = (uint32_t)(((d_tmp - w)+1) * 10000000);
                Myitoa(w, &(gRMC_info.Speed));
                gRMC_info.Speed = '.';
            }
            else if (w < 1000)
            {
                w = (uint32_t)(((d_tmp - w)+1) * 10000000);
                Myitoa(w, &(gRMC_info.Speed));
                gRMC_info.Speed = '.';
            }
      }
      osal_mem_free(pGPRMC_tmp);
    }
}

skype 发表于 2013-8-2 11:37:06

朋友们先别回复,请等待我写完再回复,谢谢!

hell-prototypes 发表于 2013-8-2 11:39:26

写完了吗。。{:titter:}

skype 发表于 2013-8-2 11:57:00

我们还是来看一下这个 GPS_GPRMC_T 结构吧,它其实是一个中间变量,不过我在网上看到很多人解码的时候只做到这里,但这不是我们的终极结果!

/*
GPRMC推荐定位信息(GPRMC)
$GPRMC,<1>,<2>,<3>,<4>,<5>,<6>,<7>,<8>,<9>,<10>,<11>,<12>*hh
<1> UTC时间,hhmmss.sss(时分秒.毫秒)格式
<2> 定位状态,A=有效定位,V=无效定位
<3> 纬度ddmm.mmmm(度分)格式(前面的0也将被传输)
<4> 纬度半球N(北半球)或S(南半球)
<5> 经度dddmm.mmmm(度分)格式(前面的0也将被传输)
<6> 经度半球E(东经)或W(西经)
<7> 地面速率(000.0~999.9节,前面的0也将被传输)
<8> 地面航向(000.0~359.9度,以正北为参考基准,前面的0也将被传输)
<9> UTC日期,ddmmyy(日月年)格式
<10> 磁偏角(000.0~180.0度,前面的0也将被传输)
<11> 磁偏角方向,E(东)或W(西)
<12> 模式指示(仅NMEA0183 3.00版本输出,A=自主定位,D=差分,E=估算,N=数据无效)
*/
//经纬度buf长度
#define JWD_BUTF_LEN                        11
typedef struct         //typedef __packed struct
{        //GPRMC
    uint8_t UTC_Time;                                    
    uint8_t POS_state;
    uint8_t WD_Lat;             //加\0
    uint8_t NS_WD;
    uint8_t JD_Long;            //加\0
    uint8_t EW_JD;
    uint8_t Speed;
    uint8_t Azimuth;
    uint8_t UTC_Date;
    uint8_t MagneticDegrees;
    uint8_t MagneticDirection;
    uint8_t Mode;
}GPS_GPRMC_T;




下面就是分离数据了:


呵呵,先去吃饭,一会再写

skype 发表于 2013-8-2 13:23:43

我们的最终结果是存放在这个结构体中:
typedef struct
{        //GPRMC
    uint8_t Y_M_D;//20130707
    uint8_t H_M_S;    //222401
    uint8_t POS_state;
    uint8_t WD_string;             //加\0
    uint8_t NS_WD;
    uint8_t JD_string;            //加\0
    uint8_t EW_JD;
    uint8_t Speed;
    uint8_t Azimuth;
    uint8_t MagneticDegrees;
    uint8_t MagneticDirection;
    uint8_t Mode;
}GPRMC_T;

extern GPRMC_T gRMC_info;//是全局变量
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
下面我们把时间做一下转换,改成北京时间,UTC时间加8小时就是北京时间.

      //时分秒
      hour    =   (GPRMC_tmp.UTC_Time-0x30) * 10 + GPRMC_tmp.UTC_Time-0x30;
      minute=   (GPRMC_tmp.UTC_Time-0x30) * 10 + GPRMC_tmp.UTC_Time-0x30;
      sec   =   (GPRMC_tmp.UTC_Time-0x30) * 10 + GPRMC_tmp.UTC_Time-0x30;
      //年月日
      year    =   (GPRMC_tmp.UTC_Date-0x30) * 10 + GPRMC_tmp.UTC_Date-0x30;
      month         =   (GPRMC_tmp.UTC_Date-0x30) * 10 + GPRMC_tmp.UTC_Date-0x30;
      day   =   (GPRMC_tmp.UTC_Date-0x30) * 10 + GPRMC_tmp.UTC_Date-0x30;
      
            
      //处理时间及日期
      t = GetDom(year+2000, month);                ////返回当月的最大天数
      hour = hour+8;
      if (hour >= 24)
      {
            hour -= 24;
            day += 1;
            if (day > t)
            {
                day -= t;
                month++;
                if (month > 12)
                {
                  month -= 12;
                  year++;
                }
            }
      }
//GetDom()代码实现:

/*----------------------------------------------------------------------------------------
0000年~9999年星期算法(菜农自创)
-----------------------------------------------------------------------------------------*/
unsigned int GetDow(unsigned int y, unsigned int m, unsigned int d)
{
        unsigned int w, c;
        if (m <= 2)
        {
                m |= 4;//1月2月同5月六月表
                y--;
        }
        c = y / 100;
        c &= 0x03;//百年%4
        y %= 100;
        w = ((c | (c << 2)) + (y + (y >> 2)) + (13 * m + 8)/ 5 + d) % 7;//(星期=百年%4*5+年+年/4+(13*月+8)/5+日)%7
        return w;//返回星期
}

/*----------------------------------------------------------------------------------------
附赠0000年~9999年月最大天数算法(菜农自创)
-----------------------------------------------------------------------------------------*/
int GetDom(unsigned int y, unsigned int m)
{
        int dn;
        dn = GetDow(y, m + 1, 1) - GetDow(y, m, 1);//m+1=13表示明年的1月
        if (dn < 0) dn += 7;
        return dn + 28;//返回当月的最大天数
}

运行结果:


纬度数据解析


//函数实现

//====================================================================//
// 语法格式: double Str_To_Double(uint8_t *buf)
// 实现功能: 把一个字符串转化成浮点数
// 参    数:字符串
// 返 回 值:转化后双精度值
//====================================================================//
double Str_To_Double(uint8_t *buf)
{
        double rev = 0.0f;
        double dat;
        int integer = 1;
        uint8_t *str = buf;
        int i=0;
        while(*str != '\0')
        {
                switch(*str)
                {
                        case '0':
                                dat = 0;
                                break;
                        case '1':
                                dat = 1;
                                break;
                        case '2':
                                dat = 2;
                                break;               
                        case '3':
                                dat = 3;
                                break;
                        case '4':
                                dat = 4;
                                break;
                        case '5':
                                dat = 5;
                                break;
                        case '6':
                                dat = 6;
                                break;
                        case '7':
                                dat = 7;
                                break;
                        case '8':
                                dat = 8;
                                break;
                        case '9':
                                dat = 9;
                                break;
                        case '.':
                                dat = '.';
                                break;
                }
                if(dat == '.')
                {
                        integer = 0;
                        i = 1;
                        str ++;
                        continue;
                }
                if( integer == 1 )
                {
                        rev = rev * 10 + dat;
                }
                else
                {
                        rev = rev + dat / (10 * i);
                        i = i * 10 ;
                }
                str ++;
        }
        return rev;
}

//整数转字符串,最大长度 11 位,包括正负标志位
void Myitoa(int32_t n, uint8_t str[])
{
    int32_t i, sign;
    uint8_t s;
    uint8_t j=0;
    if((sign=n) < 0)
      n=-n;

    i=0;
    do
    {
      s = n % 10 + '0';
    }while ((n/=10) > 0);

    if(sign < 0)
      s = '-';

    s = '\0';

    //for (; i>0; i-- )
    do{
      str = s;
    }while(--i > 0);
}





skype 发表于 2013-8-2 13:30:31



对此,我们已完成了GPRMC数据的全部解码。

YUZH282 发表于 2013-8-2 13:42:08

顶一下,楼主辛苦了,暂时还用不上.

mcuprogram 发表于 2013-8-2 13:52:07

skype 发表于 2013-8-2 13:30 static/image/common/back.gif
对此,我们已完成了GPRMC数据的全部解码。

不cuo!                           

yinglively 发表于 2013-8-2 13:53:28

支持一下,讲解的比较详细

stely 发表于 2013-8-2 13:58:27

楼主辛苦发帖一定要顶。

pisgah 发表于 2013-8-2 20:23:06

頂一下{:biggrin:}

huangdog 发表于 2013-8-2 20:26:37

标题叫串口数据解码更合适吧~一开始我还以为是软件解码GPS前端的卫星数据呢。。。

dong889 发表于 2013-8-2 20:26:46

mark楼主辛苦了!

lklhzu 发表于 2013-11-1 16:22:07

额,楼主好有耐心,顶一下!

lryxr2507 发表于 2013-11-4 14:33:16

对我们初学的来说非常有用,谢楼主了.

alexsham 发表于 2013-11-4 16:34:34

MARK,过段时间解析GPS再仔细阅读

sunliezhi 发表于 2013-11-4 18:13:47

很高兴见到用结构体封装数据流;
在数据类型组合能重复出现的场合(多见于通讯),运用结构体指针是再合适不过的了;
我以前说老德的tcp/ip代码写的很优雅,也是这个原因;
还有就是状态机的运用;
顶楼主,高兴。当然,代码收下,不谢! {:lol:}

tlsmf 发表于 2013-11-4 19:16:12

学习了         

wgm_123 发表于 2013-11-18 10:45:03

太好了,早点看到楼主的大作该多好啊,谢谢楼主无私指教。先收下,等以后再研究。

zjk 发表于 2013-12-26 13:37:47

来学习下。。。

zhaoylx@163.com 发表于 2014-4-20 15:58:16

结构体封装的不错啊,就是名字命名可以更加直白

wang55 发表于 2014-5-3 16:44:46

好东西,只恨相见太晚了。

Glen_f 发表于 2014-5-3 19:27:20

多谢分享,不过这个按照标准数据格式解析一下即可

osli524 发表于 2014-5-6 20:44:15

辛苦了,很详细

taojie 发表于 2014-8-24 07:13:40


辛苦了,很详细

wbxjtu 发表于 2014-9-5 14:25:14

标记一下

ghostxdy 发表于 2014-9-5 16:04:40

里面有几个函数很有参考价值

maimaige 发表于 2014-9-6 08:25:01

mark 一下 图解GPS解码

stevencheng 发表于 2014-9-9 18:33:43


标记一下

lvsonghai 发表于 2014-9-9 18:36:45

楼主辛苦,已下载

RudeBoy 发表于 2014-9-9 20:10:34

讲的很漂亮,抽时间好好研究下……

ghostxdy 发表于 2014-9-15 00:53:36

LZ的教程写得很好,特别系解析部分的状态机
但没发现有CHECKSUM部分,是因为LZ项目上用不到吗?

河河河 发表于 2014-9-15 09:24:01

mark楼主辛苦了!

zenger_xu 发表于 2014-10-12 20:04:46

谢谢 楼主过段时间好好看下

爱上DIY的汤姆猫 发表于 2014-10-16 23:10:17

围观...{:smile:}{:smile:}

jweih 发表于 2014-10-16 23:15:12

收藏,谢谢楼主

apolloty 发表于 2014-10-16 23:25:25

mark
感谢~~~~~

sylisi 发表于 2015-10-13 17:45:10

非常感谢

inkfish321 发表于 2015-10-13 17:51:04

这个并不通用。如果移植个SEHLL的代码过来。。。不只是GPS,AT命令也一样能拆成一个个参数指针。我就是这么干的。

sml009 发表于 2015-11-19 14:22:36

学习了

20835579 发表于 2015-11-19 14:51:40

谢谢楼主,收藏了
页: [1]
查看完整版本: 图解GPS解码