手把手教你一步步从零玩转 STM32 GPS:HAL 库驱动 NEO-6M 获取时间经纬度
(一)模块介绍:NEO-6M GPS 模块
1. 模块核心特性

- 型号:NEO-6M 是嵌入式开发中最常用的民用 GPS 定位模块;
- 通信方式:UART 串口(TTL 电平),默认配置:
9600波特率、8N1; - 输出协议:标准
NMEA-0183定位协议,自动发送多帧定位数据; - 核心数据帧:
$GPRMC帧,包含:✅ UTC 时间 / 日期 ✅ 经纬度原始数据 ✅ 定位有效状态 ✅ 速度航向信息 - 硬件接口:共 4 个引脚(VCC、GND、TX、RX),3.3V 供电,切记需要到室外,该模块室内无法获取信息!
2. 硬件连接规则
STM32 ↔ NEO-6M
- VCC ↔ 3.3V
- GND ↔ GND
- USART2_RX(PA3) ↔ GPS_TX
- USART2_TX(PA2) ↔ GPS_RX
3. 定位说明
模块需要在室外空旷环境才能定位成功,室内 / 遮挡环境会提示定位无效。
(二)初始化:STM32CUBEMX 配置步骤
本工程使用 USART2 接收 GPS 数据,按照以下步骤配置:
步骤 1:基础系统配置
- 选择你的 STM32 芯片型号(如 STM32F103C8T6);
- RCC 配置:选择外部晶振(HSE),配置系统时钟为 72MHz;

选择外部晶振作为系统时钟

设置为72Mhz时钟主频 速度更快
3.SYS 配置:Debug 选择 Serial Wire(如果没勾选可能导致下一次无法烧录需要通过拉BOOT)。

开启SW调试下载模式
步骤 2:USART2 串口配置
- 左侧
Connectivity→ 选择USART2; - 模式选择:
Asynchronous(异步通信); - 参数设置(与 GPS 模块一致):
- 波特率:9600
- 字长:8 Bits
- 校验位:None
- 停止位:1
- 无需修改硬件默认引脚(TX=PA2,RX=PA3)。

选择串口2异步模式,设置为9600波特率
步骤 3:开启串口接收中断
- 左侧
System Core→NVIC; - 勾选
USART2 global interrupt使能中断; - 优先级默认即可,点击 OK。

