mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-07-26 18:48:41 +00:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e472cf88c8 | |||
| 2a42fa7cbb | |||
| 5d96a56603 | |||
| f931ad44fb | |||
| 50778af656 | |||
| 936a255df9 | |||
| 9ed648f4a5 | |||
| fab688a701 | |||
| d4654668f2 | |||
| 253f8e3220 | |||
| c710cc8236 | |||
| 4b831af52b | |||
| 09fd01340b | |||
| 23833e69c7 | |||
| 744fda5eec | |||
| 027c91272e | |||
| 5847506ba6 | |||
| e35e6b8e13 | |||
| 408851a9f5 |
@@ -11,13 +11,12 @@ on:
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/tinygo-org/tinygo-dev:latest
|
||||
container:
|
||||
image: ghcr.io/tinygo-org/tinygo:latest
|
||||
options: --user root
|
||||
steps:
|
||||
- name: Work around CVE-2022-24765
|
||||
# We're not on a multi-user machine, so this is safe.
|
||||
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v6
|
||||
- name: TinyGo version check
|
||||
run: tinygo version
|
||||
- name: Enforce Go Formatted Code
|
||||
@@ -25,4 +24,6 @@ jobs:
|
||||
- name: Run unit tests
|
||||
run: make unit-test
|
||||
- name: Run build and smoke tests
|
||||
run: make smoke-test
|
||||
run: |
|
||||
go env -w GOFLAGS=-buildvcs=false
|
||||
make smoke-test
|
||||
|
||||
@@ -1,3 +1,56 @@
|
||||
0.34.0
|
||||
---
|
||||
- **core**
|
||||
- add regmap package to facilitate heapless driver development
|
||||
- PinInput+PinOutput HAL (#753, reloaded) (#795)
|
||||
- Add Device8I2C/SPI types and their logic (#801)
|
||||
|
||||
- **new devices**
|
||||
- **bno8x**
|
||||
- Add support for CEVA BNO08x 9DoF sensor (#809)
|
||||
- **hineyhsc**
|
||||
- Add Honeywell HSC TruStability SPI+I2C pressure sensor driver (#799)
|
||||
- **p25q16h**
|
||||
- added support for P25Q16H flash chip for xiao-ble target
|
||||
- **si5351**
|
||||
- add support for si5351 (#810)
|
||||
- **w25q80dv**
|
||||
- added support for W25Q80DV flash chip for xiao-ble target
|
||||
- **w5500**
|
||||
- initial version the driver (#788)
|
||||
|
||||
- **enhancements**
|
||||
- **ds3231**
|
||||
- DS3231 Alarm features (#805)
|
||||
- **general**
|
||||
- add simplest driver ports
|
||||
- **lis3dh**
|
||||
- add Update and Acceleration calls
|
||||
- use correct error handling and make configurable
|
||||
- **lsm9ds1**
|
||||
- avoid unnecessary heap allocations
|
||||
- **pixel**
|
||||
- add Grayscale2bit color (#817)
|
||||
- **scd4x**
|
||||
- add support for SCD41 single-shot measurements
|
||||
- remove dead code
|
||||
- update package to use standard methods
|
||||
- **si5351**
|
||||
- add many missing functions needed for convenient use.
|
||||
- **ssd1xxx**
|
||||
- break dependency from machine package (#812)
|
||||
- **test**
|
||||
- Add TestImageRGB888 and TestImageRGB555
|
||||
|
||||
- **bugfixes**
|
||||
- **quadrature**
|
||||
- add RP2350 to quadrature_interrupt.go
|
||||
- **pixel**
|
||||
- correct logic error in image size checks in pixel's tests
|
||||
- correct logic error in image size checks in pixel's tests (Monochrome)
|
||||
- correct RGB555 to RGBA conversion logic
|
||||
|
||||
|
||||
0.33.0
|
||||
---
|
||||
- **new devices**
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
[](https://pkg.go.dev/tinygo.org/x/drivers) [](https://github.com/tinygo-org/drivers/actions/workflows/build.yml)
|
||||
|
||||
|
||||
This package provides a collection of over 100 different hardware drivers for devices such as sensors, displays, wireless adaptors, and actuators, that can be used together with [TinyGo](https://tinygo.org).
|
||||
This package provides a collection of over 130 different hardware drivers for devices such as sensors, displays, wireless adaptors, and actuators, that can be used together with [TinyGo](https://tinygo.org).
|
||||
|
||||
For the complete list, please see:
|
||||
https://tinygo.org/docs/reference/devices/
|
||||
|
||||
+7
-3
@@ -5,9 +5,10 @@ package apa102 // import "tinygo.org/x/drivers/apa102"
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"machine"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
"tinygo.org/x/drivers/internal/legacy"
|
||||
"tinygo.org/x/drivers/internal/pin"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -37,8 +38,11 @@ func New(b drivers.SPI) *Device {
|
||||
|
||||
// NewSoftwareSPI returns a new APA102 driver that will use a software based
|
||||
// implementation of the SPI protocol.
|
||||
func NewSoftwareSPI(sckPin, sdoPin machine.Pin, delay uint32) *Device {
|
||||
return New(&bbSPI{SCK: sckPin, SDO: sdoPin, Delay: delay})
|
||||
func NewSoftwareSPI(sckPin, sdoPin pin.Output, delay uint32) *Device {
|
||||
return New(&bbSPI{SCK: sckPin.Set, SDO: sdoPin.Set, Delay: delay, configurePins: func() {
|
||||
legacy.ConfigurePinOut(sckPin)
|
||||
legacy.ConfigurePinOut(sdoPin)
|
||||
}})
|
||||
}
|
||||
|
||||
// WriteColors writes the given RGBA color slice out using the APA102 protocol.
|
||||
|
||||
+12
-6
@@ -1,6 +1,9 @@
|
||||
package apa102
|
||||
|
||||
import "machine"
|
||||
import (
|
||||
"tinygo.org/x/drivers/internal/legacy"
|
||||
"tinygo.org/x/drivers/internal/pin"
|
||||
)
|
||||
|
||||
// bbSPI is a dumb bit-bang implementation of SPI protocol that is hardcoded
|
||||
// to mode 0 and ignores trying to receive data. Just enough for the APA102.
|
||||
@@ -8,15 +11,18 @@ import "machine"
|
||||
// most purposes other than the APA102 package. It might be desirable to make
|
||||
// this more generic and include it in the TinyGo "machine" package instead.
|
||||
type bbSPI struct {
|
||||
SCK machine.Pin
|
||||
SDO machine.Pin
|
||||
Delay uint32
|
||||
SCK pin.OutputFunc
|
||||
SDO pin.OutputFunc
|
||||
Delay uint32
|
||||
configurePins func()
|
||||
}
|
||||
|
||||
// Configure sets up the SCK and SDO pins as outputs and sets them low
|
||||
func (s *bbSPI) Configure() {
|
||||
s.SCK.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
s.SDO.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
if s.configurePins == nil {
|
||||
panic(legacy.ErrConfigBeforeInstantiated)
|
||||
}
|
||||
s.configurePins()
|
||||
s.SCK.Low()
|
||||
s.SDO.Low()
|
||||
if s.Delay == 0 {
|
||||
|
||||
+31
-24
@@ -1,31 +1,36 @@
|
||||
package bmi160
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
"tinygo.org/x/drivers/internal/legacy"
|
||||
"tinygo.org/x/drivers/internal/pin"
|
||||
)
|
||||
|
||||
// DeviceSPI is the SPI interface to a BMI160 accelerometer/gyroscope. There is
|
||||
// also an I2C interface, but it is not yet supported.
|
||||
type DeviceSPI struct {
|
||||
// Chip select pin
|
||||
CSB machine.Pin
|
||||
csb pin.OutputFunc
|
||||
|
||||
buf [7]byte
|
||||
|
||||
// SPI bus (requires chip select to be usable).
|
||||
Bus drivers.SPI
|
||||
bus drivers.SPI
|
||||
configurePins func()
|
||||
}
|
||||
|
||||
// NewSPI returns a new device driver. The pin and SPI interface are not
|
||||
// touched, provide a fully configured SPI object and call Configure to start
|
||||
// using this device.
|
||||
func NewSPI(csb machine.Pin, spi drivers.SPI) *DeviceSPI {
|
||||
func NewSPI(csb pin.Output, spi drivers.SPI) *DeviceSPI {
|
||||
return &DeviceSPI{
|
||||
CSB: csb, // chip select
|
||||
Bus: spi,
|
||||
csb: csb.Set, // chip select
|
||||
bus: spi,
|
||||
configurePins: func() {
|
||||
legacy.ConfigurePinOut(csb)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,9 +38,11 @@ func NewSPI(csb machine.Pin, spi drivers.SPI) *DeviceSPI {
|
||||
// configures the BMI160, but it does not configure the SPI interface (it is
|
||||
// assumed to be up and running).
|
||||
func (d *DeviceSPI) Configure() error {
|
||||
d.CSB.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
d.CSB.High()
|
||||
|
||||
if d.configurePins == nil {
|
||||
return legacy.ErrConfigBeforeInstantiated
|
||||
}
|
||||
d.configurePins()
|
||||
d.csb.High()
|
||||
// The datasheet recommends doing a register read from address 0x7F to get
|
||||
// SPI communication going:
|
||||
// > If CSB sees a rising edge after power-up, the BMI160 interface switches
|
||||
@@ -86,9 +93,9 @@ func (d *DeviceSPI) ReadTemperature() (temperature int32, err error) {
|
||||
data[0] = 0x80 | reg_TEMPERATURE_0
|
||||
data[1] = 0
|
||||
data[2] = 0
|
||||
d.CSB.Low()
|
||||
err = d.Bus.Tx(data, data)
|
||||
d.CSB.High()
|
||||
d.csb.Low()
|
||||
err = d.bus.Tx(data, data)
|
||||
d.csb.High()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -123,9 +130,9 @@ func (d *DeviceSPI) ReadAcceleration() (x int32, y int32, z int32, err error) {
|
||||
for i := 1; i < len(data); i++ {
|
||||
data[i] = 0
|
||||
}
|
||||
d.CSB.Low()
|
||||
err = d.Bus.Tx(data, data)
|
||||
d.CSB.High()
|
||||
d.csb.Low()
|
||||
err = d.bus.Tx(data, data)
|
||||
d.csb.High()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -153,9 +160,9 @@ func (d *DeviceSPI) ReadRotation() (x int32, y int32, z int32, err error) {
|
||||
for i := 1; i < len(data); i++ {
|
||||
data[i] = 0
|
||||
}
|
||||
d.CSB.Low()
|
||||
err = d.Bus.Tx(data, data)
|
||||
d.CSB.High()
|
||||
d.csb.Low()
|
||||
err = d.bus.Tx(data, data)
|
||||
d.csb.High()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -201,9 +208,9 @@ func (d *DeviceSPI) readRegister(address uint8) uint8 {
|
||||
data := d.buf[:2]
|
||||
data[0] = 0x80 | address
|
||||
data[1] = 0
|
||||
d.CSB.Low()
|
||||
d.Bus.Tx(data, data)
|
||||
d.CSB.High()
|
||||
d.csb.Low()
|
||||
d.bus.Tx(data, data)
|
||||
d.csb.High()
|
||||
return data[1]
|
||||
}
|
||||
|
||||
@@ -217,7 +224,7 @@ func (d *DeviceSPI) writeRegister(address, data uint8) {
|
||||
buf[0] = address
|
||||
buf[1] = data
|
||||
|
||||
d.CSB.Low()
|
||||
d.Bus.Tx(buf, buf)
|
||||
d.CSB.High()
|
||||
d.csb.Low()
|
||||
d.bus.Tx(buf, buf)
|
||||
d.csb.High()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,256 @@
|
||||
// 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)
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
package bno08x
|
||||
|
||||
// I2C and protocol constants
|
||||
const (
|
||||
shtpHeaderLength = 4
|
||||
maxTransferOut = 256
|
||||
maxTransferIn = 384
|
||||
|
||||
i2cDefaultChunk = 32
|
||||
continueMask = 0x8000
|
||||
)
|
||||
|
||||
// SHTP channel numbers
|
||||
const (
|
||||
channelCommand = 0
|
||||
channelExecutable = 1
|
||||
channelControl = 2
|
||||
channelSensorReport = 3
|
||||
channelWakeReport = 4
|
||||
channelGyroRV = 5
|
||||
)
|
||||
|
||||
// SH-2 report IDs
|
||||
const (
|
||||
reportProdIDReq = 0xF9
|
||||
reportProdIDResp = 0xF8
|
||||
reportSetFeature = 0xFD
|
||||
reportGetFeature = 0xFE
|
||||
reportGetFeatureResp = 0xFC
|
||||
reportCommandReq = 0xF2
|
||||
reportCommandResp = 0xF1
|
||||
reportFRSWriteReq = 0xF7
|
||||
reportFRSWriteData = 0xF6
|
||||
reportFRSReadReq = 0xF4
|
||||
reportFRSReadResp = 0xF3
|
||||
reportBaseTimestamp = 0xFB
|
||||
reportTimestampReuse = 0xFA
|
||||
reportForceFlush = 0xF0
|
||||
reportFlushCompleted = 0xEF
|
||||
reportResetReq = 0xF1
|
||||
reportResetResp = 0xF0
|
||||
)
|
||||
|
||||
// SH-2 commands
|
||||
const (
|
||||
cmdErrors = 0x01
|
||||
cmdCounts = 0x02
|
||||
cmdTare = 0x03
|
||||
cmdInitialize = 0x04
|
||||
cmdFRS = 0x05
|
||||
cmdDCD = 0x06
|
||||
cmdMECal = 0x07
|
||||
cmdProdIDReq = 0x07
|
||||
cmdDCDSave = 0x09
|
||||
cmdGetOscType = 0x0A
|
||||
cmdClearDCDReset = 0x0B
|
||||
cmdCal = 0x0C
|
||||
cmdBootloader = 0x0D
|
||||
cmdInteractiveZRO = 0x0E
|
||||
|
||||
// Command parameters
|
||||
initSystem = 0x01
|
||||
initUnsolicited = 0x80
|
||||
|
||||
countsClearCounts = 0x01
|
||||
countsGetCounts = 0x00
|
||||
|
||||
tareTareNow = 0x00
|
||||
tarePersist = 0x01
|
||||
tareSetReorientation = 0x02
|
||||
|
||||
calStart = 0x00
|
||||
calFinish = 0x01
|
||||
|
||||
commandParamCount = 9
|
||||
responseValueCount = 11
|
||||
)
|
||||
|
||||
// Feature report flags
|
||||
const (
|
||||
featChangeSensitivityRelative = 0x01
|
||||
featChangeSensitivityEnabled = 0x02
|
||||
featWakeEnabled = 0x04
|
||||
featAlwaysOnEnabled = 0x08
|
||||
)
|
||||
|
||||
// Scaling factors for sensor data
|
||||
// These are derived from the Q-point encoding in the SH-2 specification
|
||||
const (
|
||||
scaleQuat = 1.0 / 16384.0 // Q14
|
||||
scaleAccel = 1.0 / 256.0 // Q8
|
||||
scaleGyro = 1.0 / 512.0 // Q9
|
||||
scaleMag = 1.0 / 16.0 // Q4
|
||||
scaleAccuracy = 1.0 / 4096.0 // Q12
|
||||
scalePressure = 1.0 / 1048576.0 // Q20
|
||||
scaleLight = 1.0 / 256.0 // Q8
|
||||
scaleHumidity = 1.0 / 256.0 // Q8
|
||||
scaleProximity = 1.0 / 16.0 // Q4
|
||||
scaleTemperature = 1.0 / 128.0 // Q7
|
||||
scaleAngle = 1.0 / 16.0 // Q4
|
||||
scaleHeartRate = 1.0 / 16.0 // Q4
|
||||
)
|
||||
|
||||
// Activity classifier codes (extended beyond standard SH-2)
|
||||
const (
|
||||
ActivityUnknown = 0
|
||||
ActivityInVehicle = 1
|
||||
ActivityOnBicycle = 2
|
||||
ActivityOnFoot = 3
|
||||
ActivityStill = 4
|
||||
ActivityTilting = 5
|
||||
ActivityWalking = 6
|
||||
ActivityRunning = 7
|
||||
ActivityOnStairs = 8
|
||||
ActivityOptionCount = 9
|
||||
)
|
||||
|
||||
// Stability classifier values
|
||||
const (
|
||||
StabilityUnknown = 0
|
||||
StabilityOnTable = 1
|
||||
StabilityStationary = 2
|
||||
StabilityStable = 3
|
||||
StabilityMotion = 4
|
||||
)
|
||||
|
||||
// Tap detector flags
|
||||
const (
|
||||
TapX = 0x01 // 1 - X axis tapped
|
||||
TapXPos = 0x02 // 2 - X positive direction
|
||||
TapY = 0x04 // 4 - Y axis tapped
|
||||
TapYPos = 0x08 // 8 - Y positive direction
|
||||
TapZ = 0x10 // 16 - Z axis tapped
|
||||
TapZPos = 0x20 // 32 - Z positive direction
|
||||
TapDouble = 0x40 // 64 - Double tap occurred
|
||||
)
|
||||
|
||||
// GUID values for SHTP
|
||||
const (
|
||||
guidSHTP = 0
|
||||
guidExecutable = 1
|
||||
guidSensorHub = 2
|
||||
)
|
||||
|
||||
// Advertisement tags
|
||||
const (
|
||||
tagNull = 0
|
||||
tagGUID = 1
|
||||
tagMaxCargoHeaderWrite = 2
|
||||
tagMaxCargoHeaderRead = 3
|
||||
tagMaxTransferWrite = 4
|
||||
tagMaxTransferRead = 5
|
||||
tagNormalChannel = 6
|
||||
tagWakeChannel = 7
|
||||
tagAppName = 8
|
||||
tagChannelName = 9
|
||||
tagAdvCount = 10
|
||||
tagAppSpecific = 0x80
|
||||
tagSH2Version = 0x80
|
||||
tagSH2ReportLengths = 0x81
|
||||
)
|
||||
|
||||
// Timeouts
|
||||
const (
|
||||
advertTimeout = 200000 // microseconds
|
||||
commandTimeout = 300000 // microseconds
|
||||
)
|
||||
|
||||
// Executable device commands
|
||||
const (
|
||||
execDeviceCmdReset = 1
|
||||
execDeviceCmdOn = 2
|
||||
execDeviceCmdSleep = 3
|
||||
)
|
||||
|
||||
// Executable device responses
|
||||
const (
|
||||
execDeviceRespResetComplete = 1
|
||||
)
|
||||
@@ -0,0 +1,316 @@
|
||||
package bno08x
|
||||
|
||||
import "encoding/binary"
|
||||
|
||||
// decodeSensor decodes a sensor report payload into a SensorValue.
|
||||
func decodeSensor(payload []byte, timestamp uint32) (SensorValue, bool) {
|
||||
if len(payload) < 4 {
|
||||
return SensorValue{}, false
|
||||
}
|
||||
|
||||
value := SensorValue{
|
||||
id: SensorID(payload[0]),
|
||||
sequence: payload[1],
|
||||
status: payload[2] & 0x03,
|
||||
delay: payload[3],
|
||||
timestamp: uint64(timestamp),
|
||||
}
|
||||
|
||||
data := payload[4:]
|
||||
|
||||
switch value.id {
|
||||
case SensorRawAccelerometer:
|
||||
if len(data) >= 10 {
|
||||
value.rawAccelerometer = RawVector3{
|
||||
X: int16(binary.LittleEndian.Uint16(data[0:])),
|
||||
Y: int16(binary.LittleEndian.Uint16(data[2:])),
|
||||
Z: int16(binary.LittleEndian.Uint16(data[4:])),
|
||||
Timestamp: binary.LittleEndian.Uint32(data[6:]),
|
||||
}
|
||||
}
|
||||
|
||||
case SensorAccelerometer:
|
||||
if len(data) >= 6 {
|
||||
value.accelerometer = Vector3{
|
||||
X: qToFloat(data[0:], scaleAccel),
|
||||
Y: qToFloat(data[2:], scaleAccel),
|
||||
Z: qToFloat(data[4:], scaleAccel),
|
||||
}
|
||||
}
|
||||
|
||||
case SensorLinearAcceleration:
|
||||
if len(data) >= 6 {
|
||||
value.linearAcceleration = Vector3{
|
||||
X: qToFloat(data[0:], scaleAccel),
|
||||
Y: qToFloat(data[2:], scaleAccel),
|
||||
Z: qToFloat(data[4:], scaleAccel),
|
||||
}
|
||||
}
|
||||
|
||||
case SensorGravity:
|
||||
if len(data) >= 6 {
|
||||
value.gravity = Vector3{
|
||||
X: qToFloat(data[0:], scaleAccel),
|
||||
Y: qToFloat(data[2:], scaleAccel),
|
||||
Z: qToFloat(data[4:], scaleAccel),
|
||||
}
|
||||
}
|
||||
|
||||
case SensorRawGyroscope:
|
||||
if len(data) >= 12 {
|
||||
value.rawGyroscope = RawGyroscope{
|
||||
X: int16(binary.LittleEndian.Uint16(data[0:])),
|
||||
Y: int16(binary.LittleEndian.Uint16(data[2:])),
|
||||
Z: int16(binary.LittleEndian.Uint16(data[4:])),
|
||||
Temperature: int16(binary.LittleEndian.Uint16(data[6:])),
|
||||
Timestamp: binary.LittleEndian.Uint32(data[8:]),
|
||||
}
|
||||
}
|
||||
|
||||
case SensorGyroscope:
|
||||
if len(data) >= 6 {
|
||||
value.gyroscope = Vector3{
|
||||
X: qToFloat(data[0:], scaleGyro),
|
||||
Y: qToFloat(data[2:], scaleGyro),
|
||||
Z: qToFloat(data[4:], scaleGyro),
|
||||
}
|
||||
}
|
||||
|
||||
case SensorGyroscopeUncalibrated:
|
||||
if len(data) >= 12 {
|
||||
value.gyroscopeUncal = GyroscopeUncalibrated{
|
||||
X: qToFloat(data[0:], scaleGyro),
|
||||
Y: qToFloat(data[2:], scaleGyro),
|
||||
Z: qToFloat(data[4:], scaleGyro),
|
||||
BiasX: qToFloat(data[6:], scaleGyro),
|
||||
BiasY: qToFloat(data[8:], scaleGyro),
|
||||
BiasZ: qToFloat(data[10:], scaleGyro),
|
||||
}
|
||||
}
|
||||
|
||||
case SensorRawMagnetometer:
|
||||
if len(data) >= 10 {
|
||||
value.rawMagnetometer = RawVector3{
|
||||
X: int16(binary.LittleEndian.Uint16(data[0:])),
|
||||
Y: int16(binary.LittleEndian.Uint16(data[2:])),
|
||||
Z: int16(binary.LittleEndian.Uint16(data[4:])),
|
||||
Timestamp: binary.LittleEndian.Uint32(data[6:]),
|
||||
}
|
||||
}
|
||||
|
||||
case SensorMagneticField:
|
||||
if len(data) >= 6 {
|
||||
value.magneticField = Vector3{
|
||||
X: qToFloat(data[0:], scaleMag),
|
||||
Y: qToFloat(data[2:], scaleMag),
|
||||
Z: qToFloat(data[4:], scaleMag),
|
||||
}
|
||||
}
|
||||
|
||||
case SensorMagneticFieldUncalibrated:
|
||||
if len(data) >= 12 {
|
||||
value.magneticFieldUncal = MagneticFieldUncalibrated{
|
||||
X: qToFloat(data[0:], scaleMag),
|
||||
Y: qToFloat(data[2:], scaleMag),
|
||||
Z: qToFloat(data[4:], scaleMag),
|
||||
BiasX: qToFloat(data[6:], scaleMag),
|
||||
BiasY: qToFloat(data[8:], scaleMag),
|
||||
BiasZ: qToFloat(data[10:], scaleMag),
|
||||
}
|
||||
}
|
||||
|
||||
case SensorRotationVector:
|
||||
if len(data) >= 10 {
|
||||
value.quaternion = Quaternion{
|
||||
I: qToFloat(data[0:], scaleQuat),
|
||||
J: qToFloat(data[2:], scaleQuat),
|
||||
K: qToFloat(data[4:], scaleQuat),
|
||||
Real: qToFloat(data[6:], scaleQuat),
|
||||
}
|
||||
value.quaternionAccuracy = qToFloat(data[8:], scaleAccuracy)
|
||||
}
|
||||
|
||||
case SensorGameRotationVector:
|
||||
if len(data) >= 8 {
|
||||
value.quaternion = Quaternion{
|
||||
I: qToFloat(data[0:], scaleQuat),
|
||||
J: qToFloat(data[2:], scaleQuat),
|
||||
K: qToFloat(data[4:], scaleQuat),
|
||||
Real: qToFloat(data[6:], scaleQuat),
|
||||
}
|
||||
}
|
||||
|
||||
case SensorGeomagneticRotationVector:
|
||||
if len(data) >= 10 {
|
||||
value.quaternion = Quaternion{
|
||||
I: qToFloat(data[0:], scaleQuat),
|
||||
J: qToFloat(data[2:], scaleQuat),
|
||||
K: qToFloat(data[4:], scaleQuat),
|
||||
Real: qToFloat(data[6:], scaleQuat),
|
||||
}
|
||||
value.quaternionAccuracy = qToFloat(data[8:], scaleAccuracy)
|
||||
}
|
||||
|
||||
case SensorARVRStabilizedRV:
|
||||
if len(data) >= 10 {
|
||||
value.quaternion = Quaternion{
|
||||
I: qToFloat(data[0:], scaleQuat),
|
||||
J: qToFloat(data[2:], scaleQuat),
|
||||
K: qToFloat(data[4:], scaleQuat),
|
||||
Real: qToFloat(data[6:], scaleQuat),
|
||||
}
|
||||
value.quaternionAccuracy = qToFloat(data[8:], scaleAccuracy)
|
||||
}
|
||||
|
||||
case SensorARVRStabilizedGRV:
|
||||
if len(data) >= 8 {
|
||||
value.quaternion = Quaternion{
|
||||
I: qToFloat(data[0:], scaleQuat),
|
||||
J: qToFloat(data[2:], scaleQuat),
|
||||
K: qToFloat(data[4:], scaleQuat),
|
||||
Real: qToFloat(data[6:], scaleQuat),
|
||||
}
|
||||
}
|
||||
|
||||
case SensorGyroIntegratedRV:
|
||||
if len(data) >= 10 {
|
||||
value.quaternion = Quaternion{
|
||||
I: qToFloat(data[0:], scaleQuat),
|
||||
J: qToFloat(data[2:], scaleQuat),
|
||||
K: qToFloat(data[4:], scaleQuat),
|
||||
Real: qToFloat(data[6:], scaleQuat),
|
||||
}
|
||||
// Angular velocity X at data[8:10]
|
||||
}
|
||||
|
||||
case SensorPressure:
|
||||
if len(data) >= 4 {
|
||||
value.pressure = float32(int32(binary.LittleEndian.Uint32(data[0:]))) * scalePressure
|
||||
}
|
||||
|
||||
case SensorAmbientLight:
|
||||
if len(data) >= 4 {
|
||||
value.ambientLight = float32(int32(binary.LittleEndian.Uint32(data[0:]))) * scaleLight
|
||||
}
|
||||
|
||||
case SensorHumidity:
|
||||
if len(data) >= 2 {
|
||||
value.humidity = qToFloat(data[0:], scaleHumidity)
|
||||
}
|
||||
|
||||
case SensorProximity:
|
||||
if len(data) >= 2 {
|
||||
value.proximity = qToFloat(data[0:], scaleProximity)
|
||||
}
|
||||
|
||||
case SensorTemperature:
|
||||
if len(data) >= 2 {
|
||||
value.temperature = qToFloat(data[0:], scaleTemperature)
|
||||
}
|
||||
|
||||
case SensorTapDetector:
|
||||
if len(data) >= 1 {
|
||||
value.tapDetector = TapDetector{
|
||||
Flags: data[0],
|
||||
}
|
||||
}
|
||||
|
||||
case SensorStepDetector:
|
||||
if len(data) >= 4 {
|
||||
value.stepDetector = StepDetector{
|
||||
Latency: binary.LittleEndian.Uint32(data[0:]),
|
||||
}
|
||||
}
|
||||
|
||||
case SensorStepCounter:
|
||||
if len(data) >= 8 {
|
||||
value.stepCounter = StepCounter{
|
||||
Count: uint16(binary.LittleEndian.Uint32(data[4:8])),
|
||||
Latency: binary.LittleEndian.Uint32(data[0:4]),
|
||||
}
|
||||
}
|
||||
|
||||
case SensorSignificantMotion:
|
||||
if len(data) >= 2 {
|
||||
value.significantMotion = SignificantMotion{
|
||||
Motion: binary.LittleEndian.Uint16(data[0:]),
|
||||
}
|
||||
}
|
||||
|
||||
case SensorStabilityClassifier:
|
||||
if len(data) >= 1 {
|
||||
value.stabilityClassifier = StabilityClassifier{
|
||||
Classification: data[0],
|
||||
}
|
||||
}
|
||||
|
||||
case SensorStabilityDetector:
|
||||
if len(data) >= 1 {
|
||||
value.stabilityDetector = data[0]
|
||||
}
|
||||
|
||||
case SensorShakeDetector:
|
||||
if len(data) >= 2 {
|
||||
value.shakeDetector = ShakeDetector{
|
||||
Shake: binary.LittleEndian.Uint16(data[0:]),
|
||||
}
|
||||
}
|
||||
|
||||
case SensorFlipDetector:
|
||||
if len(data) >= 2 {
|
||||
value.flipDetector = binary.LittleEndian.Uint16(data[0:2])
|
||||
}
|
||||
|
||||
case SensorPickupDetector:
|
||||
if len(data) >= 2 {
|
||||
// Pickup detected at data[0:2]
|
||||
}
|
||||
|
||||
case SensorPersonalActivityClassifier:
|
||||
if len(data) >= 16 {
|
||||
value.personalActivityClassifier = PersonalActivityClassifier{
|
||||
Page: data[0],
|
||||
MostLikelyState: data[1],
|
||||
EndOfPage: data[15],
|
||||
}
|
||||
for i := 0; i < 10 && i+2 < len(data); i++ {
|
||||
value.personalActivityClassifier.Confidence[i] = data[2+i]
|
||||
}
|
||||
}
|
||||
|
||||
case SensorSleepDetector:
|
||||
if len(data) >= 1 {
|
||||
value.sleepDetector = data[0]
|
||||
}
|
||||
|
||||
case SensorTiltDetector:
|
||||
if len(data) >= 1 {
|
||||
value.tiltDetector = data[0]
|
||||
}
|
||||
|
||||
case SensorPocketDetector:
|
||||
if len(data) >= 1 {
|
||||
value.pocketDetector = data[0]
|
||||
}
|
||||
|
||||
case SensorCircleDetector:
|
||||
if len(data) >= 1 {
|
||||
value.circleDetector = data[0]
|
||||
}
|
||||
|
||||
case SensorHeartRateMonitor:
|
||||
if len(data) >= 2 {
|
||||
value.heartRateMonitor = binary.LittleEndian.Uint16(data[0:])
|
||||
}
|
||||
}
|
||||
|
||||
return value, true
|
||||
}
|
||||
|
||||
// qToFloat converts a Q-point fixed-point value to float32.
|
||||
func qToFloat(data []byte, scale float32) float32 {
|
||||
if len(data) < 2 {
|
||||
return 0
|
||||
}
|
||||
return float32(int16(binary.LittleEndian.Uint16(data))) * scale
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package bno08x
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// hal implements the hardware abstraction layer for bus communication.
|
||||
type hal struct {
|
||||
device *Device
|
||||
}
|
||||
|
||||
func newHAL(dev *Device) *hal {
|
||||
return &hal{
|
||||
device: dev,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *hal) open() error {
|
||||
// HAL is now open and ready for communication
|
||||
// Soft reset will be sent after handlers are registered
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *hal) close() {}
|
||||
|
||||
func (h *hal) read(target []byte) (int, uint32, error) {
|
||||
return h.device.bus.read(target)
|
||||
}
|
||||
|
||||
func (h *hal) write(frame []byte) (int, error) {
|
||||
if len(frame) > maxTransferOut {
|
||||
return 0, errFrameTooLarge
|
||||
}
|
||||
err := h.device.bus.write(frame)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return len(frame), nil
|
||||
}
|
||||
|
||||
func (h *hal) getTimeUs() uint32 {
|
||||
return uint32(time.Now().UnixNano() / 1000)
|
||||
}
|
||||
+387
@@ -0,0 +1,387 @@
|
||||
// SH-2 specification found at https://www.ceva-ip.com/wp-content/uploads/SH-2-Reference-Manual.pdf
|
||||
|
||||
package bno08x
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"time"
|
||||
)
|
||||
|
||||
// getReportLen returns the length in bytes of a sensor report given its ID.
|
||||
// Returns 0 for unknown report IDs.
|
||||
func getReportLen(reportID byte) int {
|
||||
switch reportID {
|
||||
case 0xF1: // FLUSH_COMPLETED
|
||||
return 6
|
||||
case 0xFA: // TIMESTAMP_REBASE
|
||||
return 5
|
||||
case 0xFB: // BASE_TIMESTAMP_REF
|
||||
return 5
|
||||
case 0xFC: // GET_FEATURE_RESP
|
||||
return 17
|
||||
case 0x01: // Accelerometer (calibrated)
|
||||
return 10
|
||||
case 0x02: // Gyroscope (calibrated)
|
||||
return 10
|
||||
case 0x03: // Magnetic field (calibrated)
|
||||
return 10
|
||||
case 0x04: // Linear acceleration
|
||||
return 10
|
||||
case 0x05: // Rotation vector
|
||||
return 14
|
||||
case 0x06: // Gravity
|
||||
return 10
|
||||
case 0x07: // Gyroscope uncalibrated
|
||||
return 16
|
||||
case 0x08: // Game rotation vector
|
||||
return 12
|
||||
case 0x09: // Geomagnetic rotation vector
|
||||
return 14
|
||||
case 0x0A: // Pressure
|
||||
return 10
|
||||
case 0x0B: // Ambient light
|
||||
return 10
|
||||
case 0x0C: // Humidity
|
||||
return 10
|
||||
case 0x0D: // Proximity
|
||||
return 10
|
||||
case 0x0E: // Temperature
|
||||
return 10
|
||||
case 0x0F: // Magnetic field uncalibrated
|
||||
return 16
|
||||
case 0x10: // Tap detector
|
||||
return 5
|
||||
case 0x11: // Step counter
|
||||
return 12
|
||||
case 0x12: // Significant motion
|
||||
return 6
|
||||
case 0x13: // Stability classifier
|
||||
return 5
|
||||
case 0x14: // Raw accelerometer
|
||||
return 16
|
||||
case 0x15: // Raw gyroscope
|
||||
return 16
|
||||
case 0x16: // Raw magnetometer
|
||||
return 16
|
||||
case 0x18: // Step detector
|
||||
return 8
|
||||
case 0x19: // Shake detector
|
||||
return 6
|
||||
case 0x1A: // Flip detector
|
||||
return 6
|
||||
case 0x1B: // Pickup detector
|
||||
return 6
|
||||
case 0x1C: // Stability detector
|
||||
return 6
|
||||
case 0x1E: // Personal activity classifier
|
||||
return 16
|
||||
default:
|
||||
// For most sensor reports, they are typically 10-16 bytes
|
||||
// If we don't know the exact length, return a safe default
|
||||
// that covers most cases (the handler will bounds-check)
|
||||
if reportID < 0xF0 {
|
||||
return 10 // Most sensor reports are at least this long
|
||||
}
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// sh2Protocol implements the Sensor Hub 2 (SH-2) application protocol.
|
||||
type sh2Protocol struct {
|
||||
device *Device
|
||||
transport *shtp
|
||||
cmdSeq uint8
|
||||
waiting bool
|
||||
lastCmd uint8
|
||||
pendingConfigRequest bool
|
||||
pendingConfigSensor SensorID
|
||||
receivedConfig SensorConfig
|
||||
configReady bool
|
||||
configBuf [17]byte // Reusable buffer for setSensorConfig
|
||||
commandBuf [3 + commandParamCount]byte // Reusable buffer for sendCommand
|
||||
}
|
||||
|
||||
func newSH2Protocol(device *Device) *sh2Protocol {
|
||||
proto := &sh2Protocol{
|
||||
device: device,
|
||||
transport: device.shtp,
|
||||
}
|
||||
|
||||
// Register handlers for each channel
|
||||
device.shtp.register(channelControl, proto.handleControl)
|
||||
device.shtp.register(channelSensorReport, proto.handleSensor)
|
||||
device.shtp.register(channelWakeReport, proto.handleSensor)
|
||||
device.shtp.register(channelGyroRV, proto.handleSensor)
|
||||
device.shtp.register(channelExecutable, proto.handleExecutable)
|
||||
|
||||
return proto
|
||||
}
|
||||
|
||||
// softReset sends a software reset command to the sensor.
|
||||
func (s *sh2Protocol) softReset() error {
|
||||
payload := []byte{execDeviceCmdReset}
|
||||
return s.transport.send(channelExecutable, payload)
|
||||
}
|
||||
|
||||
// initialize sends the initialize command to the sensor.
|
||||
func (s *sh2Protocol) initialize() error {
|
||||
return s.sendCommand(cmdInitialize, []byte{initSystem})
|
||||
}
|
||||
|
||||
// requestProductIDs requests product identification information.
|
||||
func (s *sh2Protocol) requestProductIDs() error {
|
||||
payload := []byte{reportProdIDReq, 0x00}
|
||||
return s.transport.send(channelControl, payload)
|
||||
}
|
||||
|
||||
// enableReport enables a sensor report at the specified interval.
|
||||
func (s *sh2Protocol) enableReport(id SensorID, intervalUs uint32) error {
|
||||
config := SensorConfig{
|
||||
ReportInterval: intervalUs,
|
||||
}
|
||||
return s.setSensorConfig(id, config)
|
||||
}
|
||||
|
||||
// getSensorConfig retrieves the configuration for a sensor.
|
||||
// This method sends a GET_FEATURE request and waits for the response
|
||||
// by polling the device. It will timeout after approximately 1 second.
|
||||
func (s *sh2Protocol) getSensorConfig(id SensorID) (SensorConfig, error) {
|
||||
// Mark that we're waiting for a config response
|
||||
s.pendingConfigRequest = true
|
||||
s.pendingConfigSensor = id
|
||||
s.configReady = false
|
||||
|
||||
payload := []byte{reportGetFeature, byte(id)}
|
||||
err := s.transport.send(channelControl, payload)
|
||||
if err != nil {
|
||||
s.pendingConfigRequest = false
|
||||
return SensorConfig{}, err
|
||||
}
|
||||
|
||||
// Poll for response with timeout
|
||||
maxAttempts := 100 // ~1 second with 10ms delays
|
||||
for i := 0; i < maxAttempts; i++ {
|
||||
// Service the device to process incoming messages
|
||||
s.device.shtp.poll()
|
||||
|
||||
if s.configReady {
|
||||
s.pendingConfigRequest = false
|
||||
s.configReady = false
|
||||
return s.receivedConfig, nil
|
||||
}
|
||||
|
||||
// Small delay between polls
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
|
||||
s.pendingConfigRequest = false
|
||||
return SensorConfig{}, errTimeout
|
||||
}
|
||||
|
||||
// setSensorConfig configures a sensor.
|
||||
func (s *sh2Protocol) setSensorConfig(id SensorID, config SensorConfig) error {
|
||||
// Use pre-allocated buffer to avoid allocations
|
||||
payload := s.configBuf[:]
|
||||
payload[0] = reportSetFeature
|
||||
payload[1] = byte(id)
|
||||
|
||||
// Build feature flags
|
||||
var flags uint8
|
||||
if config.ChangeSensitivityEnabled {
|
||||
flags |= featChangeSensitivityEnabled
|
||||
}
|
||||
if config.ChangeSensitivityRelative {
|
||||
flags |= featChangeSensitivityRelative
|
||||
}
|
||||
if config.WakeupEnabled {
|
||||
flags |= featWakeEnabled
|
||||
}
|
||||
if config.AlwaysOnEnabled {
|
||||
flags |= featAlwaysOnEnabled
|
||||
}
|
||||
payload[2] = flags
|
||||
|
||||
binary.LittleEndian.PutUint16(payload[3:5], config.ChangeSensitivity)
|
||||
binary.LittleEndian.PutUint32(payload[5:9], config.ReportInterval)
|
||||
binary.LittleEndian.PutUint32(payload[9:13], config.BatchInterval)
|
||||
binary.LittleEndian.PutUint32(payload[13:17], config.SensorSpecific)
|
||||
|
||||
return s.transport.send(channelControl, payload)
|
||||
}
|
||||
|
||||
// sendCommand sends a command with parameters to the sensor.
|
||||
func (s *sh2Protocol) sendCommand(command byte, params []byte) error {
|
||||
// Use pre-allocated buffer to avoid allocations
|
||||
payload := s.commandBuf[:]
|
||||
payload[0] = reportCommandReq
|
||||
payload[1] = s.cmdSeq
|
||||
payload[2] = command
|
||||
s.cmdSeq++
|
||||
s.lastCmd = command
|
||||
s.waiting = true
|
||||
|
||||
for i := 0; i < commandParamCount && i < len(params); i++ {
|
||||
payload[3+i] = params[i]
|
||||
}
|
||||
|
||||
return s.transport.send(channelControl, payload[:3+commandParamCount])
|
||||
}
|
||||
|
||||
// handleControl processes control channel messages.
|
||||
func (s *sh2Protocol) handleControl(payload []byte, timestamp uint32) {
|
||||
if len(payload) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
reportID := payload[0]
|
||||
|
||||
switch reportID {
|
||||
case reportProdIDResp:
|
||||
s.handleProdID(payload, timestamp)
|
||||
case reportCommandResp:
|
||||
s.handleCommandResp(payload, timestamp)
|
||||
case reportGetFeatureResp:
|
||||
s.handleGetFeatureResp(payload, timestamp)
|
||||
case reportFRSReadResp:
|
||||
// FRS (Flash Record System) read response
|
||||
// Not implemented in basic version
|
||||
}
|
||||
}
|
||||
|
||||
// handleProdID processes product ID responses.
|
||||
func (s *sh2Protocol) handleProdID(payload []byte, timestamp uint32) {
|
||||
if len(payload) < 16 {
|
||||
return
|
||||
}
|
||||
|
||||
entry := ProductID{
|
||||
ResetCause: payload[1],
|
||||
VersionMajor: payload[2],
|
||||
VersionMinor: payload[3],
|
||||
PartNumber: binary.LittleEndian.Uint32(payload[4:8]),
|
||||
BuildNumber: binary.LittleEndian.Uint32(payload[8:12]),
|
||||
VersionPatch: binary.LittleEndian.Uint16(payload[12:14]),
|
||||
Reserved0: payload[14],
|
||||
Reserved1: payload[15],
|
||||
}
|
||||
|
||||
// Store in first slot
|
||||
s.device.productIDs.Entries[0] = entry
|
||||
s.device.productIDs.NumEntries = 1
|
||||
}
|
||||
|
||||
// handleCommandResp processes command responses.
|
||||
func (s *sh2Protocol) handleCommandResp(payload []byte, timestamp uint32) {
|
||||
if len(payload) < 16 {
|
||||
return
|
||||
}
|
||||
|
||||
// seq := payload[1]
|
||||
command := payload[2]
|
||||
// commandSeq := payload[3]
|
||||
// respSeq := payload[4]
|
||||
|
||||
// Check if this response is for our command
|
||||
if s.waiting && command == s.lastCmd {
|
||||
s.waiting = false
|
||||
// Status is in payload[6]
|
||||
// For now, we just acknowledge receipt
|
||||
}
|
||||
}
|
||||
|
||||
// handleGetFeatureResp processes get feature responses.
|
||||
func (s *sh2Protocol) handleGetFeatureResp(payload []byte, timestamp uint32) {
|
||||
if len(payload) < 17 {
|
||||
return
|
||||
}
|
||||
|
||||
// Parse the response
|
||||
sensorID := SensorID(payload[1])
|
||||
flags := payload[2]
|
||||
changeSensitivity := binary.LittleEndian.Uint16(payload[3:5])
|
||||
reportInterval := binary.LittleEndian.Uint32(payload[5:9])
|
||||
batchInterval := binary.LittleEndian.Uint32(payload[9:13])
|
||||
sensorSpecific := binary.LittleEndian.Uint32(payload[13:17])
|
||||
|
||||
// If we're waiting for this sensor's config, store it
|
||||
if s.pendingConfigRequest && s.pendingConfigSensor == sensorID {
|
||||
s.receivedConfig = SensorConfig{
|
||||
ChangeSensitivityEnabled: flags&featChangeSensitivityEnabled != 0,
|
||||
ChangeSensitivityRelative: flags&featChangeSensitivityRelative != 0,
|
||||
WakeupEnabled: flags&featWakeEnabled != 0,
|
||||
AlwaysOnEnabled: flags&featAlwaysOnEnabled != 0,
|
||||
ChangeSensitivity: changeSensitivity,
|
||||
ReportInterval: reportInterval,
|
||||
BatchInterval: batchInterval,
|
||||
SensorSpecific: sensorSpecific,
|
||||
}
|
||||
s.configReady = true
|
||||
}
|
||||
}
|
||||
|
||||
// handleSensor processes sensor report messages.
|
||||
// The payload can contain multiple sensor reports batched together.
|
||||
func (s *sh2Protocol) handleSensor(payload []byte, timestamp uint32) {
|
||||
cursor := 0
|
||||
var referenceDelta uint32
|
||||
|
||||
for cursor < len(payload) {
|
||||
if cursor >= len(payload) {
|
||||
break
|
||||
}
|
||||
|
||||
reportID := payload[cursor]
|
||||
reportLen := getReportLen(reportID)
|
||||
|
||||
if reportLen == 0 {
|
||||
// Unknown report ID
|
||||
break
|
||||
}
|
||||
|
||||
if cursor+reportLen > len(payload) {
|
||||
// Not enough data for this report
|
||||
break
|
||||
}
|
||||
|
||||
// Handle special report types
|
||||
switch reportID {
|
||||
case 0xFB: // SENSORHUB_BASE_TIMESTAMP_REF
|
||||
if reportLen >= 5 {
|
||||
// Extract timebase (little-endian uint32)
|
||||
timebase := binary.LittleEndian.Uint32(payload[cursor+1 : cursor+5])
|
||||
referenceDelta = -timebase // Store negative for delta calculation
|
||||
}
|
||||
|
||||
case 0xFA: // SENSORHUB_TIMESTAMP_REBASE
|
||||
if reportLen >= 5 {
|
||||
timebase := binary.LittleEndian.Uint32(payload[cursor+1 : cursor+5])
|
||||
referenceDelta += timebase
|
||||
}
|
||||
|
||||
case 0xF1: // SENSORHUB_FLUSH_COMPLETED
|
||||
// Route to control handler
|
||||
s.handleControl(payload[cursor:cursor+reportLen], timestamp)
|
||||
|
||||
default:
|
||||
// Regular sensor report
|
||||
value, ok := decodeSensor(payload[cursor:cursor+reportLen], timestamp)
|
||||
if ok {
|
||||
s.device.enqueue(value)
|
||||
}
|
||||
}
|
||||
|
||||
cursor += reportLen
|
||||
}
|
||||
} // handleExecutable processes executable channel messages.
|
||||
func (s *sh2Protocol) handleExecutable(payload []byte, timestamp uint32) {
|
||||
if len(payload) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
reportID := payload[0]
|
||||
|
||||
switch reportID {
|
||||
case execDeviceRespResetComplete:
|
||||
s.device.lastReset = true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
// SHTP specification found at https://www.ceva-ip.com/wp-content/uploads/SH-2-SHTP-Reference-Manual.pdf
|
||||
|
||||
package bno08x
|
||||
|
||||
import "encoding/binary"
|
||||
|
||||
// shtpHandler is a callback for handling SHTP channel data.
|
||||
type shtpHandler func(payload []byte, timestamp uint32)
|
||||
|
||||
// shtp implements the Sensor Hub Transport Protocol layer.
|
||||
type shtp struct {
|
||||
hal *hal
|
||||
handlers map[uint8]shtpHandler
|
||||
seq [8]uint8
|
||||
rx [maxTransferIn]byte // Reusable receive buffer
|
||||
tx [maxTransferOut]byte // Reusable transmit buffer
|
||||
}
|
||||
|
||||
func newSHTP(hal *hal) *shtp {
|
||||
return &shtp{
|
||||
hal: hal,
|
||||
handlers: make(map[uint8]shtpHandler),
|
||||
}
|
||||
}
|
||||
|
||||
// register registers a handler for a specific SHTP channel.
|
||||
func (s *shtp) register(channel uint8, handler shtpHandler) {
|
||||
if handler == nil {
|
||||
delete(s.handlers, channel)
|
||||
return
|
||||
}
|
||||
s.handlers[channel] = handler
|
||||
}
|
||||
|
||||
// send transmits a payload on the specified channel.
|
||||
func (s *shtp) send(channel uint8, payload []byte) error {
|
||||
total := len(payload) + shtpHeaderLength
|
||||
if total > maxTransferOut {
|
||||
return errFrameTooLarge
|
||||
}
|
||||
|
||||
// Use pre-allocated transmit buffer to avoid allocations
|
||||
frame := s.tx[:total]
|
||||
binary.LittleEndian.PutUint16(frame[0:2], uint16(total))
|
||||
frame[2] = channel
|
||||
frame[3] = s.seq[channel]
|
||||
s.seq[channel]++
|
||||
copy(frame[shtpHeaderLength:], payload)
|
||||
|
||||
_, err := s.hal.write(frame)
|
||||
return err
|
||||
}
|
||||
|
||||
// poll checks for and processes incoming SHTP packets.
|
||||
// Returns true if a packet was processed, false if no data available.
|
||||
func (s *shtp) poll() (bool, error) {
|
||||
n, timestamp, err := s.hal.read(s.rx[:])
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if n == 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
packet := s.rx[:n]
|
||||
length := int(binary.LittleEndian.Uint16(packet[0:2]) & ^uint16(continueMask))
|
||||
if length > n {
|
||||
length = n
|
||||
}
|
||||
if length < shtpHeaderLength {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
channel := packet[2]
|
||||
// seq := packet[3] // sequence number, not currently validated
|
||||
payload := packet[shtpHeaderLength:length]
|
||||
|
||||
if handler := s.handlers[channel]; handler != nil {
|
||||
handler(payload, timestamp)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
+572
@@ -0,0 +1,572 @@
|
||||
package bno08x
|
||||
|
||||
// SensorID identifies a specific sensor type.
|
||||
type SensorID uint8
|
||||
|
||||
// Sensor IDs as defined in the SH-2 specification.
|
||||
const (
|
||||
SensorRawAccelerometer SensorID = 0x14
|
||||
SensorAccelerometer SensorID = 0x01
|
||||
SensorLinearAcceleration SensorID = 0x04
|
||||
SensorGravity SensorID = 0x06
|
||||
SensorRawGyroscope SensorID = 0x15
|
||||
SensorGyroscope SensorID = 0x02
|
||||
SensorGyroscopeUncalibrated SensorID = 0x07
|
||||
SensorRawMagnetometer SensorID = 0x16
|
||||
SensorMagneticField SensorID = 0x03
|
||||
SensorMagneticFieldUncalibrated SensorID = 0x0F
|
||||
SensorRotationVector SensorID = 0x05
|
||||
SensorGameRotationVector SensorID = 0x08
|
||||
SensorGeomagneticRotationVector SensorID = 0x09
|
||||
SensorPressure SensorID = 0x0A
|
||||
SensorAmbientLight SensorID = 0x0B
|
||||
SensorHumidity SensorID = 0x0C
|
||||
SensorProximity SensorID = 0x0D
|
||||
SensorTemperature SensorID = 0x0E
|
||||
SensorReserved SensorID = 0x17
|
||||
SensorTapDetector SensorID = 0x10
|
||||
SensorStepDetector SensorID = 0x18
|
||||
SensorStepCounter SensorID = 0x11
|
||||
SensorSignificantMotion SensorID = 0x12
|
||||
SensorStabilityClassifier SensorID = 0x13
|
||||
SensorShakeDetector SensorID = 0x19
|
||||
SensorFlipDetector SensorID = 0x1A
|
||||
SensorPickupDetector SensorID = 0x1B
|
||||
SensorStabilityDetector SensorID = 0x1C
|
||||
SensorPersonalActivityClassifier SensorID = 0x1E
|
||||
SensorSleepDetector SensorID = 0x1F
|
||||
SensorTiltDetector SensorID = 0x20
|
||||
SensorPocketDetector SensorID = 0x21
|
||||
SensorCircleDetector SensorID = 0x22
|
||||
SensorHeartRateMonitor SensorID = 0x23
|
||||
SensorARVRStabilizedRV SensorID = 0x28
|
||||
SensorARVRStabilizedGRV SensorID = 0x29
|
||||
SensorGyroIntegratedRV SensorID = 0x2A
|
||||
SensorIZROMotionRequest SensorID = 0x2B
|
||||
SensorMaxID SensorID = 0x2B
|
||||
)
|
||||
|
||||
// ProductID contains firmware information from the sensor.
|
||||
type ProductID struct {
|
||||
ResetCause uint8
|
||||
VersionMajor uint8
|
||||
VersionMinor uint8
|
||||
PartNumber uint32
|
||||
BuildNumber uint32
|
||||
VersionPatch uint16
|
||||
Reserved0 uint8
|
||||
Reserved1 uint8
|
||||
}
|
||||
|
||||
// ProductIDs holds all product ID entries returned by the sensor.
|
||||
type ProductIDs struct {
|
||||
Entries [5]ProductID
|
||||
NumEntries uint8
|
||||
}
|
||||
|
||||
// Vector3 represents a 3D vector.
|
||||
type Vector3 struct {
|
||||
X float32
|
||||
Y float32
|
||||
Z float32
|
||||
}
|
||||
|
||||
// Quaternion represents a quaternion in (real, i, j, k) format.
|
||||
// Note: This maps to (w, x, y, z) convention where w=real, x=i, y=j, z=k.
|
||||
type Quaternion struct {
|
||||
Real float32
|
||||
I float32
|
||||
J float32
|
||||
K float32
|
||||
}
|
||||
|
||||
// RawVector3 contains raw ADC counts with timestamp.
|
||||
type RawVector3 struct {
|
||||
X int16
|
||||
Y int16
|
||||
Z int16
|
||||
Timestamp uint32
|
||||
}
|
||||
|
||||
// RawGyroscope contains raw gyro readings with temperature and timestamp.
|
||||
type RawGyroscope struct {
|
||||
X int16
|
||||
Y int16
|
||||
Z int16
|
||||
Temperature int16
|
||||
Timestamp uint32
|
||||
}
|
||||
|
||||
// GyroscopeUncalibrated contains uncalibrated gyroscope data with bias.
|
||||
type GyroscopeUncalibrated struct {
|
||||
X float32
|
||||
Y float32
|
||||
Z float32
|
||||
BiasX float32
|
||||
BiasY float32
|
||||
BiasZ float32
|
||||
}
|
||||
|
||||
// MagneticFieldUncalibrated contains uncalibrated magnetometer data with bias.
|
||||
type MagneticFieldUncalibrated struct {
|
||||
X float32
|
||||
Y float32
|
||||
Z float32
|
||||
BiasX float32
|
||||
BiasY float32
|
||||
BiasZ float32
|
||||
}
|
||||
|
||||
// TapDetector contains tap/double-tap detection flags.
|
||||
type TapDetector struct {
|
||||
Flags uint8
|
||||
}
|
||||
|
||||
// StepDetector contains step detection with latency.
|
||||
type StepDetector struct {
|
||||
Latency uint32
|
||||
}
|
||||
|
||||
// StepCounter contains step count with latency.
|
||||
type StepCounter struct {
|
||||
Count uint16
|
||||
Latency uint32
|
||||
}
|
||||
|
||||
// SignificantMotion indicates significant motion was detected.
|
||||
type SignificantMotion struct {
|
||||
Motion uint16
|
||||
}
|
||||
|
||||
// ActivityClassification contains activity classification data.
|
||||
type ActivityClassification struct {
|
||||
Page uint8
|
||||
MostLikelyState uint8
|
||||
Classification [10]uint8
|
||||
EndOfPage uint8
|
||||
}
|
||||
|
||||
// ShakeDetector contains shake detection data.
|
||||
type ShakeDetector struct {
|
||||
Shake uint16
|
||||
}
|
||||
|
||||
// StabilityClassifier contains stability classification.
|
||||
type StabilityClassifier struct {
|
||||
Classification uint8
|
||||
}
|
||||
|
||||
// PersonalActivityClassifier contains personal activity data.
|
||||
type PersonalActivityClassifier struct {
|
||||
Page uint8
|
||||
MostLikelyState uint8
|
||||
Confidence [10]uint8
|
||||
EndOfPage uint8
|
||||
}
|
||||
|
||||
// SensorValue contains decoded sensor data for all sensor types.
|
||||
type SensorValue struct {
|
||||
id SensorID
|
||||
status uint8
|
||||
sequence uint8
|
||||
delay uint8
|
||||
timestamp uint64
|
||||
|
||||
// Orientation data (quaternions)
|
||||
quaternion Quaternion
|
||||
quaternionAccuracy float32
|
||||
|
||||
// Linear measurements
|
||||
accelerometer Vector3
|
||||
linearAcceleration Vector3
|
||||
gravity Vector3
|
||||
gyroscope Vector3
|
||||
gyroscopeUncal GyroscopeUncalibrated
|
||||
magneticField Vector3
|
||||
magneticFieldUncal MagneticFieldUncalibrated
|
||||
|
||||
// Raw sensor data
|
||||
rawAccelerometer RawVector3
|
||||
rawGyroscope RawGyroscope
|
||||
rawMagnetometer RawVector3
|
||||
|
||||
// Environmental sensors
|
||||
pressure float32 // hPa
|
||||
ambientLight float32 // lux
|
||||
humidity float32 // %
|
||||
proximity float32 // cm
|
||||
temperature float32 // °C
|
||||
|
||||
// Activity detection
|
||||
tapDetector TapDetector
|
||||
stepCounter StepCounter
|
||||
stepDetector StepDetector
|
||||
significantMotion SignificantMotion
|
||||
shakeDetector ShakeDetector
|
||||
flipDetector uint16
|
||||
stabilityClassifier StabilityClassifier
|
||||
stabilityDetector uint8
|
||||
activityClassifier ActivityClassification
|
||||
personalActivityClassifier PersonalActivityClassifier
|
||||
sleepDetector uint8
|
||||
tiltDetector uint8
|
||||
pocketDetector uint8
|
||||
circleDetector uint8
|
||||
heartRateMonitor uint16
|
||||
}
|
||||
|
||||
// SensorConfig holds configuration settings for a sensor.
|
||||
type SensorConfig struct {
|
||||
ChangeSensitivityEnabled bool
|
||||
ChangeSensitivityRelative bool
|
||||
WakeupEnabled bool
|
||||
AlwaysOnEnabled bool
|
||||
ChangeSensitivity uint16
|
||||
ReportInterval uint32 // microseconds
|
||||
BatchInterval uint32 // microseconds
|
||||
SensorSpecific uint32
|
||||
}
|
||||
|
||||
// Error represents a driver error.
|
||||
type Error string
|
||||
|
||||
func (e Error) Error() string { return string(e) }
|
||||
|
||||
// Error constants.
|
||||
var (
|
||||
errBufferTooSmall = Error("bno08x: buffer too small")
|
||||
errNoEvent = Error("bno08x: no sensor event available")
|
||||
errTimeout = Error("bno08x: operation timed out")
|
||||
errFrameTooLarge = Error("bno08x: frame exceeds maximum size")
|
||||
errNoBus = Error("bno08x: I2C bus not configured")
|
||||
errInvalidParam = Error("bno08x: invalid parameter")
|
||||
errHubError = Error("bno08x: sensor hub error")
|
||||
errIO = Error("bno08x: I/O error")
|
||||
)
|
||||
|
||||
// Metadata accessor methods (always available for any sensor type)
|
||||
|
||||
// ID returns the sensor ID.
|
||||
func (sv SensorValue) ID() SensorID {
|
||||
return sv.id
|
||||
}
|
||||
|
||||
// Status returns the sensor status flags.
|
||||
func (sv SensorValue) Status() uint8 {
|
||||
return sv.status
|
||||
}
|
||||
|
||||
// Sequence returns the sequence number.
|
||||
func (sv SensorValue) Sequence() uint8 {
|
||||
return sv.sequence
|
||||
}
|
||||
|
||||
// Delay returns the sensor delay value.
|
||||
func (sv SensorValue) Delay() uint8 {
|
||||
return sv.delay
|
||||
}
|
||||
|
||||
// Timestamp returns the sensor timestamp.
|
||||
func (sv SensorValue) Timestamp() uint64 {
|
||||
return sv.timestamp
|
||||
}
|
||||
|
||||
// Orientation data accessor methods
|
||||
|
||||
// Quaternion returns the quaternion value for rotation vector sensors.
|
||||
// Panics if called on a sensor type that doesn't provide quaternion data.
|
||||
func (sv SensorValue) Quaternion() Quaternion {
|
||||
switch sv.id {
|
||||
case SensorRotationVector, SensorGameRotationVector, SensorGeomagneticRotationVector,
|
||||
SensorARVRStabilizedRV, SensorARVRStabilizedGRV, SensorGyroIntegratedRV:
|
||||
return sv.quaternion
|
||||
default:
|
||||
panic("bno08x: Quaternion() called on non-rotation sensor type")
|
||||
}
|
||||
}
|
||||
|
||||
// QuaternionAccuracy returns the quaternion accuracy estimate.
|
||||
// Panics if called on a sensor type that doesn't provide quaternion accuracy.
|
||||
func (sv SensorValue) QuaternionAccuracy() float32 {
|
||||
switch sv.id {
|
||||
case SensorRotationVector, SensorGeomagneticRotationVector, SensorARVRStabilizedRV:
|
||||
return sv.quaternionAccuracy
|
||||
default:
|
||||
panic("bno08x: QuaternionAccuracy() called on sensor type without accuracy data")
|
||||
}
|
||||
}
|
||||
|
||||
// Linear measurement accessor methods
|
||||
|
||||
// Accelerometer returns the accelerometer vector.
|
||||
// Panics if called on a sensor type other than SensorAccelerometer.
|
||||
func (sv SensorValue) Accelerometer() Vector3 {
|
||||
if sv.id != SensorAccelerometer {
|
||||
panic("bno08x: Accelerometer() called on non-accelerometer sensor type")
|
||||
}
|
||||
return sv.accelerometer
|
||||
}
|
||||
|
||||
// LinearAcceleration returns the linear acceleration vector.
|
||||
// Panics if called on a sensor type other than SensorLinearAcceleration.
|
||||
func (sv SensorValue) LinearAcceleration() Vector3 {
|
||||
if sv.id != SensorLinearAcceleration {
|
||||
panic("bno08x: LinearAcceleration() called on wrong sensor type")
|
||||
}
|
||||
return sv.linearAcceleration
|
||||
}
|
||||
|
||||
// Gravity returns the gravity vector.
|
||||
// Panics if called on a sensor type other than SensorGravity.
|
||||
func (sv SensorValue) Gravity() Vector3 {
|
||||
if sv.id != SensorGravity {
|
||||
panic("bno08x: Gravity() called on non-gravity sensor type")
|
||||
}
|
||||
return sv.gravity
|
||||
}
|
||||
|
||||
// Gyroscope returns the gyroscope vector.
|
||||
// Panics if called on a sensor type other than SensorGyroscope.
|
||||
func (sv SensorValue) Gyroscope() Vector3 {
|
||||
if sv.id != SensorGyroscope {
|
||||
panic("bno08x: Gyroscope() called on non-gyroscope sensor type")
|
||||
}
|
||||
return sv.gyroscope
|
||||
}
|
||||
|
||||
// GyroscopeUncal returns the uncalibrated gyroscope data.
|
||||
// Panics if called on a sensor type other than SensorGyroscopeUncalibrated.
|
||||
func (sv SensorValue) GyroscopeUncal() GyroscopeUncalibrated {
|
||||
if sv.id != SensorGyroscopeUncalibrated {
|
||||
panic("bno08x: GyroscopeUncal() called on wrong sensor type")
|
||||
}
|
||||
return sv.gyroscopeUncal
|
||||
}
|
||||
|
||||
// MagneticField returns the magnetic field vector.
|
||||
// Panics if called on a sensor type other than SensorMagneticField.
|
||||
func (sv SensorValue) MagneticField() Vector3 {
|
||||
if sv.id != SensorMagneticField {
|
||||
panic("bno08x: MagneticField() called on wrong sensor type")
|
||||
}
|
||||
return sv.magneticField
|
||||
}
|
||||
|
||||
// MagneticFieldUncal returns the uncalibrated magnetic field data.
|
||||
// Panics if called on a sensor type other than SensorMagneticFieldUncalibrated.
|
||||
func (sv SensorValue) MagneticFieldUncal() MagneticFieldUncalibrated {
|
||||
if sv.id != SensorMagneticFieldUncalibrated {
|
||||
panic("bno08x: MagneticFieldUncal() called on wrong sensor type")
|
||||
}
|
||||
return sv.magneticFieldUncal
|
||||
}
|
||||
|
||||
// Raw sensor data accessor methods
|
||||
|
||||
// RawAccelerometer returns the raw accelerometer data.
|
||||
// Panics if called on a sensor type other than SensorRawAccelerometer.
|
||||
func (sv SensorValue) RawAccelerometer() RawVector3 {
|
||||
if sv.id != SensorRawAccelerometer {
|
||||
panic("bno08x: RawAccelerometer() called on wrong sensor type")
|
||||
}
|
||||
return sv.rawAccelerometer
|
||||
}
|
||||
|
||||
// RawGyroscope returns the raw gyroscope data.
|
||||
// Panics if called on a sensor type other than SensorRawGyroscope.
|
||||
func (sv SensorValue) RawGyroscope() RawGyroscope {
|
||||
if sv.id != SensorRawGyroscope {
|
||||
panic("bno08x: RawGyroscope() called on wrong sensor type")
|
||||
}
|
||||
return sv.rawGyroscope
|
||||
}
|
||||
|
||||
// RawMagnetometer returns the raw magnetometer data.
|
||||
// Panics if called on a sensor type other than SensorRawMagnetometer.
|
||||
func (sv SensorValue) RawMagnetometer() RawVector3 {
|
||||
if sv.id != SensorRawMagnetometer {
|
||||
panic("bno08x: RawMagnetometer() called on wrong sensor type")
|
||||
}
|
||||
return sv.rawMagnetometer
|
||||
}
|
||||
|
||||
// Environmental sensor accessor methods
|
||||
|
||||
// Pressure returns the pressure reading in hPa.
|
||||
// Panics if called on a sensor type other than SensorPressure.
|
||||
func (sv SensorValue) Pressure() float32 {
|
||||
if sv.id != SensorPressure {
|
||||
panic("bno08x: Pressure() called on non-pressure sensor type")
|
||||
}
|
||||
return sv.pressure
|
||||
}
|
||||
|
||||
// AmbientLight returns the ambient light reading in lux.
|
||||
// Panics if called on a sensor type other than SensorAmbientLight.
|
||||
func (sv SensorValue) AmbientLight() float32 {
|
||||
if sv.id != SensorAmbientLight {
|
||||
panic("bno08x: AmbientLight() called on wrong sensor type")
|
||||
}
|
||||
return sv.ambientLight
|
||||
}
|
||||
|
||||
// Humidity returns the humidity reading in percent.
|
||||
// Panics if called on a sensor type other than SensorHumidity.
|
||||
func (sv SensorValue) Humidity() float32 {
|
||||
if sv.id != SensorHumidity {
|
||||
panic("bno08x: Humidity() called on non-humidity sensor type")
|
||||
}
|
||||
return sv.humidity
|
||||
}
|
||||
|
||||
// Proximity returns the proximity reading in cm.
|
||||
// Panics if called on a sensor type other than SensorProximity.
|
||||
func (sv SensorValue) Proximity() float32 {
|
||||
if sv.id != SensorProximity {
|
||||
panic("bno08x: Proximity() called on non-proximity sensor type")
|
||||
}
|
||||
return sv.proximity
|
||||
}
|
||||
|
||||
// Temperature returns the temperature reading in °C.
|
||||
// Panics if called on a sensor type other than SensorTemperature.
|
||||
func (sv SensorValue) Temperature() float32 {
|
||||
if sv.id != SensorTemperature {
|
||||
panic("bno08x: Temperature() called on non-temperature sensor type")
|
||||
}
|
||||
return sv.temperature
|
||||
}
|
||||
|
||||
// Activity detection accessor methods
|
||||
|
||||
// TapDetector returns the tap detector data.
|
||||
// Panics if called on a sensor type other than SensorTapDetector.
|
||||
func (sv SensorValue) TapDetector() TapDetector {
|
||||
if sv.id != SensorTapDetector {
|
||||
panic("bno08x: TapDetector() called on wrong sensor type")
|
||||
}
|
||||
return sv.tapDetector
|
||||
}
|
||||
|
||||
// StepCounter returns the step counter value.
|
||||
// Panics if called on a sensor type other than SensorStepCounter.
|
||||
func (sv SensorValue) StepCounter() StepCounter {
|
||||
if sv.id != SensorStepCounter {
|
||||
panic("bno08x: StepCounter() called on wrong sensor type")
|
||||
}
|
||||
return sv.stepCounter
|
||||
}
|
||||
|
||||
// StepDetector returns the step detector data.
|
||||
// Panics if called on a sensor type other than SensorStepDetector.
|
||||
func (sv SensorValue) StepDetector() StepDetector {
|
||||
if sv.id != SensorStepDetector {
|
||||
panic("bno08x: StepDetector() called on wrong sensor type")
|
||||
}
|
||||
return sv.stepDetector
|
||||
}
|
||||
|
||||
// SignificantMotion returns the significant motion data.
|
||||
// Panics if called on a sensor type other than SensorSignificantMotion.
|
||||
func (sv SensorValue) SignificantMotion() SignificantMotion {
|
||||
if sv.id != SensorSignificantMotion {
|
||||
panic("bno08x: SignificantMotion() called on wrong sensor type")
|
||||
}
|
||||
return sv.significantMotion
|
||||
}
|
||||
|
||||
// ShakeDetector returns the shake detector data.
|
||||
// Panics if called on a sensor type other than SensorShakeDetector.
|
||||
func (sv SensorValue) ShakeDetector() ShakeDetector {
|
||||
if sv.id != SensorShakeDetector {
|
||||
panic("bno08x: ShakeDetector() called on wrong sensor type")
|
||||
}
|
||||
return sv.shakeDetector
|
||||
}
|
||||
|
||||
// FlipDetector returns the flip detector data.
|
||||
// Panics if called on a sensor type other than SensorFlipDetector.
|
||||
func (sv SensorValue) FlipDetector() uint16 {
|
||||
if sv.id != SensorFlipDetector {
|
||||
panic("bno08x: FlipDetector() called on wrong sensor type")
|
||||
}
|
||||
return sv.flipDetector
|
||||
}
|
||||
|
||||
// StabilityClassifier returns the stability classifier data.
|
||||
// Panics if called on a sensor type other than SensorStabilityClassifier.
|
||||
func (sv SensorValue) StabilityClassifier() StabilityClassifier {
|
||||
if sv.id != SensorStabilityClassifier {
|
||||
panic("bno08x: StabilityClassifier() called on wrong sensor type")
|
||||
}
|
||||
return sv.stabilityClassifier
|
||||
}
|
||||
|
||||
// StabilityDetector returns the stability detector value.
|
||||
// Panics if called on a sensor type other than SensorStabilityDetector.
|
||||
func (sv SensorValue) StabilityDetector() uint8 {
|
||||
if sv.id != SensorStabilityDetector {
|
||||
panic("bno08x: StabilityDetector() called on wrong sensor type")
|
||||
}
|
||||
return sv.stabilityDetector
|
||||
}
|
||||
|
||||
// ActivityClassifier returns the activity classification data.
|
||||
// Note: This field appears unused in decode.go, keeping for API compatibility.
|
||||
func (sv SensorValue) ActivityClassifier() ActivityClassification {
|
||||
return sv.activityClassifier
|
||||
}
|
||||
|
||||
// PersonalActivityClassifier returns the personal activity classifier data.
|
||||
// Panics if called on a sensor type other than SensorPersonalActivityClassifier.
|
||||
func (sv SensorValue) PersonalActivityClassifier() PersonalActivityClassifier {
|
||||
if sv.id != SensorPersonalActivityClassifier {
|
||||
panic("bno08x: PersonalActivityClassifier() called on wrong sensor type")
|
||||
}
|
||||
return sv.personalActivityClassifier
|
||||
}
|
||||
|
||||
// SleepDetector returns the sleep detector value.
|
||||
// Panics if called on a sensor type other than SensorSleepDetector.
|
||||
func (sv SensorValue) SleepDetector() uint8 {
|
||||
if sv.id != SensorSleepDetector {
|
||||
panic("bno08x: SleepDetector() called on wrong sensor type")
|
||||
}
|
||||
return sv.sleepDetector
|
||||
}
|
||||
|
||||
// TiltDetector returns the tilt detector value.
|
||||
// Panics if called on a sensor type other than SensorTiltDetector.
|
||||
func (sv SensorValue) TiltDetector() uint8 {
|
||||
if sv.id != SensorTiltDetector {
|
||||
panic("bno08x: TiltDetector() called on wrong sensor type")
|
||||
}
|
||||
return sv.tiltDetector
|
||||
}
|
||||
|
||||
// PocketDetector returns the pocket detector value.
|
||||
// Panics if called on a sensor type other than SensorPocketDetector.
|
||||
func (sv SensorValue) PocketDetector() uint8 {
|
||||
if sv.id != SensorPocketDetector {
|
||||
panic("bno08x: PocketDetector() called on wrong sensor type")
|
||||
}
|
||||
return sv.pocketDetector
|
||||
}
|
||||
|
||||
// CircleDetector returns the circle detector value.
|
||||
// Panics if called on a sensor type other than SensorCircleDetector.
|
||||
func (sv SensorValue) CircleDetector() uint8 {
|
||||
if sv.id != SensorCircleDetector {
|
||||
panic("bno08x: CircleDetector() called on wrong sensor type")
|
||||
}
|
||||
return sv.circleDetector
|
||||
}
|
||||
|
||||
// HeartRateMonitor returns the heart rate monitor value.
|
||||
// Panics if called on a sensor type other than SensorHeartRateMonitor.
|
||||
func (sv SensorValue) HeartRateMonitor() uint16 {
|
||||
if sv.id != SensorHeartRateMonitor {
|
||||
panic("bno08x: HeartRateMonitor() called on wrong sensor type")
|
||||
}
|
||||
return sv.heartRateMonitor
|
||||
}
|
||||
+7
-7
@@ -2,22 +2,22 @@
|
||||
package buzzer // import "tinygo.org/x/drivers/buzzer"
|
||||
|
||||
import (
|
||||
"machine"
|
||||
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/internal/pin"
|
||||
)
|
||||
|
||||
// Device wraps a GPIO connection to a buzzer.
|
||||
type Device struct {
|
||||
pin machine.Pin
|
||||
pin pin.OutputFunc
|
||||
High bool
|
||||
BPM float64
|
||||
}
|
||||
|
||||
// New returns a new buzzer driver given which pin to use
|
||||
func New(pin machine.Pin) Device {
|
||||
func New(pin pin.Output) Device {
|
||||
return Device{
|
||||
pin: pin,
|
||||
pin: pin.Set,
|
||||
High: false,
|
||||
BPM: 96.0,
|
||||
}
|
||||
@@ -25,14 +25,14 @@ func New(pin machine.Pin) Device {
|
||||
|
||||
// On sets the buzzer to a high state.
|
||||
func (l *Device) On() (err error) {
|
||||
l.pin.Set(true)
|
||||
l.pin.High()
|
||||
l.High = true
|
||||
return
|
||||
}
|
||||
|
||||
// Off sets the buzzer to a low state.
|
||||
func (l *Device) Off() (err error) {
|
||||
l.pin.Set(false)
|
||||
l.pin.Low()
|
||||
l.High = false
|
||||
return
|
||||
}
|
||||
|
||||
+288
-36
@@ -5,10 +5,12 @@
|
||||
package ds3231 // import "tinygo.org/x/drivers/ds3231"
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
"tinygo.org/x/drivers/internal/legacy"
|
||||
"tinygo.org/x/drivers/internal/regmap"
|
||||
)
|
||||
|
||||
type Mode uint8
|
||||
@@ -17,6 +19,7 @@ type Mode uint8
|
||||
type Device struct {
|
||||
bus drivers.I2C
|
||||
Address uint16
|
||||
d regmap.Device8I2C
|
||||
}
|
||||
|
||||
// New creates a new DS3231 connection. The I2C bus must already be
|
||||
@@ -24,54 +27,50 @@ type Device struct {
|
||||
//
|
||||
// This function only creates the Device object, it does not touch the device.
|
||||
func New(bus drivers.I2C) Device {
|
||||
return Device{
|
||||
d := Device{
|
||||
bus: bus,
|
||||
Address: Address,
|
||||
}
|
||||
d.Configure()
|
||||
return d
|
||||
}
|
||||
|
||||
// Configure sets up the device for communication
|
||||
func (d *Device) Configure() bool {
|
||||
d.d.SetBus(d.bus, d.Address, binary.BigEndian)
|
||||
return true
|
||||
}
|
||||
|
||||
// IsTimeValid return true/false is the time in the device is valid
|
||||
func (d *Device) IsTimeValid() bool {
|
||||
data := []byte{0}
|
||||
err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_STATUS, data)
|
||||
status, err := d.d.Read8(REG_STATUS)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return (data[0] & (1 << OSF)) == 0x00
|
||||
return (status & (1 << OSF)) == 0x00
|
||||
}
|
||||
|
||||
// IsRunning returns if the oscillator is running
|
||||
func (d *Device) IsRunning() bool {
|
||||
data := []uint8{0}
|
||||
err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_CONTROL, data)
|
||||
control, err := d.d.Read8(REG_CONTROL)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return (data[0] & (1 << EOSC)) == 0x00
|
||||
return (control & (1 << EOSC)) == 0x00
|
||||
}
|
||||
|
||||
// SetRunning starts the internal oscillator
|
||||
func (d *Device) SetRunning(isRunning bool) error {
|
||||
data := []uint8{0}
|
||||
err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_CONTROL, data)
|
||||
control, err := d.d.Read8(REG_CONTROL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if isRunning {
|
||||
data[0] &^= uint8(1 << EOSC)
|
||||
control &^= uint8(1 << EOSC)
|
||||
} else {
|
||||
data[0] |= 1 << EOSC
|
||||
control |= 1 << EOSC
|
||||
}
|
||||
err = legacy.WriteRegister(d.bus, uint8(d.Address), REG_CONTROL, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return d.d.Write8(REG_CONTROL, control)
|
||||
}
|
||||
|
||||
// SetTime sets the date and time in the DS3231. The DS3231 hardware supports
|
||||
@@ -86,18 +85,16 @@ func (d *Device) SetRunning(isRunning bool) error {
|
||||
// 2100 as a leap year, causing it to increment from 2100-02-28 to 2100-02-29
|
||||
// instead of 2100-03-01.
|
||||
func (d *Device) SetTime(dt time.Time) error {
|
||||
data := []byte{0}
|
||||
err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_STATUS, data)
|
||||
status, err := d.d.Read8(REG_STATUS)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data[0] &^= 1 << OSF
|
||||
err = legacy.WriteRegister(d.bus, uint8(d.Address), REG_STATUS, data)
|
||||
if err != nil {
|
||||
status &^= 1 << OSF
|
||||
if err = d.d.Write8(REG_STATUS, status); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data = make([]uint8, 7)
|
||||
data := make([]uint8, 7)
|
||||
data[0] = uint8ToBCD(uint8(dt.Second()))
|
||||
data[1] = uint8ToBCD(uint8(dt.Minute()))
|
||||
data[2] = uint8ToBCD(uint8(dt.Hour()))
|
||||
@@ -118,21 +115,16 @@ func (d *Device) SetTime(dt time.Time) error {
|
||||
data[5] = uint8ToBCD(uint8(dt.Month()) | centuryFlag)
|
||||
data[6] = uint8ToBCD(year)
|
||||
|
||||
err = legacy.WriteRegister(d.bus, uint8(d.Address), REG_TIMEDATE, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return d.bus.Tx(d.Address, append([]byte{REG_TIMEDATE}, data...), nil)
|
||||
}
|
||||
|
||||
// ReadTime returns the date and time
|
||||
func (d *Device) ReadTime() (dt time.Time, err error) {
|
||||
data := make([]uint8, 7)
|
||||
err = legacy.ReadRegister(d.bus, uint8(d.Address), REG_TIMEDATE, data)
|
||||
if err != nil {
|
||||
if err = d.d.ReadData(REG_TIMEDATE, data); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
second := bcdToInt(data[0] & 0x7F)
|
||||
minute := bcdToInt(data[1])
|
||||
hour := hoursBCDToInt(data[2])
|
||||
@@ -150,12 +142,264 @@ func (d *Device) ReadTime() (dt time.Time, err error) {
|
||||
|
||||
// ReadTemperature returns the temperature in millicelsius (mC)
|
||||
func (d *Device) ReadTemperature() (int32, error) {
|
||||
data := make([]uint8, 2)
|
||||
err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_TEMP, data)
|
||||
temp, err := d.d.Read16(REG_TEMP)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return milliCelsius(data[0], data[1]), nil
|
||||
return milliCelsius(temp), nil
|
||||
}
|
||||
|
||||
// GetSqwPinMode returns the current square wave output frequency
|
||||
func (d *Device) GetSqwPinMode() SqwPinMode {
|
||||
control, err := d.d.Read8(REG_CONTROL)
|
||||
if err != nil {
|
||||
return SQW_OFF
|
||||
}
|
||||
|
||||
control &= 0x1C // turn off INTCON
|
||||
if control&0x04 != 0 {
|
||||
return SQW_OFF
|
||||
}
|
||||
|
||||
return SqwPinMode(control)
|
||||
}
|
||||
|
||||
// SetSqwPinMode sets the square wave output mode to the given frequency
|
||||
func (d *Device) SetSqwPinMode(mode SqwPinMode) error {
|
||||
control, err := d.d.Read8(REG_CONTROL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
control &^= 0x04 // turn off INTCON
|
||||
control &^= 0x18 // set freq bits to 0
|
||||
|
||||
control |= uint8(mode)
|
||||
|
||||
return d.d.Write8(REG_CONTROL, control)
|
||||
}
|
||||
|
||||
// SetAlarm1 sets alarm1 to the given time and mode
|
||||
func (d *Device) SetAlarm1(dt time.Time, mode Alarm1Mode) error {
|
||||
control, err := d.d.Read8(REG_CONTROL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if control&(1<<INTCN) == 0x00 {
|
||||
return errors.New("INTCN has to be disabled")
|
||||
}
|
||||
|
||||
A1M1 := uint8((mode & 0x01) << 7)
|
||||
A1M2 := uint8((mode & 0x02) << 6)
|
||||
A1M3 := uint8((mode & 0x04) << 5)
|
||||
A1M4 := uint8((mode & 0x08) << 4)
|
||||
DY_DT := uint8((mode & 0x10) << 2)
|
||||
|
||||
day := dt.Day()
|
||||
if DY_DT > 0 {
|
||||
day = dowToDS3231(int(dt.Weekday()))
|
||||
}
|
||||
|
||||
alarm1 := uint32(uint8ToBCD(uint8(dt.Second()))|A1M1) << 24
|
||||
alarm1 |= uint32(uint8ToBCD(uint8(dt.Minute()))|A1M2) << 16
|
||||
alarm1 |= uint32(uint8ToBCD(uint8(dt.Hour()))|A1M3) << 8
|
||||
alarm1 |= uint32(uint8ToBCD(uint8(day)) | A1M4 | DY_DT)
|
||||
|
||||
if err := d.d.Write32(REG_ALARMONE, alarm1); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
control |= AlarmFlag_Alarm1
|
||||
return d.d.Write8(REG_CONTROL, control)
|
||||
}
|
||||
|
||||
// ReadAlarm1 returns the alarm1 time
|
||||
func (d *Device) ReadAlarm1() (dt time.Time, err error) {
|
||||
data := make([]uint8, 4)
|
||||
if err = d.d.ReadData(REG_ALARMONE, data); err != nil {
|
||||
return
|
||||
}
|
||||
second := bcdToInt(data[0] & 0x7F)
|
||||
minute := bcdToInt(data[1] & 0x7F)
|
||||
hour := hoursBCDToInt(data[2] & 0x3F)
|
||||
|
||||
isDayOfWeek := (data[3] & 0x40) >> 6
|
||||
var day int
|
||||
if isDayOfWeek > 0 {
|
||||
day = bcdToInt(data[3] & 0x0F)
|
||||
} else {
|
||||
day = bcdToInt(data[3] & 0x3F)
|
||||
}
|
||||
|
||||
dt = time.Date(2000, 5, day, hour, minute, second, 0, time.UTC)
|
||||
return
|
||||
}
|
||||
|
||||
// SetAlarm2 sets alarm2 to the given time and mode
|
||||
func (d *Device) SetAlarm2(dt time.Time, mode Alarm2Mode) error {
|
||||
control, err := d.d.Read8(REG_CONTROL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if control&(1<<INTCN) == 0x00 {
|
||||
return errors.New("INTCN has to be disabled")
|
||||
}
|
||||
|
||||
A2M2 := uint8((mode & 0x01) << 7)
|
||||
A2M3 := uint8((mode & 0x02) << 6)
|
||||
A2M4 := uint8((mode & 0x04) << 5)
|
||||
DY_DT := uint8((mode & 0x08) << 3)
|
||||
|
||||
day := dt.Day()
|
||||
if DY_DT > 0 {
|
||||
day = dowToDS3231(int(dt.Weekday()))
|
||||
}
|
||||
|
||||
data := make([]uint8, 4)
|
||||
data[0] = uint8ToBCD(uint8(dt.Minute())) | A2M2
|
||||
data[1] = uint8ToBCD(uint8(dt.Hour())) | A2M3
|
||||
data[2] = uint8ToBCD(uint8(day)) | A2M4 | DY_DT
|
||||
if err = d.bus.Tx(d.Address, append([]byte{REG_ALARMTWO}, data...), nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
control |= AlarmFlag_Alarm2
|
||||
return d.d.Write8(REG_CONTROL, control)
|
||||
}
|
||||
|
||||
// ReadAlarm2 returns the alarm2 time
|
||||
func (d *Device) ReadAlarm2() (dt time.Time, err error) {
|
||||
data := make([]uint8, 3)
|
||||
if err = d.d.ReadData(REG_ALARMTWO, data); err != nil {
|
||||
return
|
||||
}
|
||||
minute := bcdToInt(data[0] & 0x7F)
|
||||
hour := hoursBCDToInt(data[1] & 0x3F)
|
||||
|
||||
isDayOfWeek := (data[2] & 0x40) >> 6
|
||||
var day int
|
||||
if isDayOfWeek > 0 {
|
||||
day = bcdToInt(data[2] & 0x0F)
|
||||
} else {
|
||||
day = bcdToInt(data[2] & 0x3F)
|
||||
}
|
||||
|
||||
dt = time.Date(2000, 5, day, hour, minute, 0, 0, time.UTC)
|
||||
return
|
||||
}
|
||||
|
||||
// IsEnabledAlarm1 returns true when alarm1 is enabled
|
||||
func (d *Device) IsEnabledAlarm1() bool {
|
||||
return d.isEnabledAlarm(1)
|
||||
}
|
||||
|
||||
// SetEnabledAlarm1 sets the enabled status of alarm1
|
||||
func (d *Device) SetEnabledAlarm1(enable bool) error {
|
||||
if enable {
|
||||
return d.enableAlarm(1)
|
||||
}
|
||||
return d.disableAlarm(1)
|
||||
}
|
||||
|
||||
// IsEnabledAlarm2 returns true when alarm2 is enabled
|
||||
func (d *Device) IsEnabledAlarm2() bool {
|
||||
return d.isEnabledAlarm(2)
|
||||
}
|
||||
|
||||
// SetEnabledAlarm2 sets the enabled status of alarm2
|
||||
func (d *Device) SetEnabledAlarm2(enable bool) error {
|
||||
if enable {
|
||||
return d.enableAlarm(2)
|
||||
}
|
||||
return d.disableAlarm(2)
|
||||
}
|
||||
|
||||
// ClearAlarm1 clears status of alarm1
|
||||
func (d *Device) ClearAlarm1() error {
|
||||
return d.clearAlarm(1)
|
||||
}
|
||||
|
||||
// ClearAlarm2 clears status of alarm2
|
||||
func (d *Device) ClearAlarm2() error {
|
||||
return d.clearAlarm(2)
|
||||
}
|
||||
|
||||
// IsAlarm1Fired returns true when alarm1 is firing
|
||||
func (d *Device) IsAlarm1Fired() bool {
|
||||
return d.isAlarmFired(1)
|
||||
}
|
||||
|
||||
// IsAlarm2Fired returns true when alarm2 is firing
|
||||
func (d *Device) IsAlarm2Fired() bool {
|
||||
return d.isAlarmFired(2)
|
||||
}
|
||||
|
||||
// SetEnabled32K sets the enabled status of the 32KHz output
|
||||
func (d *Device) SetEnabled32K(enable bool) error {
|
||||
status, err := d.d.Read8(REG_STATUS)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if enable {
|
||||
status |= 1 << EN32KHZ
|
||||
} else {
|
||||
status &^= 1 << EN32KHZ
|
||||
}
|
||||
|
||||
return d.d.Write8(REG_STATUS, status)
|
||||
}
|
||||
|
||||
// IsEnabled32K returns true when the 32KHz output is enabled
|
||||
func (d *Device) IsEnabled32K() bool {
|
||||
status, err := d.d.Read8(REG_STATUS)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return (status & (1 << EN32KHZ)) != 0x00
|
||||
}
|
||||
|
||||
func (d *Device) disableAlarm(alarm_num uint8) error {
|
||||
control, err := d.d.Read8(REG_CONTROL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
control &^= (1 << (alarm_num - 1))
|
||||
return d.d.Write8(REG_CONTROL, control)
|
||||
}
|
||||
|
||||
func (d *Device) enableAlarm(alarm_num uint8) error {
|
||||
control, err := d.d.Read8(REG_CONTROL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
control |= (1 << (alarm_num - 1))
|
||||
return d.d.Write8(REG_CONTROL, control)
|
||||
}
|
||||
|
||||
func (d *Device) isEnabledAlarm(alarm_num uint8) bool {
|
||||
control, err := d.d.Read8(REG_CONTROL)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return (control & (1 << (alarm_num - 1))) != 0x00
|
||||
}
|
||||
|
||||
func (d *Device) clearAlarm(alarm_num uint8) error {
|
||||
status, err := d.d.Read8(REG_STATUS)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
status &^= (1 << (alarm_num - 1))
|
||||
return d.d.Write8(REG_STATUS, status)
|
||||
}
|
||||
|
||||
func (d *Device) isAlarmFired(alarm_num uint8) bool {
|
||||
status, err := d.d.Read8(REG_STATUS)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return (status & (1 << (alarm_num - 1))) != 0x00
|
||||
}
|
||||
|
||||
// milliCelsius converts the raw temperature bytes (msb and lsb) from the DS3231
|
||||
@@ -172,8 +416,8 @@ func (d *Device) ReadTemperature() (int32, error) {
|
||||
// 16-bit signed integer in units of centi Celsius (1/100 deg C) with no loss of
|
||||
// precision or dynamic range. But for backwards compatibility, let's instead
|
||||
// convert this into a 32-bit signed integer in units of milli Celsius.
|
||||
func milliCelsius(msb uint8, lsb uint8) int32 {
|
||||
t256 := int16(uint16(msb)<<8 | uint16(lsb))
|
||||
func milliCelsius(tempBytes uint16) int32 {
|
||||
t256 := int16(uint16(tempBytes>>8)<<8 | uint16(tempBytes&0xFF))
|
||||
t1000 := int32(t256) / 64 * 250
|
||||
return t1000
|
||||
}
|
||||
@@ -200,3 +444,11 @@ func hoursBCDToInt(value uint8) (hour int) {
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// dowToDS3231 converts the day of the week to internal DS3231 format
|
||||
func dowToDS3231(d int) int {
|
||||
if d == 0 {
|
||||
return 7
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
+13
-13
@@ -5,71 +5,71 @@ import (
|
||||
)
|
||||
|
||||
func TestPositiveMilliCelsius(t *testing.T) {
|
||||
t1000 := milliCelsius(0, 0)
|
||||
t1000 := milliCelsius(0)
|
||||
if t1000 != 0 {
|
||||
t.Fatal(t1000)
|
||||
}
|
||||
|
||||
t1000 = milliCelsius(0, 0b01000000)
|
||||
t1000 = milliCelsius(0b0000000001000000)
|
||||
if t1000 != 250 {
|
||||
t.Fatal(t1000)
|
||||
}
|
||||
|
||||
t1000 = milliCelsius(0, 0b10000000)
|
||||
t1000 = milliCelsius(0b0000000010000000)
|
||||
if t1000 != 500 {
|
||||
t.Fatal(t1000)
|
||||
}
|
||||
|
||||
t1000 = milliCelsius(0, 0b11000000)
|
||||
t1000 = milliCelsius(0b0000000011000000)
|
||||
if t1000 != 750 {
|
||||
t.Fatal(t1000)
|
||||
}
|
||||
|
||||
t1000 = milliCelsius(1, 0b00000000)
|
||||
t1000 = milliCelsius(0b0000000100000000)
|
||||
if t1000 != 1000 {
|
||||
t.Fatal(t1000)
|
||||
}
|
||||
|
||||
t1000 = milliCelsius(2, 0b00000000)
|
||||
t1000 = milliCelsius(0b0000001000000000)
|
||||
if t1000 != 2000 {
|
||||
t.Fatal(t1000)
|
||||
}
|
||||
|
||||
// highest temperature is 127.750C
|
||||
t1000 = milliCelsius(0x7f, 0b11000000)
|
||||
t1000 = milliCelsius(0b0111111111000000)
|
||||
if t1000 != 127750 {
|
||||
t.Fatal(t1000)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNegativeMilliCelsius(t *testing.T) {
|
||||
t1000 := milliCelsius(0xff, 0b11000000)
|
||||
t1000 := milliCelsius(0b1111111111000000)
|
||||
if t1000 != -250 {
|
||||
t.Fatal(t1000)
|
||||
}
|
||||
|
||||
t1000 = milliCelsius(0xff, 0b10000000)
|
||||
t1000 = milliCelsius(0b1111111110000000)
|
||||
if t1000 != -500 {
|
||||
t.Fatal(t1000)
|
||||
}
|
||||
|
||||
t1000 = milliCelsius(0xff, 0b01000000)
|
||||
t1000 = milliCelsius(0b1111111101000000)
|
||||
if t1000 != -750 {
|
||||
t.Fatal(t1000)
|
||||
}
|
||||
|
||||
t1000 = milliCelsius(0xff, 0b00000000)
|
||||
t1000 = milliCelsius(0b1111111100000000)
|
||||
if t1000 != -1000 {
|
||||
t.Fatal(t1000)
|
||||
}
|
||||
|
||||
t1000 = milliCelsius(0xfe, 0b00000000)
|
||||
t1000 = milliCelsius(0b1111111000000000)
|
||||
if t1000 != -2000 {
|
||||
t.Fatal(t1000)
|
||||
}
|
||||
|
||||
// lowest temperature is -128.000C
|
||||
t1000 = milliCelsius(0x80, 0b00000000)
|
||||
t1000 = milliCelsius(0b1000000000000000)
|
||||
if t1000 != -128000 {
|
||||
t.Fatal(t1000)
|
||||
}
|
||||
|
||||
@@ -46,3 +46,52 @@ const (
|
||||
AlarmTwo Mode = 4
|
||||
ModeAlarmBoth Mode = 5
|
||||
)
|
||||
|
||||
// SQW Pin Modes
|
||||
type SqwPinMode uint8
|
||||
|
||||
const (
|
||||
SQW_OFF SqwPinMode = 0x1C
|
||||
SQW_1HZ SqwPinMode = 0x00
|
||||
SQW_1KHZ SqwPinMode = 0x08
|
||||
SQW_4KHZ SqwPinMode = 0x10
|
||||
SQW_8KHZ SqwPinMode = 0x18
|
||||
)
|
||||
|
||||
// Alarm1 Modes define which parts of the set alarm time has to match the current timestamp of the clock device for
|
||||
// alarm1 to fire
|
||||
type Alarm1Mode uint8
|
||||
|
||||
const (
|
||||
// Alarm1 fires every second
|
||||
A1_PER_SECOND Alarm1Mode = 0x0F
|
||||
// Alarm1 fires when the seconds match
|
||||
A1_SECOND Alarm1Mode = 0x0E
|
||||
// Alarm1 fires when both seconds and minutes match
|
||||
A1_MINUTE Alarm1Mode = 0x0C
|
||||
// Alarm1 fires when seconds, minutes and hours match
|
||||
A1_HOUR Alarm1Mode = 0x08
|
||||
// Alarm1 fires when seconds, minutes, hours and the day of the month match
|
||||
A1_DATE Alarm1Mode = 0x00
|
||||
// Alarm1 fires when seconds, minutes, hours and the day of the week match
|
||||
A1_DAY Alarm1Mode = 0x10
|
||||
)
|
||||
|
||||
// Alarm2 Modes define which parts of the set alarm time has to match the current timestamp of the clock device for
|
||||
// alarm2 to fire.
|
||||
//
|
||||
// Alarm2 only supports matching down to the minute unlike alarm1 which supports matching down to the second.
|
||||
type Alarm2Mode uint8
|
||||
|
||||
const (
|
||||
// Alarm2 fires every minute
|
||||
A2_PER_MINUTE Alarm2Mode = 0x07
|
||||
// Alarm2 fires when the minutes match
|
||||
A2_MINUTE Alarm2Mode = 0x06
|
||||
// Alarm2 fires when both minutes and hours match
|
||||
A2_HOUR Alarm2Mode = 0x04
|
||||
// Alarm2 fires when minutes, hours and the day of the month match
|
||||
A2_DATE Alarm2Mode = 0x00
|
||||
// Alarm2 fires when minutes, hours and the day of the week match
|
||||
A2_DAY Alarm2Mode = 0x08
|
||||
)
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
// Package main provides a basic example of using the BNO08x driver
|
||||
// to read rotation vector (quaternion) data from the sensor.
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/bno08x"
|
||||
)
|
||||
|
||||
func main() {
|
||||
time.Sleep(2 * time.Second) // Wait for sensor to power up
|
||||
// Initialize I2C bus
|
||||
i2c := machine.I2C0
|
||||
err := i2c.Configure(machine.I2CConfig{
|
||||
Frequency: 400 * machine.KHz,
|
||||
})
|
||||
if err != nil {
|
||||
println("Failed to configure I2C:", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
println("Initializing BNO08x sensor...")
|
||||
|
||||
// Create and configure sensor using I2C
|
||||
sensor := bno08x.NewI2C(i2c)
|
||||
err = sensor.Configure(bno08x.Config{})
|
||||
if err != nil {
|
||||
println("Failed to configure sensor:", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
println("Sensor initialized successfully")
|
||||
|
||||
// Enable Game Rotation Vector reports at 100Hz (10000 microseconds = 10ms interval)
|
||||
// Using Game Rotation Vector (0x08) to match the working channel_debug test
|
||||
err = sensor.EnableReport(bno08x.SensorGameRotationVector, 10000)
|
||||
if err != nil {
|
||||
println("Failed to enable game rotation vector:", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
println("Reading rotation vectors...")
|
||||
println("Format: Real I J K Accuracy")
|
||||
|
||||
// Add a delay after enabling reports (Arduino does this)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
// Main loop - read and display quaternion data
|
||||
for {
|
||||
event, ok := sensor.GetSensorEvent()
|
||||
if ok && (event.ID() == bno08x.SensorRotationVector || event.ID() == bno08x.SensorGameRotationVector) {
|
||||
q := event.Quaternion()
|
||||
if event.ID() == bno08x.SensorRotationVector {
|
||||
println(q.Real, q.I, q.J, q.K, event.QuaternionAccuracy())
|
||||
} else {
|
||||
// GameRotationVector doesn't have accuracy
|
||||
println(q.Real, q.I, q.J, q.K)
|
||||
}
|
||||
}
|
||||
|
||||
// Arduino uses 10ms delay in loop
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
// Connects to an DS3231 I2C Real Time Clock (RTC) and sets both alarms. It then repeatedly checks
|
||||
// if the alarms are firing and prints out a message if that is the case.
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/ds3231"
|
||||
)
|
||||
|
||||
func main() {
|
||||
machine.I2C0.Configure(machine.I2CConfig{})
|
||||
|
||||
rtc := ds3231.New(machine.I2C0)
|
||||
rtc.Configure()
|
||||
|
||||
valid := rtc.IsTimeValid()
|
||||
if !valid {
|
||||
date := time.Date(2019, 12, 05, 20, 34, 12, 0, time.UTC)
|
||||
rtc.SetTime(date)
|
||||
}
|
||||
|
||||
// Set alarm1 so it triggers when the seconds match 59 => repeats every minute at dd:hh:mm:59
|
||||
if err := rtc.SetAlarm1(time.Date(0, 0, 0, 0, 0, 59, 0, time.UTC), ds3231.A1_SECOND); err != nil {
|
||||
println("Error while setting Alarm1")
|
||||
}
|
||||
if err := rtc.SetEnabledAlarm1(true); err != nil {
|
||||
println("Error while enabling Alarm1")
|
||||
}
|
||||
|
||||
// Set alarm2 so it triggers when the minutes match 35 => repeats every hour at dd:hh:35:ss
|
||||
if err := rtc.SetAlarm2(time.Date(0, 0, 0, 0, 35, 0, 0, time.UTC), ds3231.A2_MINUTE); err != nil {
|
||||
println("Error while setting Alarm2")
|
||||
}
|
||||
if err := rtc.SetEnabledAlarm2(true); err != nil {
|
||||
println("Error while enabling Alarm2")
|
||||
}
|
||||
|
||||
running := rtc.IsRunning()
|
||||
if !running {
|
||||
err := rtc.SetRunning(true)
|
||||
if err != nil {
|
||||
println("Error configuring RTC")
|
||||
}
|
||||
}
|
||||
|
||||
for {
|
||||
dt, err := rtc.ReadTime()
|
||||
if err != nil {
|
||||
println("Error reading date:", err)
|
||||
continue
|
||||
}
|
||||
|
||||
a1 := rtc.IsAlarm1Fired()
|
||||
a2 := rtc.IsAlarm2Fired()
|
||||
|
||||
println(dt.Format(time.DateTime), "A1:", a1, "A2:", a2)
|
||||
|
||||
if a1 {
|
||||
if err := rtc.ClearAlarm1(); err != nil {
|
||||
println("Error while clearing alarm1")
|
||||
}
|
||||
}
|
||||
if a2 {
|
||||
if err := rtc.ClearAlarm2(); err != nil {
|
||||
println("Error while clearing alarm2")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
time.Sleep(time.Second * 1)
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,9 @@ package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"fmt"
|
||||
|
||||
"tinygo.org/x/drivers/ds3231"
|
||||
)
|
||||
|
||||
@@ -26,19 +25,19 @@ func main() {
|
||||
if !running {
|
||||
err := rtc.SetRunning(true)
|
||||
if err != nil {
|
||||
fmt.Println("Error configuring RTC")
|
||||
println("Error configuring RTC")
|
||||
}
|
||||
}
|
||||
|
||||
for {
|
||||
dt, err := rtc.ReadTime()
|
||||
if err != nil {
|
||||
fmt.Println("Error reading date:", err)
|
||||
println("Error reading date:", err)
|
||||
} else {
|
||||
fmt.Printf("Date: %d/%s/%02d %02d:%02d:%02d \r\n", dt.Year(), dt.Month(), dt.Day(), dt.Hour(), dt.Minute(), dt.Second())
|
||||
println(dt.Format(time.DateTime))
|
||||
}
|
||||
temp, _ := rtc.ReadTemperature()
|
||||
fmt.Printf("Temperature: %.2f °C \r\n", float32(temp)/1000)
|
||||
println("Temperature:", strconv.FormatFloat(float64(temp)/1000, 'f', -1, 32), "°C")
|
||||
|
||||
time.Sleep(time.Second * 1)
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/si5351"
|
||||
)
|
||||
|
||||
// Simple demo of the SI5351 clock generator.
|
||||
// This is like the Arduino library example:
|
||||
// https://github.com/adafruit/Adafruit_Si5351_Library/blob/master/examples/si5351/si5351.ino
|
||||
// Which will configure the chip with:
|
||||
// - PLL A at 900mhz
|
||||
// - PLL B at 616.66667mhz
|
||||
// - Clock 0 at 112.5mhz, using PLL A as a source divided by 8
|
||||
// - Clock 1 at 13.5531mhz, using PLL B as a source divided by 45.5
|
||||
// - Clock 2 at 10.76khz, using PLL B as a source divided by 900 and further divided with an R divider of 64.
|
||||
|
||||
func main() {
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
println("Si5351 Clockgen Test")
|
||||
println()
|
||||
|
||||
// Configure I2C bus
|
||||
machine.I2C0.Configure(machine.I2CConfig{})
|
||||
|
||||
// Create driver instance
|
||||
clockgen := si5351.New(machine.I2C0)
|
||||
|
||||
// Verify device wired properly
|
||||
connected, err := clockgen.Connected()
|
||||
if err != nil {
|
||||
println("Unable to read device status")
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
if !connected {
|
||||
for {
|
||||
println("Unable to detect si5351 device")
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
// Initialise device
|
||||
clockgen.Configure()
|
||||
|
||||
// Now configue the PLLs and clock outputs.
|
||||
// The PLLs can be configured with a multiplier and division of the on-board
|
||||
// 25mhz reference crystal. For example configure PLL A to 900mhz by multiplying
|
||||
// by 36. This uses an integer multiplier which is more accurate over time
|
||||
// but allows less of a range of frequencies compared to a fractional
|
||||
// multiplier shown next.
|
||||
clockgen.ConfigurePLL(si5351.PLL_A, 36, 0, 1) // Multiply 25mhz by 36
|
||||
println("PLL A frequency: 900mhz")
|
||||
|
||||
// And next configure PLL B to 616.6667mhz by multiplying 25mhz by 24.667 using
|
||||
// the fractional multiplier configuration. Notice you specify the integer
|
||||
// multiplier and then a numerator and denominator as separate values, i.e.
|
||||
// numerator 2 and denominator 3 means 2/3 or 0.667. This fractional
|
||||
// configuration is susceptible to some jitter over time but can set a larger
|
||||
// range of frequencies.
|
||||
clockgen.ConfigurePLL(si5351.PLL_B, 24, 2, 3) // Multiply 25mhz by 24.667 (24 2/3)
|
||||
println("PLL B frequency: 616.6667mhz")
|
||||
|
||||
// Now configure the clock outputs. Each is driven by a PLL frequency as input
|
||||
// and then further divides that down to a specific frequency.
|
||||
// Configure clock 0 output to be driven by PLL A divided by 8, so an output
|
||||
// of 112.5mhz (900mhz / 8). Again this uses the most precise integer division
|
||||
// but can't set as wide a range of values.
|
||||
clockgen.ConfigureMultisynth(0, si5351.PLL_A, 8, 0, 1) // Divide by 8 (8 0/1)
|
||||
println("Clock 0: 112.5mhz")
|
||||
|
||||
// Next configure clock 1 to be driven by PLL B divided by 45.5 to get
|
||||
// 13.5531mhz (616.6667mhz / 45.5). This uses fractional division and again
|
||||
// notice the numerator and denominator are explicitly specified. This is less
|
||||
// precise but allows a large range of frequencies.
|
||||
clockgen.ConfigureMultisynth(1, si5351.PLL_B, 45, 1, 2) // Divide by 45.5 (45 1/2)
|
||||
println("Clock 1: 13.5531mhz")
|
||||
|
||||
// Finally configure clock 2 to be driven by PLL B divided once by 900 to get
|
||||
// down to 685.15 khz and then further divided by a special R divider that
|
||||
// divides 685.15 khz by 64 to get a final output of 10.706khz.
|
||||
clockgen.ConfigureMultisynth(2, si5351.PLL_B, 900, 0, 1) // Divide by 900 (900 0/1)
|
||||
// Set the R divider, this can be a value of:
|
||||
// - R_DIV_1: divider of 1
|
||||
// - R_DIV_2: divider of 2
|
||||
// - R_DIV_4: divider of 4
|
||||
// - R_DIV_8: divider of 8
|
||||
// - R_DIV_16: divider of 16
|
||||
// - R_DIV_32: divider of 32
|
||||
// - R_DIV_64: divider of 64
|
||||
// - R_DIV_128: divider of 128
|
||||
clockgen.ConfigureRdiv(2, si5351.R_DIV_64)
|
||||
println("Clock 2: 10.706khz")
|
||||
|
||||
// After configuring PLLs and clocks, enable the outputs.
|
||||
clockgen.EnableOutputs()
|
||||
|
||||
time.Sleep(time.Second)
|
||||
|
||||
clockgen.DisableOutputs()
|
||||
println("All outputs disabled for 5 seconds")
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
// Now use SetFrequency to re-set the frequencies of the outputs
|
||||
on := false
|
||||
for {
|
||||
if on {
|
||||
println("Setting Clock 0 output off")
|
||||
clockgen.OutputEnable(0, false)
|
||||
on = false
|
||||
} else {
|
||||
println("Setting Clock 0 output to 100mhz")
|
||||
clockgen.SetFrequency(100*machine.MHz, 0, si5351.PLL_A)
|
||||
on = true
|
||||
}
|
||||
time.Sleep(5 * time.Second)
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"machine"
|
||||
"math/rand"
|
||||
|
||||
"tinygo.org/x/drivers/internal/pin"
|
||||
"tinygo.org/x/drivers/ssd1289"
|
||||
)
|
||||
|
||||
@@ -16,7 +17,7 @@ func main() {
|
||||
//consider creating a more efficient bus implementation that uses
|
||||
//your microcontrollers built in "ports"
|
||||
//see rp2040bus.go for an example for the rapsberry pi pico
|
||||
bus := ssd1289.NewPinBus([16]machine.Pin{
|
||||
bus := ssd1289.NewPinBus([16]pin.Output{
|
||||
machine.GP4, //DB0
|
||||
machine.GP5, //DB1
|
||||
machine.GP6, //DB2
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"net"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/netdev"
|
||||
"tinygo.org/x/drivers/w5500"
|
||||
)
|
||||
|
||||
func main() {
|
||||
machine.SPI0.Configure(machine.SPIConfig{
|
||||
Frequency: 33 * machine.MHz,
|
||||
})
|
||||
machine.GPIO17.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
|
||||
eth := w5500.New(machine.SPI0, machine.GPIO17)
|
||||
eth.Configure(w5500.Config{
|
||||
MAC: net.HardwareAddr{0xee, 0xbe, 0xe9, 0xa9, 0xb6, 0x4f},
|
||||
IP: netip.AddrFrom4([4]byte{192, 168, 1, 2}),
|
||||
SubnetMask: netip.AddrFrom4([4]byte{255, 255, 255, 0}),
|
||||
Gateway: netip.AddrFrom4([4]byte{192, 168, 1, 1}),
|
||||
})
|
||||
netdev.UseNetdev(eth)
|
||||
|
||||
for {
|
||||
if eth.LinkStatus() != w5500.LinkStatusUp {
|
||||
println("Waiting for link to be up")
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,6 @@
|
||||
// Guarded because still unsure of how to deal with interrupt drivers.
|
||||
//go:build tinygo
|
||||
|
||||
// Package ft6336 provides a driver for the FT6336 I2C Self-Capacitive touch
|
||||
// panel controller.
|
||||
//
|
||||
|
||||
+5
-5
@@ -210,7 +210,7 @@ func (d *Device) SendCommand(command byte) {
|
||||
d.bus.SetCommandMode(true)
|
||||
d.bus.Write([]byte{command})
|
||||
|
||||
for d.busy(command == DISPLAY_CLEAR || command == CURSOR_HOME) {
|
||||
for d.isBusy(command == DISPLAY_CLEAR || command == CURSOR_HOME) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,7 +219,7 @@ func (d *Device) sendData(data byte) {
|
||||
d.bus.SetCommandMode(false)
|
||||
d.bus.Write([]byte{data})
|
||||
|
||||
for d.busy(false) {
|
||||
for d.isBusy(false) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,9 +231,9 @@ func (d *Device) CreateCharacter(cgramAddr uint8, data []byte) {
|
||||
}
|
||||
}
|
||||
|
||||
// busy returns true when hd447890 is busy
|
||||
// isBusy returns true when hd447890 is isBusy
|
||||
// or after the timeout specified
|
||||
func (d *Device) busy(longDelay bool) bool {
|
||||
func (d *Device) isBusy(longDelay bool) bool {
|
||||
if d.bus.WriteOnly() {
|
||||
// Can't read busy flag if write only, so sleep a bit then return
|
||||
if longDelay {
|
||||
@@ -261,7 +261,7 @@ func (d *Device) busy(longDelay bool) bool {
|
||||
|
||||
// Busy returns true when hd447890 is busy
|
||||
func (d *Device) Busy() bool {
|
||||
return d.busy(false)
|
||||
return d.isBusy(false)
|
||||
}
|
||||
|
||||
// Size returns the current size of the display.
|
||||
|
||||
@@ -1,196 +0,0 @@
|
||||
// Package regmap provides transaction-based interfaces for reading and writing
|
||||
// to device registers over I2C and SPI buses with pre-allocated buffers.
|
||||
package regmap
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
var (
|
||||
// errNotInTx indicates an operation was attempted outside of an active transaction.
|
||||
errNotInTx = errors.New("device not in Tx")
|
||||
|
||||
// errInTx indicates a transaction was started while another is still active.
|
||||
errInTx = errors.New("device already in Tx")
|
||||
|
||||
// errShortWriteBuffer indicates the write buffer is too small for the requested operation.
|
||||
errShortWriteBuffer = errors.New("device write buffer too short")
|
||||
|
||||
// errShortReadBuffer indicates the read buffer is too small for the requested operation.
|
||||
errShortReadBuffer = errors.New("device read buffer too short")
|
||||
)
|
||||
|
||||
// Device8Txer wraps a Device8 to provide buffered transaction support for
|
||||
// I2C and SPI operations. It maintains pre-allocated buffers to avoid heap
|
||||
// allocations during register access operations.
|
||||
//
|
||||
// Users must call SetBuffers to configure the write and read buffers before
|
||||
// initiating transactions.
|
||||
type Device8Txer struct {
|
||||
Device8
|
||||
writeBuf []byte // Pre-allocated buffer for write operations
|
||||
readBuf []byte // Pre-allocated buffer for read operations
|
||||
inTx bool // Tracks whether a transaction is currently active
|
||||
}
|
||||
|
||||
// SetTxBuffers configures the write and read buffers for this device.
|
||||
// These buffers are reused across transactions to avoid heap allocations.
|
||||
//
|
||||
// The writebuf should be large enough to hold the register address plus
|
||||
// all data bytes to be written in a single transaction.
|
||||
func (d *Device8Txer) SetTxBuffers(writebuf, readbuf []byte) {
|
||||
d.readBuf = readbuf
|
||||
d.writeBuf = writebuf
|
||||
}
|
||||
|
||||
// Tx8 represents an active transaction for an 8-bit register device.
|
||||
// It tracks the write buffer and current offset as data is added to the transaction.
|
||||
//
|
||||
// Use AddWriteByte or AddWriteData to add data to the transaction, then call
|
||||
// DoTxI2C or DoTxSPI to execute the transaction over the bus.
|
||||
type Tx8 struct {
|
||||
dw *Device8Txer // Reference to the parent device
|
||||
off int // Current offset in the write buffer
|
||||
}
|
||||
|
||||
// Tx initiates a new transaction for writing to the specified register address.
|
||||
//
|
||||
// Parameters:
|
||||
// - writeAddr: The 8-bit register address to write to
|
||||
//
|
||||
// Returns a Tx8 handle that can be used to add data and execute the transaction.
|
||||
//
|
||||
// Returns an error if:
|
||||
// - A transaction is already active (errInTx)
|
||||
// - The write buffer is too short (errShortWriteBuffer)
|
||||
func (dw *Device8Txer) Tx(writeAddr uint8) (Tx8, error) {
|
||||
if dw.inTx {
|
||||
return Tx8{}, errInTx
|
||||
} else if len(dw.writeBuf) < 1 {
|
||||
return Tx8{}, errShortWriteBuffer
|
||||
}
|
||||
dw.writeBuf[0] = writeAddr
|
||||
return Tx8{dw: dw, off: 1}, nil
|
||||
}
|
||||
|
||||
// AddWriteData appends multiple bytes to the current transaction's write buffer.
|
||||
//
|
||||
// Parameters:
|
||||
// - buf: Variable number of bytes to add to the transaction
|
||||
//
|
||||
// Returns an error if:
|
||||
// - No transaction is active (errNotInTx)
|
||||
// - The write buffer doesn't have enough space (errShortWriteBuffer)
|
||||
func (tx *Tx8) AddWriteData(buf ...byte) error {
|
||||
if !tx.dw.inTx {
|
||||
return errNotInTx
|
||||
}
|
||||
avail := tx.dw.writeBuf[tx.off:]
|
||||
if len(avail) < len(buf) {
|
||||
return errShortWriteBuffer
|
||||
}
|
||||
n := copy(avail, buf)
|
||||
tx.off += n
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddWriteByte appends a single byte to the current transaction's write buffer.
|
||||
//
|
||||
// Parameters:
|
||||
// - b: The byte to add to the transaction
|
||||
//
|
||||
// Returns an error if:
|
||||
// - No transaction is active (errNotInTx)
|
||||
// - The write buffer doesn't have enough space (errShortWriteBuffer)
|
||||
func (tx *Tx8) AddWriteByte(b byte) error {
|
||||
if !tx.dw.inTx {
|
||||
return errNotInTx
|
||||
}
|
||||
avail := tx.dw.writeBuf[tx.off:]
|
||||
if len(avail) < 1 {
|
||||
return errShortWriteBuffer
|
||||
}
|
||||
avail[0] = b
|
||||
tx.off++
|
||||
return nil
|
||||
}
|
||||
|
||||
// DoTxI2C executes the transaction over an I2C bus.
|
||||
//
|
||||
// This performs a combined write-read I2C transaction, first sending the
|
||||
// register address and any data added to the transaction, then reading
|
||||
// the specified number of bytes from the device.
|
||||
//
|
||||
// Parameters:
|
||||
// - bus: The I2C bus to communicate over
|
||||
// - deviceAddr: The I2C address of the target device
|
||||
// - readLength: Number of bytes to read from the device
|
||||
//
|
||||
// Returns the read data as a slice of the internal read buffer, valid until
|
||||
// the next transaction. The transaction is automatically freed after execution.
|
||||
//
|
||||
// Returns an error if:
|
||||
// - No transaction is active (errNotInTx)
|
||||
// - The read buffer is too short (errShortReadBuffer)
|
||||
// - The I2C transaction fails
|
||||
func (tx *Tx8) DoTxI2C(bus drivers.I2C, deviceAddr uint16, readLength int) ([]byte, error) {
|
||||
if tx.off == 0 || !tx.dw.inTx {
|
||||
return nil, errNotInTx
|
||||
}
|
||||
defer tx.freeTx()
|
||||
if len(tx.dw.readBuf) < readLength {
|
||||
return nil, errShortReadBuffer
|
||||
}
|
||||
rbuf := tx.dw.readBuf[:readLength]
|
||||
err := bus.Tx(deviceAddr, tx.dw.writeBuf[:tx.off], rbuf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rbuf, err
|
||||
}
|
||||
|
||||
// DoTxSPI executes the transaction over an SPI bus.
|
||||
//
|
||||
// This performs a full-duplex SPI transaction, simultaneously writing the
|
||||
// register address and data while reading the same number of bytes from the device.
|
||||
//
|
||||
// If no read buffer was configured (readBuf is nil), this performs a write-only
|
||||
// transaction and returns nil without error.
|
||||
//
|
||||
// Parameters:
|
||||
// - bus: The SPI bus to communicate over
|
||||
//
|
||||
// Returns the read data as a slice of the internal read buffer (same length as
|
||||
// the write data), valid until the next transaction. The transaction is
|
||||
// automatically freed after execution.
|
||||
//
|
||||
// Returns an error if:
|
||||
// - No transaction is active (errNotInTx)
|
||||
// - The read buffer is too short (errShortReadBuffer)
|
||||
// - The SPI transaction fails
|
||||
func (tx *Tx8) DoTxSPI(bus drivers.SPI) (readBuf []byte, err error) {
|
||||
if tx.off == 0 || !tx.dw.inTx {
|
||||
return nil, errNotInTx
|
||||
}
|
||||
defer tx.freeTx()
|
||||
if tx.dw.readBuf == nil {
|
||||
err = bus.Tx(tx.dw.writeBuf[:tx.off], nil) // Special case, only use write buffer functionality.
|
||||
return nil, err
|
||||
} else if len(readBuf) < tx.off {
|
||||
return nil, errShortReadBuffer
|
||||
}
|
||||
rbuf := tx.dw.readBuf[:tx.off]
|
||||
err = bus.Tx(tx.dw.writeBuf[:tx.off], rbuf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rbuf, err
|
||||
}
|
||||
|
||||
// freeTx marks the transaction as complete, allowing a new transaction to be started.
|
||||
// This is called internally by DoTxI2C and DoTxSPI after the transaction completes.
|
||||
func (tx *Tx8) freeTx() {
|
||||
tx.dw.inTx = false
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package regmap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
func ExampleDevice8Txer() {
|
||||
// Initialization.
|
||||
var dtx Device8Txer
|
||||
dtx.SetTxBuffers(make([]byte, 256), make([]byte, 256))
|
||||
|
||||
// Usage.
|
||||
const (
|
||||
defaultAddr = 65
|
||||
REG_WRITE = 0x1f
|
||||
IOCTL_CALL = 0xc0
|
||||
)
|
||||
tx, err := dtx.Tx(REG_WRITE)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = tx.AddWriteData(IOCTL_CALL, 0x80, 0x80)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var bus drivers.I2C
|
||||
readData, err := tx.DoTxI2C(bus, defaultAddr, 20)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(readData)
|
||||
}
|
||||
+4
-4
@@ -3,9 +3,9 @@ package max6675
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"machine"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
"tinygo.org/x/drivers/internal/pin"
|
||||
)
|
||||
|
||||
// ErrThermocoupleOpen is returned when the thermocouple input is open.
|
||||
@@ -14,16 +14,16 @@ var ErrThermocoupleOpen = errors.New("thermocouple input open")
|
||||
|
||||
type Device struct {
|
||||
bus drivers.SPI
|
||||
cs machine.Pin
|
||||
cs pin.OutputFunc
|
||||
}
|
||||
|
||||
// Create a new Device to read from a MAX6675 thermocouple.
|
||||
// Pins must be configured before use. Frequency for SPI
|
||||
// should be 4.3MHz maximum.
|
||||
func NewDevice(bus drivers.SPI, cs machine.Pin) *Device {
|
||||
func NewDevice(bus drivers.SPI, cs pin.Output) *Device {
|
||||
return &Device{
|
||||
bus: bus,
|
||||
cs: cs,
|
||||
cs: cs.Set,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+14
-9
@@ -3,31 +3,36 @@
|
||||
package max72xx
|
||||
|
||||
import (
|
||||
"machine"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
"tinygo.org/x/drivers/internal/legacy"
|
||||
"tinygo.org/x/drivers/internal/pin"
|
||||
)
|
||||
|
||||
type Device struct {
|
||||
bus drivers.SPI
|
||||
cs machine.Pin
|
||||
bus drivers.SPI
|
||||
cs pin.OutputFunc
|
||||
configurePins func()
|
||||
}
|
||||
|
||||
// NewDriver creates a new max7219 connection. The SPI wire must already be configured
|
||||
// The SPI frequency must not be higher than 10MHz.
|
||||
// parameter cs: the datasheet also refers to this pin as "load" pin.
|
||||
func NewDevice(bus drivers.SPI, cs machine.Pin) *Device {
|
||||
func NewDevice(bus drivers.SPI, cs pin.Output) *Device {
|
||||
return &Device{
|
||||
bus: bus,
|
||||
cs: cs,
|
||||
cs: cs.Set,
|
||||
configurePins: func() {
|
||||
legacy.ConfigurePinOut(cs)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Configure setups the pins.
|
||||
func (driver *Device) Configure() {
|
||||
outPutConfig := machine.PinConfig{Mode: machine.PinOutput}
|
||||
|
||||
driver.cs.Configure(outPutConfig)
|
||||
if driver.configurePins == nil {
|
||||
panic(legacy.ErrConfigBeforeInstantiated)
|
||||
}
|
||||
driver.configurePins()
|
||||
}
|
||||
|
||||
// SetScanLimit sets the scan limit. Maximum is 8.
|
||||
|
||||
+16
-8
@@ -8,18 +8,20 @@ package mcp2515 // import "tinygo.org/x/drivers/mcp2515"
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
"tinygo.org/x/drivers/internal/legacy"
|
||||
"tinygo.org/x/drivers/internal/pin"
|
||||
)
|
||||
|
||||
// Device wraps MCP2515 SPI CAN Module.
|
||||
type Device struct {
|
||||
spi SPI
|
||||
cs machine.Pin
|
||||
msg *CANMsg
|
||||
mcpMode byte
|
||||
spi SPI
|
||||
cs pin.OutputFunc
|
||||
msg *CANMsg
|
||||
mcpMode byte
|
||||
configurePins func()
|
||||
}
|
||||
|
||||
// CANMsg stores CAN message fields.
|
||||
@@ -36,15 +38,18 @@ const (
|
||||
)
|
||||
|
||||
// New returns a new MCP2515 driver. Pass in a fully configured SPI bus.
|
||||
func New(b drivers.SPI, csPin machine.Pin) *Device {
|
||||
func New(b drivers.SPI, csPin pin.Output) *Device {
|
||||
d := &Device{
|
||||
spi: SPI{
|
||||
bus: b,
|
||||
tx: make([]byte, 0, bufferSize),
|
||||
rx: make([]byte, 0, bufferSize),
|
||||
},
|
||||
cs: csPin,
|
||||
cs: csPin.Set,
|
||||
msg: &CANMsg{},
|
||||
configurePins: func() {
|
||||
legacy.ConfigurePinOut(csPin)
|
||||
},
|
||||
}
|
||||
|
||||
return d
|
||||
@@ -52,7 +57,10 @@ func New(b drivers.SPI, csPin machine.Pin) *Device {
|
||||
|
||||
// Configure sets up the device for communication.
|
||||
func (d *Device) Configure() {
|
||||
d.cs.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
if d.configurePins == nil {
|
||||
panic(legacy.ErrConfigBeforeInstantiated)
|
||||
}
|
||||
d.configurePins()
|
||||
}
|
||||
|
||||
const beginTimeoutValue int = 10
|
||||
|
||||
+8
-8
@@ -6,18 +6,18 @@ package pcd8544 // import "tinygo.org/x/drivers/pcd8544"
|
||||
import (
|
||||
"errors"
|
||||
"image/color"
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
"tinygo.org/x/drivers/internal/pin"
|
||||
)
|
||||
|
||||
// Device wraps an SPI connection.
|
||||
type Device struct {
|
||||
bus drivers.SPI
|
||||
dcPin machine.Pin
|
||||
rstPin machine.Pin
|
||||
scePin machine.Pin
|
||||
dcPin pin.OutputFunc
|
||||
rstPin pin.OutputFunc
|
||||
scePin pin.OutputFunc
|
||||
buffer []byte
|
||||
width int16
|
||||
height int16
|
||||
@@ -30,12 +30,12 @@ type Config struct {
|
||||
}
|
||||
|
||||
// New creates a new PCD8544 connection. The SPI bus must already be configured.
|
||||
func New(bus drivers.SPI, dcPin, rstPin, scePin machine.Pin) *Device {
|
||||
func New(bus drivers.SPI, dcPin, rstPin, scePin pin.Output) *Device {
|
||||
return &Device{
|
||||
bus: bus,
|
||||
dcPin: dcPin,
|
||||
rstPin: rstPin,
|
||||
scePin: scePin,
|
||||
dcPin: dcPin.Set,
|
||||
rstPin: rstPin.Set,
|
||||
scePin: scePin.Set,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -149,6 +149,20 @@ func (img Image[T]) setPixel(index int, c T) {
|
||||
}
|
||||
|
||||
return
|
||||
case zeroColor.BitsPerPixel() == 2:
|
||||
// Grayscale2bit.
|
||||
offset := index / 4 // 4 pixels per byte
|
||||
shift := 6 - (index%4)*2 // bits: 6, 4, 2, 0
|
||||
|
||||
ptr := (*byte)(unsafe.Add(img.data, offset))
|
||||
|
||||
raw := *(*uint8)(unsafe.Pointer(&c))
|
||||
gray := raw & 0b11
|
||||
|
||||
mask := byte(0b11 << shift)
|
||||
*ptr = (*ptr &^ mask) | (gray << shift)
|
||||
return
|
||||
|
||||
case zeroColor.BitsPerPixel()%8 == 0:
|
||||
// Each color starts at a whole byte offset.
|
||||
// This is the easy case.
|
||||
@@ -206,6 +220,13 @@ func (img Image[T]) Get(x, y int) T {
|
||||
ptr := (*byte)(unsafe.Add(img.data, offset))
|
||||
c = ((*ptr >> (7 - uint8(bits))) & 0x1) > 0
|
||||
return any(c).(T)
|
||||
case zeroColor.BitsPerPixel() == 2:
|
||||
// Grayscale2bit.
|
||||
offset := index / 4 // 4 pixels per byte
|
||||
shift := 6 - (index%4)*2 // bits: 6, 4, 2, 0
|
||||
ptr := (*byte)(unsafe.Add(img.data, offset))
|
||||
value := ((*ptr) >> shift) & 0b11
|
||||
return any(Grayscale2bit(value)).(T)
|
||||
case zeroColor.BitsPerPixel()%8 == 0:
|
||||
// Colors like RGB565, RGB888, etc.
|
||||
offset := index * int(unsafe.Sizeof(zeroColor))
|
||||
|
||||
+108
-3
@@ -9,9 +9,30 @@ import (
|
||||
"tinygo.org/x/drivers/pixel"
|
||||
)
|
||||
|
||||
func TestImageRGB888(t *testing.T) {
|
||||
image := pixel.NewImage[pixel.RGB888](5, 3)
|
||||
if width, height := image.Size(); width != 5 || height != 3 {
|
||||
t.Errorf("image.Size(): expected 5, 3 but got %d, %d", width, height)
|
||||
}
|
||||
for _, c := range []color.RGBA{
|
||||
{R: 0xff, A: 0xff},
|
||||
{G: 0xff, A: 0xff},
|
||||
{B: 0xff, A: 0xff},
|
||||
{R: 0x10, A: 0xff},
|
||||
{G: 0x10, A: 0xff},
|
||||
{B: 0x10, A: 0xff},
|
||||
} {
|
||||
image.Set(4, 2, pixel.NewColor[pixel.RGB888](c.R, c.G, c.B))
|
||||
c2 := image.Get(4, 2).RGBA()
|
||||
if c2 != c {
|
||||
t.Errorf("failed to roundtrip color: expected %v but got %v", c, c2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestImageRGB565BE(t *testing.T) {
|
||||
image := pixel.NewImage[pixel.RGB565BE](5, 3)
|
||||
if width, height := image.Size(); width != 5 && height != 3 {
|
||||
if width, height := image.Size(); width != 5 || height != 3 {
|
||||
t.Errorf("image.Size(): expected 5, 3 but got %d, %d", width, height)
|
||||
}
|
||||
for _, c := range []color.RGBA{
|
||||
@@ -30,9 +51,30 @@ func TestImageRGB565BE(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestImageRGB555(t *testing.T) {
|
||||
image := pixel.NewImage[pixel.RGB555](5, 3)
|
||||
if width, height := image.Size(); width != 5 || height != 3 {
|
||||
t.Errorf("image.Size(): expected 5, 3 but got %d, %d", width, height)
|
||||
}
|
||||
for _, c := range []color.RGBA{
|
||||
{R: 0xff, A: 0xff},
|
||||
{G: 0xff, A: 0xff},
|
||||
{B: 0xff, A: 0xff},
|
||||
{R: 0x10, A: 0xff},
|
||||
{G: 0x10, A: 0xff},
|
||||
{B: 0x10, A: 0xff},
|
||||
} {
|
||||
image.Set(4, 2, pixel.NewColor[pixel.RGB555](c.R, c.G, c.B))
|
||||
c2 := image.Get(4, 2).RGBA()
|
||||
if c2 != c {
|
||||
t.Errorf("failed to roundtrip color: expected %v but got %v", c, c2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestImageRGB444BE(t *testing.T) {
|
||||
image := pixel.NewImage[pixel.RGB444BE](5, 3)
|
||||
if width, height := image.Size(); width != 5 && height != 3 {
|
||||
if width, height := image.Size(); width != 5 || height != 3 {
|
||||
t.Errorf("image.Size(): expected 5, 3 but got %d, %d", width, height)
|
||||
}
|
||||
for _, c := range []color.RGBA{
|
||||
@@ -65,9 +107,69 @@ func TestImageRGB444BE(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestImageGrayscale2bit(t *testing.T) {
|
||||
image := pixel.NewImage[pixel.Grayscale2bit](128, 64)
|
||||
|
||||
if width, height := image.Size(); width != 128 || height != 64 {
|
||||
t.Errorf("image.Size(): expected 128, 64 but got %d, %d", width, height)
|
||||
}
|
||||
|
||||
// Define test colors representing 4 Grayscale levels.
|
||||
testColors := []color.RGBA{
|
||||
{R: 0x00, G: 0x00, B: 0x00, A: 0xff}, // black
|
||||
{R: 0x55, G: 0x55, B: 0x55, A: 0xff}, // dark gray
|
||||
{R: 0xaa, G: 0xaa, B: 0xaa, A: 0xff}, // light gray
|
||||
{R: 0xff, G: 0xff, B: 0xff, A: 0xff}, // white
|
||||
}
|
||||
|
||||
// Single pixel roundtrip test at a fixed coordinate.
|
||||
for _, c := range testColors {
|
||||
encoded := pixel.NewColor[pixel.Grayscale2bit](c.R, c.G, c.B)
|
||||
image.Set(5, 3, encoded)
|
||||
actual := image.Get(5, 3).RGBA()
|
||||
if actual != c {
|
||||
t.Errorf("failed to roundtrip color: expected %v but got %v", c, actual)
|
||||
}
|
||||
}
|
||||
|
||||
// Multi-coordinate test across the image.
|
||||
for x := 0; x < 8; x++ {
|
||||
for y, c := range testColors {
|
||||
encoded := pixel.NewColor[pixel.Grayscale2bit](c.R, c.G, c.B)
|
||||
image.Set(x, y, encoded)
|
||||
actual := image.Get(x, y).RGBA()
|
||||
if actual != c {
|
||||
t.Errorf("Set/Get mismatch at (%d,%d): expected %v but got %v", x, y, c, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewGrayscale2bitMapping(t *testing.T) {
|
||||
testCases := []struct {
|
||||
input color.RGBA
|
||||
expect pixel.Grayscale2bit
|
||||
}{
|
||||
{color.RGBA{R: 0x00, G: 0x00, B: 0x00}, 0}, // 0
|
||||
{color.RGBA{R: 0x3F, G: 0x3F, B: 0x3F}, 0}, // 63
|
||||
{color.RGBA{R: 0x40, G: 0x40, B: 0x40}, 1}, // 64
|
||||
{color.RGBA{R: 0x7F, G: 0x7F, B: 0x7F}, 1}, // 127
|
||||
{color.RGBA{R: 0x80, G: 0x80, B: 0x80}, 2}, // 128
|
||||
{color.RGBA{R: 0xBF, G: 0xBF, B: 0xBF}, 2}, // 191
|
||||
{color.RGBA{R: 0xC0, G: 0xC0, B: 0xC0}, 3}, // 192
|
||||
{color.RGBA{R: 0xFF, G: 0xFF, B: 0xFF}, 3}, // 255
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
actual := pixel.NewColor[pixel.Grayscale2bit](tc.input.R, tc.input.G, tc.input.B)
|
||||
if actual != tc.expect {
|
||||
t.Errorf("NewGrayscale2bit(%#v) = %d, want %d", tc.input, actual, tc.expect)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestImageMonochrome(t *testing.T) {
|
||||
image := pixel.NewImage[pixel.Monochrome](128, 64)
|
||||
if width, height := image.Size(); width != 128 && height != 64 {
|
||||
if width, height := image.Size(); width != 128 || height != 64 {
|
||||
t.Errorf("image.Size(): expected 128, 64 but got %d, %d", width, height)
|
||||
}
|
||||
for _, expected := range []color.RGBA{
|
||||
@@ -194,6 +296,9 @@ func TestImageNoise(t *testing.T) {
|
||||
t.Run("RGB444BE", func(t *testing.T) {
|
||||
testImageNoiseN[pixel.RGB444BE](t)
|
||||
})
|
||||
t.Run("Grayscale2bit", func(t *testing.T) {
|
||||
testImageNoiseN[pixel.Grayscale2bit](t)
|
||||
})
|
||||
t.Run("Monochrome", func(t *testing.T) {
|
||||
testImageNoiseN[pixel.Monochrome](t)
|
||||
})
|
||||
|
||||
+34
-4
@@ -16,7 +16,7 @@ import (
|
||||
// particular display. Each pixel is at least 1 byte in size.
|
||||
// The color format is sRGB (or close to it) in all cases except for 1-bit.
|
||||
type Color interface {
|
||||
RGB888 | RGB565BE | RGB555 | RGB444BE | Monochrome
|
||||
RGB888 | RGB565BE | RGB555 | RGB444BE | Grayscale2bit | Monochrome
|
||||
|
||||
BaseColor
|
||||
}
|
||||
@@ -50,6 +50,8 @@ func NewColor[T Color](r, g, b uint8) T {
|
||||
return any(NewRGB555(r, g, b)).(T)
|
||||
case RGB444BE:
|
||||
return any(NewRGB444BE(r, g, b)).(T)
|
||||
case Grayscale2bit:
|
||||
return any(NewGrayscale2bit(r, g, b)).(T)
|
||||
case Monochrome:
|
||||
return any(NewMonochrome(r, g, b)).(T)
|
||||
default:
|
||||
@@ -161,9 +163,9 @@ func (c RGB555) BitsPerPixel() int {
|
||||
|
||||
func (c RGB555) RGBA() color.RGBA {
|
||||
color := color.RGBA{
|
||||
R: uint8(c>>10) << 3,
|
||||
G: uint8(c>>5) << 3,
|
||||
B: uint8(c) << 3,
|
||||
R: (uint8(c) & 0x1F) << 3,
|
||||
G: (uint8(c>>5) & 0x1F) << 3,
|
||||
B: (uint8(c>>10) & 0x1F) << 3,
|
||||
A: 255,
|
||||
}
|
||||
// Correct color rounding, so that 0xff roundtrips back to 0xff.
|
||||
@@ -204,6 +206,34 @@ func (c RGB444BE) RGBA() color.RGBA {
|
||||
return color
|
||||
}
|
||||
|
||||
// Grayscale2bit represents a 2-bit Grayscale value (4 levels: black, dark gray, light gray, white).
|
||||
type Grayscale2bit uint8
|
||||
|
||||
func NewGrayscale2bit(r, g, b uint8) Grayscale2bit {
|
||||
// Convert RGB to luminance using standard weights (approximation of human perception)
|
||||
// Use shift-based operations to reduce processing time.
|
||||
// luminance := (299*uint32(r) + 587*uint32(g) + 114*uint32(b)) / 1000
|
||||
luminance := (77*uint32(r) + 150*uint32(g) + 29*uint32(b)) >> 8
|
||||
// Map to 2-bit value: 0–63 => 0, 64–127 => 1, 128–191 => 2, 192–255 => 3
|
||||
return Grayscale2bit((luminance >> 6) & 0b11)
|
||||
}
|
||||
|
||||
func (c Grayscale2bit) BitsPerPixel() int {
|
||||
return 2
|
||||
}
|
||||
|
||||
func (c Grayscale2bit) RGBA() color.RGBA {
|
||||
// Expand 2-bit Grayscale back to 8-bit (0–255) using multiplication
|
||||
// 0 → 0x00, 1 → 0x55, 2 → 0xAA, 3 → 0xFF (i.e., multiply by 85)
|
||||
gray := uint8(c&0b11) * 85
|
||||
return color.RGBA{
|
||||
R: gray,
|
||||
G: gray,
|
||||
B: gray,
|
||||
A: 255,
|
||||
}
|
||||
}
|
||||
|
||||
type Monochrome bool
|
||||
|
||||
func NewMonochrome(r, g, b uint8) Monochrome {
|
||||
|
||||
@@ -22,4 +22,5 @@ const (
|
||||
CmdStartLowPowerPeriodicMeasurement = 0x21AC
|
||||
CmdStartPeriodicMeasurement = 0x21B1
|
||||
CmdStopPeriodicMeasurement = 0x3F86
|
||||
CmdMeasureSingleShot = 0x219D
|
||||
)
|
||||
|
||||
+46
-8
@@ -82,6 +82,13 @@ func (d *Device) StartLowPowerPeriodicMeasurement() error {
|
||||
return d.sendCommand(CmdStartLowPowerPeriodicMeasurement)
|
||||
}
|
||||
|
||||
// MeasureSingleShot starts a single measurement cycle (SCD41 only). After this
|
||||
// command is complete, the caller should wait for 5000ms before trying to read
|
||||
// the result.
|
||||
func (d *Device) MeasureSingleShot() error {
|
||||
return d.sendCommand(CmdMeasureSingleShot)
|
||||
}
|
||||
|
||||
// ReadData reads the data from the sensor and caches it.
|
||||
func (d *Device) ReadData() error {
|
||||
if err := d.sendCommandWithResult(CmdReadMeasurement, d.rx[0:9]); err != nil {
|
||||
@@ -93,7 +100,18 @@ func (d *Device) ReadData() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update reads new data from the sensor (if new data is available) and caches
|
||||
// it for reading in the CO2, Temperature, and Humidity methods.
|
||||
func (d *Device) Update(measurements drivers.Measurement) error {
|
||||
if measurements&(drivers.Temperature|drivers.Humidity|drivers.Concentration) != 0 {
|
||||
return d.ReadData()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadCO2 returns the CO2 concentration in PPM (parts per million).
|
||||
//
|
||||
// Deprecated: use Update() and CO2() instead.
|
||||
func (d *Device) ReadCO2() (co2 int32, err error) {
|
||||
ok, err := d.DataReady()
|
||||
if err != nil {
|
||||
@@ -105,7 +123,14 @@ func (d *Device) ReadCO2() (co2 int32, err error) {
|
||||
return int32(d.co2), err
|
||||
}
|
||||
|
||||
// CO2 returns last read the CO2 concentration in PPM (parts per million).
|
||||
func (d *Device) CO2() int32 {
|
||||
return int32(d.co2)
|
||||
}
|
||||
|
||||
// ReadTemperature returns the temperature in celsius milli degrees (°C/1000)
|
||||
//
|
||||
// Deprecated: use Update() and Temperature() instead.
|
||||
func (d *Device) ReadTemperature() (temperature int32, err error) {
|
||||
ok, err := d.DataReady()
|
||||
if err != nil {
|
||||
@@ -114,8 +139,14 @@ func (d *Device) ReadTemperature() (temperature int32, err error) {
|
||||
if ok {
|
||||
err = d.ReadData()
|
||||
}
|
||||
return d.Temperature(), err
|
||||
}
|
||||
|
||||
// Temperature returns the last read temperature in celsius milli degrees
|
||||
// (°C/1000).
|
||||
func (d *Device) Temperature() int32 {
|
||||
// temp = -45 + 175 * value / 2¹⁶
|
||||
return (-1 * 45000) + (21875 * (int32(d.temperature)) / 8192), err
|
||||
return (-1 * 45000) + (21875 * (int32(d.temperature)) / 8192)
|
||||
}
|
||||
|
||||
// ReadTempC returns the value in the temperature value in Celsius.
|
||||
@@ -130,6 +161,11 @@ func (d *Device) ReadTempF() float32 {
|
||||
}
|
||||
|
||||
// ReadHumidity returns the current relative humidity in %rH.
|
||||
//
|
||||
// Warning: the value returned here is less precise than the humidity returned
|
||||
// from Humidity()!
|
||||
//
|
||||
// Deprecated: use Update() and Temperature() instead.
|
||||
func (d *Device) ReadHumidity() (humidity int32, err error) {
|
||||
ok, err := d.DataReady()
|
||||
if err != nil {
|
||||
@@ -142,18 +178,20 @@ func (d *Device) ReadHumidity() (humidity int32, err error) {
|
||||
return (25 * int32(d.humidity)) / 16384, err
|
||||
}
|
||||
|
||||
// Humidity returns the relative humidity in hundredths of a percent (in other
|
||||
// words, with a range 0..10_000).
|
||||
//
|
||||
// Warning: the value returned here is of a different scale (more precise) than
|
||||
// ReadHumidity()!
|
||||
func (d *Device) Humidity() int32 {
|
||||
return (2500 * int32(d.humidity)) / 16384
|
||||
}
|
||||
|
||||
func (d *Device) sendCommand(command uint16) error {
|
||||
binary.BigEndian.PutUint16(d.tx[0:], command)
|
||||
return d.bus.Tx(uint16(d.Address), d.tx[0:2], nil)
|
||||
}
|
||||
|
||||
func (d *Device) sendCommandWithValue(command, value uint16) error {
|
||||
binary.BigEndian.PutUint16(d.tx[0:], command)
|
||||
binary.BigEndian.PutUint16(d.tx[2:], value)
|
||||
d.tx[4] = crc8(d.tx[2:4])
|
||||
return d.bus.Tx(uint16(d.Address), d.tx[0:5], nil)
|
||||
}
|
||||
|
||||
func (d *Device) sendCommandWithResult(command uint16, result []byte) error {
|
||||
binary.BigEndian.PutUint16(d.tx[0:], command)
|
||||
if err := d.bus.Tx(uint16(d.Address), d.tx[0:2], nil); err != nil {
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
package si5351
|
||||
|
||||
// The I2C address which this device listens to.
|
||||
const AddressDefault = 0x60 // Assumes ADDR pin is low
|
||||
const AddressAlternative = 0x61 // Assumes ADDR pin is high
|
||||
|
||||
const (
|
||||
OUTPUT_ENABLE_CONTROL = 3
|
||||
|
||||
CLK0_CONTROL = 16
|
||||
CLK1_CONTROL = 17
|
||||
CLK2_CONTROL = 18
|
||||
CLK3_CONTROL = 19
|
||||
CLK4_CONTROL = 20
|
||||
CLK5_CONTROL = 21
|
||||
CLK6_CONTROL = 22
|
||||
CLK7_CONTROL = 23
|
||||
|
||||
MULTISYNTH0_PARAMETERS_1 = 42
|
||||
MULTISYNTH0_PARAMETERS_3 = 44
|
||||
MULTISYNTH1_PARAMETERS_1 = 50
|
||||
MULTISYNTH1_PARAMETERS_3 = 52
|
||||
MULTISYNTH2_PARAMETERS_1 = 58
|
||||
MULTISYNTH2_PARAMETERS_3 = 60
|
||||
|
||||
SPREAD_SPECTRUM_PARAMETERS = 149
|
||||
|
||||
PLL_RESET = 177
|
||||
|
||||
CRYSTAL_INTERNAL_LOAD_CAPACITANCE = 183
|
||||
|
||||
FANOUT_ENABLE = 187
|
||||
)
|
||||
|
||||
const (
|
||||
CRYSTAL_LOAD_6PF = (1 << 6)
|
||||
CRYSTAL_LOAD_8PF = (2 << 6)
|
||||
CRYSTAL_LOAD_10PF = (3 << 6)
|
||||
)
|
||||
|
||||
const (
|
||||
CRYSTAL_FREQ_25MHZ = 25000000
|
||||
CRYSTAL_FREQ_27MHZ = 27000000
|
||||
)
|
||||
|
||||
const (
|
||||
PLL_A = iota
|
||||
PLL_B
|
||||
)
|
||||
|
||||
const (
|
||||
R_DIV_1 = iota
|
||||
R_DIV_2
|
||||
R_DIV_4
|
||||
R_DIV_8
|
||||
R_DIV_16
|
||||
R_DIV_32
|
||||
R_DIV_64
|
||||
R_DIV_128
|
||||
)
|
||||
|
||||
const (
|
||||
MULTISYNTH_DIV_4 = 4
|
||||
MULTISYNTH_DIV_6 = 6
|
||||
MULTISYNTH_DIV_8 = 8
|
||||
)
|
||||
|
||||
// Frequency constants (in Hz)
|
||||
const (
|
||||
CLKOUT_MIN_FREQ = 8000 // 8 kHz
|
||||
CLKOUT_MAX_FREQ = 150000000 // 150 MHz
|
||||
MULTISYNTH_MAX_FREQ = 150000000 // 150 MHz
|
||||
MULTISYNTH_SHARE_MAX = 100000000 // 100 MHz
|
||||
MULTISYNTH_DIVBY4_FREQ = 150000000 // 150 MHz
|
||||
PLL_VCO_MIN = 600000000 // 600 MHz
|
||||
PLL_VCO_MAX = 900000000 // 900 MHz
|
||||
)
|
||||
|
||||
const (
|
||||
SI5351_PLL_C_MAX = 1048575
|
||||
)
|
||||
|
||||
// Bit masks for FANOUT_ENABLE register
|
||||
const (
|
||||
CLKIN_ENABLE = (1 << 7)
|
||||
XTAL_ENABLE = (1 << 6)
|
||||
MULTISYNTH_ENABLE = (1 << 4)
|
||||
)
|
||||
|
||||
const (
|
||||
FANOUT_CLKIN = iota
|
||||
FANOUT_XO
|
||||
FANOUT_MS
|
||||
)
|
||||
|
||||
// Clock source selection masks for CLKx_CONTROL registers
|
||||
const (
|
||||
CLK_INPUT_MASK = 0x3 // Bits 0 and 1
|
||||
CLK_INPUT_XTAL = 0x0
|
||||
CLK_INPUT_CLKIN = 0x1
|
||||
CLK_INPUT_MULTISYNTH_0_4 = 0x2
|
||||
CLK_INPUT_MULTISYNTH_N = 0x3
|
||||
)
|
||||
|
||||
// Clock source selection
|
||||
const (
|
||||
CLK_SRC_XTAL = iota
|
||||
CLK_SRC_CLKIN
|
||||
CLK_SRC_MS0
|
||||
CLK_SRC_MS
|
||||
)
|
||||
|
||||
const (
|
||||
CLK_INVERT = 1 << 4
|
||||
)
|
||||
|
||||
const (
|
||||
CLK_DRIVE_STRENGTH_2MA = 0
|
||||
CLK_DRIVE_STRENGTH_4MA = 1
|
||||
CLK_DRIVE_STRENGTH_6MA = 2
|
||||
CLK_DRIVE_STRENGTH_8MA = 3
|
||||
)
|
||||
@@ -0,0 +1,789 @@
|
||||
package si5351
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
"tinygo.org/x/drivers/internal/regmap"
|
||||
)
|
||||
|
||||
// Device wraps an I2C connection to a SI5351 device.
|
||||
type Device struct {
|
||||
bus drivers.I2C
|
||||
Address uint8
|
||||
|
||||
rw regmap.Device8I2C
|
||||
initialised bool
|
||||
crystalFreq uint32
|
||||
crystalLoad uint8
|
||||
pllaConfigured bool
|
||||
pllaFreq uint32
|
||||
pllbConfigured bool
|
||||
pllbFreq uint32
|
||||
lastRdivValue [3]uint8
|
||||
}
|
||||
|
||||
var ErrNotInitialised = errors.New("Si5351 not initialised")
|
||||
var ErrInvalidParameter = errors.New("Si5351 invalid parameter")
|
||||
|
||||
// New creates a new SI5351 connection. The I2C bus must already be configured.
|
||||
//
|
||||
// This function only creates the Device object, it does not touch the device.
|
||||
func New(bus drivers.I2C) Device {
|
||||
rw := regmap.Device8I2C{}
|
||||
rw.SetBus(bus, AddressDefault, binary.BigEndian)
|
||||
|
||||
return Device{
|
||||
bus: bus,
|
||||
rw: rw,
|
||||
Address: AddressDefault,
|
||||
crystalFreq: CRYSTAL_FREQ_25MHZ,
|
||||
crystalLoad: CRYSTAL_LOAD_10PF,
|
||||
}
|
||||
}
|
||||
|
||||
// Configure sets up the device for communication
|
||||
// TODO error handling
|
||||
func (d *Device) Configure() error {
|
||||
// // Disable all outputs setting CLKx_DIS high
|
||||
d.rw.Write8(OUTPUT_ENABLE_CONTROL, 0xFF)
|
||||
|
||||
// Set the load capacitance for the XTAL
|
||||
d.rw.Write8(CRYSTAL_INTERNAL_LOAD_CAPACITANCE, d.crystalLoad)
|
||||
|
||||
// Power down all output drivers
|
||||
buf := []byte{CLK0_CONTROL, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}
|
||||
d.bus.Tx(uint16(d.Address), buf, nil)
|
||||
|
||||
// Disable spread spectrum output.
|
||||
if err := d.DisableSpreadSpectrum(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.initialised = true
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Connected returns whether a device at SI5351 address has been found.
|
||||
func (d *Device) Connected() (bool, error) {
|
||||
if err := d.bus.Tx(uint16(d.Address), []byte{}, []byte{0}); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// EnableSpreadSpectrum enables spread spectrum modulation to reduce EMI.
|
||||
func (d *Device) EnableSpreadSpectrum() error {
|
||||
data, err := d.rw.Read8(SPREAD_SPECTRUM_PARAMETERS)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data |= 0x80
|
||||
return d.rw.Write8(SPREAD_SPECTRUM_PARAMETERS, data)
|
||||
}
|
||||
|
||||
func (d *Device) DisableSpreadSpectrum() error {
|
||||
data, err := d.rw.Read8(SPREAD_SPECTRUM_PARAMETERS)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data &^= 0x80
|
||||
return d.rw.Write8(SPREAD_SPECTRUM_PARAMETERS, data)
|
||||
}
|
||||
|
||||
func (d *Device) OutputEnable(output uint8, enable bool) error {
|
||||
if !d.initialised {
|
||||
return ErrNotInitialised
|
||||
}
|
||||
|
||||
// Read the current value of the OUTPUT_ENABLE_CONTROL register
|
||||
regVal, err := d.rw.Read8(OUTPUT_ENABLE_CONTROL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Modify regVal based on clk and enable
|
||||
if enable {
|
||||
regVal &= ^(1 << output)
|
||||
} else {
|
||||
regVal |= (1 << output)
|
||||
}
|
||||
|
||||
// Write the modified value back to the OUTPUT_ENABLE_CONTROL register
|
||||
return d.rw.Write8(OUTPUT_ENABLE_CONTROL, regVal)
|
||||
}
|
||||
|
||||
func (d *Device) EnableOutputs() error {
|
||||
if !d.initialised {
|
||||
return ErrNotInitialised
|
||||
}
|
||||
|
||||
return d.rw.Write8(OUTPUT_ENABLE_CONTROL, 0x00)
|
||||
}
|
||||
|
||||
func (d *Device) DisableOutputs() error {
|
||||
if !d.initialised {
|
||||
return ErrNotInitialised
|
||||
}
|
||||
return d.rw.Write8(OUTPUT_ENABLE_CONTROL, 0xFF)
|
||||
}
|
||||
|
||||
// packRegSet packs P1, P2, P3 values into the 8-byte register format
|
||||
// used by both PLL and Multisynth configuration.
|
||||
// For multisynth, rDivBits should contain the R divider value shifted left by 4.
|
||||
// For PLL, rDivBits should be 0.
|
||||
func packRegSet(p1, p2, p3 uint32, rDivBits uint8) [8]byte {
|
||||
var data [8]byte
|
||||
data[0] = uint8((p3 & 0xFF00) >> 8)
|
||||
data[1] = uint8(p3 & 0xFF)
|
||||
data[2] = uint8((p1&0x30000)>>16) | rDivBits
|
||||
data[3] = uint8((p1 & 0xFF00) >> 8)
|
||||
data[4] = uint8(p1 & 0xFF)
|
||||
data[5] = uint8(((p3 & 0xF0000) >> 12) | ((p2 & 0xF0000) >> 16))
|
||||
data[6] = uint8((p2 & 0xFF00) >> 8)
|
||||
data[7] = uint8(p2 & 0xFF)
|
||||
return data
|
||||
}
|
||||
|
||||
// ConfigurePLL sets the multiplier for the specified PLL
|
||||
// pll The PLL to configure, which must be one of the following:
|
||||
// - PLL_A
|
||||
// - PLL_B
|
||||
//
|
||||
// mult The PLL integer multiplier (must be between 15 and 90)
|
||||
//
|
||||
// num The 20-bit numerator for fractional output (0..1,048,575).
|
||||
// Set this to '0' for integer output.
|
||||
//
|
||||
// denom The 20-bit denominator for fractional output (1..1,048,575).
|
||||
// Set this to '1' or higher to avoid divider by zero errors.
|
||||
//
|
||||
// PLL Configuration
|
||||
// fVCO is the PLL output, and must be between 600..900MHz, where:
|
||||
//
|
||||
// fVCO = fXTAL * (a+(b/c))
|
||||
//
|
||||
// fXTAL = the crystal input frequency
|
||||
// a = an integer between 15 and 90
|
||||
// b = the fractional numerator (0..1,048,575)
|
||||
// c = the fractional denominator (1..1,048,575)
|
||||
//
|
||||
// NOTE: Try to use integers whenever possible to avoid clock jitter
|
||||
// (only use the a part, setting b to '0' and c to '1').
|
||||
//
|
||||
// See: http://www.silabs.com/Support%20Documents/TechnicalDocs/AN619.pdf
|
||||
func (d *Device) ConfigurePLL(pll uint8, mult uint8, num uint32, denom uint32) error {
|
||||
// Basic validation
|
||||
switch {
|
||||
case !d.initialised:
|
||||
return ErrNotInitialised
|
||||
// mult = 15..90
|
||||
case !((mult > 14) && (mult < 91)):
|
||||
return ErrInvalidParameter
|
||||
// Avoid divide by zero
|
||||
case !(denom > 0):
|
||||
return ErrInvalidParameter
|
||||
// 20-bit limit
|
||||
case !(num <= 0xFFFFF):
|
||||
return ErrInvalidParameter
|
||||
// 20-bit limit
|
||||
case !(denom <= 0xFFFFF):
|
||||
return ErrInvalidParameter
|
||||
}
|
||||
|
||||
// Calculate PLL register values
|
||||
var p1, p2, p3 uint32
|
||||
if num == 0 {
|
||||
// Integer mode
|
||||
p1 = 128*uint32(mult) - 512
|
||||
p2 = num
|
||||
p3 = denom
|
||||
} else {
|
||||
// Fractional mode
|
||||
p1 = uint32(128*float64(mult) + math.Floor(128*(float64(num)/float64(denom))) - 512)
|
||||
p2 = uint32(128*float64(num) - float64(denom)*math.Floor(128*(float64(num)/float64(denom))))
|
||||
p3 = denom
|
||||
}
|
||||
|
||||
// Get the appropriate starting point for the PLL registers
|
||||
baseaddr := uint8(26)
|
||||
if pll == PLL_B {
|
||||
baseaddr = 34
|
||||
}
|
||||
|
||||
// Pack and write registers
|
||||
data := packRegSet(p1, p2, p3, 0)
|
||||
if err := d.bus.Tx(uint16(baseaddr), data[:], nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Reset both PLLs
|
||||
if err := d.rw.Write8(PLL_RESET, (1<<7)|(1<<5)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Store the frequency settings for use with the Multisynth helper
|
||||
fvco := float64(d.crystalFreq) * (float64(mult) + (float64(num) / float64(denom)))
|
||||
if pll == PLL_A {
|
||||
d.pllaConfigured = true
|
||||
d.pllaFreq = uint32(math.Floor(fvco))
|
||||
} else {
|
||||
d.pllbConfigured = true
|
||||
d.pllbFreq = uint32(math.Floor(fvco))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ConfigureMultisynth divider, which determines the
|
||||
// output clock frequency based on the specified PLL input.
|
||||
//
|
||||
// output The output channel to use (0..2)
|
||||
//
|
||||
// pll The PLL input source to use, which must be one of:
|
||||
// - PLL_A
|
||||
// - PLL_B
|
||||
//
|
||||
// div The integer divider for the Multisynth output.
|
||||
//
|
||||
// If pure integer values are used, this value must be one of:
|
||||
// - MULTISYNTH_DIV_4
|
||||
// - MULTISYNTH_DIV_6
|
||||
// - MULTISYNTH_DIV_8
|
||||
// If fractional output is used, this value must be between 8 and 900.
|
||||
//
|
||||
// num The 20-bit numerator for fractional output (0..1,048,575).
|
||||
//
|
||||
// Set this to '0' for integer output.
|
||||
//
|
||||
// denom The 20-bit denominator for fractional output (1..1,048,575).
|
||||
//
|
||||
// Set this to '1' or higher to avoid divide by zero errors.
|
||||
//
|
||||
// # Output Clock Configuration
|
||||
//
|
||||
// The multisynth dividers are applied to the specified PLL output,
|
||||
// and are used to reduce the PLL output to a valid range (500kHz
|
||||
// to 160MHz). The relationship can be seen in this formula, where
|
||||
// fVCO is the PLL output frequency and MSx is the multisynth divider:
|
||||
//
|
||||
// fOUT = fVCO / MSx
|
||||
//
|
||||
// Valid multisynth dividers are 4, 6, or 8 when using integers,
|
||||
// or any fractional values between 8 + 1/1,048,575 and 900 + 0/1
|
||||
// The following formula is used for the fractional mode divider:
|
||||
//
|
||||
// a + b / c
|
||||
//
|
||||
// a = The integer value, which must be 4, 6 or 8 in integer mode (MSx_INT=1) or 8..900 in fractional mode (MSx_INT=0).
|
||||
// b = The fractional numerator (0..1,048,575)
|
||||
// c = The fractional denominator (1..1,048,575)
|
||||
//
|
||||
// NOTE: Try to use integers whenever possible to avoid clock jitter
|
||||
// NOTE: For output frequencies > 150MHz, you must set the divider
|
||||
//
|
||||
// to 4 and adjust to PLL to generate the frequency (for example
|
||||
// a PLL of 640 to generate a 160MHz output clock). This is not
|
||||
// yet supported in the driver, which limits frequencies to 500kHz .. 150MHz.
|
||||
//
|
||||
// NOTE: For frequencies below 500kHz (down to 8kHz) Rx_DIV must be
|
||||
//
|
||||
// used, but this isn't currently implemented in the driver.
|
||||
func (d *Device) ConfigureMultisynth(output uint8, pll uint8, div uint32, num uint32, denom uint32) error {
|
||||
// Basic validation
|
||||
switch {
|
||||
case !d.initialised:
|
||||
return ErrNotInitialised
|
||||
// Channel range
|
||||
case !(output < 3):
|
||||
return fmt.Errorf("output channel must be between 0 and 2")
|
||||
// Divider integer value
|
||||
case !((div > 3) && (div < 2049)):
|
||||
return ErrInvalidParameter
|
||||
// Avoid divide by zero
|
||||
case !(denom > 0):
|
||||
return ErrInvalidParameter
|
||||
// 20-bit limit
|
||||
case !(num <= 0xFFFFF):
|
||||
return ErrInvalidParameter
|
||||
// 20-bit limit
|
||||
case !(denom <= 0xFFFFF):
|
||||
return ErrInvalidParameter
|
||||
// Make sure the requested PLL has been initialised
|
||||
case pll == PLL_A && !d.pllaConfigured:
|
||||
return ErrInvalidParameter
|
||||
case pll == PLL_B && !d.pllbConfigured:
|
||||
return ErrInvalidParameter
|
||||
}
|
||||
|
||||
// Calculate register values
|
||||
var reg si5351RegSet
|
||||
switch {
|
||||
case num == 0:
|
||||
// Integer mode
|
||||
reg.p1 = 128*div - 512
|
||||
reg.p2 = 0
|
||||
reg.p3 = denom
|
||||
case denom == 1:
|
||||
// Fractional mode, simplified calculations
|
||||
reg.p1 = 128*div + 128*num - 512
|
||||
reg.p2 = 128*num - 128
|
||||
reg.p3 = 1
|
||||
default:
|
||||
// Fractional mode
|
||||
reg.p1 = uint32(128*float64(div) + math.Floor(128*(float64(num)/float64(denom))) - 512)
|
||||
reg.p2 = uint32(128*float64(num) - float64(denom)*math.Floor(128*(float64(num)/float64(denom))))
|
||||
reg.p3 = denom
|
||||
}
|
||||
|
||||
// Determine if we should use integer mode
|
||||
intMode := num == 0
|
||||
|
||||
// Use existing R divider value (0 if not previously set)
|
||||
rDiv := d.lastRdivValue[output] >> 4
|
||||
|
||||
return d.setMS(output, reg, intMode, rDiv, pll)
|
||||
}
|
||||
|
||||
func (d *Device) ConfigureRdiv(output uint8, div uint8) error {
|
||||
// Channel range
|
||||
if !(output < 3) {
|
||||
return ErrInvalidParameter
|
||||
}
|
||||
|
||||
var register uint8
|
||||
switch output {
|
||||
case 0:
|
||||
register = MULTISYNTH0_PARAMETERS_3
|
||||
case 1:
|
||||
register = MULTISYNTH1_PARAMETERS_3
|
||||
case 2:
|
||||
register = MULTISYNTH2_PARAMETERS_3
|
||||
}
|
||||
|
||||
data, err := d.rw.Read8(register)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.lastRdivValue[output] = (div & 0x07) << 4
|
||||
data = (data & 0x0F) | d.lastRdivValue[output]
|
||||
return d.rw.Write8(register, data)
|
||||
}
|
||||
|
||||
// si5351RegSet holds the register values for multisynth configuration
|
||||
type si5351RegSet struct {
|
||||
p1 uint32
|
||||
p2 uint32
|
||||
p3 uint32
|
||||
}
|
||||
|
||||
var ErrFrequencyOutOfRange = errors.New("Si5351 frequency out of range")
|
||||
var ErrClockConflict = errors.New("Si5351 clock conflict with existing configuration")
|
||||
|
||||
// SetFrequency sets the clock frequency of the specified CLK output.
|
||||
// Frequency range is 8 kHz to 150 MHz.
|
||||
//
|
||||
// freq - Output frequency in Hz
|
||||
// output - Clock output (0, 1, or 2 for this driver)
|
||||
// pll - The PLL to use (PLL_A or PLL_B)
|
||||
func (d *Device) SetFrequency(freq uint64, output uint8, pll uint8) error {
|
||||
switch {
|
||||
case !d.initialised:
|
||||
return ErrNotInitialised
|
||||
case output > 2:
|
||||
return ErrInvalidParameter
|
||||
}
|
||||
|
||||
switch {
|
||||
// Lower bounds check
|
||||
case freq < CLKOUT_MIN_FREQ:
|
||||
freq = CLKOUT_MIN_FREQ
|
||||
// Upper bounds check
|
||||
case freq > MULTISYNTH_MAX_FREQ:
|
||||
freq = MULTISYNTH_MAX_FREQ
|
||||
}
|
||||
|
||||
// Select the proper R divider value for low frequencies
|
||||
rDiv := d.selectRDiv(&freq)
|
||||
|
||||
// Calculate PLL and multisynth parameters
|
||||
var pllFreq uint64
|
||||
switch {
|
||||
case pll == PLL_A && d.pllaConfigured:
|
||||
pllFreq = uint64(d.pllaFreq)
|
||||
case pll == PLL_B && d.pllbConfigured:
|
||||
pllFreq = uint64(d.pllbFreq)
|
||||
default:
|
||||
// PLL not configured, calculate optimal PLL frequency
|
||||
pllFreq = d.calculatePLLFreq(freq)
|
||||
}
|
||||
|
||||
// Calculate multisynth divider parameters
|
||||
msReg := d.multisynthCalc(freq, pllFreq)
|
||||
|
||||
// Determine if we should use integer mode
|
||||
intMode := msReg.p2 == 0
|
||||
|
||||
// Configure PLL if not already configured or if we need a new frequency
|
||||
if (pll == PLL_A && !d.pllaConfigured) || (pll == PLL_B && !d.pllbConfigured) {
|
||||
if err := d.setPLL(pllFreq, pll); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Set multisynth registers
|
||||
if err := d.setMS(output, msReg, intMode, rDiv, pll); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Enable output
|
||||
return d.OutputEnable(output, true)
|
||||
}
|
||||
|
||||
// selectRDiv selects the appropriate R divider for low frequencies
|
||||
// and modifies the frequency accordingly
|
||||
func (d *Device) selectRDiv(freq *uint64) uint8 {
|
||||
var rDiv uint8 = 0
|
||||
|
||||
if *freq >= CLKOUT_MIN_FREQ && *freq < CLKOUT_MIN_FREQ*2 {
|
||||
rDiv = R_DIV_128
|
||||
*freq *= 128
|
||||
} else if *freq >= CLKOUT_MIN_FREQ*2 && *freq < CLKOUT_MIN_FREQ*4 {
|
||||
rDiv = R_DIV_64
|
||||
*freq *= 64
|
||||
} else if *freq >= CLKOUT_MIN_FREQ*4 && *freq < CLKOUT_MIN_FREQ*8 {
|
||||
rDiv = R_DIV_32
|
||||
*freq *= 32
|
||||
} else if *freq >= CLKOUT_MIN_FREQ*8 && *freq < CLKOUT_MIN_FREQ*16 {
|
||||
rDiv = R_DIV_16
|
||||
*freq *= 16
|
||||
} else if *freq >= CLKOUT_MIN_FREQ*16 && *freq < CLKOUT_MIN_FREQ*32 {
|
||||
rDiv = R_DIV_8
|
||||
*freq *= 8
|
||||
} else if *freq >= CLKOUT_MIN_FREQ*32 && *freq < CLKOUT_MIN_FREQ*64 {
|
||||
rDiv = R_DIV_4
|
||||
*freq *= 4
|
||||
} else if *freq >= CLKOUT_MIN_FREQ*64 && *freq < CLKOUT_MIN_FREQ*128 {
|
||||
rDiv = R_DIV_2
|
||||
*freq *= 2
|
||||
}
|
||||
|
||||
return rDiv
|
||||
}
|
||||
|
||||
// calculatePLLFreq calculates an optimal PLL frequency for the given output frequency
|
||||
func (d *Device) calculatePLLFreq(freq uint64) uint64 {
|
||||
// Try to find an integer divider that puts PLL in valid range (600-900 MHz)
|
||||
// Start with a divider that gives us a PLL freq near 750 MHz (middle of range)
|
||||
targetPLL := uint64(750000000)
|
||||
divider := targetPLL / freq
|
||||
|
||||
// Ensure divider is in valid range (8-900 for fractional, 4/6/8 for integer)
|
||||
|
||||
switch {
|
||||
case divider < 8:
|
||||
divider = 8
|
||||
case divider > 900:
|
||||
divider = 900
|
||||
}
|
||||
|
||||
pllFreq := freq * divider
|
||||
|
||||
// Ensure PLL frequency is in valid range
|
||||
switch {
|
||||
case pllFreq < PLL_VCO_MIN:
|
||||
pllFreq = PLL_VCO_MIN
|
||||
case pllFreq > PLL_VCO_MAX:
|
||||
pllFreq = PLL_VCO_MAX
|
||||
}
|
||||
|
||||
return pllFreq
|
||||
}
|
||||
|
||||
// multisynthCalc calculates the multisynth register values
|
||||
func (d *Device) multisynthCalc(freq, pllFreq uint64) si5351RegSet {
|
||||
var reg si5351RegSet
|
||||
|
||||
// Calculate the division ratio
|
||||
// divider = pllFreq / freq
|
||||
a := uint32(pllFreq / freq)
|
||||
remainder := pllFreq % freq
|
||||
|
||||
// Calculate b and c for fractional part
|
||||
// We use c = SI5351_PLL_C_MAX (max 20-bit value) for best resolution
|
||||
c := uint32(SI5351_PLL_C_MAX)
|
||||
b := uint32((uint64(remainder) * uint64(c)) / freq)
|
||||
|
||||
// Calculate P1, P2, P3
|
||||
// P1 = 128 * a + floor(128 * b / c) - 512
|
||||
// P2 = 128 * b - c * floor(128 * b / c)
|
||||
// P3 = c
|
||||
floor128bc := uint32((128 * uint64(b)) / uint64(c))
|
||||
|
||||
reg.p1 = 128*a + floor128bc - 512
|
||||
reg.p2 = 128*b - c*floor128bc
|
||||
reg.p3 = c
|
||||
|
||||
return reg
|
||||
}
|
||||
|
||||
// setPLL configures the PLL with the specified frequency
|
||||
func (d *Device) setPLL(pllFreq uint64, pll uint8) error {
|
||||
// Calculate PLL multiplier from crystal frequency
|
||||
// pllFreq = crystalFreq * (a + b/c)
|
||||
xtalFreq := uint64(d.crystalFreq)
|
||||
|
||||
a := uint32(pllFreq / xtalFreq)
|
||||
remainder := pllFreq % xtalFreq
|
||||
|
||||
// Use max denominator for best resolution
|
||||
c := uint32(SI5351_PLL_C_MAX)
|
||||
b := uint32((remainder * uint64(c)) / xtalFreq)
|
||||
|
||||
return d.ConfigurePLL(pll, uint8(a), b, c)
|
||||
}
|
||||
|
||||
// setMS sets the multisynth registers for the specified output
|
||||
func (d *Device) setMS(output uint8, reg si5351RegSet, intMode bool, rDiv uint8, pll uint8) error {
|
||||
// Get the appropriate starting point for the registers
|
||||
var baseaddr uint8
|
||||
switch output {
|
||||
case 0:
|
||||
baseaddr = MULTISYNTH0_PARAMETERS_1
|
||||
case 1:
|
||||
baseaddr = MULTISYNTH1_PARAMETERS_1
|
||||
case 2:
|
||||
baseaddr = MULTISYNTH2_PARAMETERS_1
|
||||
default:
|
||||
return ErrInvalidParameter
|
||||
}
|
||||
|
||||
// Store R divider value
|
||||
d.lastRdivValue[output] = (rDiv & 0x07) << 4
|
||||
|
||||
// Pack and write registers
|
||||
data := packRegSet(reg.p1, reg.p2, reg.p3, d.lastRdivValue[output])
|
||||
if err := d.bus.Tx(uint16(baseaddr), data[:], nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Configure the clk control register
|
||||
clkControlReg := uint8(0x0F) // 8mA drive strength, powered up
|
||||
if pll == PLL_B {
|
||||
clkControlReg |= (1 << 5) // Use PLLB
|
||||
}
|
||||
if intMode {
|
||||
clkControlReg |= (1 << 6) // Integer mode
|
||||
}
|
||||
|
||||
var clkReg uint8
|
||||
switch output {
|
||||
case 0:
|
||||
clkReg = CLK0_CONTROL
|
||||
case 1:
|
||||
clkReg = CLK1_CONTROL
|
||||
case 2:
|
||||
clkReg = CLK2_CONTROL
|
||||
}
|
||||
|
||||
return d.rw.Write8(clkReg, clkControlReg)
|
||||
}
|
||||
|
||||
// GetFreqStep returns the frequency step size of the radio in Hz.
|
||||
// This is the smallest frequency increment that can be achieved,
|
||||
// determined by the PLL frequency and denominator resolution.
|
||||
// If pll is PLL_A, uses PLLA settings; if PLL_B, uses PLLB settings.
|
||||
// Returns 0 if the specified PLL is not configured.
|
||||
func (d *Device) GetFreqStep(pll uint8) uint64 {
|
||||
// The frequency step at the output is:
|
||||
// step = pllFreq / (SI5351_PLL_C_MAX * multisynth_divider)
|
||||
//
|
||||
// However, since multisynth divider varies per output, we return
|
||||
// the base step from the PLL, which is:
|
||||
// step = pllFreq / SI5351_PLL_C_MAX
|
||||
|
||||
var pllFreq uint64
|
||||
|
||||
switch pll {
|
||||
case PLL_A:
|
||||
if !d.pllaConfigured {
|
||||
return 0
|
||||
}
|
||||
pllFreq = uint64(d.pllaFreq)
|
||||
case PLL_B:
|
||||
if !d.pllbConfigured {
|
||||
return 0
|
||||
}
|
||||
pllFreq = uint64(d.pllbFreq)
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
|
||||
return pllFreq / SI5351_PLL_C_MAX
|
||||
}
|
||||
|
||||
// SetClockFanout enables or disables the specified clock fanout.
|
||||
// fanout - The fanout to configure, which must be one of:
|
||||
// - FANOUT_CLKIN
|
||||
// - FANOUT_XO
|
||||
// - FANOUT_MS
|
||||
//
|
||||
// enable - true to enable the fanout, false to disable it.
|
||||
func (d *Device) SetClockFanout(fanout int, enable bool) error {
|
||||
if !d.initialised {
|
||||
return ErrNotInitialised
|
||||
}
|
||||
|
||||
// Read the current value of the FANOUT_ENABLE register
|
||||
regVal, err := d.rw.Read8(FANOUT_ENABLE)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Modify regVal based on fanout and enable
|
||||
switch fanout {
|
||||
case FANOUT_CLKIN:
|
||||
if enable {
|
||||
regVal |= CLKIN_ENABLE
|
||||
} else {
|
||||
regVal &^= CLKIN_ENABLE
|
||||
}
|
||||
case FANOUT_XO:
|
||||
if enable {
|
||||
regVal |= XTAL_ENABLE
|
||||
} else {
|
||||
regVal &^= XTAL_ENABLE
|
||||
}
|
||||
case FANOUT_MS:
|
||||
if enable {
|
||||
regVal |= MULTISYNTH_ENABLE
|
||||
} else {
|
||||
regVal &^= MULTISYNTH_ENABLE
|
||||
}
|
||||
default:
|
||||
return ErrInvalidParameter
|
||||
}
|
||||
|
||||
return d.rw.Write8(FANOUT_ENABLE, regVal)
|
||||
}
|
||||
|
||||
// SetClockSource sets the clock source for a multisynth output.
|
||||
// clk - Clock output (0..7)
|
||||
// src - Clock source (see constants below)
|
||||
func (d *Device) SetClockSource(clk uint8, src uint8) error {
|
||||
if !d.initialised {
|
||||
return ErrNotInitialised
|
||||
}
|
||||
|
||||
clkReg := CLK0_CONTROL + clk
|
||||
regVal, err := d.rw.Read8(clkReg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Clear the input source bits
|
||||
regVal &^= CLK_INPUT_MASK
|
||||
|
||||
switch src {
|
||||
case CLK_SRC_XTAL:
|
||||
regVal |= CLK_INPUT_XTAL
|
||||
case CLK_SRC_CLKIN:
|
||||
regVal |= CLK_INPUT_CLKIN
|
||||
case CLK_SRC_MS0:
|
||||
if clk == 0 {
|
||||
return nil // MS0 not valid for CLK0
|
||||
}
|
||||
regVal |= CLK_INPUT_MULTISYNTH_0_4
|
||||
case CLK_SRC_MS:
|
||||
regVal |= CLK_INPUT_MULTISYNTH_N
|
||||
default:
|
||||
return ErrInvalidParameter
|
||||
}
|
||||
|
||||
return d.rw.Write8(clkReg, regVal)
|
||||
}
|
||||
|
||||
// SetClockInvert enables or disables inversion of the clock output waveform.
|
||||
// clk - Clock output (0..7)
|
||||
// invert - true to enable inversion, false to disable
|
||||
func (d *Device) SetClockInvert(clk uint8, invert bool) error {
|
||||
if !d.initialised {
|
||||
return ErrNotInitialised
|
||||
}
|
||||
|
||||
clkReg := CLK0_CONTROL + clk
|
||||
regVal, err := d.rw.Read8(clkReg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if invert {
|
||||
regVal |= CLK_INVERT
|
||||
} else {
|
||||
regVal &^= CLK_INVERT
|
||||
}
|
||||
|
||||
return d.rw.Write8(clkReg, regVal)
|
||||
}
|
||||
|
||||
// SetDriveStrength sets the drive strength of the specified clock output.
|
||||
// clk - Clock output (0..7)
|
||||
// drive - Desired drive level (use SI5351_CLK_DRIVE_STRENGTH_* constants)
|
||||
func (d *Device) SetDriveStrength(clk uint8, drive uint8) error {
|
||||
if !d.initialised {
|
||||
return ErrNotInitialised
|
||||
}
|
||||
|
||||
const mask = 0x03
|
||||
clkReg := CLK0_CONTROL + clk
|
||||
regVal, err := d.rw.Read8(clkReg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Clear drive strength bits
|
||||
regVal &^= mask
|
||||
|
||||
switch drive {
|
||||
case CLK_DRIVE_STRENGTH_2MA:
|
||||
regVal |= 0x00
|
||||
case CLK_DRIVE_STRENGTH_4MA:
|
||||
regVal |= 0x01
|
||||
case CLK_DRIVE_STRENGTH_6MA:
|
||||
regVal |= 0x02
|
||||
case CLK_DRIVE_STRENGTH_8MA:
|
||||
regVal |= 0x03
|
||||
default:
|
||||
return ErrInvalidParameter
|
||||
}
|
||||
|
||||
return d.rw.Write8(clkReg, regVal)
|
||||
}
|
||||
|
||||
// SetClockPower enables or disables power to a clock output (power saving).
|
||||
// clk - Clock output (0..7)
|
||||
// enable - true to enable power, false to disable (power down)
|
||||
func (d *Device) SetClockPower(clk uint8, enable bool) error {
|
||||
if !d.initialised {
|
||||
return ErrNotInitialised
|
||||
}
|
||||
|
||||
clkReg := CLK0_CONTROL + clk
|
||||
regVal, err := d.rw.Read8(clkReg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if enable {
|
||||
regVal &^= 0x80 // Clear bit 7 to enable power
|
||||
} else {
|
||||
regVal |= 0x80 // Set bit 7 to disable power (power down)
|
||||
}
|
||||
|
||||
return d.rw.Write8(clkReg, regVal)
|
||||
}
|
||||
+5
-1
@@ -20,9 +20,11 @@ tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/bmi
|
||||
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/bmp180/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/bmp280/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=trinket-m0 ./examples/bmp388/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=metro-rp2350 ./examples/bno08x/i2c/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=bluepill ./examples/ds1307/sram/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=bluepill ./examples/ds1307/time/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/ds3231/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/ds3231/alarms/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/ds3231/basic/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/easystepper/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/flash/console/spi
|
||||
tinygo build -size short -o ./build/test.hex -target=pyportal ./examples/flash/console/qspi
|
||||
@@ -144,6 +146,8 @@ tinygo build -size short -o ./build/test.hex -target=pico ./examples/tmc5160/mai
|
||||
tinygo build -size short -o ./build/test.uf2 -target=nicenano ./examples/sharpmem/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=feather-nrf52840 ./examples/max6675/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=pico ./examples/ens160/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=pico ./examples/si5351/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=pico ./examples/w5500/main.go
|
||||
# network examples (espat)
|
||||
tinygo build -size short -o ./build/test.hex -target=challenger-rp2040 ./examples/net/ntpclient/
|
||||
# network examples (wifinina)
|
||||
|
||||
+11
-20
@@ -1,15 +1,19 @@
|
||||
package ssd1289
|
||||
|
||||
import "machine"
|
||||
import (
|
||||
"tinygo.org/x/drivers/internal/legacy"
|
||||
"tinygo.org/x/drivers/internal/pin"
|
||||
)
|
||||
|
||||
type pinBus struct {
|
||||
pins [16]machine.Pin
|
||||
pins [16]pin.Output
|
||||
}
|
||||
|
||||
func NewPinBus(pins [16]machine.Pin) pinBus {
|
||||
func NewPinBus(pins [16]pin.Output) pinBus {
|
||||
|
||||
// configure GPIO pins (on baremetal targets only, for backwards compatibility)
|
||||
for i := 0; i < 16; i++ {
|
||||
pins[i].Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
legacy.ConfigurePinOut(pins[i])
|
||||
}
|
||||
|
||||
return pinBus{
|
||||
@@ -18,20 +22,7 @@ func NewPinBus(pins [16]machine.Pin) pinBus {
|
||||
}
|
||||
|
||||
func (b pinBus) Set(data uint16) {
|
||||
b.pins[15].Set((data & (1 << 15)) != 0)
|
||||
b.pins[14].Set((data & (1 << 14)) != 0)
|
||||
b.pins[13].Set((data & (1 << 13)) != 0)
|
||||
b.pins[12].Set((data & (1 << 12)) != 0)
|
||||
b.pins[11].Set((data & (1 << 11)) != 0)
|
||||
b.pins[10].Set((data & (1 << 10)) != 0)
|
||||
b.pins[9].Set((data & (1 << 9)) != 0)
|
||||
b.pins[8].Set((data & (1 << 8)) != 0)
|
||||
b.pins[7].Set((data & (1 << 7)) != 0)
|
||||
b.pins[6].Set((data & (1 << 6)) != 0)
|
||||
b.pins[5].Set((data & (1 << 5)) != 0)
|
||||
b.pins[4].Set((data & (1 << 4)) != 0)
|
||||
b.pins[3].Set((data & (1 << 3)) != 0)
|
||||
b.pins[2].Set((data & (1 << 2)) != 0)
|
||||
b.pins[1].Set((data & (1 << 1)) != 0)
|
||||
b.pins[0].Set((data & (1 << 0)) != 0)
|
||||
for i := 15; i >= 0; i-- {
|
||||
b.pins[i].Set((data & (1 << i)) != 0)
|
||||
}
|
||||
}
|
||||
|
||||
+21
-18
@@ -5,8 +5,10 @@ package ssd1289
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/internal/legacy"
|
||||
"tinygo.org/x/drivers/internal/pin"
|
||||
)
|
||||
|
||||
type Bus interface {
|
||||
@@ -14,33 +16,34 @@ type Bus interface {
|
||||
}
|
||||
|
||||
type Device struct {
|
||||
rs machine.Pin
|
||||
wr machine.Pin
|
||||
cs machine.Pin
|
||||
rst machine.Pin
|
||||
rs pin.OutputFunc
|
||||
wr pin.OutputFunc
|
||||
cs pin.OutputFunc
|
||||
rst pin.OutputFunc
|
||||
bus Bus
|
||||
}
|
||||
|
||||
const width = int16(240)
|
||||
const height = int16(320)
|
||||
|
||||
func New(rs machine.Pin, wr machine.Pin, cs machine.Pin, rst machine.Pin, bus Bus) Device {
|
||||
d := Device{
|
||||
rs: rs,
|
||||
wr: wr,
|
||||
cs: cs,
|
||||
rst: rst,
|
||||
func New(rs, wr, cs, rst pin.Output, bus Bus) *Device {
|
||||
d := &Device{
|
||||
rs: rs.Set,
|
||||
wr: wr.Set,
|
||||
cs: cs.Set,
|
||||
rst: rst.Set,
|
||||
bus: bus,
|
||||
}
|
||||
|
||||
rs.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
wr.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
cs.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
rst.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
// configure GPIO pins (only on baremetal targets, for backwards compatibility)
|
||||
legacy.ConfigurePinOut(rs)
|
||||
legacy.ConfigurePinOut(wr)
|
||||
legacy.ConfigurePinOut(cs)
|
||||
legacy.ConfigurePinOut(rst)
|
||||
|
||||
cs.High()
|
||||
rst.High()
|
||||
wr.High()
|
||||
d.cs.High()
|
||||
d.rst.High()
|
||||
d.wr.High()
|
||||
|
||||
return d
|
||||
}
|
||||
|
||||
+14
-12
@@ -1,31 +1,33 @@
|
||||
package ssd1306
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
"tinygo.org/x/drivers/internal/legacy"
|
||||
"tinygo.org/x/drivers/internal/pin"
|
||||
)
|
||||
|
||||
type SPIBus struct {
|
||||
wire drivers.SPI
|
||||
dcPin machine.Pin
|
||||
resetPin machine.Pin
|
||||
csPin machine.Pin
|
||||
dcPin pin.OutputFunc
|
||||
resetPin pin.OutputFunc
|
||||
csPin pin.OutputFunc
|
||||
buffer []byte // buffer to avoid heap allocations
|
||||
}
|
||||
|
||||
// NewSPI creates a new SSD1306 connection. The SPI wire must already be configured.
|
||||
func NewSPI(bus drivers.SPI, dcPin, resetPin, csPin machine.Pin) *Device {
|
||||
dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
resetPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
func NewSPI(bus drivers.SPI, dcPin, resetPin, csPin pin.Output) *Device {
|
||||
// configure GPIO pins (on baremetal targets only, for backwards compatibility)
|
||||
legacy.ConfigurePinOut(dcPin)
|
||||
legacy.ConfigurePinOut(resetPin)
|
||||
legacy.ConfigurePinOut(csPin)
|
||||
return &Device{
|
||||
bus: &SPIBus{
|
||||
wire: bus,
|
||||
dcPin: dcPin,
|
||||
resetPin: resetPin,
|
||||
csPin: csPin,
|
||||
dcPin: dcPin.Set,
|
||||
resetPin: resetPin.Set,
|
||||
csPin: csPin.Set,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -60,7 +62,7 @@ func (b *SPIBus) flush() error {
|
||||
// tx sends data to the display
|
||||
func (b *SPIBus) tx(data []byte, isCommand bool) error {
|
||||
b.csPin.High()
|
||||
b.dcPin.Set(!isCommand)
|
||||
b.dcPin(!isCommand)
|
||||
b.csPin.Low()
|
||||
err := b.wire.Tx(data, nil)
|
||||
b.csPin.High()
|
||||
|
||||
+14
-12
@@ -5,12 +5,13 @@ package ssd1331 // import "tinygo.org/x/drivers/ssd1331"
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"machine"
|
||||
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
"tinygo.org/x/drivers/internal/legacy"
|
||||
"tinygo.org/x/drivers/internal/pin"
|
||||
)
|
||||
|
||||
type Model uint8
|
||||
@@ -19,9 +20,9 @@ type Rotation uint8
|
||||
// Device wraps an SPI connection.
|
||||
type Device struct {
|
||||
bus drivers.SPI
|
||||
dcPin machine.Pin
|
||||
resetPin machine.Pin
|
||||
csPin machine.Pin
|
||||
dcPin pin.OutputFunc
|
||||
resetPin pin.OutputFunc
|
||||
csPin pin.OutputFunc
|
||||
width int16
|
||||
height int16
|
||||
batchLength int16
|
||||
@@ -36,15 +37,16 @@ type Config struct {
|
||||
}
|
||||
|
||||
// New creates a new SSD1331 connection. The SPI wire must already be configured.
|
||||
func New(bus drivers.SPI, resetPin, dcPin, csPin machine.Pin) Device {
|
||||
dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
resetPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
func New(bus drivers.SPI, resetPin, dcPin, csPin pin.Output) Device {
|
||||
// configure GPIO pins (on baremetal targets only, for backwards compatibility)
|
||||
legacy.ConfigurePinOut(dcPin)
|
||||
legacy.ConfigurePinOut(resetPin)
|
||||
legacy.ConfigurePinOut(csPin)
|
||||
return Device{
|
||||
bus: bus,
|
||||
dcPin: dcPin,
|
||||
resetPin: resetPin,
|
||||
csPin: csPin,
|
||||
dcPin: dcPin.Set,
|
||||
resetPin: resetPin.Set,
|
||||
csPin: csPin.Set,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,7 +253,7 @@ func (d *Device) Data(data uint8) {
|
||||
|
||||
// Tx sends data to the display
|
||||
func (d *Device) Tx(data []byte, isCommand bool) {
|
||||
d.dcPin.Set(!isCommand)
|
||||
d.dcPin(!isCommand)
|
||||
d.bus.Tx(data, nil)
|
||||
}
|
||||
|
||||
|
||||
+30
-25
@@ -6,10 +6,11 @@ package ssd1351 // import "tinygo.org/x/drivers/ssd1351"
|
||||
import (
|
||||
"errors"
|
||||
"image/color"
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
"tinygo.org/x/drivers/internal/legacy"
|
||||
"tinygo.org/x/drivers/internal/pin"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -19,17 +20,18 @@ var (
|
||||
|
||||
// Device wraps an SPI connection.
|
||||
type Device struct {
|
||||
bus drivers.SPI
|
||||
dcPin machine.Pin
|
||||
resetPin machine.Pin
|
||||
csPin machine.Pin
|
||||
enPin machine.Pin
|
||||
rwPin machine.Pin
|
||||
width int16
|
||||
height int16
|
||||
rowOffset int16
|
||||
columnOffset int16
|
||||
bufferLength int16
|
||||
bus drivers.SPI
|
||||
dcPin pin.OutputFunc
|
||||
resetPin pin.OutputFunc
|
||||
csPin pin.OutputFunc
|
||||
enPin pin.OutputFunc
|
||||
rwPin pin.OutputFunc
|
||||
width int16
|
||||
height int16
|
||||
rowOffset int16
|
||||
columnOffset int16
|
||||
bufferLength int16
|
||||
configurePins func()
|
||||
}
|
||||
|
||||
// Config is the configuration for the display
|
||||
@@ -41,14 +43,21 @@ type Config struct {
|
||||
}
|
||||
|
||||
// New creates a new SSD1351 connection. The SPI wire must already be configured.
|
||||
func New(bus drivers.SPI, resetPin, dcPin, csPin, enPin, rwPin machine.Pin) Device {
|
||||
func New(bus drivers.SPI, resetPin, dcPin, csPin, enPin, rwPin pin.Output) Device {
|
||||
return Device{
|
||||
bus: bus,
|
||||
dcPin: dcPin,
|
||||
resetPin: resetPin,
|
||||
csPin: csPin,
|
||||
enPin: enPin,
|
||||
rwPin: rwPin,
|
||||
dcPin: dcPin.Set,
|
||||
resetPin: resetPin.Set,
|
||||
csPin: csPin.Set,
|
||||
enPin: enPin.Set,
|
||||
rwPin: rwPin.Set,
|
||||
configurePins: func() {
|
||||
legacy.ConfigurePinOut(dcPin)
|
||||
legacy.ConfigurePinOut(resetPin)
|
||||
legacy.ConfigurePinOut(csPin)
|
||||
legacy.ConfigurePinOut(enPin)
|
||||
legacy.ConfigurePinOut(rwPin)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,12 +81,8 @@ func (d *Device) Configure(cfg Config) {
|
||||
d.bufferLength = d.height
|
||||
}
|
||||
|
||||
// configure GPIO pins
|
||||
d.dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
d.resetPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
d.csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
d.enPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
d.rwPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
// configure GPIO pins (on baremetal targets only, for backwards compatibility)
|
||||
d.configurePins()
|
||||
|
||||
// reset the device
|
||||
d.resetPin.High()
|
||||
@@ -278,7 +283,7 @@ func (d *Device) Data(data uint8) {
|
||||
|
||||
// Tx sends data to the display
|
||||
func (d *Device) Tx(data []byte, isCommand bool) {
|
||||
d.dcPin.Set(!isCommand)
|
||||
d.dcPin(!isCommand)
|
||||
d.csPin.Low()
|
||||
d.bus.Tx(data, nil)
|
||||
d.csPin.High()
|
||||
|
||||
+19
-16
@@ -5,12 +5,13 @@ package st7735 // import "tinygo.org/x/drivers/st7735"
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"errors"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
"tinygo.org/x/drivers/internal/legacy"
|
||||
"tinygo.org/x/drivers/internal/pin"
|
||||
"tinygo.org/x/drivers/pixel"
|
||||
)
|
||||
|
||||
@@ -39,10 +40,10 @@ type Device = DeviceOf[pixel.RGB565BE]
|
||||
// formats.
|
||||
type DeviceOf[T Color] struct {
|
||||
bus drivers.SPI
|
||||
dcPin machine.Pin
|
||||
resetPin machine.Pin
|
||||
csPin machine.Pin
|
||||
blPin machine.Pin
|
||||
dcPin pin.OutputFunc
|
||||
resetPin pin.OutputFunc
|
||||
csPin pin.OutputFunc
|
||||
blPin pin.OutputFunc
|
||||
width int16
|
||||
height int16
|
||||
columnOffset int16
|
||||
@@ -65,23 +66,25 @@ type Config struct {
|
||||
}
|
||||
|
||||
// New creates a new ST7735 connection. The SPI wire must already be configured.
|
||||
func New(bus drivers.SPI, resetPin, dcPin, csPin, blPin machine.Pin) Device {
|
||||
func New(bus drivers.SPI, resetPin, dcPin, csPin, blPin pin.Output) Device {
|
||||
return NewOf[pixel.RGB565BE](bus, resetPin, dcPin, csPin, blPin)
|
||||
}
|
||||
|
||||
// NewOf creates a new ST7735 connection with a particular pixel format. The SPI
|
||||
// wire must already be configured.
|
||||
func NewOf[T Color](bus drivers.SPI, resetPin, dcPin, csPin, blPin machine.Pin) DeviceOf[T] {
|
||||
dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
resetPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
blPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
func NewOf[T Color](bus drivers.SPI, resetPin, dcPin, csPin, blPin pin.Output) DeviceOf[T] {
|
||||
// IMPORTANT: pin configuration should really be done outside of this driver,
|
||||
// but for backwards compatibility with existing code, we do it here.
|
||||
legacy.ConfigurePinOut(dcPin)
|
||||
legacy.ConfigurePinOut(resetPin)
|
||||
legacy.ConfigurePinOut(csPin)
|
||||
legacy.ConfigurePinOut(blPin)
|
||||
return DeviceOf[T]{
|
||||
bus: bus,
|
||||
dcPin: dcPin,
|
||||
resetPin: resetPin,
|
||||
csPin: csPin,
|
||||
blPin: blPin,
|
||||
dcPin: dcPin.Set,
|
||||
resetPin: resetPin.Set,
|
||||
csPin: csPin.Set,
|
||||
blPin: blPin.Set,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -423,7 +426,7 @@ func (d *DeviceOf[T]) Data(data uint8) {
|
||||
|
||||
// Tx sends data to the display
|
||||
func (d *DeviceOf[T]) Tx(data []byte, isCommand bool) {
|
||||
d.dcPin.Set(!isCommand)
|
||||
d.dcPin(!isCommand)
|
||||
d.bus.Tx(data, nil)
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -2,4 +2,4 @@ package drivers
|
||||
|
||||
// Version returns a user-readable string showing the version of the drivers package for support purposes.
|
||||
// Update this value before release of new version of software.
|
||||
const Version = "0.33.0"
|
||||
const Version = "0.34.0"
|
||||
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
package w5500
|
||||
|
||||
import "time"
|
||||
|
||||
func (d *Device) irqPoll(sockn uint8, state uint8, deadline time.Time) uint8 {
|
||||
waitTime := 500 * time.Microsecond
|
||||
for {
|
||||
if !deadline.IsZero() && time.Now().After(deadline) {
|
||||
// If a deadline is set and it has passed, return 0.
|
||||
return sockIntUnknown
|
||||
}
|
||||
|
||||
irq := d.readByte(sockInt, sockAddr(sockn)) & 0b00011111
|
||||
if got := irq & state; got != 0 {
|
||||
// Acknowledge the interrupt.
|
||||
d.writeByte(sockInt, sockAddr(sockn), got)
|
||||
|
||||
return got
|
||||
}
|
||||
|
||||
d.mu.Unlock()
|
||||
|
||||
time.Sleep(waitTime)
|
||||
|
||||
// Exponential backoff for polling.
|
||||
waitTime *= 2
|
||||
if waitTime > 10*time.Millisecond {
|
||||
waitTime = 10 * time.Millisecond
|
||||
}
|
||||
|
||||
d.mu.Lock()
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Device) read(addr uint16, bsb uint8, p []byte) {
|
||||
d.cs(false)
|
||||
if len(p) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
d.sendReadHeader(addr, bsb)
|
||||
_ = d.bus.Tx(nil, p)
|
||||
d.cs(true)
|
||||
}
|
||||
|
||||
func (d *Device) readUint16(addr uint16, bsb uint8) uint16 {
|
||||
d.cs(false)
|
||||
d.sendReadHeader(addr, bsb)
|
||||
buf := d.cmdBuf
|
||||
_ = d.bus.Tx(nil, buf[:2])
|
||||
d.cs(true)
|
||||
return uint16(buf[1]) | uint16(buf[0])<<8
|
||||
}
|
||||
|
||||
func (d *Device) readByte(addr uint16, bsb uint8) byte {
|
||||
d.cs(false)
|
||||
d.sendReadHeader(addr, bsb)
|
||||
r, _ := d.bus.Transfer(byte(0))
|
||||
d.cs(true)
|
||||
return r
|
||||
}
|
||||
|
||||
func (d *Device) write(addr uint16, bsb uint8, p []byte) {
|
||||
d.cs(false)
|
||||
if len(p) == 0 {
|
||||
return
|
||||
}
|
||||
d.sendWriteHeader(addr, bsb)
|
||||
_ = d.bus.Tx(p, nil)
|
||||
d.cs(true)
|
||||
}
|
||||
|
||||
func (d *Device) writeUint16(addr uint16, bsb uint8, v uint16) {
|
||||
d.cs(false)
|
||||
d.sendWriteHeader(addr, bsb)
|
||||
buf := d.cmdBuf
|
||||
buf[0] = byte(v >> 8)
|
||||
buf[1] = byte(v & 0xff)
|
||||
_ = d.bus.Tx(buf[:2], nil)
|
||||
d.cs(true)
|
||||
}
|
||||
|
||||
func (d *Device) writeByte(addr uint16, bsb uint8, b byte) {
|
||||
d.cs(false)
|
||||
d.sendWriteHeader(addr, bsb)
|
||||
_, _ = d.bus.Transfer(b)
|
||||
d.cs(true)
|
||||
}
|
||||
|
||||
func (d *Device) sendReadHeader(addr uint16, bsb uint8) {
|
||||
buf := d.cmdBuf
|
||||
buf[0] = byte(addr >> 8)
|
||||
buf[1] = byte(addr & 0xff)
|
||||
buf[2] = bsb << 3
|
||||
_ = d.bus.Tx(buf[:], nil)
|
||||
}
|
||||
|
||||
func (d *Device) sendWriteHeader(addr uint16, bsb uint8) {
|
||||
buf := d.cmdBuf
|
||||
buf[0] = byte(addr >> 8)
|
||||
buf[1] = byte(addr & 0xff)
|
||||
buf[2] = bsb<<3 | 0b100
|
||||
_ = d.bus.Tx(buf[:], nil)
|
||||
}
|
||||
+445
@@ -0,0 +1,445 @@
|
||||
package w5500
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/netdev"
|
||||
)
|
||||
|
||||
type socket struct {
|
||||
sockn uint8
|
||||
protocol uint8
|
||||
port uint16
|
||||
inUse bool
|
||||
closed bool
|
||||
}
|
||||
|
||||
func (s *socket) setProtocol(proto byte) *socket {
|
||||
s.protocol = proto
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *socket) setPort(port uint16) *socket {
|
||||
s.port = port
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *socket) setInUse(inUse bool) *socket {
|
||||
s.inUse = inUse
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *socket) setClosed(closed bool) *socket {
|
||||
s.closed = closed
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *socket) reset() {
|
||||
s.protocol = 0
|
||||
s.port = 0
|
||||
s.inUse = false
|
||||
s.closed = false
|
||||
}
|
||||
|
||||
// GetHostByName resolves the given host name to an IP address.
|
||||
func (d *Device) GetHostByName(name string) (netip.Addr, error) {
|
||||
d.mu.Lock()
|
||||
dns := d.dns
|
||||
d.mu.Unlock()
|
||||
|
||||
if dns == nil {
|
||||
return netip.Addr{}, netdev.ErrNotSupported
|
||||
}
|
||||
return dns(name)
|
||||
}
|
||||
|
||||
func (d *Device) Socket(domain int, stype int, protocol int) (int, error) {
|
||||
if domain != netdev.AF_INET {
|
||||
return -1, netdev.ErrFamilyNotSupported
|
||||
}
|
||||
switch {
|
||||
case stype == netdev.SOCK_STREAM && protocol == netdev.IPPROTO_TCP:
|
||||
case stype == netdev.SOCK_DGRAM && protocol == netdev.IPPROTO_UDP:
|
||||
default:
|
||||
return -1, errors.New("unsupported combination of socket type and protocol")
|
||||
}
|
||||
|
||||
var proto byte
|
||||
switch protocol {
|
||||
case netdev.IPPROTO_TCP:
|
||||
proto = 1 // TCP
|
||||
case netdev.IPPROTO_UDP:
|
||||
proto = 2 // UDP
|
||||
default:
|
||||
return -1, netdev.ErrNotSupported
|
||||
}
|
||||
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
sockfd, sock, err := d.nextSocket()
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
d.openSocket(sock.sockn, proto)
|
||||
|
||||
sock.setProtocol(proto).setInUse(true)
|
||||
return sockfd, nil
|
||||
}
|
||||
|
||||
func (d *Device) openSocket(sockn uint8, proto byte) {
|
||||
d.writeByte(sockMode, sockAddr(sockn), proto&0x0F)
|
||||
}
|
||||
|
||||
func (d *Device) Bind(sockfd int, ip netip.AddrPort) error {
|
||||
// The IP address is irrelevant. The configured ip will always be used.
|
||||
port := ip.Port()
|
||||
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
sock, err := d.socket(sockfd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = d.bindSocket(sock.sockn, port); err != nil {
|
||||
return errors.New("could not set socket port: " + err.Error())
|
||||
}
|
||||
|
||||
sock.setPort(port)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Device) bindSocket(sockn uint8, port uint16) error {
|
||||
d.writeUint16(sockSrcPort, sockAddr(sockn), port)
|
||||
d.socketSendCmd(sockn, sockCmdOpen)
|
||||
if d.sockStatus(sockn) == sockStatusClosed {
|
||||
return errors.New("socket is closed after binding")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetSockOpt sets the socket option for the given socket file descriptor.
|
||||
// It is not supported by the W5500, so it always returns an error.
|
||||
func (d *Device) SetSockOpt(int, int, int, any) error {
|
||||
return netdev.ErrNotSupported
|
||||
}
|
||||
|
||||
// Connect establishes a connection to the specified host and port or ip and port.
|
||||
//
|
||||
// If the host is an empty string, it will use the provided ip address and port,
|
||||
// otherwise it will resolve the host name to an IP address.
|
||||
func (d *Device) Connect(sockfd int, host string, ip netip.AddrPort) error {
|
||||
destIP := ip.Addr()
|
||||
if host != "" {
|
||||
var err error
|
||||
destIP, err = d.GetHostByName(host)
|
||||
if err != nil {
|
||||
return errors.New("could not resolve host " + host + ":" + err.Error())
|
||||
}
|
||||
}
|
||||
if !destIP.IsValid() || !destIP.Is4() {
|
||||
return errors.New("invalid destination IP address: " + destIP.String())
|
||||
}
|
||||
port := ip.Port()
|
||||
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
sock, err := d.socket(sockfd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.write(sockDestIP, sockAddr(sock.sockn), destIP.AsSlice())
|
||||
d.writeUint16(sockDestPort, sockAddr(sock.sockn), port)
|
||||
d.socketSendCmd(sock.sockn, sockCmdOpen)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Listen sets the socket to listen for incoming connections on the specified socket file descriptor.
|
||||
//
|
||||
// The backlog parameter is ignored, as the W5500 does not support it.
|
||||
func (d *Device) Listen(sockfd int, _ int) error {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
sock, err := d.socket(sockfd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if sock.protocol != 1 { // Only TCP sockets can listen
|
||||
return errors.New("not a TCP socket")
|
||||
}
|
||||
|
||||
if err = d.listen(sock.sockn); err != nil {
|
||||
return errors.New("could not send listen command: " + err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Device) listen(sockn uint8) error {
|
||||
state := d.sockStatus(sockn)
|
||||
if state != sockStatusInit {
|
||||
return errors.New("socket is not in the initial state")
|
||||
}
|
||||
d.socketSendCmd(sockn, sockCmdListen)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Accept waits for an incoming connection on the specified socket file descriptor.
|
||||
func (d *Device) Accept(sockfd int) (int, netip.AddrPort, error) {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
lsock, err := d.socket(sockfd)
|
||||
if err != nil {
|
||||
return -1, netip.AddrPort{}, errors.New("could not get socket: " + err.Error())
|
||||
}
|
||||
|
||||
if err = d.waitForEstablished(lsock.sockn); err != nil {
|
||||
return -1, netip.AddrPort{}, err
|
||||
}
|
||||
|
||||
// Acquire a new socket for the listening connection.
|
||||
csockfd, csock, err := d.nextSocket()
|
||||
if err != nil {
|
||||
return -1, netip.AddrPort{}, err
|
||||
}
|
||||
// Swap the socket numbers of the client and listening sockets.
|
||||
lsock.sockn, csock.sockn = csock.sockn, lsock.sockn
|
||||
|
||||
// Rebind the listening socket to the local address and port and start listening.
|
||||
d.openSocket(lsock.sockn, lsock.protocol)
|
||||
if err = d.bindSocket(lsock.sockn, lsock.port); err != nil {
|
||||
return -1, netip.AddrPort{}, errors.New("could not bind listening socket: " + err.Error())
|
||||
}
|
||||
if err = d.listen(lsock.sockn); err != nil {
|
||||
return -1, netip.AddrPort{}, errors.New("could not set listening socket: " + err.Error())
|
||||
}
|
||||
|
||||
csock.setInUse(true)
|
||||
|
||||
remoteIP := d.remoteIP(csock.sockn)
|
||||
return csockfd, remoteIP, nil
|
||||
}
|
||||
|
||||
func (d *Device) waitForEstablished(sockn uint8) error {
|
||||
for {
|
||||
status := d.sockStatus(sockn)
|
||||
switch status {
|
||||
case sockStatusEstablished:
|
||||
return nil
|
||||
case sockStatusClosed:
|
||||
return net.ErrClosed
|
||||
case sockStatusCloseWait:
|
||||
// The server closed the connection, so we need to reset the socket
|
||||
// and set it to listen again.
|
||||
if err := d.listen(sockn); err != nil {
|
||||
return errors.New("could not set socket to listen: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
d.irqPoll(sockn, sockIntConnect|sockIntDisconnect, time.Time{})
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Device) remoteIP(sockn uint8) netip.AddrPort {
|
||||
var rip [4]byte
|
||||
d.read(sockDestIP, sockAddr(sockn), rip[:])
|
||||
|
||||
var rport [2]byte
|
||||
d.read(sockDestPort, sockAddr(sockn), rport[:])
|
||||
|
||||
return netip.AddrPortFrom(netip.AddrFrom4(rip), uint16(rport[0])<<8|uint16(rport[1]))
|
||||
}
|
||||
|
||||
// Send sends data to the socket with the given file descriptor.
|
||||
// It blocks until all data is sent or the deadline is reached.
|
||||
func (d *Device) Send(sockfd int, buf []byte, _ int, deadline time.Time) (int, error) {
|
||||
bufLen := len(buf)
|
||||
if bufLen <= d.maxSockSize {
|
||||
// Fast path for small buffers.
|
||||
return d.sendChunk(sockfd, buf, deadline)
|
||||
}
|
||||
|
||||
var n int
|
||||
for i := 0; i < bufLen; i += d.maxSockSize {
|
||||
end := i + d.maxSockSize
|
||||
if end > bufLen {
|
||||
end = bufLen
|
||||
}
|
||||
|
||||
sent, err := d.sendChunk(sockfd, buf[i:end], deadline)
|
||||
if err != nil {
|
||||
return n, errors.New("could not send chunk: " + err.Error())
|
||||
}
|
||||
n += sent
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (d *Device) sendChunk(sockfd int, buf []byte, deadline time.Time) (int, error) {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
sock, err := d.socket(sockfd)
|
||||
if err != nil {
|
||||
return 0, errors.New("could not get socket: " + err.Error())
|
||||
}
|
||||
if sock.closed {
|
||||
return 0, os.ErrClosed
|
||||
}
|
||||
|
||||
bufLen := uint16(len(buf))
|
||||
if err = d.waitForFreeBuffer(sock.sockn, bufLen, deadline); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
sendPtr := d.readUint16(sockTXWritePtr, sockAddr(sock.sockn))
|
||||
|
||||
d.write(sendPtr, sock.sockn<<2|0b10, buf)
|
||||
d.writeUint16(sockTXWritePtr, sockAddr(sock.sockn), sendPtr+bufLen)
|
||||
d.writeByte(sockCmd, sockAddr(sock.sockn), sockCmdSend)
|
||||
|
||||
irq := d.irqPoll(sock.sockn, sockIntSendOK|sockIntDisconnect|sockIntTimeout, deadline)
|
||||
switch {
|
||||
case irq == sockIntUnknown:
|
||||
return 0, os.ErrDeadlineExceeded
|
||||
case irq&sockIntDisconnect != 0:
|
||||
sock.setClosed(true)
|
||||
return 0, net.ErrClosed
|
||||
case irq&sockIntTimeout != 0:
|
||||
return 0, netdev.ErrTimeout
|
||||
default:
|
||||
return int(bufLen), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Device) waitForFreeBuffer(sockn uint8, len uint16, deadline time.Time) error {
|
||||
for {
|
||||
freeSize := d.readUint16(sockTXFreeSize, sockAddr(sockn))
|
||||
if freeSize >= len {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !deadline.IsZero() && time.Now().After(deadline) {
|
||||
return netdev.ErrTimeout
|
||||
}
|
||||
|
||||
status := d.sockStatus(sockn)
|
||||
switch status {
|
||||
case sockStatusEstablished, sockStatusCloseWait:
|
||||
default:
|
||||
return errors.New("socket is not in a valid state for sending data")
|
||||
}
|
||||
|
||||
d.mu.Unlock()
|
||||
|
||||
time.Sleep(time.Millisecond)
|
||||
|
||||
d.mu.Lock()
|
||||
}
|
||||
}
|
||||
|
||||
// Recv reads data from the socket with the given file descriptor into the provided buffer.
|
||||
// It blocks until data is available or the deadline is reached.
|
||||
func (d *Device) Recv(sockfd int, buf []byte, _ int, deadline time.Time) (int, error) {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
sock, err := d.socket(sockfd)
|
||||
if err != nil {
|
||||
return 0, errors.New("could not get socket: " + err.Error())
|
||||
}
|
||||
if sock.closed {
|
||||
return 0, os.ErrClosed
|
||||
}
|
||||
|
||||
size, err := d.waitForData(sock, deadline)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
recvPtr := d.readUint16(sockRXReadPtr, sockAddr(sock.sockn))
|
||||
|
||||
buf = buf[:min(size, len(buf))]
|
||||
d.read(recvPtr, sock.sockn<<2|0b00011, buf)
|
||||
d.writeUint16(sockRXReadPtr, sockAddr(sock.sockn), recvPtr+uint16(len(buf)))
|
||||
d.socketSendCmd(sock.sockn, sockCmdRecv)
|
||||
|
||||
return len(buf), nil
|
||||
}
|
||||
|
||||
func (d *Device) waitForData(sock *socket, deadline time.Time) (int, error) {
|
||||
for {
|
||||
recvdSize := d.readUint16(sockRXReceivedSize, sockAddr(sock.sockn))
|
||||
if recvdSize > 0 {
|
||||
return int(recvdSize), nil
|
||||
}
|
||||
|
||||
irq := d.irqPoll(sock.sockn, sockIntReceive|sockIntDisconnect, deadline)
|
||||
switch {
|
||||
case irq == sockIntUnknown:
|
||||
return 0, os.ErrDeadlineExceeded
|
||||
case irq&sockIntDisconnect != 0:
|
||||
sock.setClosed(true)
|
||||
return 0, net.ErrClosed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Close closes the socket with the given file descriptor.
|
||||
func (d *Device) Close(sockfd int) error {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
sock, err := d.socket(sockfd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.socketSendCmd(sock.sockn, sockCmdClose)
|
||||
sock.reset()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Device) nextSocket() (int, *socket, error) {
|
||||
for i, sock := range d.sockets {
|
||||
if sock.inUse {
|
||||
continue
|
||||
}
|
||||
return i, sock, nil
|
||||
}
|
||||
return -1, nil, netdev.ErrNoMoreSockets
|
||||
}
|
||||
|
||||
func (d *Device) socket(sockfd int) (*socket, error) {
|
||||
if sockfd < 0 || sockfd >= len(d.sockets) {
|
||||
return nil, netdev.ErrInvalidSocketFd
|
||||
}
|
||||
return d.sockets[sockfd], nil
|
||||
}
|
||||
|
||||
func (d *Device) socketSendCmd(sockn uint8, cmd byte) {
|
||||
d.writeByte(sockCmd, sockAddr(sockn), cmd)
|
||||
for d.readByte(sockCmd, sockAddr(sockn)) != 0 {
|
||||
runtime.Gosched()
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Device) sockStatus(sockn uint8) int {
|
||||
return int(d.readByte(sockStatus, sockAddr(sockn)))
|
||||
}
|
||||
|
||||
func sockAddr(sockn uint8) uint8 {
|
||||
return sockn<<2 | 0b0001
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package w5500
|
||||
|
||||
// Common Registers.
|
||||
const (
|
||||
regMode = 0x0000
|
||||
regGatewayAddr = 0x0001
|
||||
regSubnetMask = 0x0005
|
||||
regMAC = 0x0009
|
||||
regIPAddr = 0x000F
|
||||
regIntLevel = 0x0013
|
||||
regInt = 0x0015
|
||||
regIntMask = 0x0016
|
||||
regSockInt = 0x0017
|
||||
regSockIntMask = 0x0018
|
||||
regRetryTime = 0x0019
|
||||
regRetryN = 0x001B
|
||||
// ... PPP registers, not needed
|
||||
regPHYCfg = 0x002E
|
||||
regChipVer = 0x0039
|
||||
)
|
||||
|
||||
// Socket Registers.
|
||||
const (
|
||||
sockMode = 0x0000
|
||||
sockCmd = 0x0001
|
||||
sockInt = 0x0002
|
||||
sockStatus = 0x0003
|
||||
sockSrcPort = 0x0004
|
||||
sockDestMAC = 0x0006
|
||||
sockDestIP = 0x000C
|
||||
sockDestPort = 0x0010
|
||||
sockMaxSegSize = 0x0012
|
||||
sockIPTOS = 0x0015
|
||||
sockIPTTL = 0x0016
|
||||
sockRXBUFSize = 0x001E
|
||||
sockTXBUFSize = 0x001F
|
||||
sockTXFreeSize = 0x0020
|
||||
sockTXReadPtr = 0x0022
|
||||
sockTXWritePtr = 0x0024
|
||||
sockRXReceivedSize = 0x0026
|
||||
sockRXReadPtr = 0x0028
|
||||
sockRXWritePtr = 0x002A
|
||||
sockIntMask = 0x002C
|
||||
sockKeepInt = 0x002F
|
||||
)
|
||||
|
||||
// Socket Commands.
|
||||
const (
|
||||
sockCmdOpen = 0x01
|
||||
sockCmdClose = 0x10
|
||||
sockCmdListen = 0x02
|
||||
sockCmdConnect = 0x04
|
||||
sockCmdDisconnect = 0x08
|
||||
sockCmdSend = 0x20
|
||||
sockCmdSendMacRaw = 0x21
|
||||
sockCmdSendKeep = 0x22
|
||||
sockCmdRecv = 0x40
|
||||
)
|
||||
|
||||
// Socket Statuses.
|
||||
const (
|
||||
sockStatusClosed = 0x00
|
||||
sockStatusInit = 0x13
|
||||
sockStatusListen = 0x14
|
||||
sockStatusEstablished = 0x17
|
||||
sockStatusCloseWait = 0x1C
|
||||
sockStatusUdp = 0x22
|
||||
sockStatusMacRaw = 0x42
|
||||
// Temporary TCP states
|
||||
sockStatusSynSent = 0x15
|
||||
sockStatusSynRecv = 0x16
|
||||
sockStatusFinWait = 0x18
|
||||
sockStatusClosing = 0x1A
|
||||
sockStatusTimeWait = 0x1B
|
||||
sockStatusLastAck = 0x1D
|
||||
sockStatusUnknown = 0xFF
|
||||
)
|
||||
|
||||
// Socket Interrupts.
|
||||
const (
|
||||
sockIntConnect uint8 = 1 << iota
|
||||
sockIntDisconnect
|
||||
sockIntReceive
|
||||
sockIntTimeout
|
||||
sockIntSendOK
|
||||
|
||||
sockIntUnknown uint8 = 0
|
||||
)
|
||||
+248
@@ -0,0 +1,248 @@
|
||||
// Package w5500 implements a driver for the W5500 Ethernet controller.
|
||||
//
|
||||
// The driver supports basic network functionality including TCP and UDP sockets.
|
||||
// It currently does not use the IRQ or RST pins.
|
||||
//
|
||||
// Datasheet: https://docs.wiznet.io/img/products/w5500/W5500_ds_v110e.pdf
|
||||
// Product Page: https://wiznet.io/products/ethernet-chips/w5500
|
||||
package w5500
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"net/netip"
|
||||
"sync"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
"tinygo.org/x/drivers/internal/pin"
|
||||
"tinygo.org/x/drivers/netdev"
|
||||
)
|
||||
|
||||
var _ netdev.Netdever = &Device{}
|
||||
|
||||
// Resolver is a function that resolves a hostname to an IP address.
|
||||
type Resolver func(host string) (netip.Addr, error)
|
||||
|
||||
// Device is a driver for the W5500 Ethernet controller.
|
||||
type Device struct {
|
||||
maxSockets int
|
||||
maxSockSize int
|
||||
|
||||
mu sync.Mutex
|
||||
bus drivers.SPI
|
||||
cs pin.OutputFunc
|
||||
dns Resolver
|
||||
|
||||
sockets []*socket
|
||||
laddr netip.Addr
|
||||
|
||||
cmdBuf [3]byte
|
||||
}
|
||||
|
||||
// New returns a new w5500 driver.
|
||||
func New(bus drivers.SPI, csPin pin.Output) *Device {
|
||||
return &Device{
|
||||
bus: bus,
|
||||
cs: csPin.Set,
|
||||
}
|
||||
}
|
||||
|
||||
// Config is the configuration for the device.
|
||||
//
|
||||
// The SPI bus must be fully configured.
|
||||
type Config struct {
|
||||
DNS Resolver
|
||||
|
||||
MAC net.HardwareAddr
|
||||
IP netip.Addr
|
||||
SubnetMask netip.Addr
|
||||
Gateway netip.Addr
|
||||
|
||||
// Optional, default is 8.
|
||||
MaxSockets int
|
||||
}
|
||||
|
||||
// Configure sets up the device.
|
||||
//
|
||||
// MAC address must be provided. The other fields are optional.
|
||||
func (d *Device) Configure(cfg Config) error {
|
||||
d.cs(true)
|
||||
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
d.dns = cfg.DNS
|
||||
|
||||
d.reset()
|
||||
|
||||
if err := d.setupSockets(cfg.MaxSockets); err != nil {
|
||||
return errors.New("could not setup sockets: " + err.Error())
|
||||
}
|
||||
|
||||
// Set the MAC address and IP configuration.
|
||||
d.write(regMAC, 0, cfg.MAC)
|
||||
d.write(regIPAddr, 0, cfg.IP.AsSlice())
|
||||
d.write(regSubnetMask, 0, cfg.SubnetMask.AsSlice())
|
||||
d.write(regGatewayAddr, 0, cfg.Gateway.AsSlice())
|
||||
d.laddr = cfg.IP
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Device) setupSockets(maxSockets int) error {
|
||||
if maxSockets == 0 {
|
||||
maxSockets = 8 // Default to 8 sockets if not specified.
|
||||
}
|
||||
switch maxSockets {
|
||||
case 1, 2, 4, 8:
|
||||
// Valid socket counts.
|
||||
default:
|
||||
return errors.New("invalid number of sockets, must be one of 1, 2, 4, or 8")
|
||||
}
|
||||
|
||||
socks := make([]*socket, maxSockets)
|
||||
for i := range socks {
|
||||
socks[i] = &socket{
|
||||
sockn: uint8(i),
|
||||
}
|
||||
}
|
||||
|
||||
d.maxSockets = maxSockets
|
||||
d.maxSockSize = 16 * 1024 / maxSockets
|
||||
d.sockets = socks
|
||||
|
||||
// Set the RX and TX buffer sizes for each socket.
|
||||
for i := 0; i < 8; i++ {
|
||||
size := byte(d.maxSockSize >> 10)
|
||||
if i >= maxSockets {
|
||||
size = 0
|
||||
}
|
||||
|
||||
d.writeByte(sockRXBUFSize, sockAddr(uint8(i)), size)
|
||||
d.writeByte(sockTXBUFSize, sockAddr(uint8(i)), size)
|
||||
}
|
||||
|
||||
mask := byte(0b11111111)
|
||||
switch maxSockets {
|
||||
case 1:
|
||||
mask = 0b00000001
|
||||
case 2:
|
||||
mask = 0b00000011
|
||||
case 4:
|
||||
mask = 0b00001111
|
||||
}
|
||||
d.writeByte(regSockIntMask, 0, mask)
|
||||
d.writeByte(regIntMask, 0, 0)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Reset performs a soft reset.
|
||||
func (d *Device) Reset() {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
d.reset()
|
||||
}
|
||||
|
||||
func (d *Device) reset() {
|
||||
// RST is bit 7 of regMode.
|
||||
d.writeByte(regMode, 0, 0x80)
|
||||
}
|
||||
|
||||
// GetHardwareAddr returns the hardware address of the device.
|
||||
func (d *Device) GetHardwareAddr() (net.HardwareAddr, error) {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
mac := make([]byte, 6)
|
||||
d.read(regMAC, 0, mac)
|
||||
return mac, nil
|
||||
}
|
||||
|
||||
// Addr returns the IP address of the device.
|
||||
func (d *Device) Addr() (netip.Addr, error) {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
var ip [4]byte
|
||||
d.read(regIPAddr, 0, ip[:])
|
||||
return netip.AddrFrom4(ip), nil
|
||||
}
|
||||
|
||||
// SetAddr sets the IP address of the device.
|
||||
//
|
||||
// The IP address must be a valid IPv4 address.
|
||||
func (d *Device) SetAddr(ip netip.Addr) error {
|
||||
if err := d.setAddress(regIPAddr, ip); err != nil {
|
||||
return errors.New("could not set IP address: " + err.Error())
|
||||
}
|
||||
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
d.laddr = ip
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetSubnetMask sets the subnet mask of the device.
|
||||
//
|
||||
// The subnet mask must be a valid IPv4 address.
|
||||
// It is not checked if the subnet mask is valid for the device's IP address.
|
||||
func (d *Device) SetSubnetMask(mask netip.Addr) error {
|
||||
return d.setAddress(regSubnetMask, mask)
|
||||
}
|
||||
|
||||
// SetGateway sets the gateway address of the device.
|
||||
//
|
||||
// The gateway must be a valid IPv4 address.
|
||||
// It is not checked if the gateway is in the same subnet as the device.
|
||||
func (d *Device) SetGateway(gateway netip.Addr) error {
|
||||
return d.setAddress(regGatewayAddr, gateway)
|
||||
}
|
||||
|
||||
func (d *Device) setAddress(addr uint16, ip netip.Addr) error {
|
||||
if !ip.IsValid() || !ip.Is4() {
|
||||
return errors.New("invalid IP address: " + ip.String())
|
||||
}
|
||||
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
d.write(addr, 0, ip.AsSlice())
|
||||
return nil
|
||||
}
|
||||
|
||||
// LinkStatus is the link status of the device.
|
||||
type LinkStatus = uint8
|
||||
|
||||
// LinkStatus values.
|
||||
const (
|
||||
LinkStatusDown LinkStatus = iota
|
||||
LinkStatusUp
|
||||
)
|
||||
|
||||
// LinkStatus returns the current link status of the device.
|
||||
func (d *Device) LinkStatus() LinkStatus {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
return d.readByte(regPHYCfg, 0) & 0b00000001
|
||||
}
|
||||
|
||||
// LinkInfo returns the current link information of the device.
|
||||
func (d *Device) LinkInfo() string {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
linkInfo := d.readByte(regPHYCfg, 0) & 0b00000110
|
||||
|
||||
speed := "10Mbps"
|
||||
if linkInfo&0b00000010 != 0 {
|
||||
speed = "100Mbps"
|
||||
}
|
||||
duplex := "Half Duplex"
|
||||
if linkInfo&0b00000100 != 0 {
|
||||
duplex = "Full Duplex"
|
||||
}
|
||||
return speed + " " + duplex
|
||||
}
|
||||
Reference in New Issue
Block a user