news 2026/9/12 21:00:11

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
树莓派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 走 USB,GPS 走 GPIO UART

树莓派 40Pin 引脚图里真正可直接用的 UART 只有一组,对应 BCM14 的 TXD、BCM15 的 RXD,在排针上是第 8 和第 10 脚,另外需要一个 GND。问题在于,GPS 和 NB-IoT 模组默认都是串口设备,两个抢一路 UART 是毕设翻车率最高的地方。我一般会把 NB-IoT 模组放到 USB-TTL 转接线上,让树莓派把模组当成/dev/ttyUSB0;GPS 模块则直接接 GPIO UART,这样两边互不干扰,也方便在 GPS 串口上直接看 NMEA 原始数据。

模块接口形式树莓派连接典型波特率注意事项
GPS 模块(NEO-6M / ATGM336H)TTL UARTGPIO 14/15(TXD/RXD)9600串口电平必须 3.3V 兼容
NB-IoT 模组(SIM7020 / SIM7000)USB-TTL 转串口USB A 口115200AT 命令交互时支持回显
NB-IoT 模组自带 GNSS同一条 UARTGPIO 14/15模块默认同时输出 AT 和 NMEA,较少接外部 GPS

如果只想少接一根 USB 线,可以反过来把 NB-IoT 模组接 GPIO UART,GPS 接 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_uart=1 dtoverlay=disable-bt EOF' sudo reboot

追加了dtoverlay=disable-bt之后,蓝牙会被禁用,GPIO UART 对应的设备节点变成/dev/ttyAMA0。如果不想关蓝牙,也可以用dtoverlay=miniuart-bt,但此时 GPIO 串口会变成 mini-uart,波特率会随核心频率变化。GPS 本身只有 9600 波特,稳定性要求不算高,但输出日志量大时仍有丢字节风险,因此毕设项目里建议直接关蓝牙。

重启后确认节点:

ls -l /dev/serial*

正常能看到/dev/serial0 -> ttyAMA0的软链接。然后把 GPS 模块的 TX 接到树莓派 Pin10,GND 接 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 模组对话。树莓派上可以直接用minicom

sudo minicom -D /dev/ttyUSB0 -b 115200

输入AT并回车能看到OK。这表示模组本身存活。接下来按这个顺序检查网络状态。

AT 指令期望返回含义
ATI厂商和固件信息模组身份确认
AT+CPIN?+CPIN: READY物联网卡已经识别
AT+CSQ+CSQ: 20,0信号强度,99 表示无信号
AT+CEREG?+CEREG: 0,10,5已注册上 NB 网络
AT+CGNAPN运营商默认 APN查询卡内预置接入点

AT+CEREG返回的第二个数字是关键,1表示已注册,5表示已注册且漫游,3表示被拒绝,4表示未知。毕设里最常遇见的3是物联网卡没有开通 NB 套餐,或者 APN 配置错误。如果 APN 需要手动配置,通常用AT+CGDCONT=1,"IP","cmnbiot"这类指令设置,具体 APN 名由运营商提供。这里不要一上来就接树莓派 GPIO,先用独立供电和 minicom 把网络注册问题解决,再谈上层代码。

3. GPS 数据解析:NMEA 协议、经纬度换算与坐标偏转

3.1 从串口读哪一条语句:GPRMC 或 GGA

GPS 模块上电后会持续输出一串 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", errors="ignore"): 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(errors="ignore").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, timeout=3) 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,那需要处理AT+CMQTTSTARTAT+CMQTTCONNECTAT+CMQTTTOPIC等一大串 AT 状态机,调试成本高。简化做法是让树莓派把坐标拼成一段 JSON,通过 TCP 或 UDP 直接发给服务器。TCP 有 ACK 回执,适合本地自建服务端验证;UDP 更贴合 NB-IoT 场景,但容易丢包,毕设演示时不好解释。

下面以 TCP 为例展示 AT 交互流程。若服务器地址是demo.lab.edu.cn、端口5050,模组注册网络后执行:

AT+CGACT=1,1 OK AT+CIPSTART="TCP","demo.lab.edu.cn","5050" CONNECT OK AT+CIPSEND > {"lat":23.761315,"lon":113.206093,"utc":"2025-01-01T08:00:00Z"} \x1a SEND OK

CIPSEND之后的 JSON 末尾要发送一个十六进制1A,也就是 Ctrl+Z,表示报文结束。模组收到后才把数据发出去并返回SEND OK。不同厂商模组的 AT 指令前缀有差异,有些用AT+CIPSTART,有些用AT+NSOST,但交互范式基本一致。下面表格列出了通用操作,实际使用时按模组文档对照即可。

操作指令示例完成标识
激活 PDP 上下文AT+CGACT=1,1OK
建立 TCP 连接AT+CIPSTART="TCP","host","port"CONNECT OK
开始发送数据AT+CIPSEND>
结束并发送数据后跟\x1ASEND OK
断开连接AT+CIPCLOSEOK

