公司动态

树莓派4G HAT GNSS功能实战:从AT命令到Python物联网定位应用

📅 2026/8/2 12:50:24
树莓派4G HAT GNSS功能实战:从AT命令到Python物联网定位应用
1. 从零开始为什么你需要一个带GNSS的树莓派如果你手头有一块树莓派并且正在捣鼓物联网项目比如远程气象站、资产追踪器、自动驾驶小车或者只是想做一个能告诉你精确位置和时间戳的数据记录仪那么给树莓派加上全球导航卫星系统GNSS功能绝对能让你的项目上一个台阶。这不仅仅是“知道自己在哪”那么简单一个稳定可靠的GNSS模块能提供微秒级的时间同步、厘米级的定位精度配合RTK以及不受网络限制的全球可用性。最近集成4G LTE和GNSS功能的HATHardware Attached on Top扩展板变得非常流行。它把蜂窝通信和卫星定位两大核心功能集成在一块板子上通过树莓派的40针GPIO接口直接堆叠供电和通信都走I2C或UART省去了外接一堆模块和连线的麻烦。对于我这样的项目开发者来说这意味着更简洁的原型设计、更可靠的连接以及更快的产品化路径。我这次上手的是市面上比较常见的一款树莓派4G HAT它通常基于移远EC25或SIM7600这类模块。这类模块的强大之处在于它们不仅是4G Cat.1或Cat.4的调制解调器内部还集成了多模GNSS接收机支持GPS、GLONASS、BeiDou、Galileo可以通过AT命令进行完全控制。所以我们今天要聊的“开始使用GNSS功能”本质上就是学会如何通过树莓派上的Python去和这个集成在4G模块里的“小卫星接收机”对话让它告诉我们位置、时间、速度甚至卫星状态。2. 硬件连接与初始环境搭建在写第一行代码之前正确的硬件连接和系统配置是成功的一半。这一步如果搞错了后面所有的AT命令都会石沉大海。2.1 硬件组装与引脚确认首先确保你的4G HAT和树莓派型号匹配。大多数HAT设计时都考虑了树莓派3B/4B/5的40针GPIO布局。将HAT对准GPIO排针轻轻按压确保所有针脚都接触良好。很多HAT板子上会有跳线帽或DIP开关用于选择GNSS模块与树莓派通信的串口。这是第一个关键点。通常GNSS数据输出默认连接到树莓派的主串口/dev/ttyAMA0 或 /dev/serial0或一个额外的串口如 /dev/ttyUSB2。你必须查阅你所使用的具体HAT板的说明书。以我手头的这款基于SIM7600的HAT为例它的GNSS UART默认映射到了/dev/ttyUSB2因为4G模块本身是通过USB转串口与树莓派连接的其多个功能口被虚拟为不同的/dev/ttyUSBx设备。注意树莓派的主串口/dev/ttyAMA0默认可能被分配给蓝牙模块树莓派3及以后版本或控制台。如果你打算使用这个口需要在系统配置中禁用蓝牙或控制台串口功能这比较麻烦。因此使用HAT板提供的额外USB虚拟串口如/dev/ttyUSB2通常是更简单、更推荐的选择。连接天线是另一个容易出错的环节。你的4G HAT上至少会有两个天线接口一个标着“MAIN”或“4G”用于蜂窝网络另一个标着“GNSS”或“GPS”专门用于卫星定位。务必把GNSS有源天线接到正确的接口上并将天线放置在具有开阔天空视野的位置。室内、金属盒内或窗户边信号遮挡严重的地方可能很长时间都无法定位。2.2 系统配置与依赖安装给树莓派上电并确保其连接到了互联网可以通过Wi-Fi或者先正确配置4G HAT的蜂窝网络。我们首先通过SSH登录。第一步更新系统并安装必要工具。sudo apt update sudo apt upgrade -y sudo apt install python3-pip minicom -yminicom是一个经典的串口调试工具我们将用它来手动发送AT命令进行最初的硬件测试和功能验证。这是硬件调试的必备技能。第二步确认GNSS串口设备。插入4G HAT并上电后列出所有串口设备ls /dev/ttyUSB* ls /dev/ttyAMA*你应该能看到类似/dev/ttyUSB0,/dev/ttyUSB1,/dev/ttyUSB2的设备。哪个是GNSS口这里有个实用的方法使用dmesg命令查看内核日志的最后部分当你插入HAT时会有相应的USB设备识别记录。dmesg | grep tty输出可能会显示cp210x或pl2303等USB转串口芯片的信息并指明对应的ttyUSB编号。通常编号顺序是稳定的。最可靠的方法还是查阅板子手册或者用排除法先配置好4G拨号占用的口就不是GNSS口。第三步配置串口权限和参数。默认情况下普通用户无法直接访问串口设备。为了避免每次都使用sudo我们将当前用户加入dialout组。sudo usermod -a -G dialout $USER执行后需要注销并重新登录或者重启树莓派这个组权限变更才会生效。GNSS模块的典型通信参数是9600 8N1波特率96008位数据位无奇偶校验1位停止位。也有模块支持更高的波特率如115200以提高数据吞吐量但9600是广泛支持的默认值。3. 使用Minicom进行手动AT命令测试在编写自动化脚本之前用minicom进行手动测试是至关重要的。这能帮你确认硬件连接无误、串口号正确、模块能正常响应。启动minicom连接GNSS串口假设是/dev/ttyUSB2minicom -D /dev/ttyUSB2 -b 9600如果连接成功终端会清空并进入一个“哑终端”模式。此时你敲击键盘字符会直接发送到串口。第一步检查模块通信。在minicom终端里输入AT然后按回车。你应该立即看到模块返回OK这证明串口链路是通的模块处于AT命令模式。如果没反应检查1) 串口号是否正确2) 波特率是否正确3) 线缆和连接4) 模块是否已供电并启动。第二步查询GNSS模块的固件信息。ATCGNSPWR?此命令查询GNSS电源状态。返回CGNSPWR: 1表示已上电0表示关闭。ATCGNSINF这是一个非常重要的综合信息查询命令。如果GNSS未定位它可能返回CGNSINF: 0,,,,,,,,,,,,,,,,,,如果已定位则会返回一长串用逗号分隔的数据包含定位状态、UTC时间、纬度、经度、海拔高度、速度、航向、定位模式、卫星数等丰富信息。这是获取位置数据最常用的命令之一。第三步控制GNSS电源和输出频率。你可以手动开启或关闭GNSS功能以省电ATCGNSPWR1 // 开启GNSS电源 ATCGNSPWR0 // 关闭GNSS电源设置NMEA语句的输出频率例如每秒1次ATCGNSTST1 // 开始输出NMEA数据到串口 ATCGNSURC1 // 设置URC非请求结果码输出周期为1秒设置后模块会自动、周期性地向串口输出标准的NMEA-0183格式语句如$GPGGA,$GPRMC等。你会在minicom里看到滚动出现的文本数据。退出minicom按CtrlA然后按Z调出帮助菜单再按X选择退出。通过以上手动测试你已经验证了从硬件到基础命令的整个通路。接下来我们将用Python脚本让这一切自动化。4. 编写Python脚本与GNSS模块交互手动测试没问题后我们就可以用Python的pyserial库来编写一个稳定、可配置的GNSS数据读取程序。相比手动操作脚本化可以方便地进行数据解析、记录、上传或触发其他动作。4.1 安装Python串口库并建立基础连接首先安装pyserialpip3 install pyserial创建一个新的Python脚本比如gnss_reader.py。import serial import time import sys class GNSSController: def __init__(self, port/dev/ttyUSB2, baudrate9600, timeout1): 初始化GNSS控制器 :param port: 串口设备路径 :param baudrate: 波特率 :param timeout: 读超时时间秒 self.port port self.baudrate baudrate self.timeout timeout self.ser None def open_connection(self): 打开串口连接 try: self.ser serial.Serial( portself.port, baudrateself.baudrate, bytesizeserial.EIGHTBITS, parityserial.PARITY_NONE, stopbitsserial.STOPBITS_ONE, timeoutself.timeout ) if self.ser.is_open: print(f成功连接到 {self.port}) # 清空输入缓冲区避免残留数据 self.ser.reset_input_buffer() time.sleep(0.1) return True except serial.SerialException as e: print(f无法打开串口 {self.port}: {e}) print(请检查1. 设备路径是否正确 2. 用户是否有权限需在dialout组3. 设备是否被其他进程占用) return False def send_at_command(self, command, expected_responseOK, wait_time0.5): 发送AT命令并检查响应 :param command: AT命令字符串无需加回车 :param expected_response: 期望在响应中包含的字符串 :param wait_time: 发送命令后等待响应的基础时间秒 :return: (是否成功, 完整响应) if not self.ser or not self.ser.is_open: print(串口未打开) return False, # 确保命令以回车结尾 full_cmd command.strip() \r\n self.ser.write(full_cmd.encode(utf-8)) time.sleep(wait_time) # 等待模块处理 # 读取响应 response_bytes self.ser.read(self.ser.in_waiting or 1) response response_bytes.decode(utf-8, errorsignore) print(f发送: {command}) print(f接收: {response}) if expected_response in response: return True, response else: print(f未收到预期响应 {expected_response}) return False, response def close_connection(self): 关闭串口连接 if self.ser and self.ser.is_open: self.ser.close() print(串口连接已关闭) # 使用示例 if __name__ __main__: # 根据你的实际设备修改端口 gnss GNSSController(port/dev/ttyUSB2) if not gnss.open_connection(): sys.exit(1) # 测试AT命令 success, resp gnss.send_at_command(AT) if success: print(模块通信正常) # 查询GNSS状态 success, resp gnss.send_at_command(ATCGNSPWR?) # 解析resp例如判断是否是1 gnss.close_connection()这个类封装了串口连接、命令发送和响应读取的基础操作。send_at_command方法是一个通用框架你可以用它发送任何AT命令。4.2 实现GNSS功能控制与数据解析接下来我们在类中添加专门针对GNSS功能的方法。def gnss_power_on(self): 开启GNSS电源 return self.send_at_command(ATCGNSPWR1) def gnss_power_off(self): 关闭GNSS电源 return self.send_at_command(ATCGNSPWR0) def get_gnss_info(self): 获取GNSS综合信息CGNSINF。 这是最常用的获取位置、时间、速度等信息的方法。 :return: 解析后的字典如果未定位则返回None success, resp self.send_at_command(ATCGNSINF, wait_time1.0) # 等待稍长时间 if not success: return None # 解析响应行例如: CGNSINF: 1,1,20241010123045.000,22.123456,113.987654,55.5,0.89,120.5,2,,,,,12,1,0,0 for line in resp.split(\n): if line.startswith(CGNSINF:): data_str line[len(CGNSINF:):].strip() fields data_str.split(,) # 根据模块手册定义字段以下是一个常见顺序的示例 # 字段索引可能因模块固件版本而异务必查阅你的模块AT命令手册。 try: info { fix_status: int(fields[0]), # 定位状态: 1有效定位0无效 run_status: int(fields[1]), # 运行状态 utc_time: fields[2] if fields[2] else None, # UTC时间 YYYYMMDDHHMMSS.SSS latitude: float(fields[3]) if fields[3] else None, # 纬度 longitude: float(fields[4]) if fields[4] else None, # 经度 altitude: float(fields[5]) if fields[5] else None, # 海拔 (米) speed: float(fields[6]) if fields[6] else None, # 地面速度 (公里/小时) course: float(fields[7]) if fields[7] else None, # 航向 (度) fix_mode: int(fields[8]) if fields[8] else None, # 定位模式 reserved1: fields[9], hdop: float(fields[10]) if fields[10] else None, # 水平精度因子 pdop: float(fields[11]) if fields[11] else None, # 位置精度因子 vdop: float(fields[12]) if fields[12] else None, # 垂直精度因子 satellites_used: int(fields[13]) if fields[13] else None, # 使用的卫星数 gnss_satellites: int(fields[14]) if fields[14] else None, # GNSS卫星数 glonass_satellites: int(fields[15]) if fields[15] else None, reserved2: fields[16] if len(fields) 16 else None } return info except (IndexError, ValueError) as e: print(f解析CGNSINF数据时出错: {e}, 原始数据: {data_str}) return None return None def enable_nmea_output(self, enableTrue): 启用或禁用NMEA语句的自动输出。 启用后模块会持续向串口发送GGA, RMC等NMEA语句。 cmd ATCGNSTST1 if enable else ATCGNSTST0 return self.send_at_command(cmd) def read_nmea_sentence(self, timeout5): 从串口读取一行NMEA语句。 这个方法在启用NMEA输出后用于持续读取数据流。 :param timeout: 读取超时时间秒 :return: 一行NMEA字符串或超时返回None if not self.ser: return None original_timeout self.ser.timeout self.ser.timeout timeout try: line self.ser.readline().decode(utf-8, errorsignore).strip() if line.startswith($): # 基本的NMEA语句校验 return line except Exception as e: print(f读取NMEA数据时出错: {e}) finally: self.ser.timeout original_timeout return None现在你可以编写一个主循环来持续获取位置信息def main_loop(): gnss GNSSController(port/dev/ttyUSB2) if not gnss.open_connection(): return # 1. 开启GNSS电源 print(正在开启GNSS电源...) success, _ gnss.gnss_power_on() if not success: print(GNSS电源开启失败请检查模块或天线。) gnss.close_connection() return print(GNSS已启动等待定位...首次定位可能需要几十秒到数分钟) # 2. 循环获取位置信息 fix_obtained False for i in range(60): # 最多尝试60次每次间隔2秒 time.sleep(2) info gnss.get_gnss_info() if info and info.get(fix_status) 1: print(f\n定位成功) print(fUTC时间: {info[utc_time]}) print(f纬度: {info[latitude]}, 经度: {info[longitude]}) print(f海拔: {info[altitude]} 米) print(f速度: {info[speed]} km/h) print(f使用卫星数: {info[satellites_used]}) fix_obtained True break else: sys.stdout.write(.) sys.stdout.flush() if not fix_obtained: print(\n在指定时间内未完成定位。请检查) print(1. GNSS天线是否连接正确且放置在户外开阔处) print(2. 模块的GNSS功能是否完好) # 3. 可选切换到NMEA输出模式并持续读取 # gnss.enable_nmea_output(True) # while True: # nmea gnss.read_nmea_sentence(timeout10) # if nmea: # print(fNMEA: {nmea}) # # 这里可以添加NMEA解析逻辑例如解析$GPGGA获取定位质量 # 4. 关闭GNSS电源 print(\n关闭GNSS电源...) gnss.gnss_power_off() gnss.close_connection() if __name__ __main__: main_loop()5. 实战中的关键问题排查与性能优化脚本跑起来只是第一步。在实际部署中你会遇到各种问题。下面是我在多个项目中总结出的关键排查点和优化技巧。5.1 GNSS无法定位或定位慢的排查流程这是最常见的问题。请按照以下步骤系统性排查天线与信号这是首要原因。确保使用的是有源GNSS天线需要供电并且接口连接牢固。将天线移至户外、远离建筑物和金属物体的开阔地带。可以用命令ATCGNSINF查看satellites_used字段如果一直为0或很小4基本就是信号问题。首次定位时间TTFF模块冷启动完全断电后首次启动或温启动超过星历有效期后需要从卫星下载星历数据这可能需要30秒到几分钟。热启动近期定位过则很快。耐心等待是必要的。你可以通过ATCGNSCOLD、ATCGNSWARM、ATCGNSHOT等命令如果模块支持来手动选择启动模式但通常自动模式即可。模块供电与状态用ATCGNSPWR?确认GNSS电源已开启返回1。有些模块的GNSS和4G共用部分电路确保4G模块整体供电稳定树莓派USB口供电可能不足建议使用外接5V/2A以上电源并通过HAT的电源接口供电。串口配置与数据流确认波特率匹配。如果波特率不对可能收不到数据或收到乱码。用ATCGNSPRT?命令如果支持可以查询和设置GNSS串口的波特率、数据位等参数。另一个隐藏问题如果同时启用了NMEA输出ATCGNSTST1和频繁查询ATCGNSINF串口缓冲区可能会被NMEA数据填满导致AT命令响应被冲掉。在调试阶段建议先关闭NMEA输出ATCGNSTST0只使用查询命令。AGPS辅助数据许多现代GNSS模块支持辅助GPSAGPS即通过网络4G下载当前的星历、历书等数据能极大缩短首次定位时间尤其是在城市峡谷等信号弱的环境。这通常需要通过AT命令如ATCGNSAGPS来启用和配置并可能需要连接到运营商的AGPS服务器。这是提升用户体验的关键优化点。5.2 数据精度与稳定性的考量从ATCGNSINF或NMEA语句中获取的经纬度默认是WGS84坐标系下的。对于大多数民用项目如地图显示是直接可用的。但如果你需要更高的精度或与本地坐标系转换就需要考虑以下因素精度因子DOP值CGNSINF返回的hdop水平精度因子、pdop位置精度因子、vdop垂直精度因子是衡量定位几何精度的关键指标。数值越小精度越高。通常hdop 1.0表示极好的几何条件 2.0很好 5.0中等 5.0则精度较差。在你的程序中可以加入对DOP值的判断只有当hdop低于某个阈值例如3.0时才认为定位数据是“高质量”的用于记录或触发关键动作。多星系支持确保你的模块固件支持并启用了多星系GPS北斗GLONASSGalileo。更多的卫星意味着更快的定位速度和更好的精度尤其是在高楼林立的城市环境。可以用ATCGNSMOD?查询当前模式用ATCGNSMOD31这样的命令具体值需查手册来启用所有支持的星系。数据滤波与平滑原始的GNSS数据会有跳动噪声。对于低速或静止的应用如气象站可以对连续多次的定位结果进行平均或使用卡尔曼滤波等算法进行平滑处理能有效减少位置漂移。但对于高速移动物体如车辆平滑算法需要更谨慎以免引入滞后。5.3 电源管理与长期运行策略对于电池供电的物联网设备GNSS模块是耗电大户。必须进行精细的电源管理。按需开启不要一直开启GNSS。只在需要定位的时候例如设备移动后、到达特定区域、定时上报时通过ATCGNSPWR1开启获取到有效定位数据后立即用ATCGNSPWR0关闭。两次定位之间可以休眠很长时间。使用低功耗模式一些高级模块支持ATCGNSPWR2这样的命令进入“备份”或“低功耗跟踪”模式。在这种模式下模块维持部分电路工作能更快地重新定位热启动功耗比完全关闭后再冷启动要低但比完全开启要省电得多。这需要在功耗和重新定位时间之间做权衡。与主控协同休眠如果你的树莓派也能深度休眠虽然比较难可以设计一个协同唤醒的流程树莓派定时唤醒 - 给4G HAT上电 - 开启GNSS - 获取定位 - 通过4G上传数据 - 关闭GNSS和4G - 树莓派再次休眠。重要提示频繁地开关GNSS模块特别是冷启动本身也会消耗可观的能量。最佳的省电策略是根据应用场景找到最长的、可接受的定位间隔。例如一个共享单车追踪器如果车辆静止可以每小时定位一次如果检测到移动通过加速度计则切换到每分钟定位一次。6. 进阶应用将GNSS数据融入你的物联网项目获取到经纬度、时间和速度后这些数据才能真正产生价值。下面介绍几个常见的集成思路。6.1 构建一个简单的轨迹记录器你可以扩展之前的Python脚本将获取到的定位信息时间、经纬度、海拔、速度以CSV或JSON格式保存到本地文件或者直接写入SQLite数据库。import csv from datetime import datetime class Tracker: def __init__(self, gnss_controller, log_filetracker_log.csv): self.gnss gnss_controller self.log_file log_file # 初始化CSV文件并写入表头 with open(self.log_file, a, newline) as f: writer csv.writer(f) writer.writerow([timestamp_utc, local_time, latitude, longitude, altitude_m, speed_kmh, satellites, hdop]) def log_one_point(self): 获取一次定位并记录 info self.gnss.get_gnss_info() if info and info.get(fix_status) 1: local_time datetime.now().strftime(%Y-%m-%d %H:%M:%S) with open(self.log_file, a, newline) as f: writer csv.writer(f) writer.writerow([ info[utc_time], local_time, info[latitude], info[longitude], info[altitude], info[speed], info[satellites_used], info[hdop] ]) print(f点位已记录: {local_time}, Lat:{info[latitude]}, Lon:{info[longitude]}) return True else: print(未获得有效定位跳过记录。) return False在主循环中你可以定时例如每30秒调用tracker.log_one_point()。这个CSV文件可以被QGIS、Google Earth等工具轻松导入用于绘制轨迹图。6.2 通过4G网络上传位置到云平台这才是4G HAT的完整威力所在。你可以在获取定位后立即通过同一模块的4G功能将数据上传到你的服务器或物联网云平台如阿里云IoT、ThingsBoard、自建MQTT服务器。这里涉及到4G网络的拨号通常使用pppd或NetworkManager以及使用Python的requests或paho-mqtt库进行HTTP/MQTT通信。逻辑流程如下初始化并开启GNSS如前述。等待并获得有效定位。通过AT命令激活4G PDP上下文例如ATCGACT1,1或者依赖系统已建立的移动网络连接。构造JSON数据包import json data_payload { device_id: raspberry_pi_001, timestamp: info[utc_time], location: { lat: info[latitude], lon: info[longitude], alt: info[altitude] }, speed: info[speed], hdop: info[hdop], satellites: info[satellites_used] }使用HTTP POST或MQTT Publish发送数据。关闭GNSS以省电等待下一个上报周期。踩坑提醒同时操作GNSS和4G的AT命令时务必注意串口资源的冲突。很多HAT的4G AT命令和GNSS AT命令共享同一个主通信端口如/dev/ttyUSB1用于AT命令控制/dev/ttyUSB2专用于GNSS数据。最佳实践是使用两个独立的串口对象一个用于发送控制类AT命令包括控制4G和开关GNSS另一个专门用于读取GNSS的NMEA数据流。如果混用极易发生命令响应被数据流冲掉的问题。6.3 实现基于位置的触发与地理围栏有了实时位置你可以让设备变得更“智能”。例如实现一个简单的“地理围栏”Geofencing功能当设备进入或离开某个预设区域时触发一个动作如发送警报、拍照、改变工作模式。def is_point_in_polygon(point, polygon): 判断点是否在多边形内使用射线法 # 这是一个简化的实现生产环境建议使用Shapely等库 x, y point inside False n len(polygon) j n - 1 for i in range(n): xi, yi polygon[i] xj, yj polygon[j] intersect ((yi y) ! (yj y)) and (x (xj - xi) * (y - yi) / (yj - yi) xi) if intersect: inside not inside j i return inside # 定义一个矩形围栏[经度 纬度] fence_polygon [ (113.950, 22.530), (113.960, 22.530), (113.960, 22.540), (113.950, 22.540) ] current_location (info[longitude], info[latitude]) if is_point_in_polygon(current_location, fence_polygon): print(设备已进入围栏区域) # 触发动作发送通知、打开继电器等 else: print(设备在围栏外。)这个功能可以广泛应用于车辆管理、宠物追踪、设备防盗等场景。将地理围栏的逻辑与4G上传结合就能实现实时的越界报警。从硬件连接到手动测试再到Python脚本自动化最后到融入实际项目并优化这就是使用树莓派4G HAT的GNSS功能的完整路径。整个过程最磨人的地方往往不是编码而是前期的硬件调试和环境配置。一旦打通这个“树莓派4GGNSS”的组合就会变成一个极其灵活和强大的空间信息感知终端能解锁的玩法只受你的想象力限制。我自己的经验是多备几根不同类型的天线有源/无源磁吸/蘑菇头在项目初期花时间做好信号测试后期能省去无数莫名其妙的定位失败问题。