ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

树莓派GPS定位数据NB-IoT回传:串口接线、AT指令与坐标转换

树莓派GPS定位数据NB-IoT回传:串口接线、AT指令与坐标转换 简介面向毕业设计、物联网课程设计或嵌入式开发学习者本资源演示了树莓派结合NBIoT模块采集GPS数据并上传的完整项目。项目聚焦NBIoT低功耗广覆盖特性与GPS定位精度涵盖设备连接、软件配置、数据解析、云端上传等工程环节适合作为实战参考或二次开发蓝本。压缩包共15个文件以C语言源码.c/.h为核心配套Makefile构建脚本、编译生成的.o/.out文件及测试程序整体约20KB结构精简便于快速阅读。目前已有54人学习下载。通过该资源可掌握树莓派串口通信、GPS报文解析、NBIoT模块指令交互等关键技能涉及串口驱动、NMEA协议解析、AT指令控制及Makefile工程管理帮助读者理清物联网终端开发流程相关方案可延伸至物流跟踪、智慧停车、资产监控等场景具有较强的实战参考价值。1. 树莓派毕业设计NB-IoT 回传 GPS 数据的整体链路一个常见的毕业设计场景是把一辆小车或一个资产定位标签放到校园里希望每半分钟在网页上看到一个坐标点。用 WiFi 方案很容易但设备一旦移动到楼宇角落或地下停车场WiFi 就断了用 4G 模块成本高、功耗也高毕设答辩时很难把设备功耗说得清楚。NB-IoT 的价值就在这时体现出来窄带、低功耗、深覆盖非常符合“少量数据、低频上报”的定位场景。标题里三个关键词组合起来实际要做的事情很简单树莓派负责系统协调GPS 模块输出定位数据NB-IoT 模组作为网络通道把数据送回服务器。接下来按一条能直接跑起来的链路展开树莓派 4B 的 GPIO UART 接一个 GPS 模块NB-IoT 模组通过 USB-TTL 接到树莓派 USB 口用 Python 读取 NMEA 报文并解析经纬度再用 AT 指令把坐标通过 NB-IoT 网络发出。文章侧重的四个点是串口设备怎么固定、GPS 坐标怎么转成地图可用的十进制格式、AT 指令怎么和模组配合以及最后如何验证整条链路确实在正确工作。2. 树莓派接入 GPS 与 NB-IoT串口接线、UART 开启和 AT 验证2.1 两种常见硬件拓扑NB-IoT 走 USBGPS 走 GPIO UART树莓派 40Pin 引脚图里真正可直接用的 UART 只有一组对应 BCM14 的 TXD、BCM15 的 RXD在排针上是第 8 和第 10 脚另外需要一个 GND。问题在于GPS 和 NB-IoT 模组默认都是串口设备两个抢一路 UART 是毕设翻车率最高的地方。我一般会把 NB-IoT 模组放到 USB-TTL 转接线上让树莓派把模组当成/dev/ttyUSB0GPS 模块则直接接 GPIO UART这样两边互不干扰也方便在 GPS 串口上直接看 NMEA 原始数据。模块接口形式树莓派连接典型波特率注意事项GPS 模块NEO-6M / ATGM336HTTL UARTGPIO 14/15TXD/RXD9600串口电平必须 3.3V 兼容NB-IoT 模组SIM7020 / SIM7000USB-TTL 转串口USB A 口115200AT 命令交互时支持回显NB-IoT 模组自带 GNSS同一条 UARTGPIO 14/15模块默认同时输出 AT 和 NMEA较少接外部 GPS如果只想少接一根 USB 线可以反过来把 NB-IoT 模组接 GPIO UARTGPS 接 USB-TTL但调试布局、设备名识别都会麻烦一些。另一种更省事的方案是直接用带 GNSS 功能的 NB-IoT 模组比如 SIM7000 系列GPS 和 NB 功能共用同一路串口树莓派既可以用 AT 指令控制模块也能在串口里读到 NMEA 报文毕设展示时硬件体积会小很多。接线时最需要记住的是电压关系。树莓派 GPIO 是 3.3V 电平模块的 UART 串口如果是 5V TTL直接接会损坏引脚。市售 GPS 模块多数板载电平转换芯片VCC 接 Pin2 的 5V、串口脚依然输出 3.3V 逻辑可以安全接入。但如果买的是工业级 RS232 电平模块必须外接 MAX3232 转换不能直接进 GPIO。2.2 在树莓派上打开 UART并把串口设备固定下来树莓派系统的默认配置会占用串口作为登录终端导致程序读不到 GPS 数据。打开sudo raspi-config进入 Interface Options - Serial Port第一项 “login shell over serial” 选 No第二项 “serial port hardware enabled” 选 Yes。为了把性能更好的 PL011 UART 从蓝牙释放出来我习惯在/boot/config.txt里追加两行sudo bash -c cat /boot/config.txt EOF enable_uart1 dtoverlaydisable-bt EOF sudo reboot追加了dtoverlaydisable-bt之后蓝牙会被禁用GPIO UART 对应的设备节点变成/dev/ttyAMA0。如果不想关蓝牙也可以用dtoverlayminiuart-bt但此时 GPIO 串口会变成 mini-uart波特率会随核心频率变化。GPS 本身只有 9600 波特稳定性要求不算高但输出日志量大时仍有丢字节风险因此毕设项目里建议直接关蓝牙。重启后确认节点ls -l /dev/serial*正常能看到/dev/serial0 - ttyAMA0的软链接。然后把 GPS 模块的 TX 接到树莓派 Pin10GND 接 GND模块通电后执行下面命令stty -F /dev/ttyAMA0 9600 raw -echo timeout 8 cat /dev/ttyAMA0如果天线已经放在能收到卫星的地方几十秒内会出现一串$GNRMC、$GNGGA开头的文本。若只有乱码先检查波特率是否为 9600如果没有数据检查模块的 TX 是否接到树莓派的 RXD注意模块丝印 TX 表示模块发送脚应该接树莓派接收脚不要按模块上 TX/RX 的图标直觉去对接。NB-IoT 模组通过 USB-TTL 接到树莓派之后用dmesg | grep tty查看设备名。市面上常见 CH340/CP2102 转换芯片会映射为/dev/ttyUSB0部分新款驱动会使用/dev/ttyCH341USB0。如果设备名每次重启都在变建议用 udev 规则按串口芯片或 USB 物理位置固定设备名否则后面的 Python 服务很容易一开机就找错串口。2.3 用 AT 指令先确认 NB-IoT 能注册到网络串口通了之后先用一个串口工具和 NB-IoT 模组对话。树莓派上可以直接用minicomsudo minicom -D /dev/ttyUSB0 -b 115200输入AT并回车能看到OK。这表示模组本身存活。接下来按这个顺序检查网络状态。AT 指令期望返回含义ATI厂商和固件信息模组身份确认ATCPIN?CPIN: READY物联网卡已经识别ATCSQCSQ: 20,0信号强度99 表示无信号ATCEREG?CEREG: 0,1或0,5已注册上 NB 网络ATCGNAPN运营商默认 APN查询卡内预置接入点ATCEREG返回的第二个数字是关键1表示已注册5表示已注册且漫游3表示被拒绝4表示未知。毕设里最常遇见的3是物联网卡没有开通 NB 套餐或者 APN 配置错误。如果 APN 需要手动配置通常用ATCGDCONT1,IP,cmnbiot这类指令设置具体 APN 名由运营商提供。这里不要一上来就接树莓派 GPIO先用独立供电和 minicom 把网络注册问题解决再谈上层代码。3. GPS 数据解析NMEA 协议、经纬度换算与坐标偏转3.1 从串口读哪一条语句GPRMC 或 GGAGPS 模块上电后会持续输出一串 NMEA 0183 格式的文本行每行以$开头以*和两个十六进制字符结束。字段之间用逗号分隔。要拿经纬度优先读GPRMC它的状态位可以明确告诉你当前定位是否有效。一条典型的 GPRMC 如下$GPRMC,083559.00,A,2345.6789,N,11312.3456,E,0.058,,160722,,,D*77字段顺序从$GPRMC之后算起UTC 时间、定位状态 A/V、纬度、纬度方向、经度、经度方向、速度、航向、日期、磁偏角、校验位。A表示有效定位V表示当前没定位成功。判断时不要只检查字符串里有没有经纬度数字还要检查状态位否则会把上一次定位的残留值当作有效数据上传。3.2 用 Python 把度分格式转换成十进制度数GPS 输出的纬度和经度不是直接的小数点度数而是“度分”格式。例如2345.6789表示北纬 23 度 45.6789 分换算成十进制度为23 45.6789 / 60 23.761315。很多同学把2345.6789当作23.456789直接拿去画地图画出来的位置会偏出一公里以上。下面这个脚本可以直接在树莓派上运行从 GPIO UART 读取有效的 RMC 定位并返回经纬度。# gps_reader.py import serial import datetime import math SERIAL_PORT /dev/ttyAMA0 BAUD 9600 def nmea_checksum(line): 校验 NMEA 行格式如 $GPRMC,.....*77 if not line.startswith($): return False body, _, checksum line.rstrip().partition(*) calc 0 for ch in body[1:].encode(ascii, errorsignore): calc ^ ch return f{calc:02X} checksum.upper() def ddm_to_decimal(ddm, direction): 把度分转十进制度S/W 方向取负值 if not ddm: return None deg int(float(ddm) // 100) minute float(ddm) - deg * 100 result deg minute / 60.0 if direction in (S, W): result -result return round(result, 6) def read_fix(ser): while True: raw ser.readline().decode(errorsignore).strip() if not nmea_checksum(raw): continue if raw.startswith($GPRMC): parts raw.split(,) if len(parts) 10 or parts[2] ! A: continue lat ddm_to_decimal(parts[3], parts[4]) lon ddm_to_decimal(parts[5], parts[6]) if lat is None or lon is None: continue return lat, lon, parts[9] if __name__ __main__: ser serial.Serial(SERIAL_PORT, BAUD, timeout3) lat, lon, date read_fix(ser) print(lat, lon, date)校验和函数把$和*之间的所有字符按字节异或转成十六进制后与报文尾部的值比对。这一步不能省因为 NB-IoT 模组和 GPS 线缆可能引入干扰错位的一行解析出的坐标会直接污染接收端的地图数据。时间字段在 GPRMC 里是ddmmyy最好结合定位日期使用后面的周数翻转问题会用到它。3.3 定位误差、周数翻转处理和高德坐标转换使用单点定位的消费级 GPS 模块精度通常在 2 米到 5 米之间静止状态下还会出现无明显方向的漂移。毕设阶段不需要上 RTK常见做法是连续取最近 5 个有效定位点剔除偏差过大的点后取平均。实现上很简单把每次定位的经纬度放入一个列表列表满 5 个点之后用与平均值的距离筛选异常点。还有一个需要在代码里规避的坑是 GPS 周数翻转。GPS 周计数是 10 位2019 年 4 月 6 日清零后再次翻转较老的 GPS 模块如果固件没有更新解析出的日期会变成 2009 或 2099 年。这种情况在二手板卡和库存模块里非常常见。最简单的补丁是校验日期try: fix_date datetime.datetime.strptime(date, %d%m%y) except ValueError: continue if not (2020 fix_date.year datetime.datetime.utcnow().year 1): continue再有就是国内地图显示问题。GPS 返回的是 WGS-84 坐标系而高德地图使用的是 GCJ-02 火星坐标直接把23.761315画到高德地图上会偏移约 100 米。需要的转换算法是公开的标准偏移算法Python 代码并不复杂下面给一个可以照用的版本。import math def wgs84_to_gcj02(wgs_lat, wgs_lon): a 6378245.0 ee 0.00669342162296594323 def transform(lat, lon): result [0.0, 0.0] d 300.0 1.0 * lat 2.0 * lon 0.1 * lat * lat 0.1 * lat * lon 0.1 * math.sqrt(abs(lat)) result[0] 20.0 * math.sin(6.0 * d * math.pi) 20.0 * math.sin(2.0 * d * math.pi) result[0] * 2.0 / 3.0 result[0] 20.0 * math.sin(lon * math.pi) 40.0 * math.sin(lon / 3.0 * math.pi) result[0] * 2.0 / 3.0 result[0] 160.0 * math.sin(lon / 12.0 * math.pi) 320.0 * math.sin(lon / 30.0 * math.pi) result[0] * 2.0 / 3.0 d 300.0 1.0 * lat 2.0 * lon 0.1 * lat * lat 0.1 * lat * lon 0.1 * math.sqrt(abs(lat)) result[1] 20.0 * math.sin(6.0 * lat * math.pi) 20.0 * math.sin(2.0 * lat * math.pi) result[1] * 2.0 / 3.0 result[1] 20.0 * math.sin(lat * math.pi) 40.0 * math.sin(lat / 3.0 * math.pi) result[1] * 2.0 / 3.0 result[1] 150.0 * math.sin(lat / 12.0 * math.pi) 300.0 * math.sin(lat / 30.0 * math.pi) result[1] * 2.0 / 3.0 return result if wgs_lon 72.004 or wgs_lon 137.8347 or wgs_lat 0.8293 or wgs_lat 55.8271: return wgs_lat, wgs_lon dlat, dlon transform(wgs_lon - 105.0, wgs_lat - 35.0) radlat wgs_lat / 180.0 * math.pi magic math.sin(radlat) magic 1 - ee * magic * magic sqrtmagic math.sqrt(magic) dlat (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * math.pi) dlon (dlon * 180.0) / (a / sqrtmagic * math.cos(radlat) * math.pi) return round(wgs_lat dlat, 6), round(wgs_lon dlon, 6)代码里先判断经纬度是否在中国大陆范围内避免把海外坐标误转换。高德开放平台文档里推荐的做法也是先判断区域再转坐标这个边界判断不是可选项。4. Python 脚本把 GPS 数据通过 NB-IoT 发给服务器4.1 上报协议选型毕设直接用 TCP/UDP 透传即可NB-IoT 的核心特点是频繁小包和偶发性上行不适合一直维持一个长连接。因为模组附着网络会产生信令开销持续在线不仅耗电还可能被运营商网络侧判定为异常。毕设阶段不建议一开始就去写 MQTT那需要处理ATCMQTTSTART、ATCMQTTCONNECT、ATCMQTTTOPIC等一大串 AT 状态机调试成本高。简化做法是让树莓派把坐标拼成一段 JSON通过 TCP 或 UDP 直接发给服务器。TCP 有 ACK 回执适合本地自建服务端验证UDP 更贴合 NB-IoT 场景但容易丢包毕设演示时不好解释。下面以 TCP 为例展示 AT 交互流程。若服务器地址是demo.lab.edu.cn、端口5050模组注册网络后执行ATCGACT1,1 OK ATCIPSTARTTCP,demo.lab.edu.cn,5050 CONNECT OK ATCIPSEND {lat:23.761315,lon:113.206093,utc:2025-01-01T08:00:00Z} \x1a SEND OKCIPSEND之后的 JSON 末尾要发送一个十六进制1A也就是 CtrlZ表示报文结束。模组收到后才把数据发出去并返回SEND OK。不同厂商模组的 AT 指令前缀有差异有些用ATCIPSTART有些用ATNSOST但交互范式基本一致。下面表格列出了通用操作实际使用时按模组文档对照即可。操作指令示例完成标识激活 PDP 上下文ATCGACT1,1OK建立 TCP 连接ATCIPSTARTTCP,host,portCONNECT OK开始发送数据ATCIPSEND结束并发送数据后跟\x1ASEND OK断开连接ATCIPCLOSEOK4.2 用 Python 把 GPS 读取和 NB-IoT 发送串起来现在把串口读取和 AT 操作合并成一个上传脚本。核心思路是GPS 串口和 NB-IoT 串口各自独立打开主循环先卡在 GPS 读取上拿到有效定位后再打开 NB-IoT 串口发送发送完毕立即断开。用send_at函数等待模组返回指定标识符避免sleep弹出微妙的时序问题。# nb_uploader.py import serial import json import time import datetime from gps_reader import read_fix, wgs84_to_gcj02 GPS_PORT /dev/ttyAMA0 NB_PORT /dev/ttyUSB0 SERVER_HOST demo.lab.edu.cn SERVER_PORT 5050 def send_at(nb, cmd, waitOK, timeout10): nb.reset_input_buffer() nb.write((cmd \r\n).encode()) reply b end time.time() timeout while time.time() end: chunk nb.read(nb.in_waiting or 1) if chunk: reply chunk if wait.encode() in reply: break return reply.decode(errorsignore) def upload_payload(payload): nb serial.Serial(NB_PORT, 115200, timeout2) try: send_at(nb, ATCGACT1,1) send_at(nb, fATCIPSTARTTCP,{SERVER_HOST},{SERVER_PORT}, waitCONNECT OK) send_at(nb, ATCIPSEND, wait) data json.dumps(payload) \x1a nb.write(data.encode()) time.sleep(2) return SEND OK in send_at(nb, , waitSEND OK, timeout5) finally: nb.close() if __name__ __main__: gps serial.Serial(GPS_PORT, 9600, timeout3) while True: try: lat, lon, date read_fix(gps) except serial.SerialTimeoutException: continue display_lat, display_lon wgs84_to_gcj02(lat, lon) payload { device_id: raspi_gps_01, lat: display_lat, lon: display_lon, wgs_lat: lat, wgs_lon: lon, utc: datetime.datetime.utcnow().isoformat() Z, signal: NB-IoT } ok False for attempt in range(3): ok upload_payload(payload) if ok: break time.sleep(3) print(time.time(), send ok if ok else send failed) time.sleep(15)send_at函数每次发送命令前清空输入缓冲区避免上一条命令的旧回显干扰判断。建立连接的等待标识符是CONNECT OK这是最关键的调整点如果把所有 AT 都统一等待OKCIPSEND 之后无法判断服务器是否真的收到报文。主循环里的三次重发是针对 NB 网络偶发失败的实际做法超过三次后丢弃本轮数据等待下一个周期而不是死循环重试把模组打挂。4.3 树莓派与 NB-IoT 模组之间的日志怎么看NB-IoT 模组在 AT 模式下回显很慢尤其是第一次附着网络时ATCGACT可能长时间不返回OK。遇到这种情况先在 minicom 里手动执行一遍建立连接的指令观察 CH340 LED 是否闪烁以及服务器端能否收到包。一个非常实用的排查手段是给 NB-IoT 模组接一个 TTL 转 USB 调试线同时接树莓派 UART 和电脑串口工具物理层有没有通立刻就能判断。运行上传脚本后如果 GPS 串口打印正常但 NB-IoT 串口没有SEND OK多半是服务器 IP 写错、端口被封或者 NB 模组尚未成功附着出网而不是 Python 代码问题。5. 上电后的故障定位信号评估、看门狗自启与日志验证5.1 先评估模块信号再启动服务树莓派装好脚本后不要直接后台运行而是在前台跑一次先把 NB-IoT 模组的信号质量打出来。用ATCSQ拿到数值20 以上基本可稳定发送15 到 20 需要把天线放到窗边低于 10 则先不要调试上层。信号检查建议写成一个小检查函数放在send_at之后签名合法时记录环境信息。echo -e ATCSQ\r\n /dev/ttyUSB0注意不是所有 NeoTec 模组都自动回显所以脚本里已经把ATCSQ封装在send_at中这一步只是预处理里的人工观测手段。GPS 侧则看read_fix是否在 3 秒内返回A状态。树莓派放在室内且没有外接 GPS 天线时冷启动定位时间可能超过 3 分钟这是正常的不要误判为程序死循环。5.2 用 systemd 把上传脚本变成常驻服务树莓派重启后不会自动运行 Python 脚本毕设演示时当然可以手动敲命令但更稳妥的做法是注册一个 systemd 服务并设置失败自动重启。# /etc/systemd/system/gps-uploader.service [Unit] DescriptionGPS data uploader via NB-IoT Afternetwork-online.target [Service] ExecStart/usr/bin/python3 /home/pi/nb_gps/gps_uploader.py WorkingDirectory/home/pi/nb_gps Restarton-failure RestartSec15 Userpi [Install] WantedBymulti-user.target配置好后执行sudo systemctl enable --now gps-uploader。Restarton-failure保证脚本因串口异常退出后能自动拉起如果 GPS 模块断电脚本会卡在串口读取而不会崩掉。这种守护方式比在 Shell 里套while true干净得多树莓派 4B 上是标准做法。5.3 用三条日志验证整条链路确实通了毕设答辩时拿一段日志比拿一张截图更有说服力。上电后观察三行内容第一行是 GPS 定位状态和 WGS-84 坐标第二行是 GCJ-02 转换后的坐标第三行是SEND OK。这三行能同时出现说明串口读取、坐标换算、NB-IoT 网络附着、TCP 发送四段链路全部正常。日志格式可以简单到只打印时间戳和状态。继续在/home/pi/nb_gps/gps_uploader.py里把print调整成统一格式例如[GPS] lat23.761315 lon113.206093然后用journalctl -u gps-uploader -f跟踪输出。这个命令本身是可以直接在树莓派上执行的也就是验证阶段的最后一步如果考试时只允许用一条命令证明项目完成记录下这条命令的运行结果就够了。本文还有配套的精品资源点击获取
返回列表