获得GPS数据的两种方法 1读串口 - [技术]
版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
http://memset.blogbus.com/logs/17801310.html
1.读串口
先找出gps使用的串口号,然后直接打开串口,读取串口数据了 串口通信api函数:1. 用途:打开串口原型:HANDLE CreateFile(LPCTSTR lpFileName,
DWORD dwDesiredAccess,
DWORD dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes
DWORD dwCreationDistribution,
DWORD dwFlagsAndAttributes,
HANDLE hTemplateFile);
参数说明:
-lpFileName:要打开的文件名称。对串口通信来说就是COM1或COM2。
-dwDesiredAccess:读写模式设置。此处应该用GENERIC_READ及GENERIC_WRITE。
-dwShareMode:串口共享模式。此处不允许其他应用程序共享,应为0。
-lpSecurityAttributes:串口的安全属性,应为0,表示该串口不可被子程序继承。
-dwCreationDistribution:创建文件的性质,此处为OPEN_EXISTING.
-dwFlagsAndAttributes:属性及相关标志,这里使用异步方式应该用FILE_FLAG_OVERLAPPED。
-hTemplateFile:此处为0。
操作说明:若文件打开成功,串口即可使用了,该函数返回串口的句柄,以后对串口操作时即可使用该句柄。
举例:HANDLE hComm;
hComm=CreateFile("COM1", //串口号
GENERIC_READ|GENERIC_WRITE, //允许读写0, //通讯设备必须以独占方式打开
NULL, //无安全属性
OPEN_EXISTING, //通讯设备已存在FILE_FLAG_OVERLAPPED, //异步I/O 0); //通讯设备不能用模板打开hComm即为函数返回的串口1的句柄。
2 . CloseHandle()
用途:关闭串口
原型:BOOL CloseHandle(HANDLE hObjedt)
参数说明:
-hObjedt:串口句柄
操作说明:成功关闭串口时返回true,否则返回false
举例:CloseHandle(hComm);
3. GetCommState()
用途:取得串口当前状态
原型:BOOL GetCommState(HANDLE hFile,LPDCB lpDCB);
参数说明:
-hFile:串口句柄
-lpDCB:设备控制块(Device Control Block)结构地址。此结构中含有和设备相关的参数。此处是与串口相关的参数。由于参数非常多,当需要设置串口参数 时,通常是先取得串口的参数结构,修改部分参数后再将参数结构写入。
在此仅介绍少数的几个常用的参数:
DWORD BaudRate:串口波特率
DWORD fParity:为1的话激活奇偶校验检查
DWORD Parity:校验方式,值0~4分别对应无校验、奇校验、偶校验、校验置位、校验清零
DWORD ByteSize:一个字节的数据位个数,范围是5~8
DWORD StopBits:停止位个数,0~2分别对应1位、1.5位、2位停止位操作举例:DCB ComDCB; //串口设备控制块
GetCommState(hComm,&ComDCB);
4. SetCommState()
用途:设置串口状态,包括常用的更改串口号、波特率、奇偶校验方式、数据位数等原型:BOOL SetCommState(HANDLE hFile,LPDCB lpDCB);
参数说明:
-hFile:串口句柄
-lpDCB:设备控制块(Device Control Block)结构地址。要更改的串口参数包含在此结构中。
操作举例:DCB ComDCB;
GetCommState(hComm,&ComDCB);//取得当前串口状态ComDCB.BaudRate=9600;//更改为9600bps,该值即为你要修改后的波特率
SetCommState(hComm,&ComDCB;//将更改后的参数写入串口
5. WriteFile()
用途:向串口写数据
原型:BOOL WriteFile(HANDLE hFile,
LPCVOID lpBuffer,
DWORD nNumberOfBytesToWrite,
LPDWORD lpNumberOfBytesWritten,
LPOVERLAPPED lpOverlapped);
参数说明:
-hFile:串口句柄
-lpBuffer:待写入数据的首地址
-nNumberOfBytesToWrite:待写入数据的字节数长度
-lpNumberOfBytesWritten:函数返回的实际写入串口的数据个数的地址,利用此变量可判断实际写入的字节数和准备写入的字节数是否相同。
-lpOverlapped:重叠I/O结构的指针 操作举例:DWORD BytesSent=0;
unsigned char SendBytes[5]={1,2,3,4,5};
OVERLAPPED ov_Write;
ov_Write.Offset=0;
ov_Write.OffsetHigh=0;
WriteFile(hComm, //调用成功返回非零,失败返回零
SendBytes, //输出缓冲区
5, //准备发送的字符长度
&BytesSent, //实际发出的字符数
&ov_Write); //重叠结构
如果函数执行成功的话检查BytesSent的值应该为5,此函数是WriteFile函数执行完毕后自行填充的,利用此变量的填充值可以用来检查该函数是否将所有的数据成功写入串口
6. ReadFile()
用途:读串口数据 原型:BOOL ReadFile(HANDLE hFile,
LPVOID lpBuffer,
DWORD nNumberOfBytesToRead,
lpNumberOfBytesRead,
lpOverlapped); 参数说明:
-hFile:串口句柄
-lpBuffer:存储被读出数据的首地址
-nNumberOfBytesToRead:准备读出的字节个数
-NumberOfBytesRead:实际读出的字节个数
-lpOverlapped:异步I/O结构, 操作举例:unsigned char ucRxBuff[20];
COMSTAT ComStat;
DWORD dwError=0;
DWORD BytesRead=0;
OVERLAPPED ov_Read;
ov_Read.hEvent=CreateEvent(NULL, true, false, NULL);//必须创建有效事件
ClearCommError(hComm,&dwError,&ComStat);//检查串口接收缓冲区中的数据个数
bResult=ReadFile(hComm, //串口句柄
ucRxBuff, //输入缓冲区地址
ComStat.cbInQue, //想读入的字符数
&BytesRead, //实际读出的字节数的变量指针
&ov_Read); //重叠结构指针
假如当前串口中有5个字节数据的话,那么执行完ClearCommError()函数后,ComStat
结构中的ComStat.cbInQue将被填充为5,此值在ReadFile函数中可被直接利用。
7. ClearCommError()
用途:清除串口错误或者读取串口现在的状态 原型:BOOL ClearCommError(HANDLE hFile,
LPDWORD lpErrors,
LPCOMATAT lpStat
); 参数说明:
-hFile:串口句柄
-lpErrors:返回错误数值,错误常数如下:
1-CE_BREAK:检测到中断信号。意思是说检测到某个字节数据缺少合法的停止位。
2-CE_FRAME:硬件检测到帧错误。
3-CE_IOE:通信设备发生输入/输出错误。
4-CE_MODE:设置模式错误,或是hFile值错误。
5-CE_OVERRUN:溢出错误,缓冲区容量不足,数据将丢失。
6-CE_RXOVER:溢出错误。
7-CE_RXPARITY:硬件检查到校验位错误。
8-CE_TXFULL:发送缓冲区已满。
-lpStat:指向通信端口状态的结构变量,原型如下:
typedef struct _COMSTAT{
...
...
DWORD cbInQue; //输入缓冲区中的字节数
DWORD cbOutQue;//输出缓冲区中的字节数
}COMSTAT,*LPCOMSTAT;
该结构中对我们很重要的只有上面两个参数,其他的我们可以不用管。 操作举例:COMSTAT ComStat;
DWORD dwError=0;
ClearCommError(hComm,&dwError,&ComStat);
上式执行完后,ComStat.cbInQue就是串口中当前含有的数据字节个数,我们利用此
数值就可以用ReadFile()函数去读串口中的数据了。
8. PurgeComm()
用途:清除串口缓冲区 原型:BOOL PurgeComm(HANDLE hFile,DWORD dwFlags);
参数说明:
-hFile:串口句柄
-dwFlags:指定串口执行的动作,由以下参数组成:
-PURGE_TXABORT:停止目前所有的传输工作立即返回不管是否完成传输动作。
-PURGE_RXABORT:停止目前所有的读取工作立即返回不管是否完成读取动作。
-PURGE_TXCLEAR:清除发送缓冲区的所有数据。
-PURGE_RXCLEAR:清除接收缓冲区的所有数据。 操作举例:PurgeComm(hComm, PURGE_RXCLEAR|PURGE_TXCLEAR|PURGE_RXABORT|PURGE_TXABORT);
清除串口的所有操作。
10-SetCommMask()
用途:设置串口通信事件。 原型:BOOL SetCommMask(HANDLE hFile,DWORD dwEvtMask);
参数说明:
-hFile:串口句柄
-dwEvtMask:准备监视的串口事件掩码
注:在用api函数撰写串口通信函数时大体上有两种方法,一种是查寻法,另外一种是事件通知法。
这两种方法的区别在于收串口数据时,前一种方法是主动的周期性的查询串口中当前有没有数据;后一种方法是事先设置好需要监视的串口通信事件,然后依靠单独开设的辅助线程进行监视该事件是否已发生,如果没有发生的话该线程就一直不停的等待直到该事件发生后,将该串口事件以消息的方式通知主窗体,然后主窗体收到该消息后依据不同的事件性质进行处理。
比如说当主窗体收到监视线程发来的RX_CHAR(串口中有数据)的消息后,就可以用ReadFile()
函数去读串口。该参数有如下信息掩码位值:
EV_BREAK:收到BREAK信号
EV_CTS:CTS(clear to send)线路发生变化
EV_DSR:DST(Data Set Ready)线路发生变化
EV_ERR:线路状态错误,包括了CE_FRAME/CE_OVERRUN/CE_RXPARITY 3钟错误。
EV_RING:检测到振铃信号。
EV_RLSD:CD(Carrier Detect)线路信号发生变化。
EV_RXCHAR:输入缓冲区中已收到数据。
EV_RXFLAG:使用SetCommState()函数设置的DCB结构中的等待字符已被传入输入缓冲区中。
EV_TXEMPTY:输出缓冲区中的数据已被完全送出。 操作举例:SetCommMask(hComm,EV_RXCHAR|EV_TXEMPTY);
上面函数执行完毕后将监视串口中有无数据和发送缓冲区中的数据是否全部发送完毕。
11-WaitCommEvent()
用途:用来判断用SetCommMask()函数设置的串口通信事件是否已发生。 原型:BOOL WaitCommEvent(HANDLE hFile,
LPDWORD lpEvtMask,
LPOVERLAPPED lpOverlapped
);参数说明:
-hFile:串口句柄
-lpEvtMask:函数执行完后如果检测到串口通信事件的话就将其写入该参数中。
-lpOverlapped:异步结构,用来保存异步操作结果。
由于GPS定位信息内容较少,因此多用RS-232串口将定位信息(NEMA0183语句)从GPS接收机传送到计算机中进行信息提取处理。
在Windows下不允许直接对硬件端口进行控制操作,所有的端口均被视为"文件",因此在对串口进行侦听之前需要通过打开文件来打开串口,并对其进行相关参数配置:
m_hCom=CreateFile("COM1",GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING, FILE_FLAG_OVERLAPPED,NULL); file://以异步方式打开COM1口
SetCommMask (m_hCom, EV_RXCHAR ) ; file://添加或修改Windows所报告的事件列表
SetupComm (m_hCom,READBUFLEN/*读缓冲*/,WRITEBUFLEN/*写缓冲*/); // 初始化通讯设备参数
// 清除缓冲信息
PurgeComm (m_hCom, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR) ;
// 对异步I/O进行设置
CommTimeOuts.ReadIntervalTimeout = MAXDWORD ; file://接收两连续字节的最大时间间隔
CommTimeOuts.ReadTotalTimeoutMultiplier =0; file://接收每字节的平均允许时间
CommTimeOuts.ReadTotalTimeoutConstant = 0 ; file://接收时间常数
SetCommTimeouts (m_hCom , &CommTimeOuts) ;
file://获取并设置串口
GetCommState ( m_hCom, &dcb) ;
dcb.BaudRate = CBR_4800;
dcb.ByteSize = 8;
dcb.Parity = ODDPARITY;
dcb.StopBits = ONESTOPBIT ;
SetCommState( m_hCom, &dcb); 在成功打开并设置通讯口后,可采取轮询串口和事件触发两种方式对数据进行接收处理,本文在此采取效率比较高的事件触发方式进行接收处理,通过等待EV_RXCHAR事件的发生来启动ReadFile函数完成对GPS定位信息的接收:
while(true){
WaitCommEvent (m_hCom,&dwEvtMask,NULL);
if (dwEvtMask&EV_RXCHAR == EV_RXCHAR)
if(ComStat.cbInQue>0)
ReadFile(m_hCom,m_readbuf,ComStat.cbInQue,&nLength,&olRead);
}提取定位数据
GPS接收机只要处于工作状态就会源源不断地把接收并计算出的GPS导航定位信息通过串口传送到计算机中。前面的代码只负责从串口接收数据并将其放置于缓存,在没有进一步处理之前缓存中是一长串字节流,这些信息在没有经过分类提取之前是无法加以利用的。因此,必须通过程序将各个字段的信息从缓存字节流中提取出来,将其转化成有实际意义的,可供高层决策使用的定位信息数据。同其他通讯协议类似,对GPS进行信息提取必须首先明确其帧结构,然后才能根据其结构完成对各定位信息的提取。对于本文所使用的GARMIN GPS天线板,其发送到计算机的数据主要由帧头、帧尾和帧内数据组成,根据数据帧的不同,帧头也不相同,主要有"$GPGGA"、"$GPGSA"、"$GPGSV"以及"$GPRMC"等。这些帧头标识了后续帧内数据的组成结构,各帧均以回车符和换行符作为帧尾标识一帧的结束。对于通常的情况,我们所关心的定位数据如经纬度、速度、时间等均可以从"$GPRMC"帧中获取得到,该帧的结构及各字段释义如下:
$GPRMC,<1>,<2>,<3>,<4>,<5>,<6>,<7>,<8>,<9>,<10>,<11>*hh
<1> 当前位置的格林尼治时间,格式为hhmmss
<2> 状态, A 为有效位置, V为非有效接收警告,即当前天线视野上方的卫星个数少于3颗。
<3> 纬度, 格式为ddmm.mmmm
<4> 标明南北半球, N 为北半球、S为南半球
<5> 径度,格式为dddmm.mmmm
<6> 标明东西半球,E为东半球、W为西半球
<7> 地面上的速度,范围为0.0到999.9
<8> 方位角,范围为000.0到 359.9 度
<9> 日期, 格式为ddmmyy
<10> 地磁变化,从000.0到 180.0 度
<11> 地磁变化方向,为E 或 W
至于其他几种帧格式,除了特殊用途外,平时并不常用,虽然接收机也在源源不断地向主机发送各种数据帧,但在处理时一般先通过对帧头的判断而只对"$GPRMC"帧进行数据的提取处理。如果情况特殊,需要从其他帧获取数据,处理方法与之也是完全类似的。由于帧内各数据段由逗号分割,因此在处理缓存数据时一般是通过搜寻ASCII码"$"来判断是否是帧头,在对帧头的类别进行识别后再通过对所经历逗号个数的计数来判断出当前正在处理的是哪一种定位导航参数,并作出相应的处理。下面就是对缓存Data中的数据进行解帧处理的主要代码,本文在此只关心时间(日期和时间)和地理坐标(经、纬度):
for(int i=0;i<DATALENGTH;I++){
if(Data[i]==''$'') file://帧头,SectionID为逗号计数器
SectionID=0;
if(Data[i]==10){ file://帧尾
}
if(Data[i]=='','') file://逗号计数
SectionID++;
else {
switch(SectionID){
case 1: file://