I2C 定位程序:LC29H/LC76G ZED-F9P

LC29H/LC76G

复制代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
LC29H DA GNSS 模块 I2C 中文寄存器程序

适用模块:
  Quectel LC29H DA 以及同类 LC29H/L89/LC79H I2C 协议模块

I2C 地址与寄存器偏移:
  0x50:配置读写命令地址
  0x54:数据读取地址
  0x58:数据写入地址

  0x0008:发送缓冲区已有数据长度
  0x2000:发送缓冲区数据内容
  0x0004:接收缓冲区剩余空间
  0x1000:接收缓冲区写入入口

资料依据:
  Quectel_L89_R2.0LC29H_SeriesLC79HAL_I2C_Application_Note

运行:
  sudo raspi-config 里开启 I2C
  i2cdetect -y 1 应能看到 0x50
  python3 lc29h_i2c.py
"""

import argparse
import sys
import time


try:
    from smbus2 import SMBus, i2c_msg
except ImportError:
    print("未找到 smbus2,请先安装:sudo apt install -y python3-smbus python3-pip && pip3 install smbus2")
    sys.exit(1)


默认总线号 = 1

地址_配置命令 = 0x50
地址_读取数据 = 0x54
地址_写入数据 = 0x58

命令_配置读取 = 0xAA51
命令_配置写入 = 0xAA53

寄存器_发送长度 = 0x0008
寄存器_发送数据 = 0x2000
寄存器_接收剩余 = 0x0004
寄存器_接收数据 = 0x1000

单次最多读取 = 1024
重试次数 = 20
步骤延时 = 0.01


def 整数转4字节小端(数值):
    return int(数值).to_bytes(4, byteorder="little", signed=False)


def 组成配置命令(命令, 寄存器偏移, 长度):
    """
    按移远 I2C 应用说明组成 8 字节配置命令。
    第一个 word:高 16 位为命令,低 16 位为寄存器偏移。
    第二个 word:要读或写的数据长度。
    """
    第一字 = ((命令 & 0xFFFF) << 16) | (寄存器偏移 & 0xFFFF)
    return 整数转4字节小端(第一字) + 整数转4字节小端(长度)


def 原始写(总线, 地址, 数据):
    消息 = i2c_msg.write(地址, bytes(数据))
    总线.i2c_rdwr(消息)


def 原始读(总线, 地址, 长度):
    消息 = i2c_msg.read(地址, 长度)
    总线.i2c_rdwr(消息)
    return bytes(消息)


def 带重试写(总线, 地址, 数据, 说明):
    最后错误 = None
    for _ in range(重试次数):
        try:
            time.sleep(步骤延时)
            原始写(总线, 地址, 数据)
            return True
        except OSError as 错误:
            最后错误 = 错误
    print(f"{说明}失败:{最后错误}")
    return False


def 带重试读(总线, 地址, 长度, 说明):
    最后错误 = None
    for _ in range(重试次数):
        try:
            time.sleep(步骤延时)
            return 原始读(总线, 地址, 长度)
        except OSError as 错误:
            最后错误 = 错误
    print(f"{说明}失败:{最后错误}")
    return b""


def 读32位小端(数据):
    if len(数据) < 4:
        return 0
    return int.from_bytes(数据[:4], byteorder="little", signed=False)


def 唤醒或恢复一次(总线):
    """
    如果上一次 I2C 传输中断,模块可能停在 0x54 或 0x58 状态。
    读一下这两个地址或写一下 0x50,有助于回到新一轮通信流程。
    """
    for 地址 in (地址_读取数据, 地址_写入数据):
        try:
            原始读(总线, 地址, 1)
        except OSError:
            pass
    try:
        原始写(总线, 地址_配置命令, b"\x00")
    except OSError:
        pass


def 读取模块可读长度(总线):
    命令 = 组成配置命令(命令_配置读取, 寄存器_发送长度, 4)
    if not 带重试写(总线, 地址_配置命令, 命令, "发送读取长度配置命令"):
        return 0

    数据 = 带重试读(总线, 地址_读取数据, 4, "读取发送缓冲区长度")
    return 读32位小端(数据)


def 读取模块数据(总线, 读取长度):
    if 读取长度 <= 0:
        return b""

    读取长度 = min(int(读取长度), 单次最多读取)
    命令 = 组成配置命令(命令_配置读取, 寄存器_发送数据, 读取长度)
    if not 带重试写(总线, 地址_配置命令, 命令, "发送读取数据配置命令"):
        return b""

    return 带重试读(总线, 地址_读取数据, 读取长度, "读取模块数据")


def 读取接收剩余空间(总线):
    命令 = 组成配置命令(命令_配置读取, 寄存器_接收剩余, 4)
    if not 带重试写(总线, 地址_配置命令, 命令, "发送读取剩余空间配置命令"):
        return 0

    数据 = 带重试读(总线, 地址_读取数据, 4, "读取接收缓冲区剩余空间")
    return 读32位小端(数据)


def 写入模块数据(总线, 数据):
    数据 = bytes(数据)
    if not 数据:
        return True

    位置 = 0
    while 位置 < len(数据):
        剩余空间 = 读取接收剩余空间(总线)
        if 剩余空间 <= 0:
            time.sleep(0.05)
            continue

        本次长度 = min(len(数据) - 位置, 剩余空间, 单次最多读取)
        分片 = 数据[位置:位置 + 本次长度]

        命令 = 组成配置命令(命令_配置写入, 寄存器_接收数据, 本次长度)
        if not 带重试写(总线, 地址_配置命令, 命令, "发送写入数据配置命令"):
            return False
        if not 带重试写(总线, 地址_写入数据, 分片, "写入模块数据"):
            return False

        位置 += 本次长度
        time.sleep(步骤延时)

    return True


def 打印NMEA文本(数据, 文本缓存):
    文本缓存.extend(数据)
    while b"\n" in 文本缓存:
        一行, _, 剩余 = 文本缓存.partition(b"\n")
        文本缓存[:] = 剩余
        文本 = 一行.strip().decode("ascii", errors="ignore")
        if 文本:
            print(文本)


def 主程序():
    global 单次最多读取

    解析器 = argparse.ArgumentParser(description="LC29H DA I2C 中文寄存器读取程序")
    解析器.add_argument("--总线", type=int, default=默认总线号, help="I2C 总线号,树莓派默认 1")
    解析器.add_argument("--间隔", type=float, default=0.2, help="循环读取间隔,单位秒")
    解析器.add_argument("--最大读取", type=int, default=单次最多读取, help="单次最大读取字节数")
    解析器.add_argument("--读版本", action="store_true", help="启动时发送 $PQTMVERNO*58 查询版本")
    参数 = 解析器.parse_args()

    单次最多读取 = max(1, min(4096, 参数.最大读取))

    print("LC29H DA I2C 中文寄存器读取")
    print(f"I2C 总线:{参数.总线}")
    print("地址:0x50=配置命令  0x54=读取数据  0x58=写入数据")
    print("寄存器:0x0008=可读长度  0x2000=读取数据  0x0004=接收剩余  0x1000=写入数据")
    print("按 Ctrl+C 退出\n")

    文本缓存 = bytearray()

    try:
        with SMBus(参数.总线) as 总线:
            唤醒或恢复一次(总线)

            if 参数.读版本:
                print("正在发送版本查询命令...")
                写入模块数据(总线, b"$PQTMVERNO*58\r\n")

            while True:
                可读长度 = 读取模块可读长度(总线)
                if 可读长度 > 0:
                    数据 = 读取模块数据(总线, 可读长度)
                    if 数据:
                        打印NMEA文本(数据, 文本缓存)
                time.sleep(参数.间隔)

    except KeyboardInterrupt:
        print("\n已退出")
    except OSError as 错误:
        print(f"I2C 通信失败:{错误}")
        print("请检查:")
        print("1. i2cdetect -y 1 是否能看到 0x50")
        print("2. 模块 I2C 固件是否开启")
        print("3. SDA SCL GND 和电平是否正确")
        sys.exit(1)


if __name__ == "__main__":
    主程序()

ZED-F9P

复制代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZED-F9P GPS-RTK HAT I2C/DDC 寄存器形式读取程序

硬件默认 I2C 地址:0x42

u-blox DDC(I2C) 常用寄存器:
  0xFD:可读取数据字节数高 8 位
  0xFE:可读取数据字节数低 8 位
  0xFF:数据流寄存器,读出 NMEA/UBX 数据,也可写入 UBX 命令

运行:
  sudo raspi-config 里开启 I2C
  sudo apt install -y python3-smbus i2c-tools
  i2cdetect -y 1
  python3 i2czed.py
"""

