通过I2C与u-blox GPS通信的Golang实现方法

通过I2C与u-blox GPS通信的Golang实现方法 我一直在寻找通过I2C总线与u-blox M8系列GPS接收器进行二进制通信的Golang实现方案,但至今没有找到合适的。

我已经编写了一个使用USB端口和NMEA消息的实现,但担心这对我的应用来说会有些慢。

在阅读了u-blox的规格说明后(我使用的GPS是RY836AI板卡的一部分),我的印象是这种实现不会像普通的I2C总线通信那样简单,特别是如果使用二进制通信而不是冗长的NMEA消息。

在我将快速流逝的青春耗费在这个项目之前,我向各位求助。有人愿意接手吗?

致以最诚挚的问候

4 回复

我正在使用 periph.io 项目进行 I2C 通信,既用于树莓派也在某种程度上用于 Rock64,因为我认为其背后的开发者非常致力于高质量和良好的架构。

iNav 项目确实很有趣,stratux 的实现也同样出色。

感谢!

更多关于通过I2C与u-blox GPS通信的Golang实现方法的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


看起来你可以在树莓派上使用这个库通过I2C进行通信。

我之前没有使用过这个库,但它是搜索"golang i2c"时的第一个结果 🙂

iNav(和Betaflight)是用于遥控飞行器(如四轴飞行器和飞翼)的飞行控制软件项目。你应该能够了解它们如何与u-blox GPS设备通信,并在Go中实现相应功能。参考iNav源代码:https://github.com/iNavFlight/inav/blob/912d131556302cf6ea67d59308e43dfe65ca8026/src/main/drivers/gps_i2cnav.c

你的平台是什么?如果你使用的是类似树莓派、Beaglebone或其他类似的"小型"开发板,可能会比较容易,因为你拥有硬件I2C接口,并且已经存在一些现成的代码。如果你使用的是普通PC,就需要一个硬件接口。最终FTDI应该有类似的产品,可以通过USB端口连接。然后他们提供驱动程序和示例,但你可能需要制作Golang绑定… (如果你在谷歌搜索"golang ftdi",会有几个结果,所以可能已经有现成的解决方案…)

[编辑:] 如果你使用的是Linux(我使用的是Windows),似乎会更容易:https://dave.cheney.net/2014/08/03/tinyterm-a-silly-terminal-emulator-written-in-go

以下是一个基于Go语言通过I2C与u-blox M8系列GPS接收器进行二进制通信的示例实现。该实现使用periph.io库来处理I2C通信,并遵循u-blox二进制协议(UBX协议)的结构。假设GPS设备已正确连接到I2C总线,且地址为0x42(u-blox设备的常见I2C地址)。

首先,确保安装必要的依赖:

go get periph.io/x/conn/v3/i2c
go get periph.io/x/host/v3

然后,创建Go文件(例如main.go)并实现以下代码:

package main

import (
	"encoding/binary"
	"fmt"
	"log"
	"time"

	"periph.io/x/conn/v3/i2c"
	"periph.io/x/conn/v3/i2c/i2creg"
	"periph.io/x/host/v3"
)

const (
	i2cBus      = "1"           // I2C总线编号,根据系统调整(如树莓派上常用1)
	gpsAddress  = 0x42          // u-blox GPS的I2C地址
	ubxSync1    = 0xB5          // UBX协议同步字符1
	ubxSync2    = 0x62          // UBX协议同步字符2
)

// UBXMessage 定义UBX消息结构
type UBXMessage struct {
	Class    uint8
	ID       uint8
	Length   uint16
	Payload  []byte
	Checksum [2]byte
}

// calcChecksum 计算UBX消息的校验和
func calcChecksum(class, id uint8, length uint16, payload []byte) [2]byte {
	var ckA, ckB uint8
	ckA += class
	ckB += ckA
	ckA += id
	ckB += ckA
	ckA += uint8(length & 0xFF)
	ckB += ckA
	ckA += uint8(length >> 8)
	ckB += ckA
	for _, b := range payload {
		ckA += b
		ckB += ckA
	}
	return [2]byte{ckA, ckB}
}