开启串口2中断
同理可以开启串口1作为测试接口,打印一些结果
步骤 4:生成工程代码
- 点击
GENERATE CODE生成 MDK/STM32CubeIDE 工程; - 打开工程,准备添加 GPS 驱动代码。
(三)核心代码:gps.h + gps.c 完整文件
1. GPS 头文件 gps.h
#ifndef _GPS_H
#define _GPS_H
#include "main.h"
// 缓存与数据长度宏定义
#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 GPSRX_LEN_MAX 255
// GPS数据存储结构体
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; // 定位信息是否有效
char UTCDate[15]; // GPS日期(DDMMYY)
} _SaveData;
// 函数声明
void BSP_GPS_IRQHandler(uint8_t dat);
void parseGpsBuffer(void);
void printGpsBuffer(void);
float gps_str_to_float(char *dm_str, uint8_t is_longitude);
void str_time_to_arr(char *utc_str, uint8_t *time);
void gps_date_split(char *date_str, uint8_t *day);
uint8_t Get_Week(int year, int month, int day);
#endif
2. GPS 驱动文件 gps.c
#include "gps.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
// GPS接收全局变量
unsigned char GPSRX_BUFF[GPSRX_LEN_MAX];
unsigned char GPSRX_LEN = 0;
_SaveData Save_Data;
#define GPRMC_FRAME_MAX_LEN 80
/******************************************************************
* 函 数 名 称:BSP_GPS_IRQHandler
* 函 数 说 明:GPS串口中断接收函数
******************************************************************/
void BSP_GPS_IRQHandler(uint8_t dat)
{
// 边界检查,防止数组越界
if(GPSRX_LEN >= (GPSRX_LEN_MAX - 1))
{
GPSRX_LEN = 0;
memset((void*)GPSRX_BUFF, 0, GPSRX_LEN_MAX);
}
// 帧头$,重置接收缓存
if(dat == '$')
{
GPSRX_LEN = 0;
memset((void*)GPSRX_BUFF, 0, GPSRX_LEN_MAX);
}
GPSRX_BUFF[GPSRX_LEN++] = dat;
// 识别$GPRMC帧,接收完成
if(GPSRX_BUFF[0] == '$' && strncmp((char*)&GPSRX_BUFF[1], "GPRMC", 4) == 0)
{
if(dat == '\n' && GPSRX_LEN <= GPRMC_FRAME_MAX_LEN)
{
memcpy(Save_Data.GPS_Buffer, (char*)GPSRX_BUFF, GPSRX_LEN);
Save_Data.GPS_Buffer[GPSRX_LEN] = '\0';
Save_Data.isGetData = 1;
// 重置缓存
GPSRX_LEN = 0;
memset((void*)GPSRX_BUFF, 0, GPSRX_LEN_MAX);
}
}
}
/******************************************************************
* 函 数 名 称:parseGpsBuffer
* 函 数 说 明:解析GPRMC数据帧
******************************************************************/
void parseGpsBuffer(void)
{
char *subString;
char *subStringNext;
char i = 0;
if (Save_Data.isGetData)
{
Save_Data.isGetData = 0;
for (i = 0 ; i <= 9 ; i++)
{
if (i == 0)
{
if ((subString = strstr(Save_Data.GPS_Buffer, ",")) == NULL)
printf("GPS_1\r\n");
}
else
{
subString++;
if ((subStringNext = strstr(subString, ",")) != NULL)
{
char usefullBuffer[2];
switch(i)
{
case 1:memcpy(Save_Data.UTCTime, subString, subStringNext - subString);break;
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;
case 9:memcpy(Save_Data.UTCDate, subString, subStringNext - subString);break;
default:break;
}
subString = subStringNext;
Save_Data.isParseData = 1;
if(usefullBuffer[0] == 'A')
Save_Data.isUsefull = 1;
else if(usefullBuffer[0] == 'V')
Save_Data.isUsefull = 0;
}
else
{
printf("GPS_2\r\n");
}
}
}
}
}
/******************************************************************
* 函 数 名 称:printGpsBuffer
* 函 数 说 明:串口打印GPS解析数据
******************************************************************/
void printGpsBuffer(void)
{
if (Save_Data.isParseData)
{
Save_Data.isParseData = 0;
printf("UTC时间: %s\r\n",Save_Data.UTCTime);
if(Save_Data.isUsefull)
{
Save_Data.isUsefull = 0;
printf("纬度: %s %s\r\n",Save_Data.latitude,Save_Data.N_S);
printf("经度: %s %s\r\n",Save_Data.longitude,Save_Data.E_W);
}
else
{
printf("GPS 定位无效!\r\n");
}
}
}
/******************************************************************
* 函 数 名 称:gps_str_to_float
* 函 数 说 明:度分格式转十进制经纬度
******************************************************************/
float gps_str_to_float(char *dm_str, uint8_t is_longitude)
{
if(dm_str == NULL || strlen(dm_str) == 0)
{
return 0.0f;
}
float degree = 0.0f;
float minute = 0.0f;
char degree_str[4] = {0};
char minute_str[15] = {0};
uint16_t dm_len = strlen(dm_str);
if(is_longitude)
{
if(dm_len < 3) return 0.0f;
memcpy(degree_str, dm_str, 3);
strncpy(minute_str, dm_str + 3, dm_len - 3);
}
else
{
if(dm_len < 2) return 0.0f;
memcpy(degree_str, dm_str, 2);
strncpy(minute_str, dm_str + 2, dm_len - 2);
}
degree = atof(degree_str);
minute = atof(minute_str);
return (degree + minute / 60.0f);
}
/******************************************************************
* 函 数 名 称:str_time_to_arr
* 函 数 说 明:UTC时间转北京时间(UTC+8)
******************************************************************/
void str_time_to_arr(char *utc_str, uint8_t *time)
{
uint8_t utc_hour, utc_min;
if(utc_str == NULL || time == NULL) return;
utc_hour = (utc_str[0] - '0') * 10 + (utc_str[1] - '0');
utc_min = (utc_str[2] - '0') * 10 + (utc_str[3] - '0');
time[0] = utc_hour + 8;
if(time[0] >= 24) time[0] -= 24;
time[1] = utc_min;
}
/******************************************************************
* 函 数 名 称:gps_date_split
* 函 数 说 明:拆分GPS日期
******************************************************************/
void gps_date_split(char *date_str, uint8_t *day)
{
if(date_str == NULL || day == NULL) return;
day[0] = (date_str[0]-'0')*10 + (date_str[1]-'0');
day[1] = (date_str[2]-'0')*10 + (date_str[3]-'0');
day[2] = (date_str[4]-'0')*10 + (date_str[5]-'0');
}
/******************************************************************
* 函 数 名 称:Get_Week
* 函 数 说 明:计算星期几
******************************************************************/
uint8_t Get_Week(int year, int month, int day)
{
if(month == 1 || month == 2)
{
month += 12;
year--;
}
int week = (day + 2*month + 3*(month+1)/5 + year + year/4 - year/100 + year/400) % 7;
return (uint8_t)week;
}
(四)使用实例:main.c 中调用 GPS 驱动
1. 第一步:添加串口中断回调
在工程中找到 stm32xx_it.c 文件,添加以下代码(USART2 中断处理)
#include "gps.h"
// 定义GPS串口接收变量
uint8_t rx2;
// 串口接收完成回调函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART2)
{
// 把接收到的数据传给GPS处理函数
BSP_GPS_IRQHandler(rx2);
// 重新开启中断接收
HAL_UART_Receive_IT(&huart2, &rx2, 1);
}
}
2. 第二步:main.c 完整使用代码
#include "main.h"
#include "usart.h"
#include "gpio.h"
#include "gps.h"
#include <stdio.h>
// 全局变量
uint8_t beijing_time[2]; // 北京时间[小时,分钟]
uint8_t gps_date[3]; // 日期[日,月,年]
uint8_t week; // 星期
char *week_table[] = {"周一","周二","周三","周四","周五","周六","周日"};
// 重定向printf
int fputc(int ch ,FILE *f)
{
HAL_UART_Transmit(&huart1,(uint8_t *)&ch,1,1000);
return ch;
}
int main(void)
{
// 1. 系统初始化
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init(); // 调试串口
MX_USART2_UART_Init(); // GPS串口
// 2. 开启GPS串口中断接收
HAL_UART_Receive_IT(&huart2, &rx2, 1);
while (1)
{
// 3. 解析GPS数据
parseGpsBuffer();
// 4. 打印GPS原始数据
printGpsBuffer();
// 5. 数据格式转换
if(Save_Data.isParseData && Save_Data.isUsefull)
{
// 转换北京时间
str_time_to_arr(Save_Data.UTCTime, beijing_time);
// 拆分日期
gps_date_split(Save_Data.UTCDate, gps_date);
// 计算星期
week = Get_Week(2000 + gps_date[2], gps_date[1], gps_date[0]);
// 转换十进制经纬度
float lat = gps_str_to_float(Save_Data.latitude, 0);
float lon = gps_str_to_float(Save_Data.longitude, 1);
// 打印转换后数据
printf("=====================================\r\n");
printf("北京时间: %02d:%02d\r\n", beijing_time[0], beijing_time[1]);
printf("日期: 20%02d-%02d-%02d\r\n", gps_date[2], gps_date[1], gps_date[0]);
printf("星期: %s\r\n", week_table[week]);
printf("十进制纬度: %.5f\r\n", lat);
printf("十进制经度: %.5f\r\n", lon);
printf("=====================================\r\n\r\n");
}
HAL_Delay(1000);
}
}
测试帧:
6,03,40,105,27,14,86,293,36,19,36,290,37*7B
$GPGSV,2,2,07,23,,,10,30,24,208,26,50,37,132,31*76$GPGLL,3438.65354,N,11257.61544,E,072530.00,A,A*65
$GPRMC,072531.00,A,3438.65335,N,11257.61546,E,0.019,,130226,,,A*74
$GPVTG,,T,,M,0.019,N,0.036,K,A*2E
$GPGGA,072531.00,3438.65335,N,11257.61546,E,1,05,1.40,276.4,M,-17.0,M,,*78
$GPGSA,A,3,01,30,19,03,14,,,,,,,,3.05,1.40,2.71*09
$GPGSV,2,1,07,01,44,044,36,03,40,105,28,14,86,293,37,19,36,290,38*7A
$GPGSV,2,2,07,23,,,12,30,24,208,26,50,37,132,32*77
也可参考下面文章
手把手教你一步步使用基于HAL库的STM32的GPS模块解析(NEO-6M-GPS)_stm32 hal库的 给gps 编程-CSDN博客
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)