import argparse
import sys
import time


try:
    from smbus2 import SMBus
except ImportError:
    try:
        from smbus import SMBus
    except ImportError:
        print("未找到 smbus2 或 smbus,请先安装:sudo apt install -y python3-smbus")
        sys.exit(1)


默认总线号 = 1
默认设备地址 = 0x42
寄存器_可读高位 = 0xFD
寄存器_可读低位 = 0xFE
寄存器_数据流 = 0xFF
单次最多读取 = 32


def 读寄存器(总线, 地址, 寄存器):
    """读取一个 8 位寄存器。"""
    return 总线.read_byte_data(地址, 寄存器)


def 写寄存器数据块(总线, 地址, 寄存器, 数据):
    """向指定寄存器写入一组字节。"""
    总线.write_i2c_block_data(地址, 寄存器, list(数据))


def 读取可用字节数(总线, 地址):
    """通过 0xFD/0xFE 两个寄存器读取接收缓冲区字节数。"""
    高位 = 读寄存器(总线, 地址, 寄存器_可读高位)
    低位 = 读寄存器(总线, 地址, 寄存器_可读低位)
    可用 = (高位 << 8) | 低位

    # u-blox 在没有数据时可能返回 0xFFFF,这里按 0 处理。
    if 可用 == 0xFFFF:
        return 0
    return 可用