4.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, wait="OK", timeout=10): 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(errors="ignore") def upload_payload(payload): nb = serial.Serial(NB_PORT, 115200, timeout=2) try: send_at(nb, "AT+CGACT=1,1") send_at(nb, f'AT+CIPSTART="TCP","{SERVER_HOST}","{SERVER_PORT}"', wait="CONNECT OK") send_at(nb, "AT+CIPSEND", wait=">") data = json.dumps(payload) + "\x1a" nb.write(data.encode()) time.sleep(2) return "SEND OK" in send_at(nb, "", wait="SEND OK", timeout=5) finally: nb.close() if __name__ == "__main__": gps = serial.Serial(GPS_PORT, 9600, timeout=3) 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 都统一等待OK,CIPSEND 之后无法判断服务器是否真的收到报文。主循环里的三次重发是针对 NB 网络偶发失败的实际做法,超过三次后丢弃本轮数据,等待下一个周期,而不是死循环重试把模组打挂。

4.3 树莓派与 NB-IoT 模组之间的日志怎么看

NB-IoT 模组在 AT 模式下回显很慢,尤其是第一次附着网络时,AT+CGACT可能长时间不返回OK。遇到这种情况,先在 minicom 里手动执行一遍建立连接的指令,观察 CH340 LED 是否闪烁,以及服务器端能否收到包。一个非常实用的排查手段是给 NB-IoT 模组接一个 TTL 转 USB 调试线,同时接树莓派 UART 和电脑串口工具,物理层有没有通立刻就能判断。运行上传脚本后,如果 GPS 串口打印正常但 NB-IoT 串口没有SEND OK,多半是服务器 IP 写错、端口被封,或者 NB 模组尚未成功附着出网,而不是 Python 代码问题。

5. 上电后的故障定位:信号评估、看门狗自启与日志验证

5.1 先评估模块信号再启动服务

树莓派装好脚本后不要直接后台运行,而是在前台跑一次,先把 NB-IoT 模组的信号质量打出来。用AT+CSQ拿到数值,20 以上基本可稳定发送,15 到 20 需要把天线放到窗边,低于 10 则先不要调试上层。信号检查建议写成一个小检查函数,放在send_at之后,签名合法时记录环境信息。

echo -e "AT+CSQ\r\n" > /dev/ttyUSB0

注意不是所有 NeoTec 模组都自动回显,所以脚本里已经把AT+CSQ封装在send_at中,这一步只是预处理里的人工观测手段。GPS 侧则看read_fix是否在 3 秒内返回A状态。树莓派放在室内且没有外接 GPS 天线时,冷启动定位时间可能超过 3 分钟,这是正常的,不要误判为程序死循环。

5.2 用 systemd 把上传脚本变成常驻服务

树莓派重启后不会自动运行 Python 脚本,毕设演示时当然可以手动敲命令,但更稳妥的做法是注册一个 systemd 服务,并设置失败自动重启。

# /etc/systemd/system/gps-uploader.service [Unit] Description=GPS data uploader via NB-IoT After=network-online.target [Service] ExecStart=/usr/bin/python3 /home/pi/nb_gps/gps_uploader.py WorkingDirectory=/home/pi/nb_gps Restart=on-failure RestartSec=15 User=pi [Install] WantedBy=multi-user.target

配置好后执行sudo systemctl enable --now gps-uploaderRestart=on-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] lat=23.761315 lon=113.206093,然后用journalctl -u gps-uploader -f跟踪输出。这个命令本身是可以直接在树莓派上执行的,也就是验证阶段的最后一步:如果考试时只允许用一条命令证明项目完成,记录下这条命令的运行结果就够了。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/12 20:59:35

无人机河道污染巡检 河道漂浮物检测数据集 河道环保识别数据集 yolo数据集第10791期

无人机河道污染巡检 河道漂浮物检测数据集项目详情任务类型目标检测样本总量2400张 类别索引识别类别名称0废弃物1漂浮物 数据集简介 烟火检测数据集&#xff0c;一共2430张图像&#xff0c;配套YOLO格式标签文件。数据集面向火情视觉监测场景&#xff0c;可识别画面中的废弃物…

作者头像 李华
网站建设 2026/9/12 20:58:38

缓存双写一致性问题

当我们更新数据库的时候&#xff0c;需要保持数据可的数据和缓存中的数据保持一致在业务背景的不同&#xff0c;所对应的就有两种解决方式1.必须保证强一致性的业务&#xff1a;解决方案&#xff1a;i&#xff1a;延迟双删&#xff0c;先去删除缓存&#xff0c;再去更新数据库&…

作者头像 李华
网站建设 2026/9/12 20:54:00

LoRaWAN 网络容量估算:一个 SX1302 网关到底能带多少设备

一个永远在变的数字 “你们的网关能带多少设备&#xff1f;” 这是每一个 LoRaWAN 方案沟通会上被问到的第一个问题&#xff0c;也是每一个采购经理在 Excel 表里最先填的格子。答案不在任何一份产品手册里&#xff0c;因为同一个网关在真实部署中可能服务 700 台设备 &#…

作者头像 李华
网站建设 2026/9/12 20:51:50

Same Sky技术:解决音频与电源接口标准化的革命性方案

1. Same Sky技术背景与行业痛点音频与电源线缆接口的标准化问题困扰行业数十年。我曾在2018年参与某跨国企业的会议室改造项目&#xff0c;光是处理不同厂商设备的接口兼容问题就耗费了整个团队37%的工期。这种混乱主要体现在三个方面&#xff1a;物理接口碎片化&#xff1a;光…

作者头像 李华