STM32嵌入式开发GPS如何操作
本篇教程将介绍如何使用STM32微控制器进行GPS定位。首先我们需要了解GPS定位的原理和基本知识,然后学习如何通过STM32芯片来接收和解析GPS信号,最后实现获取设备的准确位置信息。一、GPS是什么?GPS(全球定位系统)是一种基于卫星定位的导航技术,它在现代社会的多个领域中发挥着重要作用。以下是GPS开发的重要性:导航与定位、灾害管理、农业与渔业、科学研究与勘探、交通管理。以上就是今天要讲的
前言
本篇教程将介绍如何使用STM32微控制器进行GPS定位。首先我们需要了解GPS定位的原理和基本知识,然后学习如何通过STM32芯片来接收和解析GPS信号,最后实现获取设备的准确位置信息。
一、GPS是什么?
GPS(全球定位系统)是一种基于卫星定位的导航技术,它在现代社会的多个领域中发挥着重要作用。以下是GPS开发的重要性:导航与定位、灾害管理、农业与渔业、科学研究与勘探、交通管理。

二、使用步骤
1.GPS.C文件
#include "sys.h"
#include "GPS.h"
char rxdatabufer;
u16 point1 = 0;
_SaveData Save_Data;
#if SYSTEM_SUPPORT_UCOS
#include "includes.h"
#endif
#if 1
#pragma import(__use_no_semihosting)
struct __FILE
{
int handle;
};
FILE __stdout;
int _sys_exit(int x)
{
x = x;
return x;
}
int fputc(int ch, FILE *f)
{
while((USART2->SR&0X40)==0);
USART2->DR = (u8) ch;
return ch;
}
#endif
#if EN_USART2_RX
char USART_RX_BUF[USART_REC_LEN];
u16 USART_RX_STA=0;
void uart_init(u32 bound)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2|RCC_APB2Periph_GPIOA, ENABLE);
//USART2_TX PA.9
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; //PA.9
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//USART2_RX PA.10
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//USART2 NVIC 中断
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
USART_InitStructure.USART_BaudRate = bound;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
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);
CLR_Buf();
void USART2_IRQHandler(void)
{
u8 Res;
#ifdef OS_TICKS_PER_SEC
OSIntEnter();
#endif
if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET)
{
Res =USART_ReceiveData(USART2);//(USART2->DR);
if(Res == '$')
{
point1 = 0;
}
USART_RX_BUF[point1++] = Res;
if(USART_RX_BUF[0] == '$' && USART_RX_BUF[4] == 'M' && USART_RX_BUF[5] == 'C')
{
if(Res == '\n')
{
memset(Save_Data.GPS_Buffer, 0, GPS_Buffer_Length);
memcpy(Save_Data.GPS_Buffer, USART_RX_BUF, point1);
Save_Data.isGetData = true;
point1 = 0;
memset(USART_RX_BUF, 0, USART_REC_LEN);
}
}
if(point1 >= USART_REC_LEN)
{
point1 = USART_REC_LEN;
}
}
#ifdef OS_TICKS_PER_SEC
OSIntExit();
#endif
}
u8 Hand(char *a)
{
if(strstr(USART_RX_BUF,a)!=NULL)
return 1;
else
return 0;
}
void CLR_Buf(void)
{
memset(USART_RX_BUF, 0, USART_REC_LEN);
point1 = 0;
}
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);
}
#endif
-
uart_init(u32 bound):这个函数用于初始化串口2,包括配置GPIO引脚、中断优先级和串口参数等。具体步骤如下:- 使能USART2和GPIOA的时钟。
- 配置USART2的TX引脚(PA.9)为复用推挽输出模式。
- 配置USART2的RX引脚(PA.10)为浮空输入模式。
- 配置USART2的中断优先级。
- 初始化USART2的参数,包括波特率、数据位、停止位、校验位和硬件流控制等。
- 使能USART2的接收中断。
- 使能USART2。
- 清空接收缓冲区。
-
USART2_IRQHandler(void):这个函数是串口2的中断服务函数,当串口接收到数据时,会触发这个中断。在这个函数中,首先读取接收到的数据,然后判断是否是特定的帧头,如果是,则开始接收数据,并在接收到换行符时,将接收到的数据保存到一个结构体中,并标记数据接收完成。具体步骤如下:- 进入中断服务函数。
- 判断是否是接收中断。
- 读取接收到的数据。
- 判断是否是帧头,如果是,则清空接收缓冲区,并开始接收数据。
- 判断是否是换行符,如果是,则将接收到的数据保存到结构体中,并标记数据接收完成。
- 判断接收缓冲区是否已满,如果是,则停止接收数据。
- 退出中断服务函数。
-
Hand(char *a):这个函数用于判断接收到的数据中是否包含特定的字符串。 -
CLR_Buf(void):这个函数用于清空接收缓冲区。 -
clrStruct():这个函数用于清空保存GPS数据的结构体。
2.GPSUP.c文件
代码如下(示例):
#include "stm32f10x.h" // 引入STM32F10x系列芯片的头文件
#include "GPS.h" // 引入GPS模块的头文件
uint8_t buff[128]; // 定义一个128字节的缓冲区
uint8_t buff1[128]; // 定义一个128字节的缓冲区
uint8_t buff2[128]; // 定义一个128字节的缓冲区
uint8_t latitude[32]; // 定义一个32字节的数组,用于存储纬度信息
uint8_t longitude[32]; // 定义一个32字节的数组,用于存储经度信息
uint16_t latitude1,latitude2; // 定义两个16位的变量,用于存储纬度的整数部分和小数部分
uint32_t latitude3; // 定义一个32位的变量,用于存储纬度的小数部分的小数部分
uint16_t longitude1,longitude2; // 定义两个16位的变量,用于存储经度的整数部分和小数部分
uint32_t longitude3; // 定义一个32位的变量,用于存储经度的小数部分的小数部分
double GPS_latitude; // 定义一个双精度浮点数,用于存储转换后的纬度值
double GPS_longitude; // 定义一个双精度浮点数,用于存储转换后的经度值
void GPS_Init(u32 bound)
{
uart_init(bound); // 初始化串口,设置波特率为bound
}
void errorLog(int num)
{
while(1)
{
printf("error\r\n"); // 打印错误信息
}
}
uint8_t ASCLL_TO_OCT(uint8_t c)
{
uint8_t num;
switch(c)
{
case 0x30: num = 0;break; // 将ASCII码字符'0'转换为数字0
case 0x31: num = 1;break; // 将ASCII码字符'1'转换为数字1
case 0x32: num = 2;break; // 将ASCII码字符'2'转换为数字2
case 0x33: num = 3;break; // 将ASCII码字符'3'转换为数字3
case 0x34: num = 4;break; // 将ASCII码字符'4'转换为数字4
case 0x35: num = 5;break; // 将ASCII码字符'5'转换为数字5
case 0x36: num = 6;break; // 将ASCII码字符'6'转换为数字6
case 0x37: num = 7;break; // 将ASCII码字符'7'转换为数字7
case 0x38: num = 8;break; // 将ASCII码字符'8'转换为数字8
case 0x39: num = 9;break; // 将ASCII码字符'9'转换为数字9
}
return num; // 返回转换后的数字
}
void parseGpsBuffer(void)
{
char *subString; // 定义一个字符指针,用于存储子字符串的起始位置
char *subStringNext; // 定义一个字符指针,用于存储下一个子字符串的起始位置
char i = 0;
if (Save_Data.isGetData) // 如果已经获取到GPS数据
{
Save_Data.isGetData = false; // 将标志位设置为false,表示数据已经被处理
for (i = 0 ; i <= 6 ; i++) // 循环解析7个字段
{
if (i == 0)
{
if ((subString = strstr(Save_Data.GPS_Buffer, ",")) == NULL) // 查找第一个逗号
errorLog(1); // 如果没有找到逗号,记录错误
}
else
{
subString++; // 移动到下一个字符
if ((subStringNext = strstr(subString, ",")) != NULL) // 查找下一个逗号
{
char usefullBuffer[2]; // 定义一个2字节的缓冲区来存储有用的数据
switch(i)
{
case 1:memcpy(Save_Data.UTCTime, subString, subStringNext - subString);break; // 解析UTC时间
case 2:memcpy(usefullBuffer, subString, subStringNext - subString);break; // 解析有用数据
case 3:memcpy(Save_Data.latitude, subString, subStringNext - subString);break; // 解析纬度
case 4:memcpy(Save_Data.N_S, subString, subStringNext - subString);break; // 解析南北半球
case 5:memcpy(Save_Data.longitude, subString, subStringNext - subString);break; // 解析经度
case 6:memcpy(Save_Data.E_W, subString, subStringNext - subString);break; // 解析东西半球
default:break;
}
subString = subStringNext; // 更新子字符串指针
Save_Data.isParseData = true; // 标记数据已经被解析
if(usefullBuffer[0] == 'A') // 如果有用数据是A,表示数据有效
Save_Data.isUsefull = true;
else if(usefullBuffer[0] == 'V') // 如果有用数据是V,表示数据无效
Save_Data.isUsefull = false;
}
else
{
errorLog(2); // 如果没有找到下一个逗号,记录错误
}
}
}
// 将解析后的纬度和经度数据格式化为字符串
sprintf((char*)latitude,"latitude.%s",Save_Data.latitude);
sprintf((char*)longitude,"longitude.%s",Save_Data.longitude);
for(int i = 0; i <= 1; i++)
{
if(i == 0)
{
if((subString = strstr((char*)longitude, ".")) == NULL){errorLog(1);} // 查找经度中的小数点
}
else
{
subString++; // 移动到小数点后面的字符
// 解析经度的小数部分
if ((subStringNext = strstr(subString, ".")) != NULL)
{
int len;
len = subStringNext - subString;
switch(len)
{
case 3:
longitude1 = ASCLL_TO_OCT(*subString);
subString++;
for(int j = 0; j <= 1; j++)
{
longitude2 = longitude2 * 10 + ASCLL_TO_OCT(*subString);
subString++;
}
subString++;
for(int j = 0; j <= 4; j++)
{
longitude3 = longitude3 * 10 + ASCLL_TO_OCT(*subString);
subString++;
}
break;
case 4:
for(int j = 0; j <= 1; j++)
{
longitude1 = longitude1 * 10 + ASCLL_TO_OCT(*subString);
subString++;
}
for(int j = 0; j <= 1; j++)
{
longitude2 = longitude2 * 10 + ASCLL_TO_OCT(*subString);
subString++;
}
subString++;
for(int j = 0; j <= 4; j++)
{
longitude3 = longitude3 * 10 + ASCLL_TO_OCT(*subString);
subString++;
}
break;
case 5:
for(int j = 0; j <= 2; j++)
{
longitude1 = longitude1 * 10 + ASCLL_TO_OCT(*subString);
subString++;
}
for(int j = 0; j <= 1; j++)
{
longitude2 = longitude2 * 10 + ASCLL_TO_OCT(*subString);
subString++;
}
subString++;
for(int j = 0; j <= 4; j++)
{
longitude3 = longitude3 * 10 + ASCLL_TO_OCT(*subString);
subString++;
}
break;
}
GPS_longitude = longitude1 + longitude2 / 60.0 + longitude3 / 6000000.0; // 计算经度的十进制表示
}
}
}
longitude1 = 0;
longitude2 = 0;
longitude3 = 0;
for(int i = 0; i <= 1; i++)
{
if(i == 0)
{
if((subString = strstr((char*)latitude, ".")) == NULL){errorLog(1);} // 查找纬度中的小数点
}
else
{
subString++; // 移动到小数点后面的字符
// 解析纬度的小数部分
if ((subStringNext = strstr(subString, ".")) != NULL)
{
int len;
len = subStringNext - subString;
switch(len)
{
case 3:
latitude1 = *subString;
subString++;
for(int j = 0; j <= 1; j++)
{
latitude2 = latitude2 * 10 + ASCLL_TO_OCT(*substring);
subString++;
}
subString++;
for(int j = 0; j <= 4; j++)
{
latitude3 = latitude3 * 10 + ASCLL_TO_OCT(*subString);
subString++;
}
break;
//1409.80518
case 4:
for(int j = 0; j <= 1; j++)
{
latitude1 = latitude1 * 10 + ASCLL_TO_OCT(*subString);
subString++;
}
for(int j = 0; j <= 1; j++)
{
latitude2 = latitude2 * 10 + ASCLL_TO_OCT(*subString);
subString++;
}
subString++;
for(int j = 0; j <= 4; j++)
{
latitude3 = latitude3 * 10 + ASCLL_TO_OCT(*subString);
subString++;
}
break;
}
GPS_latitude = latitude1 + latitude2 / 60.0 + latitude3 / 6000000.0;
}
}
}
latitude1 = 0;
latitude2 = 0;
latitude3 = 0;
}
这段代码是一个完整的GPS数据解析程序,主要功能包括串口初始化、错误日志记录、ASCII码到八进制的转换、GPS数据缓冲区解析以及经纬度数据的提取和转换。
以下是对代码的详细解释:
-
串口初始化:
GPS_Init(u32 bound)函数用于初始化串口通信,设置波特率等参数。 -
错误日志记录:
errorLog(int num)函数用于在解析过程中出现错误时记录错误信息。 -
ASCII码到八进制的转换:
ASCLL_TO_OCT(uint8_t c)函数用于将ASCII字符转换为对应的八进制数值。 -
GPS数据缓冲区解析:
parseGpsBuffer(void)函数是核心部分,用于解析接收到的GPS数据。它通过字符串处理函数strstr来定位和提取关键信息,如UTC时间、纬度、经度等。 -
经纬度数据的提取和转换:在解析过程中,通过
subString和subStringNext指针来定位和提取经纬度数据的各个部分,然后进行相应的转换和计算,最终得到以度为单位的经纬度值。
代码的执行流程如下:
-
首先调用
GPS_Init函数初始化串口通信。 -
在主循环中,不断检查是否接收到GPS数据。如果接收到数据,将数据存储在
Save_Data.GPS_Buffer中,并设置Save_Data.isGetData标志位为true。 -
当
Save_Data.isGetData为true时,调用parseGpsBuffer函数进行数据解析。 -
在
parseGpsBuffer函数中,首先检查数据是否有效,如果有效,则解析出UTC时间、纬度、经度等信息,并将这些信息存储在相应的变量中。 -
解析完成后,将解析得到的经纬度数据进行进一步的处理和转换,得到以度为单位的经纬度值。
-
如果在解析过程中出现错误,将调用
errorLog函数记录错误信息。
3.main.c文件
#include "stm32f10x.h" //STM32头文件
#include "GPSUP.h" //GPS头文件
#include "oled.h" //OLED屏头文件
extern double GPS_latitude; //GPS数据获取
extern double GPS_longitude; //GPS数据获取
void ShowData_GPS(void);
int main(void)
{
GPS_Init(9600); //GPS、初始化
OLED_Init(); //OLED屏初始化
while(1)
{
parseGpsBuffer(); //GPS数据获取
ShowData_GPS();
}
}
void ShowData_GPS(void)
{
OLED_Clear();
OLED_ShowChinese(0,16,"经度");
OLED_ShowChar(33,16,':',OLED_8X16);
OLED_ShowFloatNum(42,16,GPS_longitude,3,2,OLED_8X16);
OLED_ShowChinese(83,16,"°");
OLED_ShowChinese(0,32,"纬度");
OLED_ShowChar(33,32,':',OLED_8X16);
OLED_ShowFloatNum(42,32,GPS_latitude,3,2,OLED_8X16);
OLED_ShowChinese(83,32,"°");
OLED_Update();
}
总结
以上就是今天要讲的内容,本文仅仅简单介绍了GPS的使用,而GPS提供了大量能使我们快速便捷地处理数据的函数和方法。
如果还想继续了解可加🐧群:310604674 毕业设计可做。
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐
所有评论(0)