树莓派4G HAT GNSS功能实战:从AT命令到Python物联网定位应用
1. 从零开始:为什么你需要一个带GNSS的树莓派
如果你手头有一块树莓派,并且正在捣鼓物联网项目,比如远程气象站、资产追踪器、自动驾驶小车,或者只是想做一个能告诉你精确位置和时间戳的数据记录仪,那么给树莓派加上全球导航卫星系统(GNSS)功能,绝对能让你的项目上一个台阶。这不仅仅是“知道自己在哪”那么简单,一个稳定可靠的GNSS模块,能提供微秒级的时间同步、厘米级的定位精度(配合RTK),以及不受网络限制的全球可用性。
最近,集成4G LTE和GNSS功能的HAT(Hardware 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(波特率9600,8位数据位,无奇偶校验,1位停止位)。也有模块支持更高的波特率如115200以提高数据吞吐量,但9600是广泛支持的默认值。
3. 使用Minicom进行手动AT命令测试
在编写自动化脚本之前,用minicom进行手动测试是至关重要的。这能帮你确认硬件连接无误、串口号正确、模块能正常响应。
启动minicom连接GNSS串口(假设是/dev/ttyUSB2):
minicom -D /dev/ttyUSB2 -b 9600如果连接成功,终端会清空并进入一个“哑终端”模式。此时,你敲击键盘,字符会直接发送到串口。
第一步:检查模块通信。在minicom终端里,输入:
AT然后按回车。你应该立即看到模块返回:
OK这证明串口链路是通的,模块处于AT命令模式。如果没反应,检查:1) 串口号是否正确;2) 波特率是否正确;3) 线缆和连接;4) 模块是否已供电并启动。
第二步:查询GNSS模块的固件信息。
AT+CGNSPWR?此命令查询GNSS电源状态。返回+CGNSPWR: 1表示已上电,0表示关闭。
AT+CGNSINF这是一个非常重要的综合信息查询命令。如果GNSS未定位,它可能返回:
+CGNSINF: 0,,,,,,,,,,,,,,,,,,如果已定位,则会返回一长串用逗号分隔的数据,包含定位状态、UTC时间、纬度、经度、海拔高度、速度、航向、定位模式、卫星数等丰富信息。这是获取位置数据最常用的命令之一。
第三步:控制GNSS电源和输出频率。你可以手动开启或关闭GNSS功能以省电:
AT+CGNSPWR=1 // 开启GNSS电源 AT+CGNSPWR=0 // 关闭GNSS电源设置NMEA语句的输出频率(例如,每秒1次):
AT+CGNSTST=1 // 开始输出NMEA数据到串口 AT+CGNSURC=1 // 设置URC(非请求结果码)输出周期为1秒设置后,模块会自动、周期性地向串口输出标准的NMEA-0183格式语句,如$GPGGA,$GPRMC等。你会在minicom里看到滚动出现的文本数据。
退出minicom:按Ctrl+A,然后按Z调出帮助菜单,再按X选择退出。
通过以上手动测试,你已经验证了从硬件到基础命令的整个通路。接下来,我们将用Python脚本让这一切自动化。
4. 编写Python脚本与GNSS模块交互
手动测试没问题后,我们就可以用Python的pyserial库来编写一个稳定、可配置的GNSS数据读取程序。相比手动操作,脚本化可以方便地进行数据解析、记录、上传或触发其他动作。
4.1 安装Python串口库并建立基础连接
首先安装pyserial:
pip3 install pyserial创建一个新的Python脚本,比如gnss_reader.py。
import serial import time import sys class GNSSController: def __init__(self, port='/dev/ttyUSB2', baudrate=9600, timeout=1): """ 初始化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( port=self.port, baudrate=self.baudrate, bytesize=serial.EIGHTBITS, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, timeout=self.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_response="OK", wait_time=0.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', errors='ignore') 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("AT+CGNSPWR?") # 解析resp,例如判断是否是1 gnss.close_connection()这个类封装了串口连接、命令发送和响应读取的基础操作。send_at_command方法是一个通用框架,你可以用它发送任何AT命令。
4.2 实现GNSS功能控制与数据解析
接下来,我们在类中添加专门针对GNSS功能的方法。
def gnss_power_on(self): """开启GNSS电源""" return self.send_at_command("AT+CGNSPWR=1") def gnss_power_off(self): """关闭GNSS电源""" return self.send_at_command("AT+CGNSPWR=0") def get_gnss_info(self): """ 获取GNSS综合信息(CGNSINF)。 这是最常用的获取位置、时间、速度等信息的方法。 :return: 解析后的字典,如果未定位则返回None """ success, resp = self.send_at_command("AT+CGNSINF", wait_time=1.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, enable=True): """ 启用或禁用NMEA语句的自动输出。 启用后,模块会持续向串口发送GGA, RMC等NMEA语句。 """ cmd = "AT+CGNSTST=1" if enable else "AT+CGNSTST=0" return self.send_at_command(cmd) def read_nmea_sentence(self, timeout=5): """ 从串口读取一行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', errors='ignore').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(f"UTC时间: {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(timeout=10) # if nmea: # print(f"NMEA: {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天线(需要供电),并且接口连接牢固。将天线移至户外、远离建筑物和金属物体的开阔地带。可以用命令
AT+CGNSINF查看satellites_used字段,如果一直为0或很小(<4),基本就是信号问题。首次定位时间(TTFF):模块冷启动(完全断电后首次启动)或温启动(超过星历有效期)后,需要从卫星下载星历数据,这可能需要30秒到几分钟。热启动(近期定位过)则很快。耐心等待是必要的。你可以通过
AT+CGNSCOLD、AT+CGNSWARM、AT+CGNSHOT等命令(如果模块支持)来手动选择启动模式,但通常自动模式即可。模块供电与状态:用
AT+CGNSPWR?确认GNSS电源已开启(返回1)。有些模块的GNSS和4G共用部分电路,确保4G模块整体供电稳定(树莓派USB口供电可能不足,建议使用外接5V/2A以上电源并通过HAT的电源接口供电)。串口配置与数据流:确认波特率匹配。如果波特率不对,可能收不到数据或收到乱码。用
AT+CGNSPRT?命令(如果支持)可以查询和设置GNSS串口的波特率、数据位等参数。另一个隐藏问题:如果同时启用了NMEA输出(AT+CGNSTST=1)和频繁查询AT+CGNSINF,串口缓冲区可能会被NMEA数据填满,导致AT命令响应被冲掉。在调试阶段,建议先关闭NMEA输出(AT+CGNSTST=0),只使用查询命令。AGPS辅助数据:许多现代GNSS模块支持辅助GPS(AGPS),即通过网络(4G)下载当前的星历、历书等数据,能极大缩短首次定位时间,尤其是在城市峡谷等信号弱的环境。这通常需要通过AT命令(如
AT+CGNSAGPS)来启用和配置,并可能需要连接到运营商的AGPS服务器。这是提升用户体验的关键优化点。
5.2 数据精度与稳定性的考量
从AT+CGNSINF或NMEA语句中获取的经纬度,默认是WGS84坐标系下的。对于大多数民用项目(如地图显示)是直接可用的。但如果你需要更高的精度或与本地坐标系转换,就需要考虑以下因素:
精度因子(DOP值):
CGNSINF返回的hdop(水平精度因子)、pdop(位置精度因子)、vdop(垂直精度因子)是衡量定位几何精度的关键指标。数值越小,精度越高。通常,hdop < 1.0表示极好的几何条件,< 2.0很好,< 5.0中等,> 5.0则精度较差。在你的程序中,可以加入对DOP值的判断,只有当hdop低于某个阈值(例如3.0)时,才认为定位数据是“高质量”的,用于记录或触发关键动作。多星系支持:确保你的模块固件支持并启用了多星系(GPS+北斗+GLONASS+Galileo)。更多的卫星意味着更快的定位速度和更好的精度,尤其是在高楼林立的城市环境。可以用
AT+CGNSMOD?查询当前模式,用AT+CGNSMOD=31这样的命令(具体值需查手册)来启用所有支持的星系。数据滤波与平滑:原始的GNSS数据会有跳动(噪声)。对于低速或静止的应用(如气象站),可以对连续多次的定位结果进行平均或使用卡尔曼滤波等算法进行平滑处理,能有效减少位置漂移。但对于高速移动物体(如车辆),平滑算法需要更谨慎,以免引入滞后。
5.3 电源管理与长期运行策略
对于电池供电的物联网设备,GNSS模块是耗电大户。必须进行精细的电源管理。
按需开启:不要一直开启GNSS。只在需要定位的时候(例如,设备移动后、到达特定区域、定时上报时)通过
AT+CGNSPWR=1开启,获取到有效定位数据后,立即用AT+CGNSPWR=0关闭。两次定位之间可以休眠很长时间。使用低功耗模式:一些高级模块支持
AT+CGNSPWR=2这样的命令,进入“备份”或“低功耗跟踪”模式。在这种模式下,模块维持部分电路工作,能更快地重新定位(热启动),功耗比完全关闭后再冷启动要低,但比完全开启要省电得多。这需要在功耗和重新定位时间之间做权衡。与主控协同休眠:如果你的树莓派也能深度休眠(虽然比较难),可以设计一个协同唤醒的流程:树莓派定时唤醒 -> 给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_file='tracker_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上下文(例如
AT+CGACT=1,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功能的完整路径。整个过程最磨人的地方往往不是编码,而是前期的硬件调试和环境配置。一旦打通,这个“树莓派+4G+GNSS”的组合就会变成一个极其灵活和强大的空间信息感知终端,能解锁的玩法只受你的想象力限制。我自己的经验是,多备几根不同类型的天线(有源/无源,磁吸/蘑菇头),在项目初期花时间做好信号测试,后期能省去无数莫名其妙的定位失败问题。