Compare commits

..

13 Commits

Author SHA1 Message Date
deadprogram d224b5d648 feature: some additional changes to drivers.Pin HAL for clarity and readability.
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-09-20 08:30:51 +02:00
Patricio Whittingslow 97ed81556b whoops, forgot to change easystepper method calls 2025-09-20 08:30:51 +02:00
Patricio Whittingslow 8ac285e821 legacy.PinOutput->pin.Output and add High+Low methods on drivers.PinOutput and use them in drivers 2025-09-20 08:30:51 +02:00
Patricio Whittingslow 2e007a6de7 rename busy fields to isBusy 2025-09-20 08:30:51 +02:00
Patricio Whittingslow 807ae3da9b missed high or low ~voltage~ level 2025-09-20 08:30:51 +02:00
Patricio Whittingslow c815f2560e apply @gen2thomas suggestions 2025-09-20 08:30:51 +02:00
Patricio Whittingslow 5d1bc2fc36 pins.go -> pin.go 2025-09-20 08:30:51 +02:00
soypat a9a59a6241 bugfix: output misconfigured 2025-09-20 08:30:51 +02:00
soypat c3f8435bc3 fix pullups not available on fe310 2025-09-20 08:30:51 +02:00
soypat 67b669f192 finish rewrites! 2025-09-20 08:30:51 +02:00
soypat cd37668592 more drivers are now cross platform 2025-09-20 08:30:51 +02:00
soypat 7d3404f060 apply suggestions from team 2025-09-20 08:30:51 +02:00
soypat cf81c5ab02 add PinInput and PinOutput HAL 2025-09-20 08:30:51 +02:00
129 changed files with 1230 additions and 11333 deletions
-1
View File
@@ -1,3 +1,2 @@
# These are supported funding model platforms
open_collective: tinygo
+6 -7
View File
@@ -11,12 +11,13 @@ on:
jobs:
build:
runs-on: ubuntu-latest
container:
image: ghcr.io/tinygo-org/tinygo:latest
options: --user root
container: ghcr.io/tinygo-org/tinygo-dev:latest
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@v6
uses: actions/checkout@v3
- name: TinyGo version check
run: tinygo version
- name: Enforce Go Formatted Code
@@ -24,6 +25,4 @@ jobs:
- name: Run unit tests
run: make unit-test
- name: Run build and smoke tests
run: |
go env -w GOFLAGS=-buildvcs=false
make smoke-test
run: make smoke-test
-90
View File
@@ -1,93 +1,3 @@
0.35.0
---
- **new devices**
- **unoqmatrix**
- LED matrix on the Arduino Uno Q
- **waveshare-epd (ssd1680)**
- Add driver for Waveshare 2.9 inch v2 e-paper display
- **enhancements**
- **gps**
- add UBX config command support (#831)
- improve implementation for UBX config commands
- revamp validSentence() to avoid heap allocation for errors
- export some errors for checking/suppression from client
- improvements and corrections for config commands
- **lora**
- fill out more constants for lora device
- **mcp2515**
- add support for extended CAN IDs (#857)
- **si5351**
- complete refactor for more complete interface
- **st7735**
- remove dependency on the machine package
- **sx127x**
- add functions used for FSK radio communication
- **ws2812**
- add brightness control
- add PIO support for RP2040/RP2350
- **bugfixes**
- **st7789**
- fix scroll on rotated displays
- fix driver when rotated 90º
- **ws2812**
- fix brightness control issues (#858)
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**
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright The TinyGo Authors. All rights reserved.
Copyright (c) 2018-2025 The TinyGo Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
-14
View File
@@ -26,17 +26,3 @@ unit-test:
@go test -v $(addprefix ./,$(TESTS))
test: clean fmt-check unit-test smoke-test
EXCLUDE_DIRS = build cmd examples internal lora ndir netdev netlink tester
drivers-count:
@root_count=$$(find . -mindepth 1 -maxdepth 1 -type d | grep -vE '^\./($(subst $(space),|,$(EXCLUDE_DIRS)))$$' | wc -l); \
epd_count=$$(find ./waveshare-epd -mindepth 1 -maxdepth 1 -type d 2>/dev/null | wc -l); \
total=$$((root_count + epd_count)); \
echo "Total drivers: $$total (root: $$root_count, waveshare-epd: $$epd_count)"
drivers-list:
@{ \
find . -mindepth 1 -maxdepth 1 -type d | grep -vE '^\./($(subst $(space),|,$(EXCLUDE_DIRS)))$$'; \
if [ -d ./waveshare-epd ]; then find ./waveshare-epd -mindepth 1 -maxdepth 1 -type d; fi; \
} | sed 's|^\./||' | sort
+1 -4
View File
@@ -3,14 +3,11 @@
[![PkgGoDev](https://pkg.go.dev/badge/tinygo.org/x/drivers)](https://pkg.go.dev/tinygo.org/x/drivers) [![Build](https://github.com/tinygo-org/drivers/actions/workflows/build.yml/badge.svg?branch=dev)](https://github.com/tinygo-org/drivers/actions/workflows/build.yml)
This package provides a collection of over 140 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 100 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/
> [!IMPORTANT]
> You can help TinyGo with a financial contribution using OpenCollective. Please see https://opencollective.com/tinygo for more information. Thank you!
## Installing
```shell
+1 -2
View File
@@ -8,7 +8,6 @@ import (
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/legacy"
"tinygo.org/x/drivers/internal/pin"
)
const (
@@ -38,7 +37,7 @@ 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 pin.Output, delay uint32) *Device {
func NewSoftwareSPI(sckPin, sdoPin drivers.PinOutput, delay uint32) *Device {
return New(&bbSPI{SCK: sckPin.Set, SDO: sdoPin.Set, Delay: delay, configurePins: func() {
legacy.ConfigurePinOut(sckPin)
legacy.ConfigurePinOut(sdoPin)
+3 -3
View File
@@ -1,8 +1,8 @@
package apa102
import (
"tinygo.org/x/drivers"
"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
@@ -11,8 +11,8 @@ import (
// 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 pin.OutputFunc
SDO pin.OutputFunc
SCK drivers.PinOutputFunc
SDO drivers.PinOutputFunc
Delay uint32
configurePins func()
}
+3 -3
View File
@@ -5,14 +5,13 @@ import (
"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 pin.OutputFunc
csb drivers.PinOutputFunc
buf [7]byte
@@ -24,7 +23,7 @@ type DeviceSPI struct {
// 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 pin.Output, spi drivers.SPI) *DeviceSPI {
func NewSPI(csb drivers.PinOutput, spi drivers.SPI) *DeviceSPI {
return &DeviceSPI{
csb: csb.Set, // chip select
bus: spi,
@@ -43,6 +42,7 @@ func (d *DeviceSPI) Configure() error {
}
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
-256
View File
@@ -1,256 +0,0 @@
// 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)
}
-173
View File
@@ -1,173 +0,0 @@
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
}
-179
View File
@@ -1,179 +0,0 @@
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
)
-316
View File
@@ -1,316 +0,0 @@
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
}
-43
View File
@@ -1,43 +0,0 @@
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
View File
@@ -1,387 +0,0 @@
// 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
}
}
-83
View File
@@ -1,83 +0,0 @@
// 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
View File
@@ -1,572 +0,0 @@
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
}
+3 -3
View File
@@ -4,18 +4,18 @@ package buzzer // import "tinygo.org/x/drivers/buzzer"
import (
"time"
"tinygo.org/x/drivers/internal/pin"
"tinygo.org/x/drivers"
)
// Device wraps a GPIO connection to a buzzer.
type Device struct {
pin pin.OutputFunc
pin drivers.PinOutputFunc
High bool
BPM float64
}
// New returns a new buzzer driver given which pin to use
func New(pin pin.Output) Device {
func New(pin drivers.PinOutput) Device {
return Device{
pin: pin.Set,
High: false,
+36 -288
View File
@@ -5,12 +5,10 @@
package ds3231 // import "tinygo.org/x/drivers/ds3231"
import (
"encoding/binary"
"errors"
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/regmap"
"tinygo.org/x/drivers/internal/legacy"
)
type Mode uint8
@@ -19,7 +17,6 @@ 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
@@ -27,50 +24,54 @@ type Device struct {
//
// This function only creates the Device object, it does not touch the device.
func New(bus drivers.I2C) Device {
d := Device{
return 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 {
status, err := d.d.Read8(REG_STATUS)
data := []byte{0}
err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_STATUS, data)
if err != nil {
return false
}
return (status & (1 << OSF)) == 0x00
return (data[0] & (1 << OSF)) == 0x00
}
// IsRunning returns if the oscillator is running
func (d *Device) IsRunning() bool {
control, err := d.d.Read8(REG_CONTROL)
data := []uint8{0}
err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_CONTROL, data)
if err != nil {
return false
}
return (control & (1 << EOSC)) == 0x00
return (data[0] & (1 << EOSC)) == 0x00
}
// SetRunning starts the internal oscillator
func (d *Device) SetRunning(isRunning bool) error {
control, err := d.d.Read8(REG_CONTROL)
data := []uint8{0}
err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_CONTROL, data)
if err != nil {
return err
}
if isRunning {
control &^= uint8(1 << EOSC)
data[0] &^= uint8(1 << EOSC)
} else {
control |= 1 << EOSC
data[0] |= 1 << EOSC
}
return d.d.Write8(REG_CONTROL, control)
err = legacy.WriteRegister(d.bus, uint8(d.Address), REG_CONTROL, data)
if err != nil {
return err
}
return nil
}
// SetTime sets the date and time in the DS3231. The DS3231 hardware supports
@@ -85,16 +86,18 @@ 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 {
status, err := d.d.Read8(REG_STATUS)
data := []byte{0}
err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_STATUS, data)
if err != nil {
return err
}
status &^= 1 << OSF
if err = d.d.Write8(REG_STATUS, status); err != nil {
data[0] &^= 1 << OSF
err = legacy.WriteRegister(d.bus, uint8(d.Address), REG_STATUS, data)
if 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()))
@@ -115,16 +118,21 @@ func (d *Device) SetTime(dt time.Time) error {
data[5] = uint8ToBCD(uint8(dt.Month()) | centuryFlag)
data[6] = uint8ToBCD(year)
return d.bus.Tx(d.Address, append([]byte{REG_TIMEDATE}, data...), nil)
err = legacy.WriteRegister(d.bus, uint8(d.Address), REG_TIMEDATE, data)
if err != nil {
return err
}
return nil
}
// ReadTime returns the date and time
func (d *Device) ReadTime() (dt time.Time, err error) {
data := make([]uint8, 7)
if err = d.d.ReadData(REG_TIMEDATE, data); err != nil {
err = legacy.ReadRegister(d.bus, uint8(d.Address), REG_TIMEDATE, data)
if err != nil {
return
}
second := bcdToInt(data[0] & 0x7F)
minute := bcdToInt(data[1])
hour := hoursBCDToInt(data[2])
@@ -142,264 +150,12 @@ func (d *Device) ReadTime() (dt time.Time, err error) {
// ReadTemperature returns the temperature in millicelsius (mC)
func (d *Device) ReadTemperature() (int32, error) {
temp, err := d.d.Read16(REG_TEMP)
data := make([]uint8, 2)
err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_TEMP, data)
if err != nil {
return 0, err
}
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
return milliCelsius(data[0], data[1]), nil
}
// milliCelsius converts the raw temperature bytes (msb and lsb) from the DS3231
@@ -416,8 +172,8 @@ func (d *Device) isAlarmFired(alarm_num uint8) bool {
// 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(tempBytes uint16) int32 {
t256 := int16(uint16(tempBytes>>8)<<8 | uint16(tempBytes&0xFF))
func milliCelsius(msb uint8, lsb uint8) int32 {
t256 := int16(uint16(msb)<<8 | uint16(lsb))
t1000 := int32(t256) / 64 * 250
return t1000
}
@@ -444,11 +200,3 @@ 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
View File
@@ -5,71 +5,71 @@ import (
)
func TestPositiveMilliCelsius(t *testing.T) {
t1000 := milliCelsius(0)
t1000 := milliCelsius(0, 0)
if t1000 != 0 {
t.Fatal(t1000)
}
t1000 = milliCelsius(0b0000000001000000)
t1000 = milliCelsius(0, 0b01000000)
if t1000 != 250 {
t.Fatal(t1000)
}
t1000 = milliCelsius(0b0000000010000000)
t1000 = milliCelsius(0, 0b10000000)
if t1000 != 500 {
t.Fatal(t1000)
}
t1000 = milliCelsius(0b0000000011000000)
t1000 = milliCelsius(0, 0b11000000)
if t1000 != 750 {
t.Fatal(t1000)
}
t1000 = milliCelsius(0b0000000100000000)
t1000 = milliCelsius(1, 0b00000000)
if t1000 != 1000 {
t.Fatal(t1000)
}
t1000 = milliCelsius(0b0000001000000000)
t1000 = milliCelsius(2, 0b00000000)
if t1000 != 2000 {
t.Fatal(t1000)
}
// highest temperature is 127.750C
t1000 = milliCelsius(0b0111111111000000)
t1000 = milliCelsius(0x7f, 0b11000000)
if t1000 != 127750 {
t.Fatal(t1000)
}
}
func TestNegativeMilliCelsius(t *testing.T) {
t1000 := milliCelsius(0b1111111111000000)
t1000 := milliCelsius(0xff, 0b11000000)
if t1000 != -250 {
t.Fatal(t1000)
}
t1000 = milliCelsius(0b1111111110000000)
t1000 = milliCelsius(0xff, 0b10000000)
if t1000 != -500 {
t.Fatal(t1000)
}
t1000 = milliCelsius(0b1111111101000000)
t1000 = milliCelsius(0xff, 0b01000000)
if t1000 != -750 {
t.Fatal(t1000)
}
t1000 = milliCelsius(0b1111111100000000)
t1000 = milliCelsius(0xff, 0b00000000)
if t1000 != -1000 {
t.Fatal(t1000)
}
t1000 = milliCelsius(0b1111111000000000)
t1000 = milliCelsius(0xfe, 0b00000000)
if t1000 != -2000 {
t.Fatal(t1000)
}
// lowest temperature is -128.000C
t1000 = milliCelsius(0b1000000000000000)
t1000 = milliCelsius(0x80, 0b00000000)
if t1000 != -128000 {
t.Fatal(t1000)
}
-49
View File
@@ -46,52 +46,3 @@ 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
)
+4 -67
View File
@@ -2,9 +2,9 @@
package easystepper // import "tinygo.org/x/drivers/easystepper"
import (
"errors"
"machine"
"time"
"tinygo.org/x/drivers"
)
// StepMode determines the coil sequence used to perform a single step
@@ -30,28 +30,10 @@ func (sm StepMode) stepCount() uint {
}
}
// DeviceConfig contains the configuration data for a single easystepper driver
type DeviceConfig struct {
// Pin1 ... Pin4 determines the pins to configure and use for the device
Pin1, Pin2, Pin3, Pin4 machine.Pin
// StepCount is the number of steps required to perform a full revolution of the stepper motor
StepCount uint
// RPM determines the speed of the stepper motor in 'Revolutions per Minute'
RPM uint
// Mode determines the coil sequence used to perform a single step
Mode StepMode
}
// DualDeviceConfig contains the configuration data for a dual easystepper driver
type DualDeviceConfig struct {
DeviceConfig
// Pin5 ... Pin8 determines the pins to configure and use for the second device
Pin5, Pin6, Pin7, Pin8 machine.Pin
}
// Device holds the pins and the delay between steps
type Device struct {
pins [4]machine.Pin
pins [4]drivers.PinOutputFunc
config func()
stepDelay time.Duration
stepNumber uint8
stepMode StepMode
@@ -62,51 +44,6 @@ type DualDevice struct {
devices [2]*Device
}
// New returns a new single easystepper driver given a DeviceConfig
func New(config DeviceConfig) (*Device, error) {
if config.StepCount == 0 || config.RPM == 0 {
return nil, errors.New("config.StepCount and config.RPM must be > 0")
}
return &Device{
pins: [4]machine.Pin{config.Pin1, config.Pin2, config.Pin3, config.Pin4},
stepDelay: time.Second * 60 / time.Duration((config.StepCount * config.RPM)),
stepMode: config.Mode,
}, nil
}
// Configure configures the pins of the Device
func (d *Device) Configure() {
for _, pin := range d.pins {
pin.Configure(machine.PinConfig{Mode: machine.PinOutput})
}
}
// NewDual returns a new dual easystepper driver given 8 pins, number of steps and rpm
func NewDual(config DualDeviceConfig) (*DualDevice, error) {
// Create the first device
dev1, err := New(config.DeviceConfig)
if err != nil {
return nil, err
}
// Create the second device
config.DeviceConfig.Pin1 = config.Pin5
config.DeviceConfig.Pin2 = config.Pin6
config.DeviceConfig.Pin3 = config.Pin7
config.DeviceConfig.Pin4 = config.Pin8
dev2, err := New(config.DeviceConfig)
if err != nil {
return nil, err
}
// Return composite dual device
return &DualDevice{devices: [2]*Device{dev1, dev2}}, nil
}
// Configure configures the pins of the DualDevice
func (d *DualDevice) Configure() {
d.devices[0].Configure()
d.devices[1].Configure()
}
// Move rotates the motor the number of given steps
// (negative steps will rotate it the opposite direction)
func (d *Device) Move(steps int32) {
+26
View File
@@ -0,0 +1,26 @@
package easystepper
import (
"errors"
"time"
"tinygo.org/x/drivers"
)
func NewCrossPlatform(stepcount, rpm uint, mode StepMode, pins [4]drivers.PinOutputFunc) (*Device, error) {
if stepcount == 0 || rpm == 0 {
return nil, errors.New("zero rpm and/or stepcount")
}
for i := range pins {
if pins[i] == nil {
return nil, errors.New("nil pin")
}
}
d := &Device{
pins: pins,
stepDelay: time.Second * 60 / time.Duration((stepcount * rpm)),
stepMode: mode,
config: func() {},
}
return d, nil
}
+83
View File
@@ -0,0 +1,83 @@
//go:build baremetal
package easystepper
import (
"errors"
"machine"
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/legacy"
)
// New returns a new single easystepper driver given a DeviceConfig
func New(config DeviceConfig) (*Device, error) {
if config.StepCount == 0 || config.RPM == 0 {
return nil, errors.New("config.StepCount and config.RPM must be > 0")
}
return &Device{
pins: [4]drivers.PinOutputFunc{config.Pin1.Set, config.Pin2.Set, config.Pin3.Set, config.Pin4.Set},
stepDelay: time.Second * 60 / time.Duration((config.StepCount * config.RPM)),
stepMode: config.Mode,
config: func() {
legacy.ConfigurePinOut(config.Pin1)
legacy.ConfigurePinOut(config.Pin2)
legacy.ConfigurePinOut(config.Pin3)
legacy.ConfigurePinOut(config.Pin4)
},
}, nil
}
// Configure configures the pins of the Device
func (d *Device) Configure() {
if d.config == nil {
panic(legacy.ErrConfigBeforeInstantiated)
}
d.config()
}
// Configure configures the pins of the DualDevice
func (d *DualDevice) Configure() {
d.devices[0].Configure()
d.devices[1].Configure()
}
// NewDual returns a new dual easystepper driver given 8 pins, number of steps and rpm
func NewDual(config DualDeviceConfig) (*DualDevice, error) {
// Create the first device
dev1, err := New(config.DeviceConfig)
if err != nil {
return nil, err
}
// Create the second device
config.DeviceConfig.Pin1 = config.Pin5
config.DeviceConfig.Pin2 = config.Pin6
config.DeviceConfig.Pin3 = config.Pin7
config.DeviceConfig.Pin4 = config.Pin8
dev2, err := New(config.DeviceConfig)
if err != nil {
return nil, err
}
// Return composite dual device
return &DualDevice{devices: [2]*Device{dev1, dev2}}, nil
}
// DeviceConfig contains the configuration data for a single easystepper driver
type DeviceConfig struct {
// Pin1 ... Pin4 determines the pins to configure and use for the device
Pin1, Pin2, Pin3, Pin4 machine.Pin
// StepCount is the number of steps required to perform a full revolution of the stepper motor
StepCount uint
// RPM determines the speed of the stepper motor in 'Revolutions per Minute'
RPM uint
// Mode determines the coil sequence used to perform a single step
Mode StepMode
}
// DualDeviceConfig contains the configuration data for a dual easystepper driver
type DualDeviceConfig struct {
DeviceConfig
// Pin5 ... Pin8 determines the pins to configure and use for the second device
Pin5, Pin6, Pin7, Pin8 machine.Pin
}
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build tinygo && (rp2040 || rp2350 || stm32 || k210 || esp32c3 || nrf || sam || (avr && (atmega328p || atmega328pb)))
//go:build tinygo && (rp2040 || stm32 || k210 || esp32c3 || nrf || sam || (avr && (atmega328p || atmega328pb)))
// Implementation based on:
// https://gist.github.com/aykevl/3fc1683ed77bb0a9c07559dfe857304a
-66
View File
@@ -1,66 +0,0 @@
// 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)
}
}
-74
View File
@@ -1,74 +0,0 @@
// 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,9 +3,10 @@ package main
import (
"machine"
"strconv"
"time"
"fmt"
"tinygo.org/x/drivers/ds3231"
)
@@ -25,19 +26,19 @@ func main() {
if !running {
err := rtc.SetRunning(true)
if err != nil {
println("Error configuring RTC")
fmt.Println("Error configuring RTC")
}
}
for {
dt, err := rtc.ReadTime()
if err != nil {
println("Error reading date:", err)
fmt.Println("Error reading date:", err)
} else {
println(dt.Format(time.DateTime))
fmt.Printf("Date: %d/%s/%02d %02d:%02d:%02d \r\n", dt.Year(), dt.Month(), dt.Day(), dt.Hour(), dt.Minute(), dt.Second())
}
temp, _ := rtc.ReadTemperature()
println("Temperature:", strconv.FormatFloat(float64(temp)/1000, 'f', -1, 32), "°C")
fmt.Printf("Temperature: %.2f °C \r\n", float32(temp)/1000)
time.Sleep(time.Second * 1)
}
+6 -18
View File
@@ -8,6 +8,7 @@ import (
)
func main() {
println("GPS UART Example")
machine.UART1.Configure(machine.UARTConfig{BaudRate: 9600})
ublox := gps.NewUART(machine.UART1)
parser := gps.NewParser()
@@ -15,24 +16,14 @@ func main() {
for {
s, err := ublox.NextSentence()
if err != nil {
switch err {
case gps.ErrUnknownNMEASentence, gps.ErrInvalidNMEASentence, gps.ErrInvalidNMEASentenceLength:
continue
default:
println("sentence error:", err)
continue
}
println(err)
continue
}
fix, err = parser.Parse(s)
if err != nil {
switch err {
case gps.ErrUnknownNMEASentence, gps.ErrInvalidNMEASentence, gps.ErrInvalidNMEASentenceLength:
continue
default:
println("parse error:", err)
continue
}
println(err)
continue
}
if fix.Valid {
print(fix.Time.Format("15:04:05"))
@@ -52,10 +43,7 @@ func main() {
}
println()
} else {
if fix.Type == gps.GSV {
// GSV sentence provides satellite count even if no fix yet
println(fix.Satellites, "satellites visible")
}
println("No fix")
}
time.Sleep(200 * time.Millisecond)
}
-49
View File
@@ -1,49 +0,0 @@
package main
import (
"machine"
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/honeyhsc"
)
// Data taken from https://github.com/rodan/honeywell_hsc_ssc_i2c/blob/master/hsc_ssc_i2c.cpp
// these defaults are valid for the HSCMRNN030PA2A3 chip
const (
i2cAddress = 0x28
// 10%
outputMinimum = 0x666
// 90% of 2^14 - 1
outputMax = 0x399A
// min is 0 for sensors that give absolute values
pressureMin = 0
// 30psi (and we want results in millipascals)
// pressureMax = 206842.7
pressureMax = 206843 * 1000
)
func main() {
bus := machine.I2C0
err := bus.Configure(machine.I2CConfig{
Frequency: 400_000, // 100kHz minimum and 400kHz I2C maximum clock. 50 to 800 for SPI.
SDA: machine.I2C0_SDA_PIN,
SCL: machine.I2C0_SCL_PIN,
})
if err != nil {
panic(err.Error())
}
sensor := honeyhsc.NewDevI2C(bus, i2cAddress, outputMinimum, outputMax, pressureMin, pressureMax)
for {
time.Sleep(time.Second)
const measuremask = drivers.Pressure | drivers.Temperature
err := sensor.Update(measuremask)
if err != nil {
println("error updating measurements:", err.Error())
continue
}
P := sensor.Pressure()
T := sensor.Temperature()
println("pressure:", P, "temperature:", T)
}
}
+3 -12
View File
@@ -14,18 +14,9 @@ func main() {
i2c.Configure(machine.I2CConfig{SCL: machine.SCL1_PIN, SDA: machine.SDA1_PIN})
accel := lis3dh.New(i2c)
err := accel.Configure(lis3dh.Config{
Address: lis3dh.Address1, // address on the Circuit Playground Express
})
for err != nil {
println("could not configure LIS3DH:", err)
time.Sleep(time.Second)
}
err = accel.SetRange(lis3dh.RANGE_2_G)
for err != nil {
println("could not set acceleration range:", err)
time.Sleep(time.Second)
}
accel.Address = lis3dh.Address1 // address on the Circuit Playground Express
accel.Configure()
accel.SetRange(lis3dh.RANGE_2_G)
println(accel.Connected())
+1 -1
View File
@@ -21,7 +21,7 @@ func main() {
SDI: machine.SPI0_SDI_PIN,
Mode: 0})
can := mcp2515.New(spi, csPin)
can.Configure(mcp2515.Configuration{})
can.Configure()
err := can.Begin(mcp2515.CAN500kBps, mcp2515.Clock8MHz)
if err != nil {
failMessage(err.Error())
-88
View File
@@ -1,88 +0,0 @@
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)
// Initialize device
cnf := si5351.Config{
Capacitance: si5351.CrystalLoad10PF,
}
if err := clockgen.Configure(cnf); err != nil {
println("Failed to configure Si5351:", err.Error())
return
}
println("Si5351 configured")
// Now configure the clock outputs.
clockgen.SetFrequency(si5351.Clock0, 112_500_000)
println("Clock 0: 112.5mhz")
// Next configure clock 1 for 13.5531mhz (616.6667mhz / 45.5).
// This uses fractional division.
clockgen.SetFrequency(si5351.Clock1, 13_553_125)
println("Clock 1: 13.5531mhz")
// Finally configure clock 2 to output of 10.706khz.
clockgen.SetFrequency(si5351.Clock2, 10_706)
println("Clock 2: 10.706khz")
// After configuring the clocks enable the outputs.
clockgen.EnableOutput(si5351.Clock0, true)
clockgen.EnableOutput(si5351.Clock1, true)
clockgen.EnableOutput(si5351.Clock2, true)
println("All outputs enabled")
time.Sleep(time.Second)
clockgen.EnableOutput(si5351.Clock0, false)
clockgen.EnableOutput(si5351.Clock1, false)
clockgen.EnableOutput(si5351.Clock2, false)
println("All outputs disabled for 5 seconds")
time.Sleep(5 * time.Second)
// Now turn clock outputs on and off repeatedly
on := false
for {
if on {
println("Setting clock outputs off")
clockgen.EnableOutput(si5351.Clock0, false)
clockgen.EnableOutput(si5351.Clock1, false)
clockgen.EnableOutput(si5351.Clock2, false)
on = false
} else {
println("Setting clock outputs on")
clockgen.EnableOutput(si5351.Clock0, true)
clockgen.EnableOutput(si5351.Clock1, true)
clockgen.EnableOutput(si5351.Clock2, true)
on = true
}
time.Sleep(1 * time.Second)
}
}
+1 -2
View File
@@ -5,7 +5,6 @@ import (
"machine"
"math/rand"
"tinygo.org/x/drivers/internal/pin"
"tinygo.org/x/drivers/ssd1289"
)
@@ -17,7 +16,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]pin.Output{
bus := ssd1289.NewPinBus([16]machine.Pin{
machine.GP4, //DB0
machine.GP5, //DB1
machine.GP6, //DB2
-106
View File
@@ -1,106 +0,0 @@
package main
import (
"errors"
"machine"
"runtime"
"time"
"tinygo.org/x/drivers/sx128x"
)
var (
// pin mapping specific to the lilygo t3s3, change as needed for your board
sdoPin = machine.GPIO6
sdiPin = machine.GPIO3
sckPin = machine.GPIO5
nssPin = machine.GPIO7
busyPin = machine.GPIO36
resetPin = machine.GPIO8
dio1Pin = machine.GPIO9
)
func setupPins() {
nssPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
nssPin.Set(true)
resetPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
resetPin.Set(true)
busyPin.Configure(machine.PinConfig{Mode: machine.PinInput})
dio1Pin.Configure(machine.PinConfig{Mode: machine.PinInput})
}
func main() {
setupPins()
spi := machine.SPI0
spi.Configure(machine.SPIConfig{
Mode: 0,
Frequency: 8 * 1e6,
SDO: sdoPin,
SDI: sdiPin,
SCK: sckPin,
})
radio := sx128x.New(
spi,
nssPin,
resetPin,
busyPin,
)
radio.WaitWhileBusy(time.Second)
SetupLora(radio)
for {
data, err := Rx(radio)
if err != nil {
println("failed to receive:", err)
} else {
println("received:", string(data))
}
}
}
func SetupLora(radio *sx128x.Device) {
radio.SetStandby(sx128x.STANDBY_RC)
radio.SetPacketType(sx128x.PACKET_TYPE_LORA)
radio.SetRegulatorMode(sx128x.REGULATOR_DC_DC)
radio.SetRfFrequency(2400000000) // 2.4Ghz
radio.SetModulationParamsLoRa(sx128x.LORA_SF_9, sx128x.LORA_BW_1600, sx128x.LORA_CR_4_7)
// section 14.4.1 shows required register setting for setting up LoRa operations. These depend on the chosen spreading factor.
radio.WriteRegister(0x925, []byte{0x32})
radio.WriteRegister(0x93C, []byte{0x01})
radio.SetTxParams(13, sx128x.RADIO_RAMP_02_US)
radio.SetPacketParamsLoRa(12, sx128x.LORA_HEADER_EXPLICIT, 0xFF, sx128x.LORA_CRC_DISABLE, sx128x.LORA_IQ_STD)
radio.WriteRegister(sx128x.REG_LORA_SYNC_WORD_MSB, []byte{0x14, 0x24}) // full sync word is 0x1424
}
func Rx(radio *sx128x.Device) ([]byte, error) {
radio.SetStandby(sx128x.STANDBY_RC)
radio.SetDioIrqParams(sx128x.IRQ_RX_DONE_MASK|sx128x.IRQ_RX_TX_TIMEOUT_MASK, sx128x.IRQ_RX_DONE_MASK|sx128x.IRQ_RX_TX_TIMEOUT_MASK, 0x00, 0x00)
radio.SetBufferBaseAddress(0, 0)
radio.ClearIrqStatus(sx128x.IRQ_ALL_MASK)
radio.SetRx(sx128x.PERIOD_BASE_4_MS, 250) // 4ms * 250 = 1s
// busy wait for IRQ indication
for dio1Pin.Get() == false {
runtime.Gosched()
}
irqStatus, _ := radio.GetIrqStatus()
if irqStatus&sx128x.IRQ_RX_DONE_MASK != 0 {
payloadLength, bufferOffset, err := radio.GetRxBufferStatus()
if err != nil {
return nil, err
}
data, err := radio.ReadBuffer(bufferOffset, payloadLength)
return data, nil
} else if irqStatus&sx128x.IRQ_RX_TX_TIMEOUT_MASK != 0 {
return nil, errors.New("rx timeout")
}
return nil, errors.New("unexpected IRQ status")
}
-96
View File
@@ -1,96 +0,0 @@
package main
import (
"errors"
"machine"
"runtime"
"time"
"tinygo.org/x/drivers/sx128x"
)
var (
// pin mapping specific to the lilygo t3s3, change as needed for your board
sdoPin = machine.GPIO6
sdiPin = machine.GPIO3
sckPin = machine.GPIO5
nssPin = machine.GPIO7
busyPin = machine.GPIO36
resetPin = machine.GPIO8
dio1Pin = machine.GPIO9
)
func setupPins() {
nssPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
nssPin.Set(true)
resetPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
resetPin.Set(true)
busyPin.Configure(machine.PinConfig{Mode: machine.PinInput})
dio1Pin.Configure(machine.PinConfig{Mode: machine.PinInput})
}
func main() {
setupPins()
spi := machine.SPI0
spi.Configure(machine.SPIConfig{
Mode: 0,
Frequency: 8 * 1e6,
SDO: sdoPin,
SDI: sdiPin,
SCK: sckPin,
})
radio := sx128x.New(
spi,
nssPin,
resetPin,
busyPin,
)
radio.WaitWhileBusy(time.Second)
SetupLora(radio)
for {
Tx(radio, []byte("Hello, world!"))
time.Sleep(1 * time.Second)
}
}
func SetupLora(radio *sx128x.Device) {
radio.SetStandby(sx128x.STANDBY_RC)
radio.SetPacketType(sx128x.PACKET_TYPE_LORA)
radio.SetRegulatorMode(sx128x.REGULATOR_DC_DC)
radio.SetRfFrequency(2400000000) // 2.4Ghz
radio.SetModulationParamsLoRa(sx128x.LORA_SF_9, sx128x.LORA_BW_1600, sx128x.LORA_CR_4_7)
// section 14.4.1 shows required register setting for setting up LoRa operations. These depend on the chosen spreading factor.
radio.WriteRegister(0x925, []byte{0x32})
radio.WriteRegister(0x93C, []byte{0x01})
radio.SetTxParams(13, sx128x.RADIO_RAMP_02_US)
radio.SetPacketParamsLoRa(12, sx128x.LORA_HEADER_EXPLICIT, 0xFF, sx128x.LORA_CRC_DISABLE, sx128x.LORA_IQ_STD)
radio.WriteRegister(sx128x.REG_LORA_SYNC_WORD_MSB, []byte{0x14, 0x24}) // full sync word is 0x1424
}
func Tx(radio *sx128x.Device, data []byte) error {
if len(data) > 255 {
return errors.New("data length exceeds maximum of 255 bytes")
}
radio.SetStandby(sx128x.STANDBY_RC)
radio.SetPacketParamsLoRa(12, sx128x.LORA_HEADER_EXPLICIT, uint8(len(data)&0xFF), sx128x.LORA_CRC_DISABLE, sx128x.LORA_IQ_STD)
radio.SetBufferBaseAddress(0, 0)
radio.WriteBuffer(0, data)
radio.SetDioIrqParams(sx128x.IRQ_TX_DONE_MASK|sx128x.IRQ_RX_TX_TIMEOUT_MASK, sx128x.IRQ_TX_DONE_MASK|sx128x.IRQ_RX_TX_TIMEOUT_MASK, 0x00, 0x00)
radio.ClearIrqStatus(sx128x.IRQ_ALL_MASK)
radio.SetTx(sx128x.PERIOD_BASE_4_MS, 250) // 4ms * 250 = 1s
// busy wait for IRQ indication
for dio1Pin.Get() == false {
runtime.Gosched()
}
return nil
}
-54
View File
@@ -1,54 +0,0 @@
package main
import (
"machine"
"image/color"
"math/rand"
"tinygo.org/x/drivers/unoqmatrix"
)
var on = color.RGBA{255, 255, 255, 255}
func main() {
display := unoqmatrix.NewFromBasePin(machine.PF0)
display.ClearDisplay()
w, h := display.Size()
x := int16(0)
y := int16(0)
deltaX := int16(1)
deltaY := int16(1)
for {
pixel := display.GetPixel(x, y)
if pixel.R != 0 || pixel.G != 0 || pixel.B != 0 {
display.ClearDisplay()
x = 1 + int16(rand.Int31n(3))
y = 1 + int16(rand.Int31n(3))
deltaX = 1
deltaY = 1
if rand.Int31n(2) == 0 {
deltaX = -1
}
if rand.Int31n(2) == 0 {
deltaY = -1
}
}
display.SetPixel(x, y, on)
x += deltaX
y += deltaY
if x == 0 || x == w-1 {
deltaX = -deltaX
}
if y == 0 || y == h-1 {
deltaY = -deltaY
}
display.Display()
}
}
-37
View File
@@ -1,37 +0,0 @@
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
}
}
-137
View File
@@ -1,137 +0,0 @@
package main
import (
"image/color"
"machine"
"time"
"tinygo.org/x/drivers/waveshare-epd/epd2in9v2"
)
var display epd2in9v2.Device
func main() {
machine.SPI0.Configure(machine.SPIConfig{
Frequency: 12000000,
SCK: machine.EPD_SCK_PIN,
SDO: machine.EPD_SDO_PIN,
})
display = epd2in9v2.New(
machine.SPI0,
machine.EPD_CS_PIN,
machine.EPD_DC_PIN,
machine.EPD_RESET_PIN,
machine.EPD_BUSY_PIN,
)
display.Configure(epd2in9v2.Config{
Rotation: epd2in9v2.ROTATION_270,
Speed: epd2in9v2.SPEED_DEFAULT,
Blocking: true,
})
black := color.RGBA{0, 0, 0, 255}
white := color.RGBA{255, 255, 255, 255}
// --- Step 1: clear to white ---
println("epd2in9v2: clearing display")
display.ClearBuffer()
display.Display()
time.Sleep(2 * time.Second)
// --- Step 2: full refresh checkerboard ---
println("epd2in9v2: drawing checkerboard (full refresh)")
w, h := display.Size()
for i := int16(0); i < w/8; i++ {
for j := int16(0); j < h/8; j++ {
if (i+j)%2 == 0 {
fillRect(i*8, j*8, 8, 8, black)
}
}
}
display.Display()
time.Sleep(2 * time.Second)
// --- Step 3: fast refresh - draw border and diagonal cross ---
println("epd2in9v2: switching to fast refresh")
display.SetSpeed(epd2in9v2.SPEED_FAST)
display.ClearBuffer()
for x := int16(0); x < w; x++ {
display.SetPixel(x, 0, black)
display.SetPixel(x, h-1, black)
}
for y := int16(0); y < h; y++ {
display.SetPixel(0, y, black)
display.SetPixel(w-1, y, black)
}
for i := int16(0); i < w && i < h; i++ {
display.SetPixel(i, i*h/w, black)
display.SetPixel(w-1-i, i*h/w, black)
}
display.Display()
time.Sleep(2 * time.Second)
// --- Step 4: partial refresh counter ---
println("epd2in9v2: partial refresh demo")
display.SetSpeed(epd2in9v2.SPEED_DEFAULT)
display.ClearBuffer()
println("epd2in9v2: setting base image")
display.DisplayWithBase()
for count := 0; count < 10; count++ {
cx := int16(120)
cy := int16(50)
fillRect(cx, cy, 60, 20, white)
digit := int16(count % 10)
drawDigit(cx+22, cy+2, digit, black)
display.DisplayPartial()
time.Sleep(500 * time.Millisecond)
}
time.Sleep(2 * time.Second)
// --- Step 5: sleep ---
println("epd2in9v2: entering deep sleep")
display.ClearBuffer()
display.Display()
display.Sleep()
println("epd2in9v2: done, you can remove power")
}
func fillRect(x, y, w, h int16, c color.RGBA) {
for i := x; i < x+w; i++ {
for j := y; j < y+h; j++ {
display.SetPixel(i, j, c)
}
}
}
// drawDigit draws a simple 3x5-pixel-block digit (each block 4x3 px) at position (x,y).
func drawDigit(x, y, digit int16, c color.RGBA) {
segments := [10][5]uint8{
{0x7, 0x5, 0x5, 0x5, 0x7}, // 0
{0x2, 0x2, 0x2, 0x2, 0x2}, // 1
{0x7, 0x1, 0x7, 0x4, 0x7}, // 2
{0x7, 0x1, 0x7, 0x1, 0x7}, // 3
{0x5, 0x5, 0x7, 0x1, 0x1}, // 4
{0x7, 0x4, 0x7, 0x1, 0x7}, // 5
{0x7, 0x4, 0x7, 0x5, 0x7}, // 6
{0x7, 0x1, 0x1, 0x1, 0x1}, // 7
{0x7, 0x5, 0x7, 0x5, 0x7}, // 8
{0x7, 0x5, 0x7, 0x1, 0x7}, // 9
}
if digit < 0 || digit > 9 {
return
}
for row := int16(0); row < 5; row++ {
for col := int16(0); col < 3; col++ {
if segments[digit][row]&(0x4>>uint(col)) != 0 {
fillRect(x+col*4, y+row*3, 4, 3, c)
}
}
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build arduino || arduino_uno
//go:build arduino
package main
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build !digispark && !arduino && !arduino_uno && !xiao_esp32c3
//go:build !digispark && !arduino
package main
-11
View File
@@ -1,11 +0,0 @@
//go:build xiao_esp32c3
package main
import "machine"
func init() {
// Replace neo in the code below to match the pin
// that you are using if different.
neo = machine.D6
}
+12 -12
View File
@@ -1,6 +1,3 @@
// 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.
//
@@ -8,8 +5,6 @@
package ft6336
import (
"machine"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/legacy"
"tinygo.org/x/drivers/touch"
@@ -17,19 +12,21 @@ import (
// Device wraps FT6336 I2C Self-Capacitive touch
type Device struct {
bus drivers.I2C
buf []byte
Address uint8
intPin machine.Pin
bus drivers.I2C
buf []byte
Address uint8
configurePins func()
}
// New returns FT6336 device for the provided I2C bus using default address.
func New(i2c drivers.I2C, intPin machine.Pin) *Device {
func New(i2c drivers.I2C, intPin drivers.PinInput) *Device {
return &Device{
bus: i2c,
buf: make([]byte, 11),
Address: Address,
intPin: intPin,
configurePins: func() {
legacy.ConfigurePinInputPulldown(intPin)
},
}
}
@@ -39,8 +36,11 @@ type Config struct {
// Configure the FT6336 device.
func (d *Device) Configure(config Config) error {
if d.configurePins == nil {
return legacy.ErrConfigBeforeInstantiated
}
d.write1Byte(0xA4, 0x00)
d.intPin.Configure(machine.PinConfig{Mode: machine.PinInputPulldown})
d.configurePins()
return nil
}
+15 -15
View File
@@ -5,12 +5,12 @@ package gc9a01 // import "tinygo.org/x/drivers/gc9a01"
import (
"image/color"
"machine"
"time"
"errors"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/legacy"
)
// Rotation controls the rotation used by the display.
@@ -22,10 +22,10 @@ type FrameRate uint8
// Device wraps an SPI connection.
type Device struct {
bus drivers.SPI
dcPin machine.Pin
resetPin machine.Pin
csPin machine.Pin
blPin machine.Pin
dcPin drivers.PinOutputFunc
resetPin drivers.PinOutputFunc
csPin drivers.PinOutputFunc
blPin drivers.PinOutputFunc
width int16
height int16
columnOffsetCfg int16
@@ -52,17 +52,17 @@ type Config struct {
}
// New creates a new ST7789 connection. The SPI wire must already be configured.
func New(bus drivers.SPI, resetPin, dcPin, csPin, blPin machine.Pin) Device {
resetPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
blPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
func New(bus drivers.SPI, resetPin, dcPin, csPin, blPin drivers.PinOutput) Device {
legacy.ConfigurePinOut(resetPin)
legacy.ConfigurePinOut(dcPin)
legacy.ConfigurePinOut(csPin)
legacy.ConfigurePinOut(blPin)
return Device{
bus: bus,
resetPin: resetPin,
dcPin: dcPin,
csPin: csPin,
blPin: blPin,
resetPin: resetPin.Set,
dcPin: dcPin.Set,
csPin: csPin.Set,
blPin: blPin.Set,
}
}
@@ -226,7 +226,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)
}
+2 -1
View File
@@ -1,16 +1,17 @@
module tinygo.org/x/drivers
go 1.22.1
toolchain go1.23.1
require (
github.com/eclipse/paho.mqtt.golang v1.2.0
github.com/frankban/quicktest v1.10.2
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
github.com/orsinium-labs/tinymath v1.1.0
github.com/soypat/natiu-mqtt v0.5.1
github.com/tinygo-org/pio v0.3.0
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d
golang.org/x/net v0.33.0
tinygo.org/x/tinyfont v0.3.0
-2
View File
@@ -17,8 +17,6 @@ github.com/orsinium-labs/tinymath v1.1.0 h1:KomdsyLHB7vE3f1nRAJF2dyf1m/gnM2HxfTe
github.com/orsinium-labs/tinymath v1.1.0/go.mod h1:WPXX6ei3KSXG7JfA03a+ekCYaY9SWN4I+JRl2p6ck+A=
github.com/soypat/natiu-mqtt v0.5.1 h1:rwaDmlvjzD2+3MCOjMZc4QEkDkNwDzbct2TJbpz+TPc=
github.com/soypat/natiu-mqtt v0.5.1/go.mod h1:xEta+cwop9izVCW7xOx2W+ct9PRMqr0gNVkvBPnQTc4=
github.com/tinygo-org/pio v0.3.0 h1:opEnOtw58KGB4RJD3/n/Rd0/djYGX3DeJiXLI6y/yDI=
github.com/tinygo-org/pio v0.3.0/go.mod h1:wf6c6lKZp+pQOzKKcpzchmRuhiMc27ABRuo7KVnaMFU=
github.com/valyala/fastjson v1.6.3/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d h1:0olWaB5pg3+oychR51GUVCEsGkeCU/2JxjBgIo4f3M0=
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
+34 -11
View File
@@ -11,18 +11,37 @@ import (
)
var (
ErrInvalidNMEASentenceLength = errors.New("invalid NMEA sentence length")
ErrInvalidNMEASentence = errors.New("invalid NMEA sentence format")
ErrEmptyNMEASentence = errors.New("cannot parse empty NMEA sentence")
ErrUnknownNMEASentence = errors.New("unsupported NMEA sentence type")
errInvalidGSVSentence = errors.New("invalid GSV NMEA sentence")
errInvalidNMEASentenceLength = errors.New("invalid NMEA sentence length")
errInvalidNMEAChecksum = errors.New("invalid NMEA sentence checksum")
errEmptyNMEASentence = errors.New("cannot parse empty NMEA sentence")
errUnknownNMEASentence = errors.New("unsupported NMEA sentence type")
errInvalidGGASentence = errors.New("invalid GGA NMEA sentence")
errInvalidRMCSentence = errors.New("invalid RMC NMEA sentence")
errInvalidGLLSentence = errors.New("invalid GLL NMEA sentence")
errGPSCommandRejected = errors.New("GPS command rejected (NAK)")
errNoACKToGPSCommand = errors.New("no ACK to GPS command")
)
type GPSError struct {
Err error
Info string
Sentence string
}
func newGPSError(err error, sentence string, info string) GPSError {
return GPSError{
Info: info,
Err: err,
Sentence: sentence,
}
}
func (ge GPSError) Error() string {
return ge.Err.Error() + " " + ge.Info + " " + ge.Sentence
}
func (ge GPSError) Unwrap() error {
return ge.Err
}
const (
minimumNMEALength = 7
startingDelimiter = '$'
@@ -31,18 +50,19 @@ const (
// Device wraps a connection to a GPS device.
type Device struct {
buffer []byte
bufIdx int
sentence strings.Builder
uart drivers.UART
bus drivers.I2C
address uint16
buffer [bufferSize]byte
}
// NewUART creates a new UART GPS connection. The UART must already be configured.
func NewUART(uart drivers.UART) Device {
return Device{
uart: uart,
buffer: make([]byte, bufferSize),
bufIdx: bufferSize,
sentence: strings.Builder{},
}
@@ -59,6 +79,7 @@ func NewI2CWithAddress(bus drivers.I2C, i2cAddress uint16) Device {
return Device{
bus: bus,
address: i2cAddress,
buffer: make([]byte, bufferSize),
bufIdx: bufferSize,
sentence: strings.Builder{},
}
@@ -151,15 +172,17 @@ func (gps *Device) WriteBytes(bytes []byte) {
// It has to end with a '*' character following by a checksum.
func validSentence(sentence string) error {
if len(sentence) < minimumNMEALength || sentence[0] != startingDelimiter || sentence[len(sentence)-3] != checksumDelimiter {
return ErrInvalidNMEASentenceLength
return errInvalidNMEASentenceLength
}
var cs byte = 0
for i := 1; i < len(sentence)-3; i++ {
cs ^= sentence[i]
}
checksum := strings.ToUpper(hex.EncodeToString([]byte{cs}))
if checksum != sentence[len(sentence)-2:] {
return ErrInvalidNMEASentence
if checksum != sentence[len(sentence)-2:len(sentence)] {
return newGPSError(errInvalidNMEAChecksum, sentence,
"expected "+sentence[len(sentence)-2:len(sentence)]+
" got "+checksum)
}
return nil
+3 -39
View File
@@ -6,28 +6,12 @@ import (
"time"
)
type NMEASentenceType string
const (
GSA NMEASentenceType = "GSA"
GGA NMEASentenceType = "GGA"
GLL NMEASentenceType = "GLL"
GSV NMEASentenceType = "GSV"
RMC NMEASentenceType = "RMC"
VTG NMEASentenceType = "VTG"
ZDA NMEASentenceType = "ZDA"
TXT NMEASentenceType = "TXT"
)
// Parser for GPS NMEA sentences.
type Parser struct {
}
// Fix is a GPS location fix
type Fix struct {
// Type is the NMEA sentence type that provided this fix.
Type NMEASentenceType
// Valid if the fix was valid.
Valid bool
@@ -62,30 +46,13 @@ func NewParser() Parser {
func (parser *Parser) Parse(sentence string) (Fix, error) {
var fix Fix
if sentence == "" {
return fix, ErrEmptyNMEASentence
return fix, errEmptyNMEASentence
}
if len(sentence) < 6 {
return fix, ErrInvalidNMEASentenceLength
return fix, errInvalidNMEASentenceLength
}
typ := sentence[3:6]
switch typ {
case "GSV":
// https://docs.novatel.com/OEM7/Content/Logs/GPGSV.htm
fields := strings.Split(sentence, ",")
// GSV sentences have at least 4 fields, but typically 8, 12, 16, or 20 depending on satellites in view
if len(fields) < 4 {
return fix, errInvalidGSVSentence
}
fix.Type = GSV
// Number of satellites in view is always field 3
fix.Satellites = findSatellites(fields[3])
// GSV does not provide position, time, or fix validity
fix.Valid = false
return fix, nil
case "GGA":
// https://docs.novatel.com/OEM7/Content/Logs/GPGGA.htm
fields := strings.Split(sentence, ",")
@@ -93,7 +60,6 @@ func (parser *Parser) Parse(sentence string) (Fix, error) {
return fix, errInvalidGGASentence
}
fix.Type = GGA
fix.Time = findTime(fields[1])
fix.Latitude = findLatitude(fields[2], fields[3])
fix.Longitude = findLongitude(fields[4], fields[5])
@@ -109,7 +75,6 @@ func (parser *Parser) Parse(sentence string) (Fix, error) {
return fix, errInvalidGLLSentence
}
fix.Type = GLL
fix.Latitude = findLatitude(fields[1], fields[2])
fix.Longitude = findLongitude(fields[3], fields[4])
fix.Time = findTime(fields[5])
@@ -124,7 +89,6 @@ func (parser *Parser) Parse(sentence string) (Fix, error) {
return fix, errInvalidRMCSentence
}
fix.Type = RMC
fix.Time = findTime(fields[1])
fix.Valid = (fields[2] == "A")
fix.Latitude = findLatitude(fields[3], fields[4])
@@ -140,7 +104,7 @@ func (parser *Parser) Parse(sentence string) (Fix, error) {
return fix, nil
}
return fix, ErrUnknownNMEASentence
return fix, newGPSError(errUnknownNMEASentence, sentence, typ)
}
// findTime returns the time from an NMEA sentence:
+2 -18
View File
@@ -8,29 +8,13 @@ import (
)
func TestParseUnknownSentence(t *testing.T) {
p := NewParser()
val := "$GPVTG,89.68,T,,M,0.00,N,0.0,K*5F"
_, err := p.Parse(val)
if err == nil {
t.Error("should have unknown sentence err")
}
}
func TestParseGSV(t *testing.T) {
c := qt.New(t)
p := NewParser()
val := "$GPGSV,3,1,09,07,14,317,22,08,31,284,25,10,32,133,39,16,85,232,29*7F"
fix, err := p.Parse(val)
if err != nil {
t.Error("should have parsed")
}
c.Assert(fix.Type, qt.Equals, GSV)
c.Assert(fix.Satellites, qt.Equals, int16(9))
c.Assert(fix.Valid, qt.Equals, false)
_, err := p.Parse(val)
c.Assert(err.Error(), qt.Contains, "unsupported NMEA sentence type")
}
func TestParseGGA(t *testing.T) {
+39 -240
View File
@@ -1,254 +1,53 @@
package gps
import (
"errors"
"time"
)
// FlightModeCmd is a UBX-CFG-NAV5 command
var nav5Cmd = CfgNav5{
Mask: CfgNav5Dyn | CfgNav5MinEl | CfgNav5PosFixMode,
DynModel: DynModeAirborne1g, // Airborne with <1g acceleration
FixMode: FixModeAuto, // Auto 2D/3D
MinElev_deg: 5, // Minimum elevation 5 degrees
FixedAlt_me2: 0, // Not used
FixedAltVar_m2e4: 0, // Not used
PDop: 100, // 10.0
TDop: 100, // 10.0
PAcc_m: 5000, // 5 meters
TAcc_m: 5000, // 5 meters
StaticHoldThresh_cm_s: 0, // Not used
DgnssTimeout_s: 0, // Not used
CnoThreshNumSVs: 0, // Not used
CnoThresh_dbhz: 0, // Not used
StaticHoldMaxDist_m: 0, // Not used
UtcStandard: 0, // Automatic
Reserved1: [2]byte{},
Reserved2: [5]byte{},
// flight mode disables the GPS COCOM limits
var flight_mode_cmd = [...]byte{
0xB5, 0x62, 0x06, 0x24, 0x24, 0x00, 0xFF, 0xFF, 0x06, 0x03, 0x00, 0x00, 0x00,
0x00, 0x10, 0x27, 0x00, 0x00, 0x05, 0x00, 0xFA, 0x00, 0xFA, 0x00, 0x64, 0x00,
0x2C, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x16, 0xDC}
// Sets CFG-GNSS to disable everything other than GPS GNSS
// solution. Failure to do this means GPS power saving
// doesn't work. Not needed for MAX7, needed for MAX8's
var cfg_gnss_cmd = [...]byte{
0xB5, 0x62, 0x06, 0x3E, 0x2C, 0x00, 0x00, 0x00,
0x20, 0x05, 0x00, 0x08, 0x10, 0x00, 0x01, 0x00,
0x01, 0x01, 0x01, 0x01, 0x03, 0x00, 0x00, 0x00,
0x01, 0x01, 0x03, 0x08, 0x10, 0x00, 0x00, 0x00,
0x01, 0x01, 0x05, 0x00, 0x03, 0x00, 0x00, 0x00,
0x01, 0x01, 0x06, 0x08, 0x0E, 0x00, 0x00, 0x00,
0x01, 0x01, 0xFC, 0x11}
func FlightMode(d Device) (err error) {
err = sendCommand(d, flight_mode_cmd[:])
return err
}
// SetFlightMode sends UBX-CFG-NAV5 command to set GPS into flight mode
func (d *Device) SetFlightMode() (err error) {
nav5Cmd.DynModel = DynModeAirborne1g
nav5Cmd.FixMode = FixModeAuto
nav5Cmd.Put42Bytes(d.buffer[:])
return d.SendCommand(d.buffer[:42])
func SetCfgGNSS(d Device) (err error) {
err = sendCommand(d, cfg_gnss_cmd[:])
return err
}
// SetPedestrianMode sends UBX-CFG-NAV5 command to set GPS into pedestrian mode
func (d *Device) SetPedestrianMode() (err error) {
nav5Cmd.DynModel = DynModePedestrian
nav5Cmd.FixMode = FixModeAuto
nav5Cmd.Put42Bytes(d.buffer[:])
return d.SendCommand(d.buffer[:42])
}
// SetAutomotiveMode sends UBX-CFG-NAV5 command to set GPS into automotive mode
func (d *Device) SetAutomotiveMode() (err error) {
nav5Cmd.DynModel = DynModeAutomotive
nav5Cmd.FixMode = FixModeAuto
nav5Cmd.Put42Bytes(d.buffer[:])
return d.SendCommand(d.buffer[:42])
}
// SetBikeMode sends UBX-CFG-NAV5 command to set GPS into bike mode
func (d *Device) SetBikeMode() (err error) {
nav5Cmd.DynModel = DynModeBike
nav5Cmd.FixMode = FixModeAuto
nav5Cmd.Put42Bytes(d.buffer[:])
return d.SendCommand(d.buffer[:42])
}
var (
// GGA (time, lat/lng, altitude)
messageRateGGACmd = CfgMsg1{
MsgClass: 0xF0,
MsgID: 0x00,
Rate: 1, // Every position fix
}
// GLL (time, lat/lng)
messageRateGLLCmd = CfgMsg1{
MsgClass: 0xF0,
MsgID: 0x01,
Rate: 1, // Every position fix
}
// GSA (satellite id list)
messageRateGSACmd = CfgMsg1{
MsgClass: 0xF0,
MsgID: 0x02,
Rate: 0, // Disabled
}
// GSV (satellite locations)
messageRateGSVCmd = CfgMsg1{
MsgClass: 0xF0,
MsgID: 0x03,
Rate: 0, // Every position fix
}
// RMC (time, lat/lng, speed, course)
messageRateRMCCmd = CfgMsg1{
MsgClass: 0xF0,
MsgID: 0x04,
Rate: 1, // Every position fix
}
// VTG (speed, course)
messageRateVTGCmd = CfgMsg1{
MsgClass: 0xF0,
MsgID: 0x05,
Rate: 0, // Disabled
}
// ZDA (time, timezone)
messageRateZDACmd = CfgMsg1{
MsgClass: 0xF0,
MsgID: 0x08,
Rate: 0, // Disabled
}
// TXT (text transmission)
messageRateTXTCmd = CfgMsg1{
MsgClass: 0xF0,
MsgID: 0x41,
Rate: 0, // Disabled
}
)
// SetMessageRatesMinimal configures the GPS to output a minimal set of NMEA sentences:
// GSV, GGA, GLL, and RMC only.
func SetMessageRatesMinimal(d *Device) (err error) {
commands := []CfgMsg1{
messageRateGSACmd,
messageRateGLLCmd,
messageRateVTGCmd,
messageRateZDACmd,
messageRateTXTCmd,
}
for i := range commands {
commands[i].Rate = 0 // Disable
}
return setCfg1s(d, commands)
}
// SetMessageRatesAllEnabled configures the GPS to output all NMEA sentences
func SetMessageRatesAllEnabled(d *Device) (err error) {
commands := []CfgMsg1{
messageRateGSACmd,
messageRateGGACmd,
messageRateGLLCmd,
messageRateGSVCmd,
messageRateRMCCmd,
messageRateVTGCmd,
messageRateZDACmd,
messageRateTXTCmd,
}
for i := range commands {
commands[i].Rate = 1 // Enable
}
return setCfg1s(d, commands)
}
func setCfg1s(d *Device, commands []CfgMsg1) (err error) {
var buf [9]byte
for _, cmd := range commands {
cmd.Put9Bytes(buf[:])
// TODO handle errors differently here?
// This implementation just saves the last error and continues.
// Due to the GPS modules sending updates asynchronously
// the response is interleaved along with regular ASCII
// NMEA messages.
err = d.SendCommand(buf[:])
time.Sleep(100 * time.Millisecond)
}
return
}
// gnssDisableCmd is a UBX-CFG-GNSS command to disable all GNSS but GPS
// Needed for MAX8's, not needed for MAX7
var gnssDisableCmd = CfgGnss{
MsgVer: 0x00,
NumTrkChHw: 0x20, // 32 channels
NumTrkChUse: 0x20,
ConfigBlocks: []CfgGnssConfigBlocksType{
{GnssId: 0, ResTrkCh: 8, MaxTrkCh: 16, Flags: CfgGnssEnable | 0x010000}, // GPS enabled
{GnssId: 1, ResTrkCh: 1, MaxTrkCh: 3, Flags: 0x010000}, // SBAS disabled
{GnssId: 3, ResTrkCh: 8, MaxTrkCh: 16, Flags: 0x010000}, // BeiDou disabled
{GnssId: 5, ResTrkCh: 0, MaxTrkCh: 3, Flags: 0x010000}, // QZSS disabled
{GnssId: 6, ResTrkCh: 8, MaxTrkCh: 14, Flags: 0x010000}, // GLONASS disabled
},
}
// SetGNSSDisable sends UBX-CFG-GNSS command to disable all GNSS but GPS
func (d *Device) SetGNSSDisable() (err error) {
err = gnssDisableCmd.Put(d.buffer[:])
if err != nil {
return err
}
return d.SendCommand(d.buffer[:])
}
// SendCommand sends a UBX command and waits for ACK/NAK response
func (d *Device) SendCommand(command []byte) error {
// Calculate and append checksum
checksummed := appendChecksum(command)
d.WriteBytes(checksummed)
func sendCommand(d Device, command []byte) (err error) {
d.WriteBytes(command)
start := time.Now()
for time.Since(start) < time.Second {
// Look for UBX sync sequence
if d.readNextByte() != ubxSyncChar1 {
continue
}
if d.readNextByte() != ubxSyncChar2 {
continue
}
// Read message class and ID
msgClass := d.readNextByte()
msgID := d.readNextByte()
// Check if it's an ACK class message
if msgClass != ubxClassACK {
continue
}
// Read length (2 bytes, little-endian) - ACK is always 2 bytes payload
lenLo := d.readNextByte()
lenHi := d.readNextByte()
length := uint16(lenLo) | uint16(lenHi)<<8
if length != 2 {
continue
}
// Read ACK payload: class and ID of acknowledged message
ackClass := d.readNextByte()
ackID := d.readNextByte()
// Verify ACK is for our command (command[2] = class, command[3] = ID)
if ackClass != command[2] || ackID != command[3] {
continue
}
if msgID == ubxACK_ACK {
return nil
}
if msgID == ubxACK_NAK {
return errGPSCommandRejected
for time.Now().Sub(start) < 1000 {
if d.readNextByte() == '\n' {
if d.readNextByte() == 0xB5 {
d.readNextByte()
if d.readNextByte() == 0x05 {
if d.readNextByte() == 0x01 {
return
}
}
}
}
}
return errNoACKToGPSCommand
}
// appendChecksum calculates UBX checksum and appends it to the message
func appendChecksum(msg []byte) []byte {
var ckA, ckB byte
// Checksum covers class, ID, length, and payload (skip sync chars)
for i := 2; i < len(msg); i++ {
ckA += msg[i]
ckB += ckA
}
return append(msg, ckA, ckB)
return errors.New("no ACK to GPS command")
}
-353
View File
@@ -1,353 +0,0 @@
package gps
import (
"testing"
)
func TestAppendChecksum(t *testing.T) {
testCases := []struct {
name string
input []byte
expected []byte
}{
{
name: "simple message",
input: []byte{0xB5, 0x62, 0x06, 0x24, 0x00, 0x00},
expected: []byte{0xB5, 0x62, 0x06, 0x24, 0x00, 0x00, 0x2A, 0x84},
},
{
name: "CFG-NAV5 header only",
input: []byte{0xB5, 0x62, 0x06, 0x24, 0x24, 0x00},
expected: []byte{0xB5, 0x62, 0x06, 0x24, 0x24, 0x00, 0x4E, 0xCC},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := appendChecksum(tc.input)
if len(result) != len(tc.expected) {
t.Errorf("expected length %d, got %d", len(tc.expected), len(result))
return
}
// Check checksum bytes (last two bytes)
ckA := result[len(result)-2]
ckB := result[len(result)-1]
expectedCkA := tc.expected[len(tc.expected)-2]
expectedCkB := tc.expected[len(tc.expected)-1]
if ckA != expectedCkA || ckB != expectedCkB {
t.Errorf("expected checksum 0x%02X 0x%02X, got 0x%02X 0x%02X",
expectedCkA, expectedCkB, ckA, ckB)
}
})
}
}
func TestAppendChecksumPreservesOriginal(t *testing.T) {
input := []byte{0xB5, 0x62, 0x06, 0x24, 0x00, 0x00}
original := make([]byte, len(input))
copy(original, input)
result := appendChecksum(input)
// Verify original bytes are preserved
for i := range input {
if result[i] != original[i] {
t.Errorf("byte %d changed: expected 0x%02X, got 0x%02X", i, original[i], result[i])
}
}
// Verify two bytes were appended
if len(result) != len(input)+2 {
t.Errorf("expected length %d, got %d", len(input)+2, len(result))
}
}
func TestNav5CmdConfig(t *testing.T) {
// Verify nav5Cmd has expected values
if nav5Cmd.DynModel != 6 {
t.Errorf("expected DynModel 6 (airborne <1g), got %d", nav5Cmd.DynModel)
}
if nav5Cmd.FixMode != 3 {
t.Errorf("expected FixMode 3 (auto 2D/3D), got %d", nav5Cmd.FixMode)
}
expectedMask := CfgNav5Dyn | CfgNav5MinEl | CfgNav5PosFixMode
if nav5Cmd.Mask != expectedMask {
t.Errorf("expected Mask 0x%04X, got 0x%04X", expectedMask, nav5Cmd.Mask)
}
if nav5Cmd.MinElev_deg != 5 {
t.Errorf("expected MinElev_deg 5, got %d", nav5Cmd.MinElev_deg)
}
}
func TestGNSSDisableCmdConfig(t *testing.T) {
// Verify GNSSDisableCmd has expected structure
if gnssDisableCmd.MsgVer != 0 {
t.Errorf("expected MsgVer 0, got %d", gnssDisableCmd.MsgVer)
}
if gnssDisableCmd.NumTrkChHw != 0x20 {
t.Errorf("expected NumTrkChHw 0x20, got 0x%02X", gnssDisableCmd.NumTrkChHw)
}
if len(gnssDisableCmd.ConfigBlocks) != 5 {
t.Errorf("expected 5 config blocks, got %d", len(gnssDisableCmd.ConfigBlocks))
return
}
// Verify GPS is enabled
gpsBlock := gnssDisableCmd.ConfigBlocks[0]
if gpsBlock.GnssId != 0 {
t.Errorf("expected first block GnssId 0 (GPS), got %d", gpsBlock.GnssId)
}
if gpsBlock.Flags&CfgGnssEnable == 0 {
t.Error("expected GPS to be enabled")
}
// Verify other GNSS are disabled
for i := 1; i < len(gnssDisableCmd.ConfigBlocks); i++ {
block := gnssDisableCmd.ConfigBlocks[i]
if block.Flags&CfgGnssEnable != 0 {
t.Errorf("expected block %d (GnssId %d) to be disabled", i, block.GnssId)
}
}
}
func TestNav5CmdWrite(t *testing.T) {
buf := make([]byte, 64)
nav5Cmd.Put42Bytes(buf)
// Verify sync chars
if buf[0] != 0xB5 || buf[1] != 0x62 {
t.Errorf("expected sync 0xB5 0x62, got 0x%02X 0x%02X", buf[0], buf[1])
}
// Verify class/id
if buf[2] != 0x06 || buf[3] != 0x24 {
t.Errorf("expected class/id 0x06 0x24, got 0x%02X 0x%02X", buf[2], buf[3])
}
// Verify DynModel at offset 8
if buf[8] != 6 {
t.Errorf("expected DynModel 6, got %d", buf[8])
}
}
func TestGNSSDisableCmdWrite(t *testing.T) {
buf := make([]byte, 64)
err := gnssDisableCmd.Put(buf)
if err != nil {
t.Errorf("unexpected error, likely buffer too short for data: %v", err)
}
// 6 header + 4 payload header + 5*8 blocks = 50 bytes
const expectedLen = 6 + 4 + 5*8
sz := gnssDisableCmd.Size()
if sz != expectedLen {
t.Errorf("expected %d bytes, got %d", expectedLen, sz)
}
// Verify sync chars
if buf[0] != 0xB5 || buf[1] != 0x62 {
t.Errorf("expected sync 0xB5 0x62, got 0x%02X 0x%02X", buf[0], buf[1])
}
// Verify class/id
if buf[2] != 0x06 || buf[3] != 0x3E {
t.Errorf("expected class/id 0x06 0x3E, got 0x%02X 0x%02X", buf[2], buf[3])
}
// Verify number of blocks
if buf[9] != 5 {
t.Errorf("expected 5 blocks, got %d", buf[9])
}
}
func TestChecksumCalculation(t *testing.T) {
// Test with known UBX message and expected checksum
// This is a minimal CFG-NAV5 poll message
msg := []byte{0xB5, 0x62, 0x06, 0x24, 0x00, 0x00}
result := appendChecksum(msg)
// Verify checksum by recalculating
var ckA, ckB byte
for i := 2; i < len(msg); i++ {
ckA += msg[i]
ckB += ckA
}
if result[6] != ckA || result[7] != ckB {
t.Errorf("checksum mismatch: expected 0x%02X 0x%02X, got 0x%02X 0x%02X",
ckA, ckB, result[6], result[7])
}
}
func TestMessageRateCmdConfigs(t *testing.T) {
testCases := []struct {
name string
cmd CfgMsg1
msgClass byte
msgID byte
rate byte
}{
{"GGA", messageRateGGACmd, 0xF0, 0x00, 1},
{"GLL", messageRateGLLCmd, 0xF0, 0x01, 1},
{"GSA", messageRateGSACmd, 0xF0, 0x02, 0},
{"GSV", messageRateGSVCmd, 0xF0, 0x03, 0},
{"RMC", messageRateRMCCmd, 0xF0, 0x04, 1},
{"VTG", messageRateVTGCmd, 0xF0, 0x05, 0},
{"ZDA", messageRateZDACmd, 0xF0, 0x08, 0},
{"TXT", messageRateTXTCmd, 0xF0, 0x41, 0},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if tc.cmd.MsgClass != tc.msgClass {
t.Errorf("expected MsgClass 0x%02X, got 0x%02X", tc.msgClass, tc.cmd.MsgClass)
}
if tc.cmd.MsgID != tc.msgID {
t.Errorf("expected MsgID 0x%02X, got 0x%02X", tc.msgID, tc.cmd.MsgID)
}
if tc.cmd.Rate != tc.rate {
t.Errorf("expected Rate %d, got %d", tc.rate, tc.cmd.Rate)
}
})
}
}
func TestCfgMsg1Write(t *testing.T) {
cmd := CfgMsg1{
MsgClass: 0xF0,
MsgID: 0x00,
Rate: 1,
}
buf := make([]byte, 16)
cmd.Put9Bytes(buf)
// Verify sync chars
if buf[0] != 0xB5 || buf[1] != 0x62 {
t.Errorf("expected sync 0xB5 0x62, got 0x%02X 0x%02X", buf[0], buf[1])
}
// Verify class/id (0x06 0x01 for CFG-MSG)
if buf[2] != 0x06 || buf[3] != 0x01 {
t.Errorf("expected class/id 0x06 0x01, got 0x%02X 0x%02X", buf[2], buf[3])
}
// Verify length (3 bytes payload)
if buf[4] != 3 || buf[5] != 0 {
t.Errorf("expected length 3, got %d", uint16(buf[4])|uint16(buf[5])<<8)
}
// Verify payload
if buf[6] != 0xF0 {
t.Errorf("expected MsgClass 0xF0, got 0x%02X", buf[6])
}
if buf[7] != 0x00 {
t.Errorf("expected MsgID 0x00, got 0x%02X", buf[7])
}
if buf[8] != 1 {
t.Errorf("expected Rate 1, got %d", buf[8])
}
}
func TestCfgMsg1ClassID(t *testing.T) {
cmd := CfgMsg1{}
if got := cmd.classID(); got != 0x0106 {
t.Errorf("expected 0x0106, got 0x%04x", got)
}
}
func TestMinimalMessageRatesConfig(t *testing.T) {
// Verify the minimal config has correct rates set
// GGA and RMC should be enabled (rate=1), others disabled (rate=0)
expectedRates := map[byte]byte{
0x00: 1, // GGA - enabled
0x01: 1, // GLL - enabled
0x02: 0, // GSA - disabled
0x03: 0, // GSV - disabled
0x04: 1, // RMC - enabled
0x05: 0, // VTG - disabled
0x08: 0, // ZDA - disabled
0x41: 0, // TXT - disabled
}
commands := []CfgMsg1{
messageRateGGACmd,
messageRateGLLCmd,
messageRateGSACmd,
messageRateGSVCmd,
messageRateRMCCmd,
messageRateVTGCmd,
messageRateZDACmd,
messageRateTXTCmd,
}
for _, cmd := range commands {
expectedRate, ok := expectedRates[cmd.MsgID]
if !ok {
t.Errorf("unexpected MsgID 0x%02X", cmd.MsgID)
continue
}
if cmd.Rate != expectedRate {
t.Errorf("MsgID 0x%02X: expected rate %d, got %d", cmd.MsgID, expectedRate, cmd.Rate)
}
}
}
func TestAllMessageRatesWriteCorrectBytes(t *testing.T) {
// Test that each message rate command writes the correct bytes
commands := []CfgMsg1{
messageRateGGACmd,
messageRateGLLCmd,
messageRateGSACmd,
messageRateGSVCmd,
messageRateRMCCmd,
messageRateVTGCmd,
messageRateZDACmd,
messageRateTXTCmd,
}
for _, cmd := range commands {
buf := make([]byte, 16)
cmd.Put9Bytes(buf)
// Verify MsgClass in payload
if buf[6] != 0xF0 {
t.Errorf("MsgID 0x%02X: expected MsgClass 0xF0, got 0x%02X", cmd.MsgID, buf[6])
}
// Verify MsgID in payload
if buf[7] != cmd.MsgID {
t.Errorf("expected MsgID 0x%02X in payload, got 0x%02X", cmd.MsgID, buf[7])
}
// Verify Rate in payload
if buf[8] != cmd.Rate {
t.Errorf("MsgID 0x%02X: expected Rate %d, got %d", cmd.MsgID, cmd.Rate, buf[8])
}
}
}
func TestSetMessageRatesAllEnabledModifiesRate(t *testing.T) {
// Verify that when we copy a command and set Rate=1, it works correctly
cmd := messageRateGSACmd // This one is disabled by default
if cmd.Rate != 0 {
t.Errorf("expected GSA default rate 0, got %d", cmd.Rate)
}
// Simulate what SetMessageRatesAllEnabled does
cmd.Rate = 1
buf := make([]byte, 16)
cmd.Put9Bytes(buf)
if buf[8] != 1 {
t.Errorf("expected Rate 1 in buffer, got %d", buf[8])
}
}
-220
View File
@@ -1,220 +0,0 @@
package gps
import "io"
// UBX message classes
const (
ubxClassACK = 0x05
)
// UBX ACK message IDs
const (
ubxACK_NAK = 0x00 // Message not acknowledged
ubxACK_ACK = 0x01 // Message acknowledged
)
// UBX sync characters
const (
ubxSyncChar1 = 0xB5
ubxSyncChar2 = 0x62
)
const (
DynModePortable = 0
DynModeStationary = 2
DynModePedestrian = 3
DynModeAutomotive = 4
DynModeSea = 5
DynModeAirborne1g = 6
DynModeAirborne2g = 7
DynModeAirborne4g = 8
DynModeWristWatch = 9
DynModeBike = 10
)
const (
FixMode2D = 1
FixMode3D = 2
FixModeAuto = 3
)
// from https://github.com/daedaleanai/ublox/blob/main/ubx/messages.go
// Message ubx-cfg-nav5
// CfgNav5 (Get/set) Navigation engine settings
// Class/Id 0x06 0x24 (36 bytes)
// See the Navigation Configuration Settings Description for a detailed description of how these settings affect receiver operation.
type CfgNav5 struct {
Mask CfgNav5Mask // Parameters bitmask. Only the masked parameters will be applied.
DynModel byte // Dynamic platform model: 0: portable 2: stationary 3: pedestrian 4: automotive 5: sea 6: airborne with <1g acceleration 7: airborne with <2g acceleration 8: airborne with <4g acceleration 9: wrist-worn watch (not supported in protocol versions less than 18) 10: bike (supported in protocol versions 19. 2)
FixMode byte // Position fixing mode: 1: 2D only 2: 3D only 3: auto 2D/3D
FixedAlt_me2 int32 // [1e-2 m] Fixed altitude (mean sea level) for 2D fix mode
FixedAltVar_m2e4 uint32 // [1e-4 m^2] Fixed altitude variance for 2D mode
MinElev_deg int8 // [deg] Minimum elevation for a GNSS satellite to be used in NAV
DrLimit_s byte // [s] Reserved
PDop uint16 // Position DOP mask to use
TDop uint16 // Time DOP mask to use
PAcc_m uint16 // [m] Position accuracy mask
TAcc_m uint16 // [m] Time accuracy mask
StaticHoldThresh_cm_s byte // [cm/s] Static hold threshold
DgnssTimeout_s byte // [s] DGNSS timeout
CnoThreshNumSVs byte // Number of satellites required to have C/N0 above cnoThresh for a fix to be attempted
CnoThresh_dbhz byte // [dBHz] C/N0 threshold for deciding whether to attempt a fix
Reserved1 [2]byte // Reserved
StaticHoldMaxDist_m uint16 // [m] Static hold distance threshold (before quitting static hold)
UtcStandard byte // UTC standard to be used: 0: Automatic; receiver selects based on GNSS configuration (see GNSS time bases) 3: UTC as operated by the U.S. Naval Observatory (USNO); derived from GPS time 5: UTC as combined from multiple European laboratories; derived from Galileo time 6: UTC as operated by the former Soviet Union (SU); derived from GLONASS time 7: UTC as operated by the National Time Service Center (NTSC), China; derived from BeiDou time (not supported in protocol versions less than 16).
Reserved2 [5]byte // Reserved
}
func (CfgNav5) classID() uint16 { return 0x2406 }
type CfgNav5Mask uint16
var _ io.WriterTo = CfgNav5{} // compile time guarantee of interface implementation.
const (
CfgNav5Dyn CfgNav5Mask = 0x1 // Apply dynamic model settings
CfgNav5MinEl CfgNav5Mask = 0x2 // Apply minimum elevation settings
CfgNav5PosFixMode CfgNav5Mask = 0x4 // Apply fix mode settings
CfgNav5DrLim CfgNav5Mask = 0x8 // Reserved
CfgNav5PosMask CfgNav5Mask = 0x10 // Apply position mask settings
CfgNav5TimeMask CfgNav5Mask = 0x20 // Apply time mask settings
CfgNav5StaticHoldMask CfgNav5Mask = 0x40 // Apply static hold settings
CfgNav5DgpsMask CfgNav5Mask = 0x80 // Apply DGPS settings
CfgNav5CnoThreshold CfgNav5Mask = 0x100 // Apply CNO threshold settings (cnoThresh, cnoThreshNumSVs)
CfgNav5Utc CfgNav5Mask = 0x400 // Apply UTC settings (not supported in protocol versions less than 16).
)
func (cfg CfgNav5) Append(dst []byte) []byte {
var buf [42]byte
cfg.Put42Bytes(buf[:])
dst = append(dst, buf[:]...)
return dst
}
func (cfg CfgNav5) WriteTo(w io.Writer) (int64, error) {
var buf [42]byte
cfg.Put42Bytes(buf[:])
n, err := w.Write(buf[:])
return int64(n), err
}
// Write CfgNav5 message to buffer
func (cfg CfgNav5) Put42Bytes(buf []byte) {
_ = buf[41]
copy(buf, []byte{0xb5, 0x62, byte(cfg.classID()), byte(cfg.classID() >> 8), 36, 0})
buf[6] = byte(cfg.Mask)
buf[7] = byte(cfg.Mask >> 8)
buf[8] = cfg.DynModel
buf[9] = cfg.FixMode
buf[10] = byte(cfg.FixedAlt_me2)
buf[11] = byte(cfg.FixedAlt_me2 >> 8)
buf[12] = byte(cfg.FixedAlt_me2 >> 16)
buf[13] = byte(cfg.FixedAlt_me2 >> 24)
buf[14] = byte(cfg.FixedAltVar_m2e4)
buf[15] = byte(cfg.FixedAltVar_m2e4 >> 8)
buf[16] = byte(cfg.FixedAltVar_m2e4 >> 16)
buf[17] = byte(cfg.FixedAltVar_m2e4 >> 24)
buf[18] = byte(cfg.MinElev_deg)
buf[19] = cfg.DrLimit_s
buf[20] = byte(cfg.PDop)
buf[21] = byte(cfg.PDop >> 8)
buf[22] = byte(cfg.TDop)
buf[23] = byte(cfg.TDop >> 8)
buf[24] = byte(cfg.PAcc_m)
buf[25] = byte(cfg.PAcc_m >> 8)
buf[26] = byte(cfg.TAcc_m)
buf[27] = byte(cfg.TAcc_m >> 8)
buf[28] = cfg.StaticHoldThresh_cm_s
buf[29] = cfg.DgnssTimeout_s
buf[30] = cfg.CnoThreshNumSVs
buf[31] = cfg.CnoThresh_dbhz
copy(buf[32:34], cfg.Reserved1[:])
buf[34] = byte(cfg.StaticHoldMaxDist_m)
buf[35] = byte(cfg.StaticHoldMaxDist_m >> 8)
buf[36] = cfg.UtcStandard
copy(buf[37:42], cfg.Reserved2[:])
}
// Message ubx-cfg-msg
// CfgMsg1 (Get/set) Set message rate
// Class/Id 0x06 0x01 (3 bytes)
// Set message rate configuration for the current port. See also section How to change between protocols.
type CfgMsg1 struct {
MsgClass byte // Message class
MsgID byte // Message identifier
Rate byte // Send rate on current port
}
func (CfgMsg1) classID() uint16 { return 0x0106 }
func (cfg CfgMsg1) Put9Bytes(buf []byte) {
copy(buf, []byte{0xb5, 0x62, byte(cfg.classID()), byte(cfg.classID() >> 8), 3, 0})
buf[6] = cfg.MsgClass
buf[7] = cfg.MsgID
buf[8] = cfg.Rate
}
// Message ubx-cfg-gnss
// CfgGnss (Get/set) GNSS system configuration
// Class/Id 0x06 0x3e (4 + N*8 bytes)
// Gets or sets the GNSS system channel sharing configuration. If the receiver is sent a valid new configuration, it will respond with a UBX-ACK- ACK message and immediately change to the new configuration. Otherwise the receiver will reject the request, by issuing a UBX-ACK-NAK and continuing operation with the previous configuration. Configuration requirements: It is necessary for at least one major GNSS to be enabled, after applying the new configuration to the current one. It is also required that at least 4 tracking channels are available to each enabled major GNSS, i.e. maxTrkCh must have a minimum value of 4 for each enabled major GNSS. The number of tracking channels in use must not exceed the number of tracking channels available in hardware, and the sum of all reserved tracking channels needs to be less than or equal to the number of tracking channels in use. Notes: To avoid cross-correlation issues, it is recommended that GPS and QZSS are always both enabled or both disabled. Polling this message returns the configuration of all supported GNSS, whether enabled or not; it may also include GNSS unsupported by the particular product, but in such cases the enable flag will always be unset. See section GNSS Configuration for a discussion of the use of this message. See section Satellite Numbering for a description of the GNSS IDs available. Configuration specific to the GNSS system can be done via other messages (e. g. UBX-CFG-SBAS).
type CfgGnss struct {
MsgVer byte // Message version (0x00 for this version)
NumTrkChHw byte // Number of tracking channels available in hardware (read only)
NumTrkChUse byte // (Read only in protocol versions greater than 23) Number of tracking channels to use. Must be > 0, <= numTrkChHw. If 0xFF, then number of tracking channels to use will be set to numTrkChHw.
NumConfigBlocks byte `len:"ConfigBlocks"` // Number of configuration blocks following
ConfigBlocks []CfgGnssConfigBlocksType // len: NumConfigBlocks
}
func (CfgGnss) classID() uint16 { return 0x3e06 }
type CfgGnssConfigBlocksType struct {
GnssId byte // System identifier (see Satellite Numbering )
ResTrkCh byte // (Read only in protocol versions greater than 23) Number of reserved (minimum) tracking channels for this system.
MaxTrkCh byte // (Read only in protocol versions greater than 23) Maximum number of tracking channels used for this system. Must be > 0, >= resTrkChn, <= numTrkChUse and <= maximum number of tracking channels supported for this system.
Reserved1 byte // Reserved
Flags CfgGnssFlags // Bitfield of flags. At least one signal must be configured in every enabled system.
}
type CfgGnssFlags uint32
const (
CfgGnssEnable CfgGnssFlags = 0x1 // Enable this system
CfgGnssSigCfgMask CfgGnssFlags = 0xff0000 // Signal configuration mask When gnssId is 0 (GPS) 0x01 = GPS L1C/A 0x10 = GPS L2C 0x20 = GPS L5 When gnssId is 1 (SBAS) 0x01 = SBAS L1C/A When gnssId is 2 (Galileo) 0x01 = Galileo E1 (not supported in protocol versions less than 18) 0x10 = Galileo E5a 0x20 = Galileo E5b When gnssId is 3 (BeiDou) 0x01 = BeiDou B1I 0x10 = BeiDou B2I 0x80 = BeiDou B2A When gnssId is 4 (IMES) 0x01 = IMES L1 When gnssId is 5 (QZSS) 0x01 = QZSS L1C/A 0x04 = QZSS L1S 0x10 = QZSS L2C 0x20 = QZSS L5 When gnssId is 6 (GLONASS) 0x01 = GLONASS L1 0x10 = GLONASS L2
)
// Write CfgGnss message to buffer
func (cfg CfgGnss) Put(buf []byte) error {
sz := cfg.Size()
if sz > len(buf) {
return io.ErrShortBuffer
}
copy(buf, []byte{0xb5, 0x62, byte(cfg.classID()), byte(cfg.classID() >> 8), 4 + byte(len(cfg.ConfigBlocks))*8, 0})
buf[6] = cfg.MsgVer
buf[7] = cfg.NumTrkChHw
buf[8] = cfg.NumTrkChUse
buf[9] = byte(len(cfg.ConfigBlocks))
offset := 10
for _, block := range cfg.ConfigBlocks {
buf[offset] = block.GnssId
buf[offset+1] = block.ResTrkCh
buf[offset+2] = block.MaxTrkCh
buf[offset+3] = block.Reserved1
buf[offset+4] = byte(block.Flags)
buf[offset+5] = byte(block.Flags >> 8)
buf[offset+6] = byte(block.Flags >> 16)
buf[offset+7] = byte(block.Flags >> 24)
offset += 8
}
return nil
}
// Size returns length of CfgGnss in bytes when sent over the wire.
func (cfg CfgGnss) Size() int {
return 10 + 8*len(cfg.ConfigBlocks)
}
-189
View File
@@ -1,189 +0,0 @@
package gps
import (
"testing"
)
func TestCfgNav5ClassID(t *testing.T) {
cfg := CfgNav5{}
if got := cfg.classID(); got != 0x2406 {
t.Errorf("expected 0x2406, got 0x%04x", got)
}
}
func TestCfgNav5Write(t *testing.T) {
cfg := CfgNav5{
Mask: CfgNav5Dyn | CfgNav5MinEl,
DynModel: 4,
FixMode: 3,
FixedAlt_me2: 10000,
FixedAltVar_m2e4: 10000,
MinElev_deg: 5,
DrLimit_s: 0,
PDop: 250,
TDop: 250,
PAcc_m: 100,
TAcc_m: 300,
StaticHoldThresh_cm_s: 50,
DgnssTimeout_s: 60,
CnoThreshNumSVs: 3,
CnoThresh_dbhz: 35,
Reserved1: [2]byte{0, 0},
StaticHoldMaxDist_m: 200,
UtcStandard: 0,
Reserved2: [5]byte{0, 0, 0, 0, 0},
}
buf := make([]byte, 64)
cfg.Put42Bytes(buf)
// Check sync chars
if buf[0] != 0xb5 || buf[1] != 0x62 {
t.Errorf("expected sync chars 0xb5 0x62, got 0x%02x 0x%02x", buf[0], buf[1])
}
// Check class/id (little-endian)
if buf[2] != 0x06 || buf[3] != 0x24 {
t.Errorf("expected class/id 0x06 0x24, got 0x%02x 0x%02x", buf[2], buf[3])
}
// Check length
if buf[4] != 36 || buf[5] != 0 {
t.Errorf("expected length 36, got %d", uint16(buf[4])|uint16(buf[5])<<8)
}
// Check Mask (little-endian)
mask := uint16(buf[6]) | uint16(buf[7])<<8
if mask != uint16(CfgNav5Dyn|CfgNav5MinEl) {
t.Errorf("expected mask 0x03, got 0x%04x", mask)
}
// Check DynModel
if buf[8] != 4 {
t.Errorf("expected DynModel 4, got %d", buf[8])
}
// Check FixMode
if buf[9] != 3 {
t.Errorf("expected FixMode 3, got %d", buf[9])
}
// Check FixedAlt_me2 (little-endian int32)
fixedAlt := int32(buf[10]) | int32(buf[11])<<8 | int32(buf[12])<<16 | int32(buf[13])<<24
if fixedAlt != 10000 {
t.Errorf("expected FixedAlt_me2 10000, got %d", fixedAlt)
}
}
func TestCfgGnssClassID(t *testing.T) {
cfg := CfgGnss{}
if got := cfg.classID(); got != 0x3e06 {
t.Errorf("expected 0x3e06, got 0x%04x", got)
}
}
func TestCfgGnssWrite(t *testing.T) {
testCases := []struct {
name string
cfg CfgGnss
expectedLen int
expectedBlocks byte
}{
{
name: "no config blocks",
cfg: CfgGnss{
MsgVer: 0,
NumTrkChHw: 32,
NumTrkChUse: 32,
ConfigBlocks: nil,
},
expectedLen: 10,
expectedBlocks: 0,
},
{
name: "one config block",
cfg: CfgGnss{
MsgVer: 0,
NumTrkChHw: 32,
NumTrkChUse: 32,
ConfigBlocks: []CfgGnssConfigBlocksType{
{GnssId: 0, ResTrkCh: 8, MaxTrkCh: 16, Flags: CfgGnssEnable | 0x010000},
},
},
expectedLen: 18,
expectedBlocks: 1,
},
{
name: "two config blocks",
cfg: CfgGnss{
MsgVer: 0,
NumTrkChHw: 32,
NumTrkChUse: 32,
ConfigBlocks: []CfgGnssConfigBlocksType{
{GnssId: 0, ResTrkCh: 8, MaxTrkCh: 16, Flags: CfgGnssEnable | 0x010000},
{GnssId: 6, ResTrkCh: 8, MaxTrkCh: 14, Flags: CfgGnssEnable | 0x010000},
},
},
expectedLen: 26,
expectedBlocks: 2,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
buf := make([]byte, 64)
err := tc.cfg.Put(buf)
if err != nil {
t.Errorf("unexpected error, data too long?: %v", err)
}
// Check sync chars
if buf[0] != 0xb5 || buf[1] != 0x62 {
t.Errorf("expected sync chars 0xb5 0x62, got 0x%02x 0x%02x", buf[0], buf[1])
}
// Check class/id (little-endian)
if buf[2] != 0x06 || buf[3] != 0x3e {
t.Errorf("expected class/id 0x06 0x3e, got 0x%02x 0x%02x", buf[2], buf[3])
}
// Check number of config blocks
if buf[9] != tc.expectedBlocks {
t.Errorf("expected %d config blocks, got %d", tc.expectedBlocks, buf[9])
}
})
}
}
func TestCfgGnssWriteBlockContent(t *testing.T) {
cfg := CfgGnss{
MsgVer: 0,
NumTrkChHw: 32,
NumTrkChUse: 32,
ConfigBlocks: []CfgGnssConfigBlocksType{
{GnssId: 0, ResTrkCh: 8, MaxTrkCh: 16, Reserved1: 0, Flags: CfgGnssEnable | 0x010000},
},
}
buf := make([]byte, 64)
err := cfg.Put(buf)
if err != nil {
t.Fatal(err)
}
// Check first block at offset 10
if buf[10] != 0 {
t.Errorf("expected GnssId 0, got %d", buf[10])
}
if buf[11] != 8 {
t.Errorf("expected ResTrkCh 8, got %d", buf[11])
}
if buf[12] != 16 {
t.Errorf("expected MaxTrkCh 16, got %d", buf[12])
}
// Check flags (little-endian uint32)
flags := uint32(buf[14]) | uint32(buf[15])<<8 | uint32(buf[16])<<16 | uint32(buf[17])<<24
expectedFlags := uint32(CfgGnssEnable | 0x010000)
if flags != expectedFlags {
t.Errorf("expected flags 0x%08x, got 0x%08x", expectedFlags, flags)
}
}
+19 -10
View File
@@ -5,30 +5,39 @@
package hcsr04
import (
"machine"
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/legacy"
)
const TIMEOUT = 23324 // max sensing distance (4m)
// Device holds the pins
type Device struct {
trigger machine.Pin
echo machine.Pin
trigger drivers.PinOutputFunc
echo drivers.PinInputFunc
configurePins func()
}
// New returns a new ultrasonic driver given 2 pins
func New(trigger, echo machine.Pin) Device {
func New(trigger drivers.PinOutput, echo drivers.PinInput) Device {
return Device{
trigger: trigger,
echo: echo,
trigger: trigger.Set,
echo: echo.Get,
configurePins: func() {
legacy.ConfigurePinOut(trigger)
legacy.ConfigurePinInput(echo)
},
}
}
// Configure configures the pins of the Device
func (d *Device) Configure() {
d.trigger.Configure(machine.PinConfig{Mode: machine.PinOutput})
d.echo.Configure(machine.PinConfig{Mode: machine.PinInput})
if d.configurePins == nil {
panic(legacy.ErrConfigBeforeInstantiated)
}
d.configurePins()
}
// ReadDistance returns the distance of the object in mm
@@ -52,7 +61,7 @@ func (d *Device) ReadPulse() int32 {
d.trigger.Low()
i := uint8(0)
for {
if d.echo.Get() {
if d.echo() {
t = time.Now()
break
}
@@ -66,7 +75,7 @@ func (d *Device) ReadPulse() int32 {
}
i = 0
for {
if !d.echo.Get() {
if !d.echo() {
return int32(time.Since(t).Microseconds())
}
i++
-191
View File
@@ -1,191 +0,0 @@
package honeyhsc
import (
"errors"
"math"
"tinygo.org/x/drivers"
)
var (
errSensorMissing = errors.New("hsc: not connected")
errDiagnostic = errors.New("hsc: diagnostic error")
)
const (
measuremask = drivers.Pressure | drivers.Temperature
statusMask = 0b1100_0000
statusOffset = 6
)
// DevI2C is the TruStability® High Accuracy Silicon Ceramic (HSC) Series is a piezoresistive silicon pressure sensor offering a ratiometric
// analog or digital output for reading pressure over the specified full scale pressure span and temperature range.
type DevI2C struct {
bus drivers.I2C
dev
addr uint8
buf [6]byte
}
// NewDevI2C creates and returns a new DevI2C that communicates with an HSC device over the provided I2C bus.
// Parameters:
// - bus: the I2C bus to use.
// - addr: the 7-bit I2C address of the sensor.
// - outMin, outMax: raw output code range (counts) corresponding to the pressure span. Depends on sensor model.
// - pMin, pMax: pressure range endpoints in millipascals (mPa). Depends on sensor model.
//
// The returned DevI2C will use these calibration parameters to convert raw bridge counts to pressure.
func NewDevI2C(bus drivers.I2C, addr, outMin, outMax uint16, pMin, pMax int32) *DevI2C {
h := &DevI2C{
bus: bus,
addr: uint8(addr),
dev: dev{
cmin: outMin,
cmax: outMax,
pmin: pMin,
pmax: pMax,
},
}
return h
}
// ReadTemperature reads and returns the temperature in milliKelvin (mC) from the I2C-attached HSC device.
// It performs an Update internally to get the latest temperature value.
func (h *DevI2C) ReadTemperature() (int32, error) {
err := h.Update(drivers.Temperature)
if err != nil {
return 0, err
}
return h.Temperature(), nil
}
// Update reads both temperature and pressure data from the I2C-attached HSC device when
// the requested measurement mask includes pressure or temperature.
// If neither pressure nor temperature is requested, Update is a no-op.
func (d *DevI2C) Update(which drivers.Measurement) error {
// Update performs an I2C transaction to read 4 bytes, parses the status bits, 14-bit bridge data and
// temperature bits, and forwards them to the internal update routine. Any I2C transport error is returned,
// as well as errors produced by the internal update (e.g. errSensorMissing, errDiagnostic).
if which&measuremask == 0 {
return nil
}
rbuf := d.buf[:4]
wbuf := d.buf[4:6]
const reg = 0
value := (d.addr << 1) | 1
wbuf[0] = reg
wbuf[1] = value
err := d.bus.Tx(uint16(d.addr), wbuf, rbuf)
if err != nil {
return err
}
status := (rbuf[0] & statusMask) >> statusOffset
bridgeData := (uint16(rbuf[0]&^statusMask) << 8) | uint16(rbuf[1])
tempData := uint16(rbuf[2])<<8 | uint16(rbuf[3]&0xe0)>>5
return d.dev.update(status, bridgeData, tempData)
}
type pinout func(level bool)
// DevI2C is the TruStability® High Accuracy Silicon Ceramic (HSC) Series is a piezoresistive silicon pressure sensor offering a ratiometric
// analog or digital output for reading pressure over the specified full scale pressure span and temperature range.
type DevSPI struct {
spi drivers.SPI
cs pinout
dev
buf [4]byte
}
// NewDevSPI creates and returns a new DevSPI that communicates with an HSC device over SPI.
// Parameters:
// - conn: the SPI connection to use.
// - cs: a chip-select function that drives the device select line low/high.
// - outMin, outMax: raw output code range (counts) corresponding to the pressure span. Depends on sensor model.
// - pMin, pMax: pressure range endpoints in millipascals (mPa). Depends on sensor model.
//
// The function returns the constructed DevSPI and an error value (currently always nil).
func NewDevSPI(conn drivers.SPI, cs pinout, outMin, outMax uint16, pMin, pMax int32) (*DevSPI, error) {
h := &DevSPI{
spi: conn,
cs: cs,
dev: dev{
cmin: outMin,
cmax: outMax,
pmin: pMin,
pmax: pMax,
},
}
return h, nil
}
// ReadTemperature reads and returns the temperature in milliKelvin (mC) from the SPI-attached HSC device.
// It performs an Update internally to get the latest temperature value.
func (h *DevSPI) ReadTemperature() (int32, error) {
err := h.Update(drivers.Temperature)
if err != nil {
return 0, err
}
return h.Temperature(), nil
}
// Update reads pressure and temperature data from the SPI-attached HSC device when the requested measurement mask includes
// pressure or temperature. If neither pressure nor temperature is requested, Update is a no-op.
func (h *DevSPI) Update(which drivers.Measurement) error {
// It toggles the provided chip-select, performs an SPI transfer to read 4 bytes, parses the status bits,
// 14-bit bridge data and temperature bits, and forwards them to the internal update routine. Any SPI
// transport error is returned, as well as errors produced by the internal update (e.g. errSensorMissing, errDiagnostic).
if which&measuremask == 0 {
return nil
}
buf := &h.buf
h.cs(false)
err := h.spi.Tx(nil, buf[:4])
h.cs(true)
if err != nil {
return err
}
// First two bits are status bits.
status := (buf[0] & statusMask) >> statusOffset
bridgeData := (uint16(buf[0]&^statusMask) << 8) | uint16(buf[1])
tempData := uint16(buf[2])<<8 | uint16(buf[3]&0xe0)>>5
return h.dev.update(status, bridgeData, tempData)
}
type dev struct {
pressure int32
temp int32
cmin, cmax uint16
pmin, pmax int32
}
// Pressure returns the most recently computed pressure value in millipascals (mPa).
// The value is taken from the last successful Update.
func (d *dev) Pressure() int32 {
return d.pressure
}
// Temperature returns the most recently read temperature value in milliKelvin (mC).
// The value is taken from the last successful Update.
func (d *dev) Temperature() int32 {
return d.temp + 273_150
}
// update interprets raw sensor fields (status, bridgeData, tempData) and updates the dev's stored
// pressure and temperature. It returns errSensorMissing when the temperature raw value indicates no sensor
// (tempData == math.MaxUint16), errDiagnostic when the status indicates a device diagnostic condition
// (status == 3), or nil on success. Pressure is computed with integer arithmetic using the configured
// cmin/cmax -> pmin/pmax linear mapping in order to avoid overflows.
func (d *dev) update(status uint8, bridgeData, tempData uint16) error {
if tempData == math.MaxUint16 {
return errSensorMissing
} else if status == 3 {
return errDiagnostic
}
// Take care not to overflow here.
p := (int32(bridgeData)-int32(d.cmin))*(d.pmax-d.pmin)/int32(d.cmax-d.cmin) + d.pmin
d.temp = int32(tempData)
d.pressure = p
return nil
}
-15
View File
@@ -1,15 +0,0 @@
//go:build !tinygo
package legacy
import "tinygo.org/x/drivers/internal/pin"
// This file compiles for non-tinygo builds
// for use with "big" or "upstream" Go where
// there is no machine package.
func configurePinOut(p pin.Output) {}
func configurePinInput(p pin.Input) {}
func configurePinInputPulldown(p pin.Input) {}
func configurePinInputPullup(p pin.Input) {}
func pinIsNoPin(a any) bool { return false }
@@ -3,23 +3,15 @@ package legacy
import (
"errors"
"tinygo.org/x/drivers/internal/pin"
"tinygo.org/x/drivers"
)
// The pingconfig group of files serve to abstract away
// pin configuration calls on the machine.Pin type.
// It was observed this way of developing drivers was
// non-portable and unusable on "big" Go projects so
// future projects should NOT configure pins in driver code.
// Users must configure pins before passing them as arguments
// to drivers.
// ConfigurePinOut is a legacy function used to configure pins as outputs.
//
// Deprecated: Do not configure pins in drivers.
// This is a legacy feature and should only be used by drivers that
// previously configured pins in initialization to avoid breaking users.
func ConfigurePinOut(po pin.Output) {
func ConfigurePinOut(po drivers.PinOutput) {
configurePinOut(po)
}
@@ -28,7 +20,7 @@ func ConfigurePinOut(po pin.Output) {
// Deprecated: Do not configure pins in drivers.
// This is a legacy feature and should only be used by drivers that
// previously configured pins in initialization to avoid breaking users.
func ConfigurePinInputPulldown(pi pin.Input) {
func ConfigurePinInputPulldown(pi drivers.PinInput) {
configurePinInputPulldown(pi)
}
@@ -37,7 +29,7 @@ func ConfigurePinInputPulldown(pi pin.Input) {
// Deprecated: Do not configure pins in drivers.
// This is a legacy feature and should only be used by drivers that
// previously configured pins in initialization to avoid breaking users.
func ConfigurePinInput(pi pin.Input) {
func ConfigurePinInput(pi drivers.PinInput) {
configurePinInput(pi)
}
@@ -46,7 +38,7 @@ func ConfigurePinInput(pi pin.Input) {
// Deprecated: Do not configure pins in drivers.
// This is a legacy feature and should only be used by drivers that
// previously configured pins in initialization to avoid breaking users.
func ConfigurePinInputPullup(pi pin.Input) {
func ConfigurePinInputPullup(pi drivers.PinInput) {
configurePinInputPullup(pi)
}
+13
View File
@@ -0,0 +1,13 @@
//go:build !baremetal
package legacy
import (
"tinygo.org/x/drivers"
)
func configurePinOut(p drivers.PinOutput) {}
func configurePinInput(p drivers.PinInput) {}
func configurePinInputPulldown(p drivers.PinInput) {}
func configurePinInputPullup(p drivers.PinInput) {}
func pinIsNoPin(a any) bool { return false }
@@ -5,22 +5,22 @@ package legacy
import (
"machine"
"tinygo.org/x/drivers/internal/pin"
"tinygo.org/x/drivers"
)
func configurePinOut(po pin.Output) {
func configurePinOut(po drivers.PinOutput) {
configurePin(po, machine.PinOutput)
}
func configurePinInputPulldown(pi pin.Input) {
func configurePinInputPulldown(pi drivers.PinInput) {
configurePin(pi, pulldown) // some chips do not have pull down, in which case pulldown==machine.PinInput.
}
func configurePinInput(pi pin.Input) {
func configurePinInput(pi drivers.PinInput) {
configurePin(pi, machine.PinInput)
}
func configurePinInputPullup(pi pin.Input) {
func configurePinInputPullup(pi drivers.PinInput) {
configurePin(pi, pullup) // some chips do not have pull up, in which case pullup==machine.PinInput.
}
-72
View File
@@ -1,72 +0,0 @@
// package pin implements a TinyGo Pin HAL.
// It serves to eliminate machine.Pin from driver constructors
// so that drivers can be used in "big" Go projects where
// there is no machine package.
// This file contains both function and interface-style Pin HAL definitions.
package pin
// OutputFunc is hardware abstraction for a pin which outputs a
// digital signal (high or low level).
//
// // Code conversion demo: from machine.Pin to pin.OutputFunc
// led := machine.LED
// led.Configure(machine.PinConfig{Mode: machine.Output})
// var pin pin.OutputFunc = led.Set // Going from a machine.Pin to a pin.OutputFunc
//
// This is an alternative to [Output] which is an interface type.
type OutputFunc func(level bool)
// High sets the underlying pin's level to high. This is equivalent to calling PinOutput(true).
func (setPin OutputFunc) High() {
setPin(true)
}
// Low sets the underlying pin's level to low. This is equivalent to calling PinOutput(false).
func (setPin OutputFunc) Low() {
setPin(false)
}
// InputFunc is hardware abstraction for a pin which receives a
// digital signal and reads it (high or low level).
//
// // Code conversion demo: from machine.Pin to pin.InputFunc
// input := machine.LED
// input.Configure(machine.PinConfig{Mode: machine.PinInputPulldown}) // or use machine.PinInputPullup or machine.Input
// var pin pin.InputFunc = input.Get // Going from a machine.Pin to a pin.InputFunc
//
// This is an alternative to [Input] which is an interface type.
type InputFunc func() (level bool)
// // Below is an example on how to define a input/output pin HAL for a
// // pin that must switch between input and output mode:
//
// var pinIsOutput bool
// var po PinOutputFunc = func(b bool) {
// if !pinIsOutput {
// pin.Configure(outputMode)
// pinIsOutput = true
// }
// pin.Set(b)
// }
//
// var pi PinInputFunc = func() bool {
// if pinIsOutput {
// pin.Configure(inputMode)
// pinIsOutput = false
// }
// return pin.Get()
// }
// Output interface represents a pin hardware abstraction layer for a pin that can output a digital signal.
//
// This is an alternative to [OutputFunc] abstraction which is a function type.
type Output interface {
Set(level bool)
}
// Input interface represents a pin hardware abstraction layer for a pin that can read a digital signal.
//
// This is an alternative to [InputFunc] abstraction which is a function type.
type Input interface {
Get() (level bool)
}
@@ -8,10 +8,6 @@ import (
)
// Device8 implements common logic to most 8-bit peripherals with an I2C or SPI bus.
// All methods expect the target to support conventional register read and write operations
// where the first byte sent is the register address being accessed.
//
// All methods use an internal buffer and perform no dynamic memory allocation.
type Device8 struct {
buf [10]byte
}
@@ -23,53 +19,41 @@ func (d *Device8) clear() {
// I2C methods.
// Read8I2C reads a single byte from register addr of the device at i2cAddr using the provided I2C bus.
func (d *Device8) Read8I2C(bus drivers.I2C, i2cAddr uint16, addr uint8) (byte, error) {
d.buf[0] = addr
err := bus.Tx(i2cAddr, d.buf[0:1], d.buf[1:2])
return d.buf[1], err
}
// Read16I2C reads a 16-bit value from register addr of the device at i2cAddr using the provided I2C bus.
// The byte order is specified by order.
func (d *Device8) Read16I2C(bus drivers.I2C, i2cAddr uint16, addr uint8, order binary.ByteOrder) (uint16, error) {
d.buf[0] = addr
err := bus.Tx(i2cAddr, d.buf[0:1], d.buf[1:3])
return order.Uint16(d.buf[1:3]), err
}
// Read32I2C reads a 32-bit value from register addr of the device at i2cAddr using the provided I2C bus.
// The byte order is specified by order.
func (d *Device8) Read32I2C(bus drivers.I2C, i2cAddr uint16, addr uint8, order binary.ByteOrder) (uint32, error) {
d.buf[0] = addr
err := bus.Tx(i2cAddr, d.buf[0:1], d.buf[1:5])
return order.Uint32(d.buf[1:5]), err
}
// ReadDataI2C reads dataLength bytes from register addr of the device at i2cAddr using the provided I2C bus.
// The data is stored in dataDestination.
func (d *Device8) ReadDataI2C(bus drivers.I2C, i2cAddr uint16, addr uint8, dataDestination []byte) error {
d.buf[0] = addr
return bus.Tx(i2cAddr, d.buf[:1], dataDestination)
}
// Write8I2C writes a single byte value to register addr of the device at i2cAddr using the provided I2C bus.
func (d *Device8) Write8I2C(bus drivers.I2C, i2cAddr uint16, addr, value uint8) error {
d.buf[0] = addr
d.buf[1] = value
return bus.Tx(i2cAddr, d.buf[:2], nil)
}
// Write16I2C writes a 16-bit value to register addr of the device at i2cAddr using the provided I2C bus.
// The byte order is specified by order.
func (d *Device8) Write16I2C(bus drivers.I2C, i2cAddr uint16, addr uint8, value uint16, order binary.ByteOrder) error {
d.buf[0] = addr
order.PutUint16(d.buf[1:3], value)
return bus.Tx(i2cAddr, d.buf[0:3], nil)
}
// Write32I2C writes a 32-bit value to register addr of the device at i2cAddr using the provided I2C bus.
// The byte order is specified by order.
func (d *Device8) Write32I2C(bus drivers.I2C, i2cAddr uint16, addr uint8, value uint32, order binary.ByteOrder) error {
d.buf[0] = addr
order.PutUint32(d.buf[1:5], value)
@@ -78,7 +62,6 @@ func (d *Device8) Write32I2C(bus drivers.I2C, i2cAddr uint16, addr uint8, value
// SPI methods.
// Read8SPI reads a single byte from register addr using the provided SPI bus.
func (d *Device8) Read8SPI(bus drivers.SPI, addr uint8) (byte, error) {
d.clear()
d.buf[0] = addr
@@ -86,7 +69,6 @@ func (d *Device8) Read8SPI(bus drivers.SPI, addr uint8) (byte, error) {
return d.buf[1], err
}
// Read16SPI reads a 16-bit value from register addr using the provided SPI bus. The byte order is specified by order.
func (d *Device8) Read16SPI(bus drivers.SPI, addr uint8, order binary.ByteOrder) (uint16, error) {
d.clear()
d.buf[0] = addr
@@ -94,7 +76,6 @@ func (d *Device8) Read16SPI(bus drivers.SPI, addr uint8, order binary.ByteOrder)
return order.Uint16(d.buf[4:6]), err
}
// Read32SPI reads a 32-bit value from register addr using the provided SPI bus. The byte order is specified by order.
func (d *Device8) Read32SPI(bus drivers.SPI, addr uint8, order binary.ByteOrder) (uint32, error) {
d.clear()
d.buf[0] = addr
@@ -118,7 +99,6 @@ func (d *Device8) ReadDataSPI(bus drivers.SPI, addr uint8, dataLength int, auxil
return rbuf[1:], err
}
// Write8SPI writes a single byte value to register addr using the provided SPI bus.
func (d *Device8) Write8SPI(bus drivers.SPI, addr, value uint8) error {
d.clear()
d.buf[0] = addr
@@ -126,7 +106,6 @@ func (d *Device8) Write8SPI(bus drivers.SPI, addr, value uint8) error {
return bus.Tx(d.buf[:2], nil)
}
// Write16SPI writes a 16-bit value to register addr using the provided SPI bus. The byte order is specified by order.
func (d *Device8) Write16SPI(bus drivers.SPI, addr uint8, value uint16, order binary.ByteOrder) error {
d.clear()
d.buf[0] = addr
@@ -134,7 +113,6 @@ func (d *Device8) Write16SPI(bus drivers.SPI, addr uint8, value uint16, order bi
return bus.Tx(d.buf[:3], nil)
}
// Write32SPI writes a 32-bit value to register addr using the provided SPI bus. The byte order is specified by order.
func (d *Device8) Write32SPI(bus drivers.SPI, addr uint8, value uint32, order binary.ByteOrder) error {
d.clear()
d.buf[0] = addr
-123
View File
@@ -1,123 +0,0 @@
package regmap
import (
"encoding/binary"
"tinygo.org/x/drivers"
)
// Device8SPI implements common logic to most 8-bit peripherals with an SPI bus.
// All methods expect the target to support conventional register read and write operations
// where the first byte sent is the register address being accessed.
//
// All methods use an internal buffer and perform no dynamic memory allocation.
type Device8SPI struct {
bus drivers.SPI
order binary.ByteOrder
d Device8
}
// SetBus sets the SPI bus and byte order for the Device8SPI.
//
// As a hint, most SPI devices use big-endian (MSB) byte order.
// - Big endian: A value of 0x1234 is transmitted as 0x12 followed by 0x34.
// - Little endian: A value of 0x1234 is transmitted as 0x34 followed by 0x12.
func (d *Device8SPI) SetBus(bus drivers.SPI, order binary.ByteOrder) {
d.bus = bus
d.order = order
}
// Read8 reads a single byte from register addr.
func (d *Device8SPI) Read8(addr uint8) (byte, error) {
return d.d.Read8SPI(d.bus, addr)
}
// Read16 reads a 16-bit value from register addr.
func (d *Device8SPI) Read16(addr uint8) (uint16, error) {
return d.d.Read16SPI(d.bus, addr, d.order)
}
// Read32 reads a 32-bit value from register addr.
func (d *Device8SPI) Read32(addr uint8) (uint32, error) {
return d.d.Read32SPI(d.bus, addr, d.order)
}
// ReadData reads dataLength bytes from register addr. Due to the internal functioning of
// SPI, an auxiliary buffer must be provided to perform the operation and avoid memory allocation.
// The returned slice is a subslice of auxBuffer containing the read data.
func (d *Device8SPI) ReadData(addr uint8, datalength int, auxBuffer []byte) ([]byte, error) {
return d.d.ReadDataSPI(d.bus, addr, datalength, auxBuffer)
}
// Write8 writes a single byte value to register addr.
func (d *Device8SPI) Write8(addr, value uint8) error {
return d.d.Write8SPI(d.bus, addr, value)
}
// Write16 writes a 16-bit value to register addr.
func (d *Device8SPI) Write16(addr uint8, value uint16) error {
return d.d.Write16SPI(d.bus, addr, value, d.order)
}
// Write32 writes a 32-bit value to register addr.
func (d *Device8SPI) Write32(addr uint8, value uint32) error {
return d.d.Write32SPI(d.bus, addr, value, d.order)
}
// Device8I2C implements common logic to most 8-bit peripherals with an I2C bus.
// All methods expect the target to support conventional register read and write operations
// where the first byte sent is the register address being accessed.
//
// All methods use an internal buffer and perform no dynamic memory allocation.
type Device8I2C struct {
bus drivers.I2C
i2cAddr uint16
order binary.ByteOrder
d Device8
}
// SetBus sets the I2C bus, device address, and byte order for the Device8I2C.
//
// As a hint, most I2C devices use big-endian (MSB) byte order.
// - Big endian: A value of 0x1234 is transmitted as 0x12 followed by 0x34.
// - Little endian: A value of 0x1234 is transmitted as 0x34 followed by 0x12.
func (d *Device8I2C) SetBus(bus drivers.I2C, i2cAddr uint16, order binary.ByteOrder) {
d.bus = bus
d.i2cAddr = i2cAddr
d.order = order
}
// Read8 reads a single byte from register addr.
func (d *Device8I2C) Read8(addr uint8) (byte, error) {
return d.d.Read8I2C(d.bus, d.i2cAddr, addr)
}
// Read16 reads a 16-bit value from register addr.
func (d *Device8I2C) Read16(addr uint8) (uint16, error) {
return d.d.Read16I2C(d.bus, d.i2cAddr, addr, d.order)
}
// Read32 reads a 32-bit value from register addr.
func (d *Device8I2C) Read32(addr uint8) (uint32, error) {
return d.d.Read32I2C(d.bus, d.i2cAddr, addr, d.order)
}
// ReadData reads dataLength bytes from register addr.
func (d *Device8I2C) ReadData(addr uint8, dataDestination []byte) error {
return d.d.ReadDataI2C(d.bus, d.i2cAddr, addr, dataDestination)
}
// Write8 writes a single byte value to register addr.
func (d *Device8I2C) Write8(addr, value uint8) error {
return d.d.Write8I2C(d.bus, d.i2cAddr, addr, value)
}
// Write16 writes a 16-bit value to register addr.
func (d *Device8I2C) Write16(addr uint8, value uint16) error {
return d.d.Write16I2C(d.bus, d.i2cAddr, addr, value, d.order)
}
// Write32 writes a 32-bit value to register addr.
func (d *Device8I2C) Write32(addr uint8, value uint32) error {
return d.d.Write32I2C(d.bus, d.i2cAddr, addr, value, d.order)
}
+36 -115
View File
@@ -11,57 +11,37 @@ import (
// Device wraps an I2C connection to a LIS3DH device.
type Device struct {
bus drivers.I2C
address uint16
r Range
accel [6]byte // stored acceleration data (from the Update call)
}
// Driver configuration, used for the Configure call. All fields are optional.
type Config struct {
Address uint16
r Range
}
// New creates a new LIS3DH 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 {
return Device{bus: bus, address: Address0}
return Device{bus: bus, Address: Address0}
}
// Configure sets up the device for communication
func (d *Device) Configure(config Config) error {
if config.Address != 0 {
d.address = config.Address
}
func (d *Device) Configure() {
// enable all axes, normal mode
err := legacy.WriteRegister(d.bus, uint8(d.address), REG_CTRL1, []byte{0x07})
if err != nil {
return err
}
legacy.WriteRegister(d.bus, uint8(d.Address), REG_CTRL1, []byte{0x07})
// 400Hz rate
err = d.SetDataRate(DATARATE_400_HZ)
if err != nil {
return err
}
d.SetDataRate(DATARATE_400_HZ)
// High res & BDU enabled
err = legacy.WriteRegister(d.bus, uint8(d.address), REG_CTRL4, []byte{0x88})
if err != nil {
return err
}
legacy.WriteRegister(d.bus, uint8(d.Address), REG_CTRL4, []byte{0x88})
// get current range
d.r, err = d.ReadRange()
return err
d.r = d.ReadRange()
}
// Connected returns whether a LIS3DH has been found.
// It does a "who am I" request and checks the response.
func (d *Device) Connected() bool {
data := []byte{0}
err := legacy.ReadRegister(d.bus, uint8(d.address), WHO_AM_I, data)
err := legacy.ReadRegister(d.bus, uint8(d.Address), WHO_AM_I, data)
if err != nil {
return false
}
@@ -69,51 +49,46 @@ func (d *Device) Connected() bool {
}
// SetDataRate sets the speed of data collected by the LIS3DH.
func (d *Device) SetDataRate(rate DataRate) error {
func (d *Device) SetDataRate(rate DataRate) {
ctl1 := []byte{0}
err := legacy.ReadRegister(d.bus, uint8(d.address), REG_CTRL1, ctl1)
err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_CTRL1, ctl1)
if err != nil {
return err
println(err.Error())
}
// mask off bits
ctl1[0] &^= 0xf0
ctl1[0] |= (byte(rate) << 4)
return legacy.WriteRegister(d.bus, uint8(d.address), REG_CTRL1, ctl1)
legacy.WriteRegister(d.bus, uint8(d.Address), REG_CTRL1, ctl1)
}
// SetRange sets the G range for LIS3DH.
func (d *Device) SetRange(r Range) error {
func (d *Device) SetRange(r Range) {
ctl := []byte{0}
err := legacy.ReadRegister(d.bus, uint8(d.address), REG_CTRL4, ctl)
err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_CTRL4, ctl)
if err != nil {
return err
println(err.Error())
}
// mask off bits
ctl[0] &^= 0x30
ctl[0] |= (byte(r) << 4)
err = legacy.WriteRegister(d.bus, uint8(d.address), REG_CTRL4, ctl)
if err != nil {
return err
}
legacy.WriteRegister(d.bus, uint8(d.Address), REG_CTRL4, ctl)
// store the new range
d.r = r
return nil
}
// ReadRange returns the current G range for LIS3DH.
func (d *Device) ReadRange() (r Range, err error) {
func (d *Device) ReadRange() (r Range) {
ctl := []byte{0}
err = legacy.ReadRegister(d.bus, uint8(d.address), REG_CTRL4, ctl)
err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_CTRL4, ctl)
if err != nil {
return 0, err
println(err.Error())
}
// mask off bits
r = Range(ctl[0] >> 4)
r &= 0x03
return r, nil
return r
}
// ReadAcceleration reads the current acceleration from the device and returns
@@ -121,17 +96,28 @@ func (d *Device) ReadRange() (r Range, err error) {
// and the sensor is not moving the returned value will be around 1000000 or
// -1000000.
func (d *Device) ReadAcceleration() (int32, int32, int32, error) {
rawX, rawY, rawZ := d.ReadRawAcceleration()
x, y, z := normalizeRange(rawX, rawY, rawZ, d.r)
return x, y, z, nil
x, y, z := d.ReadRawAcceleration()
divider := float32(1)
switch d.r {
case RANGE_16_G:
divider = 1365
case RANGE_8_G:
divider = 4096
case RANGE_4_G:
divider = 8190
case RANGE_2_G:
divider = 16380
}
return int32(float32(x) / divider * 1000000), int32(float32(y) / divider * 1000000), int32(float32(z) / divider * 1000000), nil
}
// ReadRawAcceleration returns the raw x, y and z axis from the LIS3DH
func (d *Device) ReadRawAcceleration() (x int16, y int16, z int16) {
legacy.WriteRegister(d.bus, uint8(d.address), REG_OUT_X_L|0x80, nil)
legacy.WriteRegister(d.bus, uint8(d.Address), REG_OUT_X_L|0x80, nil)
data := []byte{0, 0, 0, 0, 0, 0}
d.bus.Tx(d.address, nil, data)
d.bus.Tx(d.Address, nil, data)
x = int16((uint16(data[1]) << 8) | uint16(data[0]))
y = int16((uint16(data[3]) << 8) | uint16(data[2]))
@@ -139,68 +125,3 @@ func (d *Device) ReadRawAcceleration() (x int16, y int16, z int16) {
return
}
// Update the sensor values of the 'which' parameter. Only acceleration is
// supported at the moment.
func (d *Device) Update(which drivers.Measurement) error {
if which&drivers.Acceleration != 0 {
// Read raw acceleration values and store them in the driver.
err := legacy.WriteRegister(d.bus, uint8(d.address), REG_OUT_X_L|0x80, nil)
if err != nil {
return err
}
err = d.bus.Tx(d.address, nil, d.accel[:])
if err != nil {
return err
}
}
return nil
}
// Acceleration returns the last read acceleration in µg (micro-gravity).
// When one of the axes is pointing straight to Earth and the sensor is not
// moving the returned value will be around 1000000 or -1000000.
func (d *Device) Acceleration() (x, y, z int32) {
// Extract the raw 16-bit values.
rawX := int16((uint16(d.accel[1]) << 8) | uint16(d.accel[0]))
rawY := int16((uint16(d.accel[3]) << 8) | uint16(d.accel[2]))
rawZ := int16((uint16(d.accel[5]) << 8) | uint16(d.accel[4]))
// Normalize these values, to be in µg (micro-gravity).
return normalizeRange(rawX, rawY, rawZ, d.r)
}
// Convert raw 16-bit values to normalized 32-bit values while avoiding floats
// and divisions.
func normalizeRange(rawX, rawY, rawZ int16, r Range) (x, y, z int32) {
// We're going to convert the 16-bit raw values to values in the range
// -1000_000..1000_000. For now we're going to assume a range of 16G, we'll
// adjust that range later.
// The formula is derived as follows, and carefully selected to avoid
// overflow and integer divisions (the division will be optimized to a
// bitshift):
// x = x * 1000_000 / 2048
// x = x * (1000_000/64) / (2048/64)
// x = x * 15625 / 32
x = int32(rawX) * 15625 / 32
y = int32(rawY) * 15625 / 32
z = int32(rawZ) * 15625 / 32
// Now we need to normalize the three values, since we assumed 16G before.
shift := uint32(0)
switch r {
case RANGE_16_G:
shift = 0
case RANGE_8_G:
shift = 1
case RANGE_4_G:
shift = 2
case RANGE_2_G:
shift = 3
}
x >>= shift
y >>= shift
z >>= shift
return
}
+10 -14
View File
@@ -60,20 +60,16 @@ const (
)
const (
Bandwidth_7_8 = iota // 7.8 kHz
Bandwidth_10_4 // 10.4 kHz
Bandwidth_15_6 // 15.6 kHz
Bandwidth_20_8 // 20.8 kHz
Bandwidth_31_25 // 31.25 kHz
Bandwidth_41_7 // 41.7 kHz
Bandwidth_62_5 // 62.5 kHz
Bandwidth_125_0 // 125.0 kHz
Bandwidth_203_125 // 203.125 kHz
Bandwidth_250_0 // 250.0 kHz
Bandwidth_406_25 // 406.25 kHz
Bandwidth_500_0 // 500.0 kHz
Bandwidth_812_5 // 812.5 kHz
Bandwidth_1625_0 // 1625 kHz
Bandwidth_7_8 = iota // 7.8 kHz
Bandwidth_10_4 // 10.4 kHz
Bandwidth_15_6 // 15.6 kHz
Bandwidth_20_8 // 20.8 kHz
Bandwidth_31_25 // 31.25 kHz
Bandwidth_41_7 // 41.7 kHz
Bandwidth_62_5 // 62.5 kHz
Bandwidth_125_0 // 125.0 kHz
Bandwidth_250_0 // 250.0 kHz
Bandwidth_500_0 // 500.0 kHz
)
const (
+2 -2
View File
@@ -3,8 +3,8 @@ package region
import "tinygo.org/x/drivers/lora"
const (
EU868_DEFAULT_PREAMBLE_LEN = 8 // page 103 RP002-1.0.5
EU868_DEFAULT_TX_POWER_DBM = 16 // page 36 RP002-1.0.5, 16 is the max
EU868_DEFAULT_PREAMBLE_LEN = 8
EU868_DEFAULT_TX_POWER_DBM = 20
)
type ChannelEU struct {
-3
View File
@@ -6,9 +6,6 @@ const (
RadioEventTimeout
RadioEventWatchdog
RadioEventCrcError
RadioEventValidHeader
RadioEventCadDone
RadioEventCadDetected
RadioEventUnhandled
)
+28 -35
View File
@@ -7,6 +7,7 @@ import (
"errors"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/legacy"
)
type AccelRange uint8
@@ -27,7 +28,7 @@ type Device struct {
accelMultiplier int32
gyroMultiplier int32
magMultiplier int32
buf [7]uint8 // up to 6 bytes for read + 1 byte for the register address
buf [6]uint8
}
// Configuration for LSM9DS1 device.
@@ -60,15 +61,10 @@ func New(bus drivers.I2C) *Device {
// Case of boolean false and error nil means I2C is up,
// but "who am I" responses have unexpected values.
func (d *Device) Connected() bool {
data, err := d.readBytes(d.AccelAddress, WHO_AM_I, 1)
if err != nil || data[0] != 0x68 {
return false
}
data, err = d.readBytes(d.MagAddress, WHO_AM_I_M, 1)
if err != nil || data[0] != 0x3D {
return false
}
return true
data1, data2 := d.buf[:1], d.buf[1:2]
legacy.ReadRegister(d.bus, d.AccelAddress, WHO_AM_I, data1)
legacy.ReadRegister(d.bus, d.MagAddress, WHO_AM_I_M, data2)
return data1[0] == 0x68 && data2[0] == 0x3D
}
// ReadAcceleration reads the current acceleration from the device and returns
@@ -76,7 +72,8 @@ func (d *Device) Connected() bool {
// and the sensor is not moving the returned value will be around 1000000 or
// -1000000.
func (d *Device) ReadAcceleration() (x, y, z int32, err error) {
data, err := d.readBytes(d.AccelAddress, OUT_X_L_XL, 6)
data := d.buf[:6]
err = legacy.ReadRegister(d.bus, uint8(d.AccelAddress), OUT_X_L_XL, data)
if err != nil {
return
}
@@ -91,7 +88,8 @@ func (d *Device) ReadAcceleration() (x, y, z int32, err error) {
// rotation along one axis and while doing so integrate all values over time,
// you would get a value close to 360000000.
func (d *Device) ReadRotation() (x, y, z int32, err error) {
data, err := d.readBytes(d.AccelAddress, OUT_X_L_G, 6)
data := d.buf[:6]
err = legacy.ReadRegister(d.bus, uint8(d.AccelAddress), OUT_X_L_G, data)
if err != nil {
return
}
@@ -104,7 +102,8 @@ func (d *Device) ReadRotation() (x, y, z int32, err error) {
// ReadMagneticField reads the current magnetic field from the device and returns
// it in nT (nanotesla). 1 G (gauss) = 100_000 nT (nanotesla).
func (d *Device) ReadMagneticField() (x, y, z int32, err error) {
data, err := d.readBytes(d.MagAddress, OUT_X_L_M, 6)
data := d.buf[:6]
err = legacy.ReadRegister(d.bus, uint8(d.MagAddress), OUT_X_L_M, data)
if err != nil {
return
}
@@ -116,7 +115,8 @@ func (d *Device) ReadMagneticField() (x, y, z int32, err error) {
// ReadTemperature returns the temperature in Celsius milli degrees (°C/1000)
func (d *Device) ReadTemperature() (t int32, err error) {
data, err := d.readBytes(d.AccelAddress, OUT_TEMP_L, 2)
data := d.buf[:2]
err = legacy.ReadRegister(d.bus, uint8(d.AccelAddress), OUT_TEMP_L, data)
if err != nil {
return
}
@@ -167,16 +167,20 @@ func (d *Device) doConfigure(cfg Configuration) (err error) {
d.magMultiplier = 58
}
data := d.buf[:1]
// Configure accelerometer
// Sample rate & measurement range
err = d.writeByte(d.AccelAddress, CTRL_REG6_XL, uint8(cfg.AccelSampleRate)<<5|uint8(cfg.AccelRange)<<3)
data[0] = uint8(cfg.AccelSampleRate)<<5 | uint8(cfg.AccelRange)<<3
err = legacy.WriteRegister(d.bus, d.AccelAddress, CTRL_REG6_XL, data)
if err != nil {
return
}
// Configure gyroscope
// Sample rate & measurement range
err = d.writeByte(d.AccelAddress, CTRL_REG1_G, uint8(cfg.GyroSampleRate)<<5|uint8(cfg.GyroRange)<<3)
data[0] = uint8(cfg.GyroSampleRate)<<5 | uint8(cfg.GyroRange)<<3
err = legacy.WriteRegister(d.bus, d.AccelAddress, CTRL_REG1_G, data)
if err != nil {
return
}
@@ -186,44 +190,33 @@ func (d *Device) doConfigure(cfg Configuration) (err error) {
// Temperature compensation enabled
// High-performance mode XY axis
// Sample rate
err = d.writeByte(d.MagAddress, CTRL_REG1_M, 0b10000000|0b01000000|uint8(cfg.MagSampleRate)<<2)
data[0] = 0b10000000 | 0b01000000 | uint8(cfg.MagSampleRate)<<2
err = legacy.WriteRegister(d.bus, d.MagAddress, CTRL_REG1_M, data)
if err != nil {
return
}
// Measurement range
err = d.writeByte(d.MagAddress, CTRL_REG2_M, uint8(cfg.MagRange)<<5)
data[0] = uint8(cfg.MagRange) << 5
err = legacy.WriteRegister(d.bus, d.MagAddress, CTRL_REG2_M, data)
if err != nil {
return
}
// Continuous-conversion mode
// https://electronics.stackexchange.com/questions/237397/continuous-conversion-vs-single-conversion-mode
err = d.writeByte(d.MagAddress, CTRL_REG3_M, 0b00000000)
data[0] = 0b00000000
err = legacy.WriteRegister(d.bus, d.MagAddress, CTRL_REG3_M, data)
if err != nil {
return
}
// High-performance mode Z axis
err = d.writeByte(d.MagAddress, CTRL_REG4_M, 0b00001000)
data[0] = 0b00001000
err = legacy.WriteRegister(d.bus, d.MagAddress, CTRL_REG4_M, data)
if err != nil {
return
}
return nil
}
func (d *Device) readBytes(addr, reg, size uint8) ([]byte, error) {
d.buf[0] = reg
err := d.bus.Tx(uint16(addr), d.buf[0:1], d.buf[1:size+1])
if err != nil {
return nil, err
}
return d.buf[1 : size+1], nil
}
func (d *Device) writeByte(addr, reg, value uint8) error {
d.buf[0] = reg
d.buf[1] = value
return d.bus.Tx(uint16(addr), d.buf[0:2], nil)
}
+2 -3
View File
@@ -5,7 +5,6 @@ import (
"errors"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/pin"
)
// ErrThermocoupleOpen is returned when the thermocouple input is open.
@@ -14,13 +13,13 @@ var ErrThermocoupleOpen = errors.New("thermocouple input open")
type Device struct {
bus drivers.SPI
cs pin.OutputFunc
cs drivers.PinOutputFunc
}
// 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 pin.Output) *Device {
func NewDevice(bus drivers.SPI, cs drivers.PinOutput) *Device {
return &Device{
bus: bus,
cs: cs.Set,
+2 -3
View File
@@ -5,19 +5,18 @@ package max72xx
import (
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/legacy"
"tinygo.org/x/drivers/internal/pin"
)
type Device struct {
bus drivers.SPI
cs pin.OutputFunc
cs drivers.PinOutputFunc
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 pin.Output) *Device {
func NewDevice(bus drivers.SPI, cs drivers.PinOutput) *Device {
return &Device{
bus: bus,
cs: cs.Set,
+43 -48
View File
@@ -6,40 +6,23 @@
package mcp2515 // import "tinygo.org/x/drivers/mcp2515"
import (
"encoding/binary"
"errors"
"fmt"
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/legacy"
"tinygo.org/x/drivers/internal/pin"
)
var (
ErrNothingIsReceived = errors.New("readMsg: nothing is received")
ErrRequestNewModeMaxTimeEx = errors.New("requestNewMode max time expired")
ErrLengthIsLongerThanCapacity = errors.New("length is longer than capacity")
ErrTxTimeout = errors.New("Tx: Tx timeout")
ErrInvalidDirection = errors.New("invalid direction")
ErrInvalidParameter = errors.New("invalid parameter")
ErrCannotExpandBuffer = errors.New("cannot expand buffer (to avoid memory allocation)")
)
// Device wraps MCP2515 SPI CAN Module.
type Device struct {
spi SPI
cs pin.OutputFunc
cs drivers.PinOutputFunc
msg *CANMsg
extended bool
mcpMode byte
configurePins func()
}
type Configuration struct {
Extended bool
}
// CANMsg stores CAN message fields.
type CANMsg struct {
ID uint32
@@ -54,7 +37,7 @@ const (
)
// New returns a new MCP2515 driver. Pass in a fully configured SPI bus.
func New(b drivers.SPI, csPin pin.Output) *Device {
func New(b drivers.SPI, csPin drivers.PinOutput) *Device {
d := &Device{
spi: SPI{
bus: b,
@@ -72,11 +55,10 @@ func New(b drivers.SPI, csPin pin.Output) *Device {
}
// Configure sets up the device for communication.
func (d *Device) Configure(cfg Configuration) {
func (d *Device) Configure() {
if d.configurePins == nil {
panic(legacy.ErrConfigBeforeInstantiated)
}
d.extended = cfg.Extended
d.configurePins()
}
@@ -134,13 +116,9 @@ func (d *Device) Tx(canid uint32, dlc uint8, data []byte) error {
timeoutCount++
}
if timeoutCount == timeoutvalue {
return ErrTxTimeout
return fmt.Errorf("Tx: Tx timeout")
}
ext := byte(0)
if d.extended {
ext = 1
}
err = d.writeCANMsg(bufNum, canid, ext, 0, dlc, data)
err = d.writeCANMsg(bufNum, canid, 0, 0, dlc, data)
if err != nil {
return err
}
@@ -410,7 +388,7 @@ func (d *Device) configRate(speed, clock byte) error {
set = false
}
if !set {
return ErrInvalidParameter
return errors.New("invalid parameter")
}
if err := d.setRegister(mcpCNF1, cfg1); err != nil {
return err
@@ -470,7 +448,7 @@ func (d *Device) readMsg() error {
return err
}
} else {
return ErrNothingIsReceived
return fmt.Errorf("readMsg: nothing is received")
}
return nil
@@ -577,22 +555,39 @@ func (d *Device) writeCANMsg(bufNum uint8, canid uint32, ext, rtrBit, dlc uint8,
}
func (s *SPI) setTxBufData(canid uint32, ext, rtrBit, dlc uint8, data []byte) error {
var id [4]byte
canid = canid & 0x0FFFF
if ext == 1 {
canid = canid & extidBottom29Mask
extended_id := canid
high_11 := extended_id & extidTop11WriteMask
low_18 := extended_id & extidBottom18Mask
high_11 <<= 3
extended_id_shifted := high_11 | low_18
canid = extended_id_shifted | extidFlagMask
// TODO: add Extended ID
err := s.setTxData(0)
if err != nil {
return err
}
err = s.setTxData(0)
if err != nil {
return err
}
err = s.setTxData(0)
if err != nil {
return err
}
err = s.setTxData(0)
if err != nil {
return err
}
} else {
canid = canid & stdidBottom11Mask
canid <<= 16 + 5
}
binary.BigEndian.PutUint32(id[:], canid)
for _, b := range id {
err := s.setTxData(b)
err := s.setTxData(byte(canid >> 3))
if err != nil {
return err
}
err = s.setTxData(byte((canid & 0x07) << 5))
if err != nil {
return err
}
err = s.setTxData(0)
if err != nil {
return err
}
err = s.setTxData(0)
if err != nil {
return err
}
@@ -789,7 +784,7 @@ func (d *Device) requestNewMode(newMode byte) error {
if r&modeMask == newMode {
return nil
} else if e := time.Now(); e.Sub(s) > 200*time.Millisecond {
return ErrRequestNewModeMaxTimeEx
return errors.New("requestNewMode max time expired")
}
}
}
@@ -865,23 +860,23 @@ func (s *SPI) clearBuffer(dir int) error { return s.setBufferLength(0, dir) }
func (s *SPI) setBufferLength(length int, dir int) error {
if dir == tx {
if length > cap(s.tx) {
return ErrLengthIsLongerThanCapacity
return fmt.Errorf("length is longer than capacity")
}
s.tx = s.tx[:length]
} else if dir == rx {
if length > cap(s.rx) {
return ErrLengthIsLongerThanCapacity
return fmt.Errorf("length is longer than capacity")
}
s.rx = s.rx[:length]
} else {
return ErrInvalidDirection
return fmt.Errorf("invalid direction")
}
return nil
}
func (s *SPI) setTxData(data byte) error {
if len(s.tx) >= bufferSize {
return ErrCannotExpandBuffer
return fmt.Errorf("cannot expand buffer (to avoid memory allocation)")
}
s.tx = append(s.tx, data)
-7
View File
@@ -417,11 +417,4 @@ const (
canFail = 0xff
canMaxCharInMessage = 8
// for extended id
extidTop11WriteMask = 0x1FFC0000
extidBottom29Mask = (1 << 29) - 1 // extended id bits
extidBottom18Mask = (1 << 18) - 1 // bottom 18 bits
stdidBottom11Mask = 0x7FF
extidFlagMask = 1 << 19
)
+9 -12
View File
@@ -1,4 +1,5 @@
// package netlink provides an interface for L2 data link layer operations.
// L2 data link layer
package netlink
import (
@@ -19,7 +20,6 @@ var (
ErrNotSupported = errors.New("Not supported")
)
// Event is a network event type passed to the callback registered with NetNotify.
type Event int
// Network events
@@ -38,7 +38,6 @@ const (
ConnectModeAP // Connect as Wifi Access Point
)
// AuthType is the type of WiFi authorization to use when connecting to an access point.
type AuthType int
// Wifi authorization types. Used when setting up an access point, or
@@ -50,11 +49,10 @@ const (
AuthTypeWPA2Mixed // WPA2/WPA mixed authorization
)
// DefaultConnectTimeout is the default timeout for connection attempts. This is used when ConnectParams.ConnectTimeout is zero.
const DefaultConnectTimeout = 10 * time.Second
// ConnectParams is the set of parameters used to connect a Netlinker device to a network.
type ConnectParams struct {
// Connect mode
ConnectMode
@@ -83,23 +81,22 @@ type ConnectParams struct {
// downed connection or hardware fault and try to recover the
// connection. Set to zero to disable watchodog.
WatchdogTimeout time.Duration
// Hostname to use for this device.
Hostname string
}
// Netlinker is TinyGo's OSI L2 data link layer interface. Network device
// drivers implement Netlinker to expose the device's L2 functionality.
type Netlinker interface {
// NetConnect connects the device to a network
// Connect device to network
NetConnect(params *ConnectParams) error
// NetDisconnect disconnects the device from the network
// Disconnect device from network
NetDisconnect()
// NetNotify registers a callback for network events
// Notify to register callback for network events
NetNotify(cb func(Event))
// GetHardwareAddr returns the device's MAC address
// GetHardwareAddr returns device MAC address
GetHardwareAddr() (net.HardwareAddr, error)
}
+4 -5
View File
@@ -9,15 +9,14 @@ import (
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/pin"
)
// Device wraps an SPI connection.
type Device struct {
bus drivers.SPI
dcPin pin.OutputFunc
rstPin pin.OutputFunc
scePin pin.OutputFunc
dcPin drivers.PinOutputFunc
rstPin drivers.PinOutputFunc
scePin drivers.PinOutputFunc
buffer []byte
width int16
height int16
@@ -30,7 +29,7 @@ type Config struct {
}
// New creates a new PCD8544 connection. The SPI bus must already be configured.
func New(bus drivers.SPI, dcPin, rstPin, scePin pin.Output) *Device {
func New(bus drivers.SPI, dcPin, rstPin, scePin drivers.PinOutput) *Device {
return &Device{
bus: bus,
dcPin: dcPin.Set,
+39
View File
@@ -0,0 +1,39 @@
package drivers
// PinOutput represents a pin hardware abstraction layer for a pin that can output a digital signal.
type PinOutput interface {
Set(level bool)
}
// PinInput represents a pin hardware abstraction layer.
type PinInput interface {
Get() (level bool)
}
// PinOutputFunc is hardware abstraction for a function that causes pin to output a
// digital signal (high or low level).
//
// // Code conversion demo: from machine.Pin to drivers.PinOutputFunc
// led := machine.LED
// led.Configure(machine.PinConfig{Mode: machine.PinOutput})
// var pin drivers.PinOutputFunc = led.Set // Going from a machine.Pin to a drivers.PinOutputFunc
type PinOutputFunc func(level bool)
// High sets the underlying pin's level to high. This is equivalent to calling PinOutput(true).
func (po PinOutputFunc) High() {
po(true)
}
// Low sets the underlying pin's level to low. This is equivalent to calling PinOutput(false).
func (po PinOutputFunc) Low() {
po(false)
}
// PinInputFunc is hardware abstraction for a pin which receives a
// digital signal and reads it (high or low level).
//
// // Code conversion demo: from machine.Pin to drivers.PinInputFunc
// input := machine.LED
// input.Configure(machine.PinConfig{Mode: machine.PinInputPulldown}) // or use machine.PinInputPullup or machine.PinInput
// var pin drivers.PinInputFunc = input.Get // Going from a machine.Pin to a drivers.PinInputFunc
type PinInputFunc func() (level bool)
-21
View File
@@ -149,20 +149,6 @@ 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.
@@ -220,13 +206,6 @@ 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))
+3 -108
View File
@@ -9,30 +9,9 @@ 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{
@@ -51,30 +30,9 @@ 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{
@@ -107,69 +65,9 @@ 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{
@@ -296,9 +194,6 @@ 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)
})
+4 -34
View File
@@ -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 | Grayscale2bit | Monochrome
RGB888 | RGB565BE | RGB555 | RGB444BE | Monochrome
BaseColor
}
@@ -50,8 +50,6 @@ 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:
@@ -163,9 +161,9 @@ func (c RGB555) BitsPerPixel() int {
func (c RGB555) RGBA() color.RGBA {
color := color.RGBA{
R: (uint8(c) & 0x1F) << 3,
G: (uint8(c>>5) & 0x1F) << 3,
B: (uint8(c>>10) & 0x1F) << 3,
R: uint8(c>>10) << 3,
G: uint8(c>>5) << 3,
B: uint8(c) << 3,
A: 255,
}
// Correct color rounding, so that 0xff roundtrips back to 0xff.
@@ -206,34 +204,6 @@ 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: 063 => 0, 64127 => 1, 128191 => 2, 192255 => 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 (0255) 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 {
-1
View File
@@ -22,5 +22,4 @@ const (
CmdStartLowPowerPeriodicMeasurement = 0x21AC
CmdStartPeriodicMeasurement = 0x21B1
CmdStopPeriodicMeasurement = 0x3F86
CmdMeasureSingleShot = 0x219D
)
+8 -46
View File
@@ -82,13 +82,6 @@ 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 {
@@ -100,18 +93,7 @@ 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 {
@@ -123,14 +105,7 @@ 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 {
@@ -139,14 +114,8 @@ 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)
return (-1 * 45000) + (21875 * (int32(d.temperature)) / 8192), err
}
// ReadTempC returns the value in the temperature value in Celsius.
@@ -161,11 +130,6 @@ 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 {
@@ -178,20 +142,18 @@ 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 {
+19 -12
View File
@@ -2,7 +2,8 @@
package shiftregister
import (
"machine"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/legacy"
)
type NumberBit int8
@@ -16,9 +17,10 @@ const (
// Device holds pin number
type Device struct {
latch, clock, out machine.Pin // IC wiring
bits NumberBit // Pin number
mask uint32 // keep all pins state
latch, clock, out drivers.PinOutputFunc // IC wiring
config func()
bits NumberBit // Pin number
mask uint32 // keep all pins state
}
// ShiftPin is the implementation of the ShiftPin interface.
@@ -29,20 +31,25 @@ type ShiftPin struct {
}
// New returns a new shift output register device
func New(Bits NumberBit, Latch, Clock, Out machine.Pin) *Device {
func New(Bits NumberBit, Latch, Clock, Out drivers.PinOutput) *Device {
return &Device{
latch: Latch,
clock: Clock,
out: Out,
latch: Latch.Set,
clock: Clock.Set,
out: Out.Set,
bits: Bits,
config: func() {
legacy.ConfigurePinOut(Latch)
legacy.ConfigurePinOut(Clock)
legacy.ConfigurePinOut(Out)
},
}
}
// Configure set hardware configuration
func (d *Device) Configure() {
d.latch.Configure(machine.PinConfig{Mode: machine.PinOutput})
d.clock.Configure(machine.PinConfig{Mode: machine.PinOutput})
d.out.Configure(machine.PinConfig{Mode: machine.PinOutput})
if d.config == nil {
panic(legacy.ErrConfigBeforeInstantiated)
}
d.latch.High()
}
@@ -53,7 +60,7 @@ func (d *Device) WriteMask(mask uint32) {
d.latch.Low()
for i := 0; i < int(d.bits); i++ {
d.clock.Low()
d.out.Set(mask&1 != 0)
d.out(mask&1 != 0)
mask = mask >> 1
d.clock.High()
}
-158
View File
@@ -1,158 +0,0 @@
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 (
XTAL_FREQ = 25000000
PLL_FIXED = 80000000000
FREQ_MULT = 100
DEFAULT_CLK = 1000000000
PLL_VCO_MIN = 600000000
PLL_VCO_MAX = 900000000
MULTISYNTH_MIN_FREQ = 500000
MULTISYNTH_DIVBY4_FREQ = 150000000
MULTISYNTH_MAX_FREQ = 225000000
MULTISYNTH_SHARE_MAX = 100000000
MULTISYNTH_SHARE_MIN = 1024000
MULTISYNTH67_MAX_FREQ = MULTISYNTH_DIVBY4_FREQ
CLKOUT_MIN_FREQ = 4000
CLKOUT_MAX_FREQ = MULTISYNTH_MAX_FREQ
CLKOUT67_MS_MIN = PLL_VCO_MIN / MULTISYNTH67_A_MAX
CLKOUT67_MIN_FREQ = CLKOUT67_MS_MIN / 128
CLKOUT67_MAX_FREQ = MULTISYNTH67_MAX_FREQ
PLL_A_MIN = 15
PLL_A_MAX = 90
PLL_B_MAX = PLL_C_MAX - 1
PLL_C_MAX = 1048575
MULTISYNTH_A_MIN = 6
MULTISYNTH_A_MAX = 1800
MULTISYNTH67_A_MAX = 254
MULTISYNTH_B_MAX = MULTISYNTH_C_MAX - 1
MULTISYNTH_C_MAX = 1048575
MULTISYNTH_P1_MAX = (1 << 18) - 1
MULTISYNTH_P2_MAX = (1 << 20) - 1
MULTISYNTH_P3_MAX = (1 << 20) - 1
VCXO_PULL_MIN = 30
VCXO_PULL_MAX = 240
VCXO_MARGIN = 103
DEVICE_STATUS = 0
INTERRUPT_STATUS = 1
INTERRUPT_MASK = 2
STATUS_SYS_INIT = 1 << 7
STATUS_LOL_B = 1 << 6
STATUS_LOL_A = 1 << 5
STATUS_LOS = 1 << 4
OUTPUT_ENABLE_CTRL = 3
OEB_PIN_ENABLE_CTRL = 9
PLL_INPUT_SOURCE = 15
CLKIN_DIV_MASK = 3 << 6
CLKIN_DIV_1 = 0 << 6
CLKIN_DIV_2 = 1 << 6
CLKIN_DIV_4 = 2 << 6
CLKIN_DIV_8 = 3 << 6
PLLB_SOURCE = 1 << 3
PLLA_SOURCE = 1 << 2
CLK0_CTRL = 16
CLK1_CTRL = 17
CLK2_CTRL = 18
CLK3_CTRL = 19
CLK4_CTRL = 20
CLK5_CTRL = 21
CLK6_CTRL = 22
CLK7_CTRL = 23
CLK_POWERDOWN = 1 << 7
CLK_INTEGER_MODE = 1 << 6
CLK_PLL_SELECT = 1 << 5
CLK_INVERT = 1 << 4
CLK_INPUT_MASK = 3 << 2
CLK_INPUT_XTAL = 0 << 2
CLK_INPUT_CLKIN = 1 << 2
CLK_INPUT_MULTISYNTH_0_4 = 2 << 2
CLK_INPUT_MULTISYNTH_N = 3 << 2
CLK_DRIVE_STRENGTH_MASK = 3 << 0
CLK_DRIVE_STRENGTH_2MA = 0 << 0
CLK_DRIVE_STRENGTH_4MA = 1 << 0
CLK_DRIVE_STRENGTH_6MA = 2 << 0
CLK_DRIVE_STRENGTH_8MA = 3 << 0
CLK3_0_DISABLE_STATE = 24
CLK7_4_DISABLE_STATE = 25
CLK_DISABLE_STATE_MASK = 3
CLK_DISABLE_STATE_LOW = 0
CLK_DISABLE_STATE_HIGH = 1
CLK_DISABLE_STATE_FLOAT = 2
CLK_DISABLE_STATE_NEVER = 3
PARAMETERS_LENGTH = 8
PLLA_PARAMETERS = 26
PLLB_PARAMETERS = 34
CLK0_PARAMETERS = 42
CLK1_PARAMETERS = 50
CLK2_PARAMETERS = 58
CLK3_PARAMETERS = 66
CLK4_PARAMETERS = 74
CLK5_PARAMETERS = 82
CLK6_PARAMETERS = 90
CLK7_PARAMETERS = 91
CLK6_7_OUTPUT_DIVIDER = 92
OUTPUT_CLK_DIV_MASK = 7 << 4
OUTPUT_CLK6_DIV_MASK = 7 << 0
OUTPUT_CLK_DIV_SHIFT = 4
OUTPUT_CLK_DIV6_SHIFT = 0
OUTPUT_CLK_DIV_1 = 0
OUTPUT_CLK_DIV_2 = 1
OUTPUT_CLK_DIV_4 = 2
OUTPUT_CLK_DIV_8 = 3
OUTPUT_CLK_DIV_16 = 4
OUTPUT_CLK_DIV_32 = 5
OUTPUT_CLK_DIV_64 = 6
OUTPUT_CLK_DIV_128 = 7
OUTPUT_CLK_DIVBY4 = 3 << 2
SSC_PARAM0 = 149
SSC_PARAM1 = 150
SSC_PARAM2 = 151
SSC_PARAM3 = 152
SSC_PARAM4 = 153
SSC_PARAM5 = 154
SSC_PARAM6 = 155
SSC_PARAM7 = 156
SSC_PARAM8 = 157
SSC_PARAM9 = 158
SSC_PARAM10 = 159
SSC_PARAM11 = 160
SSC_PARAM12 = 161
VXCO_PARAMETERS_LOW = 162
VXCO_PARAMETERS_MID = 163
VXCO_PARAMETERS_HIGH = 164
CLK0_PHASE_OFFSET = 165
CLK1_PHASE_OFFSET = 166
CLK2_PHASE_OFFSET = 167
CLK3_PHASE_OFFSET = 168
CLK4_PHASE_OFFSET = 169
CLK5_PHASE_OFFSET = 170
PLL_RESET = 177
PLL_RESET_B = 1 << 7
PLL_RESET_A = 1 << 5
CRYSTAL_LOAD = 183
CRYSTAL_LOAD_MASK = 3 << 6
CRYSTAL_LOAD_0PF = 0 << 6
CRYSTAL_LOAD_6PF = 1 << 6
CRYSTAL_LOAD_8PF = 2 << 6
CRYSTAL_LOAD_10PF = 3 << 6
FANOUT_ENABLE = 187
CLKIN_ENABLE = 1 << 7
XTAL_ENABLE = 1 << 6
MULTISYNTH_ENABLE = 1 << 4
)
-1069
View File
File diff suppressed because it is too large Load Diff
-214
View File
@@ -1,214 +0,0 @@
package si5351
import (
"testing"
)
func TestSelectRDiv(t *testing.T) {
d := &Device{}
tests := []struct {
name string
freq Frequency
wantDiv uint8
wantFreq Frequency
}{
{"4kHz", 4000 * FREQ_MULT, OUTPUT_CLK_DIV_128, 4000 * FREQ_MULT * 128},
{"8kHz", 8000 * FREQ_MULT, OUTPUT_CLK_DIV_64, 8000 * FREQ_MULT * 64},
{"16kHz", 16000 * FREQ_MULT, OUTPUT_CLK_DIV_32, 16000 * FREQ_MULT * 32},
{"32kHz", 32000 * FREQ_MULT, OUTPUT_CLK_DIV_16, 32000 * FREQ_MULT * 16},
{"64kHz", 64000 * FREQ_MULT, OUTPUT_CLK_DIV_8, 64000 * FREQ_MULT * 8},
{"128kHz", 128000 * FREQ_MULT, OUTPUT_CLK_DIV_4, 128000 * FREQ_MULT * 4},
{"256kHz", 256000 * FREQ_MULT, OUTPUT_CLK_DIV_2, 256000 * FREQ_MULT * 2},
{"512kHz", 512000 * FREQ_MULT, OUTPUT_CLK_DIV_1, 512000 * FREQ_MULT},
{"1MHz", 1000000 * FREQ_MULT, OUTPUT_CLK_DIV_1, 1000000 * FREQ_MULT},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
freq := tt.freq
freq, gotDiv := d.selectRDiv(freq)
if gotDiv != tt.wantDiv {
t.Errorf("selectRDiv() div = %v, want %v", gotDiv, tt.wantDiv)
}
if freq != tt.wantFreq {
t.Errorf("selectRDiv() freq = %v, want %v", freq, tt.wantFreq)
}
})
}
}
func TestSelectRDivMS67(t *testing.T) {
d := &Device{}
tests := []struct {
name string
freq Frequency
wantDiv uint8
wantFreq Frequency
}{
{"4kHz", 4000 * FREQ_MULT, OUTPUT_CLK_DIV_128, 4000 * FREQ_MULT * 128},
{"8kHz", 8000 * FREQ_MULT, OUTPUT_CLK_DIV_64, 8000 * FREQ_MULT * 64},
{"16kHz", 16000 * FREQ_MULT, OUTPUT_CLK_DIV_32, 16000 * FREQ_MULT * 32},
{"64kHz", 64000 * FREQ_MULT, OUTPUT_CLK_DIV_8, 64000 * FREQ_MULT * 8},
{"256kHz", 256000 * FREQ_MULT, OUTPUT_CLK_DIV_2, 256000 * FREQ_MULT * 2},
{"512kHz", 512000 * FREQ_MULT, OUTPUT_CLK_DIV_1, 512000 * FREQ_MULT},
{"1MHz", 1000000 * FREQ_MULT, OUTPUT_CLK_DIV_1, 1000000 * FREQ_MULT},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
freq := tt.freq
freq, gotDiv := d.selectRDivMS67(freq)
if gotDiv != tt.wantDiv {
t.Errorf("selectRDivMS67() div = %v, want %v", gotDiv, tt.wantDiv)
}
if freq != tt.wantFreq {
t.Errorf("selectRDivMS67() freq = %v, want %v", freq, tt.wantFreq)
}
})
}
}
func TestCalculatePLL(t *testing.T) {
d := &Device{}
d.crystalFreq[0] = 25000000
tests := []struct {
name string
freq Frequency
wantMin Frequency
wantMax Frequency
}{
{"600MHz", 600000000 * FREQ_MULT, 599000000 * FREQ_MULT, 601000000 * FREQ_MULT},
{"750MHz", 750000000 * FREQ_MULT, 749000000 * FREQ_MULT, 751000000 * FREQ_MULT},
{"900MHz", 900000000 * FREQ_MULT, 899000000 * FREQ_MULT, 901000000 * FREQ_MULT},
{"BelowMin", 500000000 * FREQ_MULT, 600000000 * FREQ_MULT, 600000000 * FREQ_MULT},
{"AboveMax", 1000000000 * FREQ_MULT, 900000000 * FREQ_MULT, 900000000 * FREQ_MULT},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, reg := d.CalculatePLL(PLL_A, tt.freq, 0, false)
if got < tt.wantMin || got > tt.wantMax {
t.Errorf("CalculatePLL() = %v, want between %v and %v", got, tt.wantMin, tt.wantMax)
}
if reg.p1 == 0 || reg.p3 == 0 {
t.Errorf("CalculatePLL() invalid register values: p1=%v, p2=%v, p3=%v", reg.p1, reg.p2, reg.p3)
}
})
}
}
func TestCalculateMultisynth(t *testing.T) {
d := &Device{}
tests := []struct {
name string
freq Frequency
pllFreq Frequency
wantDiv bool
}{
{"10MHz from 800MHz", 10000000 * FREQ_MULT, 800000000 * FREQ_MULT, false},
{"1MHz from 800MHz", 1000000 * FREQ_MULT, 800000000 * FREQ_MULT, false},
{"Auto PLL 10MHz", 10000000 * FREQ_MULT, 0, false},
{"150MHz DivBy4", 150000000 * FREQ_MULT, 600000000 * FREQ_MULT, true},
{"BelowMin", 100000 * FREQ_MULT, 800000000 * FREQ_MULT, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, reg := d.CalculateMultisynth(tt.freq, tt.pllFreq)
if tt.pllFreq == 0 {
// Auto mode should return a valid PLL frequency
if got < PLL_VCO_MIN*FREQ_MULT || got > PLL_VCO_MAX*FREQ_MULT {
t.Errorf("CalculateMultisynth() returned invalid PLL freq %v", got)
}
}
if reg.p3 == 0 {
t.Errorf("CalculateMultisynth() p3 should not be 0")
}
})
}
}
func TestMultisynth67Calc(t *testing.T) {
d := &Device{}
tests := []struct {
name string
freq Frequency
pllFreq Frequency
wantErr bool
}{
{"10MHz Auto", 10000000 * FREQ_MULT, 0, false},
{"100MHz Auto", 100000000 * FREQ_MULT, 0, false},
{"100MHz from 800MHz", 100000000 * FREQ_MULT, 800000000 * FREQ_MULT, false},
{"Invalid Division", 10000000 * FREQ_MULT, 777000000 * FREQ_MULT, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, reg := d.multisynth67Calc(tt.freq, tt.pllFreq)
if tt.pllFreq == 0 {
if got < PLL_VCO_MIN*FREQ_MULT || got > PLL_VCO_MAX*FREQ_MULT {
t.Errorf("multisynth67Calc() returned invalid PLL freq %v", got)
}
} else if tt.wantErr {
if got != 0 {
t.Errorf("multisynth67Calc() should return 0 for invalid division, got %v", got)
}
}
if reg.p1 == 0 && !tt.wantErr {
t.Errorf("multisynth67Calc() p1 should not be 0")
}
})
}
}
func TestSetCorrection(t *testing.T) {
// Skip this test as it requires a mock I2C bus
t.Skip("Requires mock I2C bus implementation")
}
func TestSetRefFreq(t *testing.T) {
d := &Device{}
tests := []struct {
name string
freq CrystalFrequency
wantFreq CrystalFrequency
wantDiv uint8
}{
{"25MHz", 25000000, 25000000, CLKIN_DIV_1},
{"50MHz", 50000000, 25000000, CLKIN_DIV_2},
{"100MHz", 100000000, 25000000, CLKIN_DIV_4},
{"30MHz", 30000000, 30000000, CLKIN_DIV_1},
{"60MHz", 60000000, 30000000, CLKIN_DIV_2},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
d.SetReferenceFrequency(PLLInputClockIn, tt.freq)
if d.crystalFreq[PLLInputClockIn] != tt.wantFreq {
t.Errorf("SetReferenceFrequency() freq = %v, want %v", d.crystalFreq[PLLInputClockIn], tt.wantFreq)
}
if d.clkinDiv != tt.wantDiv {
t.Errorf("SetReferenceFrequency() clkinDiv = %v, want %v", d.clkinDiv, tt.wantDiv)
}
})
}
}
func TestGetCorrection(t *testing.T) {
d := &Device{}
d.refCorrection[PLLInputXO] = 5000
d.refCorrection[PLLInputClockIn] = -3000
if got := d.GetCorrection(PLLInputXO); got != 5000 {
t.Errorf("GetCorrection(PLLInputXO) = %v, want 5000", got)
}
if got := d.GetCorrection(PLLInputClockIn); got != -3000 {
t.Errorf("GetCorrection(PLLInputClockIn) = %v, want -3000", got)
}
}
+1 -8
View File
@@ -20,11 +20,9 @@ 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/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=itsybitsy-m0 ./examples/ds3231/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
@@ -93,7 +91,6 @@ tinygo build -size short -o ./build/test.bin -target=m5stamp-c3 ./examp
tinygo build -size short -o ./build/test.hex -target=feather-nrf52840 ./examples/is31fl3731/main.go
tinygo build -size short -o ./build/test.hex -target=arduino ./examples/ws2812
tinygo build -size short -o ./build/test.hex -target=digispark ./examples/ws2812
tinygo build -size short -o ./build/test.bin -target=xiao-esp32c3 ./examples/ws2812
tinygo build -size short -o ./build/test.hex -target=trinket-m0 ./examples/bme280/main.go
tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/microphone/main.go
tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/buzzer/main.go
@@ -125,11 +122,9 @@ tinygo build -size short -o ./build/test.uf2 -target=pico ./examples/xpt2046/mai
tinygo build -size short -o ./build/test.elf -target=m5stack-core2 ./examples/ft6336/basic/
tinygo build -size short -o ./build/test.elf -target=m5stack-core2 ./examples/ft6336/touchpaint/
tinygo build -size short -o ./build/test.hex -target=nucleo-wl55jc ./examples/sx126x/lora_rxtx/
tinygo build -size short -o ./build/test.hex -target=pybadge ./examples/sx127x/lora_rxtx/
tinygo build -size short -o ./build/test.uf2 -target=pico ./examples/ssd1289/main.go
tinygo build -size short -o ./build/test.hex -target=pico ./examples/irremote/main.go
tinygo build -size short -o ./build/test.hex -target=badger2040 ./examples/uc8151/main.go
tinygo build -size short -o ./build/test.hex -target=badger2040 ./examples/waveshare-epd/epd2in9v2/main.go
tinygo build -size short -o ./build/test.uf2 -target=pico ./examples/scd4x/main.go
tinygo build -size short -o ./build/test.uf2 -target=circuitplay-express ./examples/makeybutton/main.go
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/ds18b20/main.go
@@ -149,8 +144,6 @@ 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)
+22 -11
View File
@@ -1,19 +1,17 @@
//go:build baremetal
package ssd1289
import (
"tinygo.org/x/drivers/internal/legacy"
"tinygo.org/x/drivers/internal/pin"
)
import "machine"
type pinBus struct {
pins [16]pin.Output
pins [16]machine.Pin
}
func NewPinBus(pins [16]pin.Output) pinBus {
func NewPinBus(pins [16]machine.Pin) pinBus {
// configure GPIO pins (on baremetal targets only, for backwards compatibility)
for i := 0; i < 16; i++ {
legacy.ConfigurePinOut(pins[i])
pins[i].Configure(machine.PinConfig{Mode: machine.PinOutput})
}
return pinBus{
@@ -22,7 +20,20 @@ func NewPinBus(pins [16]pin.Output) pinBus {
}
func (b pinBus) Set(data uint16) {
for i := 15; i >= 0; i-- {
b.pins[i].Set((data & (1 << i)) != 0)
}
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)
}
+11 -12
View File
@@ -5,10 +5,11 @@ package ssd1289
import (
"image/color"
"machine"
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/legacy"
"tinygo.org/x/drivers/internal/pin"
)
type Bus interface {
@@ -16,34 +17,32 @@ type Bus interface {
}
type Device struct {
rs pin.OutputFunc
wr pin.OutputFunc
cs pin.OutputFunc
rst pin.OutputFunc
rs drivers.PinOutputFunc
wr drivers.PinOutputFunc
cs drivers.PinOutputFunc
rst drivers.PinOutputFunc
bus Bus
}
const width = int16(240)
const height = int16(320)
func New(rs, wr, cs, rst pin.Output, bus Bus) *Device {
d := &Device{
func New(rs machine.Pin, wr machine.Pin, cs machine.Pin, rst machine.Pin, bus Bus) Device {
d := Device{
rs: rs.Set,
wr: wr.Set,
cs: cs.Set,
rst: rst.Set,
bus: bus,
}
// configure GPIO pins (only on baremetal targets, for backwards compatibility)
legacy.ConfigurePinOut(rs)
legacy.ConfigurePinOut(wr)
legacy.ConfigurePinOut(cs)
legacy.ConfigurePinOut(rst)
d.cs.High()
d.rst.High()
d.wr.High()
cs.Set(true)
rst.Set(true)
wr.Set(true)
return d
}
+12 -14
View File
@@ -1,33 +1,31 @@
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 pin.OutputFunc
resetPin pin.OutputFunc
csPin pin.OutputFunc
dcPin machine.Pin
resetPin machine.Pin
csPin machine.Pin
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 pin.Output) *Device {
// configure GPIO pins (on baremetal targets only, for backwards compatibility)
legacy.ConfigurePinOut(dcPin)
legacy.ConfigurePinOut(resetPin)
legacy.ConfigurePinOut(csPin)
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})
return &Device{
bus: &SPIBus{
wire: bus,
dcPin: dcPin.Set,
resetPin: resetPin.Set,
csPin: csPin.Set,
dcPin: dcPin,
resetPin: resetPin,
csPin: csPin,
},
}
}
@@ -62,7 +60,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(!isCommand)
b.dcPin.Set(!isCommand)
b.csPin.Low()
err := b.wire.Tx(data, nil)
b.csPin.High()
+5 -6
View File
@@ -5,13 +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
@@ -20,9 +20,9 @@ type Rotation uint8
// Device wraps an SPI connection.
type Device struct {
bus drivers.SPI
dcPin pin.OutputFunc
resetPin pin.OutputFunc
csPin pin.OutputFunc
dcPin drivers.PinOutputFunc
resetPin drivers.PinOutputFunc
csPin drivers.PinOutputFunc
width int16
height int16
batchLength int16
@@ -37,8 +37,7 @@ type Config struct {
}
// New creates a new SSD1331 connection. The SPI wire must already be configured.
func New(bus drivers.SPI, resetPin, dcPin, csPin pin.Output) Device {
// configure GPIO pins (on baremetal targets only, for backwards compatibility)
func New(bus drivers.SPI, resetPin, dcPin, csPin machine.Pin) Device {
legacy.ConfigurePinOut(dcPin)
legacy.ConfigurePinOut(resetPin)
legacy.ConfigurePinOut(csPin)
+11 -9
View File
@@ -10,7 +10,6 @@ import (
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/legacy"
"tinygo.org/x/drivers/internal/pin"
)
var (
@@ -21,17 +20,17 @@ var (
// Device wraps an SPI connection.
type Device struct {
bus drivers.SPI
dcPin pin.OutputFunc
resetPin pin.OutputFunc
csPin pin.OutputFunc
enPin pin.OutputFunc
rwPin pin.OutputFunc
dcPin drivers.PinOutputFunc
resetPin drivers.PinOutputFunc
csPin drivers.PinOutputFunc
enPin drivers.PinOutputFunc
rwPin drivers.PinOutputFunc
configurePins func()
width int16
height int16
rowOffset int16
columnOffset int16
bufferLength int16
configurePins func()
}
// Config is the configuration for the display
@@ -43,7 +42,7 @@ 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 pin.Output) Device {
func New(bus drivers.SPI, resetPin, dcPin, csPin, enPin, rwPin drivers.PinOutput) Device {
return Device{
bus: bus,
dcPin: dcPin.Set,
@@ -63,6 +62,9 @@ func New(bus drivers.SPI, resetPin, dcPin, csPin, enPin, rwPin pin.Output) Devic
// Configure initializes the display with default configuration
func (d *Device) Configure(cfg Config) {
if d.configurePins == nil {
panic(legacy.ErrConfigBeforeInstantiated)
}
if cfg.Width == 0 {
cfg.Width = 128
}
@@ -81,7 +83,7 @@ func (d *Device) Configure(cfg Config) {
d.bufferLength = d.height
}
// configure GPIO pins (on baremetal targets only, for backwards compatibility)
// configure GPIO pins
d.configurePins()
// reset the device
+6 -9
View File
@@ -11,7 +11,6 @@ import (
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/legacy"
"tinygo.org/x/drivers/internal/pin"
"tinygo.org/x/drivers/pixel"
)
@@ -40,10 +39,10 @@ type Device = DeviceOf[pixel.RGB565BE]
// formats.
type DeviceOf[T Color] struct {
bus drivers.SPI
dcPin pin.OutputFunc
resetPin pin.OutputFunc
csPin pin.OutputFunc
blPin pin.OutputFunc
dcPin drivers.PinOutputFunc
resetPin drivers.PinOutputFunc
csPin drivers.PinOutputFunc
blPin drivers.PinOutputFunc
width int16
height int16
columnOffset int16
@@ -66,15 +65,13 @@ 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 pin.Output) Device {
func New(bus drivers.SPI, resetPin, dcPin, csPin, blPin drivers.PinOutput) 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 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.
func NewOf[T Color](bus drivers.SPI, resetPin, dcPin, csPin, blPin drivers.PinOutput) DeviceOf[T] {
legacy.ConfigurePinOut(dcPin)
legacy.ConfigurePinOut(resetPin)
legacy.ConfigurePinOut(csPin)
+26 -42
View File
@@ -7,13 +7,13 @@ package st7789 // import "tinygo.org/x/drivers/st7789"
import (
"image/color"
"machine"
"math"
"time"
"errors"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/legacy"
"tinygo.org/x/drivers/pixel"
)
@@ -46,10 +46,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 drivers.PinOutputFunc
resetPin drivers.PinOutputFunc
csPin drivers.PinOutputFunc
blPin drivers.PinOutputFunc
width int16
height int16
columnOffsetCfg int16
@@ -83,23 +83,27 @@ type Config struct {
}
// New creates a new ST7789 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 drivers.PinOutput) Device {
return NewOf[pixel.RGB565BE](bus, resetPin, dcPin, csPin, blPin)
}
// NewOf creates a new ST7789 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 drivers.PinOutput) DeviceOf[T] {
legacy.ConfigurePinOut(dcPin)
legacy.ConfigurePinOut(resetPin)
legacy.ConfigurePinOut(csPin)
legacy.ConfigurePinOut(blPin)
var cs drivers.PinOutputFunc
if !legacy.PinIsNoPin(csPin) {
cs = csPin.Set
}
return DeviceOf[T]{
bus: bus,
dcPin: dcPin,
resetPin: resetPin,
csPin: csPin,
blPin: blPin,
dcPin: dcPin.Set,
resetPin: resetPin.Set,
csPin: cs,
blPin: blPin.Set,
}
}
@@ -229,7 +233,7 @@ func (d *DeviceOf[T]) sendCommand(command uint8, data []byte) error {
// startWrite must be called at the beginning of all exported methods to set the
// chip select pin low.
func (d *DeviceOf[T]) startWrite() {
if d.csPin != machine.NoPin {
if d.csPin != nil {
d.csPin.Low()
}
}
@@ -237,7 +241,7 @@ func (d *DeviceOf[T]) startWrite() {
// endWrite must be called at the end of all exported methods to set the chip
// select pin high.
func (d *DeviceOf[T]) endWrite() {
if d.csPin != machine.NoPin {
if d.csPin != nil {
d.csPin.High()
}
}
@@ -513,8 +517,8 @@ func (d *DeviceOf[T]) setRotation(rotation Rotation) error {
d.columnOffset = 0
case drivers.Rotation90:
madctl = MADCTL_MX | MADCTL_MV
d.rowOffset = d.columnOffsetCfg
d.columnOffset = d.rowOffsetCfg
d.rowOffset = 0
d.columnOffset = 0
case drivers.Rotation180:
madctl = MADCTL_MX | MADCTL_MY
d.rowOffset = d.rowOffsetCfg
@@ -593,18 +597,8 @@ func (d *DeviceOf[T]) SetScrollArea(topFixedArea, bottomFixedArea int16) {
// The screen doesn't use the full 320 pixel height.
// Enlarge the bottom fixed area to fill the 320 pixel height, so that
// bottomFixedArea starts from the visible bottom of the screen.
//
// VSCRDEF/VSCRSADD always operate on physical frame memory rows (0-319),
// regardless of MADCTL. For rotations with MV set (90°/270°), CASET
// addresses physical rows due to row/column exchange, so the physical row
// offset is d.columnOffset (= rowOffsetCfg). For other rotations,
// d.rowOffset is the physical row offset.
physRowOffset := d.rowOffset
if d.rotation == drivers.Rotation90 || d.rotation == drivers.Rotation270 {
physRowOffset = d.columnOffset
}
topFixedArea += physRowOffset
bottomFixedArea += (320 - d.height) - physRowOffset
topFixedArea += d.rowOffset
bottomFixedArea += (320 - d.height) - d.rowOffset
}
if d.rotation == drivers.Rotation180 {
// The screen is rotated by 180°, so we have to switch the top and
@@ -623,20 +617,10 @@ func (d *DeviceOf[T]) SetScrollArea(topFixedArea, bottomFixedArea int16) {
// SetScroll sets the vertical scroll address of the display.
func (d *DeviceOf[T]) SetScroll(line int16) {
switch d.rotation {
case drivers.Rotation90:
// With MV set, hardware scroll operates on physical rows, which map to the
// visual X axis. Add the physical row offset (d.columnOffset = rowOffsetCfg)
// so that line=0 addresses the first visible physical row.
line = line + d.columnOffset
case drivers.Rotation180:
if d.rotation == drivers.Rotation180 {
// The screen is rotated by 180°, so we have to invert the scroll line
// (taking care of the RowOffset).
line = (319 - d.rowOffset) - line
case drivers.Rotation270:
// With MV+MY, physical rows map to the visual X axis in reverse direction.
// line=0 addresses the last physical row of the visible area.
line = (d.columnOffset + d.height - 1) - line
}
d.buf[0] = uint8(line >> 8)
d.buf[1] = uint8(line)
-6
View File
@@ -96,12 +96,6 @@ const (
SX127X_OPMODE_RX_SINGLE = uint8(0x06)
SX127X_OPMODE_CAD = uint8(0x07)
SX127X_OPMODE_LOW_FREQUENCY = uint8(0x4)
SX127X_OPMODE_MODULATION_MASK = uint8(0x60)
SX127X_OPMODE_MODULATION_FSK = uint8(0x0)
SX127X_OPMODE_MODULATION_OOK = uint8(0x20)
SX127X_LORA_MAC_PUBLIC_SYNCWORD = 0x34
SX127X_LORA_MAC_PRIVATE_SYNCWORD = 0x14
)
+20 -48
View File
@@ -6,7 +6,6 @@ package sx127x
import (
"errors"
"machine"
"time"
"tinygo.org/x/drivers"
@@ -21,15 +20,15 @@ const (
// Device wraps an SPI connection to a SX127x device.
type Device struct {
spi drivers.SPI // SPI bus for module communication
rstPin machine.Pin // GPIO for reset
radioEventChan chan lora.RadioEvent // Channel for Receiving events
loraConf lora.Config // Current Lora configuration
controller RadioController // to manage interrupts with the radio
deepSleep bool // Internal Sleep state
deviceType int // sx1272, sx1273, sx1276, sx1279 (defaults sx1276)
spiTxBuf []byte // global Tx buffer to avoid heap allocations in interrupt
spiRxBuf []byte // global Rx buffer to avoid heap allocations in interrupt
spi drivers.SPI // SPI bus for module communication
rstPin drivers.PinOutputFunc // GPIO for reset
radioEventChan chan lora.RadioEvent // Channel for Receiving events
loraConf lora.Config // Current Lora configuration
controller RadioController // to manage interactions with the radio
deepSleep bool // Internal Sleep state
deviceType int // sx1261,sx1262,sx1268 (defaults sx1261)
spiTxBuf []byte // global Tx buffer to avoid heap allocations in interrupt
spiRxBuf []byte // global Rx buffer to avoid heap allocations in interrupt
}
// --------------------------------------------------
@@ -43,10 +42,10 @@ func (d *Device) GetRadioEventChan() chan lora.RadioEvent {
}
// New creates a new SX127x connection. The SPI bus must already be configured.
func New(spi drivers.SPI, rstPin machine.Pin) *Device {
func New(spi drivers.SPI, rstPin drivers.PinOutput) *Device {
k := Device{
spi: spi,
rstPin: rstPin,
rstPin: rstPin.Set,
radioEventChan: make(chan lora.RadioEvent, RADIOEVENTCHAN_SIZE),
spiTxBuf: make([]byte, SPI_BUFFER_SIZE),
spiRxBuf: make([]byte, SPI_BUFFER_SIZE),
@@ -65,11 +64,6 @@ func (d *Device) SetRadioController(rc RadioController) error {
return nil
}
// Specify device type (sx1272, sx1273, sx1276, sx1279)
func (d *Device) SetDeviceType(devType int) {
d.deviceType = devType
}
// Reset re-initialize the sx127x device
func (d *Device) Reset() {
d.rstPin.Low()
@@ -86,11 +80,9 @@ func (d *Device) DetectDevice() bool {
// ReadRegister reads register value
func (d *Device) ReadRegister(reg uint8) uint8 {
if d.controller != nil {
d.controller.SetNss(false)
}
d.controller.SetNss(false)
// Send register
//d.spiTxBuf = []byte{reg & 0x7f}
d.spiTxBuf = d.spiTxBuf[:0]
d.spiTxBuf = append(d.spiTxBuf, byte(reg&0x7f))
d.spi.Tx(d.spiTxBuf, nil)
@@ -98,19 +90,13 @@ func (d *Device) ReadRegister(reg uint8) uint8 {
d.spiRxBuf = d.spiRxBuf[:0]
d.spiRxBuf = append(d.spiRxBuf, 0)
d.spi.Tx(nil, d.spiRxBuf)
if d.controller != nil {
d.controller.SetNss(true)
}
d.controller.SetNss(true)
return d.spiRxBuf[0]
}
// WriteRegister writes value to register
func (d *Device) WriteRegister(reg uint8, value uint8) uint8 {
if d.controller != nil {
d.controller.SetNss(false)
}
d.controller.SetNss(false)
// Send register
d.spiTxBuf = d.spiTxBuf[:0]
d.spiTxBuf = append(d.spiTxBuf, byte(reg|0x80))
@@ -121,10 +107,7 @@ func (d *Device) WriteRegister(reg uint8, value uint8) uint8 {
d.spiRxBuf = d.spiRxBuf[:0]
d.spiRxBuf = append(d.spiRxBuf, 0)
d.spi.Tx(d.spiTxBuf, d.spiRxBuf)
if d.controller != nil {
d.controller.SetNss(true)
}
d.controller.SetNss(true)
return d.spiRxBuf[0]
}
@@ -135,20 +118,9 @@ func (d *Device) SetOpMode(mode uint8) {
d.WriteRegister(SX127X_REG_OP_MODE, new)
}
// SetOpModeLora changes the sx1276 mode to lora.
// SetOpMode changes the sx1276 mode
func (d *Device) SetOpModeLora() {
d.WriteRegister(SX127X_REG_OP_MODE, d.ReadRegister(SX127X_REG_OP_MODE)|SX127X_OPMODE_LORA)
}
// SetOpModeFsk changes the sx1276 mode to fsk/ook.
func (d *Device) SetOpModeFsk() {
d.WriteRegister(SX127X_REG_OP_MODE, d.ReadRegister(SX127X_REG_OP_MODE)&^SX127X_OPMODE_LORA)
}
// SetModulationType changes the modulation type (SX127X_OPMODE_MODULATION_FSK, SX127X_OPMODE_MODULATION_OOK)
func (d *Device) SetModulationType(typ uint8) {
cleared := d.ReadRegister(SX127X_REG_OP_MODE) &^ SX127X_OPMODE_MODULATION_MASK
d.WriteRegister(SX127X_REG_OP_MODE, cleared|typ)
d.WriteRegister(SX127X_REG_OP_MODE, SX127X_OPMODE_LORA)
}
// GetVersion returns hardware version of sx1276 chipset
@@ -271,9 +243,9 @@ func (d *Device) SetLowDataRateOptim(val uint8) {
// SetLowFrequencyModeOn enables Low Data Rate Optimization
func (d *Device) SetLowFrequencyModeOn(val bool) {
if val {
d.WriteRegister(SX127X_REG_OP_MODE, d.ReadRegister(SX127X_REG_OP_MODE)|SX127X_OPMODE_LOW_FREQUENCY)
d.WriteRegister(SX127X_REG_OP_MODE, d.ReadRegister(SX127X_REG_OP_MODE)|0x04)
} else {
d.WriteRegister(SX127X_REG_OP_MODE, d.ReadRegister(SX127X_REG_OP_MODE)&^SX127X_OPMODE_LOW_FREQUENCY)
d.WriteRegister(SX127X_REG_OP_MODE, d.ReadRegister(SX127X_REG_OP_MODE)&0xfb)
}
}
-7
View File
@@ -1,7 +0,0 @@
# SX128x Radio
Radio from Semtech in the 2.4 GHz band. This driver uses SPI to communicate with the radio instead of the alternative UART interface.
## Supported Chips
- [SX1280](https://www.semtech.com/products/wireless-rf/lora-connect/sx1280)
- [SX1281](https://www.semtech.com/products/wireless-rf/lora-connect/sx1281)
-52
View File
@@ -1,52 +0,0 @@
package sx128x
const (
// SX128X SPI commands
cmdGetStatus = uint8(0xC0)
// Register Access Operations
cmdWriteRegister = uint8(0x18)
cmdReadRegister = uint8(0x19)
// Data Buffer Operations
cmdWriteBuffer = uint8(0x1A)
cmdReadBuffer = uint8(0x1B)
// Radio Operation Modes
cmdSetSleep = uint8(0x84)
cmdSetStandby = uint8(0x80)
cmdSetFS = uint8(0xC1)
cmdSetTx = uint8(0x83)
cmdSetRx = uint8(0x82)
cmdSetRxDutyCycle = uint8(0x94)
cmdSetLongPreamble = uint8(0x9B)
cmdSetCAD = uint8(0xC5)
cmdSetTxContinuousWave = uint8(0xD1)
cmdSetContinuousPreamble = uint8(0xD2)
cmdSetAutoTx = uint8(0x98)
cmdSetAutoFS = uint8(0x9E)
// Radio Configuration
cmdSetPacketType = uint8(0x8A)
cmdGetPacketType = uint8(0x03)
cmdSetRFFrequency = uint8(0x86)
cmdSetTxParams = uint8(0x8E)
cmdSetCADParams = uint8(0x88)
cmdSetBufferBaseAddress = uint8(0x8F)
cmdSetModulationParams = uint8(0x8B)
cmdSetPacketParams = uint8(0x8C)
// Communication Status Information
cmdGetRxBufferStatus = uint8(0x17)
cmdGetPacketStatus = uint8(0x1D)
cmdGetRSSIInst = uint8(0x1F)
// IRQ Handling
cmdSetDIOIRQParams = uint8(0x8D)
cmdGetIRQStatus = uint8(0x15)
cmdClearIRQStatus = uint8(0x97)
// Miscellaneous
cmdSetRegulatorMode = uint8(0x96)
cmdSetSaveContext = uint8(0xD5)
)
-356
View File
@@ -1,356 +0,0 @@
package sx128x
type SleepConfig uint8
type StandbyConfig uint8
type PeriodBase uint8
type PacketType uint8
type RadioRampTime uint8
type CadSymbolNum uint8
// GFSK Modulation Params
type GFSKBLEBitrateBandwidth uint8
type ModulationIndex uint8
type ModulationShaping uint8
// GFSK Packet Params
type GFSKPreambleLength uint8
type GFSKSyncWordLength uint8
type GFSKSyncWordMatch uint8
type GFSKHeaderType uint8
type GFSKCrcType uint8
// BLE Packet Params
type BLEConnectionState uint8
type BLECrcType uint8
type BLETestPayload uint8
// FLRC Modulation Params
type FLRCBitrateBandwidth uint8
type FLRCCodingRate uint8
// FLRC Packet Params
type FLRCPreambleLength uint8
type FLRCSyncWordLength uint8
type FLRCSyncWordMatch uint8
type FLRCHeaderType uint8
type FLRCCrcType uint8
// LoRa Modulation Params
type LoRaSpreadingFactor uint8
type LoRaBandwidth uint8
type LoRaCodingRate uint8
// LoRa Packet Params
type LoRaHeaderType uint8
type LoRaCrcType uint8
type LoRaIqType uint8
// Misc
type RegulatorMode uint8
type IRQMask = uint16
type CircuitMode uint8
type CommandStatus uint8
// Packet Status
type GFSKPacketInfo uint8
type BLEPacketInfo uint8
type FLRCPacketInfo uint8
const (
whiteningDisable = 0x00
whiteningEnable = 0x08
// Circuit Mode
circuitModeMask = uint8(0b11100000)
CIRCUIT_MODE_STDBY_RC = CircuitMode(0x2)
CIRCUIT_MODE_STDBY_XOSC = CircuitMode(0x3)
CIRCUIT_MODE_FS = CircuitMode(0x4)
CIRCUIT_MODE_RX = CircuitMode(0x5)
CIRCUIT_MODE_TX = CircuitMode(0x6)
// Command Status
commandStatusMask = uint8(0b00011100)
COMMAND_STATUS_SUCCESS = CommandStatus(0x1)
COMMAND_STATUS_DATA_AVAILABLE = CommandStatus(0x2)
COMMAND_STATUS_TIMEOUT = CommandStatus(0x3)
COMMAND_STATUS_PROCESSING_ERROR = CommandStatus(0x4)
COMMAND_STATUS_EXECUTION_ERROR = CommandStatus(0x5)
COMMAND_STATUS_TX_DONE = CommandStatus(0x6)
// SleepConfig
SLEEP_DATA_BUFFER_RETAIN = SleepConfig(2)
SLEEP_DATA_RAM_RETAIN = SleepConfig(1)
// StandbyConfig
STANDBY_RC = StandbyConfig(0)
STANDBY_XOSC = StandbyConfig(1)
// PeriodBase
PERIOD_BASE_15_625_US = PeriodBase(0)
PERIOD_BASE_62_5_US = PeriodBase(1)
PERIOD_BASE_1_MS = PeriodBase(2)
PERIOD_BASE_4_MS = PeriodBase(3)
// PacketType
PACKET_TYPE_GFSK = PacketType(0x00) // default
PACKET_TYPE_LORA = PacketType(0x01)
PACKET_TYPE_RANGING = PacketType(0x02)
PACKET_TYPE_FLRC = PacketType(0x03)
PACKET_TYPE_BLE = PacketType(0x04)
// RampTime
RADIO_RAMP_02_US = RadioRampTime(0x00)
RADIO_RAMP_04_US = RadioRampTime(0x20)
RADIO_RAMP_06_US = RadioRampTime(0x40)
RADIO_RAMP_08_US = RadioRampTime(0x60)
RADIO_RAMP_10_US = RadioRampTime(0x80)
RADIO_RAMP_12_US = RadioRampTime(0xA0)
RADIO_RAMP_16_US = RadioRampTime(0xC0)
RADIO_RAMP_20_US = RadioRampTime(0xE0)
// CadSymbolNum
LORA_CAD_01_SYMBOL = CadSymbolNum(0x00)
LORA_CAD_02_SYMBOLS = CadSymbolNum(0x20)
LORA_CAD_04_SYMBOLS = CadSymbolNum(0x40)
LORA_CAD_08_SYMBOLS = CadSymbolNum(0x60)
LORA_CAD_16_SYMBOLS = CadSymbolNum(0x80)
// GFSK Modulation Params
// Bitrate + Bandwidth - same for BLE
GFSK_BLE_BR_2_000_BW_2_4 = GFSKBLEBitrateBandwidth(0x04)
GFSK_BLE_BR_1_600_BW_2_4 = GFSKBLEBitrateBandwidth(0x28)
GFSK_BLE_BR_1_000_BW_2_4 = GFSKBLEBitrateBandwidth(0x4C)
GFSK_BLE_BR_1_000_BW_1_2 = GFSKBLEBitrateBandwidth(0x45)
GFSK_BLE_BR_0_800_BW_2_4 = GFSKBLEBitrateBandwidth(0x70)
GFSK_BLE_BR_0_800_BW_1_2 = GFSKBLEBitrateBandwidth(0x69)
GFSK_BLE_BR_0_500_BW_1_2 = GFSKBLEBitrateBandwidth(0x8D)
GFSK_BLE_BR_0_500_BW_0_6 = GFSKBLEBitrateBandwidth(0x86)
GFSK_BLE_BR_0_400_BW_1_2 = GFSKBLEBitrateBandwidth(0xB1)
GFSK_BLE_BR_0_400_BW_0_6 = GFSKBLEBitrateBandwidth(0xAA)
GFSK_BLE_BR_0_250_BW_0_6 = GFSKBLEBitrateBandwidth(0xCE)
GFSK_BLE_BR_0_250_BW_0_3 = GFSKBLEBitrateBandwidth(0xC7)
GFSK_BLE_BR_0_125_BW_0_3 = GFSKBLEBitrateBandwidth(0xEF)
// Modulation Index - same for BLE
MOD_IND_0_35 = ModulationIndex(0x00)
MOD_IND_0_5 = ModulationIndex(0x01)
MOD_IND_0_75 = ModulationIndex(0x02)
MOD_IND_1_00 = ModulationIndex(0x03)
MOD_IND_1_25 = ModulationIndex(0x04)
MOD_IND_1_50 = ModulationIndex(0x05)
MOD_IND_1_75 = ModulationIndex(0x06)
MOD_IND_2_00 = ModulationIndex(0x07)
MOD_IND_2_25 = ModulationIndex(0x08)
MOD_IND_2_50 = ModulationIndex(0x09)
MOD_IND_2_75 = ModulationIndex(0x0A)
MOD_IND_3_00 = ModulationIndex(0x0B)
MOD_IND_3_25 = ModulationIndex(0x0C)
MOD_IND_3_50 = ModulationIndex(0x0D)
MOD_IND_3_75 = ModulationIndex(0x0E)
MOD_IND_4_00 = ModulationIndex(0x0F)
// Modulation Shaping - same for BLE and FLRC
MOD_SHAPING_OFF = ModulationShaping(0x00)
MOD_SHAPING_1_0 = ModulationShaping(0x10)
MOD_SHAPING_0_5 = ModulationShaping(0x20)
// GFSK Packet Params
// Preamble Length
GFSK_PREAMBLE_LENGTH_04_BITS = GFSKPreambleLength(0x00)
GFSK_PREAMBLE_LENGTH_08_BITS = GFSKPreambleLength(0x10)
GFSK_PREAMBLE_LENGTH_12_BITS = GFSKPreambleLength(0x20)
GFSK_PREAMBLE_LENGTH_16_BITS = GFSKPreambleLength(0x30)
GFSK_PREAMBLE_LENGTH_20_BITS = GFSKPreambleLength(0x40)
GFSK_PREAMBLE_LENGTH_24_BITS = GFSKPreambleLength(0x50)
GFSK_PREAMBLE_LENGTH_28_BITS = GFSKPreambleLength(0x60)
GFSK_PREAMBLE_LENGTH_32_BITS = GFSKPreambleLength(0x70)
// Sync Word Length
GFSK_SYNC_WORD_LEN_1_B = GFSKSyncWordLength(0x00)
GFSK_SYNC_WORD_LEN_2_B = GFSKSyncWordLength(0x02)
GFSK_SYNC_WORD_LEN_3_B = GFSKSyncWordLength(0x04)
GFSK_SYNC_WORD_LEN_4_B = GFSKSyncWordLength(0x06)
GFSK_SYNC_WORD_LEN_5_B = GFSKSyncWordLength(0x08)
// Sync Word Match
GFSK_SYNCWORD_MATCH_OFF = GFSKSyncWordMatch(0x00)
GFSK_SYNCWORD_MATCH_1 = GFSKSyncWordMatch(0x10)
GFSK_SYNCWORD_MATCH_2 = GFSKSyncWordMatch(0x20)
GFSK_SYNCWORD_MATCH_1_2 = GFSKSyncWordMatch(0x30)
GFSK_SYNCWORD_MATCH_3 = GFSKSyncWordMatch(0x40)
GFSK_SYNCWORD_MATCH_1_3 = GFSKSyncWordMatch(0x50)
GFSK_SYNCWORD_MATCH_2_3 = GFSKSyncWordMatch(0x60)
GFSK_SYNCWORD_MATCH_1_2_3 = GFSKSyncWordMatch(0x70)
// GFSK Header Type
GFSK_HEADER_FIXED_LENGTH = GFSKHeaderType(0x00)
GFSK_HEADER_VARIABLE_LENGTH = GFSKHeaderType(0x20)
// GFSK CRC Type
GFSK_CRC_OFF = GFSKCrcType(0x00)
GFSK_CRC_1_BYTE = GFSKCrcType(0x10)
GFSK_CRC_2_BYTES = GFSKCrcType(0x20)
// BLE Packet Params
// Connection State
BLE_MASTER_SLAVE = BLEConnectionState(0x00)
BLE_ADVERTISER = BLEConnectionState(0x02)
BLE_TX_TEST_MODE = BLEConnectionState(0x04)
BLE_RX_TEST_MODE = BLEConnectionState(0x06)
BLE_RXTX_TEST_MODE = BLEConnectionState(0x08)
// CRC Type
BLE_CRC_OFF = BLECrcType(0x00)
BLE_CRC_3_BYTES = BLECrcType(0x10)
// BLE Test Payload
BLE_PAYLOAD_PRBS_9 = BLETestPayload(0x00)
BLE_PAYLOAD_EYELONG_1_0 = BLETestPayload(0x04)
BLE_PAYLOAD_EYESHORT_1_0 = BLETestPayload(0x08)
BLE_PAYLOAD_PRBS_15 = BLETestPayload(0x0C)
BLE_PAYLOAD_ALL_1 = BLETestPayload(0x10)
BLE_PAYLOAD_ALL_0 = BLETestPayload(0x14)
BLE_PAYLOAD_EYELONG_0_1 = BLETestPayload(0x18)
BLE_PAYLOAD_EYESHORT_0_1 = BLETestPayload(0x1C)
// FLRC Modulation Params
// Bitrate + Bandwidth
FLRC_BR_1_300_BW_1_2 = FLRCBitrateBandwidth(0x45)
FLRC_BR_1_000_BW_1_2 = FLRCBitrateBandwidth(0x69)
FLRC_BR_0_650_BW_0_6 = FLRCBitrateBandwidth(0x86)
FLRC_BR_0_520_BW_0_6 = FLRCBitrateBandwidth(0xAA)
FLRC_BR_0_325_BW_0_3 = FLRCBitrateBandwidth(0xC7)
FLRC_BR_0_260_BW_0_3 = FLRCBitrateBandwidth(0xEB)
// Coding Rate
FLRC_CR_1_2 = FLRCCodingRate(0x00) // 1/2
FLRC_CR_3_4 = FLRCCodingRate(0x02) // 3/4
FLRC_CR_1_0 = FLRCCodingRate(0x04) // 1
// FLRC Packet Params
// Preamble Length
FLRC_PREAMBLE_LENGTH_4_BITS = FLRCPreambleLength(0x00)
FLRC_PREAMBLE_LENGTH_8_BITS = FLRCPreambleLength(0x10)
FLRC_PREAMBLE_LENGTH_12_BITS = FLRCPreambleLength(0x20)
FLRC_PREAMBLE_LENGTH_16_BITS = FLRCPreambleLength(0x30)
FLRC_PREAMBLE_LENGTH_20_BITS = FLRCPreambleLength(0x40)
FLRC_PREAMBLE_LENGTH_24_BITS = FLRCPreambleLength(0x50)
FLRC_PREAMBLE_LENGTH_28_BITS = FLRCPreambleLength(0x60)
FLRC_PREAMBLE_LENGTH_32_BITS = FLRCPreambleLength(0x70)
// Sync Word Length
FLRC_SYNC_WORD_LEN_0 = FLRCSyncWordLength(0x00)
FLRC_SYNC_WORD_LEN_32_BITS = FLRCSyncWordLength(0x04)
// Sync Word Match
FLRC_SYNC_WORD_MATCH_DISABLE = FLRCSyncWordMatch(0x00) // Disable Sync Word
FLRC_SYNC_WORD_MATCH_1 = FLRCSyncWordMatch(0x10) // Sync Word 1
FLRC_SYNC_WORD_MATCH_2 = FLRCSyncWordMatch(0x20) // Sync Word 2
FLRC_SYNC_WORD_MATCH_1_2 = FLRCSyncWordMatch(0x30) // Sync Word 1 or Sync Word 2
FLRC_SYNC_WORD_MATCH_3 = FLRCSyncWordMatch(0x40) // Sync Word 3
FLRC_SYNC_WORD_MATCH_1_3 = FLRCSyncWordMatch(0x50) // Sync Word 1 or Sync Word 3
FLRC_SYNC_WORD_MATCH_2_3 = FLRCSyncWordMatch(0x60) // Sync Word 2 or Sync Word 3
FLRC_SYNC_WORD_MATCH_1_2_3 = FLRCSyncWordMatch(0x70) // Sync Word 1 or Sync Word 2 or Sync Word 3
// Header Type
FLRC_HEADER_FIXED_LENGTH = FLRCHeaderType(0x00)
FLRC_HEADER_VARIABLE_LENGTH = FLRCHeaderType(0x20)
// CRC Type
FLRC_CRC_OFF = FLRCCrcType(0x00)
FLRC_CRC_1_BYTE = FLRCCrcType(0x10)
FLRC_CRC_2_BYTES = FLRCCrcType(0x20)
FLRC_CRC_3_BYTES = FLRCCrcType(0x30)
// LoRa Modulation Params
// SpreadingFactor
LORA_SF_5 = LoRaSpreadingFactor(0x50)
LORA_SF_6 = LoRaSpreadingFactor(0x60)
LORA_SF_7 = LoRaSpreadingFactor(0x70)
LORA_SF_8 = LoRaSpreadingFactor(0x80)
LORA_SF_9 = LoRaSpreadingFactor(0x90)
LORA_SF_10 = LoRaSpreadingFactor(0xA0)
LORA_SF_11 = LoRaSpreadingFactor(0xB0)
LORA_SF_12 = LoRaSpreadingFactor(0xC0)
// Bandwidth
LORA_BW_1600 = LoRaBandwidth(0x0A)
LORA_BW_800 = LoRaBandwidth(0x18)
LORA_BW_400 = LoRaBandwidth(0x26)
LORA_BW_200 = LoRaBandwidth(0x34)
// CodingRate
LORA_CR_4_5 = LoRaCodingRate(0x01)
LORA_CR_4_6 = LoRaCodingRate(0x02)
LORA_CR_4_7 = LoRaCodingRate(0x03)
LORA_CR_4_8 = LoRaCodingRate(0x04)
LORA_CR_LI_4_5 = LoRaCodingRate(0x05)
LORA_CR_LI_4_6 = LoRaCodingRate(0x06)
LORA_CR_LI_4_8 = LoRaCodingRate(0x07)
// LoraPacketParams
// HeaderType
LORA_HEADER_EXPLICIT = LoRaHeaderType(0x00)
LORA_HEADER_IMPLICIT = LoRaHeaderType(0x80)
// CRC Type
LORA_CRC_ENABLE = LoRaCrcType(0x20)
LORA_CRC_DISABLE = LoRaCrcType(0x00)
// IQ Type
LORA_IQ_INVERTED = LoRaIqType(0x00)
LORA_IQ_STD = LoRaIqType(0x40)
// RegulatorMode
REGULATOR_LDO = RegulatorMode(0)
REGULATOR_DC_DC = RegulatorMode(1)
// IRQ masks
IRQ_ALL_MASK = IRQMask(0xFFFF)
IRQ_NONE_MASK = IRQMask(0x0000)
IRQ_TX_DONE_MASK = IRQMask(0b0000000000000001)
IRQ_RX_DONE_MASK = IRQMask(0b0000000000000010)
IRQ_SYNC_WORD_VALID_MASK = IRQMask(0b0000000000000100)
IRQ_SYNC_WORD_ERROR_MASK = IRQMask(0b0000000000001000)
IRQ_HEADER_VALID_MASK = IRQMask(0b0000000000010000)
IRQ_HEADER_ERROR_MASK = IRQMask(0b0000000000100000)
IRQ_CRC_ERROR_MASK = IRQMask(0b0000000001000000)
IRQ_RANGING_SLAVE_RESPONSE_DONE_MASK = IRQMask(0b0000000010000000)
IRQ_RANGING_SLAVE_RESPONSE_DISCARD_MASK = IRQMask(0b0000000100000000)
IRQ_RANGING_MASTER_RESULT_VALID_MASK = IRQMask(0b0000001000000000)
IRQ_RANGING_MASTER_TIMEOUT_MASK = IRQMask(0b0000010000000000)
IRQ_RANGING_SLAVE_REQUEST_VALID_MASK = IRQMask(0b0000100000000000)
IRQ_CAD_DONE_MASK = IRQMask(0b0001000000000000)
IRQ_CAD_DETECTED_MASK = IRQMask(0b0010000000000000)
IRQ_RX_TX_TIMEOUT_MASK = IRQMask(0b0100000000000000)
IRQ_PREAMBLE_DETECTED_MASK = IRQMask(0b1000000000000000)
IRQ_ADVANCED_RANGING_DONE_MASK = IRQMask(0b1000000000000000)
// GFSK Packet Info
GFSK_SYNC_ERROR = GFSKPacketInfo(0b1000000)
GFSK_LENGTH_ERROR = GFSKPacketInfo(0b0100000)
GFSK_CRC_ERROR = GFSKPacketInfo(0b0010000)
GFSK_ABORT_ERROR = GFSKPacketInfo(0b0001000)
GFSK_HEADER_RECEIVED = GFSKPacketInfo(0b0000100)
GFSK_PACKET_RECEIVED = GFSKPacketInfo(0b0000010)
GFSK_PACKET_CRTL_BUSY = GFSKPacketInfo(0b0000001)
// BLE Packet Info
BLE_SYNC_ERROR = BLEPacketInfo(0b1000000)
BLE_LENGTH_ERROR = BLEPacketInfo(0b0100000)
BLE_CRC_ERROR = BLEPacketInfo(0b0010000)
BLE_ABORT_ERROR = BLEPacketInfo(0b0001000)
BLE_HEADER_RECEIVED = BLEPacketInfo(0b0000100)
BLE_PACKET_RECEIVED = BLEPacketInfo(0b0000010)
BLE_PACKET_CRTL_BUSY = BLEPacketInfo(0b0000001)
// FLRC Packet Info
FLRC_SYNC_ERROR = FLRCPacketInfo(0b1000000)
FLRC_LENGTH_ERROR = FLRCPacketInfo(0b0100000)
FLRC_CRC_ERROR = FLRCPacketInfo(0b0010000)
FLRC_ABORT_ERROR = FLRCPacketInfo(0b0001000)
FLRC_HEADER_RECEIVED = FLRCPacketInfo(0b0000100)
FLRC_PACKET_RECEIVED = FLRCPacketInfo(0b0000010)
FLRC_PACKET_CRTL_BUSY = FLRCPacketInfo(0b0000001)
)
-19
View File
@@ -1,19 +0,0 @@
package sx128x
import "errors"
var (
ErrBusyPinTimeout = errors.New("busy pin timeout")
errDataTooLong = errors.New("data over 256 bytes")
errInvalidSleepConfig = errors.New("invalid sleep config")
errInvalidStandbyConfig = errors.New("invalid standby config")
errFrequencyTooLow = errors.New("frequency below 2.4Ghz")
errFrequencyTooHigh = errors.New("frequency above 2.5Ghz")
errPowerTooLow = errors.New("power level below -18dBm")
errPowerTooHigh = errors.New("power level above 13dBm")
errInvalidPeriodBase = errors.New("invalid period base")
errInvalidPacketType = errors.New("invalid packet type")
errInvalidRegulatorMode = errors.New("invalid regulator mode")
errPayloadLengthTooShort = errors.New("payload length too short")
errPayloadLengthTooLong = errors.New("payload length too long")
)
-69
View File
@@ -1,69 +0,0 @@
package sx128x
const (
// SX128X register map
REG_FIRMWARE_VERSIONS = uint16(0x153)
REG_RX_GAIN = uint16(0x891)
REG_MANUAL_GAIN_SETTING = uint16(0x895)
REG_LNA_GAIN_VALUE = uint16(0x89E)
REG_LNA_GAIN_CONTROL = uint16(0x89F)
REG_SYNCH_PEAK_ATTENUATION = uint16(0x8C2)
REG_PAYLOAD_LENGTH = uint16(0x901)
REG_LORA_HEADER_MODE = uint16(0x903)
REG_RANGING_REQUEST_ADDRESS_BYTE_3 = uint16(0x912)
REG_RANGING_REQUEST_ADDRESS_BYTE_2 = uint16(0x913)
REG_RANGING_REQUEST_ADDRESS_BYTE_1 = uint16(0x914)
REG_RANGING_REQUEST_ADDRESS_BYTE_0 = uint16(0x915)
REG_RANGING_DEVICE_ADDRESS_BYTE_3 = uint16(0x916)
REG_RANGING_DEVICE_ADDRESS_BYTE_2 = uint16(0x917)
REG_RANGING_DEVICE_ADDRESS_BYTE_1 = uint16(0x918)
REG_RANGING_DEVICE_ADDRESS_BYTE_0 = uint16(0x919)
REG_RANGING_FILTER_WINDOW_SIZE = uint16(0x91E)
REG_RESET_RANGING_FILTER = uint16(0x923)
REG_RANGING_RESULT_MUX = uint16(0x924)
REG_SF_ADDITIONAL_CONFIGURATION = uint16(0x925)
REG_RANGING_CALIBRATION_BYTE_2 = uint16(0x92B)
REG_RANGING_CALIBRATION_BYTE_1 = uint16(0x92C)
REG_RANGING_CALIBRATION_BYTE_0 = uint16(0x92D)
REG_RANGING_ID_CHECK_LENGTH = uint16(0x931)
REG_FREQUENCY_ERROR_CORRECTION = uint16(0x93C)
REG_CAD_DETECT_PEAK = uint16(0x942)
REG_LORA_SYNC_WORD_MSB = uint16(0x944)
REG_LORA_SYNC_WORD_LSB = uint16(0x945)
REG_HEADER_CRC = uint16(0x954)
REG_CODING_RATE = uint16(0x950)
REG_FEI_BYTE_2 = uint16(0x954)
REG_FEI_BYTE_1 = uint16(0x955)
REG_FEI_BYTE_0 = uint16(0x956)
REG_RANGING_RESULT_BYTE_2 = uint16(0x961)
REG_RANGING_RESULT_BYTE_1 = uint16(0x962)
REG_RANGING_RESULT_BYTE_0 = uint16(0x963)
REG_RANGING_RSSI = uint16(0x964)
REG_FREEZE_RANGING_RESULT = uint16(0x97F)
REG_PACKET_PREAMBLE_SETTINGS = uint16(0x9C1)
REG_WHITENING_INITIAL_VALUE = uint16(0x9C5)
REG_CRC_POLYNOMIAL_DEFINITION_MSB = uint16(0x9C6)
REG_CRC_POLYNOMIAL_DEFINITION_LSB = uint16(0x9C7)
REG_CRC_POLYNOMIAL_SEED_BYTE_2 = uint16(0x9C7)
REG_CRC_POLYNOMIAL_SEED_BYTE_1 = uint16(0x9C8)
REG_CRC_POLYNOMIAL_SEED_BYTE_0 = uint16(0x9C9)
REG_CRC_MSB_INITIAL_VALUE = uint16(0x9C8)
REG_CRC_LSB_INITIAL_VALUE = uint16(0x9C9)
REG_SYNC_ADDRESS_CONTROL = uint16(0x9CD)
REG_SYNC_ADDRESS_1_BYTE_4 = uint16(0x9CE)
REG_SYNC_ADDRESS_1_BYTE_3 = uint16(0x9CF)
REG_SYNC_ADDRESS_1_BYTE_2 = uint16(0x9D0)
REG_SYNC_ADDRESS_1_BYTE_1 = uint16(0x9D1)
REG_SYNC_ADDRESS_1_BYTE_0 = uint16(0x9D2)
REG_SYNC_ADDRESS_2_BYTE_4 = uint16(0x9D3)
REG_SYNC_ADDRESS_2_BYTE_3 = uint16(0x9D4)
REG_SYNC_ADDRESS_2_BYTE_2 = uint16(0x9D5)
REG_SYNC_ADDRESS_2_BYTE_1 = uint16(0x9D6)
REG_SYNC_ADDRESS_2_BYTE_0 = uint16(0x9D7)
REG_SYNC_ADDRESS_3_BYTE_4 = uint16(0x9D8)
REG_SYNC_ADDRESS_3_BYTE_3 = uint16(0x9D9)
REG_SYNC_ADDRESS_3_BYTE_2 = uint16(0x9DA)
REG_SYNC_ADDRESS_3_BYTE_1 = uint16(0x9DB)
REG_SYNC_ADDRESS_3_BYTE_0 = uint16(0x9DC)
)

Some files were not shown because too many files have changed in this diff Show More