组合导航定位实战(1)GNSS/IMU模块数据读取

在智能设备、自动驾驶、无人机等领域,组合导航定位技术是实现精准位置感知的核心技术之一。它通过融合 GNSS(全球导航卫星系统)、IMU(惯性测量单元)等多传感器数据,实现高精度、高可靠性的定位。从本期开始,我们将通过一系列实战教程,逐步讲解组合导航定位系统的开发过程。本文作为开篇,将聚焦于 GNSS 模块的原始数据读取,特别是 NMEA 协议中GNGGA 语句的解析方法。

一、相关知识

1.1 NMEA 0183 协议简介​

NMEA 0183 是全球通用的海洋电子设备数据传输标准,目前广泛应用于 GNSS 接收机、航海仪器等设备。该协议采用 ASCII 码格式,以文本行形式传输数据,每条语句以$开头,以*后跟两位十六进制校验和结尾,结构清晰易解析。

1.2 GNGGA 语句格式解析​

GNGGA(Global Navigation Satellite System Fix Data)是 NMEA 协议中最重要的定位数据语句之一,包含了实时定位的关键信息。其标准格式如下:

$GNGGA,<UTC 时间>,<纬度>,<纬度方向(N/S)>,<经度>,<经度方向>,<定位状态>,<参与定位的卫星数>,<8>,<海拔高度>

1.3 IMU 基础概念

IMU(Inertial Measurement Unit)即惯性测量单元,通常包含三轴加速度计和三轴陀螺仪,部分还集成了磁力计。在组合导航系统中,IMU 能够提供高频的相对运动数据,弥补 GNSS 更新率低、动态响应慢的缺点。这里选用 MPU6050 作为 IMU 模块示例,它是一款低成本、高性能的 6 轴传感器,集成了 3 轴加速度计和 3 轴陀螺仪,通过 I2C 接口与控制器通信。

 

二、基于 Python 的串口数据读取与解析

2.1 GNGGA 解析函数

关键逻辑说明:​

  • 度分转换公式:十进制度数 = 度 + 分 / 60,例如4807.038N表示 48 度 7.038 分,转换为 48 + 7.038/60 = 48.1173 度(北纬为正)​
  • 定位状态判断:仅当fix_status=1时(单点定位),数据有效(0 表示无定位,2 表示差分定位,4表示固定解,5表示浮点解)​未接入RTK时为单点定位(网络RTK校正后续会讲)
  • 异常处理:字段缺失或格式错误时返回None,避免程序崩溃
def parse_gngga(nmea_sentence):
    try:
        parts = nmea_sentence.split(',')
        if len(parts) < 15:  # 确保字段完整性
            return None
        
        # 时间格式转换:HHMMSS -> HH:MM:SS
        utc_time = parts[1][:6]  # 截取前6位(忽略毫秒)
        time_str = f"{utc_time[:2]}:{utc_time[2:4]}:{utc_time[4:6]}" if utc_time else "N/A"
        
        # 纬度转换:ddmm.mmmm -> 十进制度数(±表示南北)
        latitude = parts[2]
        lat_dir = parts[3]
        lat_deg = float(latitude[:2])  # 提取度部分
        lat_min = float(latitude[2:])  # 提取分部分
        lat_decimal = lat_deg + lat_min / 60.0  # 度分转换为十进制
        lat_decimal = -lat_decimal if lat_dir == 'S' else lat_decimal  # 南半球取负值
        
        # 经度转换:原理同纬度,注意经度为三位数(如011表示11度)
        longitude = parts[4]
        lon_dir = parts[5]
        lon_deg = float(longitude[:3])
        lon_min = float(longitude[3:])
        lon_decimal = lon_deg + lon_min / 60.0
        lon_decimal = -lon_decimal if lon_dir == 'W' else lon_decimal  # 西半球取负值
        
        return {
            'time': time_str,
            'latitude': lat_decimal,
            'longitude': lon_decimal,
            'fix_status': '有效' if parts[6] == '1' else '无效',  # 1=GPS fix有效
            'satellites': int(parts[7]),  # 参与定位的卫星数
            'altitude': float(parts[9])  # 海拔高度(单位:米,字段9)
        }
    except (ValueError, IndexError) as e:
        print(f"解析错误: {e}")
        return None

2.2 串口通信与数据读取

  • 端口设置:Windows 系统端口为COM3等,Linux 为/dev/ttyUSB0,需通过设备管理器 / 系统日志确认​
  • 波特率匹配:GNSS 模块默认波特率通常为 9600(可通过 AT 指令修改)​
  • 数据过滤:通过startswith('$GNGGA')确保仅处理目标语句(GNGGA较常用,也可选其他语句),避免解析其他类型数据(如 GPRMC、GNGSA)
