手把手教你用STM32F103C8T6解析UM220北斗GPS模块数据(附OLED显示完整代码)

张开发
2026/4/7 11:00:33 15 分钟阅读

分享文章

手把手教你用STM32F103C8T6解析UM220北斗GPS模块数据(附OLED显示完整代码)
从零搭建北斗GPS定位终端STM32F103C8T6与UM220模块实战指南当你第一次拿到UM220北斗GPS模块和STM32开发板时可能会被各种接线和代码搞得晕头转向。本文将带你一步步完成这个项目从硬件连接到软件实现最终在OLED屏幕上实时显示位置信息。不同于简单的代码展示我们会重点讲解那些容易踩坑的细节比如NMEA协议解析中的校验和验证、DMS坐标转换的技巧以及如何优化串口数据接收的稳定性。1. 硬件准备与连接在开始编程之前我们需要确保所有硬件正确连接。UM220模块是一款支持北斗和GPS双模的定位模块具有2米平面定位精度和3.5米高程精度刷新率为1Hz。对于嵌入式开发者来说它的优势在于接口简单只需要一个串口就能获取定位数据。所需材料清单STM32F103C8T6开发板蓝桥杯或正点原子常见型号UM220-IV北斗GPS模块0.96寸OLED显示屏I2C接口杜邦线若干微型GPS天线UM220模块配套硬件连接关系如下表所示UM220模块引脚STM32F103C8T6引脚功能说明VCC3.3V电源输入GNDGND地线TXDPA3(USART2_RX)数据接收RXDPA2(USART2_TX)数据发送OLED显示屏连接SCL → PB6(I2C1_SCL)SDA → PB7(I2C1_SDA)VCC → 3.3VGND → GND注意UM220模块默认波特率为115200如果遇到通信问题请先确认波特率设置是否正确。有些模块出厂可能设置为9600需要根据实际型号调整。2. 开发环境搭建与基础配置在Keil MDK或者STM32CubeIDE中新建工程配置时钟树使系统运行在72MHz。我们需要初始化以下几个外设USART2串口配置void USART2_Init(uint32_t baudrate) { GPIO_InitTypeDef GPIO_InitStruct {0}; USART_InitTypeDef USART_InitStruct {0}; // 使能时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); // 配置TX(PA2)为复用推挽输出 GPIO_InitStruct.GPIO_Pin GPIO_Pin_2; GPIO_InitStruct.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStruct.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStruct); // 配置RX(PA3)为浮空输入 GPIO_InitStruct.GPIO_Pin GPIO_Pin_3; GPIO_InitStruct.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, GPIO_InitStruct); // 串口参数配置 USART_InitStruct.USART_BaudRate baudrate; USART_InitStruct.USART_WordLength USART_WordLength_8b; USART_InitStruct.USART_StopBits USART_StopBits_1; USART_InitStruct.USART_Parity USART_Parity_No; USART_InitStruct.USART_Mode USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART2, USART_InitStruct); // 使能串口 USART_Cmd(USART2, ENABLE); }I2C接口初始化用于OLEDvoid I2C1_Init(void) { GPIO_InitTypeDef GPIO_InitStruct {0}; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE); // 配置PB6(SCL)和PB7(SDA)为复用开漏输出 GPIO_InitStruct.GPIO_Pin GPIO_Pin_6 | GPIO_Pin_7; GPIO_InitStruct.GPIO_Mode GPIO_Mode_AF_OD; GPIO_InitStruct.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOB, GPIO_InitStruct); // I2C配置 I2C_InitTypeDef I2C_InitStruct; I2C_InitStruct.I2C_Mode I2C_Mode_I2C; I2C_InitStruct.I2C_DutyCycle I2C_DutyCycle_2; I2C_InitStruct.I2C_OwnAddress1 0; I2C_InitStruct.I2C_Ack I2C_Ack_Enable; I2C_InitStruct.I2C_AcknowledgedAddress I2C_AcknowledgedAddress_7bit; I2C_InitStruct.I2C_ClockSpeed 400000; // 400kHz I2C_Init(I2C1, I2C_InitStruct); I2C_Cmd(I2C1, ENABLE); }OLED显示屏初始化 这部分代码通常由显示屏厂商提供主要包含复位序列、初始化命令发送等。确保在调用显示函数前完成初始化。3. NMEA-0183协议解析实战UM220模块输出的数据遵循NMEA-0183协议这是一种广泛应用于GPS设备的文本格式协议。每条语句以$开头以回车换行结束包含多个以逗号分隔的字段。常见NMEA语句类型$GNGGA全球定位系统固定数据包含时间、位置和定位质量信息$GNRMC推荐最小定位信息包含时间、日期、位置、速度和航向$GNVTG地面速度信息$GNGSA当前卫星信息3.1 数据接收与缓冲处理由于GPS数据是连续发送的我们需要一个高效的接收机制。这里采用环形缓冲区结合串口中断的方式#define BUFFER_SIZE 512 typedef struct { uint8_t buffer[BUFFER_SIZE]; uint16_t head; uint16_t tail; uint8_t full; } RingBuffer; RingBuffer gps_buffer; void USART2_IRQHandler(void) { if(USART_GetITStatus(USART2, USART_IT_RXNE) ! RESET) { uint8_t data USART_ReceiveData(USART2); // 写入环形缓冲区 if(!gps_buffer.full) { gps_buffer.buffer[gps_buffer.head] data; gps_buffer.head (gps_buffer.head 1) % BUFFER_SIZE; if(gps_buffer.head gps_buffer.tail) { gps_buffer.full 1; } } } }在主循环中我们可以这样处理接收到的数据void ProcessGPSData(void) { static uint8_t nmea_line[256]; static uint16_t line_index 0; while(gps_buffer.tail ! gps_buffer.head || gps_buffer.full) { uint8_t ch gps_buffer.buffer[gps_buffer.tail]; gps_buffer.tail (gps_buffer.tail 1) % BUFFER_SIZE; gps_buffer.full 0; // 寻找NMEA语句开头 if(ch $ line_index 0) { nmea_line[line_index] ch; } // 收集数据直到回车换行 else if(line_index 0) { nmea_line[line_index] ch; // 检测到完整语句 if(ch \n || line_index sizeof(nmea_line)-1) { nmea_line[line_index] \0; ParseNMEASentence((char*)nmea_line); line_index 0; } } } }3.2 GNRMC和GNGGA语句解析下面是一个完整的NMEA解析函数实现重点处理GNRMC和GNGGA语句typedef struct { uint8_t hour, minute, second; uint16_t year; uint8_t month, day; float latitude; // 十进制度数 float longitude; // 十进制度数 float speed; // 节 float speed_kmh; // 公里/小时 float altitude; // 米 uint8_t valid; // 定位是否有效 } GPS_Data; uint8_t ParseNMEASentence(char *sentence, GPS_Data *data) { // 校验和验证 char *checksum_pos strchr(sentence, *); if(checksum_pos) { uint8_t expected_checksum strtol(checksum_pos1, NULL, 16); uint8_t actual_checksum 0; for(char *p sentence1; p checksum_pos; p) { actual_checksum ^ *p; } if(actual_checksum ! expected_checksum) { return 0; // 校验失败 } } // 根据语句类型调用具体解析函数 if(strncmp(sentence, $GNRMC, 6) 0) { return ParseGNRMC(sentence, data); } else if(strncmp(sentence, $GNGGA, 6) 0) { return ParseGNGGA(sentence, data); } return 0; } uint8_t ParseGNRMC(char *sentence, GPS_Data *data) { char *token strtok(sentence, ,); if(!token || strcmp(token, $GNRMC) ! 0) return 0; // 解析UTC时间 (hhmmss.sss) token strtok(NULL, ,); if(token strlen(token) 6) { sscanf(token, %2hhu%2hhu%2hhu, data-hour, data-minute, data-second); // 转换为本地时间东八区 >void DisplayPage1(GPS_Data *data) { char buffer[20]; // 清屏 OLED_Clear(); // 显示时间 OLED_ShowString(0, 0, Time:); snprintf(buffer, sizeof(buffer), %02d:%02d:%02d, >int main(void) { // 初始化代码... uint32_t last_display_time 0; uint8_t current_page 0; GPS_Data gps_data {0}; while(1) { // 处理GPS数据 ProcessGPSData(); // 每3秒切换页面 if(HAL_GetTick() - last_display_time 3000) { last_display_time HAL_GetTick(); current_page !current_page; if(current_page 0) { DisplayPage1(gps_data); } else { DisplayPage2(gps_data); } } } }5. 项目优化与进阶技巧完成基础功能后我们可以考虑以下几个优化方向数据平滑处理#define POSITION_HISTORY_SIZE 5 typedef struct { float latitude[POSITION_HISTORY_SIZE]; float longitude[POSITION_HISTORY_SIZE]; uint8_t index; } PositionHistory; float GetSmoothedLatitude(PositionHistory *history) { float sum 0; for(int i 0; i POSITION_HISTORY_SIZE; i) { sum history-latitude[i]; } return sum / POSITION_HISTORY_SIZE; } // 在解析数据后调用 void UpdatePositionHistory(PositionHistory *history, float lat, float lon) { history-latitude[history-index] lat; history-longitude[history-index] lon; history-index (history-index 1) % POSITION_HISTORY_SIZE; }低功耗优化当长时间静止时降低GPS模块的更新频率利用STM32的低功耗模式在无操作时进入睡眠关闭OLED背光或降低亮度增加轨迹记录功能使用STM32内部Flash或外接SD卡存储轨迹点实现简单的轨迹回放功能无线数据传输添加蓝牙或WiFi模块将定位数据发送到手机实现基于TCP/UDP的位置共享在实际测试中我发现UM220模块在开阔环境的定位速度通常在30秒以内但在城市峡谷或多层建筑附近首次定位时间可能延长到2-3分钟。为了提高用户体验可以在OLED上添加卫星信号强度指示让用户直观了解当前定位状态。

更多文章