mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-07-26 10:38:41 +00:00
936a255df9
* Add support for CEVA BNO08x 9DoF sensor. Also includes implementation of CEVA SH-2 and SHTP protocols.
* Replace machine.I2C with drivers.I2C interface to remove dependency on machine package
* Replace Pin functionality with that provided by tinygo.org/x/drivers/internal/pin
* Unexport fields on SensorValue and replace with accessor methods. Add check for correct SensorID validation
* Add example to smoketest.sh
* Change build target for smoketest to match development environment.. Probably not important, but matches reality.
* Fix decoding of some sensor data: Step Counter, Tap Detector, Flip Detector. These are experimental.
* Refactor to allow SPI/UART etc. SPI is currently under development, but is omitted from this commit.
Example code has been moved to i2c subdirectory.
This commit introduces some major refactoring changes. It introduces a "Buser" interface and tries to remove any I2C specific code from the core. It still retains a couple of I2C specific fields in the "Config" struct ("Address" and "ReadChunk") but they are ignored in the as yet uncommited SPI code.
* Fix CRLF -> LF for gofmt
* Update smoketest to point to new example file
174 lines
4.2 KiB
Go
174 lines
4.2 KiB
Go
package bno08x
|
|
|
|
import (
|
|
"time"
|
|
|
|
"tinygo.org/x/drivers"
|
|
"tinygo.org/x/drivers/internal/pin"
|
|
)
|
|
|
|
// I2CConfig holds I2C-specific configuration options.
|
|
type I2CConfig struct {
|
|
// Address is the I2C address (default: 0x4A).
|
|
Address uint16
|
|
|
|
// ResetPin is the optional hardware reset pin.
|
|
ResetPin pin.OutputFunc
|
|
|
|
// ReadChunk is the I2C read chunk size (default: 32 bytes).
|
|
ReadChunk int
|
|
}
|
|
|
|
const (
|
|
// DefaultAddress is the default I2C address.
|
|
DefaultAddress = 0x4A
|
|
)
|
|
|
|
// NewI2C creates a new BNO08x device using I2C communication.
|
|
func NewI2C(bus drivers.I2C) *Device {
|
|
return &Device{
|
|
bus: &I2CBus{
|
|
wire: bus,
|
|
address: DefaultAddress,
|
|
readChunk: i2cDefaultChunk,
|
|
},
|
|
}
|
|
}
|
|
|
|
// I2CBus implements the Buser interface for I2C communication.
|
|
type I2CBus struct {
|
|
wire drivers.I2C
|
|
address uint16
|
|
readChunk int
|
|
scratch []byte
|
|
header [shtpHeaderLength]byte
|
|
}
|
|
|
|
// configure sets up the I2C bus with the specified address and chunk size.
|
|
func (b *I2CBus) configure(address uint16, readChunk int) error {
|
|
if address != 0 {
|
|
b.address = address
|
|
}
|
|
if readChunk > 0 {
|
|
b.readChunk = readChunk
|
|
}
|
|
|
|
chunk := b.readChunk
|
|
if chunk < shtpHeaderLength {
|
|
chunk = shtpHeaderLength
|
|
}
|
|
b.scratch = make([]byte, chunk)
|
|
|
|
return nil
|
|
}
|
|
|
|
// read reads data from the I2C bus.
|
|
func (b *I2CBus) read(target []byte) (int, uint32, error) {
|
|
// Read SHTP header (4 bytes) to get packet length
|
|
// Use pre-allocated header buffer to avoid allocations
|
|
err := b.wire.Tx(b.address, nil, b.header[:])
|
|
if err != nil {
|
|
return 0, 0, err
|
|
}
|
|
|
|
// Parse packet length from header
|
|
packetLen := uint16(b.header[0]) | (uint16(b.header[1]) << 8)
|
|
|
|
// Check if continuation bit is set (0x8000)
|
|
// This means no data is available yet
|
|
if packetLen&continueMask != 0 {
|
|
return 0, 0, nil
|
|
}
|
|
|
|
// No continuation bit, check for actual data
|
|
if packetLen == 0 {
|
|
return 0, 0, nil
|
|
}
|
|
|
|
if int(packetLen) > len(target) {
|
|
return 0, 0, errBufferTooSmall
|
|
}
|
|
|
|
// Now read the full packet in chunks, re-reading the header in first chunk
|
|
// This follows Arduino's approach: initial header read is just to get size,
|
|
// actual packet data (including header) is read in the loop
|
|
cargoRemaining := int(packetLen)
|
|
offset := 0
|
|
firstRead := true
|
|
|
|
for cargoRemaining > 0 {
|
|
var request int
|
|
if firstRead {
|
|
// First read: get the full packet including header (up to chunkSize)
|
|
request = b.readChunk
|
|
if request > cargoRemaining {
|
|
request = cargoRemaining
|
|
}
|
|
} else {
|
|
// Subsequent reads: each chunk has a 4-byte header we need to skip
|
|
request = b.readChunk
|
|
if request > cargoRemaining+shtpHeaderLength {
|
|
request = cargoRemaining + shtpHeaderLength
|
|
}
|
|
}
|
|
|
|
// Ensure scratch buffer is large enough
|
|
if request > len(b.scratch) {
|
|
b.scratch = make([]byte, request)
|
|
}
|
|
buf := b.scratch[:request]
|
|
|
|
// Read chunk
|
|
err = b.wire.Tx(b.address, nil, buf)
|
|
if err != nil {
|
|
return 0, 0, err
|
|
}
|
|
|
|
var cargoRead int
|
|
if firstRead {
|
|
// First read: copy everything including header
|
|
cargoRead = request
|
|
copy(target[offset:], buf[:cargoRead])
|
|
firstRead = false
|
|
} else {
|
|
// Subsequent reads: skip the 4-byte header
|
|
cargoRead = request - shtpHeaderLength
|
|
copy(target[offset:], buf[shtpHeaderLength:shtpHeaderLength+cargoRead])
|
|
}
|
|
|
|
offset += cargoRead
|
|
cargoRemaining -= cargoRead
|
|
}
|
|
|
|
// Extract timestamp from the header in the target buffer
|
|
timestamp := uint32(target[2]) | (uint32(target[3]) << 8)
|
|
|
|
return int(packetLen), timestamp, nil
|
|
}
|
|
|
|
// write sends data over the I2C bus.
|
|
func (b *I2CBus) write(data []byte) error {
|
|
return b.wire.Tx(b.address, data, nil)
|
|
}
|
|
|
|
// softReset sends a soft reset command via I2C.
|
|
func (b *I2CBus) softReset() error {
|
|
// Send soft reset packet via I2C as per Adafruit implementation
|
|
// Format: [length_low, length_high, channel, sequence, command]
|
|
// This is: 5 bytes total, channel 1 (executable), command 1 (reset)
|
|
softResetPacket := []byte{5, 0, 1, 0, 1}
|
|
|
|
// Try up to 5 times
|
|
var err error
|
|
for i := 0; i < 5; i++ {
|
|
err = b.wire.Tx(b.address, softResetPacket, nil)
|
|
if err == nil {
|
|
// Success - wait for sensor to process reset
|
|
time.Sleep(300 * time.Millisecond)
|
|
return nil
|
|
}
|
|
time.Sleep(30 * time.Millisecond)
|
|
}
|
|
return err
|
|
}
|