前言

        本篇教程将介绍如何使用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数据缓冲区解析以及经纬度数据的提取和转换。

以下是对代码的详细解释:

  1. 串口初始化GPS_Init(u32 bound)函数用于初始化串口通信,设置波特率等参数。

  2. 错误日志记录errorLog(int num)函数用于在解析过程中出现错误时记录错误信息。

  3. ASCII码到八进制的转换ASCLL_TO_OCT(uint8_t c)函数用于将ASCII字符转换为对应的八进制数值。

  4. GPS数据缓冲区解析parseGpsBuffer(void)函数是核心部分,用于解析接收到的GPS数据。它通过字符串处理函数strstr来定位和提取关键信息,如UTC时间、纬度、经度等。

  5. 经纬度数据的提取和转换:在解析过程中,通过subStringsubStringNext指针来定位和提取经纬度数据的各个部分,然后进行相应的转换和计算,最终得到以度为单位的经纬度值。

代码的执行流程如下:

  1. 首先调用GPS_Init函数初始化串口通信。

  2. 在主循环中,不断检查是否接收到GPS数据。如果接收到数据,将数据存储在Save_Data.GPS_Buffer中,并设置Save_Data.isGetData标志位为true

  3. Save_Data.isGetDatatrue时,调用parseGpsBuffer函数进行数据解析。

  4. parseGpsBuffer函数中,首先检查数据是否有效,如果有效,则解析出UTC时间、纬度、经度等信息,并将这些信息存储在相应的变量中。

  5. 解析完成后,将解析得到的经纬度数据进行进一步的处理和转换,得到以度为单位的经纬度值。

  6. 如果在解析过程中出现错误,将调用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   毕业设计可做。

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