目录
前言
本篇文章分享的内容是,STM32控制A9G模块通过发送AT指令,获取GPS定位的使用方法,以及经纬度WGS84转BD08坐标,可以通过百度地图直接搜索经纬度数据获取位置。
一、硬件连接
本项目使用的是正点原子F1系列的开发板以及安信可的A9G模块。开发板简介(来自官方):
l A9G开发板是基于安信可A9G GPRS/GSM+GPS/BDS 模块的多功能开发板,可以用来验证A9G模块的基础通信功能和外设功能。
l A9G开发板具备基础的 电话/短信,GPRS联网通信,GPS/BDS双模定位功能。
l A9G开发板板载了锂电池充电管理、麦克风、扬声器接口、USB通信接口、多个用户按键/led、TF卡槽、加速度传感器、SPI接口、I2C2接口、ADC接口。
硬件引脚的连接分别是:
橙色-->GND
蓝色-->5V
绿色1-->USART2_TXD
绿色2-->USART2_RXD
A9G模块通过串口与单片机进行通信,使用单片机的其他串口也是一样的,不一定要用串口2。
二、STM32--A9G 串口初始化
此处代码用于初始化串口2用于STM32与A9G模块进行通信。
代码如下(示例):
void A9G_uart2_init(u32 bound){
//GPIO端口设置
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); //使能USART1,GPIOA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //使能USART1,GPIOA时钟
//USART_TX
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_Init(GPIOA, &GPIO_InitStructure);
//USART_RX
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.10
//Usart NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3;//抢占优先级3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器
//USART 初始化设置
USART_InitStructure.USART_BaudRate = bound;//串口波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式
USART_Init(USART2, &USART_InitStructure);
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);//开启串口接受中断
USART_Cmd(USART2, ENABLE);
}
A9G_uart2_init(115200); // A9G初始化
三、A9G--AT指令获取GPS数据
因为习惯使用串口1进行调试,所以此处通过USART_printf函数,让串口2也能通过printf的方式去输出数据,方便后面向A9G模块发送AT指令。此处的USART_printf函数定义,参考了这篇文章:stm32学习笔记----双串口同时打开时的printf()问题-优快云博客。
/* 返回纬度:latitude, 经度: longitude*/
void Get_GPS_Data(float *lat_buffer, float *lon_buffer)
{
u8 GPS_Buffer[50]={0};
uint8_t Error;
float lat_minutes, lon_minutes;
double newlat, newlon;
Error = open_gps(GPS_Buffer);
sscanf((char*)GPS_Buffer, "%f,N,%f,E", &lat_minutes, &lon_minutes);
lat_minutes = (int)(lat_minutes / 100) + (lat_minutes - (int)(lat_minutes / 100) * 100) / 60.0;
lon_minutes = (int)(lon_minutes / 100) + (lon_minutes - (int)(lon_minutes / 100) * 100) / 60.0;
if(wgs2bd(lat_minutes, lon_minutes, &newlat, &newlon) == 0)
{
printf("BD09:lon=%lf,lat=%lf\r\n", newlon, newlat);
*lat_buffer = newlat;
*lon_buffer = newlon;
}
}
/*
* 函数名:USART_printf
* 描述 :格式化输出,类似于C库中的printf,但这里没有用到C库
* 输入 :-USARTx 串口通道
* -Data 要发送到串口的内容的指针
* -... 其他参数
* 输出 :无
* 返回 :无
* 调用 :外部调用
* 典型应用USART_printf( USART1, "\r\n this is a demo \r\n" );
* USART_printf( USART2, "\r\n %d \r\n", i );
* USART_printf( USART3, "\r\n %s \r\n", j );
*/
void USART_printf(USART_TypeDef* USARTx, uint8_t *Data,...)
{
const char *s;
int d;
char buf[16];
va_list ap;
va_start(ap, Data);
while ( *Data != 0) // 判断是否到达字符串结束符
{
if ( *Data == 0x5c ) //'\'
{
switch ( *++Data )
{
case 'r': //回车符
USART_SendData(USARTx, 0x0d);
Data ++;
break;
case 'n': //换行符 //???
USART_SendData(USARTx, 0x0a);
Data ++;
break;
default:
Data ++;
break;
}
}
else if ( *Data == '%')
{ //
switch ( *++Data )
{
case 's': //字符串
s = va_arg(ap, const char *);
for ( ; *s; s++)
{
USART_SendData(USARTx,*s);
while( USART_GetFlagStatus(USARTx, USART_FLAG_TC) == RESET );
}
Data++;
break;
case 'd': //十进制
d = va_arg(ap, int);
itoa(d, buf, 10);
for (s = buf; *s; s++)
{
USART_SendData(USARTx,*s);
while( USART_GetFlagStatus(USARTx, USART_FLAG_TC) == RESET );
}
Data++;
break;
default:
Data++;
break;
}
} /* end of else if */
else USART_SendData(USARTx, *Data++);
while( USART_GetFlagStatus(USARTx, USART_FLAG_TC) == RESET );
}
}
//函数功能:发送AT命令
//参数1:发送的命令
//参数2:AT响应
//返回值:响应是否正确,0表示正确响应;1表示错误响应
u8 send_com(u8 *send,u8 *respond)
{
u8 buffer[1000] = {0};//调用串口得到的数据进行
char j = 0;
USART_printf(USART2, send); //MCU通过串口发送数据
//printf("%s", send);
while(1)
{
if(BUF_STA == 1)//如果串口接受到数据
{
delay_us(100);
timeout++;
if(timeout > 2000)
{
USART2_RX_BUF[count] = '\0'; //转变成字符串的形式
strcpy((char *)buffer,(const char *)USART2_RX_BUF);//先获取串口当中的数据
count = 0; //计数清0
BUF_STA = 0; //接受数据结束
USART2_RX_STA = 1; //数据接受完成标志置1
}
}
if(USART2_RX_STA == 1)//数据接收完成了
{
USART2_RX_STA = 0;
//模块被重启啦,等待10秒之后在重新发命令
if(NULL != strstr((char *)buffer,"Init..."))
{
for(j = 0;j<30;j++)
{
delay_ms(500);
}
}
//AT+CIPSEND获取到了 ">" 之后发的命令都会无效的,因此直接退出该模式
if(NULL != strstr((char *)buffer,"> "))
{
}
if(NULL != strstr((char *)buffer,(char *)respond))
{
return 0;//返回的响应是需要的响应
}
else
{
return 1;//返回的响应不是想要的响应
}
}
}
}
//函数功能:开启GPS功能,同时值传递返回GPS数据
//参数:无
//返回值:对应的错误码
//参数:获取gps数据的指针 --- 数组的长度一定要大于30个字节(这里不进行软件过滤)
u8 open_gps(u8 *gps)
{
u8 buffer[1000] = {0};
char i = 0,j=0;
char count = 0;
char *p = NULL;
if(send_com((u8*)"AT+GPS=1\r\n",(u8 *)"OK"))//开启GPS
{
return AT_GPS_ERROR;
}
if(send_com((u8*)"AT+GPSRD=8\r\n",(u8 *)"OK"))//30秒输出一条NEMA信息
{
return AT_GPSRD_ERROR;
}
label:
strcpy((char *)buffer,(const char *)USART2_RX_BUF);
p = strstr((char *)buffer,(char *)"$GNRMC");
for(i=0;i<2;i++)
{
p=strstr((char *)p,(char *)",");
p++;
}
while( *p != 'V')//确定能够获取到有效定位
{
for(j=0;j<10;j++)
{
delay_ms(500);
}
goto label;
}
p=p+2;
i = 0;
while(count != 4)
{
gps[i]= *p;
p++;
i++;
if(*p == ',')
{
count++;
}
}
if(send_com((u8*)"AT+GPS=0\r\n",(u8 *)"OK"))//关闭GPS
{
return AT_GPS_ERROR;
}
count = 0; //USART_RX_BUF计数清零
memset(USART2_RX_BUF,0,sizeof(USART2_RX_BUF));
return NO_ERROR;
}
/*
* 函数名:itoa
* 描述 :将整形数据转换成字符串
* 输入 :-radix =10 表示10进制,其他结果为0
* -value 要转换的整形数
* -buf 转换后的字符串
* -radix = 10
* 输出 :无
* 返回 :无
* 调用 :被USART_printf()调用
*/
static char *itoa(int value, char *string, int radix)
{
int i, d;
int flag = 0;
char *ptr = string;
/* This implementation only works for decimal numbers. */
if (radix != 10)
{
*ptr = 0;
return string;
}
if (!value)
{
*ptr++ = 0x30;
*ptr = 0;
return string;
}
/* if this is a negative value insert the minus sign. */
if (value < 0)
{
*ptr++ = '-';
/* Make the value positive. */
value *= -1;
}
for (i = 10000; i > 0; i /= 10)
{
d = value / i;
if (d || flag)
{
*ptr++ = (char)(d + 0x30);
value -= (d * i);
flag = 1;
}
}
/* Null terminate the string. */
*ptr = 0;
return string;
}
// WGS84=>BD09 地球坐标系=>百度坐标系
int wgs2bd(double lat, double lon, double* pLat, double* pLon) {
double lat_ = 0.0, lon_ = 0.0;
wgs2gcj(lat, lon, &lat_, &lon_);
gcj2bd(lat_, lon_, pLat, pLon);
return 0;
}
接下来是使用实例,如果使用RTOS的话可以创建一个任务用于获取GPS的数据,这里为了简单起见直接放在while(1)中进行调用。
int main(void)
{
float lat_minutes, lon_minutes;
while(1)
{
Get_GPS_Data(&lat_minutes, &lon_minutes);
printf("\nlat = %.4f, lon = %.4f\n", lat_minutes, lon_minutes);
}
}
简单讲一下void Get_GPS_Data(float *lat_buffer, float *lon_buffer),这是一个void类型的无返回值函数,*lat_buffer是转换后的经度数据,*lon_buffer是转换后的纬度数据,使用时我们通过预先定义两个float类型的变量来存储经纬度数据转换之后的结果。
函数里的操作是通过open_gps函数发送AT指令开启GPS模式同时每30秒获取一次经纬度数据,通过sscanf()函数截取出字符串中的数字部分,将其进行转换。根据安信可科技官方的文章所述:
GPS输出的原始信息格式为NMEA标准,比如坐标(2236.3934,11350.3831)表示(22度36.3934分,113度50.3831分),转换成度:(22.606557°,113.839718°),1度=60分。有的朋友没有把度分转换为度直接在地图软件上搜索发现偏差大得离谱,或者没有按照1度=60分来转换导致偏差很大。之后还需要
在使用定位坐标显示到地图上时,注意坐标的转换,模组输出的位置是WGS84坐标,如果使用百度地图,需要转换成BD-09坐标。
因此这个函数干的就是经纬度的转换数据,并且需要注意的一点是,我这是转换成BD-09坐标,用于百度地图上进行搜索,其他地图软件可能需要先转换成GCJ-02坐标(火星坐标),此处转换方式可以参考这篇文章:【安信可A9G专题④】A9G模块/开发板GPS定位注意事项 && 坐标系转换_a9g模块使用说明-优快云博客
/* 返回纬度:latitude, 经度: longitude*/
void Get_GPS_Data(float *lat_buffer, float *lon_buffer)
{
u8 GPS_Buffer[50]={0};
uint8_t Error;
float lat_minutes, lon_minutes;
double newlat, newlon;
Error = open_gps(GPS_Buffer);
sscanf((char*)GPS_Buffer, "%f,N,%f,E", &lat_minutes, &lon_minutes);
lat_minutes = (int)(lat_minutes / 100) + (lat_minutes - (int)(lat_minutes / 100) * 100) / 60.0;
lon_minutes = (int)(lon_minutes / 100) + (lon_minutes - (int)(lon_minutes / 100) * 100) / 60.0;
if(wgs2bd(lat_minutes, lon_minutes, &newlat, &newlon) == 0)
{
printf("BD09:lon=%lf,lat=%lf\r\n", newlon, newlat);
*lat_buffer = newlat;
*lon_buffer = newlon;
}
}
四、总结
A9G_GPS.c 和 APG_GPS.h 文件都在下方链接中,作者也是个初学者,有问题希望各位不吝赐教,此外,觉得文章不错的也希望加个关注点个赞!!!
链接:https://pan.baidu.com/s/1EatsxEc0AnQ6hTMGDpkuTw?pwd=glbi
提取码:glbi
--来自百度网盘超级会员V4的分享