Compare commits

...

31 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
Pat Whittingslow bd88b70511 regmap: Add Device8I2C/SPI types and their logic (#801)
* add regmap Device8I2C/SPI types and their methods
* add endianess hint
2025-11-09 14:05:30 +01:00
Yurii Soldak 34da2d208a lsm9ds1: avoid unnecessary heap allocations 2025-11-08 10:53:22 +01:00
Pat Whittingslow 5cb360a4bf Add Honeywell HSC TruStability SPI+I2C pressure sensor driver (#799)
* add honeywell pressure sensor
* apply @aykevl suggestions
2025-11-08 10:45:18 +01:00
Ayke van Laethem 51b604ce97 lis3dh: add Update and Acceleration calls
This adjusts the API to the one proposed in
https://github.com/tinygo-org/drivers/pull/345, which I think is much
better than direct ReadAcceleration etc calls.

I have also updated the code that converts raw acceleration values to
normalized values. The new code should be faster (didn't measure) and
avoids floating point math.
2025-11-08 10:34:37 +01:00
Ayke van Laethem ec680be784 lis3dh: use correct error handling and make configurable
Instead of printing an error, this driver really should be returning
errors instead. Also, `Configure` didn't have a way to actually
configure the driver. This is now added, and can be expanded in the
future.

This is a breaking change.
2025-11-08 10:34:37 +01:00
Pat Whittingslow 5fb935001e PinInput+PinOutput HAL (#753, reloaded) (#795)
* first commit: add HAL and uc8151 driver demo
* unexport drivers.PinOutput/Input HAL
* fix non-tinygo pin config build
* change of heart
* docs: corrected some comments that were not changed at the same time as recent renaming
2025-11-08 10:21:01 +01:00
Martin Heck 297ad416d3 fix: add RP2350 to quadrature_interrupt.go 2025-09-23 14:03:35 +02:00
soypat 3fa08112db add regmap package to facilitate heapless driver development 2025-09-14 08:12:32 -04:00
Bryan Souza b639f7b12e added support for P25Q16H flash chip for xiao-ble target; 2025-09-14 08:11:14 -04:00
Bryan Souza 28d625abfd added support for W25Q80DV flash chip for xiao-ble target; 2025-09-14 08:11:14 -04:00
deadprogram 228e57cf98 release: prepare for 0.33.0 drivers release
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-08-19 21:57:30 +02:00
deadprogram 6cf1eb86e5 fix: correct smoke tests for Adafruit Seesaw
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-08-18 14:51:03 +02:00
JP Hastings-Spital 857ab80ae6 feat: add support for seesaw encoders
Adds the necessary function addresses for reading and writing encoders on a seesaw.
Also provides two helper functions to make this easier.
2025-08-18 14:15:48 +02:00
Russel Hunter Yukawa a31ba26a6c Fix gps time calculation (#785)
* Change test case to match the date patterns where the bug reproduces
* Fix RMC date and time calculation
2025-08-13 09:39:18 +02:00
Bryan Souza 303ec94529 added support for LSM303DLHC e-Compass; (#783)
fixed the spelling in the Connection error message; Initial support for LSM303DLHC added;
Added LSM303DLHC to smoketest and added an example;
Removed unnecessary comments;
fixed format error;
squashed and ready for merge;
2025-08-11 08:47:22 +02:00
Artur Nasyrov 833990f44d Add ens160 i2c driver
Driver for ENS160 sensor:
https://www.sciosense.com/wp-content/uploads/2023/12/ENS160-Datasheet.pdf
2025-08-10 10:05:18 +02:00
Ayke van Laethem 28d87eb0c5 ws2812: add RP2350 support
Adding 150MHz support for the RP2350
2025-08-10 10:05:18 +02:00
Yurii Soldak ae9e8f915e ssd1306: avoid unnecessary heap allocations (#767)
* ssd1306: avoid unnecessary heap allocations

* ssd1306: extract i2c and spi bus implementations

* ssd1306: refactor tests -- show fps and heap usage

* ssd1306: bring back the lost exported methods

* Adjust examples

* Fix smoketests for ssd1306
2025-08-10 10:05:18 +02:00
JP Hastings-Spital 45fad80c3e feat: allow gps init with address
Adafruit's Mini GPS PA1010D Module works with this device driver, but requires 0x10 as the address, rather than 0x42.

This change allows the device to be initialised with whatever i2c address is needed, while maintaining backward compatibility.

Adds new constants to allow easy configuration of both the ublox device and the PA1010D.
2025-08-10 10:05:18 +02:00
Yurii Soldak 0304d30b78 lsm6ds3tr: avoid unnecessary heap allocations (#766)
* lsm6ds3tr: avoid unnecessary heap allocations
* lsm6ds3tr: use helper functions, for readability
* lsm6ds3tr: return slice of the internal buffer on readBytes
2025-08-10 10:05:18 +02:00
Ron Evans 7de0a0814e Revert "add regmap package to facilitate heapless driver development (#768)" (#776)
This reverts commit 80356fd9d9.
2025-07-14 11:16:56 -03:00
Patricio Whittingslow 80356fd9d9 add regmap package to facilitate heapless driver development (#768) 2025-07-13 09:58:49 -03:00
deadprogram c4ff8242a7 all: updates for drivers release v0.32.0
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-06-15 22:36:34 +02:00
soypat 82c41dbf14 add driver design pointer to CONTRIBUTING.md 2025-05-28 11:03:09 +02:00
Hikmatulloh Hari Mukti c4168864fd bmp280: remove alloc on read sensor data 2025-05-28 10:29:08 +02:00
Leon Matthews dbc9022f6a ws2812: add 200MHz support for the Cortex-M0/rp2040 2025-05-20 13:09:35 +02:00
Mateusz Nowak d41bc0b85f fix: remove time.Sleep from SSD1306 SPI transfer code 2025-05-20 12:43:13 +02:00
Craig Swank e7f90166ad tmc2209 bug fixes (#755)
* Make write buffer big enough for crc
* crc according to datasheet
* fix build
* a correct crc func already exists
2025-05-20 12:27:30 +02:00
56 changed files with 3296 additions and 494 deletions
+52
View File
@@ -1,3 +1,55 @@
0.33.0
---
- **new devices**
- **ens160**
- Add ens160 i2c driver
- **lsm303dlhc**
- added support for LSM303DLHC e-Compass; (#783)
- **seesaw**
- add support for Adafruit Seesaw encoders
- **enhancements**
- **ws2812**
- add RP2350 support
- **ssd1306**
- avoid unnecessary heap allocations (#767)
- **gps**
- allow gps init with address
- **lsm6ds3tr**
- avoid unnecessary heap allocations (#766)
- **bugfixes**
- **gps**
- Fix gps time calculation (#785)
0.32.0
---
- **enhancements**
- **bmp280**
- remove alloc on read sensor data
- **ws2812**
- add 200MHz support for the Cortex-M0/rp2040
- **bugfixes**
- **ssd1306**
- remove time.Sleep from SSD1306 SPI transfer code
- **tmc2209**
- tmc2209 bug fixes (#755)
- **docs**
- **contributing**
- add driver design pointer to CONTRIBUTING.md
0.31.0
---
---
- **enhancements**
- **spi**
- update all SPI usage to use either *machine.SPI or drivers.SPI
0.30.0
---
- **new devices**
+3
View File
@@ -8,6 +8,9 @@ We would like your help to make this project better, so we appreciate any contri
We'd love to get your feedback on getting started with TinyGo. Run into any difficulty, confusion, or anything else? You are not alone. We want to know about your experience, so we can help the next people. Please open a Github issue with your questions, or you can also get in touch directly with us on our Slack channel at [https://gophers.slack.com/messages/CDJD3SUP6](https://gophers.slack.com/messages/CDJD3SUP6).
### Driver design
Before porting or writing a driver from scratch please read **[Driver Design for TinyGo](https://tinygo.org/docs/guides/driver-design)**.
### One of the TinyGo drivers is not working as you expect
Please open a Github issue with your problem, and we will be happy to assist.
+7 -8
View File
@@ -23,6 +23,7 @@ type Filter uint
type Device struct {
bus drivers.I2C
Address uint16
buf [6]byte
cali calibrationCoefficients
Temperature Oversampling
Pressure Oversampling
@@ -134,8 +135,8 @@ func (d *Device) PrintCali() {
// ReadTemperature returns the temperature in celsius milli degrees (°C/1000).
func (d *Device) ReadTemperature() (temperature int32, err error) {
data, err := d.readData(REG_TEMP, 3)
if err != nil {
data := d.buf[:3]
if err = d.readData(REG_TEMP, data); err != nil {
return
}
@@ -158,8 +159,8 @@ func (d *Device) ReadTemperature() (temperature int32, err error) {
// ReadPressure returns the pressure in milli pascals (mPa).
func (d *Device) ReadPressure() (pressure int32, err error) {
// First 3 bytes are Pressure, last 3 bytes are Temperature
data, err := d.readData(REG_PRES, 6)
if err != nil {
data := d.buf[:6]
if err = d.readData(REG_PRES, data); err != nil {
return
}
@@ -203,7 +204,7 @@ func (d *Device) ReadPressure() (pressure int32, err error) {
}
// readData reads n number of bytes of the specified register
func (d *Device) readData(register int, n int) ([]byte, error) {
func (d *Device) readData(register int, data []byte) error {
// If not in normal mode, set the mode to FORCED mode, to prevent incorrect measurements
// After the measurement in FORCED mode, the sensor will return to SLEEP mode
if d.Mode != MODE_NORMAL {
@@ -218,9 +219,7 @@ func (d *Device) readData(register int, n int) ([]byte, error) {
}
// Read the requested register
data := make([]byte, n)
err := legacy.ReadRegister(d.bus, uint8(d.Address), uint8(register), data[:])
return data, err
return legacy.ReadRegister(d.bus, uint8(d.Address), uint8(register), data[:])
}
// convert3Bytes converts three bytes to int32
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build tinygo && (rp2040 || stm32 || k210 || esp32c3 || nrf || sam || (avr && (atmega328p || atmega328pb)))
//go:build tinygo && (rp2040 || rp2350 || stm32 || k210 || esp32c3 || nrf || sam || (avr && (atmega328p || atmega328pb)))
// Implementation based on:
// https://gist.github.com/aykevl/3fc1683ed77bb0a9c07559dfe857304a
+225
View File
@@ -0,0 +1,225 @@
// Package ens160 provides a driver for the ScioSense ENS160 digital gas sensor.
//
// Datasheet: https://www.sciosense.com/wp-content/uploads/2023/12/ENS160-Datasheet.pdf
package ens160
import (
"encoding/binary"
"errors"
"time"
"tinygo.org/x/drivers"
)
const (
defaultTimeout = 30 * time.Millisecond
shortTimeout = 1 * time.Millisecond
)
// Conversion constants for environment data compensation.
const (
kelvinOffsetMilli = 273150 // 273.15 K in milli-units
tempRawFactor = 64 // As per datasheet for TEMP_IN
humRawFactor = 512 // As per datasheet for RH_IN
milliFactor = 1000 // For converting from milli-units
roundingTerm = milliFactor / 2 // For rounding before integer division
)
// validityStrings provides human-readable descriptions for validity flags.
var validityStrings = [...]string{
ValidityNormalOperation: "normal operation",
ValidityWarmUpPhase: "warm-up phase, wait ~3 minutes for valid data",
ValidityInitialStartUpPhase: "initial start-up phase, wait ~1 hour for valid data",
ValidityInvalidOutput: "invalid output",
}
// Device wraps an I2C connection to an ENS160 device.
type Device struct {
bus drivers.I2C // I²C implementation
addr uint16 // 7bit bus address, promoted to uint16 per drivers.I2C
// shadow registers / last measurements
lastTvocPPB uint16
lastEco2PPM uint16
lastAqiUBA uint8
lastValidity uint8 // Store the latest validity status
// preallocated buffers
wbuf [5]byte // longest write: reg + 4bytes (TEMP+RH)
rbuf [5]byte // longest read: DATA burst (5bytes)
}
// New returns a new ENS160 driver.
func New(bus drivers.I2C, addr uint16) *Device {
if addr == 0 {
addr = DefaultAddress
}
return &Device{
bus: bus,
addr: addr,
lastValidity: ValidityInvalidOutput,
}
}
// Connected returns whether a ENS160 has been found.
func (d *Device) Connected() bool {
d.wbuf[0] = regPartID
err := d.bus.Tx(d.addr, d.wbuf[:1], d.rbuf[:2])
return err == nil && d.rbuf[0] == LowPartID && d.rbuf[1] == HighPartID
}
// Configure sets up the device for reading.
func (d *Device) Configure() error {
// 1. Soft-reset. The device will automatically enter IDLE mode.
if err := d.write1(regOpMode, ModeReset); err != nil {
return err
}
time.Sleep(defaultTimeout)
// 2. Clear GPR registers, then go to STANDARD mode.
if err := d.write1(regCommand, cmdClrGPR); err != nil {
return err
}
time.Sleep(defaultTimeout)
if err := d.write1(regOpMode, ModeStandard); err != nil {
return err
}
time.Sleep(defaultTimeout)
return nil
}
// calculateTempRaw converts temperature from milli-degrees Celsius to the sensor's raw format.
func calculateTempRaw(tempMilliC int32) uint16 {
// Clip temperature
const (
minC = -40 * 1000
maxC = 85 * 1000
)
if tempMilliC < minC {
tempMilliC = minC
} else if tempMilliC > maxC {
tempMilliC = maxC
}
// Integer fixed-point conversion to format required by the sensor.
// Formula from datasheet: T_IN = (T_ambient_C + 273.15) * 64
return uint16((((tempMilliC + kelvinOffsetMilli) * tempRawFactor) + roundingTerm) / milliFactor)
}
// calculateHumRaw converts relative humidity from milli-percent to the sensor's raw format.
func calculateHumRaw(rhMilliPct int32) uint16 {
// Clip humidity
if rhMilliPct < 0 {
rhMilliPct = 0
} else if rhMilliPct > 100*1000 {
rhMilliPct = 100 * 1000
}
// Integer fixed-point conversion to format required by the sensor.
// Formula from datasheet: RH_IN = (RH_ambient_% * 512)
return uint16(((rhMilliPct * humRawFactor) + roundingTerm) / milliFactor)
}
// SetEnvDataMilli sets the ambient temperature and humidity for compensation.
//
// tempMilliC is the temperature in milli-degrees Celsius.
// rhMilliPct is the relative humidity in milli-percent.
func (d *Device) SetEnvDataMilli(tempMilliC, rhMilliPct int32) error {
tempRaw := calculateTempRaw(tempMilliC)
humRaw := calculateHumRaw(rhMilliPct)
d.wbuf[0] = regTempIn // start address (autoincrement)
binary.LittleEndian.PutUint16(d.wbuf[1:3], tempRaw)
binary.LittleEndian.PutUint16(d.wbuf[3:5], humRaw)
return d.bus.Tx(d.addr, d.wbuf[:5], nil)
}
// Update refreshes the concentration measurements.
func (d *Device) Update(which drivers.Measurement) error {
if which&drivers.Concentration == 0 {
return nil // nothing requested
}
const maxTries = 1000
var (
status uint8
validity uint8
)
var gotData bool
// Poll DEVICE_STATUS until NEWDAT or timeout
for range maxTries {
var err error
status, err = d.read1(regStatus)
if err != nil {
return err
}
if status&statusSTATER != 0 {
return errors.New("ENS160: error (STATER set)")
}
validity = (status & statusValidityMask) >> statusValidityShift
if status&statusNEWDAT != 0 {
gotData = true
break // Always break when data available
}
time.Sleep(shortTimeout)
}
if !gotData {
return errors.New("ENS160: timeout waiting for NEWDAT")
}
// Burst-read data regardless of validity state
d.wbuf[0] = regAQI
if err := d.bus.Tx(d.addr, d.wbuf[:1], d.rbuf[:5]); err != nil {
return errors.New("ENS160: burst read failed")
}
d.lastAqiUBA = d.rbuf[0]
d.lastTvocPPB = binary.LittleEndian.Uint16(d.rbuf[1:3])
d.lastEco2PPM = binary.LittleEndian.Uint16(d.rbuf[3:5])
d.lastValidity = validity // Store the validity status
return nil
}
// TVOC returns the last totalVOC concentration in partsperbillion.
func (d *Device) TVOC() uint16 { return d.lastTvocPPB }
// ECO2 returns the last equivalent CO₂ concentration in partspermillion.
func (d *Device) ECO2() uint16 { return d.lastEco2PPM }
// AQI returns the last AirQuality Index according to UBA (15).
func (d *Device) AQI() uint8 { return d.lastAqiUBA }
// Validity returns the current operating state of the sensor.
func (d *Device) Validity() uint8 {
return d.lastValidity
}
// ValidityString returns a human-readable string describing the current validity status.
func (d *Device) ValidityString() string {
if int(d.lastValidity) < len(validityStrings) {
return validityStrings[d.lastValidity]
}
return "unknown"
}
// write1 writes a single byte to a register.
func (d *Device) write1(reg, val uint8) error {
d.wbuf[0] = reg
d.wbuf[1] = val
return d.bus.Tx(d.addr, d.wbuf[:2], nil)
}
// read1 reads a single byte from a register.
func (d *Device) read1(reg uint8) (uint8, error) {
d.wbuf[0] = reg
if err := d.bus.Tx(d.addr, d.wbuf[:1], d.rbuf[:1]); err != nil {
return 0, err
}
return d.rbuf[0], nil
}
+54
View File
@@ -0,0 +1,54 @@
package ens160
import (
"testing"
)
func TestCalculateTempRaw(t *testing.T) {
testCases := []struct {
name string
tempMilliC int32
expectedRaw uint16
}{
{"25°C", 25000, 19082},
{"-10.5°C", -10500, 16810},
{"Min temp", -40000, 14922},
{"Below min", -50000, 14922},
{"Max temp", 85000, 22922},
{"Above max", 90000, 22922},
{"Zero", 0, 17482},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
raw := calculateTempRaw(tc.tempMilliC)
if raw != tc.expectedRaw {
t.Errorf("expected %d, got %d", tc.expectedRaw, raw)
}
})
}
}
func TestCalculateHumRaw(t *testing.T) {
testCases := []struct {
name string
rhMilliPct int32
expectedRaw uint16
}{
{"50%", 50000, 25600},
{"0%", 0, 0},
{"100%", 100000, 51200},
{"Below 0%", -10000, 0},
{"Above 100%", 110000, 51200},
{"33.3%", 33300, 17050},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
raw := calculateHumRaw(tc.rhMilliPct)
if raw != tc.expectedRaw {
t.Errorf("expected %d, got %d", tc.expectedRaw, raw)
}
})
}
}
+65
View File
@@ -0,0 +1,65 @@
package ens160
// DefaultAddress is the default I2C address for the ENS160 when the ADDR pin is
// connected to high (3.3V). When connected to low (GND), the address is 0x52.
const DefaultAddress = 0x53
// Registers
const (
regPartID = 0x00
regOpMode = 0x10
regConfig = 0x11
regCommand = 0x12
regTempIn = 0x13
regRhIn = 0x15
regStatus = 0x20
regAQI = 0x21
regTVOC = 0x22
regECO2 = 0x24
regDataT = 0x30
regDataRH = 0x32
regMISR = 0x38
regGPRWrite = 0x40
regGPRRead = 0x48
)
// Operating modes
const (
ModeDeepSleep = 0x00
ModeIdle = 0x01
ModeStandard = 0x02
ModeReset = 0xF0
)
// Status register bits
const (
statusSTATAS = 1 << 7
statusSTATER = 1 << 6
statusValidityMask = 0x0C
statusValidityShift = 2
statusNEWDAT = 1 << 1
statusNEWGPR = 1 << 0
)
// Validity flags
const (
ValidityNormalOperation = 0x00
ValidityWarmUpPhase = 0x01 // need ~3 minutes until valid data
ValidityInitialStartUpPhase = 0x02 // need ~1 hour until valid data
ValidityInvalidOutput = 0x03
)
// Commands
const (
cmdNOP = 0x00
cmdGetAppVer = 0x0E
cmdClrGPR = 0xCC
)
// Part IDs
const (
LowPartID = 0x60
HighPartID = 0x01
)
+56
View File
@@ -0,0 +1,56 @@
// This example demonstrates ENS160 usage.
//
// Wiring:
// - VCC to 3.3V, GND to ground
// - SDA to board SDA, SCL to board SCL
package main
import (
"time"
"machine"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/ens160"
)
func main() {
err := machine.I2C0.Configure(machine.I2CConfig{
Frequency: 400 * machine.KHz,
})
if err != nil {
println("Failed to configure I2C:", err)
}
dev := ens160.New(machine.I2C0, ens160.DefaultAddress)
connected := dev.Connected()
if !connected {
println("ENS160 not detected")
return
}
println("ENS160 detected")
if err := dev.Configure(); err != nil {
println("Failed to configure ENS160:", err)
}
for {
err := dev.Update(drivers.Concentration)
if err != nil {
println("Error reading ENS160: %v\n", err)
time.Sleep(5 * time.Second)
continue
}
println(
"AQI:", dev.AQI(),
"TVOC:", dev.TVOC(),
"eCO2:", dev.ECO2(),
"Validity:", dev.ValidityString(),
)
time.Sleep(2 * time.Second)
}
}
+1 -1
View File
@@ -10,7 +10,7 @@ import (
func main() {
println("GPS I2C Example")
machine.I2C0.Configure(machine.I2CConfig{})
ublox := gps.NewI2C(machine.I2C0)
ublox := gps.NewI2CWithAddress(machine.I2C0, gps.UBLOX_I2C_ADDRESS)
parser := gps.NewParser()
var fix gps.Fix
for {
+49
View File
@@ -0,0 +1,49 @@
package main
import (
"machine"
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/honeyhsc"
)
// Data taken from https://github.com/rodan/honeywell_hsc_ssc_i2c/blob/master/hsc_ssc_i2c.cpp
// these defaults are valid for the HSCMRNN030PA2A3 chip
const (
i2cAddress = 0x28
// 10%
outputMinimum = 0x666
// 90% of 2^14 - 1
outputMax = 0x399A
// min is 0 for sensors that give absolute values
pressureMin = 0
// 30psi (and we want results in millipascals)
// pressureMax = 206842.7
pressureMax = 206843 * 1000
)
func main() {
bus := machine.I2C0
err := bus.Configure(machine.I2CConfig{
Frequency: 400_000, // 100kHz minimum and 400kHz I2C maximum clock. 50 to 800 for SPI.
SDA: machine.I2C0_SDA_PIN,
SCL: machine.I2C0_SCL_PIN,
})
if err != nil {
panic(err.Error())
}
sensor := honeyhsc.NewDevI2C(bus, i2cAddress, outputMinimum, outputMax, pressureMin, pressureMax)
for {
time.Sleep(time.Second)
const measuremask = drivers.Pressure | drivers.Temperature
err := sensor.Update(measuremask)
if err != nil {
println("error updating measurements:", err.Error())
continue
}
P := sensor.Pressure()
T := sensor.Temperature()
println("pressure:", P, "temperature:", T)
}
}
+12 -3
View File
@@ -14,9 +14,18 @@ func main() {
i2c.Configure(machine.I2CConfig{SCL: machine.SCL1_PIN, SDA: machine.SDA1_PIN})
accel := lis3dh.New(i2c)
accel.Address = lis3dh.Address1 // address on the Circuit Playground Express
accel.Configure()
accel.SetRange(lis3dh.RANGE_2_G)
err := accel.Configure(lis3dh.Config{
Address: lis3dh.Address1, // address on the Circuit Playground Express
})
for err != nil {
println("could not configure LIS3DH:", err)
time.Sleep(time.Second)
}
err = accel.SetRange(lis3dh.RANGE_2_G)
for err != nil {
println("could not set acceleration range:", err)
time.Sleep(time.Second)
}
println(accel.Connected())
+58
View File
@@ -0,0 +1,58 @@
package main
import (
"machine"
"time"
"tinygo.org/x/drivers/lsm303dlhc"
)
func main() {
// LSM303DLHC is connected to the I2C0 bus on Adafruit Feather M4 via pins: 20(SDA) and 21(SCL).
machine.I2C0.Configure(machine.I2CConfig{})
sensor := lsm303dlhc.New(machine.I2C0)
//default settings
err := sensor.Configure(lsm303dlhc.Configuration{
AccelPowerMode: lsm303dlhc.ACCEL_POWER_NORMAL,
AccelRange: lsm303dlhc.ACCEL_RANGE_2G,
AccelDataRate: lsm303dlhc.ACCEL_DATARATE_100HZ,
MagPowerMode: lsm303dlhc.MAG_POWER_NORMAL,
MagSystemMode: lsm303dlhc.MAG_SYSTEM_CONTINUOUS,
MagDataRate: lsm303dlhc.MAG_DATARATE_10HZ,
})
if err != nil {
for {
println("Failed to configure", err.Error())
time.Sleep(time.Second)
}
}
for {
accel_x, accel_y, accel_z, err := sensor.ReadAcceleration()
if err != nil {
println("Failed to read accel", err.Error())
}
println("ACCEL_X:", accel_x, " ACCEL_Y:", accel_y, " ACCEL_Z:", accel_z)
mag_x, mag_y, mag_z, err := sensor.ReadMagneticField()
if err != nil {
println("Failed to read mag", err.Error())
}
println("MAG_X:", mag_x, " MAG_Y:", mag_y, " MAG_Z:", mag_z)
pitch, roll, _ := sensor.ReadPitchRoll()
println("Pitch:", float32(pitch), " Roll:", float32(roll))
heading, _ := sensor.ReadCompass()
println("Heading:", float32(heading), "degrees")
temp, _ := sensor.ReadTemperature()
println("Temperature:", float32(temp)/1000, "*C")
println("\n")
time.Sleep(time.Millisecond * 250)
}
}
+35
View File
@@ -0,0 +1,35 @@
package main
import (
"machine"
"time"
"tinygo.org/x/drivers/seesaw"
)
// example reading the position of a rotary encoder (4991) powered by a seesaw
// https://learn.adafruit.com/adafruit-i2c-qt-rotary-encoder/arduino
func main() {
// This assumes you are using an Adafruit QT Py RP2040 for its Stemma QT connector
// https://www.adafruit.com/product/4900
i2c := machine.I2C1
i2c.Configure(machine.I2CConfig{
SCL: machine.I2C1_QT_SCL_PIN,
SDA: machine.I2C1_QT_SDA_PIN,
})
dev := seesaw.New(i2c)
dev.Address = 0x36
for {
time.Sleep(time.Second)
pos, err := dev.GetEncoderPosition(0, false)
if err != nil {
println(err)
continue
}
println(pos)
}
}
-51
View File
@@ -1,51 +0,0 @@
package main
import (
"machine"
"image/color"
"time"
"tinygo.org/x/drivers/ssd1306"
)
func main() {
machine.I2C0.Configure(machine.I2CConfig{
Frequency: machine.TWI_FREQ_400KHZ,
})
display := ssd1306.NewI2C(machine.I2C0)
display.Configure(ssd1306.Config{
Address: ssd1306.Address_128_32,
Width: 128,
Height: 32,
})
display.ClearDisplay()
x := int16(0)
y := int16(0)
deltaX := int16(1)
deltaY := int16(1)
for {
pixel := display.GetPixel(x, y)
c := color.RGBA{255, 255, 255, 255}
if pixel {
c = color.RGBA{0, 0, 0, 255}
}
display.SetPixel(x, y, c)
display.Display()
x += deltaX
y += deltaY
if x == 0 || x == 127 {
deltaX = -deltaX
}
if y == 0 || y == 31 {
deltaY = -deltaY
}
time.Sleep(1 * time.Millisecond)
}
}
-60
View File
@@ -1,60 +0,0 @@
// This example shows how to use 128x64 display over I2C
// Tested on Seeeduino XIAO Expansion Board https://wiki.seeedstudio.com/Seeeduino-XIAO-Expansion-Board/
//
// According to manual, I2C address of the display is 0x78, but that's 8-bit address.
// TinyGo operates on 7-bit addresses and respective 7-bit address would be 0x3C, which we use below.
//
// To learn more about different types of I2C addresses, please see following page
// https://www.totalphase.com/support/articles/200349176-7-bit-8-bit-and-10-bit-I2C-Slave-Addressing
package main
import (
"machine"
"image/color"
"time"
"tinygo.org/x/drivers/ssd1306"
)
func main() {
machine.I2C0.Configure(machine.I2CConfig{
Frequency: machine.TWI_FREQ_400KHZ,
})
display := ssd1306.NewI2C(machine.I2C0)
display.Configure(ssd1306.Config{
Address: 0x3C,
Width: 128,
Height: 64,
})
display.ClearDisplay()
x := int16(0)
y := int16(0)
deltaX := int16(1)
deltaY := int16(1)
for {
pixel := display.GetPixel(x, y)
c := color.RGBA{255, 255, 255, 255}
if pixel {
c = color.RGBA{0, 0, 0, 255}
}
display.SetPixel(x, y, c)
display.Display()
x += deltaX
y += deltaY
if x == 0 || x == 127 {
deltaX = -deltaX
}
if y == 0 || y == 63 {
deltaY = -deltaY
}
time.Sleep(1 * time.Millisecond)
}
}
+59
View File
@@ -0,0 +1,59 @@
package main
// This example shows how to use SSD1306 OLED display driver over I2C and SPI.
//
// Check the `newSSD1306Display()` functions for I2C and SPI initializations.
import (
"runtime"
"image/color"
"time"
)
func main() {
display := newSSD1306Display()
display.ClearDisplay()
w, h := display.Size()
x := int16(0)
y := int16(0)
deltaX := int16(1)
deltaY := int16(1)
traceTime := time.Now().UnixMilli() + 1000
frames := 0
ms := runtime.MemStats{}
for {
pixel := display.GetPixel(x, y)
c := color.RGBA{255, 255, 255, 255}
if pixel {
c = color.RGBA{0, 0, 0, 255}
}
display.SetPixel(x, y, c)
display.Display()
x += deltaX
y += deltaY
if x == 0 || x == w-1 {
deltaX = -deltaX
}
if y == 0 || y == h-1 {
deltaY = -deltaY
}
frames++
now := time.Now().UnixMilli()
if now >= traceTime {
runtime.ReadMemStats(&ms)
println("TS", now, "| FPS", frames, "| HeapInuse", ms.HeapInuse)
traceTime = now + 1000
frames = 0
}
}
}
+38
View File
@@ -0,0 +1,38 @@
//go:build xiao_ble
// This initializes SSD1306 OLED display driver over I2C.
//
// Seeed XIAO BLE board + SSD1306 128x32 I2C OLED display.
//
// Wiring:
// - XIAO GND -> OLED GND
// - XIAO 3v3 -> OLED VCC
// - XIAO D4 (SDA) -> OLED SDA
// - XIAO D5 (SCL) -> OLED SCK
//
// For your case:
// - Connect the display to I2C pins on your board.
// - Adjust I2C address and display size as needed.
package main
import (
"machine"
"tinygo.org/x/drivers/ssd1306"
)
func newSSD1306Display() *ssd1306.Device {
machine.I2C0.Configure(machine.I2CConfig{
Frequency: 400 * machine.KHz,
SDA: machine.SDA0_PIN,
SCL: machine.SCL0_PIN,
})
display := ssd1306.NewI2C(machine.I2C0)
display.Configure(ssd1306.Config{
Address: ssd1306.Address_128_32, // or ssd1306.Address
Width: 128,
Height: 32, // or 64
})
return display
}
+27
View File
@@ -0,0 +1,27 @@
//go:build thumby
// This initializes SSD1306 OLED display driver over SPI.
//
// Thumby board has a tiny built-in 72x40 display.
//
// As the display is built-in, no wiring is needed.
package main
import (
"machine"
"tinygo.org/x/drivers/ssd1306"
)
func newSSD1306Display() *ssd1306.Device {
machine.SPI0.Configure(machine.SPIConfig{})
display := ssd1306.NewSPI(machine.SPI0, machine.THUMBY_DC_PIN, machine.THUMBY_RESET_PIN, machine.THUMBY_CS_PIN)
display.Configure(ssd1306.Config{
Width: 72,
Height: 40,
ResetCol: ssd1306.ResetValue{28, 99},
ResetPage: ssd1306.ResetValue{0, 5},
})
return display
}
+40
View File
@@ -0,0 +1,40 @@
//go:build xiao_rp2040
// This initializes SSD1306 OLED display driver over SPI.
//
// Seeed XIAO RP2040 board + SSD1306 128x64 SPI OLED display.
//
// Wiring:
// - XIAO GND -> OLED GND
// - XIAO 3v3 -> OLED VCC
// - XIAO D8 (SCK) -> OLED D0
// - XIAO D10 (SDO) -> OLED D1
// - XIAO D4 -> OLED RES
// - XIAO D5 -> OLED DC
// - XIAO D6 -> OLED CS
//
// For your case:
// - Connect the display to SPI pins on your board.
// - Adjust RES, DC and CS pins as needed.
// - Adjust SPI frequency as needed.
// - Adjust display size as needed.
package main
import (
"machine"
"tinygo.org/x/drivers/ssd1306"
)
func newSSD1306Display() *ssd1306.Device {
machine.SPI0.Configure(machine.SPIConfig{
Frequency: 50 * machine.MHz,
})
display := ssd1306.NewSPI(machine.SPI0, machine.D5, machine.D4, machine.D6)
display.Configure(ssd1306.Config{
Width: 128,
Height: 64,
})
return display
}
-48
View File
@@ -1,48 +0,0 @@
package main
import (
"image/color"
"machine"
"time"
"tinygo.org/x/drivers/ssd1306"
)
func main() {
machine.SPI0.Configure(machine.SPIConfig{
Frequency: 8000000,
})
display := ssd1306.NewSPI(machine.SPI0, machine.P8, machine.P7, machine.P9)
display.Configure(ssd1306.Config{
Width: 128,
Height: 64,
})
display.ClearDisplay()
x := int16(64)
y := int16(32)
deltaX := int16(1)
deltaY := int16(1)
for {
pixel := display.GetPixel(x, y)
c := color.RGBA{255, 255, 255, 255}
if pixel {
c = color.RGBA{0, 0, 0, 255}
}
display.SetPixel(x, y, c)
display.Display()
x += deltaX
y += deltaY
if x == 0 || x == 127 {
deltaX = -deltaX
}
if y == 0 || y == 63 {
deltaY = -deltaY
}
time.Sleep(1 * time.Millisecond)
}
}
-50
View File
@@ -1,50 +0,0 @@
// This example using the SSD1306 OLED display over SPI on the Thumby board
// A very tiny 72x40 display.
package main
import (
"image/color"
"machine"
"time"
"tinygo.org/x/drivers/ssd1306"
)
func main() {
machine.SPI0.Configure(machine.SPIConfig{})
display := ssd1306.NewSPI(machine.SPI0, machine.THUMBY_DC_PIN, machine.THUMBY_RESET_PIN, machine.THUMBY_CS_PIN)
display.Configure(ssd1306.Config{
Width: 72,
Height: 40,
ResetCol: ssd1306.ResetValue{28, 99},
ResetPage: ssd1306.ResetValue{0, 5},
})
display.ClearDisplay()
x := int16(36)
y := int16(20)
deltaX := int16(1)
deltaY := int16(1)
for {
pixel := display.GetPixel(x, y)
c := color.RGBA{255, 255, 255, 255}
if pixel {
c = color.RGBA{0, 0, 0, 255}
}
display.SetPixel(x, y, c)
display.Display()
x += deltaX
y += deltaY
if x == 0 || x == 71 {
deltaX = -deltaX
}
if y == 0 || y == 39 {
deltaY = -deltaY
}
time.Sleep(1 * time.Millisecond)
}
}
+41
View File
@@ -46,6 +46,10 @@ var DefaultDeviceIdentifier = DeviceIdentifierFunc(func(id JedecID) Attrs {
return GD25Q16C()
case 0xC84017:
return GD25Q64C()
case 0x856015:
return P25Q16H()
case 0xEF4014:
return W25Q80DV()
case 0xEF4015:
return W25Q16JVIQ()
case 0xEF4016:
@@ -239,6 +243,24 @@ func GD25Q64C() Attrs {
}
}
// Settings for the Puya P25Q16H 2MiB SPI flash.
// Datasheet: https://files.seeedstudio.com/wiki/github_weiruanexample/Flash_P25Q16H-UXH-IR_Datasheet.pdf
func P25Q16H() Attrs {
return Attrs{
TotalSize: 1 << 21, // 2 MiB
StartUp: 5000 * time.Microsecond,
JedecID: JedecID{0x85, 0x60, 0x15},
MaxClockSpeedMHz: 55,
QuadEnableBitMask: 0x02,
HasSectorProtection: true,
SupportsFastRead: true,
SupportsQSPI: true,
SupportsQSPIWrites: true,
WriteStatusSplit: true,
SingleStatusByte: false,
}
}
// Settings for the Winbond W25Q16JV-IQ 2MiB SPI flash. Note that JV-IM has a
// different .memory_type (0x70) Datasheet:
// https://www.winbond.com/resource-files/w25q16jv%20spi%20revf%2005092017.pdf
@@ -380,6 +402,25 @@ func W25Q80DL() Attrs {
TotalSize: 1 << 20, // 1 MiB
StartUp: 5000 * time.Microsecond,
JedecID: JedecID{0xEF, 0x60, 0x14},
MaxClockSpeedMHz: 80,
QuadEnableBitMask: 0x02,
HasSectorProtection: false,
SupportsFastRead: true,
SupportsQSPI: true,
SupportsQSPIWrites: false,
WriteStatusSplit: false,
SingleStatusByte: false,
}
}
// Settings for the Winbond W25Q80DV 2MiB SPI flash.
// Datasheet:
// https://www.winbond.com/resource-files/w25q80dv%20dl_revh_10022015.pdf
func W25Q80DV() Attrs {
return Attrs{
TotalSize: 1 << 21, // 2 MiB
StartUp: 5000 * time.Microsecond,
JedecID: JedecID{0xEF, 0x40, 0x14},
MaxClockSpeedMHz: 104,
QuadEnableBitMask: 0x02,
HasSectorProtection: false,
+7 -1
View File
@@ -69,10 +69,16 @@ func NewUART(uart drivers.UART) Device {
}
// NewI2C creates a new I2C GPS connection.
// Uses the default i2c address (0x42) for backward compatibility reasons.
func NewI2C(bus drivers.I2C) Device {
return NewI2CWithAddress(bus, I2C_ADDRESS)
}
// NewI2CWithAddress creates a new I2C GPS connection on the provided address
func NewI2CWithAddress(bus drivers.I2C, i2cAddress uint16) Device {
return Device{
bus: bus,
address: I2C_ADDRESS,
address: i2cAddress,
buffer: make([]byte, bufferSize),
bufIdx: bufferSize,
sentence: strings.Builder{},
+4 -1
View File
@@ -96,7 +96,10 @@ func (parser *Parser) Parse(sentence string) (Fix, error) {
fix.Speed = findSpeed(fields[7])
fix.Heading = findHeading(fields[8])
date := findDate(fields[9])
fix.Time = fix.Time.AddDate(date.Year(), int(date.Month()), date.Day())
fix.Time = date.Add(time.Duration(fix.Time.Hour())*time.Hour +
time.Duration(fix.Time.Minute())*time.Minute +
time.Duration(fix.Time.Second())*time.Second +
time.Duration(fix.Time.Nanosecond())*time.Nanosecond)
return fix, nil
}
+3 -3
View File
@@ -70,15 +70,15 @@ func TestParseRMC(t *testing.T) {
t.Error("should have errInvalidRMCSentence error")
}
val = "$GPRMC,203522.00,A,5109.0262308,N,11401.8407342,W,0.004,133.4,130522,0.0,E,D*2B"
val = "$GPRMC,203522.00,A,5109.0262308,N,11401.8407342,W,0.004,133.4,010622,0.0,E,D*2B"
fix, err := p.Parse(val)
if err != nil {
t.Error("should have parsed")
}
c.Assert(fix.Time.Year(), qt.Equals, 2022)
c.Assert(fix.Time.Month(), qt.Equals, time.May)
c.Assert(fix.Time.Day(), qt.Equals, 13)
c.Assert(fix.Time.Month(), qt.Equals, time.June)
c.Assert(fix.Time.Day(), qt.Equals, 1)
c.Assert(fix.Time.Hour(), qt.Equals, 20)
c.Assert(fix.Time.Minute(), qt.Equals, 35)
c.Assert(fix.Time.Second(), qt.Equals, 22)
+5 -1
View File
@@ -4,7 +4,11 @@ package gps
// The I2C address which this device listens to.
const (
I2C_ADDRESS = 0x42
// To ensure backward compatibility
I2C_ADDRESS = UBLOX_I2C_ADDRESS
UBLOX_I2C_ADDRESS = 0x42
PA1010D_I2C_ADDRESS = 0x10
)
const (
+191
View File
@@ -0,0 +1,191 @@
package honeyhsc
import (
"errors"
"math"
"tinygo.org/x/drivers"
)
var (
errSensorMissing = errors.New("hsc: not connected")
errDiagnostic = errors.New("hsc: diagnostic error")
)
const (
measuremask = drivers.Pressure | drivers.Temperature
statusMask = 0b1100_0000
statusOffset = 6
)
// DevI2C is the TruStability® High Accuracy Silicon Ceramic (HSC) Series is a piezoresistive silicon pressure sensor offering a ratiometric
// analog or digital output for reading pressure over the specified full scale pressure span and temperature range.
type DevI2C struct {
bus drivers.I2C
dev
addr uint8
buf [6]byte
}
// NewDevI2C creates and returns a new DevI2C that communicates with an HSC device over the provided I2C bus.
// Parameters:
// - bus: the I2C bus to use.
// - addr: the 7-bit I2C address of the sensor.
// - outMin, outMax: raw output code range (counts) corresponding to the pressure span. Depends on sensor model.
// - pMin, pMax: pressure range endpoints in millipascals (mPa). Depends on sensor model.
//
// The returned DevI2C will use these calibration parameters to convert raw bridge counts to pressure.
func NewDevI2C(bus drivers.I2C, addr, outMin, outMax uint16, pMin, pMax int32) *DevI2C {
h := &DevI2C{
bus: bus,
addr: uint8(addr),
dev: dev{
cmin: outMin,
cmax: outMax,
pmin: pMin,
pmax: pMax,
},
}
return h
}
// ReadTemperature reads and returns the temperature in milliKelvin (mC) from the I2C-attached HSC device.
// It performs an Update internally to get the latest temperature value.
func (h *DevI2C) ReadTemperature() (int32, error) {
err := h.Update(drivers.Temperature)
if err != nil {
return 0, err
}
return h.Temperature(), nil
}
// Update reads both temperature and pressure data from the I2C-attached HSC device when
// the requested measurement mask includes pressure or temperature.
// If neither pressure nor temperature is requested, Update is a no-op.
func (d *DevI2C) Update(which drivers.Measurement) error {
// Update performs an I2C transaction to read 4 bytes, parses the status bits, 14-bit bridge data and
// temperature bits, and forwards them to the internal update routine. Any I2C transport error is returned,
// as well as errors produced by the internal update (e.g. errSensorMissing, errDiagnostic).
if which&measuremask == 0 {
return nil
}
rbuf := d.buf[:4]
wbuf := d.buf[4:6]
const reg = 0
value := (d.addr << 1) | 1
wbuf[0] = reg
wbuf[1] = value
err := d.bus.Tx(uint16(d.addr), wbuf, rbuf)
if err != nil {
return err
}
status := (rbuf[0] & statusMask) >> statusOffset
bridgeData := (uint16(rbuf[0]&^statusMask) << 8) | uint16(rbuf[1])
tempData := uint16(rbuf[2])<<8 | uint16(rbuf[3]&0xe0)>>5
return d.dev.update(status, bridgeData, tempData)
}
type pinout func(level bool)
// DevI2C is the TruStability® High Accuracy Silicon Ceramic (HSC) Series is a piezoresistive silicon pressure sensor offering a ratiometric
// analog or digital output for reading pressure over the specified full scale pressure span and temperature range.
type DevSPI struct {
spi drivers.SPI
cs pinout
dev
buf [4]byte
}
// NewDevSPI creates and returns a new DevSPI that communicates with an HSC device over SPI.
// Parameters:
// - conn: the SPI connection to use.
// - cs: a chip-select function that drives the device select line low/high.
// - outMin, outMax: raw output code range (counts) corresponding to the pressure span. Depends on sensor model.
// - pMin, pMax: pressure range endpoints in millipascals (mPa). Depends on sensor model.
//
// The function returns the constructed DevSPI and an error value (currently always nil).
func NewDevSPI(conn drivers.SPI, cs pinout, outMin, outMax uint16, pMin, pMax int32) (*DevSPI, error) {
h := &DevSPI{
spi: conn,
cs: cs,
dev: dev{
cmin: outMin,
cmax: outMax,
pmin: pMin,
pmax: pMax,
},
}
return h, nil
}
// ReadTemperature reads and returns the temperature in milliKelvin (mC) from the SPI-attached HSC device.
// It performs an Update internally to get the latest temperature value.
func (h *DevSPI) ReadTemperature() (int32, error) {
err := h.Update(drivers.Temperature)
if err != nil {
return 0, err
}
return h.Temperature(), nil
}
// Update reads pressure and temperature data from the SPI-attached HSC device when the requested measurement mask includes
// pressure or temperature. If neither pressure nor temperature is requested, Update is a no-op.
func (h *DevSPI) Update(which drivers.Measurement) error {
// It toggles the provided chip-select, performs an SPI transfer to read 4 bytes, parses the status bits,
// 14-bit bridge data and temperature bits, and forwards them to the internal update routine. Any SPI
// transport error is returned, as well as errors produced by the internal update (e.g. errSensorMissing, errDiagnostic).
if which&measuremask == 0 {
return nil
}
buf := &h.buf
h.cs(false)
err := h.spi.Tx(nil, buf[:4])
h.cs(true)
if err != nil {
return err
}
// First two bits are status bits.
status := (buf[0] & statusMask) >> statusOffset
bridgeData := (uint16(buf[0]&^statusMask) << 8) | uint16(buf[1])
tempData := uint16(buf[2])<<8 | uint16(buf[3]&0xe0)>>5
return h.dev.update(status, bridgeData, tempData)
}
type dev struct {
pressure int32
temp int32
cmin, cmax uint16
pmin, pmax int32
}
// Pressure returns the most recently computed pressure value in millipascals (mPa).
// The value is taken from the last successful Update.
func (d *dev) Pressure() int32 {
return d.pressure
}
// Temperature returns the most recently read temperature value in milliKelvin (mC).
// The value is taken from the last successful Update.
func (d *dev) Temperature() int32 {
return d.temp + 273_150
}
// update interprets raw sensor fields (status, bridgeData, tempData) and updates the dev's stored
// pressure and temperature. It returns errSensorMissing when the temperature raw value indicates no sensor
// (tempData == math.MaxUint16), errDiagnostic when the status indicates a device diagnostic condition
// (status == 3), or nil on success. Pressure is computed with integer arithmetic using the configured
// cmin/cmax -> pmin/pmax linear mapping in order to avoid overflows.
func (d *dev) update(status uint8, bridgeData, tempData uint16) error {
if tempData == math.MaxUint16 {
return errSensorMissing
} else if status == 3 {
return errDiagnostic
}
// Take care not to overflow here.
p := (int32(bridgeData)-int32(d.cmin))*(d.pmax-d.pmin)/int32(d.cmax-d.cmin) + d.pmin
d.temp = int32(tempData)
d.pressure = p
return nil
}
+62
View File
@@ -0,0 +1,62 @@
package legacy
import (
"errors"
"tinygo.org/x/drivers/internal/pin"
)
// The pingconfig group of files serve to abstract away
// pin configuration calls on the machine.Pin type.
// It was observed this way of developing drivers was
// non-portable and unusable on "big" Go projects so
// future projects should NOT configure pins in driver code.
// Users must configure pins before passing them as arguments
// to drivers.
// ConfigurePinOut is a legacy function used to configure pins as outputs.
//
// Deprecated: Do not configure pins in drivers.
// This is a legacy feature and should only be used by drivers that
// previously configured pins in initialization to avoid breaking users.
func ConfigurePinOut(po pin.Output) {
configurePinOut(po)
}
// ConfigurePinInput is a legacy function used to configure pins as inputs.
//
// Deprecated: Do not configure pins in drivers.
// This is a legacy feature and should only be used by drivers that
// previously configured pins in initialization to avoid breaking users.
func ConfigurePinInputPulldown(pi pin.Input) {
configurePinInputPulldown(pi)
}
// ConfigurePinInput is a legacy function used to configure pins as inputs.
//
// Deprecated: Do not configure pins in drivers.
// This is a legacy feature and should only be used by drivers that
// previously configured pins in initialization to avoid breaking users.
func ConfigurePinInput(pi pin.Input) {
configurePinInput(pi)
}
// ConfigurePinInput is a legacy function used to configure pins as inputs.
//
// Deprecated: Do not configure pins in drivers.
// This is a legacy feature and should only be used by drivers that
// previously configured pins in initialization to avoid breaking users.
func ConfigurePinInputPullup(pi pin.Input) {
configurePinInputPullup(pi)
}
// PinIsNoPin returns true if the argument is a machine.Pin type and is the machine.NoPin predeclared type.
//
// Deprecated: Drivers do not require pin knowledge from now on.
func PinIsNoPin(pin any) bool {
return pinIsNoPin(pin)
}
var (
ErrConfigBeforeInstantiated = errors.New("device must be instantiated with New before calling Configure method")
)
+15
View File
@@ -0,0 +1,15 @@
//go:build !tinygo
package legacy
import "tinygo.org/x/drivers/internal/pin"
// This file compiles for non-tinygo builds
// for use with "big" or "upstream" Go where
// there is no machine package.
func configurePinOut(p pin.Output) {}
func configurePinInput(p pin.Input) {}
func configurePinInputPulldown(p pin.Input) {}
func configurePinInputPullup(p pin.Input) {}
func pinIsNoPin(a any) bool { return false }
+10
View File
@@ -0,0 +1,10 @@
//go:build baremetal && fe310
package legacy
import "machine"
const (
pulldown = machine.PinInput
pullup = machine.PinInput
)
+13
View File
@@ -0,0 +1,13 @@
//go:build baremetal && !fe310
package legacy
import "machine"
// If you are getting a build error here you then we missed adding
// your CPU build tag to the list of CPUs that do not have pulldown/pullups.
// Add it above and in pinhal_nopulls! You should also add a smoketest for it :)
const (
pulldown = machine.PinInputPulldown
pullup = machine.PinInputPullup
)
+37
View File
@@ -0,0 +1,37 @@
//go:build baremetal
package legacy
import (
"machine"
"tinygo.org/x/drivers/internal/pin"
)
func configurePinOut(po pin.Output) {
configurePin(po, machine.PinOutput)
}
func configurePinInputPulldown(pi pin.Input) {
configurePin(pi, pulldown) // some chips do not have pull down, in which case pulldown==machine.PinInput.
}
func configurePinInput(pi pin.Input) {
configurePin(pi, machine.PinInput)
}
func configurePinInputPullup(pi pin.Input) {
configurePin(pi, pullup) // some chips do not have pull up, in which case pullup==machine.PinInput.
}
func pinIsNoPin(a any) bool {
p, ok := a.(machine.Pin)
return ok && p == machine.NoPin
}
func configurePin(p any, mode machine.PinMode) {
machinePin, ok := p.(machine.Pin)
if ok {
machinePin.Configure(machine.PinConfig{Mode: mode})
}
}
+72
View File
@@ -0,0 +1,72 @@
// package pin implements a TinyGo Pin HAL.
// It serves to eliminate machine.Pin from driver constructors
// so that drivers can be used in "big" Go projects where
// there is no machine package.
// This file contains both function and interface-style Pin HAL definitions.
package pin
// OutputFunc is hardware abstraction for a pin which outputs a
// digital signal (high or low level).
//
// // Code conversion demo: from machine.Pin to pin.OutputFunc
// led := machine.LED
// led.Configure(machine.PinConfig{Mode: machine.Output})
// var pin pin.OutputFunc = led.Set // Going from a machine.Pin to a pin.OutputFunc
//
// This is an alternative to [Output] which is an interface type.
type OutputFunc func(level bool)
// High sets the underlying pin's level to high. This is equivalent to calling PinOutput(true).
func (setPin OutputFunc) High() {
setPin(true)
}
// Low sets the underlying pin's level to low. This is equivalent to calling PinOutput(false).
func (setPin OutputFunc) Low() {
setPin(false)
}
// InputFunc is hardware abstraction for a pin which receives a
// digital signal and reads it (high or low level).
//
// // Code conversion demo: from machine.Pin to pin.InputFunc
// input := machine.LED
// input.Configure(machine.PinConfig{Mode: machine.PinInputPulldown}) // or use machine.PinInputPullup or machine.Input
// var pin pin.InputFunc = input.Get // Going from a machine.Pin to a pin.InputFunc
//
// This is an alternative to [Input] which is an interface type.
type InputFunc func() (level bool)
// // Below is an example on how to define a input/output pin HAL for a
// // pin that must switch between input and output mode:
//
// var pinIsOutput bool
// var po PinOutputFunc = func(b bool) {
// if !pinIsOutput {
// pin.Configure(outputMode)
// pinIsOutput = true
// }
// pin.Set(b)
// }
//
// var pi PinInputFunc = func() bool {
// if pinIsOutput {
// pin.Configure(inputMode)
// pinIsOutput = false
// }
// return pin.Get()
// }
// Output interface represents a pin hardware abstraction layer for a pin that can output a digital signal.
//
// This is an alternative to [OutputFunc] abstraction which is a function type.
type Output interface {
Set(level bool)
}
// Input interface represents a pin hardware abstraction layer for a pin that can read a digital signal.
//
// This is an alternative to [InputFunc] abstraction which is a function type.
type Input interface {
Get() (level bool)
}
+143
View File
@@ -0,0 +1,143 @@
package regmap
import (
"encoding/binary"
"io"
"tinygo.org/x/drivers"
)
// Device8 implements common logic to most 8-bit peripherals with an I2C or SPI bus.
// All methods expect the target to support conventional register read and write operations
// where the first byte sent is the register address being accessed.
//
// All methods use an internal buffer and perform no dynamic memory allocation.
type Device8 struct {
buf [10]byte
}
// clear zeroes Device8's buffers.
func (d *Device8) clear() {
d.buf = [10]byte{}
}
// I2C methods.
// Read8I2C reads a single byte from register addr of the device at i2cAddr using the provided I2C bus.
func (d *Device8) Read8I2C(bus drivers.I2C, i2cAddr uint16, addr uint8) (byte, error) {
d.buf[0] = addr
err := bus.Tx(i2cAddr, d.buf[0:1], d.buf[1:2])
return d.buf[1], err
}
// Read16I2C reads a 16-bit value from register addr of the device at i2cAddr using the provided I2C bus.
// The byte order is specified by order.
func (d *Device8) Read16I2C(bus drivers.I2C, i2cAddr uint16, addr uint8, order binary.ByteOrder) (uint16, error) {
d.buf[0] = addr
err := bus.Tx(i2cAddr, d.buf[0:1], d.buf[1:3])
return order.Uint16(d.buf[1:3]), err
}
// Read32I2C reads a 32-bit value from register addr of the device at i2cAddr using the provided I2C bus.
// The byte order is specified by order.
func (d *Device8) Read32I2C(bus drivers.I2C, i2cAddr uint16, addr uint8, order binary.ByteOrder) (uint32, error) {
d.buf[0] = addr
err := bus.Tx(i2cAddr, d.buf[0:1], d.buf[1:5])
return order.Uint32(d.buf[1:5]), err
}
// ReadDataI2C reads dataLength bytes from register addr of the device at i2cAddr using the provided I2C bus.
// The data is stored in dataDestination.
func (d *Device8) ReadDataI2C(bus drivers.I2C, i2cAddr uint16, addr uint8, dataDestination []byte) error {
d.buf[0] = addr
return bus.Tx(i2cAddr, d.buf[:1], dataDestination)
}
// Write8I2C writes a single byte value to register addr of the device at i2cAddr using the provided I2C bus.
func (d *Device8) Write8I2C(bus drivers.I2C, i2cAddr uint16, addr, value uint8) error {
d.buf[0] = addr
d.buf[1] = value
return bus.Tx(i2cAddr, d.buf[:2], nil)
}
// Write16I2C writes a 16-bit value to register addr of the device at i2cAddr using the provided I2C bus.
// The byte order is specified by order.
func (d *Device8) Write16I2C(bus drivers.I2C, i2cAddr uint16, addr uint8, value uint16, order binary.ByteOrder) error {
d.buf[0] = addr
order.PutUint16(d.buf[1:3], value)
return bus.Tx(i2cAddr, d.buf[0:3], nil)
}
// Write32I2C writes a 32-bit value to register addr of the device at i2cAddr using the provided I2C bus.
// The byte order is specified by order.
func (d *Device8) Write32I2C(bus drivers.I2C, i2cAddr uint16, addr uint8, value uint32, order binary.ByteOrder) error {
d.buf[0] = addr
order.PutUint32(d.buf[1:5], value)
return bus.Tx(i2cAddr, d.buf[0:5], nil)
}
// SPI methods.
// Read8SPI reads a single byte from register addr using the provided SPI bus.
func (d *Device8) Read8SPI(bus drivers.SPI, addr uint8) (byte, error) {
d.clear()
d.buf[0] = addr
err := bus.Tx(d.buf[0:1], d.buf[1:2]) // We suppose data is returned after first byte in SPI.
return d.buf[1], err
}
// Read16SPI reads a 16-bit value from register addr using the provided SPI bus. The byte order is specified by order.
func (d *Device8) Read16SPI(bus drivers.SPI, addr uint8, order binary.ByteOrder) (uint16, error) {
d.clear()
d.buf[0] = addr
err := bus.Tx(d.buf[0:3], d.buf[3:6]) // We suppose data is returned after first byte in SPI.
return order.Uint16(d.buf[4:6]), err
}
// Read32SPI reads a 32-bit value from register addr using the provided SPI bus. The byte order is specified by order.
func (d *Device8) Read32SPI(bus drivers.SPI, addr uint8, order binary.ByteOrder) (uint32, error) {
d.clear()
d.buf[0] = addr
err := bus.Tx(d.buf[0:5], d.buf[5:10]) // We suppose data is returned after first byte in SPI.
return order.Uint32(d.buf[6:10]), err
}
// ReadDataSPI reads data from a 8bit device address. It assumes data at register address is sent back
// from device after first byte is written as address.
// It needs the auxiliary buffer length to be large enough to contain both the write and read portions of buffer,
// so 2*(dataLength+1) < len(auxiliaryBuf) must hold.
func (d *Device8) ReadDataSPI(bus drivers.SPI, addr uint8, dataLength int, auxiliaryBuf []byte) ([]byte, error) {
split := len(auxiliaryBuf) / 2
if split < dataLength+1 {
return nil, io.ErrShortBuffer
}
wbuf, rbuf := auxiliaryBuf[:split], auxiliaryBuf[split:]
wbuf[0] = addr
err := bus.Tx(wbuf, rbuf)
return rbuf[1:], err
}
// Write8SPI writes a single byte value to register addr using the provided SPI bus.
func (d *Device8) Write8SPI(bus drivers.SPI, addr, value uint8) error {
d.clear()
d.buf[0] = addr
d.buf[1] = value
return bus.Tx(d.buf[:2], nil)
}
// Write16SPI writes a 16-bit value to register addr using the provided SPI bus. The byte order is specified by order.
func (d *Device8) Write16SPI(bus drivers.SPI, addr uint8, value uint16, order binary.ByteOrder) error {
d.clear()
d.buf[0] = addr
order.PutUint16(d.buf[1:3], value)
return bus.Tx(d.buf[:3], nil)
}
// Write32SPI writes a 32-bit value to register addr using the provided SPI bus. The byte order is specified by order.
func (d *Device8) Write32SPI(bus drivers.SPI, addr uint8, value uint32, order binary.ByteOrder) error {
d.clear()
d.buf[0] = addr
order.PutUint32(d.buf[1:5], value)
return bus.Tx(d.buf[:5], nil)
}
+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
}
+123
View File
@@ -0,0 +1,123 @@
package regmap
import (
"encoding/binary"
"tinygo.org/x/drivers"
)
// Device8SPI implements common logic to most 8-bit peripherals with an SPI bus.
// All methods expect the target to support conventional register read and write operations
// where the first byte sent is the register address being accessed.
//
// All methods use an internal buffer and perform no dynamic memory allocation.
type Device8SPI struct {
bus drivers.SPI
order binary.ByteOrder
d Device8
}
// SetBus sets the SPI bus and byte order for the Device8SPI.
//
// As a hint, most SPI devices use big-endian (MSB) byte order.
// - Big endian: A value of 0x1234 is transmitted as 0x12 followed by 0x34.
// - Little endian: A value of 0x1234 is transmitted as 0x34 followed by 0x12.
func (d *Device8SPI) SetBus(bus drivers.SPI, order binary.ByteOrder) {
d.bus = bus
d.order = order
}
// Read8 reads a single byte from register addr.
func (d *Device8SPI) Read8(addr uint8) (byte, error) {
return d.d.Read8SPI(d.bus, addr)
}
// Read16 reads a 16-bit value from register addr.
func (d *Device8SPI) Read16(addr uint8) (uint16, error) {
return d.d.Read16SPI(d.bus, addr, d.order)
}
// Read32 reads a 32-bit value from register addr.
func (d *Device8SPI) Read32(addr uint8) (uint32, error) {
return d.d.Read32SPI(d.bus, addr, d.order)
}
// ReadData reads dataLength bytes from register addr. Due to the internal functioning of
// SPI, an auxiliary buffer must be provided to perform the operation and avoid memory allocation.
// The returned slice is a subslice of auxBuffer containing the read data.
func (d *Device8SPI) ReadData(addr uint8, datalength int, auxBuffer []byte) ([]byte, error) {
return d.d.ReadDataSPI(d.bus, addr, datalength, auxBuffer)
}
// Write8 writes a single byte value to register addr.
func (d *Device8SPI) Write8(addr, value uint8) error {
return d.d.Write8SPI(d.bus, addr, value)
}
// Write16 writes a 16-bit value to register addr.
func (d *Device8SPI) Write16(addr uint8, value uint16) error {
return d.d.Write16SPI(d.bus, addr, value, d.order)
}
// Write32 writes a 32-bit value to register addr.
func (d *Device8SPI) Write32(addr uint8, value uint32) error {
return d.d.Write32SPI(d.bus, addr, value, d.order)
}
// Device8I2C implements common logic to most 8-bit peripherals with an I2C bus.
// All methods expect the target to support conventional register read and write operations
// where the first byte sent is the register address being accessed.
//
// All methods use an internal buffer and perform no dynamic memory allocation.
type Device8I2C struct {
bus drivers.I2C
i2cAddr uint16
order binary.ByteOrder
d Device8
}
// SetBus sets the I2C bus, device address, and byte order for the Device8I2C.
//
// As a hint, most I2C devices use big-endian (MSB) byte order.
// - Big endian: A value of 0x1234 is transmitted as 0x12 followed by 0x34.
// - Little endian: A value of 0x1234 is transmitted as 0x34 followed by 0x12.
func (d *Device8I2C) SetBus(bus drivers.I2C, i2cAddr uint16, order binary.ByteOrder) {
d.bus = bus
d.i2cAddr = i2cAddr
d.order = order
}
// Read8 reads a single byte from register addr.
func (d *Device8I2C) Read8(addr uint8) (byte, error) {
return d.d.Read8I2C(d.bus, d.i2cAddr, addr)
}
// Read16 reads a 16-bit value from register addr.
func (d *Device8I2C) Read16(addr uint8) (uint16, error) {
return d.d.Read16I2C(d.bus, d.i2cAddr, addr, d.order)
}
// Read32 reads a 32-bit value from register addr.
func (d *Device8I2C) Read32(addr uint8) (uint32, error) {
return d.d.Read32I2C(d.bus, d.i2cAddr, addr, d.order)
}
// ReadData reads dataLength bytes from register addr.
func (d *Device8I2C) ReadData(addr uint8, dataDestination []byte) error {
return d.d.ReadDataI2C(d.bus, d.i2cAddr, addr, dataDestination)
}
// Write8 writes a single byte value to register addr.
func (d *Device8I2C) Write8(addr, value uint8) error {
return d.d.Write8I2C(d.bus, d.i2cAddr, addr, value)
}
// Write16 writes a 16-bit value to register addr.
func (d *Device8I2C) Write16(addr uint8, value uint16) error {
return d.d.Write16I2C(d.bus, d.i2cAddr, addr, value, d.order)
}
// Write32 writes a 32-bit value to register addr.
func (d *Device8I2C) Write32(addr uint8, value uint32) error {
return d.d.Write32I2C(d.bus, d.i2cAddr, addr, value, d.order)
}
+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)
}
+115 -36
View File
@@ -11,37 +11,57 @@ import (
// Device wraps an I2C connection to a LIS3DH device.
type Device struct {
bus drivers.I2C
Address uint16
address uint16
r Range
accel [6]byte // stored acceleration data (from the Update call)
}
// Driver configuration, used for the Configure call. All fields are optional.
type Config struct {
Address uint16
}
// New creates a new LIS3DH connection. The I2C bus must already be configured.
//
// This function only creates the Device object, it does not touch the device.
func New(bus drivers.I2C) Device {
return Device{bus: bus, Address: Address0}
return Device{bus: bus, address: Address0}
}
// Configure sets up the device for communication
func (d *Device) Configure() {
func (d *Device) Configure(config Config) error {
if config.Address != 0 {
d.address = config.Address
}
// enable all axes, normal mode
legacy.WriteRegister(d.bus, uint8(d.Address), REG_CTRL1, []byte{0x07})
err := legacy.WriteRegister(d.bus, uint8(d.address), REG_CTRL1, []byte{0x07})
if err != nil {
return err
}
// 400Hz rate
d.SetDataRate(DATARATE_400_HZ)
err = d.SetDataRate(DATARATE_400_HZ)
if err != nil {
return err
}
// High res & BDU enabled
legacy.WriteRegister(d.bus, uint8(d.Address), REG_CTRL4, []byte{0x88})
err = legacy.WriteRegister(d.bus, uint8(d.address), REG_CTRL4, []byte{0x88})
if err != nil {
return err
}
// get current range
d.r = d.ReadRange()
d.r, err = d.ReadRange()
return err
}
// Connected returns whether a LIS3DH has been found.
// It does a "who am I" request and checks the response.
func (d *Device) Connected() bool {
data := []byte{0}
err := legacy.ReadRegister(d.bus, uint8(d.Address), WHO_AM_I, data)
err := legacy.ReadRegister(d.bus, uint8(d.address), WHO_AM_I, data)
if err != nil {
return false
}
@@ -49,46 +69,51 @@ func (d *Device) Connected() bool {
}
// SetDataRate sets the speed of data collected by the LIS3DH.
func (d *Device) SetDataRate(rate DataRate) {
func (d *Device) SetDataRate(rate DataRate) error {
ctl1 := []byte{0}
err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_CTRL1, ctl1)
err := legacy.ReadRegister(d.bus, uint8(d.address), REG_CTRL1, ctl1)
if err != nil {
println(err.Error())
return err
}
// mask off bits
ctl1[0] &^= 0xf0
ctl1[0] |= (byte(rate) << 4)
legacy.WriteRegister(d.bus, uint8(d.Address), REG_CTRL1, ctl1)
return legacy.WriteRegister(d.bus, uint8(d.address), REG_CTRL1, ctl1)
}
// SetRange sets the G range for LIS3DH.
func (d *Device) SetRange(r Range) {
func (d *Device) SetRange(r Range) error {
ctl := []byte{0}
err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_CTRL4, ctl)
err := legacy.ReadRegister(d.bus, uint8(d.address), REG_CTRL4, ctl)
if err != nil {
println(err.Error())
return err
}
// mask off bits
ctl[0] &^= 0x30
ctl[0] |= (byte(r) << 4)
legacy.WriteRegister(d.bus, uint8(d.Address), REG_CTRL4, ctl)
err = legacy.WriteRegister(d.bus, uint8(d.address), REG_CTRL4, ctl)
if err != nil {
return err
}
// store the new range
d.r = r
return nil
}
// ReadRange returns the current G range for LIS3DH.
func (d *Device) ReadRange() (r Range) {
func (d *Device) ReadRange() (r Range, err error) {
ctl := []byte{0}
err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_CTRL4, ctl)
err = legacy.ReadRegister(d.bus, uint8(d.address), REG_CTRL4, ctl)
if err != nil {
println(err.Error())
return 0, err
}
// mask off bits
r = Range(ctl[0] >> 4)
r &= 0x03
return r
return r, nil
}
// ReadAcceleration reads the current acceleration from the device and returns
@@ -96,28 +121,17 @@ func (d *Device) ReadRange() (r Range) {
// and the sensor is not moving the returned value will be around 1000000 or
// -1000000.
func (d *Device) ReadAcceleration() (int32, int32, int32, error) {
x, y, z := d.ReadRawAcceleration()
divider := float32(1)
switch d.r {
case RANGE_16_G:
divider = 1365
case RANGE_8_G:
divider = 4096
case RANGE_4_G:
divider = 8190
case RANGE_2_G:
divider = 16380
}
return int32(float32(x) / divider * 1000000), int32(float32(y) / divider * 1000000), int32(float32(z) / divider * 1000000), nil
rawX, rawY, rawZ := d.ReadRawAcceleration()
x, y, z := normalizeRange(rawX, rawY, rawZ, d.r)
return x, y, z, nil
}
// ReadRawAcceleration returns the raw x, y and z axis from the LIS3DH
func (d *Device) ReadRawAcceleration() (x int16, y int16, z int16) {
legacy.WriteRegister(d.bus, uint8(d.Address), REG_OUT_X_L|0x80, nil)
legacy.WriteRegister(d.bus, uint8(d.address), REG_OUT_X_L|0x80, nil)
data := []byte{0, 0, 0, 0, 0, 0}
d.bus.Tx(d.Address, nil, data)
d.bus.Tx(d.address, nil, data)
x = int16((uint16(data[1]) << 8) | uint16(data[0]))
y = int16((uint16(data[3]) << 8) | uint16(data[2]))
@@ -125,3 +139,68 @@ func (d *Device) ReadRawAcceleration() (x int16, y int16, z int16) {
return
}
// Update the sensor values of the 'which' parameter. Only acceleration is
// supported at the moment.
func (d *Device) Update(which drivers.Measurement) error {
if which&drivers.Acceleration != 0 {
// Read raw acceleration values and store them in the driver.
err := legacy.WriteRegister(d.bus, uint8(d.address), REG_OUT_X_L|0x80, nil)
if err != nil {
return err
}
err = d.bus.Tx(d.address, nil, d.accel[:])
if err != nil {
return err
}
}
return nil
}
// Acceleration returns the last read acceleration in µg (micro-gravity).
// When one of the axes is pointing straight to Earth and the sensor is not
// moving the returned value will be around 1000000 or -1000000.
func (d *Device) Acceleration() (x, y, z int32) {
// Extract the raw 16-bit values.
rawX := int16((uint16(d.accel[1]) << 8) | uint16(d.accel[0]))
rawY := int16((uint16(d.accel[3]) << 8) | uint16(d.accel[2]))
rawZ := int16((uint16(d.accel[5]) << 8) | uint16(d.accel[4]))
// Normalize these values, to be in µg (micro-gravity).
return normalizeRange(rawX, rawY, rawZ, d.r)
}
// Convert raw 16-bit values to normalized 32-bit values while avoiding floats
// and divisions.
func normalizeRange(rawX, rawY, rawZ int16, r Range) (x, y, z int32) {
// We're going to convert the 16-bit raw values to values in the range
// -1000_000..1000_000. For now we're going to assume a range of 16G, we'll
// adjust that range later.
// The formula is derived as follows, and carefully selected to avoid
// overflow and integer divisions (the division will be optimized to a
// bitshift):
// x = x * 1000_000 / 2048
// x = x * (1000_000/64) / (2048/64)
// x = x * 15625 / 32
x = int32(rawX) * 15625 / 32
y = int32(rawY) * 15625 / 32
z = int32(rawZ) * 15625 / 32
// Now we need to normalize the three values, since we assumed 16G before.
shift := uint32(0)
switch r {
case RANGE_16_G:
shift = 0
case RANGE_8_G:
shift = 1
case RANGE_4_G:
shift = 2
case RANGE_2_G:
shift = 3
}
x >>= shift
y >>= shift
z >>= shift
return
}
+1 -1
View File
@@ -36,7 +36,7 @@ type Configuration struct {
MagDataRate uint8
}
var errNotConnected = errors.New("lsm303agr: failed to communicate with either acel or magnet sensor")
var errNotConnected = errors.New("lsm303agr: failed to communicate with either accel or magnet sensor")
// New creates a new LSM303AGR connection. The I2C bus must already be configured.
//
+214
View File
@@ -0,0 +1,214 @@
// Package lsm303dlhc implements a driver for the LSM303dlhc,
// a 3 axis accelerometer/magnetic sensor typically available on breakout boards.
//
// Datasheet: https://www.st.com/resource/en/datasheet/lsm303dlhc.pdf
package lsm303dlhc // import "tinygo.org/x/drivers/lsm303dlhc"
import (
"math"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/legacy"
)
// Device wraps an I2C connection to a LSM303dlhc device.
type Device struct {
bus drivers.I2C
AccelAddress uint8
MagAddress uint8
AccelPowerMode uint8
AccelRange uint8
AccelDataRate uint8
MagPowerMode uint8
MagSystemMode uint8
MagDataRate uint8
buf [6]uint8
}
// Configuration for LSM303dlhc device.
type Configuration struct {
AccelPowerMode uint8
AccelRange uint8
AccelDataRate uint8
MagPowerMode uint8
MagSystemMode uint8
MagDataRate uint8
}
// New creates a new LSM303DLHC connection. The I2C bus must already be configured.
// This function only creates the Device object, it does not touch the device.
func New(bus drivers.I2C) *Device {
return &Device{
bus: bus,
AccelAddress: ACCEL_ADDRESS,
MagAddress: MAG_ADDRESS,
}
}
// Configure sets up the LSM303dlhc device for communication.
func (d *Device) Configure(cfg Configuration) (err error) {
if cfg.AccelDataRate != 0 {
d.AccelDataRate = cfg.AccelDataRate
} else {
d.AccelDataRate = ACCEL_DATARATE_100HZ
}
if cfg.AccelPowerMode != 0 {
d.AccelPowerMode = cfg.AccelPowerMode
} else {
d.AccelPowerMode = ACCEL_POWER_NORMAL
}
if cfg.AccelRange != 0 {
d.AccelRange = cfg.AccelRange
} else {
d.AccelRange = ACCEL_RANGE_2G
}
if cfg.MagPowerMode != 0 {
d.MagPowerMode = cfg.MagPowerMode
} else {
d.MagPowerMode = MAG_POWER_NORMAL
}
if cfg.MagDataRate != 0 {
d.MagDataRate = cfg.MagDataRate
} else {
d.MagDataRate = MAG_DATARATE_10HZ
}
if cfg.MagSystemMode != 0 {
d.MagSystemMode = cfg.MagSystemMode
} else {
d.MagSystemMode = MAG_SYSTEM_CONTINUOUS
}
data := d.buf[:1]
data[0] = byte(d.AccelDataRate<<4 | d.AccelPowerMode | 0x07)
err = legacy.WriteRegister(d.bus, uint8(d.AccelAddress), ACCEL_CTRL_REG1_A, data)
if err != nil {
return
}
data[0] = byte(0x80 | d.AccelRange<<4)
err = legacy.WriteRegister(d.bus, uint8(d.AccelAddress), ACCEL_CTRL_REG4_A, data)
if err != nil {
return
}
data[0] = byte(0xC0)
err = legacy.WriteRegister(d.bus, uint8(d.AccelAddress), CRA_REG_M, data)
if err != nil {
return
}
// Temperature compensation is on for magnetic sensor
data[0] = byte(0x80 | d.MagPowerMode<<4 | d.MagDataRate<<2 | d.MagSystemMode)
err = legacy.WriteRegister(d.bus, uint8(d.MagAddress), MAG_MR_REG_M, data)
if err != nil {
return
}
return nil
}
// ReadAcceleration reads the current acceleration from the device and returns
// it in µg (micro-gravity). When one of the axes is pointing straight to Earth
// and the sensor is not moving the returned value will be around 1000000 or
// -1000000.
func (d *Device) ReadAcceleration() (x, y, z int32, err error) {
data := d.buf[:6]
err = legacy.ReadRegister(d.bus, uint8(d.AccelAddress), ACCEL_OUT_AUTO_INC, data)
if err != nil {
return
}
rangeFactor := int16(0)
switch d.AccelRange {
case ACCEL_RANGE_2G:
rangeFactor = 1
case ACCEL_RANGE_4G:
rangeFactor = 2
case ACCEL_RANGE_8G:
rangeFactor = 4
case ACCEL_RANGE_16G:
rangeFactor = 12 // the readings in 16G are a bit lower
}
x = int32(int32(int16((uint16(data[1])<<8|uint16(data[0])))>>4*rangeFactor) * 1000000 / 1024)
y = int32(int32(int16((uint16(data[3])<<8|uint16(data[2])))>>4*rangeFactor) * 1000000 / 1024)
z = int32(int32(int16((uint16(data[5])<<8|uint16(data[4])))>>4*rangeFactor) * 1000000 / 1024)
return
}
// ReadPitchRoll reads the current pitch and roll angles from the device and
// returns it in micro-degrees. When the z axis is pointing straight to Earth
// the returned values of pitch and roll would be zero.
func (d *Device) ReadPitchRoll() (pitch, roll int32, err error) {
x, y, z, err := d.ReadAcceleration()
if err != nil {
return
}
xf, yf, zf := float64(x), float64(y), float64(z)
pitch = int32((math.Round(math.Atan2(yf, math.Sqrt(math.Pow(xf, 2)+math.Pow(zf, 2)))*(180/math.Pi)*100) / 100) * 1000000)
roll = int32((math.Round(math.Atan2(xf, math.Sqrt(math.Pow(yf, 2)+math.Pow(zf, 2)))*(180/math.Pi)*100) / 100) * 1000000)
return
}
// ReadMagneticField reads the current magnetic field from the device and returns
// it in mG (milligauss). 1 mG = 0.1 µT (microtesla).
func (d *Device) ReadMagneticField() (x, y, z int32, err error) {
if d.MagSystemMode == MAG_SYSTEM_SINGLE {
cmd := d.buf[:1]
cmd[0] = byte(0x80 | d.MagPowerMode<<4 | d.MagDataRate<<2 | d.MagSystemMode)
err = legacy.WriteRegister(d.bus, uint8(d.MagAddress), MAG_MR_REG_M, cmd)
if err != nil {
return
}
}
data := d.buf[0:6]
legacy.ReadRegister(d.bus, uint8(d.MagAddress), MAG_OUT_AUTO_INC, data)
x = int32(int16((uint16(data[1])<<8 | uint16(data[0]))))
y = int32(int16((uint16(data[3])<<8 | uint16(data[2]))))
z = int32(int16((uint16(data[5])<<8 | uint16(data[4]))))
return
}
// ReadCompass reads the current compass heading from the device and returns
// it in micro-degrees. When the z axis is pointing straight to Earth and
// the y axis is pointing to North, the heading would be zero.
//
// However, the heading may be off due to electronic compasses would be effected
// by strong magnetic fields and require constant calibration.
func (d *Device) ReadCompass() (h int32, err error) {
x, y, _, err := d.ReadMagneticField()
if err != nil {
return
}
xf, yf := float64(x), float64(y)
h = int32(float32((180/math.Pi)*math.Atan2(yf, xf)) * 1000000)
return
}
// ReadTemperature returns the temperature in Celsius milli degrees (°C/1000)
func (d *Device) ReadTemperature() (t int32, err error) {
data := d.buf[:2]
err = legacy.ReadRegister(d.bus, uint8(d.MagAddress), TEMP_OUT_AUTO_INC, data)
if err != nil {
return
}
r := int16((uint16(data[1])<<8 | uint16(data[0]))) >> 4 // temperature offset from 25 °C
t = 25000 + int32((float32(r)/8)*1000)
return
}
+75
View File
@@ -0,0 +1,75 @@
package lsm303dlhc
const (
// Constants/addresses used for I2C.
ACCEL_ADDRESS = 0x19
MAG_ADDRESS = 0x1E
// i2C 8-bit subaddress (SUB): the 7 LSb represent the actual register address
// while the MSB enables address auto increment.
// If the MSb of the SUB field is 1, the SUB (register address) is
// automatically increased to allow multiple data read/writes.
ADDR_AUTO_INC_MASK = 0x80
// accelerometer registers.
ACCEL_CTRL_REG1_A = 0x20
ACCEL_CTRL_REG4_A = 0x23
ACCEL_OUT_X_L_A = 0x28
ACCEL_OUT_X_H_A = 0x29
ACCEL_OUT_Y_L_A = 0x2A
ACCEL_OUT_Y_H_A = 0x2B
ACCEL_OUT_Z_L_A = 0x2C
ACCEL_OUT_Z_H_A = 0x2D
ACCEL_OUT_AUTO_INC = ACCEL_OUT_X_L_A | ADDR_AUTO_INC_MASK
// magnetic sensor registers.
MAG_MR_REG_M = 0x02
MAG_OUT_X_L_M = 0x68
MAG_OUT_X_H_M = 0x69
MAG_OUT_Y_L_M = 0x6A
MAG_OUT_Y_H_M = 0x6B
MAG_OUT_Z_L_M = 0x6C
MAG_OUT_Z_H_M = 0x6D
MAG_OUT_AUTO_INC = MAG_OUT_X_L_M | ADDR_AUTO_INC_MASK
// temperature sensor registers.
CRA_REG_M = 0x80
TEMP_OUT_L_M = 0x32
TEMP_OUT_H_M = 0x31
TEMP_OUT_AUTO_INC = TEMP_OUT_L_M | ADDR_AUTO_INC_MASK
// accelerometer power mode.
ACCEL_POWER_NORMAL = 0x00 // default
ACCEL_POWER_LOW = 0x08
// accelerometer range.
ACCEL_RANGE_2G = 0x00 // default
ACCEL_RANGE_4G = 0x01
ACCEL_RANGE_8G = 0x02
ACCEL_RANGE_16G = 0x03
// accelerometer data rate.
ACCEL_DATARATE_1HZ = 0x01
ACCEL_DATARATE_10HZ = 0x02
ACCEL_DATARATE_25HZ = 0x03
ACCEL_DATARATE_50HZ = 0x04
ACCEL_DATARATE_100HZ = 0x05 // default
ACCEL_DATARATE_200HZ = 0x06
ACCEL_DATARATE_400HZ = 0x07
ACCEL_DATARATE_1344HZ = 0x09 // 5376Hz in low-power mode
// magnetic sensor power mode.
MAG_POWER_NORMAL = 0x00 // default
MAG_POWER_LOW = 0x01
// magnetic sensor operate mode.
MAG_SYSTEM_CONTINUOUS = 0x00 // default
MAG_SYSTEM_SINGLE = 0x01
// magnetic sensor data rate
MAG_DATARATE_10HZ = 0x00 // default
MAG_DATARATE_20HZ = 0x01
MAG_DATARATE_50HZ = 0x02
MAG_DATARATE_100HZ = 0x03
)
+35 -24
View File
@@ -8,7 +8,6 @@ import (
"errors"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/legacy"
)
type AccelRange uint8
@@ -26,7 +25,7 @@ type Device struct {
accelSampleRate AccelSampleRate
gyroRange GyroRange
gyroSampleRate GyroSampleRate
buf [6]uint8
buf [7]uint8 // up to 6 bytes for read + 1 byte for the register address
}
// Configuration for LSM6DS3TR device.
@@ -84,30 +83,20 @@ func (d *Device) doConfigure(cfg Configuration) (err error) {
d.gyroSampleRate = GYRO_SR_104
}
data := d.buf[:1]
// Configure accelerometer
data[0] = uint8(d.accelRange) | uint8(d.accelSampleRate)
err = legacy.WriteRegister(d.bus, uint8(d.Address), CTRL1_XL, data)
err = d.writeByte(CTRL1_XL, uint8(d.accelRange)|uint8(d.accelSampleRate))
if err != nil {
return
}
// Set ODR bit
err = legacy.ReadRegister(d.bus, uint8(d.Address), CTRL4_C, data)
if err != nil {
return
}
data[0] = data[0] &^ BW_SCAL_ODR_ENABLED
data[0] |= BW_SCAL_ODR_ENABLED
err = legacy.WriteRegister(d.bus, uint8(d.Address), CTRL4_C, data)
// Enable ODR scaling
err = d.setBits(CTRL4_C, BW_SCAL_ODR_ENABLED)
if err != nil {
return
}
// Configure gyroscope
data[0] = uint8(d.gyroRange) | uint8(d.gyroSampleRate)
err = legacy.WriteRegister(d.bus, uint8(d.Address), CTRL2_G, data)
err = d.writeByte(CTRL2_G, uint8(d.gyroRange)|uint8(d.gyroSampleRate))
if err != nil {
return
}
@@ -118,8 +107,10 @@ func (d *Device) doConfigure(cfg Configuration) (err error) {
// Connected returns whether a LSM6DS3TR has been found.
// It does a "who am I" request and checks the response.
func (d *Device) Connected() bool {
data := d.buf[:1]
legacy.ReadRegister(d.bus, uint8(d.Address), WHO_AM_I, data)
data, err := d.readBytes(WHO_AM_I, 1)
if err != nil {
return false
}
return data[0] == 0x6A
}
@@ -128,8 +119,7 @@ func (d *Device) Connected() bool {
// and the sensor is not moving the returned value will be around 1000000 or
// -1000000.
func (d *Device) ReadAcceleration() (x, y, z int32, err error) {
data := d.buf[:6]
err = legacy.ReadRegister(d.bus, uint8(d.Address), OUTX_L_XL, data)
data, err := d.readBytes(OUTX_L_XL, 6)
if err != nil {
return
}
@@ -153,8 +143,7 @@ func (d *Device) ReadAcceleration() (x, y, z int32, err error) {
// rotation along one axis and while doing so integrate all values over time,
// you would get a value close to 360000000.
func (d *Device) ReadRotation() (x, y, z int32, err error) {
data := d.buf[:6]
err = legacy.ReadRegister(d.bus, uint8(d.Address), OUTX_L_G, data)
data, err := d.readBytes(OUTX_L_G, 6)
if err != nil {
return
}
@@ -177,8 +166,7 @@ func (d *Device) ReadRotation() (x, y, z int32, err error) {
// ReadTemperature returns the temperature in celsius milli degrees (°C/1000)
func (d *Device) ReadTemperature() (t int32, err error) {
data := d.buf[:2]
err = legacy.ReadRegister(d.bus, uint8(d.Address), OUT_TEMP_L, data)
data, err := d.readBytes(OUT_TEMP_L, 2)
if err != nil {
return
}
@@ -187,3 +175,26 @@ func (d *Device) ReadTemperature() (t int32, err error) {
t = 25000 + (int32(int16((int16(data[1])<<8)|int16(data[0])))*125)/32
return
}
func (d *Device) readBytes(reg, size uint8) ([]byte, error) {
d.buf[0] = reg
err := d.bus.Tx(d.Address, d.buf[0:1], d.buf[1:size+1])
if err != nil {
return nil, err
}
return d.buf[1 : size+1], nil
}
func (d *Device) writeByte(reg, value uint8) error {
d.buf[0] = reg
d.buf[1] = value
return d.bus.Tx(d.Address, d.buf[0:2], nil)
}
func (d *Device) setBits(reg, bits uint8) error {
data, err := d.readBytes(reg, 1)
if err != nil {
return err
}
return d.writeByte(reg, (data[0]&^bits)|bits)
}
+35 -28
View File
@@ -7,7 +7,6 @@ import (
"errors"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/legacy"
)
type AccelRange uint8
@@ -28,7 +27,7 @@ type Device struct {
accelMultiplier int32
gyroMultiplier int32
magMultiplier int32
buf [6]uint8
buf [7]uint8 // up to 6 bytes for read + 1 byte for the register address
}
// Configuration for LSM9DS1 device.
@@ -61,10 +60,15 @@ func New(bus drivers.I2C) *Device {
// Case of boolean false and error nil means I2C is up,
// but "who am I" responses have unexpected values.
func (d *Device) Connected() bool {
data1, data2 := d.buf[:1], d.buf[1:2]
legacy.ReadRegister(d.bus, d.AccelAddress, WHO_AM_I, data1)
legacy.ReadRegister(d.bus, d.MagAddress, WHO_AM_I_M, data2)
return data1[0] == 0x68 && data2[0] == 0x3D
data, err := d.readBytes(d.AccelAddress, WHO_AM_I, 1)
if err != nil || data[0] != 0x68 {
return false
}
data, err = d.readBytes(d.MagAddress, WHO_AM_I_M, 1)
if err != nil || data[0] != 0x3D {
return false
}
return true
}
// ReadAcceleration reads the current acceleration from the device and returns
@@ -72,8 +76,7 @@ func (d *Device) Connected() bool {
// and the sensor is not moving the returned value will be around 1000000 or
// -1000000.
func (d *Device) ReadAcceleration() (x, y, z int32, err error) {
data := d.buf[:6]
err = legacy.ReadRegister(d.bus, uint8(d.AccelAddress), OUT_X_L_XL, data)
data, err := d.readBytes(d.AccelAddress, OUT_X_L_XL, 6)
if err != nil {
return
}
@@ -88,8 +91,7 @@ func (d *Device) ReadAcceleration() (x, y, z int32, err error) {
// rotation along one axis and while doing so integrate all values over time,
// you would get a value close to 360000000.
func (d *Device) ReadRotation() (x, y, z int32, err error) {
data := d.buf[:6]
err = legacy.ReadRegister(d.bus, uint8(d.AccelAddress), OUT_X_L_G, data)
data, err := d.readBytes(d.AccelAddress, OUT_X_L_G, 6)
if err != nil {
return
}
@@ -102,8 +104,7 @@ func (d *Device) ReadRotation() (x, y, z int32, err error) {
// ReadMagneticField reads the current magnetic field from the device and returns
// it in nT (nanotesla). 1 G (gauss) = 100_000 nT (nanotesla).
func (d *Device) ReadMagneticField() (x, y, z int32, err error) {
data := d.buf[:6]
err = legacy.ReadRegister(d.bus, uint8(d.MagAddress), OUT_X_L_M, data)
data, err := d.readBytes(d.MagAddress, OUT_X_L_M, 6)
if err != nil {
return
}
@@ -115,8 +116,7 @@ func (d *Device) ReadMagneticField() (x, y, z int32, err error) {
// ReadTemperature returns the temperature in Celsius milli degrees (°C/1000)
func (d *Device) ReadTemperature() (t int32, err error) {
data := d.buf[:2]
err = legacy.ReadRegister(d.bus, uint8(d.AccelAddress), OUT_TEMP_L, data)
data, err := d.readBytes(d.AccelAddress, OUT_TEMP_L, 2)
if err != nil {
return
}
@@ -167,20 +167,16 @@ func (d *Device) doConfigure(cfg Configuration) (err error) {
d.magMultiplier = 58
}
data := d.buf[:1]
// Configure accelerometer
// Sample rate & measurement range
data[0] = uint8(cfg.AccelSampleRate)<<5 | uint8(cfg.AccelRange)<<3
err = legacy.WriteRegister(d.bus, d.AccelAddress, CTRL_REG6_XL, data)
err = d.writeByte(d.AccelAddress, CTRL_REG6_XL, uint8(cfg.AccelSampleRate)<<5|uint8(cfg.AccelRange)<<3)
if err != nil {
return
}
// Configure gyroscope
// Sample rate & measurement range
data[0] = uint8(cfg.GyroSampleRate)<<5 | uint8(cfg.GyroRange)<<3
err = legacy.WriteRegister(d.bus, d.AccelAddress, CTRL_REG1_G, data)
err = d.writeByte(d.AccelAddress, CTRL_REG1_G, uint8(cfg.GyroSampleRate)<<5|uint8(cfg.GyroRange)<<3)
if err != nil {
return
}
@@ -190,33 +186,44 @@ func (d *Device) doConfigure(cfg Configuration) (err error) {
// Temperature compensation enabled
// High-performance mode XY axis
// Sample rate
data[0] = 0b10000000 | 0b01000000 | uint8(cfg.MagSampleRate)<<2
err = legacy.WriteRegister(d.bus, d.MagAddress, CTRL_REG1_M, data)
err = d.writeByte(d.MagAddress, CTRL_REG1_M, 0b10000000|0b01000000|uint8(cfg.MagSampleRate)<<2)
if err != nil {
return
}
// Measurement range
data[0] = uint8(cfg.MagRange) << 5
err = legacy.WriteRegister(d.bus, d.MagAddress, CTRL_REG2_M, data)
err = d.writeByte(d.MagAddress, CTRL_REG2_M, uint8(cfg.MagRange)<<5)
if err != nil {
return
}
// Continuous-conversion mode
// https://electronics.stackexchange.com/questions/237397/continuous-conversion-vs-single-conversion-mode
data[0] = 0b00000000
err = legacy.WriteRegister(d.bus, d.MagAddress, CTRL_REG3_M, data)
err = d.writeByte(d.MagAddress, CTRL_REG3_M, 0b00000000)
if err != nil {
return
}
// High-performance mode Z axis
data[0] = 0b00001000
err = legacy.WriteRegister(d.bus, d.MagAddress, CTRL_REG4_M, data)
err = d.writeByte(d.MagAddress, CTRL_REG4_M, 0b00001000)
if err != nil {
return
}
return nil
}
func (d *Device) readBytes(addr, reg, size uint8) ([]byte, error) {
d.buf[0] = reg
err := d.bus.Tx(uint16(addr), d.buf[0:1], d.buf[1:size+1])
if err != nil {
return nil, err
}
return d.buf[1 : size+1], nil
}
func (d *Device) writeByte(addr, reg, value uint8) error {
d.buf[0] = reg
d.buf[1] = value
return d.bus.Tx(uint16(addr), d.buf[0:2], nil)
}
+49
View File
@@ -0,0 +1,49 @@
package seesaw
import (
"errors"
)
var errInvalidEncoderNumber = errors.New("invalid encoder choice, 0-15 are supported")
// GetEncoderPosition returns the absolute position (or delta since the previous call) of the specified rotary encoder.
func (d *Device) GetEncoderPosition(encoder uint, asDelta bool) (int32, error) {
if encoder >= 16 {
return 0, errInvalidEncoderNumber
}
// The function address' upper nibble is the function, the lower nibble selects which encoder to communicate with
fnAddr := FunctionAddress(encoder)
if asDelta {
fnAddr |= FunctionEncoderDelta
} else {
fnAddr |= FunctionEncoderPosition
}
var buf [4]byte
err := d.Read(ModuleEncoderBase, fnAddr, buf[:])
if err != nil {
return 0, err
}
return int32(buf[0])<<24 | int32(buf[1])<<16 | int32(buf[2])<<8 | int32(buf[3]), nil
}
// SetEncoderPosition calibrate's the encoder's current absolute position to be whatever the provided position is.
func (d *Device) SetEncoderPosition(encoder uint, position int32) error {
if encoder >= 16 {
return errInvalidEncoderNumber
}
// The function address' upper nibble is the function, the lower nibble selects which encoder to communicate with
fnAddr := FunctionEncoderPosition | FunctionAddress(encoder)
buf := [4]byte{
byte(position >> 24),
byte(position >> 16),
byte(position >> 8),
byte(position),
}
return d.Write(ModuleEncoderBase, fnAddr, buf[:])
}
+10
View File
@@ -98,3 +98,13 @@ const (
FunctionKeypadCount FunctionAddress = 0x04
FunctionKeypadFifo FunctionAddress = 0x10
)
// encoder module function address registers
// these are the defaults for encoder 0, change the lower nibble to address other encoders
// see the Device.GetEncoderPosition and SetEncoderPosition methods for examples.
const (
FunctionEncoderIntenset FunctionAddress = 0x10
FunctionEncoderIntenclr FunctionAddress = 0x20
FunctionEncoderPosition FunctionAddress = 0x30
FunctionEncoderDelta FunctionAddress = 0x40
)
+7 -3
View File
@@ -44,6 +44,7 @@ tinygo build -size short -o ./build/test.hex -target=pyportal ./examples/ili9341
tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/lis3dh/main.go
tinygo build -size short -o ./build/test.hex -target=nano-33-ble ./examples/lps22hb/main.go
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/lsm303agr/main.go
tinygo build -size short -o ./build/test.hex -target=feather-m4 ./examples/lsm303dlhc/main.go
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/lsm6ds3/main.go
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/mag3110/main.go
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/mcp23017/main.go
@@ -58,15 +59,17 @@ tinygo build -size short -o ./build/test.hex -target=p1am-100 ./examples/p1am/ma
tinygo build -size short -o ./build/test.hex -target=pico ./examples/pca9685/main.go
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/pcd8544/setbuffer/main.go
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/pcd8544/setpixel/main.go
tinygo build -size short -o ./build/test.hex -target=feather-rp2040 ./examples/seesaw
tinygo build -size short -o ./build/test.hex -target=feather-rp2040 ./examples/seesaw/soil-sensor
tinygo build -size short -o ./build/test.hex -target=qtpy-rp2040 ./examples/seesaw/rotary-encoder
tinygo build -size short -o ./build/test.hex -target=arduino ./examples/servo
tinygo build -size short -o ./build/test.hex -target=pico ./examples/sgp30
tinygo build -size short -o ./build/test.hex -target=pybadge ./examples/shifter/main.go
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/sht3x/main.go
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/sht4x/main.go
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/shtc3/main.go
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/ssd1306/i2c_128x32/main.go
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/ssd1306/spi_128x64/main.go
tinygo build -size short -o ./build/test.hex -target=xiao-ble ./examples/ssd1306/
tinygo build -size short -o ./build/test.hex -target=xiao-rp2040 ./examples/ssd1306/
tinygo build -size short -o ./build/test.hex -target=thumby ./examples/ssd1306/
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/ssd1331/main.go
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/st7735/main.go
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/st7789/main.go
@@ -140,6 +143,7 @@ tinygo build -size short -o ./build/test.uf2 -target=pico ./examples/tmc2209/mai
tinygo build -size short -o ./build/test.hex -target=pico ./examples/tmc5160/main.go
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
# network examples (espat)
tinygo build -size short -o ./build/test.hex -target=challenger-rp2040 ./examples/net/ntpclient/
# network examples (wifinina)
+29 -139
View File
@@ -6,11 +6,9 @@ package ssd1306 // import "tinygo.org/x/drivers/ssd1306"
import (
"errors"
"image/color"
"machine"
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/legacy"
"tinygo.org/x/drivers/pixel"
)
@@ -23,16 +21,15 @@ type ResetValue [2]byte
// Device wraps I2C or SPI connection.
type Device struct {
bus Buser
buffer []byte
width int16
height int16
bufferSize int16
vccState VccMode
canReset bool
resetCol ResetValue
resetPage ResetValue
rotation drivers.Rotation
bus Buser
buffer []byte
width int16
height int16
vccState VccMode
canReset bool
resetCol ResetValue
resetPage ResetValue
rotation drivers.Rotation
}
// Config is the configuration for the display
@@ -51,51 +48,15 @@ type Config struct {
Rotation drivers.Rotation
}
type I2CBus struct {
wire drivers.I2C
Address uint16
}
type SPIBus struct {
wire drivers.SPI
dcPin machine.Pin
resetPin machine.Pin
csPin machine.Pin
}
type Buser interface {
configure() error
tx(data []byte, isCommand bool) error
setAddress(address uint16) error
configure(address uint16, size int16) []byte // configure the bus and return the image buffer to use
command(cmd uint8) error // send a command to the display
flush() error // send the image to the display, faster than "tx()" in i2c case since avoids slice copy
tx(data []byte, isCommand bool) error // generic transmit function
}
type VccMode uint8
// NewI2C creates a new SSD1306 connection. The I2C wire must already be configured.
func NewI2C(bus drivers.I2C) Device {
return Device{
bus: &I2CBus{
wire: bus,
Address: Address,
},
}
}
// NewSPI creates a new SSD1306 connection. The SPI wire must already be configured.
func NewSPI(bus drivers.SPI, dcPin, resetPin, csPin machine.Pin) Device {
dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
resetPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
return Device{
bus: &SPIBus{
wire: bus,
dcPin: dcPin,
resetPin: resetPin,
csPin: csPin,
},
}
}
// Configure initializes the display with default configuration
func (d *Device) Configure(cfg Config) {
var zeroReset ResetValue
@@ -109,9 +70,6 @@ func (d *Device) Configure(cfg Config) {
} else {
d.height = 64
}
if cfg.Address != 0 {
d.bus.setAddress(cfg.Address)
}
if cfg.VccState != 0 {
d.vccState = cfg.VccState
} else {
@@ -127,11 +85,9 @@ func (d *Device) Configure(cfg Config) {
} else {
d.resetPage = ResetValue{0, uint8(d.height/8) - 1}
}
d.bufferSize = d.width * d.height / 8
d.buffer = make([]byte, d.bufferSize)
d.canReset = cfg.Address != 0 || d.width != 128 || d.height != 64 // I2C or not 128x64
d.bus.configure()
d.buffer = d.bus.configure(cfg.Address, d.width*d.height/8)
time.Sleep(100 * time.Nanosecond)
d.Command(DISPLAYOFF)
@@ -193,11 +149,22 @@ func (d *Device) Configure(cfg Config) {
d.Command(NORMALDISPLAY)
d.Command(DEACTIVATE_SCROLL)
d.Command(DISPLAYON)
}
// Command sends a command to the display
func (d *Device) Command(command uint8) {
d.bus.command(command)
}
// Tx sends data to the display; if isCommand is false, this also updates the image buffer.
func (d *Device) Tx(data []byte, isCommand bool) error {
return d.bus.tx(data, isCommand)
}
// ClearBuffer clears the image buffer
func (d *Device) ClearBuffer() {
for i := int16(0); i < d.bufferSize; i++ {
for i := 0; i < len(d.buffer); i++ {
d.buffer[i] = 0
}
}
@@ -223,7 +190,7 @@ func (d *Device) Display() error {
d.Command(d.resetPage[1])
}
return d.Tx(d.buffer, false)
return d.bus.flush()
}
// SetPixel enables or disables a pixel in the buffer
@@ -252,12 +219,10 @@ func (d *Device) GetPixel(x int16, y int16) bool {
// SetBuffer changes the whole buffer at once
func (d *Device) SetBuffer(buffer []byte) error {
if int16(len(buffer)) != d.bufferSize {
if len(buffer) != len(d.buffer) {
return errBufferSize
}
for i := int16(0); i < d.bufferSize; i++ {
d.buffer[i] = buffer[i]
}
copy(d.buffer, buffer)
return nil
}
@@ -266,81 +231,6 @@ func (d *Device) GetBuffer() []byte {
return d.buffer
}
// Command sends a command to the display
func (d *Device) Command(command uint8) {
d.bus.tx([]byte{command}, true)
}
// setAddress sets the address to the I2C bus
func (b *I2CBus) setAddress(address uint16) error {
b.Address = address
return nil
}
// setAddress does nothing, but it's required to avoid reflection
func (b *SPIBus) setAddress(address uint16) error {
// do nothing
println("trying to Configure an address on a SPI device")
return nil
}
// configure does nothing, but it's required to avoid reflection
func (b *I2CBus) configure() error { return nil }
// configure configures some pins with the SPI bus
func (b *SPIBus) configure() error {
b.csPin.Low()
b.dcPin.Low()
b.resetPin.Low()
b.resetPin.High()
time.Sleep(1 * time.Millisecond)
b.resetPin.Low()
time.Sleep(10 * time.Millisecond)
b.resetPin.High()
return nil
}
// Tx sends data to the display
func (d *Device) Tx(data []byte, isCommand bool) error {
return d.bus.tx(data, isCommand)
}
// tx sends data to the display (I2CBus implementation)
func (b *I2CBus) tx(data []byte, isCommand bool) error {
if isCommand {
return legacy.WriteRegister(b.wire, uint8(b.Address), 0x00, data)
} else {
return legacy.WriteRegister(b.wire, uint8(b.Address), 0x40, data)
}
}
// tx sends data to the display (SPIBus implementation)
func (b *SPIBus) tx(data []byte, isCommand bool) error {
var err error
if isCommand {
b.csPin.High()
time.Sleep(1 * time.Millisecond)
b.dcPin.Low()
b.csPin.Low()
err = b.wire.Tx(data, nil)
b.csPin.High()
} else {
b.csPin.High()
time.Sleep(1 * time.Millisecond)
b.dcPin.High()
b.csPin.Low()
err = b.wire.Tx(data, nil)
b.csPin.High()
}
return err
}
// Size returns the current size of the display.
func (d *Device) Size() (w, h int16) {
return d.width, d.height
+52
View File
@@ -0,0 +1,52 @@
package ssd1306
import (
"tinygo.org/x/drivers"
)
type I2CBus struct {
wire drivers.I2C
address uint16
buffer []byte // buffer to avoid heap allocations
}
// NewI2C creates a new SSD1306 connection. The I2C wire must already be configured.
func NewI2C(bus drivers.I2C) *Device {
return &Device{
bus: &I2CBus{
wire: bus,
address: Address,
},
}
}
// configure address for the I2C bus and allocate the buffer
func (b *I2CBus) configure(address uint16, size int16) []byte {
if address != 0 {
b.address = address
}
b.buffer = make([]byte, size+2) // +1 for the mode and +1 for a command
return b.buffer[2:] // return the image buffer
}
// command sends a command to the display
func (b *I2CBus) command(cmd uint8) error {
b.buffer[0] = 0x00 // Command mode
b.buffer[1] = cmd
return b.wire.Tx(b.address, b.buffer[:2], nil)
}
// flush sends the image to the display
func (b *I2CBus) flush() error {
b.buffer[1] = 0x40 // Data mode
return b.wire.Tx(b.address, b.buffer[1:], nil)
}
// tx sends data to the display
func (b *I2CBus) tx(data []byte, isCommand bool) error {
if isCommand {
return b.command(data[0])
}
copy(b.buffer[2:], data)
return b.flush()
}
+68
View File
@@ -0,0 +1,68 @@
package ssd1306
import (
"machine"
"time"
"tinygo.org/x/drivers"
)
type SPIBus struct {
wire drivers.SPI
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 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,
resetPin: resetPin,
csPin: csPin,
},
}
}
// configure pins with the SPI bus and allocate the buffer
func (b *SPIBus) configure(address uint16, size int16) []byte {
b.csPin.Low()
b.dcPin.Low()
b.resetPin.Low()
b.resetPin.High()
time.Sleep(1 * time.Millisecond)
b.resetPin.Low()
time.Sleep(10 * time.Millisecond)
b.resetPin.High()
b.buffer = make([]byte, size+1) // +1 for a command
return b.buffer[1:] // return the image buffer
}
// command sends a command to the display
func (b *SPIBus) command(cmd uint8) error {
b.buffer[0] = cmd
return b.tx(b.buffer[:1], true)
}
// flush sends the image to the display
func (b *SPIBus) flush() error {
return b.tx(b.buffer[1:], false)
}
// tx sends data to the display
func (b *SPIBus) tx(data []byte, isCommand bool) error {
b.csPin.High()
b.dcPin.Set(!isCommand)
b.csPin.Low()
err := b.wire.Tx(data, nil)
b.csPin.High()
return err
}
+7 -15
View File
@@ -57,14 +57,10 @@ func (comm *UARTComm) WriteRegister(register uint8, value uint32, driverIndex ui
byte((value >> 16) & 0xFF), // Middle byte
byte((value >> 8) & 0xFF), // Next byte
byte(value & 0xFF), // LSB of value
0, // CRC
}
// Calculate checksum by XORing all bytes
checksum := byte(0)
for _, b := range buffer[:7] {
checksum ^= b
}
buffer[7] = checksum // Set checksum byte
buffer[7] = CalculateCRC(buffer[:7])
// Write the data to the TMC2209
done := make(chan error, 1)
@@ -86,10 +82,10 @@ func (comm *UARTComm) WriteRegister(register uint8, value uint32, driverIndex ui
// ReadRegister sends a register read command to the TMC2209 with a timeout.
func (comm *UARTComm) ReadRegister(register uint8, driverIndex uint8) (uint32, error) {
var writeBuffer [4]byte
writeBuffer[0] = 0x05 // Sync byte
writeBuffer[1] = 0x00 // Slave address
writeBuffer[2] = register & 0x7F // Read command (MSB clear for read)
writeBuffer[3] = writeBuffer[0] ^ writeBuffer[1] ^ writeBuffer[2] // Checksum
writeBuffer[0] = 0x05 // Sync byte
writeBuffer[1] = 0x00 // Slave address
writeBuffer[2] = register & 0x7F // Read command (MSB clear for read)
writeBuffer[3] = CalculateCRC(writeBuffer[:3])
// Send the read command
done := make(chan []byte, 1)
@@ -103,11 +99,7 @@ func (comm *UARTComm) ReadRegister(register uint8, driverIndex uint8) (uint32, e
// Implementing timeout using a 100ms timer
select {
case readBuffer := <-done:
// Validate checksum
checksum := byte(0)
for i := 0; i < 7; i++ {
checksum ^= readBuffer[i]
}
checksum := CalculateCRC(readBuffer[:7])
if checksum != readBuffer[7] {
return 0, CustomError("checksum error")
}
+23 -17
View File
@@ -8,10 +8,11 @@ package uc8151 // import "tinygo.org/x/drivers/uc8151"
import (
"errors"
"image/color"
"machine"
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/legacy"
"tinygo.org/x/drivers/internal/pin"
"tinygo.org/x/drivers/pixel"
)
@@ -31,10 +32,10 @@ type Config struct {
type Device struct {
bus drivers.SPI
cs machine.Pin
dc machine.Pin
rst machine.Pin
busy machine.Pin
cs pin.OutputFunc
dc pin.OutputFunc
rst pin.OutputFunc
isBusy pin.InputFunc
width int16
height int16
buffer []uint8
@@ -49,17 +50,22 @@ type Device struct {
type Speed uint8
// New returns a new uc8151 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})
// Pins passed in must be configured beforehand.
func New(bus drivers.SPI, csPin, dcPin, rstPin pin.Output, busyPin pin.Input) Device {
// For backwards compatibility.
// This driver used to configure pins,
// so leave in to not break users.
// May be removed in future so try not to depend on it!
legacy.ConfigurePinOut(csPin)
legacy.ConfigurePinOut(dcPin)
legacy.ConfigurePinOut(rstPin)
legacy.ConfigurePinInput(busyPin)
return Device{
bus: bus,
cs: csPin,
dc: dcPin,
rst: rstPin,
busy: busyPin,
bus: bus,
cs: csPin.Set,
dc: dcPin.Set,
rst: rstPin.Set,
isBusy: busyPin.Get,
}
}
@@ -313,14 +319,14 @@ func (d *Device) ClearDisplay() {
// WaitUntilIdle waits until the display is ready
func (d *Device) WaitUntilIdle() {
for !d.busy.Get() {
for !d.isBusy() {
time.Sleep(10 * time.Millisecond)
}
}
// IsBusy returns the busy status of the display
func (d *Device) IsBusy() bool {
return d.busy.Get()
return d.isBusy()
}
// ClearBuffer sets the buffer to 0xFF (white)
+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.30.0"
const Version = "0.33.0"
+829
View File
@@ -931,6 +931,356 @@ void ws2812_writeByte125(char c, uint32_t *portSet, uint32_t *portClear, uint32_
[portClear]"m"(*portClear));
}
__attribute__((always_inline))
void ws2812_writeByte150(char c, uint32_t *portSet, uint32_t *portClear, uint32_t maskSet, uint32_t maskClear) {
// Timings:
// T0H: 53 - 55 cycles or 353.3ns - 366.7ns
// T1H: 158 - 160 cycles or 1053.3ns - 1066.7ns
// TLD: 173 - cycles or 1153.3ns -
uint32_t value = (uint32_t)c << 24;
char i = 8;
__asm__ __volatile__(
"1: @ send_bit\n"
"\t str %[maskSet], %[portSet] @ [2] T0H and T0L start here\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t lsls %[value], #1 @ [1]\n"
"\t bcs.n 2f @ [1/3] skip_store\n"
"\t str %[maskClear], %[portClear] @ [2] T0H -> T0L transition\n"
"\t2: @ skip_store\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t str %[maskClear], %[portClear] @ [2] T1H -> T1L transition\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t subs %[i], #1 @ [1]\n"
"\t beq.n 3f @ [1/3] end\n"
"\t b 1b @ [1/3] send_bit\n"
"\t3: @ end\n"
: [value]"+r"(value),
[i]"+r"(i)
: [maskSet]"r"(maskSet),
[portSet]"m"(*portSet),
[maskClear]"r"(maskClear),
[portClear]"m"(*portClear));
}
__attribute__((always_inline))
void ws2812_writeByte168(char c, uint32_t *portSet, uint32_t *portClear, uint32_t maskSet, uint32_t maskClear) {
// Timings:
@@ -1320,6 +1670,465 @@ void ws2812_writeByte168(char c, uint32_t *portSet, uint32_t *portClear, uint32_
[maskClear]"r"(maskClear),
[portClear]"m"(*portClear));
}
__attribute__((always_inline))
void ws2812_writeByte200(char c, uint32_t *portSet, uint32_t *portClear, uint32_t maskSet, uint32_t maskClear) {
// Timings:
// T0H: 70 - 72 cycles or 350.0ns - 360.0ns
// T1H: 210 - 212 cycles or 1050.0ns - 1060.0ns
// TLD: 230 - cycles or 1150.0ns -
uint32_t value = (uint32_t)c << 24;
char i = 8;
__asm__ __volatile__(
"1: @ send_bit\n"
"\t str %[maskSet], %[portSet] @ [2] T0H and T0L start here\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t lsls %[value], #1 @ [1]\n"
"\t bcs.n 2f @ [1/3] skip_store\n"
"\t str %[maskClear], %[portClear] @ [2] T0H -> T0L transition\n"
"\t2: @ skip_store\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t str %[maskClear], %[portClear] @ [2] T1H -> T1L transition\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t subs %[i], #1 @ [1]\n"
"\t beq.n 3f @ [1/3] end\n"
"\t b 1b @ [1/3] send_bit\n"
"\t3: @ end\n"
: [value]"+r"(value),
[i]"+r"(i)
: [maskSet]"r"(maskSet),
[portSet]"m"(*portSet),
[maskClear]"r"(maskClear),
[portClear]"m"(*portClear));
}
*/
import "C"
@@ -1373,6 +2182,16 @@ func (d Device) writeByte125(c byte) {
interrupt.Restore(mask)
}
func (d Device) writeByte150(c byte) {
portSet, maskSet := d.Pin.PortMaskSet()
portClear, maskClear := d.Pin.PortMaskClear()
mask := interrupt.Disable()
C.ws2812_writeByte150(C.char(c), (*C.uint32_t)(unsafe.Pointer(portSet)), (*C.uint32_t)(unsafe.Pointer(portClear)), C.uint32_t(maskSet), C.uint32_t(maskClear))
interrupt.Restore(mask)
}
func (d Device) writeByte168(c byte) {
portSet, maskSet := d.Pin.PortMaskSet()
portClear, maskClear := d.Pin.PortMaskClear()
@@ -1382,3 +2201,13 @@ func (d Device) writeByte168(c byte) {
interrupt.Restore(mask)
}
func (d Device) writeByte200(c byte) {
portSet, maskSet := d.Pin.PortMaskSet()
portClear, maskClear := d.Pin.PortMaskClear()
mask := interrupt.Disable()
C.ws2812_writeByte200(C.char(c), (*C.uint32_t)(unsafe.Pointer(portSet)), (*C.uint32_t)(unsafe.Pointer(portClear)), C.uint32_t(maskSet), C.uint32_t(maskClear))
interrupt.Restore(mask)
}
+1 -1
View File
@@ -1,7 +1,7 @@
// Package ws2812 implements a driver for WS2812 and SK6812 RGB LED strips.
package ws2812 // import "tinygo.org/x/drivers/ws2812"
//go:generate go run gen-ws2812.go -arch=cortexm 16 48 64 120 125 168
//go:generate go run gen-ws2812.go -arch=cortexm 16 48 64 120 125 150 168 200
//go:generate go run gen-ws2812.go -arch=tinygoriscv 160 320
import (
+7 -1
View File
@@ -28,12 +28,18 @@ func (d Device) WriteByte(c byte) error {
case 120_000_000: // 120MHz
d.writeByte120(c)
return nil
case 125_000_000: // 125 MHz e.g. rp2040
case 125_000_000: // 125 MHz e.g. rp2040 originally
d.writeByte125(c)
return nil
case 150_000_000: // 150MHz, e.g. rp2350
d.writeByte150(c)
return nil
case 168_000_000: // 168MHz, e.g. stm32f405
d.writeByte168(c)
return nil
case 200_000_000: // 200MHz, e.g. rp2040 starting with TinyGo v0.37
d.writeByte200(c)
return nil
default:
return errUnknownClockSpeed
}