def 读取数据流(总线, 地址, 数量):
    """从 0xFF 数据流寄存器连续读取指定数量字节。"""
    结果 = bytearray()
    剩余 = max(0, 数量)

    while 剩余 > 0:
        本次 = min(单次最多读取, 剩余)
        数据 = 总线.read_i2c_block_data(地址, 寄存器_数据流, 本次)
        结果.extend(数据)
        剩余 -= 本次

    return bytes(结果)


def 计算_ubx校验(数据):
    """计算 UBX 协议 CK_A、CK_B 校验。"""
    校验甲 = 0
    校验乙 = 0
    for 字节 in 数据:
        校验甲 = (校验甲 + 字节) & 0xFF
        校验乙 = (校验乙 + 校验甲) & 0xFF
    return 校验甲, 校验乙


def 发送_ubx命令(总线, 地址, 类别, 编号, 负载=b""):
    """通过 0xFF 数据流寄存器发送 UBX 命令。"""
    长度 = len(负载)
    消息体 = bytes([类别, 编号, 长度 & 0xFF, (长度 >> 8) & 0xFF]) + bytes(负载)
    校验甲, 校验乙 = 计算_ubx校验(消息体)
    完整消息 = b"\xB5\x62" + 消息体 + bytes([校验甲, 校验乙])

    位置 = 0
    while 位置 < len(完整消息):
        分片 = 完整消息[位置:位置 + 单次最多读取]
        写寄存器数据块(总线, 地址, 寄存器_数据流, 分片)
        位置 += len(分片)
        time.sleep(0.01)


