mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-07-27 11:08:41 +00:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5c305f9b6f | |||
| 1e4110b5a2 | |||
| e286861661 | |||
| 589bf19b01 | |||
| ff4d15cea9 | |||
| d02d21ecea | |||
| 805e2a02f8 |
@@ -1,28 +1,3 @@
|
||||
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**
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build tinygo && (rp2040 || rp2350 || stm32 || k210 || esp32c3 || nrf || sam || (avr && (atmega328p || atmega328pb)))
|
||||
//go:build tinygo && (rp2040 || stm32 || k210 || esp32c3 || nrf || sam || (avr && (atmega328p || atmega328pb)))
|
||||
|
||||
// Implementation based on:
|
||||
// https://gist.github.com/aykevl/3fc1683ed77bb0a9c07559dfe857304a
|
||||
|
||||
@@ -1,225 +0,0 @@
|
||||
// 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 // 7‑bit 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
|
||||
|
||||
// pre‑allocated buffers
|
||||
wbuf [5]byte // longest write: reg + 4 bytes (TEMP+RH)
|
||||
rbuf [5]byte // longest read: DATA burst (5 bytes)
|
||||
}
|
||||
|
||||
// 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 (auto‑increment)
|
||||
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 total‑VOC concentration in parts‑per‑billion.
|
||||
func (d *Device) TVOC() uint16 { return d.lastTvocPPB }
|
||||
|
||||
// ECO2 returns the last equivalent CO₂ concentration in parts‑per‑million.
|
||||
func (d *Device) ECO2() uint16 { return d.lastEco2PPM }
|
||||
|
||||
// AQI returns the last Air‑Quality Index according to UBA (1–5).
|
||||
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
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
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
|
||||
)
|
||||
@@ -1,56 +0,0 @@
|
||||
// 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,49 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
"tinygo.org/x/drivers/honeyhsc"
|
||||
)
|
||||
|
||||
// Data taken from https://github.com/rodan/honeywell_hsc_ssc_i2c/blob/master/hsc_ssc_i2c.cpp
|
||||
// these defaults are valid for the HSCMRNN030PA2A3 chip
|
||||
const (
|
||||
i2cAddress = 0x28
|
||||
// 10%
|
||||
outputMinimum = 0x666
|
||||
// 90% of 2^14 - 1
|
||||
outputMax = 0x399A
|
||||
// min is 0 for sensors that give absolute values
|
||||
pressureMin = 0
|
||||
// 30psi (and we want results in millipascals)
|
||||
// pressureMax = 206842.7
|
||||
pressureMax = 206843 * 1000
|
||||
)
|
||||
|
||||
func main() {
|
||||
bus := machine.I2C0
|
||||
err := bus.Configure(machine.I2CConfig{
|
||||
Frequency: 400_000, // 100kHz minimum and 400kHz I2C maximum clock. 50 to 800 for SPI.
|
||||
SDA: machine.I2C0_SDA_PIN,
|
||||
SCL: machine.I2C0_SCL_PIN,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
sensor := honeyhsc.NewDevI2C(bus, i2cAddress, outputMinimum, outputMax, pressureMin, pressureMax)
|
||||
for {
|
||||
time.Sleep(time.Second)
|
||||
const measuremask = drivers.Pressure | drivers.Temperature
|
||||
err := sensor.Update(measuremask)
|
||||
if err != nil {
|
||||
println("error updating measurements:", err.Error())
|
||||
continue
|
||||
}
|
||||
P := sensor.Pressure()
|
||||
T := sensor.Temperature()
|
||||
println("pressure:", P, "temperature:", T)
|
||||
}
|
||||
}
|
||||
+3
-12
@@ -14,18 +14,9 @@ func main() {
|
||||
i2c.Configure(machine.I2CConfig{SCL: machine.SCL1_PIN, SDA: machine.SDA1_PIN})
|
||||
|
||||
accel := lis3dh.New(i2c)
|
||||
err := accel.Configure(lis3dh.Config{
|
||||
Address: lis3dh.Address1, // address on the Circuit Playground Express
|
||||
})
|
||||
for err != nil {
|
||||
println("could not configure LIS3DH:", err)
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
err = accel.SetRange(lis3dh.RANGE_2_G)
|
||||
for err != nil {
|
||||
println("could not set acceleration range:", err)
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
accel.Address = lis3dh.Address1 // address on the Circuit Playground Express
|
||||
accel.Configure()
|
||||
accel.SetRange(lis3dh.RANGE_2_G)
|
||||
|
||||
println(accel.Connected())
|
||||
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
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)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
+18
-28
@@ -1,31 +1,30 @@
|
||||
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"
|
||||
|
||||
"tinygo.org/x/drivers/ssd1306"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
display := newSSD1306Display()
|
||||
// Thumby will have preset size.
|
||||
// If not compiling for thumby the width and height will be whatever we suggest
|
||||
const suggestHeight = 32
|
||||
const suggestWidth = 128
|
||||
var display *ssd1306.Device
|
||||
var err error
|
||||
display, err = makeSSD1306(suggestWidth, suggestHeight)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
display.ClearDisplay()
|
||||
|
||||
w, h := display.Size()
|
||||
x := int16(0)
|
||||
y := int16(0)
|
||||
width, height := display.Size()
|
||||
x := int16(width)
|
||||
y := int16(height)
|
||||
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}
|
||||
@@ -38,22 +37,13 @@ func main() {
|
||||
x += deltaX
|
||||
y += deltaY
|
||||
|
||||
if x == 0 || x == w-1 {
|
||||
if x == 0 || x == width-1 {
|
||||
deltaX = -deltaX
|
||||
}
|
||||
|
||||
if y == 0 || y == h-1 {
|
||||
if y == 0 || y == height-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
|
||||
}
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
//go:build !thumby
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
|
||||
"tinygo.org/x/drivers/ssd1306"
|
||||
)
|
||||
|
||||
func makeSSD1306(width, height int16) (*ssd1306.Device, error) {
|
||||
err := machine.I2C0.Configure(machine.I2CConfig{
|
||||
Frequency: 400 * machine.KHz,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
address := uint16(ssd1306.Address)
|
||||
if width == 128 && (height == 32 || height == 64) {
|
||||
address = ssd1306.Address_128_32
|
||||
}
|
||||
display := ssd1306.NewI2C(machine.I2C0)
|
||||
display.Configure(ssd1306.Config{
|
||||
Address: address,
|
||||
Width: width,
|
||||
Height: height,
|
||||
})
|
||||
return &display, nil
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
//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
|
||||
}
|
||||
@@ -1,11 +1,5 @@
|
||||
//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 (
|
||||
@@ -14,7 +8,8 @@ import (
|
||||
"tinygo.org/x/drivers/ssd1306"
|
||||
)
|
||||
|
||||
func newSSD1306Display() *ssd1306.Device {
|
||||
func makeSSD1306(_, _ int16) (*ssd1306.Device, error) {
|
||||
// width and height are known for thumby.
|
||||
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{
|
||||
@@ -23,5 +18,5 @@ func newSSD1306Display() *ssd1306.Device {
|
||||
ResetCol: ssd1306.ResetValue{28, 99},
|
||||
ResetPage: ssd1306.ResetValue{0, 5},
|
||||
})
|
||||
return display
|
||||
return &display, nil
|
||||
}
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
//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
|
||||
}
|
||||
@@ -46,10 +46,6 @@ 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:
|
||||
@@ -243,24 +239,6 @@ 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
|
||||
@@ -402,25 +380,6 @@ 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,
|
||||
|
||||
+1
-4
@@ -96,10 +96,7 @@ func (parser *Parser) Parse(sentence string) (Fix, error) {
|
||||
fix.Speed = findSpeed(fields[7])
|
||||
fix.Heading = findHeading(fields[8])
|
||||
date := findDate(fields[9])
|
||||
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)
|
||||
fix.Time = fix.Time.AddDate(date.Year(), int(date.Month()), date.Day())
|
||||
|
||||
return fix, nil
|
||||
}
|
||||
|
||||
@@ -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,010622,0.0,E,D*2B"
|
||||
val = "$GPRMC,203522.00,A,5109.0262308,N,11401.8407342,W,0.004,133.4,130522,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.June)
|
||||
c.Assert(fix.Time.Day(), qt.Equals, 1)
|
||||
c.Assert(fix.Time.Month(), qt.Equals, time.May)
|
||||
c.Assert(fix.Time.Day(), qt.Equals, 13)
|
||||
c.Assert(fix.Time.Hour(), qt.Equals, 20)
|
||||
c.Assert(fix.Time.Minute(), qt.Equals, 35)
|
||||
c.Assert(fix.Time.Second(), qt.Equals, 22)
|
||||
|
||||
-191
@@ -1,191 +0,0 @@
|
||||
package honeyhsc
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
var (
|
||||
errSensorMissing = errors.New("hsc: not connected")
|
||||
errDiagnostic = errors.New("hsc: diagnostic error")
|
||||
)
|
||||
|
||||
const (
|
||||
measuremask = drivers.Pressure | drivers.Temperature
|
||||
statusMask = 0b1100_0000
|
||||
statusOffset = 6
|
||||
)
|
||||
|
||||
// DevI2C is the TruStability® High Accuracy Silicon Ceramic (HSC) Series is a piezoresistive silicon pressure sensor offering a ratiometric
|
||||
// analog or digital output for reading pressure over the specified full scale pressure span and temperature range.
|
||||
type DevI2C struct {
|
||||
bus drivers.I2C
|
||||
dev
|
||||
addr uint8
|
||||
buf [6]byte
|
||||
}
|
||||
|
||||
// NewDevI2C creates and returns a new DevI2C that communicates with an HSC device over the provided I2C bus.
|
||||
// Parameters:
|
||||
// - bus: the I2C bus to use.
|
||||
// - addr: the 7-bit I2C address of the sensor.
|
||||
// - outMin, outMax: raw output code range (counts) corresponding to the pressure span. Depends on sensor model.
|
||||
// - pMin, pMax: pressure range endpoints in millipascals (mPa). Depends on sensor model.
|
||||
//
|
||||
// The returned DevI2C will use these calibration parameters to convert raw bridge counts to pressure.
|
||||
func NewDevI2C(bus drivers.I2C, addr, outMin, outMax uint16, pMin, pMax int32) *DevI2C {
|
||||
h := &DevI2C{
|
||||
bus: bus,
|
||||
addr: uint8(addr),
|
||||
dev: dev{
|
||||
cmin: outMin,
|
||||
cmax: outMax,
|
||||
pmin: pMin,
|
||||
pmax: pMax,
|
||||
},
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
// ReadTemperature reads and returns the temperature in milliKelvin (mC) from the I2C-attached HSC device.
|
||||
// It performs an Update internally to get the latest temperature value.
|
||||
func (h *DevI2C) ReadTemperature() (int32, error) {
|
||||
err := h.Update(drivers.Temperature)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return h.Temperature(), nil
|
||||
}
|
||||
|
||||
// Update reads both temperature and pressure data from the I2C-attached HSC device when
|
||||
// the requested measurement mask includes pressure or temperature.
|
||||
// If neither pressure nor temperature is requested, Update is a no-op.
|
||||
func (d *DevI2C) Update(which drivers.Measurement) error {
|
||||
// Update performs an I2C transaction to read 4 bytes, parses the status bits, 14-bit bridge data and
|
||||
// temperature bits, and forwards them to the internal update routine. Any I2C transport error is returned,
|
||||
// as well as errors produced by the internal update (e.g. errSensorMissing, errDiagnostic).
|
||||
if which&measuremask == 0 {
|
||||
return nil
|
||||
}
|
||||
rbuf := d.buf[:4]
|
||||
wbuf := d.buf[4:6]
|
||||
const reg = 0
|
||||
value := (d.addr << 1) | 1
|
||||
wbuf[0] = reg
|
||||
wbuf[1] = value
|
||||
err := d.bus.Tx(uint16(d.addr), wbuf, rbuf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
status := (rbuf[0] & statusMask) >> statusOffset
|
||||
bridgeData := (uint16(rbuf[0]&^statusMask) << 8) | uint16(rbuf[1])
|
||||
tempData := uint16(rbuf[2])<<8 | uint16(rbuf[3]&0xe0)>>5
|
||||
return d.dev.update(status, bridgeData, tempData)
|
||||
}
|
||||
|
||||
type pinout func(level bool)
|
||||
|
||||
// DevI2C is the TruStability® High Accuracy Silicon Ceramic (HSC) Series is a piezoresistive silicon pressure sensor offering a ratiometric
|
||||
// analog or digital output for reading pressure over the specified full scale pressure span and temperature range.
|
||||
type DevSPI struct {
|
||||
spi drivers.SPI
|
||||
cs pinout
|
||||
dev
|
||||
buf [4]byte
|
||||
}
|
||||
|
||||
// NewDevSPI creates and returns a new DevSPI that communicates with an HSC device over SPI.
|
||||
// Parameters:
|
||||
// - conn: the SPI connection to use.
|
||||
// - cs: a chip-select function that drives the device select line low/high.
|
||||
// - outMin, outMax: raw output code range (counts) corresponding to the pressure span. Depends on sensor model.
|
||||
// - pMin, pMax: pressure range endpoints in millipascals (mPa). Depends on sensor model.
|
||||
//
|
||||
// The function returns the constructed DevSPI and an error value (currently always nil).
|
||||
func NewDevSPI(conn drivers.SPI, cs pinout, outMin, outMax uint16, pMin, pMax int32) (*DevSPI, error) {
|
||||
h := &DevSPI{
|
||||
spi: conn,
|
||||
cs: cs,
|
||||
dev: dev{
|
||||
cmin: outMin,
|
||||
cmax: outMax,
|
||||
pmin: pMin,
|
||||
pmax: pMax,
|
||||
},
|
||||
}
|
||||
return h, nil
|
||||
}
|
||||
|
||||
// ReadTemperature reads and returns the temperature in milliKelvin (mC) from the SPI-attached HSC device.
|
||||
// It performs an Update internally to get the latest temperature value.
|
||||
func (h *DevSPI) ReadTemperature() (int32, error) {
|
||||
err := h.Update(drivers.Temperature)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return h.Temperature(), nil
|
||||
}
|
||||
|
||||
// Update reads pressure and temperature data from the SPI-attached HSC device when the requested measurement mask includes
|
||||
// pressure or temperature. If neither pressure nor temperature is requested, Update is a no-op.
|
||||
func (h *DevSPI) Update(which drivers.Measurement) error {
|
||||
// It toggles the provided chip-select, performs an SPI transfer to read 4 bytes, parses the status bits,
|
||||
// 14-bit bridge data and temperature bits, and forwards them to the internal update routine. Any SPI
|
||||
// transport error is returned, as well as errors produced by the internal update (e.g. errSensorMissing, errDiagnostic).
|
||||
if which&measuremask == 0 {
|
||||
return nil
|
||||
}
|
||||
buf := &h.buf
|
||||
h.cs(false)
|
||||
err := h.spi.Tx(nil, buf[:4])
|
||||
h.cs(true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// First two bits are status bits.
|
||||
status := (buf[0] & statusMask) >> statusOffset
|
||||
bridgeData := (uint16(buf[0]&^statusMask) << 8) | uint16(buf[1])
|
||||
|
||||
tempData := uint16(buf[2])<<8 | uint16(buf[3]&0xe0)>>5
|
||||
return h.dev.update(status, bridgeData, tempData)
|
||||
}
|
||||
|
||||
type dev struct {
|
||||
pressure int32
|
||||
temp int32
|
||||
cmin, cmax uint16
|
||||
pmin, pmax int32
|
||||
}
|
||||
|
||||
// Pressure returns the most recently computed pressure value in millipascals (mPa).
|
||||
// The value is taken from the last successful Update.
|
||||
func (d *dev) Pressure() int32 {
|
||||
return d.pressure
|
||||
}
|
||||
|
||||
// Temperature returns the most recently read temperature value in milliKelvin (mC).
|
||||
// The value is taken from the last successful Update.
|
||||
func (d *dev) Temperature() int32 {
|
||||
return d.temp + 273_150
|
||||
}
|
||||
|
||||
// update interprets raw sensor fields (status, bridgeData, tempData) and updates the dev's stored
|
||||
// pressure and temperature. It returns errSensorMissing when the temperature raw value indicates no sensor
|
||||
// (tempData == math.MaxUint16), errDiagnostic when the status indicates a device diagnostic condition
|
||||
// (status == 3), or nil on success. Pressure is computed with integer arithmetic using the configured
|
||||
// cmin/cmax -> pmin/pmax linear mapping in order to avoid overflows.
|
||||
func (d *dev) update(status uint8, bridgeData, tempData uint16) error {
|
||||
if tempData == math.MaxUint16 {
|
||||
return errSensorMissing
|
||||
} else if status == 3 {
|
||||
return errDiagnostic
|
||||
}
|
||||
|
||||
// Take care not to overflow here.
|
||||
p := (int32(bridgeData)-int32(d.cmin))*(d.pmax-d.pmin)/int32(d.cmax-d.cmin) + d.pmin
|
||||
d.temp = int32(tempData)
|
||||
d.pressure = p
|
||||
return nil
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
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")
|
||||
)
|
||||
@@ -1,15 +0,0 @@
|
||||
//go:build !tinygo
|
||||
|
||||
package legacy
|
||||
|
||||
import "tinygo.org/x/drivers/internal/pin"
|
||||
|
||||
// This file compiles for non-tinygo builds
|
||||
// for use with "big" or "upstream" Go where
|
||||
// there is no machine package.
|
||||
|
||||
func configurePinOut(p pin.Output) {}
|
||||
func configurePinInput(p pin.Input) {}
|
||||
func configurePinInputPulldown(p pin.Input) {}
|
||||
func configurePinInputPullup(p pin.Input) {}
|
||||
func pinIsNoPin(a any) bool { return false }
|
||||
@@ -1,10 +0,0 @@
|
||||
//go:build baremetal && fe310
|
||||
|
||||
package legacy
|
||||
|
||||
import "machine"
|
||||
|
||||
const (
|
||||
pulldown = machine.PinInput
|
||||
pullup = machine.PinInput
|
||||
)
|
||||
@@ -1,13 +0,0 @@
|
||||
//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
|
||||
)
|
||||
@@ -1,37 +0,0 @@
|
||||
//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})
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
// package pin implements a TinyGo Pin HAL.
|
||||
// It serves to eliminate machine.Pin from driver constructors
|
||||
// so that drivers can be used in "big" Go projects where
|
||||
// there is no machine package.
|
||||
// This file contains both function and interface-style Pin HAL definitions.
|
||||
package pin
|
||||
|
||||
// OutputFunc is hardware abstraction for a pin which outputs a
|
||||
// digital signal (high or low level).
|
||||
//
|
||||
// // Code conversion demo: from machine.Pin to pin.OutputFunc
|
||||
// led := machine.LED
|
||||
// led.Configure(machine.PinConfig{Mode: machine.Output})
|
||||
// var pin pin.OutputFunc = led.Set // Going from a machine.Pin to a pin.OutputFunc
|
||||
//
|
||||
// This is an alternative to [Output] which is an interface type.
|
||||
type OutputFunc func(level bool)
|
||||
|
||||
// High sets the underlying pin's level to high. This is equivalent to calling PinOutput(true).
|
||||
func (setPin OutputFunc) High() {
|
||||
setPin(true)
|
||||
}
|
||||
|
||||
// Low sets the underlying pin's level to low. This is equivalent to calling PinOutput(false).
|
||||
func (setPin OutputFunc) Low() {
|
||||
setPin(false)
|
||||
}
|
||||
|
||||
// InputFunc is hardware abstraction for a pin which receives a
|
||||
// digital signal and reads it (high or low level).
|
||||
//
|
||||
// // Code conversion demo: from machine.Pin to pin.InputFunc
|
||||
// input := machine.LED
|
||||
// input.Configure(machine.PinConfig{Mode: machine.PinInputPulldown}) // or use machine.PinInputPullup or machine.Input
|
||||
// var pin pin.InputFunc = input.Get // Going from a machine.Pin to a pin.InputFunc
|
||||
//
|
||||
// This is an alternative to [Input] which is an interface type.
|
||||
type InputFunc func() (level bool)
|
||||
|
||||
// // Below is an example on how to define a input/output pin HAL for a
|
||||
// // pin that must switch between input and output mode:
|
||||
//
|
||||
// var pinIsOutput bool
|
||||
// var po PinOutputFunc = func(b bool) {
|
||||
// if !pinIsOutput {
|
||||
// pin.Configure(outputMode)
|
||||
// pinIsOutput = true
|
||||
// }
|
||||
// pin.Set(b)
|
||||
// }
|
||||
//
|
||||
// var pi PinInputFunc = func() bool {
|
||||
// if pinIsOutput {
|
||||
// pin.Configure(inputMode)
|
||||
// pinIsOutput = false
|
||||
// }
|
||||
// return pin.Get()
|
||||
// }
|
||||
|
||||
// Output interface represents a pin hardware abstraction layer for a pin that can output a digital signal.
|
||||
//
|
||||
// This is an alternative to [OutputFunc] abstraction which is a function type.
|
||||
type Output interface {
|
||||
Set(level bool)
|
||||
}
|
||||
|
||||
// Input interface represents a pin hardware abstraction layer for a pin that can read a digital signal.
|
||||
//
|
||||
// This is an alternative to [InputFunc] abstraction which is a function type.
|
||||
type Input interface {
|
||||
Get() (level bool)
|
||||
}
|
||||
@@ -1,143 +0,0 @@
|
||||
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)
|
||||
}
|
||||
@@ -1,196 +0,0 @@
|
||||
// Package regmap provides transaction-based interfaces for reading and writing
|
||||
// to device registers over I2C and SPI buses with pre-allocated buffers.
|
||||
package regmap
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
var (
|
||||
// errNotInTx indicates an operation was attempted outside of an active transaction.
|
||||
errNotInTx = errors.New("device not in Tx")
|
||||
|
||||
// errInTx indicates a transaction was started while another is still active.
|
||||
errInTx = errors.New("device already in Tx")
|
||||
|
||||
// errShortWriteBuffer indicates the write buffer is too small for the requested operation.
|
||||
errShortWriteBuffer = errors.New("device write buffer too short")
|
||||
|
||||
// errShortReadBuffer indicates the read buffer is too small for the requested operation.
|
||||
errShortReadBuffer = errors.New("device read buffer too short")
|
||||
)
|
||||
|
||||
// Device8Txer wraps a Device8 to provide buffered transaction support for
|
||||
// I2C and SPI operations. It maintains pre-allocated buffers to avoid heap
|
||||
// allocations during register access operations.
|
||||
//
|
||||
// Users must call SetBuffers to configure the write and read buffers before
|
||||
// initiating transactions.
|
||||
type Device8Txer struct {
|
||||
Device8
|
||||
writeBuf []byte // Pre-allocated buffer for write operations
|
||||
readBuf []byte // Pre-allocated buffer for read operations
|
||||
inTx bool // Tracks whether a transaction is currently active
|
||||
}
|
||||
|
||||
// SetTxBuffers configures the write and read buffers for this device.
|
||||
// These buffers are reused across transactions to avoid heap allocations.
|
||||
//
|
||||
// The writebuf should be large enough to hold the register address plus
|
||||
// all data bytes to be written in a single transaction.
|
||||
func (d *Device8Txer) SetTxBuffers(writebuf, readbuf []byte) {
|
||||
d.readBuf = readbuf
|
||||
d.writeBuf = writebuf
|
||||
}
|
||||
|
||||
// Tx8 represents an active transaction for an 8-bit register device.
|
||||
// It tracks the write buffer and current offset as data is added to the transaction.
|
||||
//
|
||||
// Use AddWriteByte or AddWriteData to add data to the transaction, then call
|
||||
// DoTxI2C or DoTxSPI to execute the transaction over the bus.
|
||||
type Tx8 struct {
|
||||
dw *Device8Txer // Reference to the parent device
|
||||
off int // Current offset in the write buffer
|
||||
}
|
||||
|
||||
// Tx initiates a new transaction for writing to the specified register address.
|
||||
//
|
||||
// Parameters:
|
||||
// - writeAddr: The 8-bit register address to write to
|
||||
//
|
||||
// Returns a Tx8 handle that can be used to add data and execute the transaction.
|
||||
//
|
||||
// Returns an error if:
|
||||
// - A transaction is already active (errInTx)
|
||||
// - The write buffer is too short (errShortWriteBuffer)
|
||||
func (dw *Device8Txer) Tx(writeAddr uint8) (Tx8, error) {
|
||||
if dw.inTx {
|
||||
return Tx8{}, errInTx
|
||||
} else if len(dw.writeBuf) < 1 {
|
||||
return Tx8{}, errShortWriteBuffer
|
||||
}
|
||||
dw.writeBuf[0] = writeAddr
|
||||
return Tx8{dw: dw, off: 1}, nil
|
||||
}
|
||||
|
||||
// AddWriteData appends multiple bytes to the current transaction's write buffer.
|
||||
//
|
||||
// Parameters:
|
||||
// - buf: Variable number of bytes to add to the transaction
|
||||
//
|
||||
// Returns an error if:
|
||||
// - No transaction is active (errNotInTx)
|
||||
// - The write buffer doesn't have enough space (errShortWriteBuffer)
|
||||
func (tx *Tx8) AddWriteData(buf ...byte) error {
|
||||
if !tx.dw.inTx {
|
||||
return errNotInTx
|
||||
}
|
||||
avail := tx.dw.writeBuf[tx.off:]
|
||||
if len(avail) < len(buf) {
|
||||
return errShortWriteBuffer
|
||||
}
|
||||
n := copy(avail, buf)
|
||||
tx.off += n
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddWriteByte appends a single byte to the current transaction's write buffer.
|
||||
//
|
||||
// Parameters:
|
||||
// - b: The byte to add to the transaction
|
||||
//
|
||||
// Returns an error if:
|
||||
// - No transaction is active (errNotInTx)
|
||||
// - The write buffer doesn't have enough space (errShortWriteBuffer)
|
||||
func (tx *Tx8) AddWriteByte(b byte) error {
|
||||
if !tx.dw.inTx {
|
||||
return errNotInTx
|
||||
}
|
||||
avail := tx.dw.writeBuf[tx.off:]
|
||||
if len(avail) < 1 {
|
||||
return errShortWriteBuffer
|
||||
}
|
||||
avail[0] = b
|
||||
tx.off++
|
||||
return nil
|
||||
}
|
||||
|
||||
// DoTxI2C executes the transaction over an I2C bus.
|
||||
//
|
||||
// This performs a combined write-read I2C transaction, first sending the
|
||||
// register address and any data added to the transaction, then reading
|
||||
// the specified number of bytes from the device.
|
||||
//
|
||||
// Parameters:
|
||||
// - bus: The I2C bus to communicate over
|
||||
// - deviceAddr: The I2C address of the target device
|
||||
// - readLength: Number of bytes to read from the device
|
||||
//
|
||||
// Returns the read data as a slice of the internal read buffer, valid until
|
||||
// the next transaction. The transaction is automatically freed after execution.
|
||||
//
|
||||
// Returns an error if:
|
||||
// - No transaction is active (errNotInTx)
|
||||
// - The read buffer is too short (errShortReadBuffer)
|
||||
// - The I2C transaction fails
|
||||
func (tx *Tx8) DoTxI2C(bus drivers.I2C, deviceAddr uint16, readLength int) ([]byte, error) {
|
||||
if tx.off == 0 || !tx.dw.inTx {
|
||||
return nil, errNotInTx
|
||||
}
|
||||
defer tx.freeTx()
|
||||
if len(tx.dw.readBuf) < readLength {
|
||||
return nil, errShortReadBuffer
|
||||
}
|
||||
rbuf := tx.dw.readBuf[:readLength]
|
||||
err := bus.Tx(deviceAddr, tx.dw.writeBuf[:tx.off], rbuf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rbuf, err
|
||||
}
|
||||
|
||||
// DoTxSPI executes the transaction over an SPI bus.
|
||||
//
|
||||
// This performs a full-duplex SPI transaction, simultaneously writing the
|
||||
// register address and data while reading the same number of bytes from the device.
|
||||
//
|
||||
// If no read buffer was configured (readBuf is nil), this performs a write-only
|
||||
// transaction and returns nil without error.
|
||||
//
|
||||
// Parameters:
|
||||
// - bus: The SPI bus to communicate over
|
||||
//
|
||||
// Returns the read data as a slice of the internal read buffer (same length as
|
||||
// the write data), valid until the next transaction. The transaction is
|
||||
// automatically freed after execution.
|
||||
//
|
||||
// Returns an error if:
|
||||
// - No transaction is active (errNotInTx)
|
||||
// - The read buffer is too short (errShortReadBuffer)
|
||||
// - The SPI transaction fails
|
||||
func (tx *Tx8) DoTxSPI(bus drivers.SPI) (readBuf []byte, err error) {
|
||||
if tx.off == 0 || !tx.dw.inTx {
|
||||
return nil, errNotInTx
|
||||
}
|
||||
defer tx.freeTx()
|
||||
if tx.dw.readBuf == nil {
|
||||
err = bus.Tx(tx.dw.writeBuf[:tx.off], nil) // Special case, only use write buffer functionality.
|
||||
return nil, err
|
||||
} else if len(readBuf) < tx.off {
|
||||
return nil, errShortReadBuffer
|
||||
}
|
||||
rbuf := tx.dw.readBuf[:tx.off]
|
||||
err = bus.Tx(tx.dw.writeBuf[:tx.off], rbuf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rbuf, err
|
||||
}
|
||||
|
||||
// freeTx marks the transaction as complete, allowing a new transaction to be started.
|
||||
// This is called internally by DoTxI2C and DoTxSPI after the transaction completes.
|
||||
func (tx *Tx8) freeTx() {
|
||||
tx.dw.inTx = false
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
package regmap
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
// Device8SPI implements common logic to most 8-bit peripherals with an SPI bus.
|
||||
// All methods expect the target to support conventional register read and write operations
|
||||
// where the first byte sent is the register address being accessed.
|
||||
//
|
||||
// All methods use an internal buffer and perform no dynamic memory allocation.
|
||||
type Device8SPI struct {
|
||||
bus drivers.SPI
|
||||
order binary.ByteOrder
|
||||
d Device8
|
||||
}
|
||||
|
||||
// SetBus sets the SPI bus and byte order for the Device8SPI.
|
||||
//
|
||||
// As a hint, most SPI devices use big-endian (MSB) byte order.
|
||||
// - Big endian: A value of 0x1234 is transmitted as 0x12 followed by 0x34.
|
||||
// - Little endian: A value of 0x1234 is transmitted as 0x34 followed by 0x12.
|
||||
func (d *Device8SPI) SetBus(bus drivers.SPI, order binary.ByteOrder) {
|
||||
d.bus = bus
|
||||
d.order = order
|
||||
}
|
||||
|
||||
// Read8 reads a single byte from register addr.
|
||||
func (d *Device8SPI) Read8(addr uint8) (byte, error) {
|
||||
return d.d.Read8SPI(d.bus, addr)
|
||||
}
|
||||
|
||||
// Read16 reads a 16-bit value from register addr.
|
||||
func (d *Device8SPI) Read16(addr uint8) (uint16, error) {
|
||||
return d.d.Read16SPI(d.bus, addr, d.order)
|
||||
}
|
||||
|
||||
// Read32 reads a 32-bit value from register addr.
|
||||
func (d *Device8SPI) Read32(addr uint8) (uint32, error) {
|
||||
return d.d.Read32SPI(d.bus, addr, d.order)
|
||||
}
|
||||
|
||||
// ReadData reads dataLength bytes from register addr. Due to the internal functioning of
|
||||
// SPI, an auxiliary buffer must be provided to perform the operation and avoid memory allocation.
|
||||
// The returned slice is a subslice of auxBuffer containing the read data.
|
||||
func (d *Device8SPI) ReadData(addr uint8, datalength int, auxBuffer []byte) ([]byte, error) {
|
||||
return d.d.ReadDataSPI(d.bus, addr, datalength, auxBuffer)
|
||||
}
|
||||
|
||||
// Write8 writes a single byte value to register addr.
|
||||
func (d *Device8SPI) Write8(addr, value uint8) error {
|
||||
return d.d.Write8SPI(d.bus, addr, value)
|
||||
}
|
||||
|
||||
// Write16 writes a 16-bit value to register addr.
|
||||
func (d *Device8SPI) Write16(addr uint8, value uint16) error {
|
||||
return d.d.Write16SPI(d.bus, addr, value, d.order)
|
||||
}
|
||||
|
||||
// Write32 writes a 32-bit value to register addr.
|
||||
func (d *Device8SPI) Write32(addr uint8, value uint32) error {
|
||||
return d.d.Write32SPI(d.bus, addr, value, d.order)
|
||||
}
|
||||
|
||||
// Device8I2C implements common logic to most 8-bit peripherals with an I2C bus.
|
||||
// All methods expect the target to support conventional register read and write operations
|
||||
// where the first byte sent is the register address being accessed.
|
||||
//
|
||||
// All methods use an internal buffer and perform no dynamic memory allocation.
|
||||
type Device8I2C struct {
|
||||
bus drivers.I2C
|
||||
i2cAddr uint16
|
||||
order binary.ByteOrder
|
||||
d Device8
|
||||
}
|
||||
|
||||
// SetBus sets the I2C bus, device address, and byte order for the Device8I2C.
|
||||
//
|
||||
// As a hint, most I2C devices use big-endian (MSB) byte order.
|
||||
// - Big endian: A value of 0x1234 is transmitted as 0x12 followed by 0x34.
|
||||
// - Little endian: A value of 0x1234 is transmitted as 0x34 followed by 0x12.
|
||||
func (d *Device8I2C) SetBus(bus drivers.I2C, i2cAddr uint16, order binary.ByteOrder) {
|
||||
d.bus = bus
|
||||
d.i2cAddr = i2cAddr
|
||||
d.order = order
|
||||
}
|
||||
|
||||
// Read8 reads a single byte from register addr.
|
||||
func (d *Device8I2C) Read8(addr uint8) (byte, error) {
|
||||
return d.d.Read8I2C(d.bus, d.i2cAddr, addr)
|
||||
}
|
||||
|
||||
// Read16 reads a 16-bit value from register addr.
|
||||
func (d *Device8I2C) Read16(addr uint8) (uint16, error) {
|
||||
return d.d.Read16I2C(d.bus, d.i2cAddr, addr, d.order)
|
||||
}
|
||||
|
||||
// Read32 reads a 32-bit value from register addr.
|
||||
func (d *Device8I2C) Read32(addr uint8) (uint32, error) {
|
||||
return d.d.Read32I2C(d.bus, d.i2cAddr, addr, d.order)
|
||||
}
|
||||
|
||||
// ReadData reads dataLength bytes from register addr.
|
||||
func (d *Device8I2C) ReadData(addr uint8, dataDestination []byte) error {
|
||||
return d.d.ReadDataI2C(d.bus, d.i2cAddr, addr, dataDestination)
|
||||
}
|
||||
|
||||
// Write8 writes a single byte value to register addr.
|
||||
func (d *Device8I2C) Write8(addr, value uint8) error {
|
||||
return d.d.Write8I2C(d.bus, d.i2cAddr, addr, value)
|
||||
}
|
||||
|
||||
// Write16 writes a 16-bit value to register addr.
|
||||
func (d *Device8I2C) Write16(addr uint8, value uint16) error {
|
||||
return d.d.Write16I2C(d.bus, d.i2cAddr, addr, value, d.order)
|
||||
}
|
||||
|
||||
// Write32 writes a 32-bit value to register addr.
|
||||
func (d *Device8I2C) Write32(addr uint8, value uint32) error {
|
||||
return d.d.Write32I2C(d.bus, d.i2cAddr, addr, value, d.order)
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package regmap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
func ExampleDevice8Txer() {
|
||||
// Initialization.
|
||||
var dtx Device8Txer
|
||||
dtx.SetTxBuffers(make([]byte, 256), make([]byte, 256))
|
||||
|
||||
// Usage.
|
||||
const (
|
||||
defaultAddr = 65
|
||||
REG_WRITE = 0x1f
|
||||
IOCTL_CALL = 0xc0
|
||||
)
|
||||
tx, err := dtx.Tx(REG_WRITE)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = tx.AddWriteData(IOCTL_CALL, 0x80, 0x80)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var bus drivers.I2C
|
||||
readData, err := tx.DoTxI2C(bus, defaultAddr, 20)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(readData)
|
||||
}
|
||||
+36
-115
@@ -11,57 +11,37 @@ import (
|
||||
// Device wraps an I2C connection to a LIS3DH device.
|
||||
type Device struct {
|
||||
bus drivers.I2C
|
||||
address uint16
|
||||
r Range
|
||||
accel [6]byte // stored acceleration data (from the Update call)
|
||||
}
|
||||
|
||||
// Driver configuration, used for the Configure call. All fields are optional.
|
||||
type Config struct {
|
||||
Address uint16
|
||||
r Range
|
||||
}
|
||||
|
||||
// New creates a new LIS3DH connection. The I2C bus must already be configured.
|
||||
//
|
||||
// This function only creates the Device object, it does not touch the device.
|
||||
func New(bus drivers.I2C) Device {
|
||||
return Device{bus: bus, address: Address0}
|
||||
return Device{bus: bus, Address: Address0}
|
||||
}
|
||||
|
||||
// Configure sets up the device for communication
|
||||
func (d *Device) Configure(config Config) error {
|
||||
if config.Address != 0 {
|
||||
d.address = config.Address
|
||||
}
|
||||
|
||||
func (d *Device) Configure() {
|
||||
// enable all axes, normal mode
|
||||
err := legacy.WriteRegister(d.bus, uint8(d.address), REG_CTRL1, []byte{0x07})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
legacy.WriteRegister(d.bus, uint8(d.Address), REG_CTRL1, []byte{0x07})
|
||||
|
||||
// 400Hz rate
|
||||
err = d.SetDataRate(DATARATE_400_HZ)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.SetDataRate(DATARATE_400_HZ)
|
||||
|
||||
// High res & BDU enabled
|
||||
err = legacy.WriteRegister(d.bus, uint8(d.address), REG_CTRL4, []byte{0x88})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
legacy.WriteRegister(d.bus, uint8(d.Address), REG_CTRL4, []byte{0x88})
|
||||
|
||||
// get current range
|
||||
d.r, err = d.ReadRange()
|
||||
return err
|
||||
d.r = d.ReadRange()
|
||||
}
|
||||
|
||||
// Connected returns whether a LIS3DH has been found.
|
||||
// It does a "who am I" request and checks the response.
|
||||
func (d *Device) Connected() bool {
|
||||
data := []byte{0}
|
||||
err := legacy.ReadRegister(d.bus, uint8(d.address), WHO_AM_I, data)
|
||||
err := legacy.ReadRegister(d.bus, uint8(d.Address), WHO_AM_I, data)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
@@ -69,51 +49,46 @@ func (d *Device) Connected() bool {
|
||||
}
|
||||
|
||||
// SetDataRate sets the speed of data collected by the LIS3DH.
|
||||
func (d *Device) SetDataRate(rate DataRate) error {
|
||||
func (d *Device) SetDataRate(rate DataRate) {
|
||||
ctl1 := []byte{0}
|
||||
err := legacy.ReadRegister(d.bus, uint8(d.address), REG_CTRL1, ctl1)
|
||||
err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_CTRL1, ctl1)
|
||||
if err != nil {
|
||||
return err
|
||||
println(err.Error())
|
||||
}
|
||||
// mask off bits
|
||||
ctl1[0] &^= 0xf0
|
||||
ctl1[0] |= (byte(rate) << 4)
|
||||
return legacy.WriteRegister(d.bus, uint8(d.address), REG_CTRL1, ctl1)
|
||||
legacy.WriteRegister(d.bus, uint8(d.Address), REG_CTRL1, ctl1)
|
||||
}
|
||||
|
||||
// SetRange sets the G range for LIS3DH.
|
||||
func (d *Device) SetRange(r Range) error {
|
||||
func (d *Device) SetRange(r Range) {
|
||||
ctl := []byte{0}
|
||||
err := legacy.ReadRegister(d.bus, uint8(d.address), REG_CTRL4, ctl)
|
||||
err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_CTRL4, ctl)
|
||||
if err != nil {
|
||||
return err
|
||||
println(err.Error())
|
||||
}
|
||||
// mask off bits
|
||||
ctl[0] &^= 0x30
|
||||
ctl[0] |= (byte(r) << 4)
|
||||
err = legacy.WriteRegister(d.bus, uint8(d.address), REG_CTRL4, ctl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
legacy.WriteRegister(d.bus, uint8(d.Address), REG_CTRL4, ctl)
|
||||
|
||||
// store the new range
|
||||
d.r = r
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadRange returns the current G range for LIS3DH.
|
||||
func (d *Device) ReadRange() (r Range, err error) {
|
||||
func (d *Device) ReadRange() (r Range) {
|
||||
ctl := []byte{0}
|
||||
err = legacy.ReadRegister(d.bus, uint8(d.address), REG_CTRL4, ctl)
|
||||
err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_CTRL4, ctl)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
println(err.Error())
|
||||
}
|
||||
// mask off bits
|
||||
r = Range(ctl[0] >> 4)
|
||||
r &= 0x03
|
||||
|
||||
return r, nil
|
||||
return r
|
||||
}
|
||||
|
||||
// ReadAcceleration reads the current acceleration from the device and returns
|
||||
@@ -121,17 +96,28 @@ func (d *Device) ReadRange() (r Range, err error) {
|
||||
// and the sensor is not moving the returned value will be around 1000000 or
|
||||
// -1000000.
|
||||
func (d *Device) ReadAcceleration() (int32, int32, int32, error) {
|
||||
rawX, rawY, rawZ := d.ReadRawAcceleration()
|
||||
x, y, z := normalizeRange(rawX, rawY, rawZ, d.r)
|
||||
return x, y, z, nil
|
||||
x, y, z := d.ReadRawAcceleration()
|
||||
divider := float32(1)
|
||||
switch d.r {
|
||||
case RANGE_16_G:
|
||||
divider = 1365
|
||||
case RANGE_8_G:
|
||||
divider = 4096
|
||||
case RANGE_4_G:
|
||||
divider = 8190
|
||||
case RANGE_2_G:
|
||||
divider = 16380
|
||||
}
|
||||
|
||||
return int32(float32(x) / divider * 1000000), int32(float32(y) / divider * 1000000), int32(float32(z) / divider * 1000000), nil
|
||||
}
|
||||
|
||||
// ReadRawAcceleration returns the raw x, y and z axis from the LIS3DH
|
||||
func (d *Device) ReadRawAcceleration() (x int16, y int16, z int16) {
|
||||
legacy.WriteRegister(d.bus, uint8(d.address), REG_OUT_X_L|0x80, nil)
|
||||
legacy.WriteRegister(d.bus, uint8(d.Address), REG_OUT_X_L|0x80, nil)
|
||||
|
||||
data := []byte{0, 0, 0, 0, 0, 0}
|
||||
d.bus.Tx(d.address, nil, data)
|
||||
d.bus.Tx(d.Address, nil, data)
|
||||
|
||||
x = int16((uint16(data[1]) << 8) | uint16(data[0]))
|
||||
y = int16((uint16(data[3]) << 8) | uint16(data[2]))
|
||||
@@ -139,68 +125,3 @@ func (d *Device) ReadRawAcceleration() (x int16, y int16, z int16) {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Update the sensor values of the 'which' parameter. Only acceleration is
|
||||
// supported at the moment.
|
||||
func (d *Device) Update(which drivers.Measurement) error {
|
||||
if which&drivers.Acceleration != 0 {
|
||||
// Read raw acceleration values and store them in the driver.
|
||||
err := legacy.WriteRegister(d.bus, uint8(d.address), REG_OUT_X_L|0x80, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = d.bus.Tx(d.address, nil, d.accel[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Acceleration returns the last read acceleration in µg (micro-gravity).
|
||||
// When one of the axes is pointing straight to Earth and the sensor is not
|
||||
// moving the returned value will be around 1000000 or -1000000.
|
||||
func (d *Device) Acceleration() (x, y, z int32) {
|
||||
// Extract the raw 16-bit values.
|
||||
rawX := int16((uint16(d.accel[1]) << 8) | uint16(d.accel[0]))
|
||||
rawY := int16((uint16(d.accel[3]) << 8) | uint16(d.accel[2]))
|
||||
rawZ := int16((uint16(d.accel[5]) << 8) | uint16(d.accel[4]))
|
||||
|
||||
// Normalize these values, to be in µg (micro-gravity).
|
||||
return normalizeRange(rawX, rawY, rawZ, d.r)
|
||||
}
|
||||
|
||||
// Convert raw 16-bit values to normalized 32-bit values while avoiding floats
|
||||
// and divisions.
|
||||
func normalizeRange(rawX, rawY, rawZ int16, r Range) (x, y, z int32) {
|
||||
// We're going to convert the 16-bit raw values to values in the range
|
||||
// -1000_000..1000_000. For now we're going to assume a range of 16G, we'll
|
||||
// adjust that range later.
|
||||
// The formula is derived as follows, and carefully selected to avoid
|
||||
// overflow and integer divisions (the division will be optimized to a
|
||||
// bitshift):
|
||||
// x = x * 1000_000 / 2048
|
||||
// x = x * (1000_000/64) / (2048/64)
|
||||
// x = x * 15625 / 32
|
||||
x = int32(rawX) * 15625 / 32
|
||||
y = int32(rawY) * 15625 / 32
|
||||
z = int32(rawZ) * 15625 / 32
|
||||
|
||||
// Now we need to normalize the three values, since we assumed 16G before.
|
||||
shift := uint32(0)
|
||||
switch r {
|
||||
case RANGE_16_G:
|
||||
shift = 0
|
||||
case RANGE_8_G:
|
||||
shift = 1
|
||||
case RANGE_4_G:
|
||||
shift = 2
|
||||
case RANGE_2_G:
|
||||
shift = 3
|
||||
}
|
||||
x >>= shift
|
||||
y >>= shift
|
||||
z >>= shift
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ type Configuration struct {
|
||||
MagDataRate uint8
|
||||
}
|
||||
|
||||
var errNotConnected = errors.New("lsm303agr: failed to communicate with either accel or magnet sensor")
|
||||
var errNotConnected = errors.New("lsm303agr: failed to communicate with either acel or magnet sensor")
|
||||
|
||||
// New creates a new LSM303AGR connection. The I2C bus must already be configured.
|
||||
//
|
||||
|
||||
@@ -1,214 +0,0 @@
|
||||
// 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
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
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
|
||||
)
|
||||
+28
-35
@@ -7,6 +7,7 @@ import (
|
||||
"errors"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
"tinygo.org/x/drivers/internal/legacy"
|
||||
)
|
||||
|
||||
type AccelRange uint8
|
||||
@@ -27,7 +28,7 @@ type Device struct {
|
||||
accelMultiplier int32
|
||||
gyroMultiplier int32
|
||||
magMultiplier int32
|
||||
buf [7]uint8 // up to 6 bytes for read + 1 byte for the register address
|
||||
buf [6]uint8
|
||||
}
|
||||
|
||||
// Configuration for LSM9DS1 device.
|
||||
@@ -60,15 +61,10 @@ func New(bus drivers.I2C) *Device {
|
||||
// Case of boolean false and error nil means I2C is up,
|
||||
// but "who am I" responses have unexpected values.
|
||||
func (d *Device) Connected() bool {
|
||||
data, err := d.readBytes(d.AccelAddress, WHO_AM_I, 1)
|
||||
if err != nil || data[0] != 0x68 {
|
||||
return false
|
||||
}
|
||||
data, err = d.readBytes(d.MagAddress, WHO_AM_I_M, 1)
|
||||
if err != nil || data[0] != 0x3D {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
data1, data2 := d.buf[:1], d.buf[1:2]
|
||||
legacy.ReadRegister(d.bus, d.AccelAddress, WHO_AM_I, data1)
|
||||
legacy.ReadRegister(d.bus, d.MagAddress, WHO_AM_I_M, data2)
|
||||
return data1[0] == 0x68 && data2[0] == 0x3D
|
||||
}
|
||||
|
||||
// ReadAcceleration reads the current acceleration from the device and returns
|
||||
@@ -76,7 +72,8 @@ func (d *Device) Connected() bool {
|
||||
// and the sensor is not moving the returned value will be around 1000000 or
|
||||
// -1000000.
|
||||
func (d *Device) ReadAcceleration() (x, y, z int32, err error) {
|
||||
data, err := d.readBytes(d.AccelAddress, OUT_X_L_XL, 6)
|
||||
data := d.buf[:6]
|
||||
err = legacy.ReadRegister(d.bus, uint8(d.AccelAddress), OUT_X_L_XL, data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -91,7 +88,8 @@ func (d *Device) ReadAcceleration() (x, y, z int32, err error) {
|
||||
// rotation along one axis and while doing so integrate all values over time,
|
||||
// you would get a value close to 360000000.
|
||||
func (d *Device) ReadRotation() (x, y, z int32, err error) {
|
||||
data, err := d.readBytes(d.AccelAddress, OUT_X_L_G, 6)
|
||||
data := d.buf[:6]
|
||||
err = legacy.ReadRegister(d.bus, uint8(d.AccelAddress), OUT_X_L_G, data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -104,7 +102,8 @@ func (d *Device) ReadRotation() (x, y, z int32, err error) {
|
||||
// ReadMagneticField reads the current magnetic field from the device and returns
|
||||
// it in nT (nanotesla). 1 G (gauss) = 100_000 nT (nanotesla).
|
||||
func (d *Device) ReadMagneticField() (x, y, z int32, err error) {
|
||||
data, err := d.readBytes(d.MagAddress, OUT_X_L_M, 6)
|
||||
data := d.buf[:6]
|
||||
err = legacy.ReadRegister(d.bus, uint8(d.MagAddress), OUT_X_L_M, data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -116,7 +115,8 @@ func (d *Device) ReadMagneticField() (x, y, z int32, err error) {
|
||||
|
||||
// ReadTemperature returns the temperature in Celsius milli degrees (°C/1000)
|
||||
func (d *Device) ReadTemperature() (t int32, err error) {
|
||||
data, err := d.readBytes(d.AccelAddress, OUT_TEMP_L, 2)
|
||||
data := d.buf[:2]
|
||||
err = legacy.ReadRegister(d.bus, uint8(d.AccelAddress), OUT_TEMP_L, data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -167,16 +167,20 @@ func (d *Device) doConfigure(cfg Configuration) (err error) {
|
||||
d.magMultiplier = 58
|
||||
}
|
||||
|
||||
data := d.buf[:1]
|
||||
|
||||
// Configure accelerometer
|
||||
// Sample rate & measurement range
|
||||
err = d.writeByte(d.AccelAddress, CTRL_REG6_XL, uint8(cfg.AccelSampleRate)<<5|uint8(cfg.AccelRange)<<3)
|
||||
data[0] = uint8(cfg.AccelSampleRate)<<5 | uint8(cfg.AccelRange)<<3
|
||||
err = legacy.WriteRegister(d.bus, d.AccelAddress, CTRL_REG6_XL, data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Configure gyroscope
|
||||
// Sample rate & measurement range
|
||||
err = d.writeByte(d.AccelAddress, CTRL_REG1_G, uint8(cfg.GyroSampleRate)<<5|uint8(cfg.GyroRange)<<3)
|
||||
data[0] = uint8(cfg.GyroSampleRate)<<5 | uint8(cfg.GyroRange)<<3
|
||||
err = legacy.WriteRegister(d.bus, d.AccelAddress, CTRL_REG1_G, data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -186,44 +190,33 @@ func (d *Device) doConfigure(cfg Configuration) (err error) {
|
||||
// Temperature compensation enabled
|
||||
// High-performance mode XY axis
|
||||
// Sample rate
|
||||
err = d.writeByte(d.MagAddress, CTRL_REG1_M, 0b10000000|0b01000000|uint8(cfg.MagSampleRate)<<2)
|
||||
data[0] = 0b10000000 | 0b01000000 | uint8(cfg.MagSampleRate)<<2
|
||||
err = legacy.WriteRegister(d.bus, d.MagAddress, CTRL_REG1_M, data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Measurement range
|
||||
err = d.writeByte(d.MagAddress, CTRL_REG2_M, uint8(cfg.MagRange)<<5)
|
||||
data[0] = uint8(cfg.MagRange) << 5
|
||||
err = legacy.WriteRegister(d.bus, d.MagAddress, CTRL_REG2_M, data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Continuous-conversion mode
|
||||
// https://electronics.stackexchange.com/questions/237397/continuous-conversion-vs-single-conversion-mode
|
||||
err = d.writeByte(d.MagAddress, CTRL_REG3_M, 0b00000000)
|
||||
data[0] = 0b00000000
|
||||
err = legacy.WriteRegister(d.bus, d.MagAddress, CTRL_REG3_M, data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// High-performance mode Z axis
|
||||
err = d.writeByte(d.MagAddress, CTRL_REG4_M, 0b00001000)
|
||||
data[0] = 0b00001000
|
||||
err = legacy.WriteRegister(d.bus, d.MagAddress, CTRL_REG4_M, data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Device) readBytes(addr, reg, size uint8) ([]byte, error) {
|
||||
d.buf[0] = reg
|
||||
err := d.bus.Tx(uint16(addr), d.buf[0:1], d.buf[1:size+1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d.buf[1 : size+1], nil
|
||||
}
|
||||
|
||||
func (d *Device) writeByte(addr, reg, value uint8) error {
|
||||
d.buf[0] = reg
|
||||
d.buf[1] = value
|
||||
return d.bus.Tx(uint16(addr), d.buf[0:2], nil)
|
||||
}
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
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[:])
|
||||
}
|
||||
@@ -98,13 +98,3 @@ 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
|
||||
)
|
||||
|
||||
+2
-6
@@ -44,7 +44,6 @@ 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
|
||||
@@ -59,16 +58,14 @@ 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/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=feather-rp2040 ./examples/seesaw
|
||||
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=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=microbit ./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
|
||||
@@ -143,7 +140,6 @@ 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)
|
||||
|
||||
+137
-29
@@ -6,9 +6,11 @@ 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"
|
||||
)
|
||||
|
||||
@@ -21,15 +23,16 @@ type ResetValue [2]byte
|
||||
|
||||
// Device wraps I2C or SPI connection.
|
||||
type Device struct {
|
||||
bus Buser
|
||||
buffer []byte
|
||||
width int16
|
||||
height int16
|
||||
vccState VccMode
|
||||
canReset bool
|
||||
resetCol ResetValue
|
||||
resetPage ResetValue
|
||||
rotation drivers.Rotation
|
||||
bus Buser
|
||||
buffer []byte
|
||||
width int16
|
||||
height int16
|
||||
bufferSize int16
|
||||
vccState VccMode
|
||||
canReset bool
|
||||
resetCol ResetValue
|
||||
resetPage ResetValue
|
||||
rotation drivers.Rotation
|
||||
}
|
||||
|
||||
// Config is the configuration for the display
|
||||
@@ -48,15 +51,51 @@ 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(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
|
||||
configure() error
|
||||
tx(data []byte, isCommand bool) error
|
||||
setAddress(address uint16) error
|
||||
}
|
||||
|
||||
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
|
||||
@@ -70,6 +109,9 @@ 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 {
|
||||
@@ -85,9 +127,11 @@ 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.buffer = d.bus.configure(cfg.Address, d.width*d.height/8)
|
||||
d.bus.configure()
|
||||
|
||||
time.Sleep(100 * time.Nanosecond)
|
||||
d.Command(DISPLAYOFF)
|
||||
@@ -149,22 +193,11 @@ 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 := 0; i < len(d.buffer); i++ {
|
||||
for i := int16(0); i < d.bufferSize; i++ {
|
||||
d.buffer[i] = 0
|
||||
}
|
||||
}
|
||||
@@ -190,7 +223,7 @@ func (d *Device) Display() error {
|
||||
d.Command(d.resetPage[1])
|
||||
}
|
||||
|
||||
return d.bus.flush()
|
||||
return d.Tx(d.buffer, false)
|
||||
}
|
||||
|
||||
// SetPixel enables or disables a pixel in the buffer
|
||||
@@ -219,10 +252,12 @@ func (d *Device) GetPixel(x int16, y int16) bool {
|
||||
|
||||
// SetBuffer changes the whole buffer at once
|
||||
func (d *Device) SetBuffer(buffer []byte) error {
|
||||
if len(buffer) != len(d.buffer) {
|
||||
if int16(len(buffer)) != d.bufferSize {
|
||||
return errBufferSize
|
||||
}
|
||||
copy(d.buffer, buffer)
|
||||
for i := int16(0); i < d.bufferSize; i++ {
|
||||
d.buffer[i] = buffer[i]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -231,6 +266,79 @@ 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()
|
||||
b.dcPin.Low()
|
||||
b.csPin.Low()
|
||||
|
||||
err = b.wire.Tx(data, nil)
|
||||
b.csPin.High()
|
||||
} else {
|
||||
b.csPin.High()
|
||||
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
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
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()
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
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
|
||||
}
|
||||
+17
-23
@@ -8,11 +8,10 @@ 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"
|
||||
)
|
||||
|
||||
@@ -32,10 +31,10 @@ type Config struct {
|
||||
|
||||
type Device struct {
|
||||
bus drivers.SPI
|
||||
cs pin.OutputFunc
|
||||
dc pin.OutputFunc
|
||||
rst pin.OutputFunc
|
||||
isBusy pin.InputFunc
|
||||
cs machine.Pin
|
||||
dc machine.Pin
|
||||
rst machine.Pin
|
||||
busy machine.Pin
|
||||
width int16
|
||||
height int16
|
||||
buffer []uint8
|
||||
@@ -50,22 +49,17 @@ type Device struct {
|
||||
type Speed uint8
|
||||
|
||||
// New returns a new uc8151 driver. Pass in a fully configured SPI bus.
|
||||
// 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)
|
||||
func New(bus drivers.SPI, csPin, dcPin, rstPin, busyPin machine.Pin) Device {
|
||||
csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
rstPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
busyPin.Configure(machine.PinConfig{Mode: machine.PinInput})
|
||||
return Device{
|
||||
bus: bus,
|
||||
cs: csPin.Set,
|
||||
dc: dcPin.Set,
|
||||
rst: rstPin.Set,
|
||||
isBusy: busyPin.Get,
|
||||
bus: bus,
|
||||
cs: csPin,
|
||||
dc: dcPin,
|
||||
rst: rstPin,
|
||||
busy: busyPin,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -319,14 +313,14 @@ func (d *Device) ClearDisplay() {
|
||||
|
||||
// WaitUntilIdle waits until the display is ready
|
||||
func (d *Device) WaitUntilIdle() {
|
||||
for !d.isBusy() {
|
||||
for !d.busy.Get() {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
// IsBusy returns the busy status of the display
|
||||
func (d *Device) IsBusy() bool {
|
||||
return d.isBusy()
|
||||
return d.busy.Get()
|
||||
}
|
||||
|
||||
// ClearBuffer sets the buffer to 0xFF (white)
|
||||
|
||||
+1
-1
@@ -2,4 +2,4 @@ package drivers
|
||||
|
||||
// Version returns a user-readable string showing the version of the drivers package for support purposes.
|
||||
// Update this value before release of new version of software.
|
||||
const Version = "0.33.0"
|
||||
const Version = "0.32.0"
|
||||
|
||||
@@ -931,356 +931,6 @@ 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:
|
||||
@@ -2182,16 +1832,6 @@ 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()
|
||||
|
||||
+1
-1
@@ -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 150 168 200
|
||||
//go:generate go run gen-ws2812.go -arch=cortexm 16 48 64 120 125 168 200
|
||||
//go:generate go run gen-ws2812.go -arch=tinygoriscv 160 320
|
||||
|
||||
import (
|
||||
|
||||
@@ -31,9 +31,6 @@ func (d Device) WriteByte(c byte) error {
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user