import serial

# 串口配置
ser = serial.Serial(
    port='/dev/ttyS0',  # Linux串口示例(如USB转TTL可能为/dev/ttyUSB0)
    baudrate=9600,       # 常见波特率:4800/9600/115200
    timeout=1           # 读取超时时间(秒)
)

try:
    while True:
        line = ser.readline().decode('ascii', errors='ignore').strip()
        if line.startswith('$GNGGA'):  # 仅处理GNGGA语句
            print(f"原始数据: {line}")
            data = parse_gngga(line)
            if data:
                # 格式化输出关键信息
                print(f"时间:{data['time']}")
                print(f"纬度:{data['latitude']:.8f}°")  # 保留8位小数
                print(f"经度:{data['longitude']:.8f}°")
                print(f"定位状态:{data['fix_status']}")
                print(f"卫星数:{data['satellites']}")
                print(f"海拔:{data['altitude']}米\n")
except KeyboardInterrupt:
    print("程序终止(用户中断)")
except Exception as e:
    print(f"发生错误: {e}")
finally:
    ser.close()  # 确保串口关闭

2.3 校验和验证(扩展优化)

NMEA 语句的校验和位于*之后,用于验证数据完整性。可通过以下代码实现校验和验证(可根据需求添加):

def check_checksum(sentence):
    # 提取数据部分(去除$和*后的内容)
    data_part = sentence[1:sentence.index('*')]
    checksum = sentence[sentence.index('*')+1:sentence.index('*')+3]
    # 计算异或校验和
    xor = 0
    for c in data_part:
        xor ^= ord(c)
    return checksum.upper() == f"{xor:02X}"

# 在解析前调用:
if check_checksum(line):
    data = parse_gngga(line)
else:
    print("校验和错误,忽略本条数据")

2.4 MPU6050 数据读取与解析

MPU6050 通过 I2C 协议通信,默认地址为 0x68,使用 smbus2 库实现 I2C 读写:

  • _write_byte:向指定寄存器写入数据
  • _read_bytes:从指定寄存器读取多个字节数据
  • read_raw_data ():从数据寄存器 (0x3B) 开始读取 14 字节数据,包含加速度、温度和陀螺仪的原始值
class MPU6050:
    def __init__(self, bus=1, address=0x68):
        self.bus = SMBus(bus)
        self.address = address
        
        # 初始化传感器
        self._write_byte(0x6B, 0x00)  # 退出睡眠模式
        self._write_byte(0x1B, 0x00)  # 陀螺仪量程 ±250°/s
        self._write_byte(0x1C, 0x00)  # 加速度计量程 ±2g

    def _write_byte(self, reg, value):
        self.bus.write_byte_data(self.address, reg, value)

    def _read_bytes(self, reg, length):
        read = i2c_msg.read(self.address, length)
        self.bus.i2c_rdwr(i2c_msg.write(self.address, [reg]), read)
        return list(read)

    def read_raw_data(self):
        # 一次性读取所有传感器数据(14字节)
        raw = self._read_bytes(0x3B, 14)
        
        # 加速度计原始值(16位有符号数)
        accel_x = (raw[0] << 8) | raw[1]
        accel_y = (raw[2] << 8) | raw[3]
        accel_z = (raw[4] << 8) | raw[5]
        
        # 温度传感器原始值
        temp = (raw[6] << 8) | raw[7]
        
        # 陀螺仪原始值
        gyro_x = (raw[8] << 8) | raw[9]
        gyro_y = (raw[10] << 8) | raw[11]
        gyro_z = (raw[12] << 8) | raw[13]
        
        return accel_x, accel_y, accel_z, gyro_x, gyro_y, gyro_z, temp

    def get_calibrated_data(self):
        # 读取原始数据并转换为物理量
        accel_x, accel_y, accel_z, gyro_x, gyro_y, gyro_z, temp = self.read_raw_data()
        
        # 加速度换算为g(±2g量程时灵敏度为16384 LSB/g)
        accel_x_g = accel_x / 16384.0
        accel_y_g = accel_y / 16384.0
        accel_z_g = accel_z / 16384.0
        
        # 陀螺仪换算为°/s(±250°/s量程时灵敏度为131 LSB/(°/s))
        gyro_x_dps = gyro_x / 131.0
        gyro_y_dps = gyro_y / 131.0
        gyro_z_dps = gyro_z / 131.0
        
        # 温度换算(可以不要)
        temp_c = (temp / 340.0) + 36.53
        
        return {
            'accel': (accel_x_g, accel_y_g, accel_z_g),
            'gyro': (gyro_x_dps, gyro_y_dps, gyro_z_dps),
            'temp': temp_c
        }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值