1. 硬件准备与环境搭建

开始之前,我们需要准备一些基础硬件。树莓派我推荐使用4B型号,性能足够运行图形界面和数据处理程序。GPS模块选择上,市面上常见的USB接口或串口模块都可以,比如UBLOX NEO-6M这类经典型号就很稳定。另外建议准备一个显示屏,方便实时查看定位数据,当然如果只是做无头模式运行,显示器可以省略。

我实际测试时用的是树莓派4B 4GB版本,搭配了一个20元的二手GPS模块,效果出乎意料的好。这里有个小建议:购买GPS模块时最好选择带外部天线的型号,室内接收效果会好很多。记得还要准备一张16GB以上的TF卡,读写速度尽量选Class10以上的,系统运行会更流畅。

操作系统方面,我习惯使用Raspberry Pi OS Lite版本,资源占用少而且稳定。烧录系统时有个关键步骤:一定要在烧录工具中提前开启SSH服务并配置WiFi连接。这样树莓派开机就能自动联网,我们后面远程操作会方便很多。第一次启动后,可以通过路由器管理界面或者手机热点客户端列表找到树莓派的IP地址。

2. GPS模块连接与测试

硬件连接其实比想象中简单。USB接口的GPS模块即插即用,串口模块则需要额外注意接线方式。树莓派的GPIO引脚中,TX和RX需要与GPS模块交叉连接:树莓派的TX接模块的RX,树莓派的RX接模块的TX。别忘了还要连接地线和3.3V电源,千万不要接5V引脚,否则可能会烧坏模块。

连接好后,我们可以先通过命令行测试模块是否正常工作。使用ls /dev/tty*命令查看设备列表,通常USB模块会显示为/dev/ttyUSB0,GPIO串口模块则是/dev/ttyAMA0。如果看不到相应设备,可能需要通过raspi-config工具启用串口功能。

我写了个简单的测试脚本,可以快速验证数据接收:

cat /dev/ttyUSB0

如果看到一堆以$开头的NMEA数据流,说明硬件连接成功了。不过这些原始数据看起来像天书,接下来我们需要用Python来解析这些信息。

3. 数据解析与处理

NMEA数据虽然看起来复杂,但其实有固定的格式规范。最常见的GGA语句包含时间、经纬度、海拔等关键信息,RMC语句则提供速度、航向等动态数据。我们需要用Python的pynmea2库来解析这些数据。

安装依赖包时可能会遇到系统保护机制,这是我踩过的坑。正确的安装命令是:

pip install pyserial pynmea2 --break-system-packages

下面是我优化后的解析脚本,增加了错误处理和数据过滤:

import serial
import pynmea2
from datetime import datetime

class GPSParser:
    def __init__(self, port='/dev/ttyUSB0', baudrate=9600):
        self.port = port
        self.baudrate = baudrate
        self.serial_conn = None
        
    def connect(self):
        try:
            self.serial_conn = serial.Serial(
                self.port, 
                self.baudrate, 
                timeout=1,
                bytesize=serial.EIGHTBITS,
                parity=serial.PARITY_NONE,
                stopbits=serial.STOPBITS_ONE
            )
            return True
        except serial.SerialException as e:
            print(f"连接失败: {e}")
            return False
            
    def parse_sentence(self, sentence):
        try:
            msg = pynmea2.parse(sentence)
            if hasattr(msg, 'latitude') and hasattr(msg, 'longitude'):
                return {
                    'time': msg.timestamp if hasattr(msg, 'timestamp') else None,
                    'latitude': msg.latitude,
                    'longitude': msg.longitude,
                    'altitude': getattr(msg, 'altitude', None),
                    'speed': getattr(msg, 'spd_over_grnd', None),
                    'satellites': getattr(msg, 'num_sats', 0)
                }
        except pynmea2.ParseError:
            pass
        return None

    def read_continuous(self):
        buffer = ""
        while True:
            if self.serial_conn.in_waiting > 0:
                char = self.serial_conn.read().decode('ascii', errors='ignore')
                if char == '$':
                    if buffer.startswith('$'):
                        yield buffer.strip()
                    buffer = char
                else:
                    buffer += char

这个改进版的解析器采用流式处理方式,能更稳定地处理数据流。在实际测试中,即使信号不稳定时出现数据残缺,也不会导致程序崩溃。

4. 实时数据可视化实现

数据解析完成后,我们需要一个直观的方式展示位置信息。我选择了Tkinter来制作图形界面,因为它轻量且无需额外安装。界面设计要考虑实用性和美观性的平衡。

先安装必要的依赖:

pip install tkinter matplotlib --break-system-packages

下面是GUI实现的核心代码:

import tkinter as tk
from tkinter import ttk
import threading
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg

class GPSGUI:
    def __init__(self, parser):
        self.parser = parser
        self.root = tk.Tk()
        self.root.title("树莓派GPS定位系统")
        self.root.geometry("800x600")
        
        self.setup_ui()
        self.running = False
        
    def setup_ui(self):
        # 状态显示区域
        status_frame = ttk.LabelFrame(self.root, text="实时状态")
        status_frame.pack(pady=10, padx=10, fill="x")
        
        self.labels = {}
        for i, key in enumerate(['时间', '纬度', '经度', '海拔', '速度', '卫星数']):
            label = ttk.Label(status_frame, text=f"{key}: --")
            label.grid(row=i//2, column=i%2, sticky="w", padx=5, pady=2)
            self.labels[key] = label
            
        # 地图显示区域
        map_frame = ttk.LabelFrame(self.root, text="位置轨迹")
        map_frame.pack(pady=10, padx=10, fill="both", expand=True)
        
        self.fig = Figure(figsize=(6, 4), dpi=100)
        self.ax = self.fig.add_subplot(111)
        self.canvas = FigureCanvasTkAgg(self.fig, map_frame)
        self.canvas.get_tk_widget().pack(fill="both", expand=True)
        
        # 控制按钮
        btn_frame = ttk.Frame(self.root)
        btn_frame.pack(pady=10)
        
        ttk.Button(btn_frame, text="开始", command=self.start).pack(side="left", padx=5)
        ttk.Button(btn_frame, text="停止", command=self.stop).pack(side="left", padx=5)
        ttk.Button(btn_frame, text="保存数据", command=self.save_data).pack(side="left", padx=5)
        
    def update_display(self, data):
        if data:
            self.labels['时间'].config(text=f"时间: {data['time']}")
            self.labels['纬度'].config(text=f"纬度: {data['latitude']:.6f}")
            self.labels['经度'].config(text=f"经度: {data['longitude']:.6f}")
            self.labels['海拔'].config(text=f"海拔: {data['altitude']}m")
            self.labels['速度'].config(text=f"速度: {data['speed']}节")
            self.labels['卫星数'].config(text=f"卫星数: {data['satellites']}")
            
            # 更新轨迹图
            self.ax.plot(data['longitude'], data['latitude'], 'ro')
            self.ax.set_xlabel('经度')
            self.ax.set_ylabel('纬度')
            self.canvas.draw()
            
    def start(self):
        if not self.running:
            self.running = True
            self.thread = threading.Thread(target=self.read_loop)
            self.thread.daemon = True
            self.thread.start()
            
    def read_loop(self):
        for data in self.parser.read_continuous():
            if not self.running:
                break
            self.root.after(0, self.update_display, data)
            
    def stop(self):
        self.running = False
        
    def save_data(self):
        # 数据保存功能
        pass

这个界面虽然简单,但包含了所有关键功能。在实际使用中,我发现 matplotlib 虽然方便,但在树莓派上性能一般。如果要求更高的刷新率,可以考虑使用PyGame或者Web前端来实现可视化。

5. 系统优化与部署

要让这个系统真正实用化,还需要考虑一些优化措施。首先是定位精度问题,单纯的GPS模块在室内效果很差,可以考虑加入IMU传感器进行数据融合。我试过用MPU6050加速度计辅助定位,在信号短暂丢失时能提供不错的惯性导航效果。

电源管理也很重要。如果用在移动场景,需要配置好树莓派的低功耗模式。可以通过sudo raspi-config设置CPU频率调节模式为"ondemand",关闭HDMI输出等不必要的功能。我实测这些设置能让续航时间提升30%以上。

数据存储方面,建议使用SQLite数据库记录轨迹数据:

import sqlite3
import json
from pathlib import Path

class DataLogger:
    def __init__(self, db_path='gps_data.db'):
        self.db_path = Path(db_path)
        self.init_db()
        
    def init_db(self):
        conn = sqlite3.connect(self.db_path)
        conn.execute('''CREATE TABLE IF NOT EXISTS locations
                     (id INTEGER PRIMARY KEY AUTOINCREMENT,
                      timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
                      latitude REAL,
                      longitude REAL,
                      altitude REAL,
                      speed REAL,
                      satellites INTEGER)''')
        conn.commit()
        conn.close()
        
    def log_data(self, data):
        conn = sqlite3.connect(self.db_path)
        conn.execute('''INSERT INTO locations 
                     (latitude, longitude, altitude, speed, satellites)
                     VALUES (?, ?, ?, ?, ?)''',
                     (data['latitude'], data['longitude'], 
                      data['altitude'], data['speed'], 
                      data['satellites']))
        conn.commit()
        conn.close()

对于长期运行的系统,还需要加入日志记录和异常监控。我建议使用Python的logging模块记录系统运行状态,这样出现问题时可以快速定位。

6. 实际应用与扩展

这个GPS系统在实际项目中有很多应用场景。比如我去年做的共享设备追踪项目,就是基于类似的技术方案。当时遇到的问题主要是信号遮挡导致的数据断续,后来通过加入卡尔曼滤波算法改善了轨迹平滑度。

如果想要更精确的定位,可以考虑接入RTK(实时动态定位)技术。虽然成本会高一些,但能达到厘米级定位精度。市面上有些开源RTK项目,比如RTKLIB,可以集成到我们的系统中。

另一个有趣的扩展是加入地理围栏功能。当设备进入或离开特定区域时自动触发动作,比如:

class GeoFence:
    def __init__(self, points):
        self.points = points  # 多边形顶点列表
        
    def is_inside(self, lat, lon):
        # 射线法判断点是否在多边形内
        count = 0
        for i in range(len(self.points)):
            p1 = self.points[i]
            p2 = self.points[(i+1) % len(self.points)]
            if ((p1[1] > lon) != (p2[1] > lon)) and \
               (lat < (p2[0]-p1[0]) * (lon-p1[1]) / (p2[1]-p1[1]) + p1[0]):
                count += 1
        return count % 2 == 1

对于需要远程监控的场景,可以考虑加入Web服务功能。用Flask搭建一个简单的HTTP服务器,就能通过浏览器远程查看实时位置:

from flask import Flask, jsonify
import threading

app = Flask(__name__)
latest_data = {}

@app.route('/api/position')
def get_position():
    return jsonify(latest_data)

def run_web_server():
    app.run(host='0.0.0.0', port=5000)

# 在主程序中启动Web服务线程
web_thread = threading.Thread(target=run_web_server)
web_thread.daemon = True
web_thread.start()

这些扩展功能让基础的GPS定位系统变得更加强大和实用。在实际项目中,可以根据具体需求选择合适的功能组合。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