def 请求版本信息(总线, 地址):
    """发送 UBX-MON-VER,请求模块版本信息。"""
    发送_ubx命令(总线, 地址, 0x0A, 0x04)


def 打印文本数据(数据, 文本缓存):
    """把数据流里的 NMEA 文本按行打印,非文本字节会自动跳过。"""
    文本缓存.extend(数据)

    while b"\n" in 文本缓存:
        一行, _, 剩余 = 文本缓存.partition(b"\n")
        文本缓存[:] = 剩余
        文本 = 一行.strip().decode("ascii", errors="ignore")
        if 文本:
            print(文本)


def 主程序():
    解析器 = argparse.ArgumentParser(description="ZED-F9P I2C/DDC 寄存器形式读取程序")
    解析器.add_argument("--总线", type=int, default=默认总线号, help="I2C 总线号,树莓派一般是 1")
    解析器.add_argument("--地址", type=lambda x: int(x, 0), default=默认设备地址, help="I2C 地址,默认 0x42")
    解析器.add_argument("--间隔", type=float, default=0.2, help="读取间隔秒数")
    解析器.add_argument("--读版本", action="store_true", help="启动时发送 UBX-MON-VER 请求版本信息")
    参数 = 解析器.parse_args()

    print("ZED-F9P I2C/DDC 寄存器读取")
    print(f"I2C 总线:{参数.总线}")
    print(f"I2C 地址:0x{参数.地址:02X}")
    print("寄存器:0xFD/0xFE=可读字节数,0xFF=数据流")
    print("按 Ctrl+C 退出\n")

    文本缓存 = bytearray()

    try:
        with SMBus(参数.总线) as 总线:
            可用 = 读取可用字节数(总线, 参数.地址)
            print(f"模块通信正常,当前可读字节数:{可用}")

            if 参数.读版本:
                print("正在发送 UBX-MON-VER 版本请求...")
                请求版本信息(总线, 参数.地址)

            while True:
                可用 = 读取可用字节数(总线, 参数.地址)
                if 可用 > 0:
                    数据 = 读取数据流(总线, 参数.地址, min(可用, 512))
                    打印文本数据(数据, 文本缓存)
                time.sleep(参数.间隔)

    except KeyboardInterrupt:
        print("\n已退出")
    except OSError as 错误:
        print(f"I2C 通信失败:{错误}")
        print("请检查:")
        print("1. 树莓派是否已开启 I2C")
        print("2. 是否能用 i2cdetect -y 1 看到 0x42")
        print("3. HAT 供电和排针连接是否正常")
        sys.exit(1)


if __name__ == "__main__":
    主程序()
相关推荐
*neverGiveUp*1 小时前
Django ORM
后端·python·django
普通网友1 小时前
【python】pyspark.errors.exceptions.base.PySparkRuntimeError [JAVA_GATEWAY_EXITED] Java gateway proce
java·python·gateway
zavoryn1 小时前
Python 面试高频:装饰器、迭代器、生成器和上下文管理器一次讲清
开发语言·python·面试
YJlio1 小时前
OpenClaw v2026.5.26-beta.1 / beta.2 预发布解读:Gateway 加速、transcript 路径统一、多通道修复、语音增强与安装更新链路加固
人工智能·windows·python·ui·缓存·gateway·outlook
许彰午10 小时前
14_Java泛型完全指南
java·windows·python
广州灵眸科技有限公司10 小时前
瑞芯微RV1126B开发板(EASY-EAI-PI2) Easy-Eai编译环境准备与更新
服务器·前端·人工智能·python·深度学习
TechWayfarer10 小时前
IP风险等级评估接入实战:金融信贷如何用IP画像辅助风控审核
python·tcp/ip·安全·金融
Esaka_Forever10 小时前
uv init 完整用法(Python 最快包管理器)
服务器·python·uv
神仙别闹13 小时前
基于Python + SQL server 实现(GUI)原神圣遗物管理与角色数值模拟系统
java·数据库·python