mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-07-28 19:48:40 +00:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 453ff96a66 | |||
| c6d83b783f | |||
| 3fa08112db | |||
| b639f7b12e | |||
| 28d625abfd | |||
| 228e57cf98 | |||
| 6cf1eb86e5 | |||
| 857ab80ae6 | |||
| a31ba26a6c | |||
| 303ec94529 | |||
| 833990f44d | |||
| 28d87eb0c5 | |||
| ae9e8f915e | |||
| 45fad80c3e | |||
| 0304d30b78 | |||
| 7de0a0814e | |||
| 80356fd9d9 |
@@ -1,3 +1,28 @@
|
||||
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**
|
||||
|
||||
+15
-16
@@ -1,17 +1,17 @@
|
||||
package bmi160
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
"tinygo.org/x/drivers/internal/legacy"
|
||||
)
|
||||
|
||||
// DeviceSPI is the SPI interface to a BMI160 accelerometer/gyroscope. There is
|
||||
// also an I2C interface, but it is not yet supported.
|
||||
type DeviceSPI struct {
|
||||
// Chip select pin
|
||||
CSB machine.Pin
|
||||
CSB drivers.PinOutput
|
||||
|
||||
buf [7]byte
|
||||
|
||||
@@ -22,9 +22,9 @@ type DeviceSPI struct {
|
||||
// NewSPI returns a new device driver. The pin and SPI interface are not
|
||||
// touched, provide a fully configured SPI object and call Configure to start
|
||||
// using this device.
|
||||
func NewSPI(csb machine.Pin, spi drivers.SPI) *DeviceSPI {
|
||||
func NewSPI(csb drivers.PinOutput, spi drivers.SPI) *DeviceSPI {
|
||||
return &DeviceSPI{
|
||||
CSB: csb, // chip select
|
||||
CSB: legacy.PinOutput(csb), // chip select
|
||||
Bus: spi,
|
||||
}
|
||||
}
|
||||
@@ -33,8 +33,7 @@ func NewSPI(csb machine.Pin, spi drivers.SPI) *DeviceSPI {
|
||||
// configures the BMI160, but it does not configure the SPI interface (it is
|
||||
// assumed to be up and running).
|
||||
func (d *DeviceSPI) Configure() error {
|
||||
d.CSB.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
d.CSB.High()
|
||||
d.CSB.Set(true)
|
||||
|
||||
// The datasheet recommends doing a register read from address 0x7F to get
|
||||
// SPI communication going:
|
||||
@@ -86,9 +85,9 @@ func (d *DeviceSPI) ReadTemperature() (temperature int32, err error) {
|
||||
data[0] = 0x80 | reg_TEMPERATURE_0
|
||||
data[1] = 0
|
||||
data[2] = 0
|
||||
d.CSB.Low()
|
||||
d.CSB.Set(false)
|
||||
err = d.Bus.Tx(data, data)
|
||||
d.CSB.High()
|
||||
d.CSB.Set(true)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -123,9 +122,9 @@ func (d *DeviceSPI) ReadAcceleration() (x int32, y int32, z int32, err error) {
|
||||
for i := 1; i < len(data); i++ {
|
||||
data[i] = 0
|
||||
}
|
||||
d.CSB.Low()
|
||||
d.CSB.Set(false)
|
||||
err = d.Bus.Tx(data, data)
|
||||
d.CSB.High()
|
||||
d.CSB.Set(true)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -153,9 +152,9 @@ func (d *DeviceSPI) ReadRotation() (x int32, y int32, z int32, err error) {
|
||||
for i := 1; i < len(data); i++ {
|
||||
data[i] = 0
|
||||
}
|
||||
d.CSB.Low()
|
||||
d.CSB.Set(false)
|
||||
err = d.Bus.Tx(data, data)
|
||||
d.CSB.High()
|
||||
d.CSB.Set(true)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -201,9 +200,9 @@ func (d *DeviceSPI) readRegister(address uint8) uint8 {
|
||||
data := d.buf[:2]
|
||||
data[0] = 0x80 | address
|
||||
data[1] = 0
|
||||
d.CSB.Low()
|
||||
d.CSB.Set(false)
|
||||
d.Bus.Tx(data, data)
|
||||
d.CSB.High()
|
||||
d.CSB.Set(true)
|
||||
return data[1]
|
||||
}
|
||||
|
||||
@@ -217,7 +216,7 @@ func (d *DeviceSPI) writeRegister(address, data uint8) {
|
||||
buf[0] = address
|
||||
buf[1] = data
|
||||
|
||||
d.CSB.Low()
|
||||
d.CSB.Set(false)
|
||||
d.Bus.Tx(buf, buf)
|
||||
d.CSB.High()
|
||||
d.CSB.Set(true)
|
||||
}
|
||||
|
||||
+7
-7
@@ -2,22 +2,22 @@
|
||||
package buzzer // import "tinygo.org/x/drivers/buzzer"
|
||||
|
||||
import (
|
||||
"machine"
|
||||
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
// Device wraps a GPIO connection to a buzzer.
|
||||
type Device struct {
|
||||
pin machine.Pin
|
||||
set drivers.PinSet
|
||||
High bool
|
||||
BPM float64
|
||||
}
|
||||
|
||||
// New returns a new buzzer driver given which pin to use
|
||||
func New(pin machine.Pin) Device {
|
||||
func New(pin drivers.PinOutput) Device {
|
||||
return Device{
|
||||
pin: pin,
|
||||
set: pin.Set,
|
||||
High: false,
|
||||
BPM: 96.0,
|
||||
}
|
||||
@@ -25,14 +25,14 @@ func New(pin machine.Pin) Device {
|
||||
|
||||
// On sets the buzzer to a high state.
|
||||
func (l *Device) On() (err error) {
|
||||
l.pin.Set(true)
|
||||
l.set(true)
|
||||
l.High = true
|
||||
return
|
||||
}
|
||||
|
||||
// Off sets the buzzer to a low state.
|
||||
func (l *Device) Off() (err error) {
|
||||
l.pin.Set(false)
|
||||
l.set(false)
|
||||
l.High = false
|
||||
return
|
||||
}
|
||||
|
||||
+10
-11
@@ -9,9 +9,10 @@
|
||||
package dht // import "tinygo.org/x/drivers/dht"
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"runtime/interrupt"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
// DummyDevice provides a basic interface for DHT devices.
|
||||
@@ -30,7 +31,7 @@ type DummyDevice interface {
|
||||
// Since taking measurements from the sensor is time consuming procedure and blocks interrupts,
|
||||
// user can avoid any hidden calls to the sensor.
|
||||
type device struct {
|
||||
pin machine.Pin
|
||||
pin drivers.Pin
|
||||
|
||||
measurements DeviceType
|
||||
initialized bool
|
||||
@@ -93,14 +94,12 @@ func (t *device) HumidityFloat() (float32, error) {
|
||||
// Perform initialization of the communication protocol.
|
||||
// Device lowers the voltage on pin for startingLow=20ms and starts listening for response
|
||||
// Section 5.2 in [1]
|
||||
func initiateCommunication(p machine.Pin) {
|
||||
func initiateCommunication(p drivers.Pin) {
|
||||
// Send low signal to the device
|
||||
p.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
p.Low()
|
||||
p.Set(false)
|
||||
time.Sleep(startingLow)
|
||||
// Set pin to high and wait for reply
|
||||
p.High()
|
||||
p.Configure(machine.PinConfig{Mode: machine.PinInput})
|
||||
p.Set(true)
|
||||
}
|
||||
|
||||
// Measurements returns both measurements: temperature and humidity as they sent by the device.
|
||||
@@ -158,7 +157,7 @@ func (t *device) read() error {
|
||||
|
||||
// receiveSignals counts number of low and high cycles. The execution is time critical, so the function disables
|
||||
// interrupts
|
||||
func receiveSignals(pin machine.Pin, result []counter) {
|
||||
func receiveSignals(pin drivers.PinInput, result []counter) {
|
||||
i := uint8(0)
|
||||
mask := interrupt.Disable()
|
||||
defer interrupt.Restore(mask)
|
||||
@@ -189,7 +188,7 @@ func (t *device) extractData(signals []counter, buf []uint8) error {
|
||||
// waitForDataTransmission waits for reply from the sensor.
|
||||
// If no reply received, returns NoSignalError.
|
||||
// For more details, see section 5.2 in [1]
|
||||
func waitForDataTransmission(p machine.Pin) error {
|
||||
func waitForDataTransmission(p drivers.PinInput) error {
|
||||
// wait for thermometer to pull down
|
||||
if expectChange(p, true) == timeout {
|
||||
return NoSignalError
|
||||
@@ -209,8 +208,8 @@ func waitForDataTransmission(p machine.Pin) error {
|
||||
// This device provides full control to the user.
|
||||
// It does not do any hidden measurements calls and does not check
|
||||
// for 2 seconds delay between measurements.
|
||||
func NewDummyDevice(pin machine.Pin, deviceType DeviceType) DummyDevice {
|
||||
pin.High()
|
||||
func NewDummyDevice(pin drivers.Pin, deviceType DeviceType) DummyDevice {
|
||||
pin.Set(true)
|
||||
return &device{
|
||||
pin: pin,
|
||||
measurements: deviceType,
|
||||
|
||||
@@ -9,8 +9,9 @@
|
||||
package dht // import "tinygo.org/x/drivers/dht"
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
// Device interface provides main functionality of the DHTXX sensors.
|
||||
@@ -124,8 +125,8 @@ func (m *managedDevice) Configure(policy UpdatePolicy) {
|
||||
|
||||
// Constructor of the Device implementation.
|
||||
// This implementation updates data every 2 seconds during data access.
|
||||
func New(pin machine.Pin, deviceType DeviceType) Device {
|
||||
pin.High()
|
||||
func New(pin drivers.Pin, deviceType DeviceType) Device {
|
||||
pin.Set(true)
|
||||
return &managedDevice{
|
||||
t: device{
|
||||
pin: pin,
|
||||
@@ -141,8 +142,8 @@ func New(pin machine.Pin, deviceType DeviceType) Device {
|
||||
}
|
||||
|
||||
// Constructor of the Device implementation with given UpdatePolicy
|
||||
func NewWithPolicy(pin machine.Pin, deviceType DeviceType, updatePolicy UpdatePolicy) Device {
|
||||
pin.High()
|
||||
func NewWithPolicy(pin drivers.Pin, deviceType DeviceType, updatePolicy UpdatePolicy) Device {
|
||||
pin.Set(true)
|
||||
result := &managedDevice{
|
||||
t: device{
|
||||
pin: pin,
|
||||
|
||||
+5
-4
@@ -3,21 +3,22 @@
|
||||
package dht // import "tinygo.org/x/drivers/dht"
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
// Check if the pin is disabled
|
||||
func powerUp(p machine.Pin) bool {
|
||||
func powerUp(p drivers.Pin) bool {
|
||||
state := p.Get()
|
||||
if !state {
|
||||
p.High()
|
||||
p.Set(true)
|
||||
time.Sleep(startTimeout)
|
||||
}
|
||||
return state
|
||||
}
|
||||
|
||||
func expectChange(p machine.Pin, oldState bool) counter {
|
||||
func expectChange(p drivers.PinInput, oldState bool) counter {
|
||||
cnt := counter(0)
|
||||
for ; p.Get() == oldState && cnt != timeout; cnt++ {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,225 @@
|
||||
// Package ens160 provides a driver for the ScioSense ENS160 digital gas sensor.
|
||||
//
|
||||
// Datasheet: https://www.sciosense.com/wp-content/uploads/2023/12/ENS160-Datasheet.pdf
|
||||
package ens160
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultTimeout = 30 * time.Millisecond
|
||||
shortTimeout = 1 * time.Millisecond
|
||||
)
|
||||
|
||||
// Conversion constants for environment data compensation.
|
||||
const (
|
||||
kelvinOffsetMilli = 273150 // 273.15 K in milli-units
|
||||
tempRawFactor = 64 // As per datasheet for TEMP_IN
|
||||
humRawFactor = 512 // As per datasheet for RH_IN
|
||||
milliFactor = 1000 // For converting from milli-units
|
||||
roundingTerm = milliFactor / 2 // For rounding before integer division
|
||||
)
|
||||
|
||||
// validityStrings provides human-readable descriptions for validity flags.
|
||||
var validityStrings = [...]string{
|
||||
ValidityNormalOperation: "normal operation",
|
||||
ValidityWarmUpPhase: "warm-up phase, wait ~3 minutes for valid data",
|
||||
ValidityInitialStartUpPhase: "initial start-up phase, wait ~1 hour for valid data",
|
||||
ValidityInvalidOutput: "invalid output",
|
||||
}
|
||||
|
||||
// Device wraps an I2C connection to an ENS160 device.
|
||||
type Device struct {
|
||||
bus drivers.I2C // I²C implementation
|
||||
addr uint16 // 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
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package ens160
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCalculateTempRaw(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
tempMilliC int32
|
||||
expectedRaw uint16
|
||||
}{
|
||||
{"25°C", 25000, 19082},
|
||||
{"-10.5°C", -10500, 16810},
|
||||
{"Min temp", -40000, 14922},
|
||||
{"Below min", -50000, 14922},
|
||||
{"Max temp", 85000, 22922},
|
||||
{"Above max", 90000, 22922},
|
||||
{"Zero", 0, 17482},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
raw := calculateTempRaw(tc.tempMilliC)
|
||||
if raw != tc.expectedRaw {
|
||||
t.Errorf("expected %d, got %d", tc.expectedRaw, raw)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalculateHumRaw(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
rhMilliPct int32
|
||||
expectedRaw uint16
|
||||
}{
|
||||
{"50%", 50000, 25600},
|
||||
{"0%", 0, 0},
|
||||
{"100%", 100000, 51200},
|
||||
{"Below 0%", -10000, 0},
|
||||
{"Above 100%", 110000, 51200},
|
||||
{"33.3%", 33300, 17050},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
raw := calculateHumRaw(tc.rhMilliPct)
|
||||
if raw != tc.expectedRaw {
|
||||
t.Errorf("expected %d, got %d", tc.expectedRaw, raw)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package ens160
|
||||
|
||||
// DefaultAddress is the default I2C address for the ENS160 when the ADDR pin is
|
||||
// connected to high (3.3V). When connected to low (GND), the address is 0x52.
|
||||
const DefaultAddress = 0x53
|
||||
|
||||
// Registers
|
||||
const (
|
||||
regPartID = 0x00
|
||||
regOpMode = 0x10
|
||||
regConfig = 0x11
|
||||
regCommand = 0x12
|
||||
regTempIn = 0x13
|
||||
regRhIn = 0x15
|
||||
regStatus = 0x20
|
||||
regAQI = 0x21
|
||||
regTVOC = 0x22
|
||||
regECO2 = 0x24
|
||||
regDataT = 0x30
|
||||
regDataRH = 0x32
|
||||
regMISR = 0x38
|
||||
regGPRWrite = 0x40
|
||||
regGPRRead = 0x48
|
||||
)
|
||||
|
||||
// Operating modes
|
||||
const (
|
||||
ModeDeepSleep = 0x00
|
||||
ModeIdle = 0x01
|
||||
ModeStandard = 0x02
|
||||
ModeReset = 0xF0
|
||||
)
|
||||
|
||||
// Status register bits
|
||||
const (
|
||||
statusSTATAS = 1 << 7
|
||||
statusSTATER = 1 << 6
|
||||
|
||||
statusValidityMask = 0x0C
|
||||
statusValidityShift = 2
|
||||
|
||||
statusNEWDAT = 1 << 1
|
||||
statusNEWGPR = 1 << 0
|
||||
)
|
||||
|
||||
// Validity flags
|
||||
const (
|
||||
ValidityNormalOperation = 0x00
|
||||
ValidityWarmUpPhase = 0x01 // need ~3 minutes until valid data
|
||||
ValidityInitialStartUpPhase = 0x02 // need ~1 hour until valid data
|
||||
ValidityInvalidOutput = 0x03
|
||||
)
|
||||
|
||||
// Commands
|
||||
const (
|
||||
cmdNOP = 0x00
|
||||
cmdGetAppVer = 0x0E
|
||||
cmdClrGPR = 0xCC
|
||||
)
|
||||
|
||||
// Part IDs
|
||||
const (
|
||||
LowPartID = 0x60
|
||||
HighPartID = 0x01
|
||||
)
|
||||
@@ -1,14 +1,18 @@
|
||||
//go:build baremetal && tinygo
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/dht"
|
||||
"tinygo.org/x/drivers/tinygo"
|
||||
)
|
||||
|
||||
func main() {
|
||||
pin := machine.D6
|
||||
pin := tinygo.New(machine.D6)
|
||||
dhtSensor := dht.New(pin, dht.DHT11)
|
||||
for {
|
||||
temp, hum, err := dhtSensor.Measurements()
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
// This example demonstrates ENS160 usage.
|
||||
//
|
||||
// Wiring:
|
||||
// - VCC to 3.3V, GND to ground
|
||||
// - SDA to board SDA, SCL to board SCL
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"machine"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
"tinygo.org/x/drivers/ens160"
|
||||
)
|
||||
|
||||
func main() {
|
||||
err := machine.I2C0.Configure(machine.I2CConfig{
|
||||
Frequency: 400 * machine.KHz,
|
||||
})
|
||||
if err != nil {
|
||||
println("Failed to configure I2C:", err)
|
||||
}
|
||||
|
||||
dev := ens160.New(machine.I2C0, ens160.DefaultAddress)
|
||||
|
||||
connected := dev.Connected()
|
||||
if !connected {
|
||||
println("ENS160 not detected")
|
||||
return
|
||||
}
|
||||
println("ENS160 detected")
|
||||
|
||||
if err := dev.Configure(); err != nil {
|
||||
println("Failed to configure ENS160:", err)
|
||||
}
|
||||
|
||||
for {
|
||||
err := dev.Update(drivers.Concentration)
|
||||
if err != nil {
|
||||
println("Error reading ENS160: %v\n", err)
|
||||
time.Sleep(5 * time.Second)
|
||||
continue
|
||||
}
|
||||
|
||||
println(
|
||||
"AQI:", dev.AQI(),
|
||||
"TVOC:", dev.TVOC(),
|
||||
"eCO2:", dev.ECO2(),
|
||||
"Validity:", dev.ValidityString(),
|
||||
)
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
func main() {
|
||||
println("GPS I2C Example")
|
||||
machine.I2C0.Configure(machine.I2CConfig{})
|
||||
ublox := gps.NewI2C(machine.I2C0)
|
||||
ublox := gps.NewI2CWithAddress(machine.I2C0, gps.UBLOX_I2C_ADDRESS)
|
||||
parser := gps.NewParser()
|
||||
var fix gps.Fix
|
||||
for {
|
||||
|
||||
@@ -5,10 +5,13 @@ import (
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/hcsr04"
|
||||
"tinygo.org/x/drivers/tinygo"
|
||||
)
|
||||
|
||||
func main() {
|
||||
sensor := hcsr04.New(machine.D10, machine.D9)
|
||||
trigger := tinygo.New(machine.D10) // automatically configures pin as output
|
||||
echo := tinygo.New(machine.D9) // automatically configures pin as input
|
||||
sensor := hcsr04.New(trigger, echo)
|
||||
sensor.Configure()
|
||||
|
||||
println("Ultrasonic starts")
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/lsm303dlhc"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
// LSM303DLHC is connected to the I2C0 bus on Adafruit Feather M4 via pins: 20(SDA) and 21(SCL).
|
||||
machine.I2C0.Configure(machine.I2CConfig{})
|
||||
|
||||
sensor := lsm303dlhc.New(machine.I2C0)
|
||||
//default settings
|
||||
err := sensor.Configure(lsm303dlhc.Configuration{
|
||||
AccelPowerMode: lsm303dlhc.ACCEL_POWER_NORMAL,
|
||||
AccelRange: lsm303dlhc.ACCEL_RANGE_2G,
|
||||
AccelDataRate: lsm303dlhc.ACCEL_DATARATE_100HZ,
|
||||
MagPowerMode: lsm303dlhc.MAG_POWER_NORMAL,
|
||||
MagSystemMode: lsm303dlhc.MAG_SYSTEM_CONTINUOUS,
|
||||
MagDataRate: lsm303dlhc.MAG_DATARATE_10HZ,
|
||||
})
|
||||
if err != nil {
|
||||
for {
|
||||
println("Failed to configure", err.Error())
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
for {
|
||||
accel_x, accel_y, accel_z, err := sensor.ReadAcceleration()
|
||||
if err != nil {
|
||||
println("Failed to read accel", err.Error())
|
||||
}
|
||||
println("ACCEL_X:", accel_x, " ACCEL_Y:", accel_y, " ACCEL_Z:", accel_z)
|
||||
|
||||
mag_x, mag_y, mag_z, err := sensor.ReadMagneticField()
|
||||
if err != nil {
|
||||
println("Failed to read mag", err.Error())
|
||||
}
|
||||
println("MAG_X:", mag_x, " MAG_Y:", mag_y, " MAG_Z:", mag_z)
|
||||
|
||||
pitch, roll, _ := sensor.ReadPitchRoll()
|
||||
println("Pitch:", float32(pitch), " Roll:", float32(roll))
|
||||
|
||||
heading, _ := sensor.ReadCompass()
|
||||
println("Heading:", float32(heading), "degrees")
|
||||
|
||||
temp, _ := sensor.ReadTemperature()
|
||||
println("Temperature:", float32(temp)/1000, "*C")
|
||||
|
||||
println("\n")
|
||||
time.Sleep(time.Millisecond * 250)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package main
|
||||
|
||||
// Example program for the ST7735 display (Waveshare 1.44" LCD HAT) using the rpio driver on a Raspberry Pi.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image/color"
|
||||
"time"
|
||||
|
||||
go_rpio "github.com/stianeikeland/go-rpio/v4"
|
||||
"tinygo.org/x/drivers/rpio"
|
||||
"tinygo.org/x/drivers/st7735"
|
||||
)
|
||||
|
||||
var (
|
||||
black = color.RGBA{0, 0, 0, 255}
|
||||
colors = [...]color.RGBA{
|
||||
{255, 0, 0, 255}, // red
|
||||
{0, 255, 0, 255}, // green
|
||||
{0, 0, 255, 255}, // blue
|
||||
{255, 255, 0, 255}, // yellow
|
||||
}
|
||||
)
|
||||
|
||||
func main() {
|
||||
if err := go_rpio.Open(); err != nil {
|
||||
fmt.Println("Error opening GPIO:", err)
|
||||
return
|
||||
}
|
||||
defer go_rpio.Close()
|
||||
|
||||
// Initialize SPI and pins
|
||||
spi := rpio.NewSPI()
|
||||
resetPin := rpio.NewPin(27)
|
||||
dcPin := rpio.NewPin(25)
|
||||
csPin := rpio.NewPin(8)
|
||||
blPin := rpio.NewPin(24)
|
||||
|
||||
// Initialize display
|
||||
device := st7735.New(spi, resetPin, dcPin, csPin, blPin)
|
||||
device.Configure(st7735.Config{
|
||||
Width: 128,
|
||||
Height: 128,
|
||||
Model: st7735.GREENTAB,
|
||||
RowOffset: 3,
|
||||
ColumnOffset: 2,
|
||||
})
|
||||
device.InvertColors(false)
|
||||
device.EnableBacklight(true)
|
||||
device.IsBGR(true) // no effect w/o rotation!
|
||||
device.SetRotation(st7735.NO_ROTATION)
|
||||
|
||||
width, height := device.Size()
|
||||
|
||||
// Clear display
|
||||
device.FillScreen(black)
|
||||
|
||||
// Draw rectangles in a loop, clockwise rotation of colors
|
||||
pos := 0
|
||||
for {
|
||||
device.FillRectangle(0, 0, width/2, height/2, colors[(pos+0)%len(colors)]) // top left
|
||||
device.FillRectangle(0, height/2, width/2, height/2, colors[(pos+1)%len(colors)]) // bottom left
|
||||
device.FillRectangle(width/2, height/2, width/2, height/2, colors[(pos+2)%len(colors)]) // bottom right
|
||||
device.FillRectangle(width/2, 0, width/2, height/2, colors[(pos+3)%len(colors)]) // top right
|
||||
pos++
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/seesaw"
|
||||
)
|
||||
|
||||
// example reading the position of a rotary encoder (4991) powered by a seesaw
|
||||
// https://learn.adafruit.com/adafruit-i2c-qt-rotary-encoder/arduino
|
||||
func main() {
|
||||
// This assumes you are using an Adafruit QT Py RP2040 for its Stemma QT connector
|
||||
// https://www.adafruit.com/product/4900
|
||||
i2c := machine.I2C1
|
||||
i2c.Configure(machine.I2CConfig{
|
||||
SCL: machine.I2C1_QT_SCL_PIN,
|
||||
SDA: machine.I2C1_QT_SDA_PIN,
|
||||
})
|
||||
|
||||
dev := seesaw.New(i2c)
|
||||
dev.Address = 0x36
|
||||
|
||||
for {
|
||||
time.Sleep(time.Second)
|
||||
|
||||
pos, err := dev.GetEncoderPosition(0, false)
|
||||
if err != nil {
|
||||
println(err)
|
||||
continue
|
||||
}
|
||||
|
||||
println(pos)
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
|
||||
"image/color"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/ssd1306"
|
||||
)
|
||||
|
||||
func main() {
|
||||
machine.I2C0.Configure(machine.I2CConfig{
|
||||
Frequency: machine.TWI_FREQ_400KHZ,
|
||||
})
|
||||
|
||||
display := ssd1306.NewI2C(machine.I2C0)
|
||||
display.Configure(ssd1306.Config{
|
||||
Address: ssd1306.Address_128_32,
|
||||
Width: 128,
|
||||
Height: 32,
|
||||
})
|
||||
|
||||
display.ClearDisplay()
|
||||
|
||||
x := int16(0)
|
||||
y := int16(0)
|
||||
deltaX := int16(1)
|
||||
deltaY := int16(1)
|
||||
for {
|
||||
pixel := display.GetPixel(x, y)
|
||||
c := color.RGBA{255, 255, 255, 255}
|
||||
if pixel {
|
||||
c = color.RGBA{0, 0, 0, 255}
|
||||
}
|
||||
display.SetPixel(x, y, c)
|
||||
display.Display()
|
||||
|
||||
x += deltaX
|
||||
y += deltaY
|
||||
|
||||
if x == 0 || x == 127 {
|
||||
deltaX = -deltaX
|
||||
}
|
||||
|
||||
if y == 0 || y == 31 {
|
||||
deltaY = -deltaY
|
||||
}
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
// This example shows how to use 128x64 display over I2C
|
||||
// Tested on Seeeduino XIAO Expansion Board https://wiki.seeedstudio.com/Seeeduino-XIAO-Expansion-Board/
|
||||
//
|
||||
// According to manual, I2C address of the display is 0x78, but that's 8-bit address.
|
||||
// TinyGo operates on 7-bit addresses and respective 7-bit address would be 0x3C, which we use below.
|
||||
//
|
||||
// To learn more about different types of I2C addresses, please see following page
|
||||
// https://www.totalphase.com/support/articles/200349176-7-bit-8-bit-and-10-bit-I2C-Slave-Addressing
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
|
||||
"image/color"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/ssd1306"
|
||||
)
|
||||
|
||||
func main() {
|
||||
machine.I2C0.Configure(machine.I2CConfig{
|
||||
Frequency: machine.TWI_FREQ_400KHZ,
|
||||
})
|
||||
|
||||
display := ssd1306.NewI2C(machine.I2C0)
|
||||
display.Configure(ssd1306.Config{
|
||||
Address: 0x3C,
|
||||
Width: 128,
|
||||
Height: 64,
|
||||
})
|
||||
|
||||
display.ClearDisplay()
|
||||
|
||||
x := int16(0)
|
||||
y := int16(0)
|
||||
deltaX := int16(1)
|
||||
deltaY := int16(1)
|
||||
for {
|
||||
pixel := display.GetPixel(x, y)
|
||||
c := color.RGBA{255, 255, 255, 255}
|
||||
if pixel {
|
||||
c = color.RGBA{0, 0, 0, 255}
|
||||
}
|
||||
display.SetPixel(x, y, c)
|
||||
display.Display()
|
||||
|
||||
x += deltaX
|
||||
y += deltaY
|
||||
|
||||
if x == 0 || x == 127 {
|
||||
deltaX = -deltaX
|
||||
}
|
||||
|
||||
if y == 0 || y == 63 {
|
||||
deltaY = -deltaY
|
||||
}
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package main
|
||||
|
||||
// This example shows how to use SSD1306 OLED display driver over I2C and SPI.
|
||||
//
|
||||
// Check the `newSSD1306Display()` functions for I2C and SPI initializations.
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
|
||||
"image/color"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
display := newSSD1306Display()
|
||||
display.ClearDisplay()
|
||||
|
||||
w, h := display.Size()
|
||||
x := int16(0)
|
||||
y := int16(0)
|
||||
deltaX := int16(1)
|
||||
deltaY := int16(1)
|
||||
|
||||
traceTime := time.Now().UnixMilli() + 1000
|
||||
frames := 0
|
||||
ms := runtime.MemStats{}
|
||||
|
||||
for {
|
||||
pixel := display.GetPixel(x, y)
|
||||
c := color.RGBA{255, 255, 255, 255}
|
||||
if pixel {
|
||||
c = color.RGBA{0, 0, 0, 255}
|
||||
}
|
||||
display.SetPixel(x, y, c)
|
||||
display.Display()
|
||||
|
||||
x += deltaX
|
||||
y += deltaY
|
||||
|
||||
if x == 0 || x == w-1 {
|
||||
deltaX = -deltaX
|
||||
}
|
||||
|
||||
if y == 0 || y == h-1 {
|
||||
deltaY = -deltaY
|
||||
}
|
||||
|
||||
frames++
|
||||
now := time.Now().UnixMilli()
|
||||
if now >= traceTime {
|
||||
runtime.ReadMemStats(&ms)
|
||||
println("TS", now, "| FPS", frames, "| HeapInuse", ms.HeapInuse)
|
||||
traceTime = now + 1000
|
||||
frames = 0
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
//go:build xiao_ble
|
||||
|
||||
// This initializes SSD1306 OLED display driver over I2C.
|
||||
//
|
||||
// Seeed XIAO BLE board + SSD1306 128x32 I2C OLED display.
|
||||
//
|
||||
// Wiring:
|
||||
// - XIAO GND -> OLED GND
|
||||
// - XIAO 3v3 -> OLED VCC
|
||||
// - XIAO D4 (SDA) -> OLED SDA
|
||||
// - XIAO D5 (SCL) -> OLED SCK
|
||||
//
|
||||
// For your case:
|
||||
// - Connect the display to I2C pins on your board.
|
||||
// - Adjust I2C address and display size as needed.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
|
||||
"tinygo.org/x/drivers/ssd1306"
|
||||
)
|
||||
|
||||
func newSSD1306Display() *ssd1306.Device {
|
||||
machine.I2C0.Configure(machine.I2CConfig{
|
||||
Frequency: 400 * machine.KHz,
|
||||
SDA: machine.SDA0_PIN,
|
||||
SCL: machine.SCL0_PIN,
|
||||
})
|
||||
display := ssd1306.NewI2C(machine.I2C0)
|
||||
display.Configure(ssd1306.Config{
|
||||
Address: ssd1306.Address_128_32, // or ssd1306.Address
|
||||
Width: 128,
|
||||
Height: 32, // or 64
|
||||
})
|
||||
return display
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
//go:build thumby
|
||||
|
||||
// This initializes SSD1306 OLED display driver over SPI.
|
||||
//
|
||||
// Thumby board has a tiny built-in 72x40 display.
|
||||
//
|
||||
// As the display is built-in, no wiring is needed.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
|
||||
"tinygo.org/x/drivers/ssd1306"
|
||||
)
|
||||
|
||||
func newSSD1306Display() *ssd1306.Device {
|
||||
machine.SPI0.Configure(machine.SPIConfig{})
|
||||
display := ssd1306.NewSPI(machine.SPI0, machine.THUMBY_DC_PIN, machine.THUMBY_RESET_PIN, machine.THUMBY_CS_PIN)
|
||||
display.Configure(ssd1306.Config{
|
||||
Width: 72,
|
||||
Height: 40,
|
||||
ResetCol: ssd1306.ResetValue{28, 99},
|
||||
ResetPage: ssd1306.ResetValue{0, 5},
|
||||
})
|
||||
return display
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
//go:build xiao_rp2040
|
||||
|
||||
// This initializes SSD1306 OLED display driver over SPI.
|
||||
//
|
||||
// Seeed XIAO RP2040 board + SSD1306 128x64 SPI OLED display.
|
||||
//
|
||||
// Wiring:
|
||||
// - XIAO GND -> OLED GND
|
||||
// - XIAO 3v3 -> OLED VCC
|
||||
// - XIAO D8 (SCK) -> OLED D0
|
||||
// - XIAO D10 (SDO) -> OLED D1
|
||||
// - XIAO D4 -> OLED RES
|
||||
// - XIAO D5 -> OLED DC
|
||||
// - XIAO D6 -> OLED CS
|
||||
//
|
||||
// For your case:
|
||||
// - Connect the display to SPI pins on your board.
|
||||
// - Adjust RES, DC and CS pins as needed.
|
||||
// - Adjust SPI frequency as needed.
|
||||
// - Adjust display size as needed.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
|
||||
"tinygo.org/x/drivers/ssd1306"
|
||||
)
|
||||
|
||||
func newSSD1306Display() *ssd1306.Device {
|
||||
machine.SPI0.Configure(machine.SPIConfig{
|
||||
Frequency: 50 * machine.MHz,
|
||||
})
|
||||
display := ssd1306.NewSPI(machine.SPI0, machine.D5, machine.D4, machine.D6)
|
||||
display.Configure(ssd1306.Config{
|
||||
Width: 128,
|
||||
Height: 64,
|
||||
})
|
||||
return display
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/ssd1306"
|
||||
)
|
||||
|
||||
func main() {
|
||||
machine.SPI0.Configure(machine.SPIConfig{
|
||||
Frequency: 8000000,
|
||||
})
|
||||
display := ssd1306.NewSPI(machine.SPI0, machine.P8, machine.P7, machine.P9)
|
||||
display.Configure(ssd1306.Config{
|
||||
Width: 128,
|
||||
Height: 64,
|
||||
})
|
||||
|
||||
display.ClearDisplay()
|
||||
|
||||
x := int16(64)
|
||||
y := int16(32)
|
||||
deltaX := int16(1)
|
||||
deltaY := int16(1)
|
||||
for {
|
||||
pixel := display.GetPixel(x, y)
|
||||
c := color.RGBA{255, 255, 255, 255}
|
||||
if pixel {
|
||||
c = color.RGBA{0, 0, 0, 255}
|
||||
}
|
||||
display.SetPixel(x, y, c)
|
||||
display.Display()
|
||||
|
||||
x += deltaX
|
||||
y += deltaY
|
||||
|
||||
if x == 0 || x == 127 {
|
||||
deltaX = -deltaX
|
||||
}
|
||||
|
||||
if y == 0 || y == 63 {
|
||||
deltaY = -deltaY
|
||||
}
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
// This example using the SSD1306 OLED display over SPI on the Thumby board
|
||||
// A very tiny 72x40 display.
|
||||
package main
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/ssd1306"
|
||||
)
|
||||
|
||||
func main() {
|
||||
machine.SPI0.Configure(machine.SPIConfig{})
|
||||
display := ssd1306.NewSPI(machine.SPI0, machine.THUMBY_DC_PIN, machine.THUMBY_RESET_PIN, machine.THUMBY_CS_PIN)
|
||||
display.Configure(ssd1306.Config{
|
||||
Width: 72,
|
||||
Height: 40,
|
||||
ResetCol: ssd1306.ResetValue{28, 99},
|
||||
ResetPage: ssd1306.ResetValue{0, 5},
|
||||
})
|
||||
|
||||
display.ClearDisplay()
|
||||
|
||||
x := int16(36)
|
||||
y := int16(20)
|
||||
deltaX := int16(1)
|
||||
deltaY := int16(1)
|
||||
for {
|
||||
pixel := display.GetPixel(x, y)
|
||||
c := color.RGBA{255, 255, 255, 255}
|
||||
if pixel {
|
||||
c = color.RGBA{0, 0, 0, 255}
|
||||
}
|
||||
display.SetPixel(x, y, c)
|
||||
display.Display()
|
||||
|
||||
x += deltaX
|
||||
y += deltaY
|
||||
|
||||
if x == 0 || x == 71 {
|
||||
deltaX = -deltaX
|
||||
}
|
||||
|
||||
if y == 0 || y == 39 {
|
||||
deltaY = -deltaY
|
||||
}
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
@@ -46,6 +46,10 @@ var DefaultDeviceIdentifier = DeviceIdentifierFunc(func(id JedecID) Attrs {
|
||||
return GD25Q16C()
|
||||
case 0xC84017:
|
||||
return GD25Q64C()
|
||||
case 0x856015:
|
||||
return P25Q16H()
|
||||
case 0xEF4014:
|
||||
return W25Q80DV()
|
||||
case 0xEF4015:
|
||||
return W25Q16JVIQ()
|
||||
case 0xEF4016:
|
||||
@@ -239,6 +243,24 @@ func GD25Q64C() Attrs {
|
||||
}
|
||||
}
|
||||
|
||||
// Settings for the Puya P25Q16H 2MiB SPI flash.
|
||||
// Datasheet: https://files.seeedstudio.com/wiki/github_weiruanexample/Flash_P25Q16H-UXH-IR_Datasheet.pdf
|
||||
func P25Q16H() Attrs {
|
||||
return Attrs{
|
||||
TotalSize: 1 << 21, // 2 MiB
|
||||
StartUp: 5000 * time.Microsecond,
|
||||
JedecID: JedecID{0x85, 0x60, 0x15},
|
||||
MaxClockSpeedMHz: 55,
|
||||
QuadEnableBitMask: 0x02,
|
||||
HasSectorProtection: true,
|
||||
SupportsFastRead: true,
|
||||
SupportsQSPI: true,
|
||||
SupportsQSPIWrites: true,
|
||||
WriteStatusSplit: true,
|
||||
SingleStatusByte: false,
|
||||
}
|
||||
}
|
||||
|
||||
// Settings for the Winbond W25Q16JV-IQ 2MiB SPI flash. Note that JV-IM has a
|
||||
// different .memory_type (0x70) Datasheet:
|
||||
// https://www.winbond.com/resource-files/w25q16jv%20spi%20revf%2005092017.pdf
|
||||
@@ -380,6 +402,25 @@ func W25Q80DL() Attrs {
|
||||
TotalSize: 1 << 20, // 1 MiB
|
||||
StartUp: 5000 * time.Microsecond,
|
||||
JedecID: JedecID{0xEF, 0x60, 0x14},
|
||||
MaxClockSpeedMHz: 80,
|
||||
QuadEnableBitMask: 0x02,
|
||||
HasSectorProtection: false,
|
||||
SupportsFastRead: true,
|
||||
SupportsQSPI: true,
|
||||
SupportsQSPIWrites: false,
|
||||
WriteStatusSplit: false,
|
||||
SingleStatusByte: false,
|
||||
}
|
||||
}
|
||||
|
||||
// Settings for the Winbond W25Q80DV 2MiB SPI flash.
|
||||
// Datasheet:
|
||||
// https://www.winbond.com/resource-files/w25q80dv%20dl_revh_10022015.pdf
|
||||
func W25Q80DV() Attrs {
|
||||
return Attrs{
|
||||
TotalSize: 1 << 21, // 2 MiB
|
||||
StartUp: 5000 * time.Microsecond,
|
||||
JedecID: JedecID{0xEF, 0x40, 0x14},
|
||||
MaxClockSpeedMHz: 104,
|
||||
QuadEnableBitMask: 0x02,
|
||||
HasSectorProtection: false,
|
||||
|
||||
@@ -12,6 +12,7 @@ require (
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
||||
github.com/orsinium-labs/tinymath v1.1.0
|
||||
github.com/soypat/natiu-mqtt v0.5.1
|
||||
github.com/stianeikeland/go-rpio/v4 v4.6.0
|
||||
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d
|
||||
golang.org/x/net v0.33.0
|
||||
tinygo.org/x/tinyfont v0.3.0
|
||||
|
||||
@@ -17,6 +17,8 @@ github.com/orsinium-labs/tinymath v1.1.0 h1:KomdsyLHB7vE3f1nRAJF2dyf1m/gnM2HxfTe
|
||||
github.com/orsinium-labs/tinymath v1.1.0/go.mod h1:WPXX6ei3KSXG7JfA03a+ekCYaY9SWN4I+JRl2p6ck+A=
|
||||
github.com/soypat/natiu-mqtt v0.5.1 h1:rwaDmlvjzD2+3MCOjMZc4QEkDkNwDzbct2TJbpz+TPc=
|
||||
github.com/soypat/natiu-mqtt v0.5.1/go.mod h1:xEta+cwop9izVCW7xOx2W+ct9PRMqr0gNVkvBPnQTc4=
|
||||
github.com/stianeikeland/go-rpio/v4 v4.6.0 h1:eAJgtw3jTtvn/CqwbC82ntcS+dtzUTgo5qlZKe677EY=
|
||||
github.com/stianeikeland/go-rpio/v4 v4.6.0/go.mod h1:A3GvHxC1Om5zaId+HqB3HKqx4K/AqeckxB7qRjxMK7o=
|
||||
github.com/valyala/fastjson v1.6.3/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
|
||||
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d h1:0olWaB5pg3+oychR51GUVCEsGkeCU/2JxjBgIo4f3M0=
|
||||
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
|
||||
|
||||
+7
-1
@@ -69,10 +69,16 @@ func NewUART(uart drivers.UART) Device {
|
||||
}
|
||||
|
||||
// NewI2C creates a new I2C GPS connection.
|
||||
// Uses the default i2c address (0x42) for backward compatibility reasons.
|
||||
func NewI2C(bus drivers.I2C) Device {
|
||||
return NewI2CWithAddress(bus, I2C_ADDRESS)
|
||||
}
|
||||
|
||||
// NewI2CWithAddress creates a new I2C GPS connection on the provided address
|
||||
func NewI2CWithAddress(bus drivers.I2C, i2cAddress uint16) Device {
|
||||
return Device{
|
||||
bus: bus,
|
||||
address: I2C_ADDRESS,
|
||||
address: i2cAddress,
|
||||
buffer: make([]byte, bufferSize),
|
||||
bufIdx: bufferSize,
|
||||
sentence: strings.Builder{},
|
||||
|
||||
+4
-1
@@ -96,7 +96,10 @@ func (parser *Parser) Parse(sentence string) (Fix, error) {
|
||||
fix.Speed = findSpeed(fields[7])
|
||||
fix.Heading = findHeading(fields[8])
|
||||
date := findDate(fields[9])
|
||||
fix.Time = fix.Time.AddDate(date.Year(), int(date.Month()), date.Day())
|
||||
fix.Time = date.Add(time.Duration(fix.Time.Hour())*time.Hour +
|
||||
time.Duration(fix.Time.Minute())*time.Minute +
|
||||
time.Duration(fix.Time.Second())*time.Second +
|
||||
time.Duration(fix.Time.Nanosecond())*time.Nanosecond)
|
||||
|
||||
return fix, nil
|
||||
}
|
||||
|
||||
@@ -70,15 +70,15 @@ func TestParseRMC(t *testing.T) {
|
||||
t.Error("should have errInvalidRMCSentence error")
|
||||
}
|
||||
|
||||
val = "$GPRMC,203522.00,A,5109.0262308,N,11401.8407342,W,0.004,133.4,130522,0.0,E,D*2B"
|
||||
val = "$GPRMC,203522.00,A,5109.0262308,N,11401.8407342,W,0.004,133.4,010622,0.0,E,D*2B"
|
||||
fix, err := p.Parse(val)
|
||||
if err != nil {
|
||||
t.Error("should have parsed")
|
||||
}
|
||||
|
||||
c.Assert(fix.Time.Year(), qt.Equals, 2022)
|
||||
c.Assert(fix.Time.Month(), qt.Equals, time.May)
|
||||
c.Assert(fix.Time.Day(), qt.Equals, 13)
|
||||
c.Assert(fix.Time.Month(), qt.Equals, time.June)
|
||||
c.Assert(fix.Time.Day(), qt.Equals, 1)
|
||||
c.Assert(fix.Time.Hour(), qt.Equals, 20)
|
||||
c.Assert(fix.Time.Minute(), qt.Equals, 35)
|
||||
c.Assert(fix.Time.Second(), qt.Equals, 22)
|
||||
|
||||
+5
-1
@@ -4,7 +4,11 @@ package gps
|
||||
|
||||
// The I2C address which this device listens to.
|
||||
const (
|
||||
I2C_ADDRESS = 0x42
|
||||
// To ensure backward compatibility
|
||||
I2C_ADDRESS = UBLOX_I2C_ADDRESS
|
||||
|
||||
UBLOX_I2C_ADDRESS = 0x42
|
||||
PA1010D_I2C_ADDRESS = 0x10
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
+11
-10
@@ -5,20 +5,22 @@
|
||||
package hcsr04
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
const TIMEOUT = 23324 // max sensing distance (4m)
|
||||
|
||||
// Device holds the pins
|
||||
type Device struct {
|
||||
trigger machine.Pin
|
||||
echo machine.Pin
|
||||
trigger drivers.PinOutput
|
||||
echo drivers.PinInput
|
||||
}
|
||||
|
||||
// New returns a new ultrasonic driver given 2 pins
|
||||
func New(trigger, echo machine.Pin) Device {
|
||||
// New returns a new ultrasonic driver given 2 pins.
|
||||
// Pins must be configured as output (trigger) and input (echo).
|
||||
func New(trigger drivers.PinOutput, echo drivers.PinInput) Device {
|
||||
return Device{
|
||||
trigger: trigger,
|
||||
echo: echo,
|
||||
@@ -27,8 +29,7 @@ func New(trigger, echo machine.Pin) Device {
|
||||
|
||||
// Configure configures the pins of the Device
|
||||
func (d *Device) Configure() {
|
||||
d.trigger.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
d.echo.Configure(machine.PinConfig{Mode: machine.PinInput})
|
||||
// no-op, left for API compatibility
|
||||
}
|
||||
|
||||
// ReadDistance returns the distance of the object in mm
|
||||
@@ -45,11 +46,11 @@ func (d *Device) ReadDistance() int32 {
|
||||
// ReadPulse returns the time of the pulse (roundtrip) in microseconds
|
||||
func (d *Device) ReadPulse() int32 {
|
||||
t := time.Now()
|
||||
d.trigger.Low()
|
||||
d.trigger.Set(false)
|
||||
time.Sleep(2 * time.Microsecond)
|
||||
d.trigger.High()
|
||||
d.trigger.Set(true)
|
||||
time.Sleep(10 * time.Microsecond)
|
||||
d.trigger.Low()
|
||||
d.trigger.Set(false)
|
||||
i := uint8(0)
|
||||
for {
|
||||
if d.echo.Get() {
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
//go:build !(baremetal && tinygo)
|
||||
|
||||
package legacy
|
||||
|
||||
import "tinygo.org/x/drivers"
|
||||
|
||||
func PinOutput(pin drivers.PinOutput) drivers.PinOutput {
|
||||
return pin
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
//go:build baremetal && tinygo
|
||||
|
||||
package legacy
|
||||
|
||||
import (
|
||||
"machine"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
func PinOutput(pin drivers.PinOutput) drivers.PinOutput {
|
||||
if p, ok := pin.(machine.Pin); ok {
|
||||
p.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
}
|
||||
return pin
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
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.
|
||||
type Device8 struct {
|
||||
buf [10]byte
|
||||
}
|
||||
|
||||
// clear zeroes Device8's buffers.
|
||||
func (d *Device8) clear() {
|
||||
d.buf = [10]byte{}
|
||||
}
|
||||
|
||||
// I2C methods.
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
@@ -36,7 +36,7 @@ type Configuration struct {
|
||||
MagDataRate uint8
|
||||
}
|
||||
|
||||
var errNotConnected = errors.New("lsm303agr: failed to communicate with either acel or magnet sensor")
|
||||
var errNotConnected = errors.New("lsm303agr: failed to communicate with either accel or magnet sensor")
|
||||
|
||||
// New creates a new LSM303AGR connection. The I2C bus must already be configured.
|
||||
//
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
// Package lsm303dlhc implements a driver for the LSM303dlhc,
|
||||
// a 3 axis accelerometer/magnetic sensor typically available on breakout boards.
|
||||
//
|
||||
// Datasheet: https://www.st.com/resource/en/datasheet/lsm303dlhc.pdf
|
||||
|
||||
package lsm303dlhc // import "tinygo.org/x/drivers/lsm303dlhc"
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
"tinygo.org/x/drivers/internal/legacy"
|
||||
)
|
||||
|
||||
// Device wraps an I2C connection to a LSM303dlhc device.
|
||||
type Device struct {
|
||||
bus drivers.I2C
|
||||
AccelAddress uint8
|
||||
MagAddress uint8
|
||||
AccelPowerMode uint8
|
||||
AccelRange uint8
|
||||
AccelDataRate uint8
|
||||
MagPowerMode uint8
|
||||
MagSystemMode uint8
|
||||
MagDataRate uint8
|
||||
buf [6]uint8
|
||||
}
|
||||
|
||||
// Configuration for LSM303dlhc device.
|
||||
type Configuration struct {
|
||||
AccelPowerMode uint8
|
||||
AccelRange uint8
|
||||
AccelDataRate uint8
|
||||
MagPowerMode uint8
|
||||
MagSystemMode uint8
|
||||
MagDataRate uint8
|
||||
}
|
||||
|
||||
// New creates a new LSM303DLHC connection. The I2C bus must already be configured.
|
||||
// This function only creates the Device object, it does not touch the device.
|
||||
func New(bus drivers.I2C) *Device {
|
||||
return &Device{
|
||||
bus: bus,
|
||||
AccelAddress: ACCEL_ADDRESS,
|
||||
MagAddress: MAG_ADDRESS,
|
||||
}
|
||||
}
|
||||
|
||||
// Configure sets up the LSM303dlhc device for communication.
|
||||
func (d *Device) Configure(cfg Configuration) (err error) {
|
||||
|
||||
if cfg.AccelDataRate != 0 {
|
||||
d.AccelDataRate = cfg.AccelDataRate
|
||||
} else {
|
||||
d.AccelDataRate = ACCEL_DATARATE_100HZ
|
||||
}
|
||||
|
||||
if cfg.AccelPowerMode != 0 {
|
||||
d.AccelPowerMode = cfg.AccelPowerMode
|
||||
} else {
|
||||
d.AccelPowerMode = ACCEL_POWER_NORMAL
|
||||
}
|
||||
|
||||
if cfg.AccelRange != 0 {
|
||||
d.AccelRange = cfg.AccelRange
|
||||
} else {
|
||||
d.AccelRange = ACCEL_RANGE_2G
|
||||
}
|
||||
|
||||
if cfg.MagPowerMode != 0 {
|
||||
d.MagPowerMode = cfg.MagPowerMode
|
||||
} else {
|
||||
d.MagPowerMode = MAG_POWER_NORMAL
|
||||
}
|
||||
|
||||
if cfg.MagDataRate != 0 {
|
||||
d.MagDataRate = cfg.MagDataRate
|
||||
} else {
|
||||
d.MagDataRate = MAG_DATARATE_10HZ
|
||||
}
|
||||
|
||||
if cfg.MagSystemMode != 0 {
|
||||
d.MagSystemMode = cfg.MagSystemMode
|
||||
} else {
|
||||
d.MagSystemMode = MAG_SYSTEM_CONTINUOUS
|
||||
}
|
||||
|
||||
data := d.buf[:1]
|
||||
|
||||
data[0] = byte(d.AccelDataRate<<4 | d.AccelPowerMode | 0x07)
|
||||
err = legacy.WriteRegister(d.bus, uint8(d.AccelAddress), ACCEL_CTRL_REG1_A, data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
data[0] = byte(0x80 | d.AccelRange<<4)
|
||||
err = legacy.WriteRegister(d.bus, uint8(d.AccelAddress), ACCEL_CTRL_REG4_A, data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
data[0] = byte(0xC0)
|
||||
err = legacy.WriteRegister(d.bus, uint8(d.AccelAddress), CRA_REG_M, data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Temperature compensation is on for magnetic sensor
|
||||
data[0] = byte(0x80 | d.MagPowerMode<<4 | d.MagDataRate<<2 | d.MagSystemMode)
|
||||
err = legacy.WriteRegister(d.bus, uint8(d.MagAddress), MAG_MR_REG_M, data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadAcceleration reads the current acceleration from the device and returns
|
||||
// it in µg (micro-gravity). When one of the axes is pointing straight to Earth
|
||||
// and the sensor is not moving the returned value will be around 1000000 or
|
||||
// -1000000.
|
||||
func (d *Device) ReadAcceleration() (x, y, z int32, err error) {
|
||||
data := d.buf[:6]
|
||||
err = legacy.ReadRegister(d.bus, uint8(d.AccelAddress), ACCEL_OUT_AUTO_INC, data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
rangeFactor := int16(0)
|
||||
switch d.AccelRange {
|
||||
case ACCEL_RANGE_2G:
|
||||
rangeFactor = 1
|
||||
case ACCEL_RANGE_4G:
|
||||
rangeFactor = 2
|
||||
case ACCEL_RANGE_8G:
|
||||
rangeFactor = 4
|
||||
case ACCEL_RANGE_16G:
|
||||
rangeFactor = 12 // the readings in 16G are a bit lower
|
||||
}
|
||||
|
||||
x = int32(int32(int16((uint16(data[1])<<8|uint16(data[0])))>>4*rangeFactor) * 1000000 / 1024)
|
||||
y = int32(int32(int16((uint16(data[3])<<8|uint16(data[2])))>>4*rangeFactor) * 1000000 / 1024)
|
||||
z = int32(int32(int16((uint16(data[5])<<8|uint16(data[4])))>>4*rangeFactor) * 1000000 / 1024)
|
||||
return
|
||||
}
|
||||
|
||||
// ReadPitchRoll reads the current pitch and roll angles from the device and
|
||||
// returns it in micro-degrees. When the z axis is pointing straight to Earth
|
||||
// the returned values of pitch and roll would be zero.
|
||||
func (d *Device) ReadPitchRoll() (pitch, roll int32, err error) {
|
||||
|
||||
x, y, z, err := d.ReadAcceleration()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
xf, yf, zf := float64(x), float64(y), float64(z)
|
||||
pitch = int32((math.Round(math.Atan2(yf, math.Sqrt(math.Pow(xf, 2)+math.Pow(zf, 2)))*(180/math.Pi)*100) / 100) * 1000000)
|
||||
roll = int32((math.Round(math.Atan2(xf, math.Sqrt(math.Pow(yf, 2)+math.Pow(zf, 2)))*(180/math.Pi)*100) / 100) * 1000000)
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
// ReadMagneticField reads the current magnetic field from the device and returns
|
||||
// it in mG (milligauss). 1 mG = 0.1 µT (microtesla).
|
||||
func (d *Device) ReadMagneticField() (x, y, z int32, err error) {
|
||||
|
||||
if d.MagSystemMode == MAG_SYSTEM_SINGLE {
|
||||
cmd := d.buf[:1]
|
||||
cmd[0] = byte(0x80 | d.MagPowerMode<<4 | d.MagDataRate<<2 | d.MagSystemMode)
|
||||
err = legacy.WriteRegister(d.bus, uint8(d.MagAddress), MAG_MR_REG_M, cmd)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
data := d.buf[0:6]
|
||||
legacy.ReadRegister(d.bus, uint8(d.MagAddress), MAG_OUT_AUTO_INC, data)
|
||||
|
||||
x = int32(int16((uint16(data[1])<<8 | uint16(data[0]))))
|
||||
y = int32(int16((uint16(data[3])<<8 | uint16(data[2]))))
|
||||
z = int32(int16((uint16(data[5])<<8 | uint16(data[4]))))
|
||||
return
|
||||
}
|
||||
|
||||
// ReadCompass reads the current compass heading from the device and returns
|
||||
// it in micro-degrees. When the z axis is pointing straight to Earth and
|
||||
// the y axis is pointing to North, the heading would be zero.
|
||||
//
|
||||
// However, the heading may be off due to electronic compasses would be effected
|
||||
// by strong magnetic fields and require constant calibration.
|
||||
func (d *Device) ReadCompass() (h int32, err error) {
|
||||
|
||||
x, y, _, err := d.ReadMagneticField()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
xf, yf := float64(x), float64(y)
|
||||
h = int32(float32((180/math.Pi)*math.Atan2(yf, xf)) * 1000000)
|
||||
return
|
||||
}
|
||||
|
||||
// ReadTemperature returns the temperature in Celsius milli degrees (°C/1000)
|
||||
func (d *Device) ReadTemperature() (t int32, err error) {
|
||||
|
||||
data := d.buf[:2]
|
||||
err = legacy.ReadRegister(d.bus, uint8(d.MagAddress), TEMP_OUT_AUTO_INC, data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
r := int16((uint16(data[1])<<8 | uint16(data[0]))) >> 4 // temperature offset from 25 °C
|
||||
t = 25000 + int32((float32(r)/8)*1000)
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package lsm303dlhc
|
||||
|
||||
const (
|
||||
|
||||
// Constants/addresses used for I2C.
|
||||
ACCEL_ADDRESS = 0x19
|
||||
MAG_ADDRESS = 0x1E
|
||||
|
||||
// i2C 8-bit subaddress (SUB): the 7 LSb represent the actual register address
|
||||
// while the MSB enables address auto increment.
|
||||
// If the MSb of the SUB field is 1, the SUB (register address) is
|
||||
// automatically increased to allow multiple data read/writes.
|
||||
ADDR_AUTO_INC_MASK = 0x80
|
||||
|
||||
// accelerometer registers.
|
||||
ACCEL_CTRL_REG1_A = 0x20
|
||||
ACCEL_CTRL_REG4_A = 0x23
|
||||
ACCEL_OUT_X_L_A = 0x28
|
||||
ACCEL_OUT_X_H_A = 0x29
|
||||
ACCEL_OUT_Y_L_A = 0x2A
|
||||
ACCEL_OUT_Y_H_A = 0x2B
|
||||
ACCEL_OUT_Z_L_A = 0x2C
|
||||
ACCEL_OUT_Z_H_A = 0x2D
|
||||
ACCEL_OUT_AUTO_INC = ACCEL_OUT_X_L_A | ADDR_AUTO_INC_MASK
|
||||
|
||||
// magnetic sensor registers.
|
||||
MAG_MR_REG_M = 0x02
|
||||
MAG_OUT_X_L_M = 0x68
|
||||
MAG_OUT_X_H_M = 0x69
|
||||
MAG_OUT_Y_L_M = 0x6A
|
||||
MAG_OUT_Y_H_M = 0x6B
|
||||
MAG_OUT_Z_L_M = 0x6C
|
||||
MAG_OUT_Z_H_M = 0x6D
|
||||
MAG_OUT_AUTO_INC = MAG_OUT_X_L_M | ADDR_AUTO_INC_MASK
|
||||
|
||||
// temperature sensor registers.
|
||||
CRA_REG_M = 0x80
|
||||
TEMP_OUT_L_M = 0x32
|
||||
TEMP_OUT_H_M = 0x31
|
||||
TEMP_OUT_AUTO_INC = TEMP_OUT_L_M | ADDR_AUTO_INC_MASK
|
||||
|
||||
// accelerometer power mode.
|
||||
ACCEL_POWER_NORMAL = 0x00 // default
|
||||
ACCEL_POWER_LOW = 0x08
|
||||
|
||||
// accelerometer range.
|
||||
ACCEL_RANGE_2G = 0x00 // default
|
||||
ACCEL_RANGE_4G = 0x01
|
||||
ACCEL_RANGE_8G = 0x02
|
||||
ACCEL_RANGE_16G = 0x03
|
||||
|
||||
// accelerometer data rate.
|
||||
ACCEL_DATARATE_1HZ = 0x01
|
||||
ACCEL_DATARATE_10HZ = 0x02
|
||||
ACCEL_DATARATE_25HZ = 0x03
|
||||
ACCEL_DATARATE_50HZ = 0x04
|
||||
ACCEL_DATARATE_100HZ = 0x05 // default
|
||||
ACCEL_DATARATE_200HZ = 0x06
|
||||
ACCEL_DATARATE_400HZ = 0x07
|
||||
ACCEL_DATARATE_1344HZ = 0x09 // 5376Hz in low-power mode
|
||||
|
||||
// magnetic sensor power mode.
|
||||
MAG_POWER_NORMAL = 0x00 // default
|
||||
MAG_POWER_LOW = 0x01
|
||||
|
||||
// magnetic sensor operate mode.
|
||||
MAG_SYSTEM_CONTINUOUS = 0x00 // default
|
||||
MAG_SYSTEM_SINGLE = 0x01
|
||||
|
||||
// magnetic sensor data rate
|
||||
MAG_DATARATE_10HZ = 0x00 // default
|
||||
MAG_DATARATE_20HZ = 0x01
|
||||
MAG_DATARATE_50HZ = 0x02
|
||||
MAG_DATARATE_100HZ = 0x03
|
||||
)
|
||||
+35
-24
@@ -8,7 +8,6 @@ import (
|
||||
"errors"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
"tinygo.org/x/drivers/internal/legacy"
|
||||
)
|
||||
|
||||
type AccelRange uint8
|
||||
@@ -26,7 +25,7 @@ type Device struct {
|
||||
accelSampleRate AccelSampleRate
|
||||
gyroRange GyroRange
|
||||
gyroSampleRate GyroSampleRate
|
||||
buf [6]uint8
|
||||
buf [7]uint8 // up to 6 bytes for read + 1 byte for the register address
|
||||
}
|
||||
|
||||
// Configuration for LSM6DS3TR device.
|
||||
@@ -84,30 +83,20 @@ func (d *Device) doConfigure(cfg Configuration) (err error) {
|
||||
d.gyroSampleRate = GYRO_SR_104
|
||||
}
|
||||
|
||||
data := d.buf[:1]
|
||||
|
||||
// Configure accelerometer
|
||||
data[0] = uint8(d.accelRange) | uint8(d.accelSampleRate)
|
||||
err = legacy.WriteRegister(d.bus, uint8(d.Address), CTRL1_XL, data)
|
||||
err = d.writeByte(CTRL1_XL, uint8(d.accelRange)|uint8(d.accelSampleRate))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Set ODR bit
|
||||
err = legacy.ReadRegister(d.bus, uint8(d.Address), CTRL4_C, data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
data[0] = data[0] &^ BW_SCAL_ODR_ENABLED
|
||||
data[0] |= BW_SCAL_ODR_ENABLED
|
||||
err = legacy.WriteRegister(d.bus, uint8(d.Address), CTRL4_C, data)
|
||||
// Enable ODR scaling
|
||||
err = d.setBits(CTRL4_C, BW_SCAL_ODR_ENABLED)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Configure gyroscope
|
||||
data[0] = uint8(d.gyroRange) | uint8(d.gyroSampleRate)
|
||||
err = legacy.WriteRegister(d.bus, uint8(d.Address), CTRL2_G, data)
|
||||
err = d.writeByte(CTRL2_G, uint8(d.gyroRange)|uint8(d.gyroSampleRate))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -118,8 +107,10 @@ func (d *Device) doConfigure(cfg Configuration) (err error) {
|
||||
// Connected returns whether a LSM6DS3TR has been found.
|
||||
// It does a "who am I" request and checks the response.
|
||||
func (d *Device) Connected() bool {
|
||||
data := d.buf[:1]
|
||||
legacy.ReadRegister(d.bus, uint8(d.Address), WHO_AM_I, data)
|
||||
data, err := d.readBytes(WHO_AM_I, 1)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return data[0] == 0x6A
|
||||
}
|
||||
|
||||
@@ -128,8 +119,7 @@ func (d *Device) Connected() bool {
|
||||
// and the sensor is not moving the returned value will be around 1000000 or
|
||||
// -1000000.
|
||||
func (d *Device) ReadAcceleration() (x, y, z int32, err error) {
|
||||
data := d.buf[:6]
|
||||
err = legacy.ReadRegister(d.bus, uint8(d.Address), OUTX_L_XL, data)
|
||||
data, err := d.readBytes(OUTX_L_XL, 6)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -153,8 +143,7 @@ func (d *Device) ReadAcceleration() (x, y, z int32, err error) {
|
||||
// rotation along one axis and while doing so integrate all values over time,
|
||||
// you would get a value close to 360000000.
|
||||
func (d *Device) ReadRotation() (x, y, z int32, err error) {
|
||||
data := d.buf[:6]
|
||||
err = legacy.ReadRegister(d.bus, uint8(d.Address), OUTX_L_G, data)
|
||||
data, err := d.readBytes(OUTX_L_G, 6)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -177,8 +166,7 @@ func (d *Device) ReadRotation() (x, y, z int32, err error) {
|
||||
|
||||
// ReadTemperature returns the temperature in celsius milli degrees (°C/1000)
|
||||
func (d *Device) ReadTemperature() (t int32, err error) {
|
||||
data := d.buf[:2]
|
||||
err = legacy.ReadRegister(d.bus, uint8(d.Address), OUT_TEMP_L, data)
|
||||
data, err := d.readBytes(OUT_TEMP_L, 2)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -187,3 +175,26 @@ func (d *Device) ReadTemperature() (t int32, err error) {
|
||||
t = 25000 + (int32(int16((int16(data[1])<<8)|int16(data[0])))*125)/32
|
||||
return
|
||||
}
|
||||
|
||||
func (d *Device) readBytes(reg, size uint8) ([]byte, error) {
|
||||
d.buf[0] = reg
|
||||
err := d.bus.Tx(d.Address, d.buf[0:1], d.buf[1:size+1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d.buf[1 : size+1], nil
|
||||
}
|
||||
|
||||
func (d *Device) writeByte(reg, value uint8) error {
|
||||
d.buf[0] = reg
|
||||
d.buf[1] = value
|
||||
return d.bus.Tx(d.Address, d.buf[0:2], nil)
|
||||
}
|
||||
|
||||
func (d *Device) setBits(reg, bits uint8) error {
|
||||
data, err := d.readBytes(reg, 1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return d.writeByte(reg, (data[0]&^bits)|bits)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package drivers
|
||||
|
||||
// Pin types abstract the underlying implementation of a pin,
|
||||
// allowing the use of different hardware or libraries without changing driver code.
|
||||
//
|
||||
// Note, pin mode functionality is not part of the Pin interface.
|
||||
// Client code is responsible for configuring pin modes correctly.
|
||||
// This can be done either before passing the pin to a driver constructor
|
||||
// or by ensuring correct pin mode is set when Get or Set methods are called.
|
||||
// See rpio package for an example of a pin implementation that does this.
|
||||
//
|
||||
// Drivers that used to configure output pin mode in their constructors can use
|
||||
// legacy.PinOutput() wrapper to keep the same behavior for machine.Pin.
|
||||
// See internal/legacy/pinlegacy_tinygo.go and internal/legacy/pinlegacy_generic.go for details.
|
||||
//
|
||||
// All new drivers are encouraged to not configure pin modes in their constructors and
|
||||
// do not depend on either machine package or legacy wrappers.
|
||||
|
||||
// PinInput is an interface for reading the state of a pin.
|
||||
type PinInput interface {
|
||||
Get() bool
|
||||
}
|
||||
|
||||
// PinOutput is an interface for setting the state of a pin.
|
||||
type PinOutput interface {
|
||||
Set(bool)
|
||||
}
|
||||
|
||||
// Pin is an interface for a pin that can be both input and output.
|
||||
type Pin interface {
|
||||
PinInput
|
||||
PinOutput
|
||||
}
|
||||
|
||||
// PinGet is a function type for getting the state of a pin
|
||||
// Shall be used in high-performance drivers to avoid interface call overhead.
|
||||
type PinGet func() bool
|
||||
|
||||
// PinSet is a function type for setting the state of a pin.
|
||||
// Shall be used in high-performance drivers to avoid interface call overhead.
|
||||
type PinSet func(bool)
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
// Implementation of the Pin interface for the Raspberry Pi GPIO pins.
|
||||
// Depends on the go-rpio library.
|
||||
// Configures pin modes automatically when Get or Set methods are called.
|
||||
|
||||
package rpio // import "tinygo.org/x/drivers/rpio"
|
||||
|
||||
import go_rpio "github.com/stianeikeland/go-rpio/v4"
|
||||
|
||||
type Pin struct {
|
||||
modeSet bool
|
||||
Mode go_rpio.Mode
|
||||
Pin go_rpio.Pin
|
||||
}
|
||||
|
||||
func NewPin(pinNumber int) *Pin {
|
||||
return &Pin{Pin: go_rpio.Pin(pinNumber)}
|
||||
}
|
||||
|
||||
func (p *Pin) Get() bool {
|
||||
if !p.modeSet || p.Mode != go_rpio.Input {
|
||||
p.Pin.Input()
|
||||
p.Mode = go_rpio.Input
|
||||
}
|
||||
return p.Pin.Read() == go_rpio.High
|
||||
}
|
||||
|
||||
func (p *Pin) Set(high bool) {
|
||||
if !p.modeSet || p.Mode != go_rpio.Output {
|
||||
p.Pin.Output()
|
||||
p.Mode = go_rpio.Output
|
||||
}
|
||||
state := go_rpio.Low
|
||||
if high {
|
||||
state = go_rpio.High
|
||||
}
|
||||
p.Pin.Write(state)
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package rpio // import "tinygo.org/x/drivers/rpio"
|
||||
|
||||
import go_rpio "github.com/stianeikeland/go-rpio/v4"
|
||||
|
||||
type SPI struct {
|
||||
}
|
||||
|
||||
func NewSPI() (s *SPI) {
|
||||
if err := go_rpio.SpiBegin(go_rpio.Spi0); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
go_rpio.SpiSpeed(25_000_000) // 25 MHz
|
||||
go_rpio.SpiChipSelect(0)
|
||||
return &SPI{}
|
||||
}
|
||||
|
||||
func (s *SPI) Tx(w, r []byte) error {
|
||||
data := make([]byte, len(w))
|
||||
copy(data, w)
|
||||
go_rpio.SpiExchange(data)
|
||||
copy(r, data)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SPI) Transfer(b byte) (byte, error) {
|
||||
w := []byte{b}
|
||||
r := make([]byte, len(w))
|
||||
err := s.Tx(w, r)
|
||||
return r[0], err
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package seesaw
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
var errInvalidEncoderNumber = errors.New("invalid encoder choice, 0-15 are supported")
|
||||
|
||||
// GetEncoderPosition returns the absolute position (or delta since the previous call) of the specified rotary encoder.
|
||||
func (d *Device) GetEncoderPosition(encoder uint, asDelta bool) (int32, error) {
|
||||
if encoder >= 16 {
|
||||
return 0, errInvalidEncoderNumber
|
||||
}
|
||||
|
||||
// The function address' upper nibble is the function, the lower nibble selects which encoder to communicate with
|
||||
fnAddr := FunctionAddress(encoder)
|
||||
if asDelta {
|
||||
fnAddr |= FunctionEncoderDelta
|
||||
} else {
|
||||
fnAddr |= FunctionEncoderPosition
|
||||
}
|
||||
|
||||
var buf [4]byte
|
||||
err := d.Read(ModuleEncoderBase, fnAddr, buf[:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return int32(buf[0])<<24 | int32(buf[1])<<16 | int32(buf[2])<<8 | int32(buf[3]), nil
|
||||
}
|
||||
|
||||
// SetEncoderPosition calibrate's the encoder's current absolute position to be whatever the provided position is.
|
||||
func (d *Device) SetEncoderPosition(encoder uint, position int32) error {
|
||||
if encoder >= 16 {
|
||||
return errInvalidEncoderNumber
|
||||
}
|
||||
|
||||
// The function address' upper nibble is the function, the lower nibble selects which encoder to communicate with
|
||||
fnAddr := FunctionEncoderPosition | FunctionAddress(encoder)
|
||||
|
||||
buf := [4]byte{
|
||||
byte(position >> 24),
|
||||
byte(position >> 16),
|
||||
byte(position >> 8),
|
||||
byte(position),
|
||||
}
|
||||
|
||||
return d.Write(ModuleEncoderBase, fnAddr, buf[:])
|
||||
}
|
||||
@@ -98,3 +98,13 @@ const (
|
||||
FunctionKeypadCount FunctionAddress = 0x04
|
||||
FunctionKeypadFifo FunctionAddress = 0x10
|
||||
)
|
||||
|
||||
// encoder module function address registers
|
||||
// these are the defaults for encoder 0, change the lower nibble to address other encoders
|
||||
// see the Device.GetEncoderPosition and SetEncoderPosition methods for examples.
|
||||
const (
|
||||
FunctionEncoderIntenset FunctionAddress = 0x10
|
||||
FunctionEncoderIntenclr FunctionAddress = 0x20
|
||||
FunctionEncoderPosition FunctionAddress = 0x30
|
||||
FunctionEncoderDelta FunctionAddress = 0x40
|
||||
)
|
||||
|
||||
+7
-3
@@ -44,6 +44,7 @@ tinygo build -size short -o ./build/test.hex -target=pyportal ./examples/ili9341
|
||||
tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/lis3dh/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=nano-33-ble ./examples/lps22hb/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/lsm303agr/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=feather-m4 ./examples/lsm303dlhc/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/lsm6ds3/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/mag3110/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/mcp23017/main.go
|
||||
@@ -58,15 +59,17 @@ tinygo build -size short -o ./build/test.hex -target=p1am-100 ./examples/p1am/ma
|
||||
tinygo build -size short -o ./build/test.hex -target=pico ./examples/pca9685/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/pcd8544/setbuffer/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/pcd8544/setpixel/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=feather-rp2040 ./examples/seesaw
|
||||
tinygo build -size short -o ./build/test.hex -target=feather-rp2040 ./examples/seesaw/soil-sensor
|
||||
tinygo build -size short -o ./build/test.hex -target=qtpy-rp2040 ./examples/seesaw/rotary-encoder
|
||||
tinygo build -size short -o ./build/test.hex -target=arduino ./examples/servo
|
||||
tinygo build -size short -o ./build/test.hex -target=pico ./examples/sgp30
|
||||
tinygo build -size short -o ./build/test.hex -target=pybadge ./examples/shifter/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/sht3x/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/sht4x/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/shtc3/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/ssd1306/i2c_128x32/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/ssd1306/spi_128x64/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=xiao-ble ./examples/ssd1306/
|
||||
tinygo build -size short -o ./build/test.hex -target=xiao-rp2040 ./examples/ssd1306/
|
||||
tinygo build -size short -o ./build/test.hex -target=thumby ./examples/ssd1306/
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/ssd1331/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/st7735/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/st7789/main.go
|
||||
@@ -140,6 +143,7 @@ tinygo build -size short -o ./build/test.uf2 -target=pico ./examples/tmc2209/mai
|
||||
tinygo build -size short -o ./build/test.hex -target=pico ./examples/tmc5160/main.go
|
||||
tinygo build -size short -o ./build/test.uf2 -target=nicenano ./examples/sharpmem/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=feather-nrf52840 ./examples/max6675/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=pico ./examples/ens160/main.go
|
||||
# network examples (espat)
|
||||
tinygo build -size short -o ./build/test.hex -target=challenger-rp2040 ./examples/net/ntpclient/
|
||||
# network examples (wifinina)
|
||||
|
||||
+29
-137
@@ -6,11 +6,9 @@ package ssd1306 // import "tinygo.org/x/drivers/ssd1306"
|
||||
import (
|
||||
"errors"
|
||||
"image/color"
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
"tinygo.org/x/drivers/internal/legacy"
|
||||
"tinygo.org/x/drivers/pixel"
|
||||
)
|
||||
|
||||
@@ -23,16 +21,15 @@ type ResetValue [2]byte
|
||||
|
||||
// Device wraps I2C or SPI connection.
|
||||
type Device struct {
|
||||
bus Buser
|
||||
buffer []byte
|
||||
width int16
|
||||
height int16
|
||||
bufferSize int16
|
||||
vccState VccMode
|
||||
canReset bool
|
||||
resetCol ResetValue
|
||||
resetPage ResetValue
|
||||
rotation drivers.Rotation
|
||||
bus Buser
|
||||
buffer []byte
|
||||
width int16
|
||||
height int16
|
||||
vccState VccMode
|
||||
canReset bool
|
||||
resetCol ResetValue
|
||||
resetPage ResetValue
|
||||
rotation drivers.Rotation
|
||||
}
|
||||
|
||||
// Config is the configuration for the display
|
||||
@@ -51,51 +48,15 @@ type Config struct {
|
||||
Rotation drivers.Rotation
|
||||
}
|
||||
|
||||
type I2CBus struct {
|
||||
wire drivers.I2C
|
||||
Address uint16
|
||||
}
|
||||
|
||||
type SPIBus struct {
|
||||
wire drivers.SPI
|
||||
dcPin machine.Pin
|
||||
resetPin machine.Pin
|
||||
csPin machine.Pin
|
||||
}
|
||||
|
||||
type Buser interface {
|
||||
configure() error
|
||||
tx(data []byte, isCommand bool) error
|
||||
setAddress(address uint16) error
|
||||
configure(address uint16, size int16) []byte // configure the bus and return the image buffer to use
|
||||
command(cmd uint8) error // send a command to the display
|
||||
flush() error // send the image to the display, faster than "tx()" in i2c case since avoids slice copy
|
||||
tx(data []byte, isCommand bool) error // generic transmit function
|
||||
}
|
||||
|
||||
type VccMode uint8
|
||||
|
||||
// NewI2C creates a new SSD1306 connection. The I2C wire must already be configured.
|
||||
func NewI2C(bus drivers.I2C) Device {
|
||||
return Device{
|
||||
bus: &I2CBus{
|
||||
wire: bus,
|
||||
Address: Address,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewSPI creates a new SSD1306 connection. The SPI wire must already be configured.
|
||||
func NewSPI(bus drivers.SPI, dcPin, resetPin, csPin machine.Pin) Device {
|
||||
dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
resetPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
return Device{
|
||||
bus: &SPIBus{
|
||||
wire: bus,
|
||||
dcPin: dcPin,
|
||||
resetPin: resetPin,
|
||||
csPin: csPin,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Configure initializes the display with default configuration
|
||||
func (d *Device) Configure(cfg Config) {
|
||||
var zeroReset ResetValue
|
||||
@@ -109,9 +70,6 @@ func (d *Device) Configure(cfg Config) {
|
||||
} else {
|
||||
d.height = 64
|
||||
}
|
||||
if cfg.Address != 0 {
|
||||
d.bus.setAddress(cfg.Address)
|
||||
}
|
||||
if cfg.VccState != 0 {
|
||||
d.vccState = cfg.VccState
|
||||
} else {
|
||||
@@ -127,11 +85,9 @@ func (d *Device) Configure(cfg Config) {
|
||||
} else {
|
||||
d.resetPage = ResetValue{0, uint8(d.height/8) - 1}
|
||||
}
|
||||
d.bufferSize = d.width * d.height / 8
|
||||
d.buffer = make([]byte, d.bufferSize)
|
||||
d.canReset = cfg.Address != 0 || d.width != 128 || d.height != 64 // I2C or not 128x64
|
||||
|
||||
d.bus.configure()
|
||||
d.buffer = d.bus.configure(cfg.Address, d.width*d.height/8)
|
||||
|
||||
time.Sleep(100 * time.Nanosecond)
|
||||
d.Command(DISPLAYOFF)
|
||||
@@ -193,11 +149,22 @@ func (d *Device) Configure(cfg Config) {
|
||||
d.Command(NORMALDISPLAY)
|
||||
d.Command(DEACTIVATE_SCROLL)
|
||||
d.Command(DISPLAYON)
|
||||
|
||||
}
|
||||
|
||||
// Command sends a command to the display
|
||||
func (d *Device) Command(command uint8) {
|
||||
d.bus.command(command)
|
||||
}
|
||||
|
||||
// Tx sends data to the display; if isCommand is false, this also updates the image buffer.
|
||||
func (d *Device) Tx(data []byte, isCommand bool) error {
|
||||
return d.bus.tx(data, isCommand)
|
||||
}
|
||||
|
||||
// ClearBuffer clears the image buffer
|
||||
func (d *Device) ClearBuffer() {
|
||||
for i := int16(0); i < d.bufferSize; i++ {
|
||||
for i := 0; i < len(d.buffer); i++ {
|
||||
d.buffer[i] = 0
|
||||
}
|
||||
}
|
||||
@@ -223,7 +190,7 @@ func (d *Device) Display() error {
|
||||
d.Command(d.resetPage[1])
|
||||
}
|
||||
|
||||
return d.Tx(d.buffer, false)
|
||||
return d.bus.flush()
|
||||
}
|
||||
|
||||
// SetPixel enables or disables a pixel in the buffer
|
||||
@@ -252,12 +219,10 @@ func (d *Device) GetPixel(x int16, y int16) bool {
|
||||
|
||||
// SetBuffer changes the whole buffer at once
|
||||
func (d *Device) SetBuffer(buffer []byte) error {
|
||||
if int16(len(buffer)) != d.bufferSize {
|
||||
if len(buffer) != len(d.buffer) {
|
||||
return errBufferSize
|
||||
}
|
||||
for i := int16(0); i < d.bufferSize; i++ {
|
||||
d.buffer[i] = buffer[i]
|
||||
}
|
||||
copy(d.buffer, buffer)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -266,79 +231,6 @@ func (d *Device) GetBuffer() []byte {
|
||||
return d.buffer
|
||||
}
|
||||
|
||||
// Command sends a command to the display
|
||||
func (d *Device) Command(command uint8) {
|
||||
d.bus.tx([]byte{command}, true)
|
||||
}
|
||||
|
||||
// setAddress sets the address to the I2C bus
|
||||
func (b *I2CBus) setAddress(address uint16) error {
|
||||
b.Address = address
|
||||
return nil
|
||||
}
|
||||
|
||||
// setAddress does nothing, but it's required to avoid reflection
|
||||
func (b *SPIBus) setAddress(address uint16) error {
|
||||
// do nothing
|
||||
println("trying to Configure an address on a SPI device")
|
||||
return nil
|
||||
}
|
||||
|
||||
// configure does nothing, but it's required to avoid reflection
|
||||
func (b *I2CBus) configure() error { return nil }
|
||||
|
||||
// configure configures some pins with the SPI bus
|
||||
func (b *SPIBus) configure() error {
|
||||
b.csPin.Low()
|
||||
b.dcPin.Low()
|
||||
b.resetPin.Low()
|
||||
|
||||
b.resetPin.High()
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
b.resetPin.Low()
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
b.resetPin.High()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Tx sends data to the display
|
||||
func (d *Device) Tx(data []byte, isCommand bool) error {
|
||||
return d.bus.tx(data, isCommand)
|
||||
}
|
||||
|
||||
// tx sends data to the display (I2CBus implementation)
|
||||
func (b *I2CBus) tx(data []byte, isCommand bool) error {
|
||||
if isCommand {
|
||||
return legacy.WriteRegister(b.wire, uint8(b.Address), 0x00, data)
|
||||
} else {
|
||||
return legacy.WriteRegister(b.wire, uint8(b.Address), 0x40, data)
|
||||
}
|
||||
}
|
||||
|
||||
// tx sends data to the display (SPIBus implementation)
|
||||
func (b *SPIBus) tx(data []byte, isCommand bool) error {
|
||||
var err error
|
||||
|
||||
if isCommand {
|
||||
b.csPin.High()
|
||||
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
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package ssd1306
|
||||
|
||||
import (
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
type I2CBus struct {
|
||||
wire drivers.I2C
|
||||
address uint16
|
||||
buffer []byte // buffer to avoid heap allocations
|
||||
}
|
||||
|
||||
// NewI2C creates a new SSD1306 connection. The I2C wire must already be configured.
|
||||
func NewI2C(bus drivers.I2C) *Device {
|
||||
return &Device{
|
||||
bus: &I2CBus{
|
||||
wire: bus,
|
||||
address: Address,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// configure address for the I2C bus and allocate the buffer
|
||||
func (b *I2CBus) configure(address uint16, size int16) []byte {
|
||||
if address != 0 {
|
||||
b.address = address
|
||||
}
|
||||
b.buffer = make([]byte, size+2) // +1 for the mode and +1 for a command
|
||||
return b.buffer[2:] // return the image buffer
|
||||
}
|
||||
|
||||
// command sends a command to the display
|
||||
func (b *I2CBus) command(cmd uint8) error {
|
||||
b.buffer[0] = 0x00 // Command mode
|
||||
b.buffer[1] = cmd
|
||||
return b.wire.Tx(b.address, b.buffer[:2], nil)
|
||||
}
|
||||
|
||||
// flush sends the image to the display
|
||||
func (b *I2CBus) flush() error {
|
||||
b.buffer[1] = 0x40 // Data mode
|
||||
return b.wire.Tx(b.address, b.buffer[1:], nil)
|
||||
}
|
||||
|
||||
// tx sends data to the display
|
||||
func (b *I2CBus) tx(data []byte, isCommand bool) error {
|
||||
if isCommand {
|
||||
return b.command(data[0])
|
||||
}
|
||||
copy(b.buffer[2:], data)
|
||||
return b.flush()
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package ssd1306
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
"tinygo.org/x/drivers/internal/legacy"
|
||||
)
|
||||
|
||||
type SPIBus struct {
|
||||
wire drivers.SPI
|
||||
dcPin drivers.PinOutput
|
||||
resetPin drivers.PinOutput
|
||||
csPin drivers.PinOutput
|
||||
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 drivers.PinOutput) *Device {
|
||||
return &Device{
|
||||
bus: &SPIBus{
|
||||
wire: bus,
|
||||
dcPin: legacy.PinOutput(dcPin),
|
||||
resetPin: legacy.PinOutput(resetPin),
|
||||
csPin: legacy.PinOutput(csPin),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// configure pins with the SPI bus and allocate the buffer
|
||||
func (b *SPIBus) configure(address uint16, size int16) []byte {
|
||||
b.csPin.Set(false)
|
||||
b.dcPin.Set(false)
|
||||
b.resetPin.Set(false)
|
||||
|
||||
b.resetPin.Set(true)
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
b.resetPin.Set(false)
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
b.resetPin.Set(true)
|
||||
|
||||
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.Set(true)
|
||||
b.dcPin.Set(!isCommand)
|
||||
b.csPin.Set(false)
|
||||
err := b.wire.Tx(data, nil)
|
||||
b.csPin.Set(true)
|
||||
return err
|
||||
}
|
||||
+17
-21
@@ -5,12 +5,12 @@ package st7735 // import "tinygo.org/x/drivers/st7735"
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"errors"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
"tinygo.org/x/drivers/internal/legacy"
|
||||
"tinygo.org/x/drivers/pixel"
|
||||
)
|
||||
|
||||
@@ -39,10 +39,10 @@ type Device = DeviceOf[pixel.RGB565BE]
|
||||
// formats.
|
||||
type DeviceOf[T Color] struct {
|
||||
bus drivers.SPI
|
||||
dcPin machine.Pin
|
||||
resetPin machine.Pin
|
||||
csPin machine.Pin
|
||||
blPin machine.Pin
|
||||
dcPin drivers.PinOutput
|
||||
resetPin drivers.PinOutput
|
||||
csPin drivers.PinOutput
|
||||
blPin drivers.PinOutput
|
||||
width int16
|
||||
height int16
|
||||
columnOffset int16
|
||||
@@ -65,23 +65,19 @@ type Config struct {
|
||||
}
|
||||
|
||||
// New creates a new ST7735 connection. The SPI wire must already be configured.
|
||||
func New(bus drivers.SPI, resetPin, dcPin, csPin, blPin machine.Pin) Device {
|
||||
func New(bus drivers.SPI, resetPin, dcPin, csPin, blPin drivers.PinOutput) Device {
|
||||
return NewOf[pixel.RGB565BE](bus, resetPin, dcPin, csPin, blPin)
|
||||
}
|
||||
|
||||
// NewOf creates a new ST7735 connection with a particular pixel format. The SPI
|
||||
// wire must already be configured.
|
||||
func NewOf[T Color](bus drivers.SPI, resetPin, dcPin, csPin, blPin machine.Pin) DeviceOf[T] {
|
||||
dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
resetPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
blPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
func NewOf[T Color](bus drivers.SPI, resetPin, dcPin, csPin, blPin drivers.PinOutput) DeviceOf[T] {
|
||||
return DeviceOf[T]{
|
||||
bus: bus,
|
||||
dcPin: dcPin,
|
||||
resetPin: resetPin,
|
||||
csPin: csPin,
|
||||
blPin: blPin,
|
||||
dcPin: legacy.PinOutput(dcPin),
|
||||
resetPin: legacy.PinOutput(resetPin),
|
||||
csPin: legacy.PinOutput(csPin),
|
||||
blPin: legacy.PinOutput(blPin),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,11 +110,11 @@ func (d *DeviceOf[T]) Configure(cfg Config) {
|
||||
d.batchData = pixel.NewImage[T](int(d.batchLength), 1)
|
||||
|
||||
// reset the device
|
||||
d.resetPin.High()
|
||||
d.resetPin.Set(true)
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
d.resetPin.Low()
|
||||
d.resetPin.Set(false)
|
||||
time.Sleep(20 * time.Millisecond)
|
||||
d.resetPin.High()
|
||||
d.resetPin.Set(true)
|
||||
time.Sleep(150 * time.Millisecond)
|
||||
|
||||
// Common initialization
|
||||
@@ -226,7 +222,7 @@ func (d *DeviceOf[T]) Configure(cfg Config) {
|
||||
|
||||
d.SetRotation(d.rotation)
|
||||
|
||||
d.blPin.High()
|
||||
d.blPin.Set(true)
|
||||
}
|
||||
|
||||
// Display does nothing, there's no buffer as it might be too big for some boards
|
||||
@@ -438,9 +434,9 @@ func (d *DeviceOf[T]) Size() (w, h int16) {
|
||||
// EnableBacklight enables or disables the backlight
|
||||
func (d *DeviceOf[T]) EnableBacklight(enable bool) {
|
||||
if enable {
|
||||
d.blPin.High()
|
||||
d.blPin.Set(true)
|
||||
} else {
|
||||
d.blPin.Low()
|
||||
d.blPin.Set(false)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
//go:build baremetal && tinygo
|
||||
|
||||
package tinygo // import "tinygo.org/x/drivers/tinygo"
|
||||
|
||||
import "machine"
|
||||
|
||||
// tinygo.Pin wraps machine.Pin to ensure correct pin mode is set when Get or Set methods are called.
|
||||
|
||||
type Pin struct {
|
||||
pin machine.Pin
|
||||
mode machine.PinMode
|
||||
|
||||
modeSet bool
|
||||
inputMode machine.PinMode
|
||||
}
|
||||
|
||||
func New(pin machine.Pin) *Pin {
|
||||
return &Pin{pin: pin, inputMode: machine.PinInput}
|
||||
}
|
||||
|
||||
func NewWithInputMode(pin machine.Pin, inputMode machine.PinMode) *Pin {
|
||||
return &Pin{pin: pin, inputMode: inputMode}
|
||||
}
|
||||
|
||||
func (p *Pin) Get() bool {
|
||||
if !p.modeSet || p.mode != p.inputMode {
|
||||
p.pin.Configure(machine.PinConfig{Mode: p.inputMode})
|
||||
p.mode = machine.PinInput
|
||||
}
|
||||
return p.pin.Get()
|
||||
}
|
||||
|
||||
func (p *Pin) Set(high bool) {
|
||||
if !p.modeSet || p.mode != machine.PinOutput {
|
||||
p.pin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
p.mode = machine.PinOutput
|
||||
}
|
||||
p.pin.Set(high)
|
||||
}
|
||||
+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.32.0"
|
||||
const Version = "0.33.0"
|
||||
|
||||
@@ -931,6 +931,356 @@ void ws2812_writeByte125(char c, uint32_t *portSet, uint32_t *portClear, uint32_
|
||||
[portClear]"m"(*portClear));
|
||||
}
|
||||
|
||||
__attribute__((always_inline))
|
||||
void ws2812_writeByte150(char c, uint32_t *portSet, uint32_t *portClear, uint32_t maskSet, uint32_t maskClear) {
|
||||
// Timings:
|
||||
// T0H: 53 - 55 cycles or 353.3ns - 366.7ns
|
||||
// T1H: 158 - 160 cycles or 1053.3ns - 1066.7ns
|
||||
// TLD: 173 - cycles or 1153.3ns -
|
||||
uint32_t value = (uint32_t)c << 24;
|
||||
char i = 8;
|
||||
__asm__ __volatile__(
|
||||
"1: @ send_bit\n"
|
||||
"\t str %[maskSet], %[portSet] @ [2] T0H and T0L start here\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t lsls %[value], #1 @ [1]\n"
|
||||
"\t bcs.n 2f @ [1/3] skip_store\n"
|
||||
"\t str %[maskClear], %[portClear] @ [2] T0H -> T0L transition\n"
|
||||
"\t2: @ skip_store\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t str %[maskClear], %[portClear] @ [2] T1H -> T1L transition\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t subs %[i], #1 @ [1]\n"
|
||||
"\t beq.n 3f @ [1/3] end\n"
|
||||
"\t b 1b @ [1/3] send_bit\n"
|
||||
"\t3: @ end\n"
|
||||
: [value]"+r"(value),
|
||||
[i]"+r"(i)
|
||||
: [maskSet]"r"(maskSet),
|
||||
[portSet]"m"(*portSet),
|
||||
[maskClear]"r"(maskClear),
|
||||
[portClear]"m"(*portClear));
|
||||
}
|
||||
|
||||
__attribute__((always_inline))
|
||||
void ws2812_writeByte168(char c, uint32_t *portSet, uint32_t *portClear, uint32_t maskSet, uint32_t maskClear) {
|
||||
// Timings:
|
||||
@@ -1832,6 +2182,16 @@ func (d Device) writeByte125(c byte) {
|
||||
interrupt.Restore(mask)
|
||||
}
|
||||
|
||||
func (d Device) writeByte150(c byte) {
|
||||
portSet, maskSet := d.Pin.PortMaskSet()
|
||||
portClear, maskClear := d.Pin.PortMaskClear()
|
||||
|
||||
mask := interrupt.Disable()
|
||||
C.ws2812_writeByte150(C.char(c), (*C.uint32_t)(unsafe.Pointer(portSet)), (*C.uint32_t)(unsafe.Pointer(portClear)), C.uint32_t(maskSet), C.uint32_t(maskClear))
|
||||
|
||||
interrupt.Restore(mask)
|
||||
}
|
||||
|
||||
func (d Device) writeByte168(c byte) {
|
||||
portSet, maskSet := d.Pin.PortMaskSet()
|
||||
portClear, maskClear := d.Pin.PortMaskClear()
|
||||
|
||||
+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 168 200
|
||||
//go:generate go run gen-ws2812.go -arch=cortexm 16 48 64 120 125 150 168 200
|
||||
//go:generate go run gen-ws2812.go -arch=tinygoriscv 160 320
|
||||
|
||||
import (
|
||||
|
||||
@@ -31,6 +31,9 @@ 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