【ESP32S3 + ATGM332D GPS模块实战二:SSD1306交互显示】
ESP32S3 ATGM332D GPS模块实战二SSD1306交互显示一、项目背景与目标上一篇博客《ESP32S3 ATGM332D GPS模块实战一TinyGPSPlus解析与本地墨卡托投影》中我们已经实现了GPS数据的解析、经纬度到本地ENU坐标系的转换以及速度和航向的计算。**本文在此基础上新增SSD1306 OLED显示屏的集成实现实时经纬度显示距离参考点距离显示GPS信号状态指示平滑的数据可视化交互二、硬件连接图ESP32S3开发板 ────────── ATGM332D GPS模块 GPIO18 (TX) ── RX GPIO17 (RX) ── TX 3.3V ── VCC GND ── GND ESP32S3开发板 ────────── SSD1306 OLED模块 (I2C) GPIO12 (SCL) ── SCL GPIO13 (SDA) ── SDA 3.3V ── VCC GND ── GNDESP32S3开发板ATGM332D GPS模块SSD1306 OLED模块三、核心代码实现完整代码#includeArduino.h#includeTinyGPS.h#includemath.h#includeU8g2lib.h// ATGM332D GPS模块配置#defineGPS_UARTSerial1// 使用硬件串口1#defineGPS_BAUD_RATE9600// ATGM332D默认波特率为9600#defineGPS_TX_PIN18// UART1_TX#defineGPS_RX_PIN17// UART1_RX// SSD1306 OLED配置#defineOLED_SCK_PIN12// SCL#defineOLED_SDA_PIN13// SDA// 输出频率配置#definePRINT_INTERVAL_MS500// 每秒5次 (1000/5200ms)// TinyGPSPlus对象TinyGPSPlus gps;// U8g2对象I2C接口SSD1306 128x64U8G2_SSD1306_128X64_NONAME_F_HW_I2Cu8g2(U8G2_R0,/* reset*/U8X8_PIN_NONE,/* clock*/OLED_SCK_PIN,/* data*/OLED_SDA_PIN);// 参考点初始位置作为本地坐标系原点doublerefLat0.0;doublerefLng0.0;boolrefSetfalse;// 上一时刻的位置和时间doublelastEast0.0;doublelastNorth0.0;unsignedlonglastTimeMs0;// 计算得到的速度和航向floatcalcSpeed0.0;floatcalcHeading0.0;// 函数声明voidlatLngToENU(doublelat,doublelng,doublerefLat,doublerefLng,doubleeast,doublenorth);voidupdateOLED(doublelat,doublelng,doubledistance);voidsetup(){// 初始化USB串口用于调试输出Serial.begin(115200);delay(500);Serial.println(ATGM332D GPS Module Test Demo);Serial.println(Using TinyGPSPlus Library with Local Mercator Projection);Serial.println(Initializing...);// 配置并初始化GPS串口GPS_UART.begin(GPS_BAUD_RATE,SERIAL_8N1,GPS_RX_PIN,GPS_TX_PIN);// 等待GPS模块启动delay(1000);// 初始化OLED屏幕u8g2.begin();u8g2.clearBuffer();u8g2.setFont(u8g2_font_ncenB08_tr);u8g2.drawStr(0,20,GPS Initializing...);u8g2.sendBuffer();Serial.println(GPS module initialized!);Serial.printf(GPS UART: Serial1, TX%d, RX%d, Baud%d\n,GPS_TX_PIN,GPS_RX_PIN,GPS_BAUD_RATE);Serial.printf(OLED: SCK%d, SDA%d\n,OLED_SCK_PIN,OLED_SDA_PIN);Serial.printf(Output frequency: %d times per second\n,1000/PRINT_INTERVAL_MS);Serial.println(Listening for GPS NMEA sentences...);Serial.println(----------------------------------------);}voidloop(){// 持续读取并解析GPS数据while(GPS_UART.available()0){gps.encode(GPS_UART.read());}// 当接收到新的定位信息时输出staticunsignedlonglastPrintTime0;if(millis()-lastPrintTimePRINT_INTERVAL_MS){// 每秒5次lastPrintTimemillis();Serial.println();// 检查是否有有效的定位数据if(gps.location.isValid()){doublelatgps.location.lat();doublelnggps.location.lng();// 设置参考点首次有效定位if(!refSet){refLatlat;refLnglng;refSettrue;Serial.println(参考点已设置);}// 将经纬度转换为本地ENU坐标doubleeast,north;latLngToENU(lat,lng,refLat,refLng,east,north);// 计算速度和航向基于位移unsignedlongcurrentTimeMsmillis();if(lastTimeMs0refSet){doubledt(currentTimeMs-lastTimeMs)/1000.0;// 时间差秒if(dt0.01){// 避免除零doubledxeast-lastEast;doubledynorth-lastNorth;// 计算速度 (m/s - km/h)calcSpeedsqrt(dx*dxdy*dy)/dt*3.6;// 计算航向角度 (弧度转角度)calcHeadingatan2(dx,dy)*180.0/M_PI;if(calcHeading0)calcHeading360.0;// 转换为0-360度}}// 更新上一时刻数据lastEasteast;lastNorthnorth;lastTimeMscurrentTimeMs;// 输出定位状态Serial.println(定位状态: 有效);// 输出时间if(gps.time.isValid()){Serial.printf(当前时间 (UTC): %02d:%02d:%06.3f\n,gps.time.hour(),gps.time.minute(),gps.time.second()gps.time.centisecond()/100.0);}else{Serial.println(当前时间: 无效);}// 输出日期if(gps.date.isValid()){Serial.printf(日期: %04d-%02d-%02d\n,gps.date.year(),gps.date.month(),gps.date.day());}// 输出经纬度Serial.printf(纬度: %.6f°\n,lat);Serial.printf(经度: %.6f°\n,lng);// 输出本地ENU坐标相对于参考点Serial.printf(东向偏移: %.3f m\n,east);Serial.printf(北向偏移: %.3f m\n,north);// 计算到参考点的距离doubledistancesqrt(east*eastnorth*north);Serial.printf(距参考点距离: %.3f m\n,distance);// 更新OLED显示updateOLED(lat,lng,distance);// 输出计算得到的速度和航向基于本地墨卡托投影Serial.printf(对地航速 (计算): %.6f km/h\n,calcSpeed);Serial.printf(航向角度 (计算): %.6f°\n,calcHeading);// 输出原始GPS数据对比Serial.printf(原始航速: %.6f km/h\n,gps.speed.kmph());Serial.printf(原始航向: %.6f°\n,gps.course.deg());// 输出卫星数量Serial.printf(卫星数量: %d\n,gps.satellites.value());// 输出HDOP (水平精度因子)if(gps.hdop.isValid()){Serial.printf(HDOP: %.2f\n,gps.hdop.hdop());}// 输出海拔高度if(gps.altitude.isValid()){Serial.printf(海拔高度: %.2f m\n,gps.altitude.meters());}}else{Serial.println(定位状态: 无效 (请移至开阔地带));Serial.println(当前时间: --:--:--.---);Serial.println(纬度: ---.------°);Serial.println(经度: ---.------°);Serial.println(对地航速: ---.------ km/h);Serial.println(航向角度: ---.------°);// 显示接收到的字符数Serial.printf(已接收字符数: %lu\n,gps.charsProcessed());// 更新OLED显示无有效定位u8g2.clearBuffer();u8g2.setFont(u8g2_font_ncenB08_tr);u8g2.drawStr(0,20,No GPS Signal);u8g2.drawStr(0,40,Move to open area);u8g2.sendBuffer();}Serial.println();Serial.println();}// 检查是否有解析错误if(gps.charsProcessed()10){Serial.println(等待GPS数据...);delay(500);}}// 将经纬度转换为本地ENU坐标东-北-天坐标系voidlatLngToENU(doublelat,doublelng,doublerefLat,doublerefLng,doubleeast,doublenorth){// 地球半径米constdoubleR6378137.0;// 将角度转换为弧度doublelatRadlat*M_PI/180.0;doublelngRadlng*M_PI/180.0;doublerefLatRadrefLat*M_PI/180.0;doublerefLngRadrefLng*M_PI/180.0;// 计算cos(lat)用于缩放doublecosLatcos(refLatRad);// 计算ENU坐标eastR*(lngRad-refLngRad)*cosLat;northR*(latRad-refLatRad);}// 更新OLED显示voidupdateOLED(doublelat,doublelng,doubledistance){charbuf[32];u8g2.clearBuffer();u8g2.setFont(u8g2_font_ncenB08_tr);// 显示纬度sprintf(buf,Lat: %.6f,lat);u8g2.drawStr(0,12,buf);// 显示经度sprintf(buf,Lng: %.6f,lng);u8g2.drawStr(0,28,buf);// 显示距离参考点距离sprintf(buf,Dist: %.2fm,distance);u8g2.drawStr(0,44,buf);// 显示定位状态u8g2.drawStr(0,60,GPS: OK);u8g2.sendBuffer();}3.1 依赖库配置在platformio.ini中添加U8g2库lib_deps mikalhart/TinyGPSPlus^1.1.0 olikraus/U8g2^2.35.143.2 OLED初始化与配置#includeU8g2lib.h// SSD1306 OLED配置#defineOLED_SCK_PIN12// SCL#defineOLED_SDA_PIN13// SDA// U8g2对象I2C接口SSD1306 128x64U8G2_SSD1306_128X64_NONAME_F_HW_I2Cu8g2(U8G2_R0,/* reset*/U8X8_PIN_NONE,/* clock*/OLED_SCK_PIN,/* data*/OLED_SDA_PIN);voidsetup(){// 初始化OLED屏幕u8g2.begin();u8g2.clearBuffer();u8g2.setFont(u8g2_font_ncenB08_tr);u8g2.drawStr(0,20,GPS Initializing...);u8g2.sendBuffer();// ... GPS串口初始化 ...}代码解析**U8G2_SSD1306_128X64_NONAME_F_HW_I2C使用硬件I2C驱动128x64分辨率的SSD1306屏幕**u8g2_font_ncenB08_tr选择8像素高度的字体适合在小屏幕上显示清晰**drawStr(x, y, text)在指定坐标绘制字符串OLED坐标系原点在左上角3.3 距离参考点距离计算// 将经纬度转换为本地ENU坐标东-北-天坐标系voidlatLngToENU(doublelat,doublelng,doublerefLat,doublerefLng,doubleeast,doublenorth){constdoubleR6378137.0;// 地球半径米doublelatRadlat*M_PI/180.0;doublelngRadlng*M_PI/180.0;doublerefLatRadrefLat*M_PI/180.0;doublerefLngRadrefLng*M_PI/180.0;doublecosLatcos(refLatRad);eastR*(lngRad-refLngRad)*cosLat;northR*(latRad-refLatRad);}// 在loop中计算距离doubleeast,north;latLngToENU(lat,lng,refLat,refLng,east,north);doubledistancesqrt(east*eastnorth*north);原理说明参考点首次有效定位时的位置作为本地坐标系原点ENU坐标系East-North-Up东-北-天局部平面坐标系距离公式distance √(east² north²)3.4 OLED显示更新函数voidupdateOLED(doublelat,doublelng,doubledistance){charbuf[32];u8g2.clearBuffer();u8g2.setFont(u8g2_font_ncenB08_tr);// 显示纬度sprintf(buf,Lat: %.6f,lat);u8g2.drawStr(0,12,buf);// 显示经度sprintf(buf,Lng: %.6f,lng);u8g2.drawStr(0,28,buf);// 显示距离参考点距离sprintf(buf,Dist: %.2fm,distance);u8g2.drawStr(0,44,buf);// 显示定位状态u8g2.drawStr(0,60,GPS: OK);u8g2.sendBuffer();}显示布局128x64像素┌────────────────────────────┐ │ Lat: 38.869190 │ ← y12 │ Lng: 121.532056 │ ← y28 │ Dist: 9.71m │ ← y44 │ GPS: OK │ ← y60 └────────────────────────────┘3.5 信号状态处理if(gps.location.isValid()){// 有有效定位 → 显示详细数据updateOLED(lat,lng,distance);}else{// 无有效定位 → 显示提示信息u8g2.clearBuffer();u8g2.setFont(u8g2_font_ncenB08_tr);u8g2.drawStr(0,20,No GPS Signal);u8g2.drawStr(0,40,Move to open area);u8g2.sendBuffer();}四、实战日志分析下面是从串口监视器中截取的实际运行数据让我们分析其中的关键信息4.1 初始定位阶段 定位状态: 有效 纬度: 38.869190° 经度: 121.532056° 东向偏移: -9.086 m 北向偏移: -3.432 m 距参考点距离: 9.713 m 卫星数量: 0 HDOP: 25.50 分析要点✅ 虽然显示卫星数量: 0但竟然有定位结果 → 这说明模块实际上还处于冷启动阶段模块可能使用了上次的星历数据⚠️ HDOP25.50非常高正常应小于2.0以下才是高精度定位 参考点已设置后续数据都以此为原点4.2 定位稳定阶段 定位状态: 有效 纬度: 38.869191° 经度: 121.531948° 东向偏移: -18.461 m 北向偏移: -3.303 m 距参考点距离: 18.754 m 卫星数量: 4 HDOP: 4.00 分析要点✅ 卫星数量增加到4颗HDOP降低到4.00 位置在距离参考点约18米处说明设备在移动 移动方向向西东向偏移-18m表示向西移动4.3 高精度定位阶段 定位状态: 有效 纬度: 38.869194° 经度: 121.531933° 东向偏移: -19.747 m 北向偏移: -2.969 m 距参考点距离: 19.968 m 卫星数量: 4 HDOP: 4.00 海拔高度: 104.10 m 分析要点✅ HDOP4.00定位精度尚可 距离参考点距离稳定在20米左右 经纬度小数点后第6位开始稳定4.4 信号改善阶段 定位状态: 有效 纬度: 38.869193° 经度: 121.531955° 东向偏移: -17.912 m 北向偏移: -3.080 m 距参考点距离: 18.175 m 卫星数量: 5 HDOP: 2.30 分析要点✅ 卫星数量增加到5颗✅ HDOP降低到2.30定位精度显著提升 这是一组比较理想的数据五、OLED屏幕实际显示效果有信号时显示Lat: 38.869190 Lng: 121.532056 Dist: 9.71m GPS: OK无信号时显示No GPS Signal Move to open area六、技术要点总结6.1 U8g2库的使用技巧缓冲区机制clearBuffer()→ 绘制 →sendBuffer()避免屏幕闪烁字体选择ncenB08_tr是8像素高的字体适合128x64屏幕坐标系统左上角为原点(0,0)向右向下递增硬件I2C比软件I2C更稳定速度更快6.2 GPS数据精度分析指标指标含义推荐值卫星数量用于定位的卫星颗数≥ 4颗HDOP水平精度因子 2.0 (优秀)海拔高度海拔高度数据-6.3 距离参考点距离的应用场景步行导航实时显示距离目标点的距离车辆追踪记录车辆驶离起点的距离位置记录记录设备移动轨迹运动测距简单的运动距离计算七、完整代码完整代码已在项目中实现核心逻辑包括GPS串口数据读取与解析本地ENU坐标转换距离参考点距离计算SSD1306 OLED显示更新八、下一步计划在接下来的博客中我们将继续扩展功能 **计划三添加LED状态指示与按键交互 **计划四SD卡数据记录与轨迹回放 **计划五Web服务器远程监控 **计划六MQTT数据上传到云平台参考资源TinyGPSPlus库文档U8g2库文档ATGM332D模块手册总结通过集成SSD1306 OLED显示屏我们将原本只能在串口监视器中看到的数据以更直观的方式展示出来。这为后续的便携式GPS追踪器、导航设备等项目打下了良好的基础。