手把手教你一步步使用基于HAL库的STM32的GPS模块解析(NEO-6M-GPS)
(一)模块介绍NEO-6MGPS模块具有高灵敏度、低功耗、小型化、高追踪灵敏度大大扩大了其定位的覆盖面在普通GPS接收模块不能定位的地方如狭窄都市天空下、密集的从林环境NEO6M都能高精度定位。模块的高灵敏度、小静态漂移、低功耗及轻巧的体积适用于车载、手持设备如PDA车辆监控、手机、摄像机及其他移动定位系统的应用是GPS产品应用的好选择。在用户手册中可以看到GPS模块可以采用3.3V供电使用9600的波特率。上面是模块的图片在使用过程中将VCC连接至3.3VGND需要和模块公地模块的RX需要和单片机的TX连接模块的TX需要和单片机的RX连接也就是对接。(二)STM3CubeMX设置SYS 配置在 SYS 选项中将 Debug 模式设置为Serial Wire串行线调试便于后续程序下载与调试。如果没有勾选会出现后续无法下载烧录程序的问题。RCC 配置在 RCC 选项中将高速外部时钟HSE设置为Crystal/Ceramic Resonator晶振 / 陶瓷谐振器为系统提供稳定的外部时钟源。时钟树 配置在时钟选项中把时钟设置为STM32F1单片机运行的最快频率72Mhz为系统提供快速的的外部时钟频率更快运行速度。模式选择将 USART1 的工作模式设置为Asynchronous异步通信这是最常用的串口通信方式。波特率设置在 Parameter Settings 中将波特率配置为9600 Bits/s和GPS模块的通信速率保持一致确保数据传输稳定。中断使能在 NVIC Settings 中开启USART1 global interrupt全局中断使单片机能够响应串口接收中断事件。PA9被配置为USART1_TX发送引脚负责将单片机的数据发送到外部串口模块。PA10被配置为USART1_RX接收引脚负责接收来自串口模块的数据。(三)代码调试编写NEO-6M GPS 模块需要在开阔地带才能接收到有效卫星数据直接接单片机调试会受环境限制。因此我们先通过 CH340 串口模块采集真实 GPS 数据并保存再用 CH340 将保存的数据包发送给 STM32模拟 NEO-6M 模块的通信行为大幅降低调试难度。将 CH340 模块插入电脑 USB 口安装驱动后打开串口助手配置串口参数波特率 9600、8 位数据位、1 位停止位、无校验位将 NEO-6M 模块带到室外开阔地带远离高楼 / 遮挡等待模块定位成功持续等待 1-2 分钟将采集的数据保存记录下来。比如我的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采集完数据后断开 NEO-6M 模块将 CH340 直接连接 STM32模拟 GPS 模块发送数据。将自己保存的数据放在发送区设置为定时发送周期为1000ms模拟GPS模块发送数据。创建gps.c和.h文件以下是单片机的gps.c代码。切记勾选维库不然会出现卡死状态因为了重定向和printf#include gps.h#include stdlib.h // atof函数需要/* GPS */unsigned char GPSRX_BUFF[GPSRX_LEN_MAX];unsigned char GPSRX_LEN 0;uint8_t rx1;_SaveData Save_Data;int fputc(int ch ,FILE *f){HAL_UART_Transmit(huart1,(uint8_t *)ch,1,1000);return ch;}/******************************************************************* 函 数 名 称BSP_GPS_IRQHandler* 函 数 说 明串口中断服务函数* 函 数 形 参无* 函 数 返 回无* 作 者LC* 备 注无******************************************************************/#define GPRMC_FRAME_MAX_LEN 80void BSP_GPS_IRQHandler(uint8_t dat){// 第一步严格边界检查避免数组越界核心修复if(GPSRX_LEN (GPSRX_LEN_MAX - 1)) // 留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帧接收完成\n为帧尾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* 函 数 说 明解析GPS发送过来的数据* 函 数 形 参无* 函 数 返 回无* 作 者LC* 备 注无******************************************************************/void parseGpsBuffer(void){char *subString;char *subStringNext;char i 0;if (Save_Data.isGetData){Save_Data.isGetData 0;printf(**************\r\n);printf(%s\r\n,Save_Data.GPS_Buffer);for (i 0 ; i 6 ; i){if (i 0){if ((subString strstr(Save_Data.GPS_Buffer, ,)) NULL)HAL_Delay(1);printf(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; //获取UTC时间case 2:memcpy(usefullBuffer, subString, subStringNext - subString);break; //获取UTC时间case 3:memcpy(Save_Data.latitude, subString, subStringNext - subString);break; //获取纬度信息case 4:memcpy(Save_Data.N_S, subString, subStringNext - subString);break; //获取N/Scase 5:memcpy(Save_Data.longitude, subString, subStringNext - subString);break; //获取经度信息case 6:memcpy(Save_Data.E_W, subString, subStringNext - subString);break; //获取E/Wdefault: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(2\r\n);HAL_Delay(1);}}}}}/******************************************************************* 函 数 名 称printGpsBuffer* 函 数 说 明输出解析后的数据* 函 数 形 参无* 函 数 返 回无* 作 者LC* 备 注无******************************************************************/void printGpsBuffer(void){// uint8_t buff[100]{0};if (Save_Data.isParseData){Save_Data.isParseData 0;printf(Save_Data.UTCTime );printf(%s,Save_Data.UTCTime);printf(\r\n);if(Save_Data.isUsefull){Save_Data.isUsefull 0;//串口显示纬度printf(Save_Data.latitude );printf(%s,Save_Data.latitude);//串口显示printf(Save_Data.N_S );printf(%s\r\n,Save_Data.N_S);//串口显示经度printf(Save_Data.longitude );printf(%s,Save_Data.longitude);printf(\r\n);//串口显示printf(Save_Data.E_W );printf(%s,Save_Data.E_W);printf(\r\n);}else{printf(GPS DATA is not usefull!\r\n);}}}/*** brief GPS度分格式字符串转十进制浮点数核心函数* param dm_str: 度分格式字符串如经度11257.61501、纬度3438.64999* param is_longitude: 标记是否为经度1经度0纬度* retval 转换后的十进制浮点数如经度返回112.9602501纬度返回34.6441665* note 经度规则前3位度剩余分纬度规则前2位度剩余分*/float gps_str_to_float(char *dm_str, uint8_t is_longitude){// 1. 异常处理空字符串/NULL直接返回0.0if(dm_str NULL || strlen(dm_str) 0){return 0.0f;}float degree 0.0f; // 度float minute 0.0f; // 分char degree_str[4] {0}; // 存度的字符串经度最多3位纬度最多2位char minute_str[15] {0}; // 存分的字符串预留足够长度uint16_t dm_len strlen(dm_str);// 2. 按经度/纬度拆分度和分核心逻辑if(is_longitude) // 经度前3位为度剩余为分{// 经度字符串长度至少3位如112否则格式错误if(dm_len 3){return 0.0f;}// 提取度的部分前3位memcpy(degree_str, dm_str, 3);// 提取分的部分3位之后的所有字符strncpy(minute_str, dm_str 3, dm_len - 3);}else // 纬度前2位为度剩余为分{// 纬度字符串长度至少2位如34否则格式错误if(dm_len 2){return 0.0f;}// 提取度的部分前2位memcpy(degree_str, dm_str, 2);// 提取分的部分2位之后的所有字符strncpy(minute_str, dm_str 2, dm_len - 2);}// 3. 字符串转浮点数度和分degree atof(degree_str); // 度转数字minute atof(minute_str); // 分转数字// 4. 计算十进制经纬度十进制 度 分/60return (degree minute / 60.0f);}下面是.h文件代码#ifndef _GPS_H#define _GPS_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 255typedef 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/Schar longitude[longitude_Length]; //经度char E_W[E_W_Length]; //E/Wchar isUsefull; //定位信息是否有效} _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);#endif在中断回调函数中调用代码extern uint8_t rx1;void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){if (huart-Instance USART1){BSP_GPS_IRQHandler(rx1);HAL_UART_Receive_IT(huart1, rx1, 1);}}主程序中声明变量。float gps_n,gps_e;extern _SaveData Save_Data;主循环中调用解析和打印函数parseGpsBuffer();//维度Save_Data.latitude ,Save_Data.N_S--NprintGpsBuffer() //经度,Save_Data.longitude Save_Data.E_W -Egps_ngps_str_to_float(Save_Data.latitude, 0);gps_egps_str_to_float(Save_Data.longitude, 1);之后可以在串口中收到解析的完整数据包含时间经纬度等等信息可以通过gps_str_to_float函数将字符串解析为浮点数方便屏幕显示或者修改。参考移植代码见下NEO-6M-GPS模块 | 立创开发板技术文档中心