手把手教你一步步从零玩转 STM32 GPS:HAL 库驱动 NEO-6M 获取时间经纬度
2026/7/14 7:11:17 网站建设 项目流程

(一)模块介绍: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:基础系统配置

  1. 选择你的 STM32 芯片型号(如 STM32F103C8T6);
  2. RCC 配置:选择外部晶振(HSE),配置系统时钟为 72MHz;

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

设置为72Mhz时钟主频 速度更快

3.SYS 配置:Debug 选择 Serial Wire(如果没勾选可能导致下一次无法烧录需要通过拉BOOT)。

开启SW调试下载模式

步骤 2:USART2 串口配置

  1. 左侧Connectivity→ 选择USART2
  2. 模式选择:Asynchronous(异步通信);
  3. 参数设置(与 GPS 模块一致):
    • 波特率:9600
    • 字长:8 Bits
    • 校验位:None
    • 停止位:1
  4. 无需修改硬件默认引脚(TX=PA2,RX=PA3)。

选择串口2异步模式,设置为9600波特率

步骤 3:开启串口接收中断

  1. 左侧System CoreNVIC
  2. 勾选USART2 global interrupt使能中断;
  3. 优先级默认即可,点击 OK。

开启串口2中断

同理可以开启串口1作为测试接口,打印一些结果

步骤 4:生成工程代码

  1. 点击GENERATE CODE生成 MDK/STM32CubeIDE 工程;
  2. 打开工程,准备添加 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博客

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询