主机通过GPS模块获取当地卫星信息——经纬度和时间信息甚至海拔,通过无线通讯将这些信息发送到从机。
1, 模块采用U-BLOX NEO-6M模组,体积小巧,性能优异。
2, 模块增加放大电路,有利于无源陶瓷天线快速搜星。(MINI版本没有板载天线)
3, 模块可通过串口进行各种参数设置,并可保存在EEPROM,使用方便。
4, 模块自带SMA接口,可以连接各种有源天线,适应能力强。另外Mini版本带有IPEX接口,可以接本店小尺寸的有源陶瓷天线。
5, 模块兼容3.3V/5V电平,方便连接各种单片机系统。
6, 模块自带可充电后备电池,可以掉电保持星历数据。
7.本模块默认波特率为 9600。
第一次测试的时候,由于是第一次初始化,建议加上外置的天线,这样连接速率更快,在以后的连接中,因为GPS有备用电池可以掉电保存,有了第一次的数据,第二次的初始化会相对较快一些。
当然在室内也是可以的,只不过需要一根较长的天线,连接好GPS模块后,将天线至于窗户外面空旷的位置
序号 | 名称 | 说明 |
---|---|---|
1 | VCC | 电源(3.3V~5.0V) |
2 | GND | 地 |
3 | TXD | 模块串口发送脚(TTL电平,不能直接接RS232电平!),可接单片机的RXD |
4 | RXD | 模块串口接收脚(TTL电平,不能直接接RS232电平!),可接单片机的TXD |
5 | PPS | 时钟脉冲输出脚 |
其中,PPS引脚同时连接到了模块自带了的状态指示灯:PPS,该引脚连接在UBLOX NEO-6M
模组的TIMEPULSE端口,该端口的输出特性可以通过程序设置。PPS指示灯(即PPS引脚),
在默认条件下(没经过程序设置),有2个状态:
1, 常亮,表示模块已开始工作,但还未实现定位。
2, 闪烁(100ms灭,900ms亮),表示模块已经定位成功。
这样,通过 PPS 指示灯,我们就可以很方便的判断模块的当前状态,方便大家使用。
我们只需要学会怎么用即可,具体内部原理,美国佬也进行了封装,我们无从知晓
调试方式有两种USB调试法,和UART调试法,
其中,如果要使用USB调试,需要先下载驱动
下载好驱动后打开官方驱动工具
在上方工具栏的接收器中的端口处选择我们GPS模块的端口,其中 右侧的小窗口可以在边界调整。
当然也可以在receiver中调节波特率,在这里我设置的是9600
然后等待启动初始化结束,主界面小窗口会显示获取到的卫星数据
然后从VIEW选项卡中,通过xxxxx_Console选项卡调用控制平台和配置其他参数(在这里我们没有用到),通过xxxxxx_View观察数据
不过,一般我们不用调节它的一些参数,最多也就是用它调节一下GPS发送速率,(在这里我们没有调节)
其实我们就是通过MCU读取GPS模块获取到的已经打包好的数据,根据我们的需求对其进行拆分。
GPS发送的数据为字符型,所以我们采用字符型数组存储接收到的数据,在这里为了方便后期调用,我们把这些数据根据信息的种类进行拆分,借用结构体进行调用(这段code可以放到H文件里)
#include
#include
#include
//定义数组长度
#define GPS_Buffer_Length 80
#define UTCTime_Length 11
#define latitude_Length 11
#define N_S_Length 2
#define longitude_Length 12
#define E_W_Length 2
#define gpsRxBufferLength 76
typedef struct SaveData
{
char GPS_Buffer[GPS_Buffer_Length];
char isGetData; //是否获取到GPS数据
char isParseData; //是否解析完成
char UTCTime[UTCTime_Length]; //UTC时间
char latitude[latitude_Length]; //纬度
char N_S[N_S_Length]; //N/S
char longitude[longitude_Length]; //经度
char E_W[E_W_Length]; //E/W
char isUsefull; //定位信息是否有效
} xdata _SaveData;
由于这些数据内存对于52单片机的存储空间较大,我们将这些从串口接收到的数据存储到RAM中,
//uart.c
#include "uart.h"
char idata gpsRxBuffer[gpsRxBufferLength];
unsigned char RX_Count = 0;
_SaveData Save_Data;
void Uart_Init()
{
SCON = 0X50; //UART方式1;8位UART
REN = 1; //允许串行口接收数据
PCON = 0x00; //SMOD=0;波特率不加倍
TMOD = 0x20; //T1方式2,用于产生波特率
TH1 = 0xFD; //装初值
TL1 = 0xFD;
TR1 = 1; //启动定时器1
EA = 1; //打开全局中断控制
ES = 1; //打开串行口中断
}
void UartPrintf(unsigned char *p) //发送字符串
{
while(*p)
{
SBUF=*(p++);
while(TI==0);
TI=0;
}
}
void UartPrintASCII(unsigned char c) //发送一个字符
{
TI=0;
SBUF=c;
while(TI==0);
TI=0;
}
void RECEIVE_DATA(void) interrupt 4 using 3
{
unsigned char temp = 0;
char i = 0;
ES=0;
temp = SBUF;
RI = 0;
if(temp == '$')
{
RX_Count = 0;
}
if(RX_Count <= 5)
{
gpsRxBuffer[RX_Count++] = temp;
}
else if(gpsRxBuffer[0] == '$' &gpsRxBuffer[4] == 'M' && gpsRxBuffer[5] == 'C') //确定是否收到"GPRMC/GNRMC"这一帧数据
{
gpsRxBuffer[RX_Count++] = temp;
if(temp == '\n')
{
memset(Save_Data.GPS_Buffer, 0, GPS_Buffer_Length); //清空
memcpy(Save_Data.GPS_Buffer, gpsRxBuffer, RX_Count); //保存数据
Save_Data.isGetData = true;
RX_Count = 0;
memset(gpsRxBuffer, 0, gpsRxBufferLength); //清空
}
if(RX_Count >= 75)
{
RX_Count = 75;
gpsRxBuffer[RX_Count] = '\0';//添加结束符
}
}
ES=1;
}
void clrStruct()
{
Save_Data.isGetData = false;
Save_Data.isParseData = false;
Save_Data.isUsefull = false;
memset(Save_Data.GPS_Buffer, 0, GPS_Buffer_Length); //清空
memset(Save_Data.UTCTime, 0, UTCTime_Length);
memset(Save_Data.latitude, 0, latitude_Length);
memset(Save_Data.N_S, 0, N_S_Length);
memset(Save_Data.longitude, 0, longitude_Length);
memset(Save_Data.E_W, 0, E_W_Length);
}
然后我们可能在主函数中可能会调用接收到的这些数据,所以再进行外部声明
//uart.h
#ifndef __GPS_H__
#define __GPS_H__
#include
#include "main.h"
#define false 0
#define true 1
//函数或者变量声明
extern void Uart_Init();
extern void UartPrintf(unsigned char *p);
extern void UartPrintASCII(unsigned char c);
extern void clrStruct();
extern char idata gpsRxBuffer[gpsRxBufferLength];
extern unsigned char RX_Count;
extern _SaveData Save_Data;
根据在串口调试助手或者官方调试助手中获取到的GPS读取到的数据我们可以得知有效数据(对于我们而言)
)
在本项目中,对于经纬度的经度没有较高的要求,在这里我是直接提取的字符进而进行显示,代码如下:
void Print_to_OLED()
{
Hour = (Save_Data.GPS_Buffer[7]-0x30)*10+(Save_Data.GPS_Buffer[8]-0x30)+8;
Minute=(Save_Data.GPS_Buffer[9]-0x30)*10+(Save_Data.GPS_Buffer[10]-0x30);
Second=(Save_Data.GPS_Buffer[11]-0x30)*10+(Save_Data.GPS_Buffer[12]-0x30);
//UTC时间转换到北京时间 UTC+8 //0x30为ASCII转换为数字
Latitude=(Save_Data.GPS_Buffer[19]-0x30)*10+(Save_Data.GPS_Buffer[20]-0x30);
Longitude=(Save_Data.GPS_Buffer[32]-0x30)*100+(Save_Data.GPS_Buffer[33]-0x30)*10+(Save_Data.GPS_Buffer[34]-0x30);
}
main.c部分代码如下(示例):
void errorLog(int num)
{
while (1)
{
UartPrintf("ERROR");
UartPrintASCII(num+0x30);
UartPrintf("\r\n");
//测试用
// OLED_Clear();
// OLED_ShowString(0,0,"ERROR",12);
// OLED_ShowNum(50,2,num,1,12);
}
}
errorLog函数就是打印数据错误的函数,就是一个循环,相当于我们平时访问网页时偶尔显示的“错误代码:404”。
void parseGpsBuffer()
{
char *subString;
char *subStringNext;
char i = 0;
if (Save_Data.isGetData)
{
LED_GPS=LED_ON;// 得到数据,指示灯亮;闪烁代表正常
Save_Data.isGetData = false;
OLED_ShowString(60,1,"GPSok",8); //测试用
for (i = 0 ; i <= 6 ; i++)
{
if (i == 0)
{
if ((subString = strstr(Save_Data.GPS_Buffer, ",")) == NULL)
errorLog(1); //解析错误
}
else
{
subString++;
if ((subStringNext = strstr(subString, ",")) != NULL)
{
char usefullBuffer[2];
switch(i)
{
case 1:memcpy(Save_Data.UTCTime, subString, subStringNext - subString);break; //获取UTC时间
case 2:memcpy(usefullBuffer, subString, subStringNext - subString);break; //获取UTC时间
case 3:memcpy(Save_Data.latitude, subString, subStringNext - subString);break; //获取纬度信息
case 4:memcpy(Save_Data.N_S, subString, subStringNext - subString);break; //获取N/S
case 5:memcpy(Save_Data.longitude, subString, subStringNext - subString);break; //获取经度信息
case 6:memcpy(Save_Data.E_W, subString, subStringNext - subString);break; //获取E/W
default:break;
}
subString = subStringNext;
Save_Data.isParseData = true;
if(usefullBuffer[0] == 'A')
Save_Data.isUsefull = true;
else if(usefullBuffer[0] == 'V')
Save_Data.isUsefull = false;
}
else
{
errorLog(2); //解析错误
}
}
}
}
else //长灭,没有数据
{
LED_GPS=LED_OFF;
OLED_ShowString(60,1,"noGPS",8); //测试用
}
}
parseGpsBuffer()函数时解析函数用来判断GPS是否获取到数据,在这里我引入了LED_GPS端口作为指示灯驱动端口和OLED函数用来调试,方便我根据现象进行开发。
同时这里面有一个函数memcpy(),需要引用stdio.h和string.h。memcpy()是一个在C语言中常用的内存拷贝函数,用于将一块内存区域的内容复制到另一块内存区域,在这里我们通过它将我们读取到的数据存放到RAM区中,用于存储GPS数据。
然后就是提取其中有用的信息,并进行格式转换,判断标准是根据它发送的packet数据来进行筛选
【packet图】
void printGpsBuffer()
{
if (Save_Data.isParseData)
{
Save_Data.isParseData = false;
UartPrintf(Save_Data.GPS_Buffer);
Print_to_OLED();//格式转换
OLED_ShowString(0,2,"Time=",8);
OLED_ShowNum(40,2,Hour,2,8);OLED_ShowString(56,2,":",8);
OLED_ShowNum(64,2,Minute,2,8);OLED_ShowString(80,2,":",8);OLED_ShowNum(88,2,Second,2,8);
if(Save_Data.isUsefull)
{
Save_Data.isUsefull = false;
OLED_ShowString(0,3,"latitude=",8); OLED_ShowNum(72,3,Latitude,2,8);//纬度
OLED_ShowString(88,3,"'",8);OLED_ShowString(96,3,Save_Data.N_S,8);
OLED_ShowString(0,4,"longitude=",8); OLED_ShowNum(80,4,Longitude,3,8); //经度
OLED_ShowString(104,4,"'",8);OLED_ShowString(112,4,Save_Data.E_W,8);
}
else
{
UartPrintf("GPS DATA is not useful!\r\n");
OLED_Clear();
OLED_ShowString(0,0,"GPS not useful!",8);
}
}
}
打印数据的话,就因人而异了,上面是我用到的数据,在Oled上进行了打印,仅供参考。
在室内有时候连不上,即使连接了天线,最好还是到室外进行测试(但是很不方便,不过也没办法)