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
257 lines
6.1 KiB
Go
257 lines
6.1 KiB
Go
// Package bno08x provides a TinyGo driver for the Adafruit BNO08x 9-DOF IMU sensors.
|
|
//
|
|
// This driver implements the CEVA SH-2 protocol over the SHTP transport layer,
|
|
// providing access to orientation, motion, and environmental sensors.
|
|
//
|
|
// Datasheet: https://www.ceva-ip.com/wp-content/uploads/BNO080_085-Datasheet.pdf
|
|
package bno08x
|
|
|
|
import (
|
|
"time"
|
|
|
|
"tinygo.org/x/drivers/internal/pin"
|
|
)
|
|
|
|
// Buser is the interface that wraps I2C or SPI bus operations.
|
|
type Buser interface {
|
|
configure(address uint16, readChunk int) error
|
|
read(target []byte) (int, uint32, error)
|
|
write(data []byte) error
|
|
softReset() error
|
|
}
|
|
|
|
// Device represents a BNO08x sensor device.
|
|
type Device struct {
|
|
bus Buser
|
|
resetPin pin.OutputFunc
|
|
|
|
hal *hal
|
|
shtp *shtp
|
|
sh2 *sh2Protocol
|
|
|
|
queue [8]SensorValue
|
|
queueHead int
|
|
queueTail int
|
|
queueCount int
|
|
|
|
productIDs ProductIDs
|
|
lastReset bool
|
|
}
|
|
|
|
// Config holds configuration options for the device.
|
|
type Config struct {
|
|
// Address is the I2C address (used only for I2C bus).
|
|
Address uint16
|
|
|
|
// ResetPin is the optional hardware reset pin.
|
|
ResetPin pin.OutputFunc
|
|
|
|
// ReadChunk is the I2C read chunk size (used only for I2C bus).
|
|
ReadChunk int
|
|
|
|
// StartupDelay is the delay after reset (default: 100ms).
|
|
StartupDelay time.Duration
|
|
}
|
|
|
|
// Configure initializes the sensor and prepares it for use.
|
|
func (d *Device) Configure(cfg Config) error {
|
|
// Configure bus-specific settings
|
|
if err := d.bus.configure(cfg.Address, cfg.ReadChunk); err != nil {
|
|
return err
|
|
}
|
|
|
|
if cfg.ResetPin != nil {
|
|
d.resetPin = cfg.ResetPin
|
|
}
|
|
if cfg.StartupDelay <= 0 {
|
|
cfg.StartupDelay = 100 * time.Millisecond
|
|
}
|
|
|
|
d.hal = newHAL(d)
|
|
d.shtp = newSHTP(d.hal)
|
|
d.sh2 = newSH2Protocol(d)
|
|
|
|
d.queueHead = 0
|
|
d.queueTail = 0
|
|
d.queueCount = 0
|
|
d.productIDs = ProductIDs{}
|
|
d.lastReset = false
|
|
|
|
if err := d.hal.open(); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Now that handlers are registered, perform reset
|
|
// Try hardware reset first if available
|
|
if d.resetPin != nil {
|
|
d.hardwareReset()
|
|
time.Sleep(cfg.StartupDelay)
|
|
} else {
|
|
// No hardware reset pin - try soft reset via bus
|
|
if err := d.bus.softReset(); err != nil {
|
|
// If that fails, try soft reset via SHTP protocol
|
|
_ = d.sh2.softReset()
|
|
time.Sleep(50 * time.Millisecond)
|
|
}
|
|
}
|
|
|
|
// Wait for reset notification by actively polling
|
|
// The sensor should send reset complete message shortly after reset
|
|
deadline := time.Now().Add(1000 * time.Millisecond)
|
|
pollCount := 0
|
|
for time.Now().Before(deadline) {
|
|
pollCount++
|
|
if err := d.service(); err != nil {
|
|
// Ignore errors during initial polling - sensor might not be ready
|
|
time.Sleep(1 * time.Millisecond)
|
|
continue
|
|
}
|
|
if d.lastReset {
|
|
break
|
|
}
|
|
time.Sleep(1 * time.Millisecond)
|
|
}
|
|
|
|
if !d.lastReset {
|
|
return errTimeout
|
|
}
|
|
|
|
// NOTE: We intentionally skip the Initialize command (sh2_initialize)
|
|
// Testing revealed that sending the Initialize command (0xF2 0x00 0x04 0x01...)
|
|
// prevents the BNO08x from sending sensor reports on channel 3.
|
|
// The sensor works correctly without this command after a soft reset.
|
|
// The Arduino library likely works because it does a hardware reset which
|
|
// may put the sensor in a different state, or their initialization sequence
|
|
// differs in a way that doesn't trigger this issue.
|
|
|
|
// Request product IDs
|
|
if err := d.sh2.requestProductIDs(); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Wait for product IDs with polling delay
|
|
deadline = time.Now().Add(500 * time.Millisecond)
|
|
for time.Now().Before(deadline) {
|
|
if err := d.service(); err != nil {
|
|
time.Sleep(10 * time.Millisecond)
|
|
continue
|
|
}
|
|
if d.productIDs.NumEntries > 0 {
|
|
break
|
|
}
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
|
|
if d.productIDs.NumEntries == 0 {
|
|
return errTimeout
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// EnableReport enables a specific sensor report at the given interval.
|
|
func (d *Device) EnableReport(id SensorID, intervalUs uint32) error {
|
|
err := d.sh2.enableReport(id, intervalUs)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Poll a few times to let the sensor process the command
|
|
// and potentially send acknowledgment
|
|
for i := 0; i < 10; i++ {
|
|
_ = d.service()
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetSensorConfig retrieves the current configuration for a sensor.
|
|
func (d *Device) GetSensorConfig(id SensorID) (SensorConfig, error) {
|
|
return d.sh2.getSensorConfig(id)
|
|
}
|
|
|
|
// SetSensorConfig sets the configuration for a sensor.
|
|
func (d *Device) SetSensorConfig(id SensorID, config SensorConfig) error {
|
|
return d.sh2.setSensorConfig(id, config)
|
|
}
|
|
|
|
// WasReset returns true if the sensor signaled a reset since the last call.
|
|
func (d *Device) WasReset() bool {
|
|
if d.lastReset {
|
|
d.lastReset = false
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// GetSensorEvent retrieves the next available sensor event if present.
|
|
func (d *Device) GetSensorEvent() (SensorValue, bool) {
|
|
if d.queueCount == 0 {
|
|
if err := d.service(); err != nil {
|
|
return SensorValue{}, false
|
|
}
|
|
if d.queueCount == 0 {
|
|
return SensorValue{}, false
|
|
}
|
|
}
|
|
|
|
value := d.queue[d.queueHead]
|
|
d.queueHead = (d.queueHead + 1) % len(d.queue)
|
|
d.queueCount--
|
|
|
|
return value, true
|
|
}
|
|
|
|
// ProductIDs returns the cached product identification information.
|
|
func (d *Device) ProductIDs() ProductIDs {
|
|
return d.productIDs
|
|
}
|
|
|
|
// Service processes pending sensor data.
|
|
// This is called automatically by GetSensorEvent but can be called manually
|
|
// for more control over timing.
|
|
func (d *Device) Service() error {
|
|
return d.service()
|
|
}
|
|
|
|
func (d *Device) enqueue(value SensorValue) {
|
|
next := (d.queueTail + 1) % len(d.queue)
|
|
if d.queueCount == len(d.queue) {
|
|
// Queue full, drop oldest
|
|
d.queueHead = (d.queueHead + 1) % len(d.queue)
|
|
d.queueCount--
|
|
}
|
|
d.queue[d.queueTail] = value
|
|
d.queueTail = next
|
|
d.queueCount++
|
|
}
|
|
|
|
func (d *Device) service() error {
|
|
if d.shtp == nil {
|
|
return nil
|
|
}
|
|
for {
|
|
processed, err := d.shtp.poll()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !processed {
|
|
break
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (d *Device) hardwareReset() {
|
|
if d.resetPin == nil {
|
|
return
|
|
}
|
|
d.resetPin.High()
|
|
time.Sleep(10 * time.Millisecond)
|
|
d.resetPin.Low()
|
|
time.Sleep(10 * time.Millisecond)
|
|
d.resetPin.High()
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|