图解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之前的数据存入数据缓冲区
看,我们第一步已做好了。
第二步就是把收到的一帧数据送去解码,在这里我模块了ucos的消息同步机制:
case 0x0a:
OS_IntSend_Msg(Task0_Prio, &Gps_data.GPS_Buffer); //给任务0发消息
break;
//任务0是GPS解码任务.
现在我们进入解码函数里:
楼主给个实例工程更好理解呀{:lol:} 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);
}
} 朋友们先别回复,请等待我写完再回复,谢谢! 写完了吗。。{:titter:} 我们还是来看一下这个 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;
下面就是分离数据了:
呵呵,先去吃饭,一会再写 我们的最终结果是存放在这个结构体中:
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);
}
对此,我们已完成了GPRMC数据的全部解码。
顶一下,楼主辛苦了,暂时还用不上. skype 发表于 2013-8-2 13:30 static/image/common/back.gif
对此,我们已完成了GPRMC数据的全部解码。
不cuo! 支持一下,讲解的比较详细 楼主辛苦发帖一定要顶。 頂一下{:biggrin:} 标题叫串口数据解码更合适吧~一开始我还以为是软件解码GPS前端的卫星数据呢。。。 mark楼主辛苦了! 额,楼主好有耐心,顶一下! 对我们初学的来说非常有用,谢楼主了. MARK,过段时间解析GPS再仔细阅读 很高兴见到用结构体封装数据流;
在数据类型组合能重复出现的场合(多见于通讯),运用结构体指针是再合适不过的了;
我以前说老德的tcp/ip代码写的很优雅,也是这个原因;
还有就是状态机的运用;
顶楼主,高兴。当然,代码收下,不谢! {:lol:} 学习了 太好了,早点看到楼主的大作该多好啊,谢谢楼主无私指教。先收下,等以后再研究。 来学习下。。。 结构体封装的不错啊,就是名字命名可以更加直白 好东西,只恨相见太晚了。 多谢分享,不过这个按照标准数据格式解析一下即可 辛苦了,很详细
辛苦了,很详细 标记一下 里面有几个函数很有参考价值 mark 一下 图解GPS解码
标记一下 楼主辛苦,已下载 讲的很漂亮,抽时间好好研究下…… LZ的教程写得很好,特别系解析部分的状态机
但没发现有CHECKSUM部分,是因为LZ项目上用不到吗? mark楼主辛苦了! 谢谢 楼主过段时间好好看下 围观...{:smile:}{:smile:} 收藏,谢谢楼主 mark
感谢~~~~~ 非常感谢 这个并不通用。如果移植个SEHLL的代码过来。。。不只是GPS,AT命令也一样能拆成一个个参数指针。我就是这么干的。 学习了 谢谢楼主,收藏了
页:
[1]