Compare commits

..

3 Commits

Author SHA1 Message Date
Patricio Whittingslow ec6385c15a add example usage 2025-11-09 12:35:11 -03:00
Patricio Whittingslow 7267dc1014 change method name 2025-11-09 12:26:53 -03:00
Patricio Whittingslow 84bfe4a928 add regmap.Device8Txer 2025-11-09 12:21:59 -03:00
110 changed files with 1071 additions and 12544 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
+3 -7
View File
@@ -5,10 +5,9 @@ package apa102 // import "tinygo.org/x/drivers/apa102"
import (
"image/color"
"machine"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/legacy"
"tinygo.org/x/drivers/internal/pin"
)
const (
@@ -38,11 +37,8 @@ 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 {
return New(&bbSPI{SCK: sckPin.Set, SDO: sdoPin.Set, Delay: delay, configurePins: func() {
legacy.ConfigurePinOut(sckPin)
legacy.ConfigurePinOut(sdoPin)
}})
func NewSoftwareSPI(sckPin, sdoPin machine.Pin, delay uint32) *Device {
return New(&bbSPI{SCK: sckPin, SDO: sdoPin, Delay: delay})
}
// WriteColors writes the given RGBA color slice out using the APA102 protocol.
+6 -12
View File
@@ -1,9 +1,6 @@
package apa102
import (
"tinygo.org/x/drivers/internal/legacy"
"tinygo.org/x/drivers/internal/pin"
)
import "machine"
// bbSPI is a dumb bit-bang implementation of SPI protocol that is hardcoded
// to mode 0 and ignores trying to receive data. Just enough for the APA102.
@@ -11,18 +8,15 @@ 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
Delay uint32
configurePins func()
SCK machine.Pin
SDO machine.Pin
Delay uint32
}
// Configure sets up the SCK and SDO pins as outputs and sets them low
func (s *bbSPI) Configure() {
if s.configurePins == nil {
panic(legacy.ErrConfigBeforeInstantiated)
}
s.configurePins()
s.SCK.Configure(machine.PinConfig{Mode: machine.PinOutput})
s.SDO.Configure(machine.PinConfig{Mode: machine.PinOutput})
s.SCK.Low()
s.SDO.Low()
if s.Delay == 0 {
+24 -31
View File
@@ -1,36 +1,31 @@
package bmi160
import (
"machine"
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/legacy"
"tinygo.org/x/drivers/internal/pin"
)
// DeviceSPI is the SPI interface to a BMI160 accelerometer/gyroscope. There is
// also an I2C interface, but it is not yet supported.
type DeviceSPI struct {
// Chip select pin
csb pin.OutputFunc
CSB machine.Pin
buf [7]byte
// SPI bus (requires chip select to be usable).
bus drivers.SPI
configurePins func()
Bus drivers.SPI
}
// 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 machine.Pin, spi drivers.SPI) *DeviceSPI {
return &DeviceSPI{
csb: csb.Set, // chip select
bus: spi,
configurePins: func() {
legacy.ConfigurePinOut(csb)
},
CSB: csb, // chip select
Bus: spi,
}
}
@@ -38,11 +33,9 @@ func NewSPI(csb pin.Output, spi drivers.SPI) *DeviceSPI {
// configures the BMI160, but it does not configure the SPI interface (it is
// assumed to be up and running).
func (d *DeviceSPI) Configure() error {
if d.configurePins == nil {
return legacy.ErrConfigBeforeInstantiated
}
d.configurePins()
d.csb.High()
d.CSB.Configure(machine.PinConfig{Mode: machine.PinOutput})
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
@@ -93,9 +86,9 @@ func (d *DeviceSPI) ReadTemperature() (temperature int32, err error) {
data[0] = 0x80 | reg_TEMPERATURE_0
data[1] = 0
data[2] = 0
d.csb.Low()
err = d.bus.Tx(data, data)
d.csb.High()
d.CSB.Low()
err = d.Bus.Tx(data, data)
d.CSB.High()
if err != nil {
return
}
@@ -130,9 +123,9 @@ func (d *DeviceSPI) ReadAcceleration() (x int32, y int32, z int32, err error) {
for i := 1; i < len(data); i++ {
data[i] = 0
}
d.csb.Low()
err = d.bus.Tx(data, data)
d.csb.High()
d.CSB.Low()
err = d.Bus.Tx(data, data)
d.CSB.High()
if err != nil {
return
}
@@ -160,9 +153,9 @@ func (d *DeviceSPI) ReadRotation() (x int32, y int32, z int32, err error) {
for i := 1; i < len(data); i++ {
data[i] = 0
}
d.csb.Low()
err = d.bus.Tx(data, data)
d.csb.High()
d.CSB.Low()
err = d.Bus.Tx(data, data)
d.CSB.High()
if err != nil {
return
}
@@ -208,9 +201,9 @@ func (d *DeviceSPI) readRegister(address uint8) uint8 {
data := d.buf[:2]
data[0] = 0x80 | address
data[1] = 0
d.csb.Low()
d.bus.Tx(data, data)
d.csb.High()
d.CSB.Low()
d.Bus.Tx(data, data)
d.CSB.High()
return data[1]
}
@@ -224,7 +217,7 @@ func (d *DeviceSPI) writeRegister(address, data uint8) {
buf[0] = address
buf[1] = data
d.csb.Low()
d.bus.Tx(buf, buf)
d.csb.High()
d.CSB.Low()
d.Bus.Tx(buf, buf)
d.CSB.High()
}
-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
}
+7 -7
View File
@@ -2,22 +2,22 @@
package buzzer // import "tinygo.org/x/drivers/buzzer"
import (
"time"
"machine"
"tinygo.org/x/drivers/internal/pin"
"time"
)
// Device wraps a GPIO connection to a buzzer.
type Device struct {
pin pin.OutputFunc
pin machine.Pin
High bool
BPM float64
}
// New returns a new buzzer driver given which pin to use
func New(pin pin.Output) Device {
func New(pin machine.Pin) Device {
return Device{
pin: pin.Set,
pin: pin,
High: false,
BPM: 96.0,
}
@@ -25,14 +25,14 @@ func New(pin pin.Output) Device {
// On sets the buzzer to a high state.
func (l *Device) On() (err error) {
l.pin.High()
l.pin.Set(true)
l.High = true
return
}
// Off sets the buzzer to a low state.
func (l *Device) Off() (err error) {
l.pin.Low()
l.pin.Set(false)
l.High = false
return
}
+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
)
-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)
}
+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())
-111
View File
@@ -1,111 +0,0 @@
package main
import (
"fmt"
"machine"
"time"
"tinygo.org/x/drivers/sd"
)
const (
SPI_RX_PIN = machine.GP16
SPI_TX_PIN = machine.GP19
SPI_SCK_PIN = machine.GP18
SPI_CS_PIN = machine.GP15
)
var (
spibus = machine.SPI0
spicfg = machine.SPIConfig{
Frequency: 250000,
Mode: 0,
SCK: SPI_SCK_PIN,
SDO: SPI_TX_PIN,
SDI: SPI_RX_PIN,
}
)
func main() {
time.Sleep(time.Second)
SPI_CS_PIN.Configure(machine.PinConfig{Mode: machine.PinOutput})
err := spibus.Configure(spicfg)
if err != nil {
panic(err.Error())
}
sdcard := sd.NewSPICard(spibus, SPI_CS_PIN.Set)
println("start init")
err = sdcard.Init()
if err != nil {
panic("sd card init:" + err.Error())
}
// After initialization it's safe to increase SPI clock speed.
csd := sdcard.CSD()
kbps := csd.TransferSpeed().RateKilobits()
spicfg.Frequency = uint32(kbps * 1000)
err = spibus.Configure(spicfg)
cid := sdcard.CID()
fmt.Printf("name=%s\ncsd=\n%s\n", cid.ProductName(), csd.String())
bd, err := sd.NewBlockDevice(sdcard, csd.ReadBlockLen(), csd.NumberOfBlocks())
if err != nil {
panic("block device creation:" + err.Error())
}
var mc MemChecker
ok, badBlkIdx, err := mc.MemCheck(bd, 2, 100)
if err != nil {
panic("memcheck:" + err.Error())
}
if !ok {
println("bad block", badBlkIdx)
} else {
println("memcheck ok")
}
}
type MemChecker struct {
rdBuf []byte
storeBuf []byte
wrBuf []byte
}
func (mc *MemChecker) MemCheck(bd *sd.BlockDevice, blockIdx, numBlocks int64) (memOK bool, badBlockIdx int64, err error) {
size := bd.BlockSize() * numBlocks
if len(mc.rdBuf) < int(size) {
mc.rdBuf = make([]byte, size)
mc.wrBuf = make([]byte, size)
mc.storeBuf = make([]byte, size)
for i := range mc.wrBuf {
mc.wrBuf[i] = byte(i)
}
}
// Start by storing the original block contents.
_, err = bd.ReadAt(mc.storeBuf, blockIdx)
if err != nil {
return false, blockIdx, err
}
// Write the test pattern.
_, err = bd.WriteAt(mc.wrBuf, blockIdx)
if err != nil {
return false, blockIdx, err
}
// Read back the test pattern.
_, err = bd.ReadAt(mc.rdBuf, blockIdx)
if err != nil {
return false, blockIdx, err
}
for j := 0; j < len(mc.rdBuf); j++ {
// Compare the read back data with the test pattern.
if mc.rdBuf[j] != mc.wrBuf[j] {
badBlock := blockIdx + int64(j)/bd.BlockSize()
return false, badBlock, nil
}
mc.rdBuf[j] = 0
}
// Leave the card in it's previous state.
_, err = bd.WriteAt(mc.storeBuf, blockIdx)
return true, -1, nil
}
-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
}
-3
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.
//
+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)
}
}
+5 -5
View File
@@ -210,7 +210,7 @@ func (d *Device) SendCommand(command byte) {
d.bus.SetCommandMode(true)
d.bus.Write([]byte{command})
for d.isBusy(command == DISPLAY_CLEAR || command == CURSOR_HOME) {
for d.busy(command == DISPLAY_CLEAR || command == CURSOR_HOME) {
}
}
@@ -219,7 +219,7 @@ func (d *Device) sendData(data byte) {
d.bus.SetCommandMode(false)
d.bus.Write([]byte{data})
for d.isBusy(false) {
for d.busy(false) {
}
}
@@ -231,9 +231,9 @@ func (d *Device) CreateCharacter(cgramAddr uint8, data []byte) {
}
}
// isBusy returns true when hd447890 is isBusy
// busy returns true when hd447890 is busy
// or after the timeout specified
func (d *Device) isBusy(longDelay bool) bool {
func (d *Device) busy(longDelay bool) bool {
if d.bus.WriteOnly() {
// Can't read busy flag if write only, so sleep a bit then return
if longDelay {
@@ -261,7 +261,7 @@ func (d *Device) isBusy(longDelay bool) bool {
// Busy returns true when hd447890 is busy
func (d *Device) Busy() bool {
return d.isBusy(false)
return d.busy(false)
}
// Size returns the current size of the display.
+196
View File
@@ -0,0 +1,196 @@
// Package regmap provides transaction-based interfaces for reading and writing
// to device registers over I2C and SPI buses with pre-allocated buffers.
package regmap
import (
"errors"
"tinygo.org/x/drivers"
)
var (
// errNotInTx indicates an operation was attempted outside of an active transaction.
errNotInTx = errors.New("device not in Tx")
// errInTx indicates a transaction was started while another is still active.
errInTx = errors.New("device already in Tx")
// errShortWriteBuffer indicates the write buffer is too small for the requested operation.
errShortWriteBuffer = errors.New("device write buffer too short")
// errShortReadBuffer indicates the read buffer is too small for the requested operation.
errShortReadBuffer = errors.New("device read buffer too short")
)
// Device8Txer wraps a Device8 to provide buffered transaction support for
// I2C and SPI operations. It maintains pre-allocated buffers to avoid heap
// allocations during register access operations.
//
// Users must call SetBuffers to configure the write and read buffers before
// initiating transactions.
type Device8Txer struct {
Device8
writeBuf []byte // Pre-allocated buffer for write operations
readBuf []byte // Pre-allocated buffer for read operations
inTx bool // Tracks whether a transaction is currently active
}
// SetTxBuffers configures the write and read buffers for this device.
// These buffers are reused across transactions to avoid heap allocations.
//
// The writebuf should be large enough to hold the register address plus
// all data bytes to be written in a single transaction.
func (d *Device8Txer) SetTxBuffers(writebuf, readbuf []byte) {
d.readBuf = readbuf
d.writeBuf = writebuf
}
// Tx8 represents an active transaction for an 8-bit register device.
// It tracks the write buffer and current offset as data is added to the transaction.
//
// Use AddWriteByte or AddWriteData to add data to the transaction, then call
// DoTxI2C or DoTxSPI to execute the transaction over the bus.
type Tx8 struct {
dw *Device8Txer // Reference to the parent device
off int // Current offset in the write buffer
}
// Tx initiates a new transaction for writing to the specified register address.
//
// Parameters:
// - writeAddr: The 8-bit register address to write to
//
// Returns a Tx8 handle that can be used to add data and execute the transaction.
//
// Returns an error if:
// - A transaction is already active (errInTx)
// - The write buffer is too short (errShortWriteBuffer)
func (dw *Device8Txer) Tx(writeAddr uint8) (Tx8, error) {
if dw.inTx {
return Tx8{}, errInTx
} else if len(dw.writeBuf) < 1 {
return Tx8{}, errShortWriteBuffer
}
dw.writeBuf[0] = writeAddr
return Tx8{dw: dw, off: 1}, nil
}
// AddWriteData appends multiple bytes to the current transaction's write buffer.
//
// Parameters:
// - buf: Variable number of bytes to add to the transaction
//
// Returns an error if:
// - No transaction is active (errNotInTx)
// - The write buffer doesn't have enough space (errShortWriteBuffer)
func (tx *Tx8) AddWriteData(buf ...byte) error {
if !tx.dw.inTx {
return errNotInTx
}
avail := tx.dw.writeBuf[tx.off:]
if len(avail) < len(buf) {
return errShortWriteBuffer
}
n := copy(avail, buf)
tx.off += n
return nil
}
// AddWriteByte appends a single byte to the current transaction's write buffer.
//
// Parameters:
// - b: The byte to add to the transaction
//
// Returns an error if:
// - No transaction is active (errNotInTx)
// - The write buffer doesn't have enough space (errShortWriteBuffer)
func (tx *Tx8) AddWriteByte(b byte) error {
if !tx.dw.inTx {
return errNotInTx
}
avail := tx.dw.writeBuf[tx.off:]
if len(avail) < 1 {
return errShortWriteBuffer
}
avail[0] = b
tx.off++
return nil
}
// DoTxI2C executes the transaction over an I2C bus.
//
// This performs a combined write-read I2C transaction, first sending the
// register address and any data added to the transaction, then reading
// the specified number of bytes from the device.
//
// Parameters:
// - bus: The I2C bus to communicate over
// - deviceAddr: The I2C address of the target device
// - readLength: Number of bytes to read from the device
//
// Returns the read data as a slice of the internal read buffer, valid until
// the next transaction. The transaction is automatically freed after execution.
//
// Returns an error if:
// - No transaction is active (errNotInTx)
// - The read buffer is too short (errShortReadBuffer)
// - The I2C transaction fails
func (tx *Tx8) DoTxI2C(bus drivers.I2C, deviceAddr uint16, readLength int) ([]byte, error) {
if tx.off == 0 || !tx.dw.inTx {
return nil, errNotInTx
}
defer tx.freeTx()
if len(tx.dw.readBuf) < readLength {
return nil, errShortReadBuffer
}
rbuf := tx.dw.readBuf[:readLength]
err := bus.Tx(deviceAddr, tx.dw.writeBuf[:tx.off], rbuf)
if err != nil {
return nil, err
}
return rbuf, err
}
// DoTxSPI executes the transaction over an SPI bus.
//
// This performs a full-duplex SPI transaction, simultaneously writing the
// register address and data while reading the same number of bytes from the device.
//
// If no read buffer was configured (readBuf is nil), this performs a write-only
// transaction and returns nil without error.
//
// Parameters:
// - bus: The SPI bus to communicate over
//
// Returns the read data as a slice of the internal read buffer (same length as
// the write data), valid until the next transaction. The transaction is
// automatically freed after execution.
//
// Returns an error if:
// - No transaction is active (errNotInTx)
// - The read buffer is too short (errShortReadBuffer)
// - The SPI transaction fails
func (tx *Tx8) DoTxSPI(bus drivers.SPI) (readBuf []byte, err error) {
if tx.off == 0 || !tx.dw.inTx {
return nil, errNotInTx
}
defer tx.freeTx()
if tx.dw.readBuf == nil {
err = bus.Tx(tx.dw.writeBuf[:tx.off], nil) // Special case, only use write buffer functionality.
return nil, err
} else if len(readBuf) < tx.off {
return nil, errShortReadBuffer
}
rbuf := tx.dw.readBuf[:tx.off]
err = bus.Tx(tx.dw.writeBuf[:tx.off], rbuf)
if err != nil {
return nil, err
}
return rbuf, err
}
// freeTx marks the transaction as complete, allowing a new transaction to be started.
// This is called internally by DoTxI2C and DoTxSPI after the transaction completes.
func (tx *Tx8) freeTx() {
tx.dw.inTx = false
}
+34
View File
@@ -0,0 +1,34 @@
package regmap
import (
"fmt"
"tinygo.org/x/drivers"
)
func ExampleDevice8Txer() {
// Initialization.
var dtx Device8Txer
dtx.SetTxBuffers(make([]byte, 256), make([]byte, 256))
// Usage.
const (
defaultAddr = 65
REG_WRITE = 0x1f
IOCTL_CALL = 0xc0
)
tx, err := dtx.Tx(REG_WRITE)
if err != nil {
panic(err)
}
err = tx.AddWriteData(IOCTL_CALL, 0x80, 0x80)
if err != nil {
panic(err)
}
var bus drivers.I2C
readData, err := tx.DoTxI2C(bus, defaultAddr, 20)
if err != nil {
panic(err)
}
fmt.Println(readData)
}
+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
)
+4 -4
View File
@@ -3,9 +3,9 @@ package max6675
import (
"errors"
"machine"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/pin"
)
// ErrThermocoupleOpen is returned when the thermocouple input is open.
@@ -14,16 +14,16 @@ var ErrThermocoupleOpen = errors.New("thermocouple input open")
type Device struct {
bus drivers.SPI
cs pin.OutputFunc
cs machine.Pin
}
// 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 machine.Pin) *Device {
return &Device{
bus: bus,
cs: cs.Set,
cs: cs,
}
}
+9 -14
View File
@@ -3,36 +3,31 @@
package max72xx
import (
"machine"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/legacy"
"tinygo.org/x/drivers/internal/pin"
)
type Device struct {
bus drivers.SPI
cs pin.OutputFunc
configurePins func()
bus drivers.SPI
cs machine.Pin
}
// 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 machine.Pin) *Device {
return &Device{
bus: bus,
cs: cs.Set,
configurePins: func() {
legacy.ConfigurePinOut(cs)
},
cs: cs,
}
}
// Configure setups the pins.
func (driver *Device) Configure() {
if driver.configurePins == nil {
panic(legacy.ErrConfigBeforeInstantiated)
}
driver.configurePins()
outPutConfig := machine.PinConfig{Mode: machine.PinOutput}
driver.cs.Configure(outPutConfig)
}
// SetScanLimit sets the scan limit. Maximum is 8.
+49 -61
View File
@@ -6,38 +6,20 @@
package mcp2515 // import "tinygo.org/x/drivers/mcp2515"
import (
"encoding/binary"
"errors"
"fmt"
"machine"
"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
msg *CANMsg
extended bool
mcpMode byte
configurePins func()
}
type Configuration struct {
Extended bool
spi SPI
cs machine.Pin
msg *CANMsg
mcpMode byte
}
// CANMsg stores CAN message fields.
@@ -54,30 +36,23 @@ 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 machine.Pin) *Device {
d := &Device{
spi: SPI{
bus: b,
tx: make([]byte, 0, bufferSize),
rx: make([]byte, 0, bufferSize),
},
cs: csPin.Set,
cs: csPin,
msg: &CANMsg{},
configurePins: func() {
legacy.ConfigurePinOut(csPin)
},
}
return d
}
// Configure sets up the device for communication.
func (d *Device) Configure(cfg Configuration) {
if d.configurePins == nil {
panic(legacy.ErrConfigBeforeInstantiated)
}
d.extended = cfg.Extended
d.configurePins()
func (d *Device) Configure() {
d.cs.Configure(machine.PinConfig{Mode: machine.PinOutput})
}
const beginTimeoutValue int = 10
@@ -134,13 +109,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 +381,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 +441,7 @@ func (d *Device) readMsg() error {
return err
}
} else {
return ErrNothingIsReceived
return fmt.Errorf("readMsg: nothing is received")
}
return nil
@@ -577,22 +548,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 +777,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 +853,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)
}
+8 -8
View File
@@ -6,18 +6,18 @@ package pcd8544 // import "tinygo.org/x/drivers/pcd8544"
import (
"errors"
"image/color"
"machine"
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/pin"
)
// Device wraps an SPI connection.
type Device struct {
bus drivers.SPI
dcPin pin.OutputFunc
rstPin pin.OutputFunc
scePin pin.OutputFunc
dcPin machine.Pin
rstPin machine.Pin
scePin machine.Pin
buffer []byte
width int16
height int16
@@ -30,12 +30,12 @@ type Config struct {
}
// New creates a new PCD8544 connection. The SPI bus must already be configured.
func New(bus drivers.SPI, dcPin, rstPin, scePin pin.Output) *Device {
func New(bus drivers.SPI, dcPin, rstPin, scePin machine.Pin) *Device {
return &Device{
bus: bus,
dcPin: dcPin.Set,
rstPin: rstPin.Set,
scePin: scePin.Set,
dcPin: dcPin,
rstPin: rstPin,
scePin: scePin,
}
}
-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 {
-11
View File
@@ -1,11 +0,0 @@
## `sd` package
File map:
* `blockdevice.go`: Contains logic for creating an `io.WriterAt` and `io.ReaderAt` with the `sd.BlockDevice` concrete type
from the `sd.Card` interface which is intrinsically a blocked reader and writer.
* `spicard.go`: Contains the `sd.SpiCard` driver for controlling an SD card over SPI using the most commonly available circuit boards.
* `responses.go`: Contains a currently unused SD response implementations as per the latest specification.
* `definitions.go`: Contains SD Card specification definitions such as the CSD and CID types as well as encoding/decoding logic, as well as CRC logic.
-231
View File
@@ -1,231 +0,0 @@
package sd
import (
"errors"
"io"
"math/bits"
)
var (
errNegativeOffset = errors.New("sd: negative offset")
)
// Compile time guarantee of interface implementation.
var _ Card = (*SPICard)(nil)
var _ io.ReaderAt = (*BlockDevice)(nil)
var _ io.WriterAt = (*BlockDevice)(nil)
// Card is the interface implemented by SD card drivers such as [SPICard].
// It provides block-aligned I/O over the card's contents. Use [NewBlockDevice]
// to wrap a Card with byte-addressed [io.ReaderAt] and [io.WriterAt] interfaces.
type Card interface {
// WriteBlocks writes the given data to the card, starting at the given block index.
// The data must be a multiple of the block size.
WriteBlocks(data []byte, startBlockIdx int64) (int, error)
// ReadBlocks reads the given number of blocks from the card, starting at the given block index.
// The dst buffer must be a multiple of the block size.
ReadBlocks(dst []byte, startBlockIdx int64) (int, error)
// EraseBlocks erases blocks starting at startBlockIdx to startBlockIdx+numBlocks.
EraseBlocks(startBlock, numBlocks int64) error
}
// NewBlockDevice creates a new [BlockDevice] from a Card. blockSize must be a
// power of 2. For an initialized [SPICard], blockSize is typically the CSD's
// [CSD.ReadBlockLen] and numBlocks is [SPICard.NumberOfBlocks].
func NewBlockDevice(card Card, blockSize int, numBlocks int64) (*BlockDevice, error) {
if card == nil || blockSize <= 0 || numBlocks <= 0 {
return nil, errors.New("invalid argument(s)")
}
blk, err := makeBlockIndexer(blockSize)
if err != nil {
return nil, err
}
bd := &BlockDevice{
card: card,
blockbuf: make([]byte, blockSize),
blk: blk,
numblocks: int64(numBlocks),
}
return bd, nil
}
// BlockDevice implements the tinyfs.BlockDevice interface for a [Card],
// providing byte-addressed reads and writes at arbitrary offsets by buffering
// non-block-aligned accesses through an internal single-block buffer.
// BlockDevice is not safe for concurrent use.
type BlockDevice struct {
card Card
blockbuf []byte
blk blkIdxer
numblocks int64
}
// ReadAt implements the [io.ReaderAt] interface for an SD card.
// Reads need not be aligned to block boundaries.
func (bd *BlockDevice) ReadAt(p []byte, off int64) (n int, err error) {
if off < 0 {
return 0, errNegativeOffset
}
blockIdx := bd.blk.idx(off)
blockOff := bd.blk.off(off)
if blockOff != 0 {
// Non-aligned first block case.
if _, err = bd.card.ReadBlocks(bd.blockbuf, blockIdx); err != nil {
return n, err
}
n += copy(p, bd.blockbuf[blockOff:])
p = p[n:]
blockIdx++
}
fullBlocksToRead := bd.blk.idx(int64(len(p)))
if fullBlocksToRead > 0 {
// 1 or more full blocks case.
endOffset := fullBlocksToRead * bd.blk.size()
ngot, err := bd.card.ReadBlocks(p[:endOffset], blockIdx)
if err != nil {
return n + ngot, err
}
p = p[endOffset:]
n += ngot
blockIdx += fullBlocksToRead
}
if len(p) > 0 {
// Non-aligned last block case.
if _, err := bd.card.ReadBlocks(bd.blockbuf, blockIdx); err != nil {
return n, err
}
n += copy(p, bd.blockbuf)
}
return n, nil
}
// WriteAt implements the [io.WriterAt] interface for an SD card. Writes need
// not be aligned to block boundaries: partial blocks are read, modified and
// written back.
func (bd *BlockDevice) WriteAt(p []byte, off int64) (n int, err error) {
if off < 0 {
return 0, errNegativeOffset
}
blockIdx := bd.blk.idx(off)
blockOff := bd.blk.off(off)
if blockOff != 0 {
// Non-aligned first block case.
if _, err := bd.card.ReadBlocks(bd.blockbuf, blockIdx); err != nil {
return n, err
}
nexpect := copy(bd.blockbuf[blockOff:], p)
ngot, err := bd.card.WriteBlocks(bd.blockbuf, blockIdx)
if err != nil {
return n, err
} else if ngot != len(bd.blockbuf) {
return n, io.ErrShortWrite
}
n += nexpect
p = p[nexpect:]
blockIdx++
}
fullBlocksToWrite := bd.blk.idx(int64(len(p)))
if fullBlocksToWrite > 0 {
// 1 or more full blocks case.
endOffset := fullBlocksToWrite * bd.blk.size()
ngot, err := bd.card.WriteBlocks(p[:endOffset], blockIdx)
n += ngot
if err != nil {
return n, err
} else if ngot != int(endOffset) {
return n, io.ErrShortWrite
}
p = p[ngot:]
blockIdx += fullBlocksToWrite
}
if len(p) > 0 {
// Non-aligned last block case.
if _, err := bd.card.ReadBlocks(bd.blockbuf, blockIdx); err != nil {
return n, err
}
copy(bd.blockbuf, p)
ngot, err := bd.card.WriteBlocks(bd.blockbuf, blockIdx)
if err != nil {
return n, err
} else if ngot != len(bd.blockbuf) {
return n, io.ErrShortWrite
}
n += len(p)
}
return n, nil
}
// Size returns the number of bytes in this block device.
func (bd *BlockDevice) Size() int64 {
return bd.BlockSize() * bd.numblocks
}
// BlockSize returns the size of a block in bytes.
func (bd *BlockDevice) BlockSize() int64 {
return bd.blk.size()
}
// EraseBlocks erases the given number of blocks. An implementation may
// transparently coalesce ranges of blocks into larger bundles if the chip
// supports this. The start and len parameters are in block numbers, use
// EraseBlockSize to map addresses to blocks.
func (bd *BlockDevice) EraseBlocks(startEraseBlockIdx, len int64) error {
return bd.card.EraseBlocks(startEraseBlockIdx, len)
}
// blkIdxer is a helper for calculating block indices and offsets.
type blkIdxer struct {
blockshift int64
blockmask int64
}
// makeBlockIndexer returns a blkIdxer for the given block size,
// which must be a power of 2.
func makeBlockIndexer(blockSize int) (blkIdxer, error) {
if blockSize <= 0 {
return blkIdxer{}, errNoblocks
}
tz := bits.TrailingZeros(uint(blockSize))
if blockSize>>tz != 1 {
return blkIdxer{}, errors.New("blockSize must be a power of 2")
}
blk := blkIdxer{
blockshift: int64(tz),
blockmask: (1 << tz) - 1,
}
return blk, nil
}
// size returns the size of a block in bytes.
func (blk *blkIdxer) size() int64 {
return 1 << blk.blockshift
}
// off gets the offset of the byte at byteIdx from the start of its block.
//
//go:inline
func (blk *blkIdxer) off(byteIdx int64) int64 {
return blk._moduloBlockSize(byteIdx)
}
// idx gets the block index that contains the byte at byteIdx.
//
//go:inline
func (blk *blkIdxer) idx(byteIdx int64) int64 {
return blk._divideBlockSize(byteIdx)
}
// modulo and divide are defined in terms of bit operations for speed since
// blockSize is a power of 2.
//go:inline
func (blk *blkIdxer) _moduloBlockSize(n int64) int64 { return n & blk.blockmask }
//go:inline
func (blk *blkIdxer) _divideBlockSize(n int64) int64 { return n >> blk.blockshift }
-178
View File
@@ -1,178 +0,0 @@
package sd
import (
"encoding/hex"
"testing"
)
func TestCRC16(t *testing.T) {
tests := []struct {
block string
wantcrc uint16
}{
{
block: "fa33c08ed0bc007c8bf45007501ffbfcbf0006b90001f2a5ea1d060000bebe07b304803c80740e803c00751c83c610fecb75efcd188b148b4c028bee83c610fecb741a803c0074f4be8b06ac3c00740b56bb0700b40ecd105eebf0ebfebf0500bb007cb8010257cd135f730c33c0cd134f75edbea306ebd3bec206bffe7d813d55aa75c78bf5ea007c0000496e76616c696420706172746974696f6e207461626c65004572726f72206c6f6164696e67206f7065726174696e672073797374656d004d697373696e67206f7065726174696e672073797374656d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000094c6dffd0000000401040cfec2ff000800000000f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000055aa",
wantcrc: 0x52ce,
},
}
for _, tt := range tests {
b, err := hex.DecodeString(tt.block)
if err != nil {
t.Fatal(err)
}
gotcrc := CRC16(b)
if gotcrc != tt.wantcrc {
t.Errorf("calculateCRC(%s) = %#x, want %#x", tt.block, gotcrc, tt.wantcrc)
}
}
}
func TestCRC7(t *testing.T) {
const cmdSendMask = 0x40
tests := []struct {
data []byte
wantCRC uint8
}{
{ // See CRC7 Examples from section 4.5 of the SD Card Physical Layer Simplified Specification.
data: []byte{cmdSendMask, 4: 0}, // CMD0, arg=0
wantCRC: 0b1001010,
},
{
data: []byte{cmdSendMask | 17, 4: 0}, // CMD17, arg=0
wantCRC: 0b0101010,
},
{
data: []byte{17, 3: 0b1001, 4: 0}, // Response of CMD17
wantCRC: 0b0110011,
},
{ // CSD for a 8GB card.
data: []byte{64, 14, 0, 50, 83, 89, 0, 0, 60, 1, 127, 128, 10, 64, 0},
wantCRC: 0b1110010,
},
}
for _, tt := range tests {
gotcrc := CRC7(tt.data[:])
if gotcrc != tt.wantCRC {
t.Errorf("got crc=%#b, want=%#b for %#b", gotcrc, tt.wantCRC, tt.data)
}
}
cmdTests := []struct {
cmd command
arg uint32
wantCRC uint8
}{
{
cmd: cmdGoIdleState,
arg: 0,
wantCRC: 0x95,
},
{
cmd: cmdSendIfCond,
arg: 0x1AA,
wantCRC: 0x87,
},
}
var dst [6]byte
for _, test := range cmdTests {
putCmd(dst[:], test.cmd, test.arg)
gotcrc := dst[5]
if gotcrc != test.wantCRC {
t.Errorf("got crc=%#x, want=%#x", gotcrc, test.wantCRC)
}
}
}
// TestCSDv2Capacity checks CSDv2 C_SIZE decoding and the capacity formula.
//
// Field layout and formula are from the SD Physical Layer Simplified
// Specification Version 9.10, section 5.3.3 "CSD Register (CSD Version 2.0)":
// C_SIZE occupies CSD bits [69:48] and user memory capacity is
//
// memory capacity = (C_SIZE+1) * 512KByte (512KByte = 524288 bytes)
//
// Specification download: https://www.sdcard.org/downloads/pls/
//
// Regression test for two past bugs in CSDv2:
// - csize() read byte 7 as data[7]>>2 instead of data[7]&0x3F, dropping
// C_SIZE bits [17:16] (misdecoded cards > 32GiB).
// - DeviceCapacity() computed csize*512000 instead of (csize+1)*524288.
func TestCSDv2Capacity(t *testing.T) {
tests := []struct {
name string
csd []byte
wantCSize uint32
wantCap int64
}{
{
// Same 8GB-card register as in TestCRC7 above, CRC byte appended
// (CRC7=0b1110010 per that test, stored as crc<<1|always1).
// C_SIZE = 0x003C01 = 15361 -> 15362 * 524288 = 8054112256 bytes.
name: "8GB card (in-repo vector)",
csd: []byte{64, 14, 0, 50, 83, 89, 0, 0, 60, 1, 127, 128, 10, 64, 0, 0b1110010<<1 | 1},
wantCSize: 0x003C01,
wantCap: 8054112256,
},
// {
// name: "8GB card",
// csd: csdv2Bytes(0x003C01),
// wantCSize: 0x003FFF,
// wantCap: 2 << 40,
// },
{
// Synthetic: C_SIZE = 0x01FFFF -> 131072 * 524288 = 64GiB.
// Exercises C_SIZE bits [17:16], stored in CSD byte 7 (CSD bits [65:64]).
name: "64GiB synthetic",
csd: csdv2Bytes(0x01FFFF),
wantCSize: 0x01FFFF,
wantCap: 64 << 30,
},
{
// Synthetic: maximum v2 C_SIZE = 0x3FFFFF -> 4194304 * 524288 = 2TiB,
// the SDXC upper bound per section 5.3.3.
name: "2TiB synthetic (max C_SIZE)",
csd: csdv2Bytes(0x3FFFFF),
wantCSize: 0x3FFFFF,
wantCap: 2 << 40,
},
}
for _, tt := range tests {
csd, err := DecodeCSD(tt.csd)
if err != nil {
t.Fatalf("%s: DecodeCSD: %v", tt.name, err)
}
v2 := csd.MustV2()
if got := v2.csize(); got != tt.wantCSize {
t.Errorf("%s: csize() = %#x, want %#x", tt.name, got, tt.wantCSize)
}
if got := csd.DeviceCapacity(); got != tt.wantCap {
t.Errorf("%s: DeviceCapacity() = %d, want %d", tt.name, got, tt.wantCap)
}
wantBlocks := tt.wantCap / int64(csd.ReadBlockLen())
if got := csd.NumberOfBlocks(); got != wantBlocks {
t.Errorf("%s: NumberOfBlocks() = %d, want %d", tt.name, got, wantBlocks)
}
}
}
// csdv2Bytes returns a 16-byte CSD v2 register with csize spliced into
// CSD bits [69:48] and a freshly computed CRC7+always1 last byte.
// Non-capacity fields are copied from the 8GB-card vector above.
func csdv2Bytes(csize uint32) []byte {
b := []byte{64, 14, 0, 50, 83, 89, 0, 0, 60, 1, 127, 128, 10, 64, 0}
b[7] = byte(csize >> 16 & 0x3F)
b[8] = byte(csize >> 8)
b[9] = byte(csize)
return append(b, crc7noshift(b)|1)
}
func putCmd(dst []byte, cmd command, arg uint32) {
dst[0] = byte(cmd) | (1 << 6)
dst[1] = byte(arg >> 24)
dst[2] = byte(arg >> 16)
dst[3] = byte(arg >> 8)
dst[4] = byte(arg)
dst[5] = crc7noshift(dst[:5]) | 1 // Stop bit added.
}
-585
View File
@@ -1,585 +0,0 @@
package sd
import (
"bytes"
"encoding/binary"
"io"
"strconv"
"time"
)
// For reference of CID/CSD structs see:
// See https://github.com/arduino-libraries/SD/blob/1c56f58252553c7537f7baf62798cacc625aa543/src/utility/SdInfo.h#L110
// CardKind classifies an SD card by its capacity class and specification
// version, as discovered during card initialization.
type CardKind uint8
// isTimeout reports whether err is one of the package's timeout errors.
func isTimeout(err error) bool {
return err == errReadTimeout || err == errWriteTimeout || err == errBusyTimeout
}
const (
// card types
TypeSD1 CardKind = 1 // Standard capacity V1 SD card
TypeSD2 CardKind = 2 // Standard capacity V2 SD card
TypeSDHC CardKind = 3 // High Capacity SD card
)
// CID is the Card Identification register, a 128-bit (16-byte) read-only
// register holding the card's identification information: manufacturer,
// product name, serial number and manufacturing date, among other data.
// It is programmed during card manufacture and cannot be changed.
type CID struct {
data [16]byte
}
// DecodeCID decodes a CID from the first 16 bytes of b. It returns an error
// if b is too short or if the CRC7/always-1 fields are invalid.
func DecodeCID(b []byte) (cid CID, _ error) {
if len(b) < 16 {
return CID{}, io.ErrShortBuffer
}
copy(cid.data[:], b)
if !cid.IsValid() {
return cid, errBadCSDCID
}
return cid, nil
}
// RawCopy returns a copy of the raw CID data.
func (c *CID) RawCopy() [16]byte { return c.data }
// ManufacturerID is an 8-bit binary number that identifies the card manufacturer. The MID number is controlled, defined, and allocated to a SD Memory Card manufacturer by the SD-3C, LLC.
func (c *CID) ManufacturerID() uint8 { return c.data[0] }
// OEMApplicationID A 2-character ASCII string that identifies the card OEM and/or the card contents (when used as a
// distribution media either on ROM or FLASH cards). The OID number is controlled, defined, and allocated
// to a SD Memory Card manufacturer by the SD-3C, LLC
func (c *CID) OEMApplicationID() uint16 {
return binary.BigEndian.Uint16(c.data[1:3])
}
// ProductName returns the product name, an ASCII string of up to 5 characters.
func (c *CID) ProductName() string {
return string(upToNull(c.data[3:8]))
}
// ProductRevision is composed of two Binary Coded Decimal (BCD) digits, four bits each, representing
// an "n.m" revision number. The "n" is the most significant nibble and "m" is the least significant nibble.
// As an example, the PRV binary value field for product revision "6.2" will be: 0110 0010b
func (c *CID) ProductRevision() (n, m uint8) {
rev := c.data[8]
return rev >> 4, rev & 0x0F
}
// ProductSerialNumber returns the product serial number, a 32-bit binary number.
func (c *CID) ProductSerialNumber() uint32 {
return binary.BigEndian.Uint32(c.data[9:13])
}
// ManufacturingDate returns the manufacturing date of the card,
// e.g. year=2023, month=4 for April 2023.
func (c *CID) ManufacturingDate() (year uint16, month uint8) {
date := binary.BigEndian.Uint16(c.data[13:15])
return (date >> 4) + 2000, uint8(date & 0x0F)
}
// CRC7 returns the CRC7 checksum for this CID. May be invalid. Use [IsValid] to check validity of CRC7+Always1 fields.
func (c *CID) CRC7() uint8 { return c.data[15] >> 1 }
// Always1 checks the presence of the Always 1 bit. Should return true for valid CIDs.
func (c *CID) Always1() bool { return c.data[15]&1 != 0 }
// IsValid checks if the CRC and always1 fields are expected values.
func (c *CID) IsValid() bool { return c.Always1() && CRC7(c.data[:15]) == c.CRC7() }
// CSD is the Card Specific Data register, a 128-bit (16-byte) register that defines how
// the SD card standard communicates with the memory field or register. This type is
// shared among V1 and V2 type devices.
type CSD struct {
data [16]byte
}
// CSDv1 is the Card Specific Data register for V1 devices. See [CSD] for more info.
type CSDv1 struct {
CSD
}
// CSDv2 is the Card Specific Data register for V2 devices. See [CSD] for more info.
type CSDv2 struct {
CSD
}
// DecodeCSD decodes the CSD from a 16-byte slice.
func DecodeCSD(b []byte) (CSD, error) {
if len(b) < 16 {
return CSD{}, io.ErrShortBuffer
}
csd := CSD{}
copy(csd.data[:], b)
if !csd.IsValid() {
return csd, errBadCSDCID
}
return csd, nil
}
// csdStructure returns the version of the CSD structure.
func (c *CSD) csdStructure() uint8 { return c.data[0] >> 6 }
// Version returns the version of the CSD structure. Effectively returns 1+CSDStructure.
func (c *CSD) Version() uint8 { return 1 + c.csdStructure() }
// MustV1 returns the CSD as a CSDv1. Panics if the CSD is not version 1.0.
func (c CSD) MustV1() CSDv1 {
if c.csdStructure() != 0 {
panic("CSD is not version 1.0")
}
return CSDv1{CSD: c}
}
// MustV2 returns the CSD as a CSDv2. Panics if the CSD is not version 2.0.
func (c CSD) MustV2() CSDv2 {
if c.csdStructure() != 1 {
panic("CSD is not version 2.0")
}
return CSDv2{CSD: c}
}
// RawCopy returns a copy of the raw CSD data.
func (c *CSD) RawCopy() [16]byte { return c.data }
// TAAC returns the Time Access Attribute Class (data read access-time-1).
func (c *CSD) TAAC() TAAC { return TAAC(c.data[1]) }
// NSAC returns the Data Read Access-time 2 in CLK cycles (NSAC*100).
func (c *CSD) NSAC() NSAC { return NSAC(c.data[2]) }
// TransferSpeed returns the Max Data Transfer Rate. Either 0x32 or 0x5A.
func (c *CSD) TransferSpeed() TransferSpeed { return TransferSpeed(c.data[3]) }
// CommandClasses returns the supported Card Command Classes as a bitfield;
// bit position i set means command class i is supported by the card.
func (c *CSD) CommandClasses() CommandClasses {
return CommandClasses(uint16(c.data[4])<<4 | uint16(c.data[5]&0xf0)>>4)
}
// ReadBlockLen returns the Max Read Data Block Length in bytes.
func (c *CSD) ReadBlockLen() int { return 1 << c.ReadBlockLenShift() }
// ReadBlockLenShift returns the base-2 logarithm of [CSD.ReadBlockLen] (READ_BL_LEN field).
func (c *CSD) ReadBlockLenShift() uint8 { return c.data[5] & 0x0F }
// AllowsReadBlockPartial indicates that partial block reads (down to a
// single byte) are allowed. Always true for SD cards.
func (c *CSD) AllowsReadBlockPartial() bool { return c.data[6]&(1<<7) != 0 }
// AllowsWriteBlockMisalignment defines if the data block to be written by one command
// can be spread over more than one physical block of the memory device.
func (c *CSD) AllowsWriteBlockMisalignment() bool { return c.data[6]&(1<<6) != 0 }
// AllowsReadBlockMisalignment defines if the data block to be read by one command
// can be spread over more than one physical block of the memory device.
func (c *CSD) AllowsReadBlockMisalignment() bool { return c.data[6]&(1<<5) != 0 }
// CRC7 returns the CRC read for this CSD. May be invalid. Use [IsValid] to check validity of CRC7+Always1 fields.
func (c *CSD) CRC7() uint8 { return c.data[15] >> 1 }
// Always1 checks the Always 1 bit. Should always evaluate to true for valid CSDs.
func (c *CSD) Always1() bool { return c.data[15]&1 != 0 }
// IsValid checks if the CRC and always1 fields are expected values.
func (c *CSD) IsValid() bool {
// Compare last byte with CRC and also the always1 bit.
return c.Always1() && CRC7(c.data[:15]) == c.CRC7()
}
// ImplementsDSR defines if the configurable driver stage is integrated on the card.
func (c *CSD) ImplementsDSR() bool { return c.data[6]&(1<<4) != 0 }
// EraseSectorSizeInBytes returns how much memory is erased by a single
// erase command, in bytes (SectorSize multiplied by the write block length).
func (c *CSDv1) EraseSectorSizeInBytes() int64 {
blklen := c.WriteBlockLen()
numblocks := c.SectorSize()
return int64(numblocks) * blklen
}
// SectorSize returns the size of an erasable sector in units of write blocks
// (SECTOR_SIZE field, range 1..128). Its meaning varies with the CSD version:
// for V1 it is the erase unit when [CSD.EraseBlockEnabled] is false; for V2
// it is fixed to 64KiB and does not reflect the real erase unit.
func (c *CSD) SectorSize() uint8 {
return 1 + ((c.data[10]&0b11_1111)<<1 | (c.data[11] >> 7))
}
// EraseBlockEnabled defines granularity of unit size of data to be erased.
// If enabled the erase operation can erase either one or multiple units of 512 bytes.
func (c *CSD) EraseBlockEnabled() bool { return (c.data[10]>>6)&1 != 0 }
// ReadToWriteFactor returns the typical write time as a power-of-2 multiple of
// the read access time (R2W_FACTOR field), i.e. writeTime = readTime << factor.
func (c *CSD) ReadToWriteFactor() uint8 { return (c.data[12] >> 2) & 0b111 }
// WriteProtectGroupSizeInSectors indicates the size of a write protected
// group in multiple of erasable sectors.
func (c *CSD) WriteProtectGroupSizeInSectors() uint8 {
return 1 + (c.data[11] & 0b111_1111)
}
// WriteBlockLen represents maximum write data block length in bytes.
func (c *CSD) WriteBlockLen() int64 {
return 1 << ((c.data[12]&0b11)<<2 | (c.data[13] >> 6))
}
// WriteGroupEnabled indicates if write group protection is available.
func (c *CSD) WriteGroupEnabled() bool { return c.data[12]&(1<<7) != 0 }
// AllowsWritePartial Defines whether partial block sizes can be used in write block sizes.
func (c *CSD) AllowsWritePartial() bool { return c.data[13]&(1<<5) != 0 }
// FileFormat returns the file format on the card. This field is read-only for ROM.
func (c *CSD) FileFormat() FileFormat { return FileFormat(c.data[14]>>2) & 0b11 }
// TmpWriteProtected indicates temporary protection over the entire card content from being overwritten or erased.
func (c *CSD) TmpWriteProtected() bool { return c.data[14]&(1<<4) != 0 }
// PermWriteProtected indicates permanent protecttion of entire card content against overwriting or erasing (write+erase permanently disabled).
func (c *CSD) PermWriteProtected() bool { return c.data[14]&(1<<5) != 0 }
// IsCopy whether contents are original or have been copied.
func (c *CSD) IsCopy() bool { return c.data[14]&(1<<6) != 0 }
// FileFormatGroup returns the file format group bit, which selects between
// the two [FileFormat] tables. Interpret together with [CSD.FileFormat].
func (c *CSD) FileFormatGroup() bool { return c.data[14]&(1<<7) != 0 }
// DeviceCapacity returns the total device capacity in bytes, dispatching on
// the CSD version. Returns 0 for unknown CSD versions.
func (c *CSD) DeviceCapacity() (size int64) {
switch c.csdStructure() {
case 0:
v1 := c.MustV1()
size = int64(v1.DeviceCapacity())
case 1:
v2 := c.MustV2()
size = v2.DeviceCapacity()
}
return size
}
// NumberOfBlocks returns amount of readable blocks in the device given by Capacity/ReadBlockLength.
func (c *CSD) NumberOfBlocks() (numBlocks int64) {
rblocks := c.ReadBlockLen()
if rblocks == 0 {
return 0
}
return c.DeviceCapacity() / int64(rblocks)
}
// After byte 5 CSDv1 and CSDv2 differ in structure at some fields.
// DeviceCapacity returns the device capacity in bytes:
// (C_SIZE+1) * 512KiB, as per section 5.3.3 of the SD Simplified Specification.
func (c *CSDv2) DeviceCapacity() int64 {
csize := c.csize()
return (int64(csize) + 1) * (512 * 1024)
}
// csize returns the 22-bit C_SIZE field (CSD bits 69:48).
func (c *CSDv2) csize() uint32 {
return uint32(c.data[7]&0x3F)<<16 | uint32(c.data[8])<<8 | uint32(c.data[9])
}
// DeviceCapacity returns the total memory capacity of the SDCard in bytes. Max is 2GB for V1.
func (c *CSDv1) DeviceCapacity() uint32 {
mult := c.mult()
csize := c.csize()
blklen := c.ReadBlockLen()
blockNR := uint32(csize+1) * uint32(mult)
return blockNR * uint32(blklen)
}
func (c *CSDv1) csize() uint16 {
// Jesus, why did SD make this so complicated?
return uint16(c.data[8]>>6) | uint16(c.data[7])<<2 | uint16(c.data[6]&0b11)<<10
}
// mult is a factor for computing total device size with csize and csizemult.
func (c *CSDv1) mult() uint16 { return 1 << (2 + c.csizemult()) }
func (c *CSDv1) csizemult() uint8 {
return (c.data[9]&0b11)<<1 | (c.data[10] >> 7)
}
// VddReadCurrent indicates min and max values for read power supply currents.
// - values min: 0=0.5mA; 1=1mA; 2=5mA; 3=10mA; 4=25mA; 5=35mA; 6=60mA; 7=100mA
// - values max: 0=1mA; 1=5mA; 2=10mA; 3=25mA; 4=35mA; 5=45mA; 6=80mA; 7=200mA
func (c *CSDv1) VddReadCurrent() (min, max uint8) {
return (c.data[8] >> 3) & 0b111, c.data[8] & 0b111
}
// VddWriteCurrent indicates min and max values for write power supply currents.
// - values min: 0=0.5mA; 1=1mA; 2=5mA; 3=10mA; 4=25mA; 5=35mA; 6=60mA; 7=100mA
// - values max: 0=1mA; 1=5mA; 2=10mA; 3=25mA; 4=35mA; 5=45mA; 6=80mA; 7=200mA
func (c *CSDv1) VddWriteCurrent() (min, max uint8) {
return c.data[9] >> 5, (c.data[9] >> 3) & 0b111
}
// String returns a human-readable multi-line summary of the CSD fields.
func (c *CSD) String() string {
version := c.csdStructure() + 1
if version > 2 {
return "<unsupported CSD version>"
}
const delim = '\n'
buf := make([]byte, 0, 64)
buf = c.appendf(buf, delim)
return string(buf)
}
func (c *CSDv1) String() string { return c.CSD.String() }
func (c *CSDv2) String() string { return c.CSD.String() }
func (c *CSD) appendf(b []byte, delim byte) []byte {
b = appendnum(b, "Version", uint64(c.Version()), delim)
b = appendnum(b, "Capacity(bytes)", uint64(c.DeviceCapacity()), delim)
b = appendnum(b, "TimeAccess_ns", uint64(c.TAAC().AccessTime()), delim)
b = appendnum(b, "NSAC", uint64(c.NSAC()), delim)
b = appendnum(b, "Tx_kb/s", uint64(c.TransferSpeed().RateKilobits()), delim)
b = appendnum(b, "CCC", uint64(c.CommandClasses()), delim)
b = appendnum(b, "ReadBlockLen", uint64(c.ReadBlockLen()), delim)
b = appendbit(b, "ReadBlockPartial", c.AllowsReadBlockPartial(), delim)
b = appendbit(b, "AllowWriteBlockMisalignment", c.AllowsWriteBlockMisalignment(), delim)
b = appendbit(b, "AllowReadBlockMisalignment", c.AllowsReadBlockMisalignment(), delim)
b = appendbit(b, "ImplementsDSR", c.ImplementsDSR(), delim)
b = appendnum(b, "WProtectNumSectors", uint64(c.WriteProtectGroupSizeInSectors()), delim)
b = appendnum(b, "WriteBlockLen", uint64(c.WriteBlockLen()), delim)
b = appendbit(b, "WGrpEnable", c.WriteGroupEnabled(), delim)
b = appendbit(b, "WPartialAllow", c.AllowsWritePartial(), delim)
b = append(b, "FileFmt:"...)
b = append(b, c.FileFormat().String()...)
b = append(b, delim)
b = appendbit(b, "TmpWriteProtect", c.TmpWriteProtected(), delim)
b = appendbit(b, "PermWriteProtect", c.PermWriteProtected(), delim)
b = appendbit(b, "IsCopy", c.IsCopy(), delim)
b = appendbit(b, "FileFormatGrp", c.FileFormatGroup(), delim)
return b
}
func appendnum(b []byte, label string, n uint64, delim byte) []byte {
b = append(b, label...)
b = append(b, ':')
b = strconv.AppendUint(b, n, 10)
b = append(b, delim)
return b
}
func appendbit(b []byte, label string, n bool, delim byte) []byte {
b = append(b, label...)
b = append(b, ':')
b = append(b, '0'+b2u8(n))
b = append(b, delim)
return b
}
func upToNull(buf []byte) []byte {
nullIdx := bytes.IndexByte(buf, 0)
if nullIdx < 0 {
return buf
}
return buf[:nullIdx]
}
type (
command byte
appcommand byte
)
// SD commands and application commands.
const (
cmdGoIdleState command = 0
cmdSendOpCnd command = 1
cmdAllSendCID command = 2
cmdSendRelativeAddr command = 3
cmdSetDSR command = 4
cmdSwitchFunc command = 6
cmdSelectDeselectCard command = 7
cmdSendIfCond command = 8
cmdSendCSD command = 9
cmdSendCID command = 10
cmdStopTransmission command = 12
cmdSendStatus command = 13
cmdGoInactiveState command = 15
cmdSetBlocklen command = 16
cmdReadSingleBlock command = 17
cmdReadMultipleBlock command = 18
cmdWriteBlock command = 24
cmdWriteMultipleBlock command = 25
cmdProgramCSD command = 27
cmdSetWriteProt command = 28
cmdClrWriteProt command = 29
cmdSendWriteProt command = 30
cmdEraseWrBlkStartAddr command = 32
cmdEraseWrBlkEndAddr command = 33
cmdErase command = 38
cmdLockUnlock command = 42
cmdAppCmd command = 55
cmdGenCmd command = 56
cmdReadOCR command = 58
cmdCRCOnOff command = 59
acmdSET_BUS_WIDTH appcommand = 6
acmdSD_STATUS appcommand = 13
acmdSEND_NUM_WR_BLOCKS appcommand = 22
acmdSET_WR_BLK_ERASE_COUNT appcommand = 23
acmdSD_APP_OP_COND appcommand = 41
acmdSET_CLR_CARD_DETECT appcommand = 42
acmdSEND_SCR appcommand = 51
acmdSECURE_READ_MULTI_BLOCK appcommand = 18
acmdSECURE_WRITE_MULTI_BLOCK appcommand = 25
acmdSECURE_WRITE_MKB appcommand = 26
acmdSECURE_ERASE appcommand = 38
acmdGET_MKB appcommand = 43
acmdGET_MID appcommand = 44
acmdSET_CER_RN1 appcommand = 45
acmdSET_CER_RN2 appcommand = 46
acmdSET_CER_RES2 appcommand = 47
acmdSET_CER_RES1 appcommand = 48
acmdCHANGE_SECURE_AREA appcommand = 49
)
// CSD field types.
type (
// TransferSpeed is the TRAN_SPEED CSD field: the maximum data transfer
// rate encoded as a rate unit (lower 3 bits) and time value multiplier.
TransferSpeed uint8
// TAAC is the data read access time CSD field, encoded as a time unit
// (lower 3 bits) and time value multiplier.
TAAC uint8
// FileFormat is the format of the data stored on the card. See the
// FileFmt* constants for possible values.
FileFormat uint8
// CommandClasses is the CCC CSD field, a bitfield where bit position i
// set means command class i is supported.
CommandClasses uint16
// NSAC is the data read access time 2 CSD field, given in units of
// 100 clock cycles.
NSAC uint8
)
const (
FileFmtPartition FileFormat = iota // Hard disk like file system with partition table.
FileFmtDOSFAT // DOS FAT (floppy like)
FileFmtUFF // Universal File Format
FileFmtUnknown
)
// String returns a human-readable name for the file format.
func (ff FileFormat) String() (s string) {
switch ff {
case FileFmtPartition:
s = "partition"
case FileFmtDOSFAT:
s = "DOS/FAT"
case FileFmtUFF:
s = "UFF"
case FileFmtUnknown:
s = "unknown"
default:
s = "<invalid format>"
}
return s
}
var log10table = [...]int64{
1,
10,
100,
1000,
10000,
100000,
1000000,
}
// RateKilobits returns the transfer rate in kilobits per second.
func (t TransferSpeed) RateKilobits() int64 {
return 100 * log10table[t&0b111]
}
// AccessTime returns the asynchronous part of the data access time.
func (t TAAC) AccessTime() (d time.Duration) {
return time.Duration(log10table[t&0b111]) * time.Nanosecond
}
func b2u8(b bool) uint8 {
if b {
return 1
}
return 0
}
// CRC16 computes the CRC16 checksum for a given payload using the CRC-16-CCITT polynomial.
func CRC16(buf []byte) (crc uint16) {
const poly uint16 = 0x1021 // Generator polynomial G(x) = x^16 + x^12 + x^5 + 1
for _, b := range buf {
crc ^= (uint16(b) << 8) // Shift byte into MSB of crc
for i := 0; i < 8; i++ { // Process each bit
if crc&0x8000 != 0 {
crc = (crc << 1) ^ poly
} else {
crc <<= 1
}
}
}
return crc
}
// CRC7 computes the CRC7 checksum for a given payload using the polynomial x^7 + x^3 + 1.
func CRC7(data []byte) (crc uint8) {
return crc7noshift(data) >> 1
}
func crc7noshift(data []byte) (crc uint8) {
for _, b := range data {
crc = crc7_table[crc^b]
}
return crc
}
var crc7_table = [256]byte{
0x00, 0x12, 0x24, 0x36, 0x48, 0x5a, 0x6c, 0x7e,
0x90, 0x82, 0xb4, 0xa6, 0xd8, 0xca, 0xfc, 0xee,
0x32, 0x20, 0x16, 0x04, 0x7a, 0x68, 0x5e, 0x4c,
0xa2, 0xb0, 0x86, 0x94, 0xea, 0xf8, 0xce, 0xdc,
0x64, 0x76, 0x40, 0x52, 0x2c, 0x3e, 0x08, 0x1a,
0xf4, 0xe6, 0xd0, 0xc2, 0xbc, 0xae, 0x98, 0x8a,
0x56, 0x44, 0x72, 0x60, 0x1e, 0x0c, 0x3a, 0x28,
0xc6, 0xd4, 0xe2, 0xf0, 0x8e, 0x9c, 0xaa, 0xb8,
0xc8, 0xda, 0xec, 0xfe, 0x80, 0x92, 0xa4, 0xb6,
0x58, 0x4a, 0x7c, 0x6e, 0x10, 0x02, 0x34, 0x26,
0xfa, 0xe8, 0xde, 0xcc, 0xb2, 0xa0, 0x96, 0x84,
0x6a, 0x78, 0x4e, 0x5c, 0x22, 0x30, 0x06, 0x14,
0xac, 0xbe, 0x88, 0x9a, 0xe4, 0xf6, 0xc0, 0xd2,
0x3c, 0x2e, 0x18, 0x0a, 0x74, 0x66, 0x50, 0x42,
0x9e, 0x8c, 0xba, 0xa8, 0xd6, 0xc4, 0xf2, 0xe0,
0x0e, 0x1c, 0x2a, 0x38, 0x46, 0x54, 0x62, 0x70,
0x82, 0x90, 0xa6, 0xb4, 0xca, 0xd8, 0xee, 0xfc,
0x12, 0x00, 0x36, 0x24, 0x5a, 0x48, 0x7e, 0x6c,
0xb0, 0xa2, 0x94, 0x86, 0xf8, 0xea, 0xdc, 0xce,
0x20, 0x32, 0x04, 0x16, 0x68, 0x7a, 0x4c, 0x5e,
0xe6, 0xf4, 0xc2, 0xd0, 0xae, 0xbc, 0x8a, 0x98,
0x76, 0x64, 0x52, 0x40, 0x3e, 0x2c, 0x1a, 0x08,
0xd4, 0xc6, 0xf0, 0xe2, 0x9c, 0x8e, 0xb8, 0xaa,
0x44, 0x56, 0x60, 0x72, 0x0c, 0x1e, 0x28, 0x3a,
0x4a, 0x58, 0x6e, 0x7c, 0x02, 0x10, 0x26, 0x34,
0xda, 0xc8, 0xfe, 0xec, 0x92, 0x80, 0xb6, 0xa4,
0x78, 0x6a, 0x5c, 0x4e, 0x30, 0x22, 0x14, 0x06,
0xe8, 0xfa, 0xcc, 0xde, 0xa0, 0xb2, 0x84, 0x96,
0x2e, 0x3c, 0x0a, 0x18, 0x66, 0x74, 0x42, 0x50,
0xbe, 0xac, 0x9a, 0x88, 0xf6, 0xe4, 0xd2, 0xc0,
0x1c, 0x0e, 0x38, 0x2a, 0x54, 0x46, 0x70, 0x62,
0x8c, 0x9e, 0xa8, 0xba, 0xc4, 0xd6, 0xe0, 0xf2,
}
-8
View File
@@ -1,8 +0,0 @@
// Package sd implements SD card drivers and the SD card specification:
// CID and CSD register decoding, command definitions and CRC7/CRC16 checksums.
//
// The [SPICard] type drives an SD card over a SPI bus and implements the
// [Card] interface, which exposes block-aligned I/O. [BlockDevice] wraps any
// [Card] with byte-addressed [io.ReaderAt]/[io.WriterAt] implementations
// suitable for filesystem libraries such as tinyfs.
package sd
-325
View File
@@ -1,325 +0,0 @@
package sd
import (
"encoding/binary"
)
const (
_CMD_TIMEOUT = 100
_R1_IDLE_STATE = 1 << 0
_R1_ERASE_RESET = 1 << 1
_R1_ILLEGAL_COMMAND = 1 << 2
_R1_COM_CRC_ERROR = 1 << 3
_R1_ERASE_SEQUENCE_ERROR = 1 << 4
_R1_ADDRESS_ERROR = 1 << 5
_R1_PARAMETER_ERROR = 1 << 6
_DATA_RES_MASK = 0x1F
_DATA_RES_ACCEPTED = 0x05
)
// response1 is the R1 response token returned by the card in SPI mode
// after every command; a bitfield of error and idle-state flags.
type response1 uint8
func (r response1) IsIdle() bool { return r&_R1_IDLE_STATE != 0 }
func (r response1) IllegalCmdError() bool { return r&_R1_ILLEGAL_COMMAND != 0 }
func (r response1) CRCError() bool { return r&_R1_COM_CRC_ERROR != 0 }
func (r response1) EraseReset() bool { return r&_R1_ERASE_RESET != 0 }
func (r response1) EraseSeqError() bool { return r&_R1_ERASE_SEQUENCE_ERROR != 0 }
func (r response1) AddressError() bool { return r&_R1_ADDRESS_ERROR != 0 }
func (r response1) ParamError() bool { return r&_R1_PARAMETER_ERROR != 0 }
// response1Err wraps a non-zero response1 status as an error.
type response1Err struct {
context string
status response1
}
func (e response1Err) Error() string {
if e.context != "" {
return "sd:" + e.context + " " + e.status.Response()
}
return e.status.Response()
}
func (e response1) Response() string {
b := make([]byte, 0, 8)
return string(e.appendf(b))
}
func (r response1) appendf(b []byte) []byte {
b = append(b, '[')
if r.IsIdle() {
b = append(b, "idle,"...)
}
if r.EraseReset() {
b = append(b, "erase-rst,"...)
}
if r.EraseSeqError() {
b = append(b, "erase-seq,"...)
}
if r.CRCError() {
b = append(b, "crc-err,"...)
}
if r.AddressError() {
b = append(b, "addr-err,"...)
}
if r.ParamError() {
b = append(b, "param-err,"...)
}
if r.IllegalCmdError() {
b = append(b, "illegal-cmd,"...)
}
if len(b) > 1 {
b = b[:len(b)-1]
}
b = append(b, ']')
return b
}
func makeResponseError(status response1) error {
return response1Err{
status: status,
}
}
// Commands used to help generate this file:
// - stringer -type=state -trimprefix=state -output=state_string.go
// - stringer -type=status -trimprefix=status -output=status_string.go
// Tokens that are sent by card during polling.
// https://github.com/arduino-libraries/SD/blob/master/src/utility/SdInfo.h
const (
tokSTART_BLOCK = 0xfe
tokSTOP_TRAN = 0xfd
tokWRITE_MULT = 0xfc
)
// state is the card state machine state as encoded in the
// CURRENT_STATE bits of the Card Status register (section 4.10.1).
type state uint8
const (
stateIdle state = iota
stateReady
stateIdent
stateStby
stateTran
stateData
stateRcv
statePrg
stateDis
)
// status represents the Card Status Register (R1), as per section 4.10.1.
type status uint32
func (s status) state() state {
return state(s >> 9 & 0xf)
}
// First status bits.
const (
statusRsvd0 status = iota
statusRsvd1
statusRsvd2
statusAuthSeqError
statusRsvdSDIO
statusAppCmd
statusFXEvent
statusRsvd7
statusReadyForData
)
// Upper bound status bits.
const (
statusEraseReset status = iota + 13
statusECCDisabled
statusWPEraseSkip
statusCSDOverwrite
_
_
statusGenericError
statusControllerError // internal card controller error
statusECCFailed
statusIllegalCommand
statusComCRCError // CRC check of previous command failed
statusLockUnlockFailed
statusCardIsLocked // Signals that the card is locked by the host.
statusWPViolation // Write protected violation
statusEraseParamError // invalid write block selection for erase
statusEraseSeqError // error in erase sequence
statusBlockLenError // tx block length not allowed
statusAddrError // misaligned address
statusAddrOutOfRange // address out of range
)
// r1 is the normal 48-bit response to a command in SD-bus mode,
// as per section 4.9.1. It carries the 32-bit Card Status register.
type r1 struct {
data [48 / 8]byte // 48 bits of response.
}
func (r *r1) RawCopy() [6]byte { return r.data }
func (r *r1) startbit() bool {
return r.data[0]&(1<<7) != 0
}
func (r *r1) txbit() bool {
return r.data[0]&(1<<6) != 0
}
func (r *r1) cmdidx() uint8 {
return r.data[0] & 0b11_1111
}
func (r *r1) cardstatus() status {
return status(binary.BigEndian.Uint32(r.data[1:5]))
}
func (r *r1) CRC7() uint8 { return r.data[5] >> 1 }
func (r *r1) endbit() bool { return r.data[5]&1 != 0 }
func (r *r1) IsValid() bool {
return r.endbit() && CRC7(r.data[:5]) == r.CRC7()
}
// r6 is the 48-bit Published RCA response, as per section 4.9.5. It carries
// the card's new Relative Card Address and a subset of the Card Status bits.
type r6 struct {
data [48 / 8]byte
}
func (r *r6) RawCopy() [6]byte { return r.data }
func (r *r6) startbit() bool {
return r.data[0]&(1<<7) != 0
}
func (r *r6) txbit() bool {
return r.data[0]&(1<<6) != 0
}
func (r *r6) cmdidx() uint8 {
return r.data[0] & 0b11_1111
}
func (r *r6) rca() uint16 {
return binary.BigEndian.Uint16(r.data[1:3])
}
func (r *r6) CardStatus() status {
moveBit := func(b status, from, to uint) status {
return (b & (1 << from)) >> from << to
}
// See 4.9.5 R6 (Published RCA response) of the SD Simplified Specification.
s := status(binary.BigEndian.Uint16(r.data[1:5]))
s = moveBit(s, 13, 19)
s = moveBit(s, 14, 22)
s = moveBit(s, 15, 23)
return s
}
func (r *r6) CRC7() uint8 { return r.data[5] >> 1 }
func (r *r6) endbit() bool { return r.data[5]&1 != 0 }
func (r *r6) IsValid() bool {
return r.endbit() && CRC7(r.data[:5]) == r.CRC7()
}
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[statusRsvd0-0]
_ = x[statusRsvd1-1]
_ = x[statusRsvd2-2]
_ = x[statusAuthSeqError-3]
_ = x[statusRsvdSDIO-4]
_ = x[statusAppCmd-5]
_ = x[statusFXEvent-6]
_ = x[statusRsvd7-7]
_ = x[statusReadyForData-8]
_ = x[statusEraseReset-13]
_ = x[statusECCDisabled-14]
_ = x[statusWPEraseSkip-15]
_ = x[statusCSDOverwrite-16]
_ = x[statusGenericError-19]
_ = x[statusControllerError-20]
_ = x[statusECCFailed-21]
_ = x[statusIllegalCommand-22]
_ = x[statusComCRCError-23]
_ = x[statusLockUnlockFailed-24]
_ = x[statusCardIsLocked-25]
_ = x[statusWPViolation-26]
_ = x[statusEraseParamError-27]
_ = x[statusEraseSeqError-28]
_ = x[statusBlockLenError-29]
_ = x[statusAddrError-30]
_ = x[statusAddrOutOfRange-31]
}
const (
_status_name_0 = "Rsvd0Rsvd1Rsvd2AuthSeqErrorRsvdSDIOAppCmdFXEventRsvd7ReadyForData"
_status_name_1 = "EraseResetECCDisabledWPEraseSkipCSDOverwrite"
_status_name_2 = "GenericErrorControllerErrorECCFailedIllegalCommandComCRCErrorLockUnlockFailedCardIsLockedWPViolationEraseParamErrorEraseSeqErrorBlockLenErrorAddrErrorAddrOutOfRange"
)
var (
_status_index_0 = [...]uint8{0, 5, 10, 15, 27, 35, 41, 48, 53, 65}
_status_index_1 = [...]uint8{0, 10, 21, 32, 44}
_status_index_2 = [...]uint8{0, 12, 27, 36, 50, 61, 77, 89, 100, 115, 128, 141, 150, 164}
)
func (i status) string() string {
switch {
case i <= 8:
return _status_name_0[_status_index_0[i]:_status_index_0[i+1]]
case 13 <= i && i <= 16:
i -= 13
return _status_name_1[_status_index_1[i]:_status_index_1[i+1]]
case 19 <= i && i <= 31:
i -= 19
return _status_name_2[_status_index_2[i]:_status_index_2[i+1]]
default:
return ""
}
}
func (s status) String() string {
return string(s.appendf(nil, ','))
}
func (s status) appendf(b []byte, delim byte) []byte {
b = append(b, s.state().String()...)
b = append(b, '[')
if s == 0 {
return append(b, ']')
}
for bit := 0; bit < 32; bit++ {
if s&(1<<bit) != 0 {
b = append(b, status(bit).string()...)
b = append(b, delim)
}
}
b = append(b[:len(b)-1], ']')
return b
}
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[stateIdle-0]
_ = x[stateReady-1]
_ = x[stateIdent-2]
_ = x[stateStby-3]
_ = x[stateTran-4]
_ = x[stateData-5]
_ = x[stateRcv-6]
_ = x[statePrg-7]
_ = x[stateDis-8]
}
const _state_name = "IdleReadyIdentStbyTranDataRcvPrgDis"
var _state_index = [...]uint8{0, 4, 9, 14, 18, 22, 26, 29, 32, 35}
func (i state) String() string {
if i >= state(len(_state_index)-1) {
return "<reserved state>"
}
return _state_name[_state_index[i]:_state_index[i+1]]
}
-558
View File
@@ -1,558 +0,0 @@
package sd
import (
"encoding/binary"
"errors"
"io"
"math"
"time"
"tinygo.org/x/drivers"
)
var (
errBadCSDCID = errors.New("sd:bad CSD/CID in CRC or always1")
errNoSDCard = errors.New("sd:no card")
errCardNotSupported = errors.New("sd:card not supported")
errWaitStartBlock = errors.New("sd:did not find start block token")
errNeedBlockLenMultiple = errors.New("sd:need blocksize multiple for I/O")
errWrite = errors.New("sd:write")
errWriteTimeout = errors.New("sd:write timeout")
errReadTimeout = errors.New("sd:read timeout")
errBusyTimeout = errors.New("sd:busy card timeout")
errOOB = errors.New("sd:oob block access")
errNoblocks = errors.New("sd:no readable blocks")
)
// digitalPinout sets the logic level of an output pin; true for high, false for low.
type digitalPinout = func(b bool)
// SPICard is a SPI-mode SD card driver. It implements the [Card] interface
// and is initialized with [NewSPICard] followed by a call to [SPICard.Init].
// SPICard is not safe for concurrent use.
type SPICard struct {
bus drivers.SPI
cs digitalPinout
timers [2]timer
timeout time.Duration
wait time.Duration
// Card Identification Register.
cid CID
// Card Specific Register.
csd CSD
bufcmd [6]byte
kind CardKind
// block indexing helper based on block size.
blk blkIdxer
lastCRC uint16
}
// NewSPICard returns a new [SPICard] that communicates over spi using cs as
// the chip select pin. The returned card must be initialized with [SPICard.Init]
// before use.
func NewSPICard(spi drivers.SPI, cs digitalPinout) *SPICard {
const defaultTimeout = 300 * time.Millisecond
s := &SPICard{
bus: spi,
cs: cs,
}
s.setTimeout(defaultTimeout)
return s
}
// setTimeout sets the timeout for all operations and the wait time between each yield during busy spins.
func (c *SPICard) setTimeout(timeout time.Duration) {
if timeout <= 0 {
panic("timeout must be positive")
}
c.timeout = timeout
c.wait = timeout / 512
}
// LastReadCRC returns the CRC for the last ReadBlock operation.
func (c *SPICard) LastReadCRC() uint16 { return c.lastCRC }
// Init initializes the SD card. This routine should be performed with a SPI clock
// speed of around 100..400kHz. One may increase the clock speed after initialization.
func (d *SPICard) Init() error {
return d.initRs()
}
// NumberOfBlocks returns the number of readable and writable blocks on the card,
// as calculated from the CSD read during [SPICard.Init].
func (d *SPICard) NumberOfBlocks() int64 {
return d.csd.NumberOfBlocks()
}
// CID returns a copy of the Card Identification Register value last read.
func (d *SPICard) CID() CID { return d.cid }
// CSD returns a copy of the Card Specific Data Register value last read.
func (d *SPICard) CSD() CSD { return d.csd }
func (d *SPICard) yield() { time.Sleep(d.wait) }
type timer struct {
deadline time.Time
}
func (t *timer) setTimeout(timeout time.Duration) *timer {
t.deadline = time.Now().Add(timeout)
return t
}
func (t timer) expired() bool {
return time.Since(t.deadline) >= 0
}
// Reference for this implementation:
// https://github.com/embassy-rs/embedded-sdmmc-rs/blob/master/src/sdmmc.rs
// Not used currently. We'd want to switch over to one way of doing things, Rust way.
func (d *SPICard) initRs() error {
// Supply minimum of 74 clock cycles with CS high.
d.csEnable(true)
for i := 0; i < 10; i++ {
d.send(0xff)
}
d.csEnable(false)
for i := 0; i < 512; i++ {
d.receive()
}
d.csEnable(true)
defer d.csEnable(false)
// Enter SPI mode
const maxRetries = 32
retries := maxRetries
tm := d.timers[0].setTimeout(2 * time.Second)
for retries > 0 {
stat, err := d.card_command(cmdGoIdleState, 0) // CMD0.
if err != nil {
if isTimeout(err) {
retries--
continue // Try again!
}
return err
}
if stat == _R1_IDLE_STATE {
break
} else if tm.expired() {
retries = 0
break
}
retries--
}
if retries <= 0 {
return errNoSDCard
}
const enableCRC = true
if enableCRC {
stat, err := d.card_command(cmdCRCOnOff, 1) // CMD59.
if err != nil {
return err
} else if stat != _R1_IDLE_STATE {
return errors.New("sd:cant enable CRC")
}
}
tm.setTimeout(time.Second)
for {
stat, err := d.card_command(cmdSendIfCond, 0x1AA) // CMD8.
if err != nil {
return err
} else if stat == (_R1_ILLEGAL_COMMAND | _R1_IDLE_STATE) {
d.kind = TypeSD1
break
}
d.receive()
d.receive()
d.receive()
status, err := d.receive()
if err != nil {
return err
}
if status == 0xaa {
d.kind = TypeSD2
break
}
d.yield()
}
var arg uint32
if d.kind != TypeSD1 {
arg = 0x4000_0000
}
tm.setTimeout(time.Second)
for !tm.expired() {
stat, err := d.card_acmd(acmdSD_APP_OP_COND, arg)
if err != nil {
return err
} else if stat == 0 { // READY state.
break
}
d.yield()
}
err := d.updateCSDCID()
if err != nil {
return err
}
if d.kind != TypeSD2 {
return nil // Done if not SD2.
}
// Discover if card is high capacity.
stat, err := d.card_command(cmdReadOCR, 0)
if err != nil {
return err
} else if stat != 0 {
return makeResponseError(response1(stat))
}
ocr, err := d.receive()
if err != nil {
return err
} else if ocr&0xc0 == 0xc0 {
d.kind = TypeSDHC
}
// Discard next 3 bytes.
d.receive()
d.receive()
d.receive()
return nil
}
func (d *SPICard) updateCSDCID() (err error) {
// read CID
d.cid, err = d.read_cid()
if err != nil {
return err
}
d.csd, err = d.read_csd()
if err != nil {
return err
}
blklen := d.csd.ReadBlockLen()
d.blk, err = makeBlockIndexer(int(blklen))
if err != nil {
return err
}
return nil
}
// ReadBlocks reads card data into dst beginning at the block index startBlockIdx.
// len(dst) must be a multiple of the card's block size (see [CSD.ReadBlockLen]).
// It returns the number of bytes read into dst and any error encountered.
func (d *SPICard) ReadBlocks(dst []byte, startBlockIdx int64) (int, error) {
numblocks, err := d.checkBounds(startBlockIdx, len(dst))
if err != nil {
return 0, err
}
if d.kind != TypeSDHC {
startBlockIdx <<= 9 // Multiply by 512 for non high capacity SD cards.
}
d.csEnable(true)
defer d.csEnable(false)
if numblocks == 1 {
return d.read_block_single(dst, startBlockIdx)
} else if numblocks > 1 {
// TODO: implement multi block transaction reading.
// Rust code is failing here.
blocksize := int(d.blk.size())
for i := 0; i < numblocks; i++ {
dataoff := i * blocksize
d.csEnable(true)
_, err := d.read_block_single(dst[dataoff:dataoff+blocksize], int64(i)+startBlockIdx)
if err != nil {
return dataoff, err
}
d.csEnable(false)
}
return len(dst), nil
}
panic("unreachable numblocks<=0")
}
// EraseBlocks erases numberOfBlocks blocks beginning at startBlock.
// It always returns an error since erase is not yet implemented for SPICard.
func (d *SPICard) EraseBlocks(startBlock, numberOfBlocks int64) error {
return errors.New("sd:erase not implemented")
}
// WriteBlocks writes data to the card beginning at the block index startBlockIdx.
// len(data) must be a multiple of the card's block size (see [CSD.WriteBlockLen]).
// It returns the number of bytes written and any error encountered.
func (d *SPICard) WriteBlocks(data []byte, startBlockIdx int64) (int, error) {
numblocks, err := d.checkBounds(startBlockIdx, len(data))
if err != nil {
return 0, err
}
if d.kind != TypeSDHC {
startBlockIdx <<= 9 // Multiply by 512 for non high capacity SD cards.
}
d.csEnable(true)
defer d.csEnable(false)
writeTimeout := 2 * d.timeout
if numblocks == 1 {
return d.write_block_single(data, startBlockIdx)
} else if numblocks > 1 {
// Start multi block write.
blocksize := int(d.blk.size())
_, err = d.card_command(cmdWriteMultipleBlock, uint32(startBlockIdx))
if err != nil {
return 0, err
}
for i := 0; i < numblocks; i++ {
offset := i * blocksize
err = d.wait_not_busy(writeTimeout)
if err != nil {
return 0, err
}
err = d.write_data(tokWRITE_MULT, data[offset:offset+blocksize])
if err != nil {
return 0, err
}
}
// Stop the multi write operation.
err = d.wait_not_busy(writeTimeout)
if err != nil {
return 0, err
}
err = d.send(tokSTOP_TRAN)
if err != nil {
return 0, err
}
_, err = d.card_command(cmdStopTransmission, 0)
if err != nil {
return 0, err
}
return len(data), nil
}
panic("unreachable numblocks<=0")
}
func (d *SPICard) read_block_single(dst []byte, startBlockIdx int64) (int, error) {
_, err := d.card_command(cmdReadSingleBlock, uint32(startBlockIdx))
if err != nil {
return 0, err
}
err = d.read_data(dst)
if err != nil {
return 0, err
}
return len(dst), nil
}
func (d *SPICard) write_block_single(data []byte, startBlockIdx int64) (_ int, err error) {
_, err = d.card_command(cmdWriteBlock, uint32(startBlockIdx))
if err != nil {
return 0, err
}
err = d.write_data(tokSTART_BLOCK, data)
if err != nil {
return 0, err
}
err = d.wait_not_busy(2 * d.timeout)
if err != nil {
return 0, err
}
status, err := d.card_command(cmdSendStatus, 0)
if err != nil {
return 0, err
} else if status != 0 {
return 0, makeResponseError(response1(status))
}
status, err = d.receive()
if err != nil {
return 0, err
} else if status != 0 {
return 0, errWrite
}
return len(data), nil
}
func (d *SPICard) checkBounds(startBlockIdx int64, datalen int) (numblocks int, err error) {
if startBlockIdx >= d.NumberOfBlocks() {
return 0, errOOB
} else if startBlockIdx > math.MaxUint32 {
return 0, errCardNotSupported
}
if d.blk.off(int64(datalen)) > 0 {
return 0, errNeedBlockLenMultiple
}
numblocks = int(d.blk.idx(int64(datalen)))
if numblocks == 0 {
return 0, io.ErrShortBuffer
}
return numblocks, nil
}
func (d *SPICard) read_cid() (cid CID, err error) {
err = d.cmd_read(cmdSendCID, 0, d.cid.data[:16]) // CMD10.
if err != nil {
return cid, err
}
if !d.cid.IsValid() {
return cid, errBadCSDCID
}
return d.cid, nil
}
func (d *SPICard) read_csd() (csd CSD, err error) {
err = d.cmd_read(cmdSendCSD, 0, d.csd.data[:16]) // CMD9.
if err != nil {
return csd, err
}
if !d.csd.IsValid() {
return csd, errBadCSDCID
}
return d.csd, nil
}
func (d *SPICard) cmd_read(cmd command, args uint32, buf []byte) error {
status, err := d.card_command(cmd, args)
if err != nil {
return err
} else if status != 0 {
return makeResponseError(response1(status))
}
return d.read_data(buf)
}
func (d *SPICard) card_acmd(acmd appcommand, args uint32) (uint8, error) {
_, err := d.card_command(cmdAppCmd, 0)
if err != nil {
return 0, err
}
return d.card_command(command(acmd), args)
}
func (d *SPICard) card_command(cmd command, args uint32) (uint8, error) {
const transmitterBit = 1 << 6
err := d.wait_not_busy(d.timeout)
if err != nil {
return 0, err
}
buf := d.bufcmd[:6]
// Start bit is always zero; transmitter bit is one since we are Host.
buf[0] = transmitterBit | byte(cmd)
binary.BigEndian.PutUint32(buf[1:5], args)
buf[5] = crc7noshift(buf[:5]) | 1 // CRC and end bit which is always 1.
err = d.bus.Tx(buf, nil)
if err != nil {
return 0, err
}
if cmd == cmdStopTransmission {
d.receive() // skip stuff byte for stop read.
}
for i := 0; i < 512; i++ {
result, err := d.receive()
if err != nil {
return 0, err
}
if result&0x80 == 0 {
return result, nil
}
}
return 0, errReadTimeout
}
func (d *SPICard) read_data(data []byte) (err error) {
var status uint8
tm := d.timers[1].setTimeout(d.timeout)
for !tm.expired() {
status, err = d.receive()
if err != nil {
return err
} else if status != 0xff {
break
} else if tm.expired() {
return errReadTimeout
}
d.yield()
}
if status != tokSTART_BLOCK {
return errWaitStartBlock
}
err = d.bus.Tx(nil, data)
if err != nil {
return err
}
// CRC16 is always sent on a data block.
crchi, _ := d.receive()
crclo, _ := d.receive()
d.lastCRC = uint16(crclo) | uint16(crchi)<<8
return nil
}
func (s *SPICard) wait_not_busy(timeout time.Duration) error {
tm := s.timers[1].setTimeout(timeout)
for {
tok, err := s.receive()
if err != nil {
return err
} else if tok == 0xff {
break
} else if tm.expired() {
return errBusyTimeout
}
s.yield()
}
return nil
}
func (s *SPICard) write_data(tok byte, data []byte) error {
if len(data) > 512 {
return errors.New("data too long for write_data")
}
crc := CRC16(data)
err := s.send(tok)
if err != nil {
return err
}
err = s.bus.Tx(data, nil)
if err != nil {
return err
}
err = s.send(byte(crc >> 8))
if err != nil {
return err
}
err = s.send(byte(crc))
if err != nil {
return err
}
status, err := s.receive()
if err != nil {
return err
}
if status&_DATA_RES_MASK != _DATA_RES_ACCEPTED {
return makeResponseError(response1(status))
}
return nil
}
func (s *SPICard) receive() (byte, error) {
return s.bus.Transfer(0xFF)
}
func (s *SPICard) send(b byte) error {
_, err := s.bus.Transfer(b)
return err
}
func (c *SPICard) csEnable(b bool) {
// SD Card initialization issues with misbehaving SD cards requires clocking the card.
// https://electronics.stackexchange.com/questions/303745/sd-card-initialization-problem-cmd8-wrong-response
c.bus.Transfer(0xff)
c.cs(!b)
c.bus.Transfer(0xff)
}
-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 -9
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,9 +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
tinygo build -size short -o ./build/test.hex -target=pico ./examples/sd
# network examples (espat)
tinygo build -size short -o ./build/test.hex -target=challenger-rp2040 ./examples/net/ntpclient/
# network examples (wifinina)
+20 -11
View File
@@ -1,19 +1,15 @@
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 +18,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)
}
+18 -21
View File
@@ -5,10 +5,8 @@ package ssd1289
import (
"image/color"
"machine"
"time"
"tinygo.org/x/drivers/internal/legacy"
"tinygo.org/x/drivers/internal/pin"
)
type Bus interface {
@@ -16,34 +14,33 @@ type Bus interface {
}
type Device struct {
rs pin.OutputFunc
wr pin.OutputFunc
cs pin.OutputFunc
rst pin.OutputFunc
rs machine.Pin
wr machine.Pin
cs machine.Pin
rst machine.Pin
bus Bus
}
const width = int16(240)
const height = int16(320)
func New(rs, wr, cs, rst pin.Output, bus Bus) *Device {
d := &Device{
rs: rs.Set,
wr: wr.Set,
cs: cs.Set,
rst: rst.Set,
func New(rs machine.Pin, wr machine.Pin, cs machine.Pin, rst machine.Pin, bus Bus) Device {
d := Device{
rs: rs,
wr: wr,
cs: cs,
rst: rst,
bus: bus,
}
// configure GPIO pins (only on baremetal targets, for backwards compatibility)
legacy.ConfigurePinOut(rs)
legacy.ConfigurePinOut(wr)
legacy.ConfigurePinOut(cs)
legacy.ConfigurePinOut(rst)
rs.Configure(machine.PinConfig{Mode: machine.PinOutput})
wr.Configure(machine.PinConfig{Mode: machine.PinOutput})
cs.Configure(machine.PinConfig{Mode: machine.PinOutput})
rst.Configure(machine.PinConfig{Mode: machine.PinOutput})
d.cs.High()
d.rst.High()
d.wr.High()
cs.High()
rst.High()
wr.High()
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()
+12 -14
View File
@@ -5,13 +5,12 @@ 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 +19,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 machine.Pin
resetPin machine.Pin
csPin machine.Pin
width int16
height int16
batchLength int16
@@ -37,16 +36,15 @@ 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)
legacy.ConfigurePinOut(dcPin)
legacy.ConfigurePinOut(resetPin)
legacy.ConfigurePinOut(csPin)
func New(bus drivers.SPI, resetPin, dcPin, csPin machine.Pin) Device {
dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
resetPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
return Device{
bus: bus,
dcPin: dcPin.Set,
resetPin: resetPin.Set,
csPin: csPin.Set,
dcPin: dcPin,
resetPin: resetPin,
csPin: csPin,
}
}
@@ -253,7 +251,7 @@ func (d *Device) Data(data uint8) {
// Tx sends data to the display
func (d *Device) Tx(data []byte, isCommand bool) {
d.dcPin(!isCommand)
d.dcPin.Set(!isCommand)
d.bus.Tx(data, nil)
}
+25 -30
View File
@@ -6,11 +6,10 @@ package ssd1351 // import "tinygo.org/x/drivers/ssd1351"
import (
"errors"
"image/color"
"machine"
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/legacy"
"tinygo.org/x/drivers/internal/pin"
)
var (
@@ -20,18 +19,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
width int16
height int16
rowOffset int16
columnOffset int16
bufferLength int16
configurePins func()
bus drivers.SPI
dcPin machine.Pin
resetPin machine.Pin
csPin machine.Pin
enPin machine.Pin
rwPin machine.Pin
width int16
height int16
rowOffset int16
columnOffset int16
bufferLength int16
}
// Config is the configuration for the display
@@ -43,21 +41,14 @@ 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 machine.Pin) Device {
return Device{
bus: bus,
dcPin: dcPin.Set,
resetPin: resetPin.Set,
csPin: csPin.Set,
enPin: enPin.Set,
rwPin: rwPin.Set,
configurePins: func() {
legacy.ConfigurePinOut(dcPin)
legacy.ConfigurePinOut(resetPin)
legacy.ConfigurePinOut(csPin)
legacy.ConfigurePinOut(enPin)
legacy.ConfigurePinOut(rwPin)
},
dcPin: dcPin,
resetPin: resetPin,
csPin: csPin,
enPin: enPin,
rwPin: rwPin,
}
}
@@ -81,8 +72,12 @@ func (d *Device) Configure(cfg Config) {
d.bufferLength = d.height
}
// configure GPIO pins (on baremetal targets only, for backwards compatibility)
d.configurePins()
// configure GPIO pins
d.dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
d.resetPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
d.csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
d.enPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
d.rwPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
// reset the device
d.resetPin.High()
@@ -283,7 +278,7 @@ func (d *Device) Data(data uint8) {
// Tx sends data to the display
func (d *Device) Tx(data []byte, isCommand bool) {
d.dcPin(!isCommand)
d.dcPin.Set(!isCommand)
d.csPin.Low()
d.bus.Tx(data, nil)
d.csPin.High()
+16 -19
View File
@@ -5,13 +5,12 @@ package st7735 // import "tinygo.org/x/drivers/st7735"
import (
"image/color"
"machine"
"time"
"errors"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/legacy"
"tinygo.org/x/drivers/internal/pin"
"tinygo.org/x/drivers/pixel"
)
@@ -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 machine.Pin
resetPin machine.Pin
csPin machine.Pin
blPin machine.Pin
width int16
height int16
columnOffset int16
@@ -66,25 +65,23 @@ 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 machine.Pin) 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.
legacy.ConfigurePinOut(dcPin)
legacy.ConfigurePinOut(resetPin)
legacy.ConfigurePinOut(csPin)
legacy.ConfigurePinOut(blPin)
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})
return DeviceOf[T]{
bus: bus,
dcPin: dcPin.Set,
resetPin: resetPin.Set,
csPin: csPin.Set,
blPin: blPin.Set,
dcPin: dcPin,
resetPin: resetPin,
csPin: csPin,
blPin: blPin,
}
}
@@ -426,7 +423,7 @@ func (d *DeviceOf[T]) Data(data uint8) {
// Tx sends data to the display
func (d *DeviceOf[T]) Tx(data []byte, isCommand bool) {
d.dcPin(!isCommand)
d.dcPin.Set(!isCommand)
d.bus.Tx(data, nil)
}
+5 -25
View File
@@ -513,8 +513,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 +593,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 +613,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
)
+11 -38
View File
@@ -25,9 +25,9 @@ type Device struct {
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
controller RadioController // to manage interactions with the radio
deepSleep bool // Internal Sleep state
deviceType int // sx1272, sx1273, sx1276, sx1279 (defaults sx1276)
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
}
@@ -65,11 +65,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 +81,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 +91,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 +108,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 +119,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 +244,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)
)
-768
View File
@@ -1,768 +0,0 @@
package sx128x
import (
"runtime"
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/pin"
)
type Device struct {
spi drivers.SPI
nssPin pin.Output
resetPin pin.Output
busyPin pin.Input
spiTxBuf []byte
spiRxBuf []byte
}
func New(spi drivers.SPI, nssPin pin.Output, resetPin pin.Output, busyPin pin.Input) *Device {
return &Device{
spi: spi,
nssPin: nssPin,
resetPin: resetPin,
busyPin: busyPin,
spiTxBuf: make([]byte, 256), // TODO: optimize buffer size
spiRxBuf: make([]byte, 256),
}
}
func (d *Device) Reset() {
d.resetPin.Set(false)
time.Sleep(10 * time.Millisecond)
d.resetPin.Set(true)
time.Sleep(10 * time.Millisecond)
}
func (d *Device) WaitWhileBusy(timeout time.Duration) error {
// largest busy period is on boot with around ~400ish this should be more than enough
now := time.Now()
for d.busyPin.Get() {
if time.Since(now) > timeout {
return ErrBusyPinTimeout
}
runtime.Gosched()
}
return nil
}
// Get tranceiver status, returns circuit mode and command status
func (d *Device) GetStatus() (CircuitMode, CommandStatus, error) {
err := d.WaitWhileBusy(time.Second)
if err != nil {
return 0, 0, err
}
d.nssPin.Set(false)
status, err := d.spi.Transfer(cmdGetStatus)
d.nssPin.Set(true)
if err != nil {
return 0, 0, err
}
circuitMode := (status & circuitModeMask) >> 5
commandStatus := (status & commandStatusMask) >> 2
return CircuitMode(circuitMode), CommandStatus(commandStatus), nil
}
func (d *Device) WriteRegister(addr uint16, data []byte) error {
err := d.WaitWhileBusy(time.Second)
if err != nil {
return err
}
d.nssPin.Set(false)
d.spiTxBuf = d.spiTxBuf[:0]
d.spiTxBuf = append(d.spiTxBuf, cmdWriteRegister, uint8((addr>>8)&0xFF), uint8(addr&0xFF))
d.spiTxBuf = append(d.spiTxBuf, data...)
err = d.spi.Tx(d.spiTxBuf, nil)
d.nssPin.Set(true)
return err
}
func (d *Device) ReadRegister(addr uint16) (uint8, error) {
err := d.WaitWhileBusy(time.Second)
if err != nil {
return 0, err
}
d.nssPin.Set(false)
d.spiTxBuf = d.spiTxBuf[:0]
d.spiTxBuf = append(d.spiTxBuf, cmdReadRegister, uint8((addr&0xFF00)>>8), uint8(addr&0x00FF), 0x00, 0x00)
d.spiRxBuf = d.spiRxBuf[:5]
err = d.spi.Tx(d.spiTxBuf, d.spiRxBuf)
d.nssPin.Set(true)
if err != nil {
return 0, err
}
return d.spiRxBuf[4], nil
}
func (d *Device) WriteBuffer(offset uint8, data []byte) error {
if len(data) > 256 {
return errDataTooLong
}
err := d.WaitWhileBusy(time.Second)
if err != nil {
return err
}
d.nssPin.Set(false)
d.spiTxBuf = d.spiTxBuf[:0]
d.spiTxBuf = append(d.spiTxBuf, cmdWriteBuffer, offset)
d.spiTxBuf = append(d.spiTxBuf, data...)
err = d.spi.Tx(d.spiTxBuf, nil)
d.nssPin.Set(true)
return err
}
// Read data from the payload buffer starting at the given offset with the given length
func (d *Device) ReadBuffer(offset uint8, length uint8) ([]byte, error) {
err := d.WaitWhileBusy(time.Second)
if err != nil {
return nil, err
}
d.nssPin.Set(false)
d.spiTxBuf = d.spiTxBuf[:0]
d.spiTxBuf = append(d.spiTxBuf, cmdReadBuffer, offset, 0x00)
for i := uint8(0); i < length; i++ {
d.spiTxBuf = append(d.spiTxBuf, 0x00)
}
d.spiRxBuf = d.spiRxBuf[:len(d.spiTxBuf)]
err = d.spi.Tx(d.spiTxBuf, d.spiRxBuf)
d.nssPin.Set(true)
if err != nil {
return nil, err
}
return d.spiRxBuf[3:], nil
}
// Set the device into sleep mode with the given configuration: 0 (no retention), 1 (ram retentation), 2 (buffer retention) or 3 (ram and buffer retention)
func (d *Device) SetSleep(sleepConfig SleepConfig) error {
if sleepConfig > (SLEEP_DATA_BUFFER_RETAIN | SLEEP_DATA_RAM_RETAIN) {
return errInvalidSleepConfig
}
err := d.WaitWhileBusy(time.Second)
if err != nil {
return err
}
d.nssPin.Set(false)
d.spiTxBuf = d.spiTxBuf[:0]
d.spiTxBuf = append(d.spiTxBuf, cmdSetSleep, uint8(sleepConfig))
err = d.spi.Tx(d.spiTxBuf, nil)
d.nssPin.Set(true)
return err
}
// Put device into standby mode, 0 (RC) or 1 (XOSC)
func (d *Device) SetStandby(standbyConfig StandbyConfig) error {
if standbyConfig > STANDBY_XOSC { // XOSC is the highest standby config anything higher is invalid
return errInvalidStandbyConfig
}
err := d.WaitWhileBusy(time.Second)
if err != nil {
return err
}
d.nssPin.Set(false)
d.spiTxBuf = d.spiTxBuf[:0]
d.spiTxBuf = append(d.spiTxBuf, cmdSetStandby, uint8(standbyConfig))
err = d.spi.Tx(d.spiTxBuf, nil)
d.nssPin.Set(true)
return err
}
// Set the device into Frequency Synthesizer mode
func (d *Device) SetFs() error {
err := d.WaitWhileBusy(time.Second)
if err != nil {
return err
}
d.nssPin.Set(false)
d.spiTxBuf = d.spiTxBuf[:0]
d.spiTxBuf = append(d.spiTxBuf, cmdSetFS)
err = d.spi.Tx(d.spiTxBuf, nil)
d.nssPin.Set(true)
return err
}
func checkPeriodBase(periodBase PeriodBase) error {
if periodBase > PERIOD_BASE_4_MS { // 4ms is the highest period base anything higher is invalid
return errInvalidPeriodBase
}
return nil
}
// Sets the device in transmit mode, the IRQ status should be cleared before using this command
// timout is determined by periodBase * periodBaseCount
func (d *Device) SetTx(periodBase PeriodBase, periodBaseCount uint16) error {
err := checkPeriodBase(periodBase)
if err != nil {
return err
}
err = d.WaitWhileBusy(time.Second)
if err != nil {
return err
}
d.nssPin.Set(false)
d.spiTxBuf = d.spiTxBuf[:0]
d.spiTxBuf = append(d.spiTxBuf, cmdSetTx, uint8(periodBase), uint8((periodBaseCount>>8)&0xFF), uint8(periodBaseCount&0xFF))
err = d.spi.Tx(d.spiTxBuf, nil)
d.nssPin.Set(true)
return err
}
// Sets the device in receive mode, the IRQ status should be cleared before using this command
// timeout is determined by periodBase * periodBaseCount
func (d *Device) SetRx(periodBase PeriodBase, periodBaseCount uint16) error {
err := checkPeriodBase(periodBase)
if err != nil {
return err
}
err = d.WaitWhileBusy(time.Second)
if err != nil {
return err
}
d.nssPin.Set(false)
d.spiTxBuf = d.spiTxBuf[:0]
d.spiTxBuf = append(d.spiTxBuf, cmdSetRx, uint8(periodBase), uint8((periodBaseCount>>8)&0xFF), uint8(periodBaseCount&0xFF))
err = d.spi.Tx(d.spiTxBuf, nil)
d.nssPin.Set(true)
return err
}
// Sets the device in a continuous receive mode, it enters receive mode with a timeout of periodBase * rxPeriodBaseCount.
// If no packet is received it will enter sleep mode for periodBase * sleepPeriodBaseCount before re-entering receive mode.
// The loop is exited when a packet is received or the device is put into standby mode.
func (d *Device) SetRxDutyCycle(periodBase PeriodBase, rxPeriodBaseCount uint16, sleepPeriodBaseCount uint16) error {
err := checkPeriodBase(periodBase)
if err != nil {
return err
}
err = d.WaitWhileBusy(time.Second)
if err != nil {
return err
}
d.nssPin.Set(false)
d.spiTxBuf = d.spiTxBuf[:0]
d.spiTxBuf = append(d.spiTxBuf, cmdSetRxDutyCycle, uint8(periodBase), uint8((rxPeriodBaseCount&0xFF00)>>8), uint8(rxPeriodBaseCount&0x00FF))
d.spiTxBuf = append(d.spiTxBuf, uint8((sleepPeriodBaseCount&0xFF00)>>8), uint8(sleepPeriodBaseCount&0x00FF))
err = d.spi.Tx(d.spiTxBuf, nil)
d.nssPin.Set(true)
return err
}
// Sets the transceiver into Long Preamble mode, and can only be used with either the LoRa mode and GFSK mode
func (d *Device) SetLongPreamble(enable bool) error {
err := d.WaitWhileBusy(time.Second)
if err != nil {
return err
}
d.nssPin.Set(false)
d.spiTxBuf = d.spiTxBuf[:0]
d.spiTxBuf = append(d.spiTxBuf, cmdSetLongPreamble)
if enable {
d.spiTxBuf = append(d.spiTxBuf, 1)
} else {
d.spiTxBuf = append(d.spiTxBuf, 0)
}
err = d.spi.Tx(d.spiTxBuf, nil)
d.nssPin.Set(true)
return err
}
// Channel activity detection (CAD) is a LoRa specific mode of operation where the device searches for a LoRa signal.
// After search has completed, the device returns to STDBY_RC mode. The length of the search is configured via the SetCadParams() command.
func (d *Device) SetCAD() error {
err := d.WaitWhileBusy(time.Second)
if err != nil {
return err
}
d.nssPin.Set(false)
d.spiTxBuf = d.spiTxBuf[:0]
d.spiTxBuf = append(d.spiTxBuf, cmdSetCAD)
err = d.spi.Tx(d.spiTxBuf, nil)
d.nssPin.Set(true)
return err
}
// Test command to generate a Continuous Wave (RF tone) at a selected frequency and output power
// The device remains in Tx Continuous Wave until the host sends a mode configuration command.
func (d *Device) SetTxContinuousWave() error {
err := d.WaitWhileBusy(time.Second)
if err != nil {
return err
}
d.nssPin.Set(false)
d.spiTxBuf = d.spiTxBuf[:0]
d.spiTxBuf = append(d.spiTxBuf, cmdSetTxContinuousWave)
err = d.spi.Tx(d.spiTxBuf, nil)
d.nssPin.Set(true)
return err
}
// Test command to generate an infinite sequence of alternating 0s and 1s in
// GFSK modulation and symbol 0 in LoRa. The device remains in transmit until the host sends a mode configuration command.
func (d *Device) SetTxContinuousPreamble() error {
err := d.WaitWhileBusy(time.Second)
if err != nil {
return err
}
d.nssPin.Set(false)
d.spiTxBuf = d.spiTxBuf[:0]
d.spiTxBuf = append(d.spiTxBuf, cmdSetContinuousPreamble)
err = d.spi.Tx(d.spiTxBuf, nil)
d.nssPin.Set(true)
return err
}
// This command allows the transceiver to send a packet at a user programmable time after the end of a packet reception.
// This is useful for Bluetooth Low Energy (BLE) compatibility which requires the transceiver to be able to send back a response 150µs after a packet reception.
func (d *Device) SetAutoTx(timeUs uint16) error {
err := d.WaitWhileBusy(time.Second)
if err != nil {
return err
}
d.nssPin.Set(false)
d.spiTxBuf = d.spiTxBuf[:0]
d.spiTxBuf = append(d.spiTxBuf, cmdSetAutoTx, uint8((timeUs&0xFF00)>>8), uint8(timeUs&0x00FF))
err = d.spi.Tx(d.spiTxBuf, nil)
d.nssPin.Set(true)
return err
}
// Modifies the chip behavior so that the state following a Rx or Tx operation is FS and not standby.
// This allows for faster transitions between Rx and/or Tx.
func (d *Device) SetAutoFs(enable bool) error {
err := d.WaitWhileBusy(time.Second)
if err != nil {
return err
}
d.nssPin.Set(false)
d.spiTxBuf = d.spiTxBuf[:0]
d.spiTxBuf = append(d.spiTxBuf, cmdSetAutoFS)
if enable {
d.spiTxBuf = append(d.spiTxBuf, 1)
} else {
d.spiTxBuf = append(d.spiTxBuf, 0)
}
err = d.spi.Tx(d.spiTxBuf, nil)
d.nssPin.Set(true)
return err
}
// Choose between GFSK, LoRa, Ranging, FLRC or BLE packet types, this will affect the available configuration parameters and the structure of the packet
func (d *Device) SetPacketType(packetType PacketType) error {
if packetType > PACKET_TYPE_BLE { // BLE is the highest packet type anything higher is invalid.
return errInvalidPacketType
}
err := d.WaitWhileBusy(time.Second)
if err != nil {
return err
}
d.nssPin.Set(false)
d.spiTxBuf = d.spiTxBuf[:0]
d.spiTxBuf = append(d.spiTxBuf, cmdSetPacketType, uint8(packetType))
err = d.spi.Tx(d.spiTxBuf, nil)
d.nssPin.Set(true)
return err
}
// Get the currently configured packet type, this will be 0 (GFSK), 1 (LoRa), 2 (Ranging), 3 (FLRC) or 4 (BLE)
func (d *Device) GetPacketType() (PacketType, error) {
err := d.WaitWhileBusy(time.Second)
if err != nil {
return 0, err
}
d.nssPin.Set(false)
d.spiTxBuf = d.spiTxBuf[:0]
d.spiTxBuf = append(d.spiTxBuf, cmdGetPacketType, 0x00, 0x00)
d.spiRxBuf = d.spiRxBuf[:3]
err = d.spi.Tx(d.spiTxBuf, d.spiRxBuf)
d.nssPin.Set(true)
if err != nil {
return 0, err
}
return PacketType(d.spiRxBuf[2]), nil
}
// Set the RF frequency in Hz, must be between 2.4 GHz and 2.5 GHz
func (d *Device) SetRfFrequency(frequencyHz uint32) error {
if frequencyHz < 2400000000 {
return errFrequencyTooLow
}
if frequencyHz > 2500000000 {
return errFrequencyTooHigh
}
err := d.WaitWhileBusy(time.Second)
if err != nil {
return err
}
d.nssPin.Set(false)
d.spiTxBuf = d.spiTxBuf[:0]
rfFrequency := uint32((uint64(frequencyHz) << 18) / 52000000)
d.spiTxBuf = append(d.spiTxBuf, cmdSetRFFrequency, uint8((rfFrequency>>16)&0xFF), uint8((rfFrequency>>8)&0xFF), uint8(rfFrequency&0xFF))
err = d.spi.Tx(d.spiTxBuf, nil)
d.nssPin.Set(true)
return err
}
// Set the output power in dBm, must be between -18 and 13 dBm, and the ramp time
func (d *Device) SetTxParams(powerdBm int8, rampTime RadioRampTime) error {
if powerdBm < -18 {
return errPowerTooLow
}
if powerdBm > 13 {
return errPowerTooHigh
}
err := d.WaitWhileBusy(time.Second)
if err != nil {
return err
}
d.nssPin.Set(false)
d.spiTxBuf = d.spiTxBuf[:0]
adjustedPower := uint8(powerdBm + 18)
d.spiTxBuf = append(d.spiTxBuf, cmdSetTxParams, adjustedPower, uint8(rampTime))
err = d.spi.Tx(d.spiTxBuf, nil)
d.nssPin.Set(true)
return err
}
// Set the number of symbols used for channel activity detection which determines the sensitivity of the detection.
// This is only applicable in LoRa mode.
func (d *Device) SetCadParams(cadSymbolNum uint8) error {
err := d.WaitWhileBusy(time.Second)
if err != nil {
return err
}
d.nssPin.Set(false)
d.spiTxBuf = d.spiTxBuf[:0]
d.spiTxBuf = append(d.spiTxBuf, cmdSetCADParams, cadSymbolNum)
err = d.spi.Tx(d.spiTxBuf, nil)
d.nssPin.Set(true)
return err
}
// Set the base address for the internal buffer for Tx and Rx operations.
// When transmitting or receiving data is read from or written to the buffer starting at the given offset.
func (d *Device) SetBufferBaseAddress(txBase uint8, rxBase uint8) error {
err := d.WaitWhileBusy(time.Second)
if err != nil {
return err
}
d.nssPin.Set(false)
d.spiTxBuf = d.spiTxBuf[:0]
d.spiTxBuf = append(d.spiTxBuf, cmdSetBufferBaseAddress, txBase, rxBase)
err = d.spi.Tx(d.spiTxBuf, nil)
d.nssPin.Set(true)
return err
}
// The arguments to this function depend on the packet type. It is recommended to use the mode specific functions for a better experience.
// BLE & GFSK: BitrateBandwidth, ModulationIndex, ModulationShaping
// FLRC: BitrateBandwidth, CodingRate, ModulationShaping
// LoRa & Ranging: SpreadingFactor, Bandwidth, CodingRate
func (d *Device) SetModulationParams(modParam1, modParam2, modParam3 uint8) error {
err := d.WaitWhileBusy(time.Second)
if err != nil {
return err
}
d.nssPin.Set(false)
d.spiTxBuf = d.spiTxBuf[:0]
d.spiTxBuf = append(d.spiTxBuf, cmdSetModulationParams, modParam1, modParam2, modParam3)
err = d.spi.Tx(d.spiTxBuf, nil)
d.nssPin.Set(true)
return err
}
func (d *Device) SetModulationParamsBLE(bitrateBandwidth GFSKBLEBitrateBandwidth, modulationIndex ModulationIndex, modulationShaping ModulationShaping) error {
return d.SetModulationParams(uint8(bitrateBandwidth), uint8(modulationIndex), uint8(modulationShaping))
}
func (d *Device) SetModulationParamsGFSK(bitrateBandwidth GFSKBLEBitrateBandwidth, modulationIndex ModulationIndex, modulationShaping ModulationShaping) error {
return d.SetModulationParams(uint8(bitrateBandwidth), uint8(modulationIndex), uint8(modulationShaping))
}
func (d *Device) SetModulationParamsFLRC(bitrateBandwidth FLRCBitrateBandwidth, codingRate FLRCCodingRate, modulationShaping ModulationShaping) error {
return d.SetModulationParams(uint8(bitrateBandwidth), uint8(codingRate), uint8(modulationShaping))
}
func (d *Device) SetModulationParamsLoRa(spreadingFactor LoRaSpreadingFactor, bandwidth LoRaBandwidth, codingRate LoRaCodingRate) error {
return d.SetModulationParams(uint8(spreadingFactor), uint8(bandwidth), uint8(codingRate))
}
// The arguments to this function depend on the packet type. It is recommended to use the mode specific functions for a better experience.
// GFSK & FLRC: PreambleLength, SyncWordLength, SyncWordMatch, HeaderType, PayloadLength, CrcLength, Whitening
// BLE: ConnectionState, CrcLength, BleTestPayload, Whitening
// LoRa & Ranging: PreambleLength, HeaderType, PayloadLength, CRC, InvertIQ/chirp invert
func (d *Device) SetPacketParams(param1, param2, param3, param4, param5, param6, param7 uint8) error {
err := d.WaitWhileBusy(time.Second)
if err != nil {
return err
}
d.nssPin.Set(false)
d.spiTxBuf = d.spiTxBuf[:0]
d.spiTxBuf = append(d.spiTxBuf, cmdSetPacketParams, param1, param2, param3, param4, param5, param6, param7)
err = d.spi.Tx(d.spiTxBuf, nil)
d.nssPin.Set(true)
return err
}
// Set GFSK related packet parameters, this assumes the packet type is already set to GFSK.
// - payloadLength: range of 0-255
func (d *Device) SetPacketParamsGFSK(preambleLength GFSKPreambleLength, syncWordLength GFSKSyncWordLength, syncWordMatch GFSKSyncWordMatch, headerType GFSKHeaderType, payloadLength uint8, crcLength GFSKCrcType, whitening bool) error {
var whiteningVal uint8
if whitening {
whiteningVal = whiteningEnable
} else {
whiteningVal = whiteningDisable
}
return d.SetPacketParams(uint8(preambleLength), uint8(syncWordLength), uint8(syncWordMatch), uint8(headerType), payloadLength, uint8(crcLength), whiteningVal)
}
// Set FLRC related packet parameters, this assumes the packet type is already set to FLRC.
// - payloadLength: range of 6-127
func (d *Device) SetPacketParamsFLRC(preambleLength FLRCPreambleLength, syncWordLength FLRCSyncWordLength, syncWordMatch FLRCSyncWordMatch, headerType FLRCHeaderType, payloadLength uint8, crcLength FLRCCrcType) error {
if payloadLength < 6 {
return errPayloadLengthTooShort
}
if payloadLength > 127 {
return errPayloadLengthTooLong
}
return d.SetPacketParams(uint8(preambleLength), uint8(syncWordLength), uint8(syncWordMatch), uint8(headerType), payloadLength, uint8(crcLength), whiteningDisable)
}
// Set BLE related packet parameters, this assumes the packet type is already set to BLE.
func (d *Device) SetPacketParamsBLE(connectionState BLEConnectionState, crcLength BLECrcType, bleTestPayload BLETestPayload, whitening bool) error {
var whiteningVal uint8
if whitening {
whiteningVal = whiteningEnable
} else {
whiteningVal = whiteningDisable
}
return d.SetPacketParams(uint8(connectionState), uint8(crcLength), uint8(bleTestPayload), whiteningVal, 0, 0, 0)
}
// Set LoRa related packet parameters, this assumes the packet type is already set to LoRa.
// - payloadLength: range of 1-255
func (d *Device) SetPacketParamsLoRa(preambleLength uint32, headerType LoRaHeaderType, payloadLength uint8, crcType LoRaCrcType, iqType LoRaIqType) error {
if payloadLength == 0 {
return errPayloadLengthTooShort
}
exponent, mantissa := getExponentAndMantissa(preambleLength)
return d.SetPacketParams(uint8(exponent<<4)|mantissa, uint8(headerType), payloadLength, uint8(crcType), uint8(iqType), 0, 0)
}
func getExponentAndMantissa(value uint32) (uint8, uint8) {
// pulled from RadioLib https://github.com/jgromes/RadioLib/blob/master/src/modules/SX128x/SX128x.cpp
e := uint8(1)
m := uint8(1)
len := uint32(0)
for e = uint8(1); e <= 15; e++ {
for m = uint8(1); m <= 15; m++ {
len = uint32(m) * (uint32(1 << e))
if len >= value {
break
}
}
if len >= value {
break
}
}
return e, m
}
// Get information about the most recent packet received.
// Return the payload length, the offset in the buffer where the payload starts.
func (d *Device) GetRxBufferStatus() (uint8, uint8, error) {
err := d.WaitWhileBusy(time.Second)
if err != nil {
return 0, 0, err
}
d.nssPin.Set(false)
d.spiTxBuf = d.spiTxBuf[:0]
d.spiTxBuf = append(d.spiTxBuf, cmdGetRxBufferStatus, 0x00, 0x00, 0x00)
d.spiRxBuf = d.spiRxBuf[:4]
err = d.spi.Tx(d.spiTxBuf, d.spiRxBuf)
d.nssPin.Set(true)
if err != nil {
return 0, 0, err
}
return d.spiRxBuf[2], d.spiRxBuf[3], nil
}
// The return type of this function depends on the packet type. Use mode specific function for typed returns.
// BLE, GFSK & FLRC: unused, rssiSync, errors, status, sync
// LoRa & Ranging: rssiSync, SNR
func (d *Device) GetPacketStatus() (uint8, uint8, uint8, uint8, uint8, error) {
err := d.WaitWhileBusy(time.Second)
if err != nil {
return 0, 0, 0, 0, 0, err
}
d.nssPin.Set(false)
d.spiTxBuf = d.spiTxBuf[:0]
d.spiTxBuf = append(d.spiTxBuf, cmdGetPacketStatus, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
d.spiRxBuf = d.spiRxBuf[:7]
err = d.spi.Tx(d.spiTxBuf, d.spiRxBuf)
d.nssPin.Set(true)
if err != nil {
return 0, 0, 0, 0, 0, err
}
return d.spiRxBuf[2], d.spiRxBuf[3], d.spiRxBuf[4], d.spiRxBuf[5], d.spiRxBuf[6], nil
}
// Get information about the most recent GFSK packet received or transmitted:
// - RSSI of last received packet
// - packet information (each bit represents a different error or status flag)
// - whether the last packet transmission has ended
// - the sync word that was used for the last packet reception (0-3)
func (d *Device) GetPacketStatusGFSK() (float32, GFSKPacketInfo, bool, uint8, error) {
_, rssiSync, packetInfo, status, sync, err := d.GetPacketStatus()
if err != nil {
return 0, 0, false, 0, err
}
return float32(int8(rssiSync)) / 2 * -1, GFSKPacketInfo(packetInfo), status != 0, sync, nil
}
// Get information about the most recent BLE packet received or transmitted:
// - RSSI of last received packet
// - packet information (each bit represents a different error or status flag)
// - whether the last packet transmission has ended
// - the sync word that was used for the last packet reception (0-1)
func (d *Device) GetPacketStatusBLE() (float32, BLEPacketInfo, bool, uint8, error) {
_, rssiSync, packetInfo, status, sync, err := d.GetPacketStatus()
if err != nil {
return 0, 0, false, 0, err
}
return float32(int8(rssiSync)) / 2 * -1, BLEPacketInfo(packetInfo), status != 0, sync, nil
}
// Get information about the most recent BLE packet received or transmitted:
// - RSSI of last received packet
// - packet information (each bit represents a different error or status flag)
// - PID field of the received packet
// - NO_ACK field of the received packet
// - PID check status of the current packet
// - whether the last packet transmission has ended
// - the sync word that was used for the last packet reception (0-1)
func (d *Device) GetPacketStatusFLRC() (float32, FLRCPacketInfo, uint8, bool, bool, bool, uint8, error) {
_, rawRSSI, packetInfo, rxTxInfo, sync, err := d.GetPacketStatus()
rxPid := (rxTxInfo & 0b11000000) >> 6
noAck := (rxTxInfo & 0b00100000) != 0
pidCheck := (rxTxInfo & 0b00010000) != 0
txDone := (rxTxInfo & 0b00000001) != 0
if err != nil {
return 0, 0, 0, false, false, false, 0, err
}
return float32(int8(rawRSSI)) / 2 * -1, FLRCPacketInfo(packetInfo), rxPid, noAck, pidCheck, txDone, sync, nil
}
// Get information about the most recent LoRa packet received:
// - RSSI of last received packet
// - signal-to-noise ratio (SNR) of last received packet
func (d *Device) GetPacketStatusLoRa() (float32, float32, error) {
rawRSSI, rawSnr, _, _, _, err := d.GetPacketStatus()
if err != nil {
return 0, 0, err
}
return float32(int8(rawRSSI)) / 2 * -1, float32(int8(rawSnr)) / 4, nil
}
// Get the instantaneous RSSI value during reception of the packet
func (d *Device) GetRssiInst() (float32, error) {
err := d.WaitWhileBusy(time.Second)
if err != nil {
return 0, err
}
d.nssPin.Set(false)
d.spiTxBuf = d.spiTxBuf[:0]
d.spiTxBuf = append(d.spiTxBuf, cmdGetRSSIInst, 0x00, 0x00)
d.spiRxBuf = d.spiRxBuf[:3]
err = d.spi.Tx(d.spiTxBuf, d.spiRxBuf)
d.nssPin.Set(true)
if err != nil {
return 0, err
}
return float32(int8(d.spiRxBuf[2])) / 2 * -1, nil
}
// Configure the overall IRQ mask and the mapping of individual IRQs to the DIO1, DIO2 and DIO3 pins
func (d *Device) SetDioIrqParams(irqMask IRQMask, dio1Mask IRQMask, dio2Mask IRQMask, dio3Mask IRQMask) error {
err := d.WaitWhileBusy(time.Second)
if err != nil {
return err
}
d.nssPin.Set(false)
d.spiTxBuf = d.spiTxBuf[:0]
d.spiTxBuf = append(d.spiTxBuf, cmdSetDIOIRQParams, uint8((irqMask&0xFF00)>>8), uint8(irqMask&0x00FF))
d.spiTxBuf = append(d.spiTxBuf, uint8((dio1Mask&0xFF00)>>8), uint8(dio1Mask&0x00FF))
d.spiTxBuf = append(d.spiTxBuf, uint8((dio2Mask&0xFF00)>>8), uint8(dio2Mask&0x00FF))
d.spiTxBuf = append(d.spiTxBuf, uint8((dio3Mask&0xFF00)>>8), uint8(dio3Mask&0x00FF))
err = d.spi.Tx(d.spiTxBuf, nil)
d.nssPin.Set(true)
return err
}
// Get the current IRQ status.
func (d *Device) GetIrqStatus() (IRQMask, error) {
err := d.WaitWhileBusy(time.Second)
if err != nil {
return 0, err
}
d.nssPin.Set(false)
d.spiTxBuf = d.spiTxBuf[:0]
d.spiTxBuf = append(d.spiTxBuf, cmdGetIRQStatus, 0x00, 0x00, 0x00)
d.spiRxBuf = d.spiRxBuf[:4]
err = d.spi.Tx(d.spiTxBuf, d.spiRxBuf)
d.nssPin.Set(true)
if err != nil {
return 0, err
}
return uint16(d.spiRxBuf[2])<<8 | uint16(d.spiRxBuf[3]), err
}
// Clear the IRQ bits specified in the irqMask.
func (d *Device) ClearIrqStatus(irqMask IRQMask) error {
err := d.WaitWhileBusy(time.Second)
if err != nil {
return err
}
d.nssPin.Set(false)
d.spiTxBuf = d.spiTxBuf[:0]
d.spiTxBuf = append(d.spiTxBuf, cmdClearIRQStatus, uint8((irqMask&0xFF00)>>8), uint8(irqMask&0x00FF))
err = d.spi.Tx(d.spiTxBuf, nil)
d.nssPin.Set(true)
return err
}
// Switch between the low-dropout regulator (LDO) and the DC-DC converter for internal power regulation.
func (d *Device) SetRegulatorMode(mode RegulatorMode) error {
if mode > REGULATOR_DC_DC { // DC-DC is the highest regulator mode anything higher is invalid
return errInvalidRegulatorMode
}
err := d.WaitWhileBusy(time.Second)
if err != nil {
return err
}
d.nssPin.Set(false)
d.spiTxBuf = d.spiTxBuf[:0]
d.spiTxBuf = append(d.spiTxBuf, cmdSetRegulatorMode, uint8(mode))
err = d.spi.Tx(d.spiTxBuf, nil)
d.nssPin.Set(true)
return err
}
// Stores the present context of the radio register values to the Data RAM which will be restored when the device wakes up from sleep mode.
func (d *Device) SetSaveContext() error {
err := d.WaitWhileBusy(time.Second)
if err != nil {
return err
}
d.nssPin.Set(false)
d.spiTxBuf = d.spiTxBuf[:0]
d.spiTxBuf = append(d.spiTxBuf, cmdSetSaveContext)
err = d.spi.Tx(d.spiTxBuf, nil)
d.nssPin.Set(true)
return err
}
-290
View File
@@ -1,290 +0,0 @@
// Package unoqmatrix provides a driver for the UnoQMatrix LED matrix display.
//
// The UnoQMatrix is an 8x13 LED matrix display that can be controlled using a single pin.
// It uses a multiplexing technique to control the LEDs, which allows for a large number of LEDs to be controlled with fewer pins.
//
// This driver provides basic functionality to set individual pixels, clear the display, and refresh the display.
//
// Note: The UnoQMatrix does not support brightness control or color depth. Each pixel can only be turned on or off.
// Could it suppport brightness control by using PWM on the pin? To be investigated.
package unoqmatrix
import (
"image/color"
"time"
pin "tinygo.org/x/drivers/internal/pin"
)
type Config struct {
// Rotation of the LED matrix.
Rotation uint8
}
// Valid values:
//
// 0: regular orientation, (0 degree rotation)
// 1: 90 degree rotation clockwise
// 2: 180 degree rotation clockwise
// 3: 270 degree rotation clockwise
const (
RotationNormal = 0
Rotation90 = 1
Rotation180 = 2
Rotation270 = 3
)
const (
ledRows = 8
ledCols = 13
pixelRefreshDelay = 10 * time.Microsecond
)
// CharlieplexPin represents a pin used for charlieplexing.
// It must be able to drive high/low (output mode) and float (high-impedance/input mode).
//
// Example construction from a machine.Pin using the pin HAL pattern:
//
// var isOutput bool
// cp := unoqmatrix.CharlieplexPin{
// Set: pin.OutputFunc(func(level bool) {
// if !isOutput {
// p.Configure(machine.PinConfig{Mode: machine.PinOutput})
// isOutput = true
// }
// p.Set(level)
// }),
// Float: func() {
// if isOutput {
// p.Configure(machine.PinConfig{Mode: machine.PinInput})
// isOutput = false
// }
// },
// }
type CharlieplexPin struct {
Set pin.OutputFunc // Drive pin high (true) or low (false); auto-configures to output mode.
Float func() // Put pin into high-impedance (input) mode.
}
const numPins = 11
// Device represents the UnoQMatrix LED matrix display.
type Device struct {
pins [numPins]CharlieplexPin
buffer [ledRows][ledCols]color.RGBA
rotation uint8
}
// New returns a new unoqmatrix driver.
// The provided pins are the 11 charlieplex pins used to control the LED matrix.
func New(pins [numPins]CharlieplexPin) Device {
return Device{pins: pins}
}
// Configure sets up the device.
func (d *Device) Configure(cfg Config) {
d.SetRotation(cfg.Rotation)
}
// SetRotation changes the rotation of the LED matrix.
//
// Valid values for rotation:
//
// 0: regular orientation, (0 degree rotation)
// 1: 90 degree rotation clockwise
// 2: 180 degree rotation clockwise
// 3: 270 degree rotation clockwise
func (d *Device) SetRotation(rotation uint8) {
d.rotation = rotation % 4
}
// SetPixel sets the color of a specific pixel.
func (d *Device) SetPixel(x int16, y int16, c color.RGBA) {
d.buffer[y][x] = c
}
// GetPixel returns the color of a specific pixel.
func (d *Device) GetPixel(x int16, y int16) color.RGBA {
return d.buffer[y][x]
}
// Display sends the buffer (if any) to the screen.
// Only lights active (non-black) pixels, and resets only the 2 previously
// driven pins between LEDs instead of all 11, making each refresh cycle
// proportional to the number of lit LEDs.
func (d *Device) Display() error {
d.clearDisplay()
var lastIdx0, lastIdx1 uint8
hasLast := false
for row := 0; row < ledRows; row++ {
for col := 0; col < ledCols; col++ {
c := d.buffer[row][col]
if c.R == 0 && c.G == 0 && c.B == 0 {
continue
}
idx := row*ledCols + col
if idx < 0 || idx >= len(pinMapping) {
continue
}
// Float only the two pins that were driving the previous LED.
if hasLast {
d.pins[lastIdx0].Float()
d.pins[lastIdx1].Float()
}
hasLast = true
idx0 := pinMapping[idx][0]
idx1 := pinMapping[idx][1]
d.pins[idx0].Set.High()
d.pins[idx1].Set.Low()
lastIdx0 = idx0
lastIdx1 = idx1
time.Sleep(pixelRefreshDelay)
}
}
// Float the last driven LED.
if hasLast {
d.pins[lastIdx0].Float()
d.pins[lastIdx1].Float()
}
return nil
}
// ClearDisplay turns off all the LEDs on the display.
func (d *Device) ClearDisplay() {
for row := 0; row < ledRows; row++ {
for col := 0; col < ledCols; col++ {
d.buffer[row][col] = color.RGBA{0, 0, 0, 255}
}
}
}
// Size returns the current size of the display.
func (d *Device) Size() (w, h int16) {
return ledCols, ledRows
}
// pinMapping defines the mapping of LED indices to pin pairs. Each entry corresponds
// to an LED index (0-104) and contains the two pin numbers that need to be set to turn on that LED.
// based on https://github.com/arduino/ArduinoCore-zephyr/blob/main/loader/matrix.inc#L13
var pinMapping = [][2]uint8{
{0, 1}, // 0
{1, 0},
{0, 2},
{2, 0},
{1, 2},
{2, 1},
{0, 3},
{3, 0},
{1, 3},
{3, 1},
{2, 3}, // 10
{3, 2},
{0, 4},
{4, 0},
{1, 4},
{4, 1},
{2, 4},
{4, 2},
{3, 4},
{4, 3},
{0, 5}, // 20
{5, 0},
{1, 5},
{5, 1},
{2, 5},
{5, 2},
{3, 5},
{5, 3},
{4, 5},
{5, 4},
{0, 6}, // 30
{6, 0},
{1, 6},
{6, 1},
{2, 6},
{6, 2},
{3, 6},
{6, 3},
{4, 6},
{6, 4},
{5, 6}, // 40
{6, 5},
{0, 7},
{7, 0},
{1, 7},
{7, 1},
{2, 7},
{7, 2},
{3, 7},
{7, 3},
{4, 7}, // 50
{7, 4},
{5, 7},
{7, 5},
{6, 7},
{7, 6},
{0, 8},
{8, 0},
{1, 8},
{8, 1},
{2, 8}, // 60
{8, 2},
{3, 8},
{8, 3},
{4, 8},
{8, 4},
{5, 8},
{8, 5},
{6, 8},
{8, 6},
{7, 8}, // 70
{8, 7},
{0, 9},
{9, 0},
{1, 9},
{9, 1},
{2, 9},
{9, 2},
{3, 9},
{9, 3},
{4, 9}, // 80
{9, 4},
{5, 9},
{9, 5},
{6, 9},
{9, 6},
{7, 9},
{9, 7},
{8, 9},
{9, 8},
{0, 10}, // 90
{10, 0},
{1, 10},
{10, 1},
{2, 10},
{10, 2},
{3, 10},
{10, 3},
{4, 10},
{10, 4},
{5, 10}, // 100
{10, 5},
{6, 10},
{10, 6},
}
// clearDisplay turns off all the LEDs on the display by floating all pins.
func (d *Device) clearDisplay() {
for i := range d.pins {
d.pins[i].Float()
}
}
-325
View File
@@ -1,325 +0,0 @@
package unoqmatrix
import (
"image/color"
"testing"
pin "tinygo.org/x/drivers/internal/pin"
)
// pinState tracks the state of a mock charlieplex pin.
type pinState struct {
level bool // true=high, false=low
isOutput bool // true=output mode, false=floating (high-Z)
}
// mockPins creates 11 mock CharlieplexPins and returns them along with their observable state.
func mockPins() ([numPins]CharlieplexPin, *[numPins]pinState) {
var pins [numPins]CharlieplexPin
var states [numPins]pinState
for i := range pins {
idx := i // capture
pins[i] = CharlieplexPin{
Set: pin.OutputFunc(func(level bool) {
states[idx].isOutput = true
states[idx].level = level
}),
Float: func() {
states[idx].isOutput = false
states[idx].level = false
},
}
}
return pins, &states
}
func newTestDevice() (Device, *[numPins]pinState) {
pins, states := mockPins()
d := New(pins)
return d, states
}
func TestNew(t *testing.T) {
d, _ := newTestDevice()
w, h := d.Size()
if w != ledCols || h != ledRows {
t.Errorf("Size() = (%d, %d), want (%d, %d)", w, h, ledCols, ledRows)
}
}
func TestSize(t *testing.T) {
d, _ := newTestDevice()
w, h := d.Size()
if w != 13 {
t.Errorf("width = %d, want 13", w)
}
if h != 8 {
t.Errorf("height = %d, want 8", h)
}
}
func TestSetGetPixel(t *testing.T) {
d, _ := newTestDevice()
c := color.RGBA{R: 255, G: 128, B: 64, A: 255}
d.SetPixel(3, 2, c)
got := d.GetPixel(3, 2)
if got != c {
t.Errorf("GetPixel(3,2) = %v, want %v", got, c)
}
// Unset pixel should be zero-value.
got = d.GetPixel(0, 0)
if got != (color.RGBA{}) {
t.Errorf("GetPixel(0,0) = %v, want zero", got)
}
}
func TestClearDisplay(t *testing.T) {
d, _ := newTestDevice()
on := color.RGBA{R: 255, G: 255, B: 255, A: 255}
off := color.RGBA{A: 255}
d.SetPixel(0, 0, on)
d.SetPixel(5, 3, on)
d.ClearDisplay()
for y := int16(0); y < ledRows; y++ {
for x := int16(0); x < ledCols; x++ {
got := d.GetPixel(x, y)
if got != off {
t.Errorf("after ClearDisplay, GetPixel(%d,%d) = %v, want %v", x, y, got, off)
}
}
}
}
func TestSetRotation(t *testing.T) {
d, _ := newTestDevice()
tests := []struct {
input uint8
want uint8
}{
{0, 0},
{1, 1},
{2, 2},
{3, 3},
{4, 0}, // wraps
{7, 3}, // wraps
}
for _, tt := range tests {
d.SetRotation(tt.input)
if d.rotation != tt.want {
t.Errorf("SetRotation(%d): rotation = %d, want %d", tt.input, d.rotation, tt.want)
}
}
}
func TestConfigure(t *testing.T) {
d, _ := newTestDevice()
d.Configure(Config{Rotation: 2})
if d.rotation != 2 {
t.Errorf("Configure(Rotation:2): rotation = %d, want 2", d.rotation)
}
}
func TestDisplayEmptyBuffer(t *testing.T) {
d, states := newTestDevice()
err := d.Display()
if err != nil {
t.Fatalf("Display() error: %v", err)
}
// All pins should be floating after displaying an empty buffer.
for i, s := range states {
if s.isOutput {
t.Errorf("pin %d still in output mode after empty Display()", i)
}
}
}
func TestDisplaySinglePixel(t *testing.T) {
d, states := newTestDevice()
on := color.RGBA{R: 255, G: 255, B: 255, A: 255}
// LED index 0 -> pinMapping[0] = {0, 1}: pin 0 high, pin 1 low.
d.SetPixel(0, 0, on)
err := d.Display()
if err != nil {
t.Fatalf("Display() error: %v", err)
}
// After Display completes, all pins should be floating (last LED turned off).
for i, s := range states {
if s.isOutput {
t.Errorf("pin %d still in output mode after Display()", i)
}
}
}
func TestDisplayMultiplePixels(t *testing.T) {
d, states := newTestDevice()
on := color.RGBA{R: 255, G: 255, B: 255, A: 255}
d.SetPixel(0, 0, on) // idx 0 -> pins {0,1}
d.SetPixel(1, 0, on) // idx 1 -> pins {1,0}
d.SetPixel(2, 0, on) // idx 2 -> pins {0,2}
err := d.Display()
if err != nil {
t.Fatalf("Display() error: %v", err)
}
// All pins floating after display completes.
for i, s := range states {
if s.isOutput {
t.Errorf("pin %d still in output mode after Display()", i)
}
}
}
// pinEvent records a single pin action during Display().
type pinEvent struct {
pinIdx int
action string // "high", "low", or "float"
}
// traceDevice creates a device that records every pin event for verification.
func traceDevice() (Device, *[]pinEvent) {
var pins [numPins]CharlieplexPin
events := &[]pinEvent{}
for i := range pins {
idx := i
pins[i] = CharlieplexPin{
Set: pin.OutputFunc(func(level bool) {
action := "low"
if level {
action = "high"
}
*events = append(*events, pinEvent{pinIdx: idx, action: action})
}),
Float: func() {
*events = append(*events, pinEvent{pinIdx: idx, action: "float"})
},
}
}
d := New(pins)
return d, events
}
func TestDisplayDrivesCorrectPins(t *testing.T) {
d, events := traceDevice()
on := color.RGBA{R: 255, A: 255}
// Set pixel at (0,0) -> LED index 0 -> pinMapping[0] = {0, 1}.
d.SetPixel(0, 0, on)
d.Display()
// Expected sequence:
// 1. clearDisplay: float pins 0..10
// 2. Drive LED 0: pin 0 high, pin 1 low
// 3. Cleanup: float pin 0, float pin 1
// Find the high/low events (skip initial floats from clearDisplay).
var driveEvents []pinEvent
for _, e := range *events {
if e.action == "high" || e.action == "low" {
driveEvents = append(driveEvents, e)
}
}
if len(driveEvents) != 2 {
t.Fatalf("expected 2 drive events, got %d: %v", len(driveEvents), driveEvents)
}
if driveEvents[0].pinIdx != 0 || driveEvents[0].action != "high" {
t.Errorf("first drive event = %v, want pin 0 high", driveEvents[0])
}
if driveEvents[1].pinIdx != 1 || driveEvents[1].action != "low" {
t.Errorf("second drive event = %v, want pin 1 low", driveEvents[1])
}
}
func TestDisplaySkipsBlackPixels(t *testing.T) {
d, events := traceDevice()
on := color.RGBA{R: 255, A: 255}
// Only set one pixel in the middle of the matrix.
d.SetPixel(4, 1, on) // idx = 1*13+4 = 17 -> pinMapping[17] = {4,2}
d.Display()
var driveEvents []pinEvent
for _, e := range *events {
if e.action == "high" || e.action == "low" {
driveEvents = append(driveEvents, e)
}
}
// Should only drive one LED's worth of pin events.
if len(driveEvents) != 2 {
t.Fatalf("expected 2 drive events for 1 lit pixel, got %d", len(driveEvents))
}
if driveEvents[0].pinIdx != 4 || driveEvents[0].action != "high" {
t.Errorf("expected pin 4 high, got %v", driveEvents[0])
}
if driveEvents[1].pinIdx != 2 || driveEvents[1].action != "low" {
t.Errorf("expected pin 2 low, got %v", driveEvents[1])
}
}
func TestDisplayFloatsBetweenLEDs(t *testing.T) {
d, events := traceDevice()
on := color.RGBA{R: 255, A: 255}
d.SetPixel(0, 0, on) // idx 0 -> {0,1}
d.SetPixel(1, 0, on) // idx 1 -> {1,0}
d.Display()
// After the initial clearDisplay floats, the sequence for two LEDs should be:
// drive LED0 (pin0 high, pin1 low)
// float pin0, float pin1 (between LEDs)
// drive LED1 (pin1 high, pin0 low)
// float pin1, float pin0 (cleanup)
// Skip the initial 11 float events from clearDisplay.
postClear := (*events)[numPins:]
// Verify pin 0 and 1 are floated between the two LEDs.
foundFloatBetween := false
driveCount := 0
for _, e := range postClear {
if e.action == "high" || e.action == "low" {
driveCount++
}
// After the first pair of drive events, we should see floats before the next pair.
if driveCount == 2 && e.action == "float" {
foundFloatBetween = true
break
}
}
if !foundFloatBetween {
t.Error("expected float events between LED drives, found none")
}
}
func TestPinMappingLength(t *testing.T) {
expected := 104 // 8x13 matrix = 104 LEDs
if len(pinMapping) != expected {
t.Errorf("pinMapping has %d entries, want %d", len(pinMapping), expected)
}
}
func TestPinMappingIndicesInRange(t *testing.T) {
for i, pair := range pinMapping {
if pair[0] >= numPins {
t.Errorf("pinMapping[%d][0] = %d, exceeds numPins (%d)", i, pair[0], numPins)
}
if pair[1] >= numPins {
t.Errorf("pinMapping[%d][1] = %d, exceeds numPins (%d)", i, pair[1], numPins)
}
if pair[0] == pair[1] {
t.Errorf("pinMapping[%d] has same pin for both: %d", i, pair[0])
}
}
}
-36
View File
@@ -1,36 +0,0 @@
//go:build baremetal
package unoqmatrix
import (
"machine"
pin "tinygo.org/x/drivers/internal/pin"
)
// NewFromBasePin creates a Device from a base machine.Pin.
// It constructs 11 CharlieplexPin values from consecutive pins starting at basePin.
// Each pin lazily switches between output and input mode as needed.
func NewFromBasePin(basePin machine.Pin) Device {
var pins [numPins]CharlieplexPin
for i := range pins {
p := basePin + machine.Pin(i)
var isOutput bool
pins[i] = CharlieplexPin{
Set: pin.OutputFunc(func(level bool) {
if !isOutput {
p.Configure(machine.PinConfig{Mode: machine.PinOutput})
isOutput = true
}
p.Set(level)
}),
Float: func() {
if isOutput {
p.Configure(machine.PinConfig{Mode: machine.PinInput})
isOutput = false
}
},
}
}
return New(pins)
}
+1 -1
View File
@@ -2,4 +2,4 @@ package drivers
// Version returns a user-readable string showing the version of the drivers package for support purposes.
// Update this value before release of new version of software.
const Version = "0.35.0"
const Version = "0.33.0"
-104
View File
@@ -1,104 +0,0 @@
package w5500
import "time"
func (d *Device) irqPoll(sockn uint8, state uint8, deadline time.Time) uint8 {
waitTime := 500 * time.Microsecond
for {
if !deadline.IsZero() && time.Now().After(deadline) {
// If a deadline is set and it has passed, return 0.
return sockIntUnknown
}
irq := d.readByte(sockInt, sockAddr(sockn)) & 0b00011111
if got := irq & state; got != 0 {
// Acknowledge the interrupt.
d.writeByte(sockInt, sockAddr(sockn), got)
return got
}
d.mu.Unlock()
time.Sleep(waitTime)
// Exponential backoff for polling.
waitTime *= 2
if waitTime > 10*time.Millisecond {
waitTime = 10 * time.Millisecond
}
d.mu.Lock()
}
}
func (d *Device) read(addr uint16, bsb uint8, p []byte) {
d.cs(false)
if len(p) == 0 {
return
}
d.sendReadHeader(addr, bsb)
_ = d.bus.Tx(nil, p)
d.cs(true)
}
func (d *Device) readUint16(addr uint16, bsb uint8) uint16 {
d.cs(false)
d.sendReadHeader(addr, bsb)
buf := d.cmdBuf
_ = d.bus.Tx(nil, buf[:2])
d.cs(true)
return uint16(buf[1]) | uint16(buf[0])<<8
}
func (d *Device) readByte(addr uint16, bsb uint8) byte {
d.cs(false)
d.sendReadHeader(addr, bsb)
r, _ := d.bus.Transfer(byte(0))
d.cs(true)
return r
}
func (d *Device) write(addr uint16, bsb uint8, p []byte) {
d.cs(false)
if len(p) == 0 {
return
}
d.sendWriteHeader(addr, bsb)
_ = d.bus.Tx(p, nil)
d.cs(true)
}
func (d *Device) writeUint16(addr uint16, bsb uint8, v uint16) {
d.cs(false)
d.sendWriteHeader(addr, bsb)
buf := d.cmdBuf
buf[0] = byte(v >> 8)
buf[1] = byte(v & 0xff)
_ = d.bus.Tx(buf[:2], nil)
d.cs(true)
}
func (d *Device) writeByte(addr uint16, bsb uint8, b byte) {
d.cs(false)
d.sendWriteHeader(addr, bsb)
_, _ = d.bus.Transfer(b)
d.cs(true)
}
func (d *Device) sendReadHeader(addr uint16, bsb uint8) {
buf := d.cmdBuf
buf[0] = byte(addr >> 8)
buf[1] = byte(addr & 0xff)
buf[2] = bsb << 3
_ = d.bus.Tx(buf[:], nil)
}
func (d *Device) sendWriteHeader(addr uint16, bsb uint8) {
buf := d.cmdBuf
buf[0] = byte(addr >> 8)
buf[1] = byte(addr & 0xff)
buf[2] = bsb<<3 | 0b100
_ = d.bus.Tx(buf[:], nil)
}
-445
View File
@@ -1,445 +0,0 @@
package w5500
import (
"errors"
"net"
"net/netip"
"os"
"runtime"
"time"
"tinygo.org/x/drivers/netdev"
)
type socket struct {
sockn uint8
protocol uint8
port uint16
inUse bool
closed bool
}
func (s *socket) setProtocol(proto byte) *socket {
s.protocol = proto
return s
}
func (s *socket) setPort(port uint16) *socket {
s.port = port
return s
}
func (s *socket) setInUse(inUse bool) *socket {
s.inUse = inUse
return s
}
func (s *socket) setClosed(closed bool) *socket {
s.closed = closed
return s
}
func (s *socket) reset() {
s.protocol = 0
s.port = 0
s.inUse = false
s.closed = false
}
// GetHostByName resolves the given host name to an IP address.
func (d *Device) GetHostByName(name string) (netip.Addr, error) {
d.mu.Lock()
dns := d.dns
d.mu.Unlock()
if dns == nil {
return netip.Addr{}, netdev.ErrNotSupported
}
return dns(name)
}
func (d *Device) Socket(domain int, stype int, protocol int) (int, error) {
if domain != netdev.AF_INET {
return -1, netdev.ErrFamilyNotSupported
}
switch {
case stype == netdev.SOCK_STREAM && protocol == netdev.IPPROTO_TCP:
case stype == netdev.SOCK_DGRAM && protocol == netdev.IPPROTO_UDP:
default:
return -1, errors.New("unsupported combination of socket type and protocol")
}
var proto byte
switch protocol {
case netdev.IPPROTO_TCP:
proto = 1 // TCP
case netdev.IPPROTO_UDP:
proto = 2 // UDP
default:
return -1, netdev.ErrNotSupported
}
d.mu.Lock()
defer d.mu.Unlock()
sockfd, sock, err := d.nextSocket()
if err != nil {
return -1, err
}
d.openSocket(sock.sockn, proto)
sock.setProtocol(proto).setInUse(true)
return sockfd, nil
}
func (d *Device) openSocket(sockn uint8, proto byte) {
d.writeByte(sockMode, sockAddr(sockn), proto&0x0F)
}
func (d *Device) Bind(sockfd int, ip netip.AddrPort) error {
// The IP address is irrelevant. The configured ip will always be used.
port := ip.Port()
d.mu.Lock()
defer d.mu.Unlock()
sock, err := d.socket(sockfd)
if err != nil {
return err
}
if err = d.bindSocket(sock.sockn, port); err != nil {
return errors.New("could not set socket port: " + err.Error())
}
sock.setPort(port)
return nil
}
func (d *Device) bindSocket(sockn uint8, port uint16) error {
d.writeUint16(sockSrcPort, sockAddr(sockn), port)
d.socketSendCmd(sockn, sockCmdOpen)
if d.sockStatus(sockn) == sockStatusClosed {
return errors.New("socket is closed after binding")
}
return nil
}
// SetSockOpt sets the socket option for the given socket file descriptor.
// It is not supported by the W5500, so it always returns an error.
func (d *Device) SetSockOpt(int, int, int, any) error {
return netdev.ErrNotSupported
}
// Connect establishes a connection to the specified host and port or ip and port.
//
// If the host is an empty string, it will use the provided ip address and port,
// otherwise it will resolve the host name to an IP address.
func (d *Device) Connect(sockfd int, host string, ip netip.AddrPort) error {
destIP := ip.Addr()
if host != "" {
var err error
destIP, err = d.GetHostByName(host)
if err != nil {
return errors.New("could not resolve host " + host + ":" + err.Error())
}
}
if !destIP.IsValid() || !destIP.Is4() {
return errors.New("invalid destination IP address: " + destIP.String())
}
port := ip.Port()
d.mu.Lock()
defer d.mu.Unlock()
sock, err := d.socket(sockfd)
if err != nil {
return err
}
d.write(sockDestIP, sockAddr(sock.sockn), destIP.AsSlice())
d.writeUint16(sockDestPort, sockAddr(sock.sockn), port)
d.socketSendCmd(sock.sockn, sockCmdOpen)
return nil
}
// Listen sets the socket to listen for incoming connections on the specified socket file descriptor.
//
// The backlog parameter is ignored, as the W5500 does not support it.
func (d *Device) Listen(sockfd int, _ int) error {
d.mu.Lock()
defer d.mu.Unlock()
sock, err := d.socket(sockfd)
if err != nil {
return err
}
if sock.protocol != 1 { // Only TCP sockets can listen
return errors.New("not a TCP socket")
}
if err = d.listen(sock.sockn); err != nil {
return errors.New("could not send listen command: " + err.Error())
}
return nil
}
func (d *Device) listen(sockn uint8) error {
state := d.sockStatus(sockn)
if state != sockStatusInit {
return errors.New("socket is not in the initial state")
}
d.socketSendCmd(sockn, sockCmdListen)
return nil
}
// Accept waits for an incoming connection on the specified socket file descriptor.
func (d *Device) Accept(sockfd int) (int, netip.AddrPort, error) {
d.mu.Lock()
defer d.mu.Unlock()
lsock, err := d.socket(sockfd)
if err != nil {
return -1, netip.AddrPort{}, errors.New("could not get socket: " + err.Error())
}
if err = d.waitForEstablished(lsock.sockn); err != nil {
return -1, netip.AddrPort{}, err
}
// Acquire a new socket for the listening connection.
csockfd, csock, err := d.nextSocket()
if err != nil {
return -1, netip.AddrPort{}, err
}
// Swap the socket numbers of the client and listening sockets.
lsock.sockn, csock.sockn = csock.sockn, lsock.sockn
// Rebind the listening socket to the local address and port and start listening.
d.openSocket(lsock.sockn, lsock.protocol)
if err = d.bindSocket(lsock.sockn, lsock.port); err != nil {
return -1, netip.AddrPort{}, errors.New("could not bind listening socket: " + err.Error())
}
if err = d.listen(lsock.sockn); err != nil {
return -1, netip.AddrPort{}, errors.New("could not set listening socket: " + err.Error())
}
csock.setInUse(true)
remoteIP := d.remoteIP(csock.sockn)
return csockfd, remoteIP, nil
}
func (d *Device) waitForEstablished(sockn uint8) error {
for {
status := d.sockStatus(sockn)
switch status {
case sockStatusEstablished:
return nil
case sockStatusClosed:
return net.ErrClosed
case sockStatusCloseWait:
// The server closed the connection, so we need to reset the socket
// and set it to listen again.
if err := d.listen(sockn); err != nil {
return errors.New("could not set socket to listen: " + err.Error())
}
}
d.irqPoll(sockn, sockIntConnect|sockIntDisconnect, time.Time{})
}
}
func (d *Device) remoteIP(sockn uint8) netip.AddrPort {
var rip [4]byte
d.read(sockDestIP, sockAddr(sockn), rip[:])
var rport [2]byte
d.read(sockDestPort, sockAddr(sockn), rport[:])
return netip.AddrPortFrom(netip.AddrFrom4(rip), uint16(rport[0])<<8|uint16(rport[1]))
}
// Send sends data to the socket with the given file descriptor.
// It blocks until all data is sent or the deadline is reached.
func (d *Device) Send(sockfd int, buf []byte, _ int, deadline time.Time) (int, error) {
bufLen := len(buf)
if bufLen <= d.maxSockSize {
// Fast path for small buffers.
return d.sendChunk(sockfd, buf, deadline)
}
var n int
for i := 0; i < bufLen; i += d.maxSockSize {
end := i + d.maxSockSize
if end > bufLen {
end = bufLen
}
sent, err := d.sendChunk(sockfd, buf[i:end], deadline)
if err != nil {
return n, errors.New("could not send chunk: " + err.Error())
}
n += sent
}
return n, nil
}
func (d *Device) sendChunk(sockfd int, buf []byte, deadline time.Time) (int, error) {
d.mu.Lock()
defer d.mu.Unlock()
sock, err := d.socket(sockfd)
if err != nil {
return 0, errors.New("could not get socket: " + err.Error())
}
if sock.closed {
return 0, os.ErrClosed
}
bufLen := uint16(len(buf))
if err = d.waitForFreeBuffer(sock.sockn, bufLen, deadline); err != nil {
return 0, err
}
sendPtr := d.readUint16(sockTXWritePtr, sockAddr(sock.sockn))
d.write(sendPtr, sock.sockn<<2|0b10, buf)
d.writeUint16(sockTXWritePtr, sockAddr(sock.sockn), sendPtr+bufLen)
d.writeByte(sockCmd, sockAddr(sock.sockn), sockCmdSend)
irq := d.irqPoll(sock.sockn, sockIntSendOK|sockIntDisconnect|sockIntTimeout, deadline)
switch {
case irq == sockIntUnknown:
return 0, os.ErrDeadlineExceeded
case irq&sockIntDisconnect != 0:
sock.setClosed(true)
return 0, net.ErrClosed
case irq&sockIntTimeout != 0:
return 0, netdev.ErrTimeout
default:
return int(bufLen), nil
}
}
func (d *Device) waitForFreeBuffer(sockn uint8, len uint16, deadline time.Time) error {
for {
freeSize := d.readUint16(sockTXFreeSize, sockAddr(sockn))
if freeSize >= len {
return nil
}
if !deadline.IsZero() && time.Now().After(deadline) {
return netdev.ErrTimeout
}
status := d.sockStatus(sockn)
switch status {
case sockStatusEstablished, sockStatusCloseWait:
default:
return errors.New("socket is not in a valid state for sending data")
}
d.mu.Unlock()
time.Sleep(time.Millisecond)
d.mu.Lock()
}
}
// Recv reads data from the socket with the given file descriptor into the provided buffer.
// It blocks until data is available or the deadline is reached.
func (d *Device) Recv(sockfd int, buf []byte, _ int, deadline time.Time) (int, error) {
d.mu.Lock()
defer d.mu.Unlock()
sock, err := d.socket(sockfd)
if err != nil {
return 0, errors.New("could not get socket: " + err.Error())
}
if sock.closed {
return 0, os.ErrClosed
}
size, err := d.waitForData(sock, deadline)
if err != nil {
return 0, err
}
recvPtr := d.readUint16(sockRXReadPtr, sockAddr(sock.sockn))
buf = buf[:min(size, len(buf))]
d.read(recvPtr, sock.sockn<<2|0b00011, buf)
d.writeUint16(sockRXReadPtr, sockAddr(sock.sockn), recvPtr+uint16(len(buf)))
d.socketSendCmd(sock.sockn, sockCmdRecv)
return len(buf), nil
}
func (d *Device) waitForData(sock *socket, deadline time.Time) (int, error) {
for {
recvdSize := d.readUint16(sockRXReceivedSize, sockAddr(sock.sockn))
if recvdSize > 0 {
return int(recvdSize), nil
}
irq := d.irqPoll(sock.sockn, sockIntReceive|sockIntDisconnect, deadline)
switch {
case irq == sockIntUnknown:
return 0, os.ErrDeadlineExceeded
case irq&sockIntDisconnect != 0:
sock.setClosed(true)
return 0, net.ErrClosed
}
}
}
// Close closes the socket with the given file descriptor.
func (d *Device) Close(sockfd int) error {
d.mu.Lock()
defer d.mu.Unlock()
sock, err := d.socket(sockfd)
if err != nil {
return err
}
d.socketSendCmd(sock.sockn, sockCmdClose)
sock.reset()
return nil
}
func (d *Device) nextSocket() (int, *socket, error) {
for i, sock := range d.sockets {
if sock.inUse {
continue
}
return i, sock, nil
}
return -1, nil, netdev.ErrNoMoreSockets
}
func (d *Device) socket(sockfd int) (*socket, error) {
if sockfd < 0 || sockfd >= len(d.sockets) {
return nil, netdev.ErrInvalidSocketFd
}
return d.sockets[sockfd], nil
}
func (d *Device) socketSendCmd(sockn uint8, cmd byte) {
d.writeByte(sockCmd, sockAddr(sockn), cmd)
for d.readByte(sockCmd, sockAddr(sockn)) != 0 {
runtime.Gosched()
}
}
func (d *Device) sockStatus(sockn uint8) int {
return int(d.readByte(sockStatus, sockAddr(sockn)))
}
func sockAddr(sockn uint8) uint8 {
return sockn<<2 | 0b0001
}
-88
View File
@@ -1,88 +0,0 @@
package w5500
// Common Registers.
const (
regMode = 0x0000
regGatewayAddr = 0x0001
regSubnetMask = 0x0005
regMAC = 0x0009
regIPAddr = 0x000F
regIntLevel = 0x0013
regInt = 0x0015
regIntMask = 0x0016
regSockInt = 0x0017
regSockIntMask = 0x0018
regRetryTime = 0x0019
regRetryN = 0x001B
// ... PPP registers, not needed
regPHYCfg = 0x002E
regChipVer = 0x0039
)
// Socket Registers.
const (
sockMode = 0x0000
sockCmd = 0x0001
sockInt = 0x0002
sockStatus = 0x0003
sockSrcPort = 0x0004
sockDestMAC = 0x0006
sockDestIP = 0x000C
sockDestPort = 0x0010
sockMaxSegSize = 0x0012
sockIPTOS = 0x0015
sockIPTTL = 0x0016
sockRXBUFSize = 0x001E
sockTXBUFSize = 0x001F
sockTXFreeSize = 0x0020
sockTXReadPtr = 0x0022
sockTXWritePtr = 0x0024
sockRXReceivedSize = 0x0026
sockRXReadPtr = 0x0028
sockRXWritePtr = 0x002A
sockIntMask = 0x002C
sockKeepInt = 0x002F
)
// Socket Commands.
const (
sockCmdOpen = 0x01
sockCmdClose = 0x10
sockCmdListen = 0x02
sockCmdConnect = 0x04
sockCmdDisconnect = 0x08
sockCmdSend = 0x20
sockCmdSendMacRaw = 0x21
sockCmdSendKeep = 0x22
sockCmdRecv = 0x40
)
// Socket Statuses.
const (
sockStatusClosed = 0x00
sockStatusInit = 0x13
sockStatusListen = 0x14
sockStatusEstablished = 0x17
sockStatusCloseWait = 0x1C
sockStatusUdp = 0x22
sockStatusMacRaw = 0x42
// Temporary TCP states
sockStatusSynSent = 0x15
sockStatusSynRecv = 0x16
sockStatusFinWait = 0x18
sockStatusClosing = 0x1A
sockStatusTimeWait = 0x1B
sockStatusLastAck = 0x1D
sockStatusUnknown = 0xFF
)
// Socket Interrupts.
const (
sockIntConnect uint8 = 1 << iota
sockIntDisconnect
sockIntReceive
sockIntTimeout
sockIntSendOK
sockIntUnknown uint8 = 0
)
-248
View File
@@ -1,248 +0,0 @@
// Package w5500 implements a driver for the W5500 Ethernet controller.
//
// The driver supports basic network functionality including TCP and UDP sockets.
// It currently does not use the IRQ or RST pins.
//
// Datasheet: https://docs.wiznet.io/img/products/w5500/W5500_ds_v110e.pdf
// Product Page: https://wiznet.io/products/ethernet-chips/w5500
package w5500
import (
"errors"
"net"
"net/netip"
"sync"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/pin"
"tinygo.org/x/drivers/netdev"
)
var _ netdev.Netdever = &Device{}
// Resolver is a function that resolves a hostname to an IP address.
type Resolver func(host string) (netip.Addr, error)
// Device is a driver for the W5500 Ethernet controller.
type Device struct {
maxSockets int
maxSockSize int
mu sync.Mutex
bus drivers.SPI
cs pin.OutputFunc
dns Resolver
sockets []*socket
laddr netip.Addr
cmdBuf [3]byte
}
// New returns a new w5500 driver.
func New(bus drivers.SPI, csPin pin.Output) *Device {
return &Device{
bus: bus,
cs: csPin.Set,
}
}
// Config is the configuration for the device.
//
// The SPI bus must be fully configured.
type Config struct {
DNS Resolver
MAC net.HardwareAddr
IP netip.Addr
SubnetMask netip.Addr
Gateway netip.Addr
// Optional, default is 8.
MaxSockets int
}
// Configure sets up the device.
//
// MAC address must be provided. The other fields are optional.
func (d *Device) Configure(cfg Config) error {
d.cs(true)
d.mu.Lock()
defer d.mu.Unlock()
d.dns = cfg.DNS
d.reset()
if err := d.setupSockets(cfg.MaxSockets); err != nil {
return errors.New("could not setup sockets: " + err.Error())
}
// Set the MAC address and IP configuration.
d.write(regMAC, 0, cfg.MAC)
d.write(regIPAddr, 0, cfg.IP.AsSlice())
d.write(regSubnetMask, 0, cfg.SubnetMask.AsSlice())
d.write(regGatewayAddr, 0, cfg.Gateway.AsSlice())
d.laddr = cfg.IP
return nil
}
func (d *Device) setupSockets(maxSockets int) error {
if maxSockets == 0 {
maxSockets = 8 // Default to 8 sockets if not specified.
}
switch maxSockets {
case 1, 2, 4, 8:
// Valid socket counts.
default:
return errors.New("invalid number of sockets, must be one of 1, 2, 4, or 8")
}
socks := make([]*socket, maxSockets)
for i := range socks {
socks[i] = &socket{
sockn: uint8(i),
}
}
d.maxSockets = maxSockets
d.maxSockSize = 16 * 1024 / maxSockets
d.sockets = socks
// Set the RX and TX buffer sizes for each socket.
for i := 0; i < 8; i++ {
size := byte(d.maxSockSize >> 10)
if i >= maxSockets {
size = 0
}
d.writeByte(sockRXBUFSize, sockAddr(uint8(i)), size)
d.writeByte(sockTXBUFSize, sockAddr(uint8(i)), size)
}
mask := byte(0b11111111)
switch maxSockets {
case 1:
mask = 0b00000001
case 2:
mask = 0b00000011
case 4:
mask = 0b00001111
}
d.writeByte(regSockIntMask, 0, mask)
d.writeByte(regIntMask, 0, 0)
return nil
}
// Reset performs a soft reset.
func (d *Device) Reset() {
d.mu.Lock()
defer d.mu.Unlock()
d.reset()
}
func (d *Device) reset() {
// RST is bit 7 of regMode.
d.writeByte(regMode, 0, 0x80)
}
// GetHardwareAddr returns the hardware address of the device.
func (d *Device) GetHardwareAddr() (net.HardwareAddr, error) {
d.mu.Lock()
defer d.mu.Unlock()
mac := make([]byte, 6)
d.read(regMAC, 0, mac)
return mac, nil
}
// Addr returns the IP address of the device.
func (d *Device) Addr() (netip.Addr, error) {
d.mu.Lock()
defer d.mu.Unlock()
var ip [4]byte
d.read(regIPAddr, 0, ip[:])
return netip.AddrFrom4(ip), nil
}
// SetAddr sets the IP address of the device.
//
// The IP address must be a valid IPv4 address.
func (d *Device) SetAddr(ip netip.Addr) error {
if err := d.setAddress(regIPAddr, ip); err != nil {
return errors.New("could not set IP address: " + err.Error())
}
d.mu.Lock()
defer d.mu.Unlock()
d.laddr = ip
return nil
}
// SetSubnetMask sets the subnet mask of the device.
//
// The subnet mask must be a valid IPv4 address.
// It is not checked if the subnet mask is valid for the device's IP address.
func (d *Device) SetSubnetMask(mask netip.Addr) error {
return d.setAddress(regSubnetMask, mask)
}
// SetGateway sets the gateway address of the device.
//
// The gateway must be a valid IPv4 address.
// It is not checked if the gateway is in the same subnet as the device.
func (d *Device) SetGateway(gateway netip.Addr) error {
return d.setAddress(regGatewayAddr, gateway)
}
func (d *Device) setAddress(addr uint16, ip netip.Addr) error {
if !ip.IsValid() || !ip.Is4() {
return errors.New("invalid IP address: " + ip.String())
}
d.mu.Lock()
defer d.mu.Unlock()
d.write(addr, 0, ip.AsSlice())
return nil
}
// LinkStatus is the link status of the device.
type LinkStatus = uint8
// LinkStatus values.
const (
LinkStatusDown LinkStatus = iota
LinkStatusUp
)
// LinkStatus returns the current link status of the device.
func (d *Device) LinkStatus() LinkStatus {
d.mu.Lock()
defer d.mu.Unlock()
return d.readByte(regPHYCfg, 0) & 0b00000001
}
// LinkInfo returns the current link information of the device.
func (d *Device) LinkInfo() string {
d.mu.Lock()
defer d.mu.Unlock()
linkInfo := d.readByte(regPHYCfg, 0) & 0b00000110
speed := "10Mbps"
if linkInfo&0b00000010 != 0 {
speed = "100Mbps"
}
duplex := "Half Duplex"
if linkInfo&0b00000100 != 0 {
duplex = "Full Duplex"
}
return speed + " " + duplex
}
-465
View File
@@ -1,465 +0,0 @@
// Package epd2in9v2 implements a driver for the Waveshare 2.9in V2 black and white e-paper display.
//
// This is for the V2 device using the SSD1680 chipset. For the V1 device (using IL3820),
// use the epd2in9 package instead.
//
// Datasheet:
// https://files.waveshare.com/upload/7/79/2.9inch-e-paper-v2-specification.pdf
// https://cdn-learn.adafruit.com/assets/assets/000/097/631/original/SSD1680_Datasheet.pdf?1607625960
//
// Reference: https://github.com/waveshareteam/e-Paper/tree/master/RaspberryPi_JetsonNano/c/lib/e-Paper
package epd2in9v2 // import "tinygo.org/x/drivers/waveshare-epd/epd2in9v2"
import (
"image/color"
"machine"
"time"
"tinygo.org/x/drivers"
)
type Config struct {
Width int16
Height int16
Rotation Rotation
Speed Speed
Blocking bool
}
type Device struct {
bus drivers.SPI
cs machine.Pin
dc machine.Pin
rst machine.Pin
busy machine.Pin
width int16
height int16
buffer []uint8
bufferLength uint32
rotation Rotation
speed Speed
blocking bool
}
type Rotation uint8
type Speed uint8
// LUT for normal full refresh (~2s)
var lutDefault = [159]uint8{
0x80, 0x66, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x0, 0x0, 0x0,
0x10, 0x66, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20, 0x0, 0x0, 0x0,
0x80, 0x66, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x0, 0x0, 0x0,
0x10, 0x66, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x14, 0x8, 0x0, 0x0, 0x0, 0x0, 0x1,
0xA, 0xA, 0x0, 0xA, 0xA, 0x0, 0x1,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x14, 0x8, 0x0, 0x1, 0x0, 0x0, 0x1,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x0, 0x0, 0x0,
0x22, 0x17, 0x41, 0x0, 0x32, 0x36,
}
// LUT for fast full refresh (~1s)
var lutFast = [159]uint8{
0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x19, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x24, 0x42, 0x22, 0x22, 0x23, 0x32, 0x00, 0x00, 0x00,
0x22, 0x17, 0x41, 0xAE, 0x32, 0x38,
}
// LUT for partial refresh
var lutPartial = [159]uint8{
0x0, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x80, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x40, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0A, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2,
0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x0, 0x0, 0x0,
0x22, 0x17, 0x41, 0xB0, 0x32, 0x36,
}
// New returns a new epd2in9v2 driver. Pass in a fully configured SPI bus.
func New(bus drivers.SPI, csPin, dcPin, rstPin, busyPin machine.Pin) Device {
csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
rstPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
busyPin.Configure(machine.PinConfig{Mode: machine.PinInput})
return Device{
bus: bus,
cs: csPin,
dc: dcPin,
rst: rstPin,
busy: busyPin,
}
}
// Configure sets up the device.
func (d *Device) Configure(cfg Config) {
if cfg.Width != 0 {
d.width = cfg.Width
} else {
d.width = EPD_WIDTH
}
if cfg.Height != 0 {
d.height = cfg.Height
} else {
d.height = EPD_HEIGHT
}
d.rotation = cfg.Rotation
d.speed = cfg.Speed
d.blocking = cfg.Blocking
d.bufferLength = (uint32(d.width) * uint32(d.height)) / 8
d.buffer = make([]uint8, d.bufferLength)
for i := uint32(0); i < d.bufferLength; i++ {
d.buffer[i] = 0xFF
}
d.Reset()
time.Sleep(100 * time.Millisecond)
d.WaitUntilIdle()
d.SendCommand(SW_RESET)
d.WaitUntilIdle()
d.SendCommand(DRIVER_OUTPUT_CONTROL)
d.SendData(uint8((d.height - 1) & 0xFF))
d.SendData(uint8((d.height - 1) >> 8))
d.SendData(0x00)
d.SendCommand(DATA_ENTRY_MODE)
d.SendData(0x03)
d.setWindow(0, 0, d.width-1, d.height-1)
if cfg.Speed == SPEED_FAST {
d.SendCommand(BORDER_WAVEFORM_CONTROL)
d.SendData(0x05)
}
d.SendCommand(DISPLAY_UPDATE_CONTROL_1)
d.SendData(0x00)
d.SendData(0x80)
d.setCursor(0, 0)
d.WaitUntilIdle()
switch cfg.Speed {
case SPEED_FAST:
d.setLUTByHost(&lutFast)
default:
d.setLUTByHost(&lutDefault)
}
}
// HardwareReset resets the device via the RST pin.
func (d *Device) Reset() {
d.rst.High()
time.Sleep(10 * time.Millisecond)
d.rst.Low()
time.Sleep(2 * time.Millisecond)
d.rst.High()
time.Sleep(10 * time.Millisecond)
}
// SendCommand sends a command byte to the display.
func (d *Device) SendCommand(command uint8) {
d.dc.Low()
d.cs.Low()
d.bus.Transfer(command)
d.cs.High()
}
// SendData sends a data byte to the display.
func (d *Device) SendData(data uint8) {
d.dc.High()
d.cs.Low()
d.bus.Transfer(data)
d.cs.High()
}
// WaitUntilIdle waits until the display is ready.
// On SSD1680, BUSY pin is HIGH when busy, LOW when idle.
func (d *Device) WaitUntilIdle() {
for d.busy.Get() {
time.Sleep(50 * time.Millisecond)
}
time.Sleep(50 * time.Millisecond)
}
// IsBusy returns the busy status of the display.
func (d *Device) IsBusy() bool {
return d.busy.Get()
}
// SetPixel modifies the internal buffer in a single pixel.
// Uses color.RGBA where black (0,0,0) = black pixel, anything else = white pixel.
func (d *Device) SetPixel(x int16, y int16, c color.RGBA) {
x, y = d.xy(x, y)
if x < 0 || x >= d.width || y < 0 || y >= d.height {
return
}
byteIndex := (y * (d.width / 8)) + (x / 8)
if c.R == 0 && c.G == 0 && c.B == 0 {
d.buffer[byteIndex] &^= 0x80 >> uint8(x%8)
} else {
d.buffer[byteIndex] |= 0x80 >> uint8(x%8)
}
}
// Display sends the buffer to the screen.
func (d *Device) Display() error {
if d.blocking {
d.WaitUntilIdle()
}
d.setCursor(0, 0)
d.SendCommand(WRITE_RAM_BW)
for i := uint32(0); i < d.bufferLength; i++ {
d.SendData(d.buffer[i])
}
d.turnOnDisplay()
if d.blocking {
d.WaitUntilIdle()
}
return nil
}
// DisplayWithBase writes the buffer to both BW and RED RAM then refreshes.
// This is useful before partial updates to set the base image.
func (d *Device) DisplayWithBase() error {
if d.blocking {
d.WaitUntilIdle()
}
d.setCursor(0, 0)
d.SendCommand(WRITE_RAM_BW)
for i := uint32(0); i < d.bufferLength; i++ {
d.SendData(d.buffer[i])
}
d.setCursor(0, 0)
d.SendCommand(WRITE_RAM_RED)
for i := uint32(0); i < d.bufferLength; i++ {
d.SendData(d.buffer[i])
}
d.turnOnDisplay()
if d.blocking {
d.WaitUntilIdle()
}
return nil
}
// DisplayPartial performs a partial refresh of the display.
// Call DisplayWithBase first to set the base image before using partial updates.
func (d *Device) DisplayPartial() error {
d.rst.Low()
time.Sleep(1 * time.Millisecond)
d.rst.High()
time.Sleep(2 * time.Millisecond)
d.setLUT(&lutPartial)
d.SendCommand(OTP_SELECTION_CONTROL)
d.SendData(0x00)
d.SendData(0x00)
d.SendData(0x00)
d.SendData(0x00)
d.SendData(0x00)
d.SendData(0x40)
d.SendData(0x00)
d.SendData(0x00)
d.SendData(0x00)
d.SendData(0x00)
d.SendCommand(BORDER_WAVEFORM_CONTROL)
d.SendData(0x80)
d.SendCommand(DISPLAY_UPDATE_CONTROL_2)
d.SendData(0xC0)
d.SendCommand(MASTER_ACTIVATION)
d.WaitUntilIdle()
d.setWindow(0, 0, d.width-1, d.height-1)
d.setCursor(0, 0)
d.SendCommand(WRITE_RAM_BW)
for i := uint32(0); i < d.bufferLength; i++ {
d.SendData(d.buffer[i])
}
d.turnOnDisplayPartial()
d.WaitUntilIdle()
return nil
}
// ClearDisplay erases the display.
func (d *Device) ClearDisplay() {
d.ClearBuffer()
d.Display()
}
// ClearBuffer sets the buffer to 0xFF (white).
func (d *Device) ClearBuffer() {
for i := uint32(0); i < d.bufferLength; i++ {
d.buffer[i] = 0xFF
}
}
// Size returns the current size of the display.
func (d *Device) Size() (w, h int16) {
if d.rotation == ROTATION_90 || d.rotation == ROTATION_270 {
return d.height, d.width
}
return d.width, d.height
}
// SetRotation changes the rotation (clock-wise) of the device.
func (d *Device) SetRotation(rotation Rotation) {
d.rotation = rotation
}
// SetBlocking changes the blocking flag of the device.
func (d *Device) SetBlocking(blocking bool) {
d.blocking = blocking
}
// SetSpeed changes the refresh speed and reconfigures the device.
func (d *Device) SetSpeed(speed Speed) {
d.Configure(Config{
Width: d.width,
Height: d.height,
Rotation: d.rotation,
Speed: speed,
Blocking: d.blocking,
})
}
// Sleep puts the display into deep sleep mode. A hardware reset is needed to wake it.
func (d *Device) Sleep() {
d.SendCommand(DEEP_SLEEP_MODE)
d.SendData(0x01)
time.Sleep(100 * time.Millisecond)
}
// PowerOff disables the display analog/clock. Lighter than Sleep.
func (d *Device) PowerOff() {
d.SendCommand(DISPLAY_UPDATE_CONTROL_2)
d.SendData(0x03)
d.SendCommand(MASTER_ACTIVATION)
d.WaitUntilIdle()
}
func (d *Device) xy(x, y int16) (int16, int16) {
switch d.rotation {
case NO_ROTATION:
return x, y
case ROTATION_90:
return d.width - y - 1, x
case ROTATION_180:
return d.width - x - 1, d.height - y - 1
case ROTATION_270:
return y, d.height - x - 1
}
return x, y
}
func (d *Device) setWindow(xStart, yStart, xEnd, yEnd int16) {
d.SendCommand(SET_RAM_X_ADDRESS)
d.SendData(uint8((xStart >> 3) & 0xFF))
d.SendData(uint8((xEnd >> 3) & 0xFF))
d.SendCommand(SET_RAM_Y_ADDRESS)
d.SendData(uint8(yStart & 0xFF))
d.SendData(uint8((yStart >> 8) & 0xFF))
d.SendData(uint8(yEnd & 0xFF))
d.SendData(uint8((yEnd >> 8) & 0xFF))
}
func (d *Device) setCursor(x, y int16) {
d.SendCommand(SET_RAM_X_COUNTER)
d.SendData(uint8(x & 0xFF))
d.SendCommand(SET_RAM_Y_COUNTER)
d.SendData(uint8(y & 0xFF))
d.SendData(uint8((y >> 8) & 0xFF))
}
func (d *Device) turnOnDisplay() {
d.SendCommand(DISPLAY_UPDATE_CONTROL_2)
d.SendData(0xC7)
d.SendCommand(MASTER_ACTIVATION)
d.WaitUntilIdle()
}
func (d *Device) turnOnDisplayPartial() {
d.SendCommand(DISPLAY_UPDATE_CONTROL_2)
d.SendData(0x0F)
d.SendCommand(MASTER_ACTIVATION)
d.WaitUntilIdle()
}
func (d *Device) setLUT(lut *[159]uint8) {
d.SendCommand(WRITE_LUT_REGISTER)
for i := 0; i < 153; i++ {
d.SendData(lut[i])
}
d.WaitUntilIdle()
}
func (d *Device) setLUTByHost(lut *[159]uint8) {
d.setLUT(lut)
d.SendCommand(END_OPTION)
d.SendData(lut[153])
d.SendCommand(GATE_DRIVING_VOLTAGE)
d.SendData(lut[154])
d.SendCommand(SOURCE_DRIVING_VOLTAGE)
d.SendData(lut[155])
d.SendData(lut[156])
d.SendData(lut[157])
d.SendCommand(WRITE_VCOM_REGISTER)
d.SendData(lut[158])
}
-50
View File
@@ -1,50 +0,0 @@
package epd2in9v2
// Commands from SSD1680 datasheet
const (
EPD_WIDTH = 128
EPD_HEIGHT = 296
DRIVER_OUTPUT_CONTROL = 0x01
GATE_DRIVING_VOLTAGE = 0x03
SOURCE_DRIVING_VOLTAGE = 0x04
DEEP_SLEEP_MODE = 0x10
DATA_ENTRY_MODE = 0x11
SW_RESET = 0x12
MASTER_ACTIVATION = 0x20
DISPLAY_UPDATE_CONTROL_1 = 0x21
DISPLAY_UPDATE_CONTROL_2 = 0x22
WRITE_RAM_BW = 0x24
WRITE_RAM_RED = 0x26
VCOM_SENSE = 0x28
VCOM_SENSE_DURATION = 0x29
PROGRAM_VCOM_OTP = 0x2A
WRITE_VCOM_CONTROL = 0x2B
WRITE_VCOM_REGISTER = 0x2C
OTP_READ_DISPLAY_OPTION = 0x2D
USER_ID_READ = 0x2E
PROGRAM_WS_OTP = 0x30
LOAD_WS_OTP = 0x31
WRITE_LUT_REGISTER = 0x32
PROGRAM_OTP_SELECTION = 0x36
OTP_SELECTION_CONTROL = 0x37
WRITE_USER_ID = 0x38
OTP_PROGRAM_MODE = 0x39
BORDER_WAVEFORM_CONTROL = 0x3C
END_OPTION = 0x3F
SET_RAM_X_ADDRESS = 0x44
SET_RAM_Y_ADDRESS = 0x45
SET_RAM_X_COUNTER = 0x4E
SET_RAM_Y_COUNTER = 0x4F
SET_ANALOG_BLOCK_CONTROL = 0x74
SET_DIGITAL_BLOCK_CONTROL = 0x7E
NO_ROTATION Rotation = 0
ROTATION_90 Rotation = 1
ROTATION_180 Rotation = 2
ROTATION_270 Rotation = 3
SPEED_DEFAULT Speed = 0
SPEED_FAST Speed = 1
SPEED_PARTIAL Speed = 2
)

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