// sendUBXMessage 通过I2C发送UBX消息
func sendUBXMessage(dev *i2c.Dev, msg UBXMessage) error {
	// 构建消息字节序列
	buf := make([]byte, 6+len(msg.Payload)+2) // 同步头(2) + 类(1) + ID(1) + 长度(2) + 载荷 + 校验和(2)
	buf[0] = ubxSync1
	buf[1] = ubxSync2
	buf[2] = msg.Class
	buf[3] = msg.ID
	binary.LittleEndian.PutUint16(buf[4:6], msg.Length)
	copy(buf[6:6+len(msg.Payload)], msg.Payload)
	checksum := calcChecksum(msg.Class, msg.ID, msg.Length, msg.Payload)
	buf[6+len(msg.Payload)] = checksum[0]
	buf[6+len(msg.Payload)+1] = checksum[1]

	// 通过I2C写入消息
	_, err := dev.Write(buf)
	return err
}

// readUBXMessage 从I2C读取UBX消息
func readUBXMessage(dev *i2c.Dev) (*UBXMessage, error) {
	// 先读取头部以确定消息长度
	header := make([]byte, 6)
	if _, err := dev.Read(header); err != nil {
		return nil, err
	}
	// 检查同步字符
	if header[0] != ubxSync1 || header[1] != ubxSync2 {
		return nil, fmt.Errorf("invalid sync characters")
	}
	class := header[2]
	id := header[3]
	length := binary.LittleEndian.Uint16(header[4:6])

	// 读取载荷和校验和
	payloadAndChecksum := make([]byte, length+2)
	if _, err := dev.Read(payloadAndChecksum); err != nil {
		return nil, err
	}
	payload := payloadAndChecksum[:length]
	checksum := [2]byte{payloadAndChecksum[length], payloadAndChecksum[length+1]}

	// 验证校验和
	expectedChecksum := calcChecksum(class, id, length, payload)
	if checksum != expectedChecksum {
		return nil, fmt.Errorf("checksum mismatch")
	}

	return &UBXMessage{
		Class:   class,
		ID:      id,
		Length:  length,
		Payload: payload,
	}, nil
}

func main() {
	// 初始化主机驱动
	if _, err := host.Init(); err != nil {
		log.Fatal(err)
	}

	// 打开I2C总线
	bus, err := i2creg.Open(i2cBus)
	if err != nil {
		log.Fatal(err)
	}
	defer bus.Close()

	// 创建设备实例
	dev := &i2c.Dev{Bus: bus, Addr: gpsAddress}

	// 示例:发送UBX-CFG-PRT命令配置I2C端口(具体载荷根据u-blox文档构建)
	// 这里使用一个示例消息,实际使用时参考u-blox协议文档
	msg := UBXMessage{
		Class:  0x06, // CFG类
		ID:     0x00, // PRT消息ID
		Length: 20,   // 假设载荷长度为20字节
		Payload: make([]byte, 20), // 实际载荷需根据配置填充
	}
	if err := sendUBXMessage(dev, msg); err != nil {
		log.Fatal("发送消息失败:", err)
	}
	fmt.Println("UBX消息发送成功")

	// 等待并读取响应
	time.Sleep(100 * time.Millisecond)
	response, err := readUBXMessage(dev)
	if err != nil {
		log.Fatal("读取消息失败:", err)
	}
	fmt.Printf("收到响应: Class=0x%02X, ID=0x%02X, Length=%d\n", response.Class, response.ID, response.Length)
}

说明:

  • I2C配置:代码使用periph.io库处理I2C通信,需根据实际硬件调整总线编号(如树莓派上常用"1")。
  • UBX协议:实现了UBX二进制消息的构建、发送和接收,包括同步字符、校验和计算。校验和计算遵循u-blox规范。
  • 消息示例:示例中发送了一个UBX-CFG-PRT消息(端口配置),实际使用时需参考u-blox M8协议文档填充正确的载荷数据。
  • 错误处理:包含基本的错误检查,如校验和验证。

此代码提供了一个基础框架,您需要根据具体需求调整消息类、ID和载荷。u-blox协议文档是必读的,以正确配置GPS模块。

回到顶部