Compare commits

...

31 Commits

Author SHA1 Message Date
soypat 0eabf16c36 undo epd2in66b Configure method deletion 2025-11-10 16:10:07 -03:00
soypat f9db7a6d0f fix type 2025-11-10 15:58:48 -03:00
soypat d6a420bb08 add missing easytepper_go.go file 2025-11-10 15:55:27 -03:00
soypat c7229ddba6 bring #753 changes over to new pin HAL 2025-11-10 15:43:56 -03:00
sago35 744fda5eec fix: correct logic error in image size checks in pixel's tests (Monochrome) 2025-11-10 12:28:58 +01:00
sago35 027c91272e pixel: correct RGB555 to RGBA conversion logic 2025-11-10 12:28:58 +01:00
sago35 5847506ba6 Add TestImageRGB888 and TestImageRGB555 2025-11-10 12:28:58 +01:00
sago35 e35e6b8e13 fix: correct logic error in image size checks in pixel's tests 2025-11-10 12:28:58 +01:00
Ron Evans 408851a9f5 si5351: add support for si5351 (#810)
* si5351: add support for si5351

Adds support for the si5351 I2C programmable clock generator using code
from @chiefMarlin which used code from @conotto which somehow never got merged.

Thank you everyone!

Signed-off-by: deadprogram <ron@hybridgroup.com>

* refactor: use regmap instead of legacy package to avoid heap allocations

Signed-off-by: deadprogram <ron@hybridgroup.com>

---------

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

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

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

* ssd1306: extract i2c and spi bus implementations

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

* ssd1306: bring back the lost exported methods

* Adjust examples

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

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

Adds new constants to allow easy configuration of both the ublox device and the PA1010D.
2025-08-10 10:05:18 +02:00
Yurii Soldak 0304d30b78 lsm6ds3tr: avoid unnecessary heap allocations (#766)
* lsm6ds3tr: avoid unnecessary heap allocations
* lsm6ds3tr: use helper functions, for readability
* lsm6ds3tr: return slice of the internal buffer on readBytes
2025-08-10 10:05:18 +02:00
Ron Evans 7de0a0814e Revert "add regmap package to facilitate heapless driver development (#768)" (#776)
This reverts commit 80356fd9d9.
2025-07-14 11:16:56 -03:00
Patricio Whittingslow 80356fd9d9 add regmap package to facilitate heapless driver development (#768) 2025-07-13 09:58:49 -03:00
88 changed files with 3778 additions and 873 deletions
+25
View File
@@ -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**
+7 -3
View File
@@ -5,9 +5,10 @@ package apa102 // import "tinygo.org/x/drivers/apa102"
import (
"image/color"
"machine"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/legacy"
"tinygo.org/x/drivers/internal/pin"
)
const (
@@ -37,8 +38,11 @@ func New(b drivers.SPI) *Device {
// NewSoftwareSPI returns a new APA102 driver that will use a software based
// implementation of the SPI protocol.
func NewSoftwareSPI(sckPin, sdoPin machine.Pin, delay uint32) *Device {
return New(&bbSPI{SCK: sckPin, SDO: sdoPin, Delay: delay})
func NewSoftwareSPI(sckPin, sdoPin pin.Output, delay uint32) *Device {
return New(&bbSPI{SCK: sckPin.Set, SDO: sdoPin.Set, Delay: delay, configurePins: func() {
legacy.ConfigurePinOut(sckPin)
legacy.ConfigurePinOut(sdoPin)
}})
}
// WriteColors writes the given RGBA color slice out using the APA102 protocol.
+12 -6
View File
@@ -1,6 +1,9 @@
package apa102
import "machine"
import (
"tinygo.org/x/drivers/internal/legacy"
"tinygo.org/x/drivers/internal/pin"
)
// bbSPI is a dumb bit-bang implementation of SPI protocol that is hardcoded
// to mode 0 and ignores trying to receive data. Just enough for the APA102.
@@ -8,15 +11,18 @@ import "machine"
// most purposes other than the APA102 package. It might be desirable to make
// this more generic and include it in the TinyGo "machine" package instead.
type bbSPI struct {
SCK machine.Pin
SDO machine.Pin
Delay uint32
SCK pin.OutputFunc
SDO pin.OutputFunc
Delay uint32
configurePins func()
}
// Configure sets up the SCK and SDO pins as outputs and sets them low
func (s *bbSPI) Configure() {
s.SCK.Configure(machine.PinConfig{Mode: machine.PinOutput})
s.SDO.Configure(machine.PinConfig{Mode: machine.PinOutput})
if s.configurePins == nil {
panic(legacy.ErrConfigBeforeInstantiated)
}
s.configurePins()
s.SCK.Low()
s.SDO.Low()
if s.Delay == 0 {
+31 -24
View File
@@ -1,31 +1,36 @@
package bmi160
import (
"machine"
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/legacy"
"tinygo.org/x/drivers/internal/pin"
)
// DeviceSPI is the SPI interface to a BMI160 accelerometer/gyroscope. There is
// also an I2C interface, but it is not yet supported.
type DeviceSPI struct {
// Chip select pin
CSB machine.Pin
csb pin.OutputFunc
buf [7]byte
// SPI bus (requires chip select to be usable).
Bus drivers.SPI
bus drivers.SPI
configurePins func()
}
// 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 pin.Output, spi drivers.SPI) *DeviceSPI {
return &DeviceSPI{
CSB: csb, // chip select
Bus: spi,
csb: csb.Set, // chip select
bus: spi,
configurePins: func() {
legacy.ConfigurePinOut(csb)
},
}
}
@@ -33,9 +38,11 @@ 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()
if d.configurePins == nil {
return legacy.ErrConfigBeforeInstantiated
}
d.configurePins()
d.csb.High()
// The datasheet recommends doing a register read from address 0x7F to get
// SPI communication going:
// > If CSB sees a rising edge after power-up, the BMI160 interface switches
@@ -86,9 +93,9 @@ func (d *DeviceSPI) ReadTemperature() (temperature int32, err error) {
data[0] = 0x80 | reg_TEMPERATURE_0
data[1] = 0
data[2] = 0
d.CSB.Low()
err = d.Bus.Tx(data, data)
d.CSB.High()
d.csb.Low()
err = d.bus.Tx(data, data)
d.csb.High()
if err != nil {
return
}
@@ -123,9 +130,9 @@ func (d *DeviceSPI) ReadAcceleration() (x int32, y int32, z int32, err error) {
for i := 1; i < len(data); i++ {
data[i] = 0
}
d.CSB.Low()
err = d.Bus.Tx(data, data)
d.CSB.High()
d.csb.Low()
err = d.bus.Tx(data, data)
d.csb.High()
if err != nil {
return
}
@@ -153,9 +160,9 @@ func (d *DeviceSPI) ReadRotation() (x int32, y int32, z int32, err error) {
for i := 1; i < len(data); i++ {
data[i] = 0
}
d.CSB.Low()
err = d.Bus.Tx(data, data)
d.CSB.High()
d.csb.Low()
err = d.bus.Tx(data, data)
d.csb.High()
if err != nil {
return
}
@@ -201,9 +208,9 @@ func (d *DeviceSPI) readRegister(address uint8) uint8 {
data := d.buf[:2]
data[0] = 0x80 | address
data[1] = 0
d.CSB.Low()
d.Bus.Tx(data, data)
d.CSB.High()
d.csb.Low()
d.bus.Tx(data, data)
d.csb.High()
return data[1]
}
@@ -217,7 +224,7 @@ func (d *DeviceSPI) writeRegister(address, data uint8) {
buf[0] = address
buf[1] = data
d.CSB.Low()
d.Bus.Tx(buf, buf)
d.CSB.High()
d.csb.Low()
d.bus.Tx(buf, buf)
d.csb.High()
}
+7 -7
View File
@@ -2,22 +2,22 @@
package buzzer // import "tinygo.org/x/drivers/buzzer"
import (
"machine"
"time"
"tinygo.org/x/drivers/internal/pin"
)
// Device wraps a GPIO connection to a buzzer.
type Device struct {
pin machine.Pin
pin pin.OutputFunc
High bool
BPM float64
}
// New returns a new buzzer driver given which pin to use
func New(pin machine.Pin) Device {
func New(pin pin.Output) Device {
return Device{
pin: pin,
pin: 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.pin.High()
l.High = true
return
}
// Off sets the buzzer to a low state.
func (l *Device) Off() (err error) {
l.pin.Set(false)
l.pin.Low()
l.High = false
return
}
+4 -67
View File
@@ -2,9 +2,9 @@
package easystepper // import "tinygo.org/x/drivers/easystepper"
import (
"errors"
"machine"
"time"
"tinygo.org/x/drivers/internal/pin"
)
// StepMode determines the coil sequence used to perform a single step
@@ -30,28 +30,10 @@ func (sm StepMode) stepCount() uint {
}
}
// DeviceConfig contains the configuration data for a single easystepper driver
type DeviceConfig struct {
// Pin1 ... Pin4 determines the pins to configure and use for the device
Pin1, Pin2, Pin3, Pin4 machine.Pin
// StepCount is the number of steps required to perform a full revolution of the stepper motor
StepCount uint
// RPM determines the speed of the stepper motor in 'Revolutions per Minute'
RPM uint
// Mode determines the coil sequence used to perform a single step
Mode StepMode
}
// DualDeviceConfig contains the configuration data for a dual easystepper driver
type DualDeviceConfig struct {
DeviceConfig
// Pin5 ... Pin8 determines the pins to configure and use for the second device
Pin5, Pin6, Pin7, Pin8 machine.Pin
}
// Device holds the pins and the delay between steps
type Device struct {
pins [4]machine.Pin
pins [4]pin.OutputFunc
config func()
stepDelay time.Duration
stepNumber uint8
stepMode StepMode
@@ -62,51 +44,6 @@ type DualDevice struct {
devices [2]*Device
}
// New returns a new single easystepper driver given a DeviceConfig
func New(config DeviceConfig) (*Device, error) {
if config.StepCount == 0 || config.RPM == 0 {
return nil, errors.New("config.StepCount and config.RPM must be > 0")
}
return &Device{
pins: [4]machine.Pin{config.Pin1, config.Pin2, config.Pin3, config.Pin4},
stepDelay: time.Second * 60 / time.Duration((config.StepCount * config.RPM)),
stepMode: config.Mode,
}, nil
}
// Configure configures the pins of the Device
func (d *Device) Configure() {
for _, pin := range d.pins {
pin.Configure(machine.PinConfig{Mode: machine.PinOutput})
}
}
// NewDual returns a new dual easystepper driver given 8 pins, number of steps and rpm
func NewDual(config DualDeviceConfig) (*DualDevice, error) {
// Create the first device
dev1, err := New(config.DeviceConfig)
if err != nil {
return nil, err
}
// Create the second device
config.DeviceConfig.Pin1 = config.Pin5
config.DeviceConfig.Pin2 = config.Pin6
config.DeviceConfig.Pin3 = config.Pin7
config.DeviceConfig.Pin4 = config.Pin8
dev2, err := New(config.DeviceConfig)
if err != nil {
return nil, err
}
// Return composite dual device
return &DualDevice{devices: [2]*Device{dev1, dev2}}, nil
}
// Configure configures the pins of the DualDevice
func (d *DualDevice) Configure() {
d.devices[0].Configure()
d.devices[1].Configure()
}
// Move rotates the motor the number of given steps
// (negative steps will rotate it the opposite direction)
func (d *Device) Move(steps int32) {
+26
View File
@@ -0,0 +1,26 @@
package easystepper
import (
"errors"
"time"
"tinygo.org/x/drivers/internal/pin"
)
func NewCrossPlatform(stepcount, rpm uint, mode StepMode, pins [4]pin.OutputFunc) (*Device, error) {
if stepcount == 0 || rpm == 0 {
return nil, errors.New("zero rpm and/or stepcount")
}
for i := range pins {
if pins[i] == nil {
return nil, errors.New("nil pin")
}
}
d := &Device{
pins: pins,
stepDelay: time.Second * 60 / time.Duration((stepcount * rpm)),
stepMode: mode,
config: func() {},
}
return d, nil
}
+83
View File
@@ -0,0 +1,83 @@
//go:build baremetal
package easystepper
import (
"errors"
"machine"
"time"
"tinygo.org/x/drivers/internal/legacy"
"tinygo.org/x/drivers/internal/pin"
)
// New returns a new single easystepper driver given a DeviceConfig
func New(config DeviceConfig) (*Device, error) {
if config.StepCount == 0 || config.RPM == 0 {
return nil, errors.New("config.StepCount and config.RPM must be > 0")
}
return &Device{
pins: [4]pin.OutputFunc{config.Pin1.Set, config.Pin2.Set, config.Pin3.Set, config.Pin4.Set},
stepDelay: time.Second * 60 / time.Duration((config.StepCount * config.RPM)),
stepMode: config.Mode,
config: func() {
legacy.ConfigurePinOut(config.Pin1)
legacy.ConfigurePinOut(config.Pin2)
legacy.ConfigurePinOut(config.Pin3)
legacy.ConfigurePinOut(config.Pin4)
},
}, nil
}
// Configure configures the pins of the Device
func (d *Device) Configure() {
if d.config == nil {
panic(legacy.ErrConfigBeforeInstantiated)
}
d.config()
}
// Configure configures the pins of the DualDevice
func (d *DualDevice) Configure() {
d.devices[0].Configure()
d.devices[1].Configure()
}
// NewDual returns a new dual easystepper driver given 8 pins, number of steps and rpm
func NewDual(config DualDeviceConfig) (*DualDevice, error) {
// Create the first device
dev1, err := New(config.DeviceConfig)
if err != nil {
return nil, err
}
// Create the second device
config.DeviceConfig.Pin1 = config.Pin5
config.DeviceConfig.Pin2 = config.Pin6
config.DeviceConfig.Pin3 = config.Pin7
config.DeviceConfig.Pin4 = config.Pin8
dev2, err := New(config.DeviceConfig)
if err != nil {
return nil, err
}
// Return composite dual device
return &DualDevice{devices: [2]*Device{dev1, dev2}}, nil
}
// DeviceConfig contains the configuration data for a single easystepper driver
type DeviceConfig struct {
// Pin1 ... Pin4 determines the pins to configure and use for the device
Pin1, Pin2, Pin3, Pin4 machine.Pin
// StepCount is the number of steps required to perform a full revolution of the stepper motor
StepCount uint
// RPM determines the speed of the stepper motor in 'Revolutions per Minute'
RPM uint
// Mode determines the coil sequence used to perform a single step
Mode StepMode
}
// DualDeviceConfig contains the configuration data for a dual easystepper driver
type DualDeviceConfig struct {
DeviceConfig
// Pin5 ... Pin8 determines the pins to configure and use for the second device
Pin5, Pin6, Pin7, Pin8 machine.Pin
}
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build tinygo && (rp2040 || stm32 || k210 || esp32c3 || nrf || sam || (avr && (atmega328p || atmega328pb)))
//go:build tinygo && (rp2040 || rp2350 || stm32 || k210 || esp32c3 || nrf || sam || (avr && (atmega328p || atmega328pb)))
// Implementation based on:
// https://gist.github.com/aykevl/3fc1683ed77bb0a9c07559dfe857304a
+225
View File
@@ -0,0 +1,225 @@
// Package ens160 provides a driver for the ScioSense ENS160 digital gas sensor.
//
// Datasheet: https://www.sciosense.com/wp-content/uploads/2023/12/ENS160-Datasheet.pdf
package ens160
import (
"encoding/binary"
"errors"
"time"
"tinygo.org/x/drivers"
)
const (
defaultTimeout = 30 * time.Millisecond
shortTimeout = 1 * time.Millisecond
)
// Conversion constants for environment data compensation.
const (
kelvinOffsetMilli = 273150 // 273.15 K in milli-units
tempRawFactor = 64 // As per datasheet for TEMP_IN
humRawFactor = 512 // As per datasheet for RH_IN
milliFactor = 1000 // For converting from milli-units
roundingTerm = milliFactor / 2 // For rounding before integer division
)
// validityStrings provides human-readable descriptions for validity flags.
var validityStrings = [...]string{
ValidityNormalOperation: "normal operation",
ValidityWarmUpPhase: "warm-up phase, wait ~3 minutes for valid data",
ValidityInitialStartUpPhase: "initial start-up phase, wait ~1 hour for valid data",
ValidityInvalidOutput: "invalid output",
}
// Device wraps an I2C connection to an ENS160 device.
type Device struct {
bus drivers.I2C // I²C implementation
addr uint16 // 7bit bus address, promoted to uint16 per drivers.I2C
// shadow registers / last measurements
lastTvocPPB uint16
lastEco2PPM uint16
lastAqiUBA uint8
lastValidity uint8 // Store the latest validity status
// preallocated buffers
wbuf [5]byte // longest write: reg + 4bytes (TEMP+RH)
rbuf [5]byte // longest read: DATA burst (5bytes)
}
// New returns a new ENS160 driver.
func New(bus drivers.I2C, addr uint16) *Device {
if addr == 0 {
addr = DefaultAddress
}
return &Device{
bus: bus,
addr: addr,
lastValidity: ValidityInvalidOutput,
}
}
// Connected returns whether a ENS160 has been found.
func (d *Device) Connected() bool {
d.wbuf[0] = regPartID
err := d.bus.Tx(d.addr, d.wbuf[:1], d.rbuf[:2])
return err == nil && d.rbuf[0] == LowPartID && d.rbuf[1] == HighPartID
}
// Configure sets up the device for reading.
func (d *Device) Configure() error {
// 1. Soft-reset. The device will automatically enter IDLE mode.
if err := d.write1(regOpMode, ModeReset); err != nil {
return err
}
time.Sleep(defaultTimeout)
// 2. Clear GPR registers, then go to STANDARD mode.
if err := d.write1(regCommand, cmdClrGPR); err != nil {
return err
}
time.Sleep(defaultTimeout)
if err := d.write1(regOpMode, ModeStandard); err != nil {
return err
}
time.Sleep(defaultTimeout)
return nil
}
// calculateTempRaw converts temperature from milli-degrees Celsius to the sensor's raw format.
func calculateTempRaw(tempMilliC int32) uint16 {
// Clip temperature
const (
minC = -40 * 1000
maxC = 85 * 1000
)
if tempMilliC < minC {
tempMilliC = minC
} else if tempMilliC > maxC {
tempMilliC = maxC
}
// Integer fixed-point conversion to format required by the sensor.
// Formula from datasheet: T_IN = (T_ambient_C + 273.15) * 64
return uint16((((tempMilliC + kelvinOffsetMilli) * tempRawFactor) + roundingTerm) / milliFactor)
}
// calculateHumRaw converts relative humidity from milli-percent to the sensor's raw format.
func calculateHumRaw(rhMilliPct int32) uint16 {
// Clip humidity
if rhMilliPct < 0 {
rhMilliPct = 0
} else if rhMilliPct > 100*1000 {
rhMilliPct = 100 * 1000
}
// Integer fixed-point conversion to format required by the sensor.
// Formula from datasheet: RH_IN = (RH_ambient_% * 512)
return uint16(((rhMilliPct * humRawFactor) + roundingTerm) / milliFactor)
}
// SetEnvDataMilli sets the ambient temperature and humidity for compensation.
//
// tempMilliC is the temperature in milli-degrees Celsius.
// rhMilliPct is the relative humidity in milli-percent.
func (d *Device) SetEnvDataMilli(tempMilliC, rhMilliPct int32) error {
tempRaw := calculateTempRaw(tempMilliC)
humRaw := calculateHumRaw(rhMilliPct)
d.wbuf[0] = regTempIn // start address (autoincrement)
binary.LittleEndian.PutUint16(d.wbuf[1:3], tempRaw)
binary.LittleEndian.PutUint16(d.wbuf[3:5], humRaw)
return d.bus.Tx(d.addr, d.wbuf[:5], nil)
}
// Update refreshes the concentration measurements.
func (d *Device) Update(which drivers.Measurement) error {
if which&drivers.Concentration == 0 {
return nil // nothing requested
}
const maxTries = 1000
var (
status uint8
validity uint8
)
var gotData bool
// Poll DEVICE_STATUS until NEWDAT or timeout
for range maxTries {
var err error
status, err = d.read1(regStatus)
if err != nil {
return err
}
if status&statusSTATER != 0 {
return errors.New("ENS160: error (STATER set)")
}
validity = (status & statusValidityMask) >> statusValidityShift
if status&statusNEWDAT != 0 {
gotData = true
break // Always break when data available
}
time.Sleep(shortTimeout)
}
if !gotData {
return errors.New("ENS160: timeout waiting for NEWDAT")
}
// Burst-read data regardless of validity state
d.wbuf[0] = regAQI
if err := d.bus.Tx(d.addr, d.wbuf[:1], d.rbuf[:5]); err != nil {
return errors.New("ENS160: burst read failed")
}
d.lastAqiUBA = d.rbuf[0]
d.lastTvocPPB = binary.LittleEndian.Uint16(d.rbuf[1:3])
d.lastEco2PPM = binary.LittleEndian.Uint16(d.rbuf[3:5])
d.lastValidity = validity // Store the validity status
return nil
}
// TVOC returns the last totalVOC concentration in partsperbillion.
func (d *Device) TVOC() uint16 { return d.lastTvocPPB }
// ECO2 returns the last equivalent CO₂ concentration in partspermillion.
func (d *Device) ECO2() uint16 { return d.lastEco2PPM }
// AQI returns the last AirQuality Index according to UBA (15).
func (d *Device) AQI() uint8 { return d.lastAqiUBA }
// Validity returns the current operating state of the sensor.
func (d *Device) Validity() uint8 {
return d.lastValidity
}
// ValidityString returns a human-readable string describing the current validity status.
func (d *Device) ValidityString() string {
if int(d.lastValidity) < len(validityStrings) {
return validityStrings[d.lastValidity]
}
return "unknown"
}
// write1 writes a single byte to a register.
func (d *Device) write1(reg, val uint8) error {
d.wbuf[0] = reg
d.wbuf[1] = val
return d.bus.Tx(d.addr, d.wbuf[:2], nil)
}
// read1 reads a single byte from a register.
func (d *Device) read1(reg uint8) (uint8, error) {
d.wbuf[0] = reg
if err := d.bus.Tx(d.addr, d.wbuf[:1], d.rbuf[:1]); err != nil {
return 0, err
}
return d.rbuf[0], nil
}
+54
View File
@@ -0,0 +1,54 @@
package ens160
import (
"testing"
)
func TestCalculateTempRaw(t *testing.T) {
testCases := []struct {
name string
tempMilliC int32
expectedRaw uint16
}{
{"25°C", 25000, 19082},
{"-10.5°C", -10500, 16810},
{"Min temp", -40000, 14922},
{"Below min", -50000, 14922},
{"Max temp", 85000, 22922},
{"Above max", 90000, 22922},
{"Zero", 0, 17482},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
raw := calculateTempRaw(tc.tempMilliC)
if raw != tc.expectedRaw {
t.Errorf("expected %d, got %d", tc.expectedRaw, raw)
}
})
}
}
func TestCalculateHumRaw(t *testing.T) {
testCases := []struct {
name string
rhMilliPct int32
expectedRaw uint16
}{
{"50%", 50000, 25600},
{"0%", 0, 0},
{"100%", 100000, 51200},
{"Below 0%", -10000, 0},
{"Above 100%", 110000, 51200},
{"33.3%", 33300, 17050},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
raw := calculateHumRaw(tc.rhMilliPct)
if raw != tc.expectedRaw {
t.Errorf("expected %d, got %d", tc.expectedRaw, raw)
}
})
}
}
+65
View File
@@ -0,0 +1,65 @@
package ens160
// DefaultAddress is the default I2C address for the ENS160 when the ADDR pin is
// connected to high (3.3V). When connected to low (GND), the address is 0x52.
const DefaultAddress = 0x53
// Registers
const (
regPartID = 0x00
regOpMode = 0x10
regConfig = 0x11
regCommand = 0x12
regTempIn = 0x13
regRhIn = 0x15
regStatus = 0x20
regAQI = 0x21
regTVOC = 0x22
regECO2 = 0x24
regDataT = 0x30
regDataRH = 0x32
regMISR = 0x38
regGPRWrite = 0x40
regGPRRead = 0x48
)
// Operating modes
const (
ModeDeepSleep = 0x00
ModeIdle = 0x01
ModeStandard = 0x02
ModeReset = 0xF0
)
// Status register bits
const (
statusSTATAS = 1 << 7
statusSTATER = 1 << 6
statusValidityMask = 0x0C
statusValidityShift = 2
statusNEWDAT = 1 << 1
statusNEWGPR = 1 << 0
)
// Validity flags
const (
ValidityNormalOperation = 0x00
ValidityWarmUpPhase = 0x01 // need ~3 minutes until valid data
ValidityInitialStartUpPhase = 0x02 // need ~1 hour until valid data
ValidityInvalidOutput = 0x03
)
// Commands
const (
cmdNOP = 0x00
cmdGetAppVer = 0x0E
cmdClrGPR = 0xCC
)
// Part IDs
const (
LowPartID = 0x60
HighPartID = 0x01
)
+56
View File
@@ -0,0 +1,56 @@
// This example demonstrates ENS160 usage.
//
// Wiring:
// - VCC to 3.3V, GND to ground
// - SDA to board SDA, SCL to board SCL
package main
import (
"time"
"machine"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/ens160"
)
func main() {
err := machine.I2C0.Configure(machine.I2CConfig{
Frequency: 400 * machine.KHz,
})
if err != nil {
println("Failed to configure I2C:", err)
}
dev := ens160.New(machine.I2C0, ens160.DefaultAddress)
connected := dev.Connected()
if !connected {
println("ENS160 not detected")
return
}
println("ENS160 detected")
if err := dev.Configure(); err != nil {
println("Failed to configure ENS160:", err)
}
for {
err := dev.Update(drivers.Concentration)
if err != nil {
println("Error reading ENS160: %v\n", err)
time.Sleep(5 * time.Second)
continue
}
println(
"AQI:", dev.AQI(),
"TVOC:", dev.TVOC(),
"eCO2:", dev.ECO2(),
"Validity:", dev.ValidityString(),
)
time.Sleep(2 * time.Second)
}
}
+1 -1
View File
@@ -10,7 +10,7 @@ import (
func main() {
println("GPS I2C Example")
machine.I2C0.Configure(machine.I2CConfig{})
ublox := gps.NewI2C(machine.I2C0)
ublox := gps.NewI2CWithAddress(machine.I2C0, gps.UBLOX_I2C_ADDRESS)
parser := gps.NewParser()
var fix gps.Fix
for {
+49
View File
@@ -0,0 +1,49 @@
package main
import (
"machine"
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/honeyhsc"
)
// Data taken from https://github.com/rodan/honeywell_hsc_ssc_i2c/blob/master/hsc_ssc_i2c.cpp
// these defaults are valid for the HSCMRNN030PA2A3 chip
const (
i2cAddress = 0x28
// 10%
outputMinimum = 0x666
// 90% of 2^14 - 1
outputMax = 0x399A
// min is 0 for sensors that give absolute values
pressureMin = 0
// 30psi (and we want results in millipascals)
// pressureMax = 206842.7
pressureMax = 206843 * 1000
)
func main() {
bus := machine.I2C0
err := bus.Configure(machine.I2CConfig{
Frequency: 400_000, // 100kHz minimum and 400kHz I2C maximum clock. 50 to 800 for SPI.
SDA: machine.I2C0_SDA_PIN,
SCL: machine.I2C0_SCL_PIN,
})
if err != nil {
panic(err.Error())
}
sensor := honeyhsc.NewDevI2C(bus, i2cAddress, outputMinimum, outputMax, pressureMin, pressureMax)
for {
time.Sleep(time.Second)
const measuremask = drivers.Pressure | drivers.Temperature
err := sensor.Update(measuremask)
if err != nil {
println("error updating measurements:", err.Error())
continue
}
P := sensor.Pressure()
T := sensor.Temperature()
println("pressure:", P, "temperature:", T)
}
}
+12 -3
View File
@@ -14,9 +14,18 @@ func main() {
i2c.Configure(machine.I2CConfig{SCL: machine.SCL1_PIN, SDA: machine.SDA1_PIN})
accel := lis3dh.New(i2c)
accel.Address = lis3dh.Address1 // address on the Circuit Playground Express
accel.Configure()
accel.SetRange(lis3dh.RANGE_2_G)
err := accel.Configure(lis3dh.Config{
Address: lis3dh.Address1, // address on the Circuit Playground Express
})
for err != nil {
println("could not configure LIS3DH:", err)
time.Sleep(time.Second)
}
err = accel.SetRange(lis3dh.RANGE_2_G)
for err != nil {
println("could not set acceleration range:", err)
time.Sleep(time.Second)
}
println(accel.Connected())
+58
View File
@@ -0,0 +1,58 @@
package main
import (
"machine"
"time"
"tinygo.org/x/drivers/lsm303dlhc"
)
func main() {
// LSM303DLHC is connected to the I2C0 bus on Adafruit Feather M4 via pins: 20(SDA) and 21(SCL).
machine.I2C0.Configure(machine.I2CConfig{})
sensor := lsm303dlhc.New(machine.I2C0)
//default settings
err := sensor.Configure(lsm303dlhc.Configuration{
AccelPowerMode: lsm303dlhc.ACCEL_POWER_NORMAL,
AccelRange: lsm303dlhc.ACCEL_RANGE_2G,
AccelDataRate: lsm303dlhc.ACCEL_DATARATE_100HZ,
MagPowerMode: lsm303dlhc.MAG_POWER_NORMAL,
MagSystemMode: lsm303dlhc.MAG_SYSTEM_CONTINUOUS,
MagDataRate: lsm303dlhc.MAG_DATARATE_10HZ,
})
if err != nil {
for {
println("Failed to configure", err.Error())
time.Sleep(time.Second)
}
}
for {
accel_x, accel_y, accel_z, err := sensor.ReadAcceleration()
if err != nil {
println("Failed to read accel", err.Error())
}
println("ACCEL_X:", accel_x, " ACCEL_Y:", accel_y, " ACCEL_Z:", accel_z)
mag_x, mag_y, mag_z, err := sensor.ReadMagneticField()
if err != nil {
println("Failed to read mag", err.Error())
}
println("MAG_X:", mag_x, " MAG_Y:", mag_y, " MAG_Z:", mag_z)
pitch, roll, _ := sensor.ReadPitchRoll()
println("Pitch:", float32(pitch), " Roll:", float32(roll))
heading, _ := sensor.ReadCompass()
println("Heading:", float32(heading), "degrees")
temp, _ := sensor.ReadTemperature()
println("Temperature:", float32(temp)/1000, "*C")
println("\n")
time.Sleep(time.Millisecond * 250)
}
}
+35
View File
@@ -0,0 +1,35 @@
package main
import (
"machine"
"time"
"tinygo.org/x/drivers/seesaw"
)
// example reading the position of a rotary encoder (4991) powered by a seesaw
// https://learn.adafruit.com/adafruit-i2c-qt-rotary-encoder/arduino
func main() {
// This assumes you are using an Adafruit QT Py RP2040 for its Stemma QT connector
// https://www.adafruit.com/product/4900
i2c := machine.I2C1
i2c.Configure(machine.I2CConfig{
SCL: machine.I2C1_QT_SCL_PIN,
SDA: machine.I2C1_QT_SDA_PIN,
})
dev := seesaw.New(i2c)
dev.Address = 0x36
for {
time.Sleep(time.Second)
pos, err := dev.GetEncoderPosition(0, false)
if err != nil {
println(err)
continue
}
println(pos)
}
}
+107
View File
@@ -0,0 +1,107 @@
package main
import (
"machine"
"time"
"tinygo.org/x/drivers/si5351"
)
// Simple demo of the SI5351 clock generator.
// This is like the Arduino library example:
// https://github.com/adafruit/Adafruit_Si5351_Library/blob/master/examples/si5351/si5351.ino
// Which will configure the chip with:
// - PLL A at 900mhz
// - PLL B at 616.66667mhz
// - Clock 0 at 112.5mhz, using PLL A as a source divided by 8
// - Clock 1 at 13.5531mhz, using PLL B as a source divided by 45.5
// - Clock 2 at 10.76khz, using PLL B as a source divided by 900 and further divided with an R divider of 64.
func main() {
time.Sleep(5 * time.Second)
println("Si5351 Clockgen Test")
println()
// Configure I2C bus
machine.I2C0.Configure(machine.I2CConfig{})
// Create driver instance
clockgen := si5351.New(machine.I2C0)
// Verify device wired properly
connected, err := clockgen.Connected()
if err != nil {
println("Unable to read device status")
time.Sleep(time.Second)
}
if !connected {
for {
println("Unable to detect si5351 device")
time.Sleep(time.Second)
}
}
// Initialise device
clockgen.Configure()
// Now configue the PLLs and clock outputs.
// The PLLs can be configured with a multiplier and division of the on-board
// 25mhz reference crystal. For example configure PLL A to 900mhz by multiplying
// by 36. This uses an integer multiplier which is more accurate over time
// but allows less of a range of frequencies compared to a fractional
// multiplier shown next.
clockgen.ConfigurePLL(si5351.PLL_A, 36, 0, 1) // Multiply 25mhz by 36
println("PLL A frequency: 900mhz")
// And next configure PLL B to 616.6667mhz by multiplying 25mhz by 24.667 using
// the fractional multiplier configuration. Notice you specify the integer
// multiplier and then a numerator and denominator as separate values, i.e.
// numerator 2 and denominator 3 means 2/3 or 0.667. This fractional
// configuration is susceptible to some jitter over time but can set a larger
// range of frequencies.
clockgen.ConfigurePLL(si5351.PLL_B, 24, 2, 3) // Multiply 25mhz by 24.667 (24 2/3)
println("PLL B frequency: 616.6667mhz")
// Now configure the clock outputs. Each is driven by a PLL frequency as input
// and then further divides that down to a specific frequency.
// Configure clock 0 output to be driven by PLL A divided by 8, so an output
// of 112.5mhz (900mhz / 8). Again this uses the most precise integer division
// but can't set as wide a range of values.
clockgen.ConfigureMultisynth(0, si5351.PLL_A, 8, 0, 1) // Divide by 8 (8 0/1)
println("Clock 0: 112.5mhz")
// Next configure clock 1 to be driven by PLL B divided by 45.5 to get
// 13.5531mhz (616.6667mhz / 45.5). This uses fractional division and again
// notice the numerator and denominator are explicitly specified. This is less
// precise but allows a large range of frequencies.
clockgen.ConfigureMultisynth(1, si5351.PLL_B, 45, 1, 2) // Divide by 45.5 (45 1/2)
println("Clock 1: 13.5531mhz")
// Finally configure clock 2 to be driven by PLL B divided once by 900 to get
// down to 685.15 khz and then further divided by a special R divider that
// divides 685.15 khz by 64 to get a final output of 10.706khz.
clockgen.ConfigureMultisynth(2, si5351.PLL_B, 900, 0, 1) // Divide by 900 (900 0/1)
// Set the R divider, this can be a value of:
// - R_DIV_1: divider of 1
// - R_DIV_2: divider of 2
// - R_DIV_4: divider of 4
// - R_DIV_8: divider of 8
// - R_DIV_16: divider of 16
// - R_DIV_32: divider of 32
// - R_DIV_64: divider of 64
// - R_DIV_128: divider of 128
clockgen.ConfigureRdiv(2, si5351.R_DIV_64)
println("Clock 2: 10.706khz")
// After configuring PLLs and clocks, enable the outputs.
clockgen.EnableOutputs()
for {
time.Sleep(5 * time.Second)
println()
println("Clock 0: 112.5mhz")
println("Clock 1: 13.5531mhz")
println("Clock 2: 10.706khz")
}
}
-51
View File
@@ -1,51 +0,0 @@
package main
import (
"machine"
"image/color"
"time"
"tinygo.org/x/drivers/ssd1306"
)
func main() {
machine.I2C0.Configure(machine.I2CConfig{
Frequency: machine.TWI_FREQ_400KHZ,
})
display := ssd1306.NewI2C(machine.I2C0)
display.Configure(ssd1306.Config{
Address: ssd1306.Address_128_32,
Width: 128,
Height: 32,
})
display.ClearDisplay()
x := int16(0)
y := int16(0)
deltaX := int16(1)
deltaY := int16(1)
for {
pixel := display.GetPixel(x, y)
c := color.RGBA{255, 255, 255, 255}
if pixel {
c = color.RGBA{0, 0, 0, 255}
}
display.SetPixel(x, y, c)
display.Display()
x += deltaX
y += deltaY
if x == 0 || x == 127 {
deltaX = -deltaX
}
if y == 0 || y == 31 {
deltaY = -deltaY
}
time.Sleep(1 * time.Millisecond)
}
}
-60
View File
@@ -1,60 +0,0 @@
// This example shows how to use 128x64 display over I2C
// Tested on Seeeduino XIAO Expansion Board https://wiki.seeedstudio.com/Seeeduino-XIAO-Expansion-Board/
//
// According to manual, I2C address of the display is 0x78, but that's 8-bit address.
// TinyGo operates on 7-bit addresses and respective 7-bit address would be 0x3C, which we use below.
//
// To learn more about different types of I2C addresses, please see following page
// https://www.totalphase.com/support/articles/200349176-7-bit-8-bit-and-10-bit-I2C-Slave-Addressing
package main
import (
"machine"
"image/color"
"time"
"tinygo.org/x/drivers/ssd1306"
)
func main() {
machine.I2C0.Configure(machine.I2CConfig{
Frequency: machine.TWI_FREQ_400KHZ,
})
display := ssd1306.NewI2C(machine.I2C0)
display.Configure(ssd1306.Config{
Address: 0x3C,
Width: 128,
Height: 64,
})
display.ClearDisplay()
x := int16(0)
y := int16(0)
deltaX := int16(1)
deltaY := int16(1)
for {
pixel := display.GetPixel(x, y)
c := color.RGBA{255, 255, 255, 255}
if pixel {
c = color.RGBA{0, 0, 0, 255}
}
display.SetPixel(x, y, c)
display.Display()
x += deltaX
y += deltaY
if x == 0 || x == 127 {
deltaX = -deltaX
}
if y == 0 || y == 63 {
deltaY = -deltaY
}
time.Sleep(1 * time.Millisecond)
}
}
+59
View File
@@ -0,0 +1,59 @@
package main
// This example shows how to use SSD1306 OLED display driver over I2C and SPI.
//
// Check the `newSSD1306Display()` functions for I2C and SPI initializations.
import (
"runtime"
"image/color"
"time"
)
func main() {
display := newSSD1306Display()
display.ClearDisplay()
w, h := display.Size()
x := int16(0)
y := int16(0)
deltaX := int16(1)
deltaY := int16(1)
traceTime := time.Now().UnixMilli() + 1000
frames := 0
ms := runtime.MemStats{}
for {
pixel := display.GetPixel(x, y)
c := color.RGBA{255, 255, 255, 255}
if pixel {
c = color.RGBA{0, 0, 0, 255}
}
display.SetPixel(x, y, c)
display.Display()
x += deltaX
y += deltaY
if x == 0 || x == w-1 {
deltaX = -deltaX
}
if y == 0 || y == h-1 {
deltaY = -deltaY
}
frames++
now := time.Now().UnixMilli()
if now >= traceTime {
runtime.ReadMemStats(&ms)
println("TS", now, "| FPS", frames, "| HeapInuse", ms.HeapInuse)
traceTime = now + 1000
frames = 0
}
}
}
+38
View File
@@ -0,0 +1,38 @@
//go:build xiao_ble
// This initializes SSD1306 OLED display driver over I2C.
//
// Seeed XIAO BLE board + SSD1306 128x32 I2C OLED display.
//
// Wiring:
// - XIAO GND -> OLED GND
// - XIAO 3v3 -> OLED VCC
// - XIAO D4 (SDA) -> OLED SDA
// - XIAO D5 (SCL) -> OLED SCK
//
// For your case:
// - Connect the display to I2C pins on your board.
// - Adjust I2C address and display size as needed.
package main
import (
"machine"
"tinygo.org/x/drivers/ssd1306"
)
func newSSD1306Display() *ssd1306.Device {
machine.I2C0.Configure(machine.I2CConfig{
Frequency: 400 * machine.KHz,
SDA: machine.SDA0_PIN,
SCL: machine.SCL0_PIN,
})
display := ssd1306.NewI2C(machine.I2C0)
display.Configure(ssd1306.Config{
Address: ssd1306.Address_128_32, // or ssd1306.Address
Width: 128,
Height: 32, // or 64
})
return display
}
+27
View File
@@ -0,0 +1,27 @@
//go:build thumby
// This initializes SSD1306 OLED display driver over SPI.
//
// Thumby board has a tiny built-in 72x40 display.
//
// As the display is built-in, no wiring is needed.
package main
import (
"machine"
"tinygo.org/x/drivers/ssd1306"
)
func newSSD1306Display() *ssd1306.Device {
machine.SPI0.Configure(machine.SPIConfig{})
display := ssd1306.NewSPI(machine.SPI0, machine.THUMBY_DC_PIN, machine.THUMBY_RESET_PIN, machine.THUMBY_CS_PIN)
display.Configure(ssd1306.Config{
Width: 72,
Height: 40,
ResetCol: ssd1306.ResetValue{28, 99},
ResetPage: ssd1306.ResetValue{0, 5},
})
return display
}
+40
View File
@@ -0,0 +1,40 @@
//go:build xiao_rp2040
// This initializes SSD1306 OLED display driver over SPI.
//
// Seeed XIAO RP2040 board + SSD1306 128x64 SPI OLED display.
//
// Wiring:
// - XIAO GND -> OLED GND
// - XIAO 3v3 -> OLED VCC
// - XIAO D8 (SCK) -> OLED D0
// - XIAO D10 (SDO) -> OLED D1
// - XIAO D4 -> OLED RES
// - XIAO D5 -> OLED DC
// - XIAO D6 -> OLED CS
//
// For your case:
// - Connect the display to SPI pins on your board.
// - Adjust RES, DC and CS pins as needed.
// - Adjust SPI frequency as needed.
// - Adjust display size as needed.
package main
import (
"machine"
"tinygo.org/x/drivers/ssd1306"
)
func newSSD1306Display() *ssd1306.Device {
machine.SPI0.Configure(machine.SPIConfig{
Frequency: 50 * machine.MHz,
})
display := ssd1306.NewSPI(machine.SPI0, machine.D5, machine.D4, machine.D6)
display.Configure(ssd1306.Config{
Width: 128,
Height: 64,
})
return display
}
-48
View File
@@ -1,48 +0,0 @@
package main
import (
"image/color"
"machine"
"time"
"tinygo.org/x/drivers/ssd1306"
)
func main() {
machine.SPI0.Configure(machine.SPIConfig{
Frequency: 8000000,
})
display := ssd1306.NewSPI(machine.SPI0, machine.P8, machine.P7, machine.P9)
display.Configure(ssd1306.Config{
Width: 128,
Height: 64,
})
display.ClearDisplay()
x := int16(64)
y := int16(32)
deltaX := int16(1)
deltaY := int16(1)
for {
pixel := display.GetPixel(x, y)
c := color.RGBA{255, 255, 255, 255}
if pixel {
c = color.RGBA{0, 0, 0, 255}
}
display.SetPixel(x, y, c)
display.Display()
x += deltaX
y += deltaY
if x == 0 || x == 127 {
deltaX = -deltaX
}
if y == 0 || y == 63 {
deltaY = -deltaY
}
time.Sleep(1 * time.Millisecond)
}
}
-50
View File
@@ -1,50 +0,0 @@
// This example using the SSD1306 OLED display over SPI on the Thumby board
// A very tiny 72x40 display.
package main
import (
"image/color"
"machine"
"time"
"tinygo.org/x/drivers/ssd1306"
)
func main() {
machine.SPI0.Configure(machine.SPIConfig{})
display := ssd1306.NewSPI(machine.SPI0, machine.THUMBY_DC_PIN, machine.THUMBY_RESET_PIN, machine.THUMBY_CS_PIN)
display.Configure(ssd1306.Config{
Width: 72,
Height: 40,
ResetCol: ssd1306.ResetValue{28, 99},
ResetPage: ssd1306.ResetValue{0, 5},
})
display.ClearDisplay()
x := int16(36)
y := int16(20)
deltaX := int16(1)
deltaY := int16(1)
for {
pixel := display.GetPixel(x, y)
c := color.RGBA{255, 255, 255, 255}
if pixel {
c = color.RGBA{0, 0, 0, 255}
}
display.SetPixel(x, y, c)
display.Display()
x += deltaX
y += deltaY
if x == 0 || x == 71 {
deltaX = -deltaX
}
if y == 0 || y == 39 {
deltaY = -deltaY
}
time.Sleep(1 * time.Millisecond)
}
}
+41
View File
@@ -46,6 +46,10 @@ var DefaultDeviceIdentifier = DeviceIdentifierFunc(func(id JedecID) Attrs {
return GD25Q16C()
case 0xC84017:
return GD25Q64C()
case 0x856015:
return P25Q16H()
case 0xEF4014:
return W25Q80DV()
case 0xEF4015:
return W25Q16JVIQ()
case 0xEF4016:
@@ -239,6 +243,24 @@ func GD25Q64C() Attrs {
}
}
// Settings for the Puya P25Q16H 2MiB SPI flash.
// Datasheet: https://files.seeedstudio.com/wiki/github_weiruanexample/Flash_P25Q16H-UXH-IR_Datasheet.pdf
func P25Q16H() Attrs {
return Attrs{
TotalSize: 1 << 21, // 2 MiB
StartUp: 5000 * time.Microsecond,
JedecID: JedecID{0x85, 0x60, 0x15},
MaxClockSpeedMHz: 55,
QuadEnableBitMask: 0x02,
HasSectorProtection: true,
SupportsFastRead: true,
SupportsQSPI: true,
SupportsQSPIWrites: true,
WriteStatusSplit: true,
SingleStatusByte: false,
}
}
// Settings for the Winbond W25Q16JV-IQ 2MiB SPI flash. Note that JV-IM has a
// different .memory_type (0x70) Datasheet:
// https://www.winbond.com/resource-files/w25q16jv%20spi%20revf%2005092017.pdf
@@ -380,6 +402,25 @@ func W25Q80DL() Attrs {
TotalSize: 1 << 20, // 1 MiB
StartUp: 5000 * time.Microsecond,
JedecID: JedecID{0xEF, 0x60, 0x14},
MaxClockSpeedMHz: 80,
QuadEnableBitMask: 0x02,
HasSectorProtection: false,
SupportsFastRead: true,
SupportsQSPI: true,
SupportsQSPIWrites: false,
WriteStatusSplit: false,
SingleStatusByte: false,
}
}
// Settings for the Winbond W25Q80DV 2MiB SPI flash.
// Datasheet:
// https://www.winbond.com/resource-files/w25q80dv%20dl_revh_10022015.pdf
func W25Q80DV() Attrs {
return Attrs{
TotalSize: 1 << 21, // 2 MiB
StartUp: 5000 * time.Microsecond,
JedecID: JedecID{0xEF, 0x40, 0x14},
MaxClockSpeedMHz: 104,
QuadEnableBitMask: 0x02,
HasSectorProtection: false,
+3
View File
@@ -1,3 +1,6 @@
// Guarded because still unsure of how to deal with interrupt drivers.
//go:build tinygo
// Package ft6336 provides a driver for the FT6336 I2C Self-Capacitive touch
// panel controller.
//
+16 -15
View File
@@ -5,12 +5,13 @@ package gc9a01 // import "tinygo.org/x/drivers/gc9a01"
import (
"image/color"
"machine"
"time"
"errors"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/legacy"
"tinygo.org/x/drivers/internal/pin"
)
// Rotation controls the rotation used by the display.
@@ -22,10 +23,10 @@ type FrameRate uint8
// Device wraps an SPI connection.
type Device struct {
bus drivers.SPI
dcPin machine.Pin
resetPin machine.Pin
csPin machine.Pin
blPin machine.Pin
dcPin pin.OutputFunc
resetPin pin.OutputFunc
csPin pin.OutputFunc
blPin pin.OutputFunc
width int16
height int16
columnOffsetCfg int16
@@ -52,17 +53,17 @@ type Config struct {
}
// New creates a new ST7789 connection. The SPI wire must already be configured.
func New(bus drivers.SPI, resetPin, dcPin, csPin, blPin machine.Pin) Device {
resetPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
blPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
func New(bus drivers.SPI, resetPin, dcPin, csPin, blPin pin.Output) Device {
legacy.ConfigurePinOut(resetPin)
legacy.ConfigurePinOut(dcPin)
legacy.ConfigurePinOut(csPin)
legacy.ConfigurePinOut(blPin)
return Device{
bus: bus,
resetPin: resetPin,
dcPin: dcPin,
csPin: csPin,
blPin: blPin,
resetPin: resetPin.Set,
dcPin: dcPin.Set,
csPin: csPin.Set,
blPin: blPin.Set,
}
}
@@ -226,7 +227,7 @@ func (d *Device) Data(data uint8) {
// Tx sends data to the display
func (d *Device) Tx(data []byte, isCommand bool) {
d.dcPin.Set(!isCommand)
d.dcPin(!isCommand)
d.bus.Tx(data, nil)
}
+7 -1
View File
@@ -69,10 +69,16 @@ func NewUART(uart drivers.UART) Device {
}
// NewI2C creates a new I2C GPS connection.
// Uses the default i2c address (0x42) for backward compatibility reasons.
func NewI2C(bus drivers.I2C) Device {
return NewI2CWithAddress(bus, I2C_ADDRESS)
}
// NewI2CWithAddress creates a new I2C GPS connection on the provided address
func NewI2CWithAddress(bus drivers.I2C, i2cAddress uint16) Device {
return Device{
bus: bus,
address: I2C_ADDRESS,
address: i2cAddress,
buffer: make([]byte, bufferSize),
bufIdx: bufferSize,
sentence: strings.Builder{},
+4 -1
View File
@@ -96,7 +96,10 @@ func (parser *Parser) Parse(sentence string) (Fix, error) {
fix.Speed = findSpeed(fields[7])
fix.Heading = findHeading(fields[8])
date := findDate(fields[9])
fix.Time = fix.Time.AddDate(date.Year(), int(date.Month()), date.Day())
fix.Time = date.Add(time.Duration(fix.Time.Hour())*time.Hour +
time.Duration(fix.Time.Minute())*time.Minute +
time.Duration(fix.Time.Second())*time.Second +
time.Duration(fix.Time.Nanosecond())*time.Nanosecond)
return fix, nil
}
+3 -3
View File
@@ -70,15 +70,15 @@ func TestParseRMC(t *testing.T) {
t.Error("should have errInvalidRMCSentence error")
}
val = "$GPRMC,203522.00,A,5109.0262308,N,11401.8407342,W,0.004,133.4,130522,0.0,E,D*2B"
val = "$GPRMC,203522.00,A,5109.0262308,N,11401.8407342,W,0.004,133.4,010622,0.0,E,D*2B"
fix, err := p.Parse(val)
if err != nil {
t.Error("should have parsed")
}
c.Assert(fix.Time.Year(), qt.Equals, 2022)
c.Assert(fix.Time.Month(), qt.Equals, time.May)
c.Assert(fix.Time.Day(), qt.Equals, 13)
c.Assert(fix.Time.Month(), qt.Equals, time.June)
c.Assert(fix.Time.Day(), qt.Equals, 1)
c.Assert(fix.Time.Hour(), qt.Equals, 20)
c.Assert(fix.Time.Minute(), qt.Equals, 35)
c.Assert(fix.Time.Second(), qt.Equals, 22)
+5 -1
View File
@@ -4,7 +4,11 @@ package gps
// The I2C address which this device listens to.
const (
I2C_ADDRESS = 0x42
// To ensure backward compatibility
I2C_ADDRESS = UBLOX_I2C_ADDRESS
UBLOX_I2C_ADDRESS = 0x42
PA1010D_I2C_ADDRESS = 0x10
)
const (
+19 -10
View File
@@ -5,30 +5,39 @@
package hcsr04
import (
"machine"
"time"
"tinygo.org/x/drivers/internal/legacy"
"tinygo.org/x/drivers/internal/pin"
)
const TIMEOUT = 23324 // max sensing distance (4m)
// Device holds the pins
type Device struct {
trigger machine.Pin
echo machine.Pin
trigger pin.OutputFunc
echo pin.InputFunc
configurePins func()
}
// New returns a new ultrasonic driver given 2 pins
func New(trigger, echo machine.Pin) Device {
func New(trigger pin.Output, echo pin.Input) Device {
return Device{
trigger: trigger,
echo: echo,
trigger: trigger.Set,
echo: echo.Get,
configurePins: func() {
legacy.ConfigurePinOut(trigger)
legacy.ConfigurePinInput(echo)
},
}
}
// Configure configures the pins of the Device
func (d *Device) Configure() {
d.trigger.Configure(machine.PinConfig{Mode: machine.PinOutput})
d.echo.Configure(machine.PinConfig{Mode: machine.PinInput})
if d.configurePins == nil {
panic(legacy.ErrConfigBeforeInstantiated)
}
d.configurePins()
}
// ReadDistance returns the distance of the object in mm
@@ -52,7 +61,7 @@ func (d *Device) ReadPulse() int32 {
d.trigger.Low()
i := uint8(0)
for {
if d.echo.Get() {
if d.echo() {
t = time.Now()
break
}
@@ -66,7 +75,7 @@ func (d *Device) ReadPulse() int32 {
}
i = 0
for {
if !d.echo.Get() {
if !d.echo() {
return int32(time.Since(t).Microseconds())
}
i++
+5 -5
View File
@@ -210,7 +210,7 @@ func (d *Device) SendCommand(command byte) {
d.bus.SetCommandMode(true)
d.bus.Write([]byte{command})
for d.busy(command == DISPLAY_CLEAR || command == CURSOR_HOME) {
for d.isBusy(command == DISPLAY_CLEAR || command == CURSOR_HOME) {
}
}
@@ -219,7 +219,7 @@ func (d *Device) sendData(data byte) {
d.bus.SetCommandMode(false)
d.bus.Write([]byte{data})
for d.busy(false) {
for d.isBusy(false) {
}
}
@@ -231,9 +231,9 @@ func (d *Device) CreateCharacter(cgramAddr uint8, data []byte) {
}
}
// busy returns true when hd447890 is busy
// isBusy returns true when hd447890 is isBusy
// or after the timeout specified
func (d *Device) busy(longDelay bool) bool {
func (d *Device) isBusy(longDelay bool) bool {
if d.bus.WriteOnly() {
// Can't read busy flag if write only, so sleep a bit then return
if longDelay {
@@ -261,7 +261,7 @@ func (d *Device) busy(longDelay bool) bool {
// Busy returns true when hd447890 is busy
func (d *Device) Busy() bool {
return d.busy(false)
return d.isBusy(false)
}
// Size returns the current size of the display.
+191
View File
@@ -0,0 +1,191 @@
package honeyhsc
import (
"errors"
"math"
"tinygo.org/x/drivers"
)
var (
errSensorMissing = errors.New("hsc: not connected")
errDiagnostic = errors.New("hsc: diagnostic error")
)
const (
measuremask = drivers.Pressure | drivers.Temperature
statusMask = 0b1100_0000
statusOffset = 6
)
// DevI2C is the TruStability® High Accuracy Silicon Ceramic (HSC) Series is a piezoresistive silicon pressure sensor offering a ratiometric
// analog or digital output for reading pressure over the specified full scale pressure span and temperature range.
type DevI2C struct {
bus drivers.I2C
dev
addr uint8
buf [6]byte
}
// NewDevI2C creates and returns a new DevI2C that communicates with an HSC device over the provided I2C bus.
// Parameters:
// - bus: the I2C bus to use.
// - addr: the 7-bit I2C address of the sensor.
// - outMin, outMax: raw output code range (counts) corresponding to the pressure span. Depends on sensor model.
// - pMin, pMax: pressure range endpoints in millipascals (mPa). Depends on sensor model.
//
// The returned DevI2C will use these calibration parameters to convert raw bridge counts to pressure.
func NewDevI2C(bus drivers.I2C, addr, outMin, outMax uint16, pMin, pMax int32) *DevI2C {
h := &DevI2C{
bus: bus,
addr: uint8(addr),
dev: dev{
cmin: outMin,
cmax: outMax,
pmin: pMin,
pmax: pMax,
},
}
return h
}
// ReadTemperature reads and returns the temperature in milliKelvin (mC) from the I2C-attached HSC device.
// It performs an Update internally to get the latest temperature value.
func (h *DevI2C) ReadTemperature() (int32, error) {
err := h.Update(drivers.Temperature)
if err != nil {
return 0, err
}
return h.Temperature(), nil
}
// Update reads both temperature and pressure data from the I2C-attached HSC device when
// the requested measurement mask includes pressure or temperature.
// If neither pressure nor temperature is requested, Update is a no-op.
func (d *DevI2C) Update(which drivers.Measurement) error {
// Update performs an I2C transaction to read 4 bytes, parses the status bits, 14-bit bridge data and
// temperature bits, and forwards them to the internal update routine. Any I2C transport error is returned,
// as well as errors produced by the internal update (e.g. errSensorMissing, errDiagnostic).
if which&measuremask == 0 {
return nil
}
rbuf := d.buf[:4]
wbuf := d.buf[4:6]
const reg = 0
value := (d.addr << 1) | 1
wbuf[0] = reg
wbuf[1] = value
err := d.bus.Tx(uint16(d.addr), wbuf, rbuf)
if err != nil {
return err
}
status := (rbuf[0] & statusMask) >> statusOffset
bridgeData := (uint16(rbuf[0]&^statusMask) << 8) | uint16(rbuf[1])
tempData := uint16(rbuf[2])<<8 | uint16(rbuf[3]&0xe0)>>5
return d.dev.update(status, bridgeData, tempData)
}
type pinout func(level bool)
// DevI2C is the TruStability® High Accuracy Silicon Ceramic (HSC) Series is a piezoresistive silicon pressure sensor offering a ratiometric
// analog or digital output for reading pressure over the specified full scale pressure span and temperature range.
type DevSPI struct {
spi drivers.SPI
cs pinout
dev
buf [4]byte
}
// NewDevSPI creates and returns a new DevSPI that communicates with an HSC device over SPI.
// Parameters:
// - conn: the SPI connection to use.
// - cs: a chip-select function that drives the device select line low/high.
// - outMin, outMax: raw output code range (counts) corresponding to the pressure span. Depends on sensor model.
// - pMin, pMax: pressure range endpoints in millipascals (mPa). Depends on sensor model.
//
// The function returns the constructed DevSPI and an error value (currently always nil).
func NewDevSPI(conn drivers.SPI, cs pinout, outMin, outMax uint16, pMin, pMax int32) (*DevSPI, error) {
h := &DevSPI{
spi: conn,
cs: cs,
dev: dev{
cmin: outMin,
cmax: outMax,
pmin: pMin,
pmax: pMax,
},
}
return h, nil
}
// ReadTemperature reads and returns the temperature in milliKelvin (mC) from the SPI-attached HSC device.
// It performs an Update internally to get the latest temperature value.
func (h *DevSPI) ReadTemperature() (int32, error) {
err := h.Update(drivers.Temperature)
if err != nil {
return 0, err
}
return h.Temperature(), nil
}
// Update reads pressure and temperature data from the SPI-attached HSC device when the requested measurement mask includes
// pressure or temperature. If neither pressure nor temperature is requested, Update is a no-op.
func (h *DevSPI) Update(which drivers.Measurement) error {
// It toggles the provided chip-select, performs an SPI transfer to read 4 bytes, parses the status bits,
// 14-bit bridge data and temperature bits, and forwards them to the internal update routine. Any SPI
// transport error is returned, as well as errors produced by the internal update (e.g. errSensorMissing, errDiagnostic).
if which&measuremask == 0 {
return nil
}
buf := &h.buf
h.cs(false)
err := h.spi.Tx(nil, buf[:4])
h.cs(true)
if err != nil {
return err
}
// First two bits are status bits.
status := (buf[0] & statusMask) >> statusOffset
bridgeData := (uint16(buf[0]&^statusMask) << 8) | uint16(buf[1])
tempData := uint16(buf[2])<<8 | uint16(buf[3]&0xe0)>>5
return h.dev.update(status, bridgeData, tempData)
}
type dev struct {
pressure int32
temp int32
cmin, cmax uint16
pmin, pmax int32
}
// Pressure returns the most recently computed pressure value in millipascals (mPa).
// The value is taken from the last successful Update.
func (d *dev) Pressure() int32 {
return d.pressure
}
// Temperature returns the most recently read temperature value in milliKelvin (mC).
// The value is taken from the last successful Update.
func (d *dev) Temperature() int32 {
return d.temp + 273_150
}
// update interprets raw sensor fields (status, bridgeData, tempData) and updates the dev's stored
// pressure and temperature. It returns errSensorMissing when the temperature raw value indicates no sensor
// (tempData == math.MaxUint16), errDiagnostic when the status indicates a device diagnostic condition
// (status == 3), or nil on success. Pressure is computed with integer arithmetic using the configured
// cmin/cmax -> pmin/pmax linear mapping in order to avoid overflows.
func (d *dev) update(status uint8, bridgeData, tempData uint16) error {
if tempData == math.MaxUint16 {
return errSensorMissing
} else if status == 3 {
return errDiagnostic
}
// Take care not to overflow here.
p := (int32(bridgeData)-int32(d.cmin))*(d.pmax-d.pmin)/int32(d.cmax-d.cmin) + d.pmin
d.temp = int32(tempData)
d.pressure = p
return nil
}
+62
View File
@@ -0,0 +1,62 @@
package legacy
import (
"errors"
"tinygo.org/x/drivers/internal/pin"
)
// The pingconfig group of files serve to abstract away
// pin configuration calls on the machine.Pin type.
// It was observed this way of developing drivers was
// non-portable and unusable on "big" Go projects so
// future projects should NOT configure pins in driver code.
// Users must configure pins before passing them as arguments
// to drivers.
// ConfigurePinOut is a legacy function used to configure pins as outputs.
//
// Deprecated: Do not configure pins in drivers.
// This is a legacy feature and should only be used by drivers that
// previously configured pins in initialization to avoid breaking users.
func ConfigurePinOut(po pin.Output) {
configurePinOut(po)
}
// ConfigurePinInput is a legacy function used to configure pins as inputs.
//
// Deprecated: Do not configure pins in drivers.
// This is a legacy feature and should only be used by drivers that
// previously configured pins in initialization to avoid breaking users.
func ConfigurePinInputPulldown(pi pin.Input) {
configurePinInputPulldown(pi)
}
// ConfigurePinInput is a legacy function used to configure pins as inputs.
//
// Deprecated: Do not configure pins in drivers.
// This is a legacy feature and should only be used by drivers that
// previously configured pins in initialization to avoid breaking users.
func ConfigurePinInput(pi pin.Input) {
configurePinInput(pi)
}
// ConfigurePinInput is a legacy function used to configure pins as inputs.
//
// Deprecated: Do not configure pins in drivers.
// This is a legacy feature and should only be used by drivers that
// previously configured pins in initialization to avoid breaking users.
func ConfigurePinInputPullup(pi pin.Input) {
configurePinInputPullup(pi)
}
// PinIsNoPin returns true if the argument is a machine.Pin type and is the machine.NoPin predeclared type.
//
// Deprecated: Drivers do not require pin knowledge from now on.
func PinIsNoPin(pin any) bool {
return pinIsNoPin(pin)
}
var (
ErrConfigBeforeInstantiated = errors.New("device must be instantiated with New before calling Configure method")
)
+15
View File
@@ -0,0 +1,15 @@
//go:build !tinygo
package legacy
import "tinygo.org/x/drivers/internal/pin"
// This file compiles for non-tinygo builds
// for use with "big" or "upstream" Go where
// there is no machine package.
func configurePinOut(p pin.Output) {}
func configurePinInput(p pin.Input) {}
func configurePinInputPulldown(p pin.Input) {}
func configurePinInputPullup(p pin.Input) {}
func pinIsNoPin(a any) bool { return false }
+10
View File
@@ -0,0 +1,10 @@
//go:build baremetal && fe310
package legacy
import "machine"
const (
pulldown = machine.PinInput
pullup = machine.PinInput
)
+13
View File
@@ -0,0 +1,13 @@
//go:build baremetal && !fe310
package legacy
import "machine"
// If you are getting a build error here you then we missed adding
// your CPU build tag to the list of CPUs that do not have pulldown/pullups.
// Add it above and in pinhal_nopulls! You should also add a smoketest for it :)
const (
pulldown = machine.PinInputPulldown
pullup = machine.PinInputPullup
)
+37
View File
@@ -0,0 +1,37 @@
//go:build baremetal
package legacy
import (
"machine"
"tinygo.org/x/drivers/internal/pin"
)
func configurePinOut(po pin.Output) {
configurePin(po, machine.PinOutput)
}
func configurePinInputPulldown(pi pin.Input) {
configurePin(pi, pulldown) // some chips do not have pull down, in which case pulldown==machine.PinInput.
}
func configurePinInput(pi pin.Input) {
configurePin(pi, machine.PinInput)
}
func configurePinInputPullup(pi pin.Input) {
configurePin(pi, pullup) // some chips do not have pull up, in which case pullup==machine.PinInput.
}
func pinIsNoPin(a any) bool {
p, ok := a.(machine.Pin)
return ok && p == machine.NoPin
}
func configurePin(p any, mode machine.PinMode) {
machinePin, ok := p.(machine.Pin)
if ok {
machinePin.Configure(machine.PinConfig{Mode: mode})
}
}
+72
View File
@@ -0,0 +1,72 @@
// package pin implements a TinyGo Pin HAL.
// It serves to eliminate machine.Pin from driver constructors
// so that drivers can be used in "big" Go projects where
// there is no machine package.
// This file contains both function and interface-style Pin HAL definitions.
package pin
// OutputFunc is hardware abstraction for a pin which outputs a
// digital signal (high or low level).
//
// // Code conversion demo: from machine.Pin to pin.OutputFunc
// led := machine.LED
// led.Configure(machine.PinConfig{Mode: machine.Output})
// var pin pin.OutputFunc = led.Set // Going from a machine.Pin to a pin.OutputFunc
//
// This is an alternative to [Output] which is an interface type.
type OutputFunc func(level bool)
// High sets the underlying pin's level to high. This is equivalent to calling PinOutput(true).
func (setPin OutputFunc) High() {
setPin(true)
}
// Low sets the underlying pin's level to low. This is equivalent to calling PinOutput(false).
func (setPin OutputFunc) Low() {
setPin(false)
}
// InputFunc is hardware abstraction for a pin which receives a
// digital signal and reads it (high or low level).
//
// // Code conversion demo: from machine.Pin to pin.InputFunc
// input := machine.LED
// input.Configure(machine.PinConfig{Mode: machine.PinInputPulldown}) // or use machine.PinInputPullup or machine.Input
// var pin pin.InputFunc = input.Get // Going from a machine.Pin to a pin.InputFunc
//
// This is an alternative to [Input] which is an interface type.
type InputFunc func() (level bool)
// // Below is an example on how to define a input/output pin HAL for a
// // pin that must switch between input and output mode:
//
// var pinIsOutput bool
// var po PinOutputFunc = func(b bool) {
// if !pinIsOutput {
// pin.Configure(outputMode)
// pinIsOutput = true
// }
// pin.Set(b)
// }
//
// var pi PinInputFunc = func() bool {
// if pinIsOutput {
// pin.Configure(inputMode)
// pinIsOutput = false
// }
// return pin.Get()
// }
// Output interface represents a pin hardware abstraction layer for a pin that can output a digital signal.
//
// This is an alternative to [OutputFunc] abstraction which is a function type.
type Output interface {
Set(level bool)
}
// Input interface represents a pin hardware abstraction layer for a pin that can read a digital signal.
//
// This is an alternative to [InputFunc] abstraction which is a function type.
type Input interface {
Get() (level bool)
}
+143
View File
@@ -0,0 +1,143 @@
package regmap
import (
"encoding/binary"
"io"
"tinygo.org/x/drivers"
)
// Device8 implements common logic to most 8-bit peripherals with an I2C or SPI bus.
// All methods expect the target to support conventional register read and write operations
// where the first byte sent is the register address being accessed.
//
// All methods use an internal buffer and perform no dynamic memory allocation.
type Device8 struct {
buf [10]byte
}
// clear zeroes Device8's buffers.
func (d *Device8) clear() {
d.buf = [10]byte{}
}
// I2C methods.
// Read8I2C reads a single byte from register addr of the device at i2cAddr using the provided I2C bus.
func (d *Device8) Read8I2C(bus drivers.I2C, i2cAddr uint16, addr uint8) (byte, error) {
d.buf[0] = addr
err := bus.Tx(i2cAddr, d.buf[0:1], d.buf[1:2])
return d.buf[1], err
}
// Read16I2C reads a 16-bit value from register addr of the device at i2cAddr using the provided I2C bus.
// The byte order is specified by order.
func (d *Device8) Read16I2C(bus drivers.I2C, i2cAddr uint16, addr uint8, order binary.ByteOrder) (uint16, error) {
d.buf[0] = addr
err := bus.Tx(i2cAddr, d.buf[0:1], d.buf[1:3])
return order.Uint16(d.buf[1:3]), err
}
// Read32I2C reads a 32-bit value from register addr of the device at i2cAddr using the provided I2C bus.
// The byte order is specified by order.
func (d *Device8) Read32I2C(bus drivers.I2C, i2cAddr uint16, addr uint8, order binary.ByteOrder) (uint32, error) {
d.buf[0] = addr
err := bus.Tx(i2cAddr, d.buf[0:1], d.buf[1:5])
return order.Uint32(d.buf[1:5]), err
}
// ReadDataI2C reads dataLength bytes from register addr of the device at i2cAddr using the provided I2C bus.
// The data is stored in dataDestination.
func (d *Device8) ReadDataI2C(bus drivers.I2C, i2cAddr uint16, addr uint8, dataDestination []byte) error {
d.buf[0] = addr
return bus.Tx(i2cAddr, d.buf[:1], dataDestination)
}
// Write8I2C writes a single byte value to register addr of the device at i2cAddr using the provided I2C bus.
func (d *Device8) Write8I2C(bus drivers.I2C, i2cAddr uint16, addr, value uint8) error {
d.buf[0] = addr
d.buf[1] = value
return bus.Tx(i2cAddr, d.buf[:2], nil)
}
// Write16I2C writes a 16-bit value to register addr of the device at i2cAddr using the provided I2C bus.
// The byte order is specified by order.
func (d *Device8) Write16I2C(bus drivers.I2C, i2cAddr uint16, addr uint8, value uint16, order binary.ByteOrder) error {
d.buf[0] = addr
order.PutUint16(d.buf[1:3], value)
return bus.Tx(i2cAddr, d.buf[0:3], nil)
}
// Write32I2C writes a 32-bit value to register addr of the device at i2cAddr using the provided I2C bus.
// The byte order is specified by order.
func (d *Device8) Write32I2C(bus drivers.I2C, i2cAddr uint16, addr uint8, value uint32, order binary.ByteOrder) error {
d.buf[0] = addr
order.PutUint32(d.buf[1:5], value)
return bus.Tx(i2cAddr, d.buf[0:5], nil)
}
// SPI methods.
// Read8SPI reads a single byte from register addr using the provided SPI bus.
func (d *Device8) Read8SPI(bus drivers.SPI, addr uint8) (byte, error) {
d.clear()
d.buf[0] = addr
err := bus.Tx(d.buf[0:1], d.buf[1:2]) // We suppose data is returned after first byte in SPI.
return d.buf[1], err
}
// Read16SPI reads a 16-bit value from register addr using the provided SPI bus. The byte order is specified by order.
func (d *Device8) Read16SPI(bus drivers.SPI, addr uint8, order binary.ByteOrder) (uint16, error) {
d.clear()
d.buf[0] = addr
err := bus.Tx(d.buf[0:3], d.buf[3:6]) // We suppose data is returned after first byte in SPI.
return order.Uint16(d.buf[4:6]), err
}
// Read32SPI reads a 32-bit value from register addr using the provided SPI bus. The byte order is specified by order.
func (d *Device8) Read32SPI(bus drivers.SPI, addr uint8, order binary.ByteOrder) (uint32, error) {
d.clear()
d.buf[0] = addr
err := bus.Tx(d.buf[0:5], d.buf[5:10]) // We suppose data is returned after first byte in SPI.
return order.Uint32(d.buf[6:10]), err
}
// ReadDataSPI reads data from a 8bit device address. It assumes data at register address is sent back
// from device after first byte is written as address.
// It needs the auxiliary buffer length to be large enough to contain both the write and read portions of buffer,
// so 2*(dataLength+1) < len(auxiliaryBuf) must hold.
func (d *Device8) ReadDataSPI(bus drivers.SPI, addr uint8, dataLength int, auxiliaryBuf []byte) ([]byte, error) {
split := len(auxiliaryBuf) / 2
if split < dataLength+1 {
return nil, io.ErrShortBuffer
}
wbuf, rbuf := auxiliaryBuf[:split], auxiliaryBuf[split:]
wbuf[0] = addr
err := bus.Tx(wbuf, rbuf)
return rbuf[1:], err
}
// Write8SPI writes a single byte value to register addr using the provided SPI bus.
func (d *Device8) Write8SPI(bus drivers.SPI, addr, value uint8) error {
d.clear()
d.buf[0] = addr
d.buf[1] = value
return bus.Tx(d.buf[:2], nil)
}
// Write16SPI writes a 16-bit value to register addr using the provided SPI bus. The byte order is specified by order.
func (d *Device8) Write16SPI(bus drivers.SPI, addr uint8, value uint16, order binary.ByteOrder) error {
d.clear()
d.buf[0] = addr
order.PutUint16(d.buf[1:3], value)
return bus.Tx(d.buf[:3], nil)
}
// Write32SPI writes a 32-bit value to register addr using the provided SPI bus. The byte order is specified by order.
func (d *Device8) Write32SPI(bus drivers.SPI, addr uint8, value uint32, order binary.ByteOrder) error {
d.clear()
d.buf[0] = addr
order.PutUint32(d.buf[1:5], value)
return bus.Tx(d.buf[:5], nil)
}
+123
View File
@@ -0,0 +1,123 @@
package regmap
import (
"encoding/binary"
"tinygo.org/x/drivers"
)
// Device8SPI implements common logic to most 8-bit peripherals with an SPI bus.
// All methods expect the target to support conventional register read and write operations
// where the first byte sent is the register address being accessed.
//
// All methods use an internal buffer and perform no dynamic memory allocation.
type Device8SPI struct {
bus drivers.SPI
order binary.ByteOrder
d Device8
}
// SetBus sets the SPI bus and byte order for the Device8SPI.
//
// As a hint, most SPI devices use big-endian (MSB) byte order.
// - Big endian: A value of 0x1234 is transmitted as 0x12 followed by 0x34.
// - Little endian: A value of 0x1234 is transmitted as 0x34 followed by 0x12.
func (d *Device8SPI) SetBus(bus drivers.SPI, order binary.ByteOrder) {
d.bus = bus
d.order = order
}
// Read8 reads a single byte from register addr.
func (d *Device8SPI) Read8(addr uint8) (byte, error) {
return d.d.Read8SPI(d.bus, addr)
}
// Read16 reads a 16-bit value from register addr.
func (d *Device8SPI) Read16(addr uint8) (uint16, error) {
return d.d.Read16SPI(d.bus, addr, d.order)
}
// Read32 reads a 32-bit value from register addr.
func (d *Device8SPI) Read32(addr uint8) (uint32, error) {
return d.d.Read32SPI(d.bus, addr, d.order)
}
// ReadData reads dataLength bytes from register addr. Due to the internal functioning of
// SPI, an auxiliary buffer must be provided to perform the operation and avoid memory allocation.
// The returned slice is a subslice of auxBuffer containing the read data.
func (d *Device8SPI) ReadData(addr uint8, datalength int, auxBuffer []byte) ([]byte, error) {
return d.d.ReadDataSPI(d.bus, addr, datalength, auxBuffer)
}
// Write8 writes a single byte value to register addr.
func (d *Device8SPI) Write8(addr, value uint8) error {
return d.d.Write8SPI(d.bus, addr, value)
}
// Write16 writes a 16-bit value to register addr.
func (d *Device8SPI) Write16(addr uint8, value uint16) error {
return d.d.Write16SPI(d.bus, addr, value, d.order)
}
// Write32 writes a 32-bit value to register addr.
func (d *Device8SPI) Write32(addr uint8, value uint32) error {
return d.d.Write32SPI(d.bus, addr, value, d.order)
}
// Device8I2C implements common logic to most 8-bit peripherals with an I2C bus.
// All methods expect the target to support conventional register read and write operations
// where the first byte sent is the register address being accessed.
//
// All methods use an internal buffer and perform no dynamic memory allocation.
type Device8I2C struct {
bus drivers.I2C
i2cAddr uint16
order binary.ByteOrder
d Device8
}
// SetBus sets the I2C bus, device address, and byte order for the Device8I2C.
//
// As a hint, most I2C devices use big-endian (MSB) byte order.
// - Big endian: A value of 0x1234 is transmitted as 0x12 followed by 0x34.
// - Little endian: A value of 0x1234 is transmitted as 0x34 followed by 0x12.
func (d *Device8I2C) SetBus(bus drivers.I2C, i2cAddr uint16, order binary.ByteOrder) {
d.bus = bus
d.i2cAddr = i2cAddr
d.order = order
}
// Read8 reads a single byte from register addr.
func (d *Device8I2C) Read8(addr uint8) (byte, error) {
return d.d.Read8I2C(d.bus, d.i2cAddr, addr)
}
// Read16 reads a 16-bit value from register addr.
func (d *Device8I2C) Read16(addr uint8) (uint16, error) {
return d.d.Read16I2C(d.bus, d.i2cAddr, addr, d.order)
}
// Read32 reads a 32-bit value from register addr.
func (d *Device8I2C) Read32(addr uint8) (uint32, error) {
return d.d.Read32I2C(d.bus, d.i2cAddr, addr, d.order)
}
// ReadData reads dataLength bytes from register addr.
func (d *Device8I2C) ReadData(addr uint8, dataDestination []byte) error {
return d.d.ReadDataI2C(d.bus, d.i2cAddr, addr, dataDestination)
}
// Write8 writes a single byte value to register addr.
func (d *Device8I2C) Write8(addr, value uint8) error {
return d.d.Write8I2C(d.bus, d.i2cAddr, addr, value)
}
// Write16 writes a 16-bit value to register addr.
func (d *Device8I2C) Write16(addr uint8, value uint16) error {
return d.d.Write16I2C(d.bus, d.i2cAddr, addr, value, d.order)
}
// Write32 writes a 32-bit value to register addr.
func (d *Device8I2C) Write32(addr uint8, value uint32) error {
return d.d.Write32I2C(d.bus, d.i2cAddr, addr, value, d.order)
}
+115 -36
View File
@@ -11,37 +11,57 @@ import (
// Device wraps an I2C connection to a LIS3DH device.
type Device struct {
bus drivers.I2C
Address uint16
address uint16
r Range
accel [6]byte // stored acceleration data (from the Update call)
}
// Driver configuration, used for the Configure call. All fields are optional.
type Config struct {
Address uint16
}
// New creates a new LIS3DH connection. The I2C bus must already be configured.
//
// This function only creates the Device object, it does not touch the device.
func New(bus drivers.I2C) Device {
return Device{bus: bus, Address: Address0}
return Device{bus: bus, address: Address0}
}
// Configure sets up the device for communication
func (d *Device) Configure() {
func (d *Device) Configure(config Config) error {
if config.Address != 0 {
d.address = config.Address
}
// enable all axes, normal mode
legacy.WriteRegister(d.bus, uint8(d.Address), REG_CTRL1, []byte{0x07})
err := legacy.WriteRegister(d.bus, uint8(d.address), REG_CTRL1, []byte{0x07})
if err != nil {
return err
}
// 400Hz rate
d.SetDataRate(DATARATE_400_HZ)
err = d.SetDataRate(DATARATE_400_HZ)
if err != nil {
return err
}
// High res & BDU enabled
legacy.WriteRegister(d.bus, uint8(d.Address), REG_CTRL4, []byte{0x88})
err = legacy.WriteRegister(d.bus, uint8(d.address), REG_CTRL4, []byte{0x88})
if err != nil {
return err
}
// get current range
d.r = d.ReadRange()
d.r, err = d.ReadRange()
return err
}
// Connected returns whether a LIS3DH has been found.
// It does a "who am I" request and checks the response.
func (d *Device) Connected() bool {
data := []byte{0}
err := legacy.ReadRegister(d.bus, uint8(d.Address), WHO_AM_I, data)
err := legacy.ReadRegister(d.bus, uint8(d.address), WHO_AM_I, data)
if err != nil {
return false
}
@@ -49,46 +69,51 @@ func (d *Device) Connected() bool {
}
// SetDataRate sets the speed of data collected by the LIS3DH.
func (d *Device) SetDataRate(rate DataRate) {
func (d *Device) SetDataRate(rate DataRate) error {
ctl1 := []byte{0}
err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_CTRL1, ctl1)
err := legacy.ReadRegister(d.bus, uint8(d.address), REG_CTRL1, ctl1)
if err != nil {
println(err.Error())
return err
}
// mask off bits
ctl1[0] &^= 0xf0
ctl1[0] |= (byte(rate) << 4)
legacy.WriteRegister(d.bus, uint8(d.Address), REG_CTRL1, ctl1)
return legacy.WriteRegister(d.bus, uint8(d.address), REG_CTRL1, ctl1)
}
// SetRange sets the G range for LIS3DH.
func (d *Device) SetRange(r Range) {
func (d *Device) SetRange(r Range) error {
ctl := []byte{0}
err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_CTRL4, ctl)
err := legacy.ReadRegister(d.bus, uint8(d.address), REG_CTRL4, ctl)
if err != nil {
println(err.Error())
return err
}
// mask off bits
ctl[0] &^= 0x30
ctl[0] |= (byte(r) << 4)
legacy.WriteRegister(d.bus, uint8(d.Address), REG_CTRL4, ctl)
err = legacy.WriteRegister(d.bus, uint8(d.address), REG_CTRL4, ctl)
if err != nil {
return err
}
// store the new range
d.r = r
return nil
}
// ReadRange returns the current G range for LIS3DH.
func (d *Device) ReadRange() (r Range) {
func (d *Device) ReadRange() (r Range, err error) {
ctl := []byte{0}
err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_CTRL4, ctl)
err = legacy.ReadRegister(d.bus, uint8(d.address), REG_CTRL4, ctl)
if err != nil {
println(err.Error())
return 0, err
}
// mask off bits
r = Range(ctl[0] >> 4)
r &= 0x03
return r
return r, nil
}
// ReadAcceleration reads the current acceleration from the device and returns
@@ -96,28 +121,17 @@ func (d *Device) ReadRange() (r Range) {
// and the sensor is not moving the returned value will be around 1000000 or
// -1000000.
func (d *Device) ReadAcceleration() (int32, int32, int32, error) {
x, y, z := d.ReadRawAcceleration()
divider := float32(1)
switch d.r {
case RANGE_16_G:
divider = 1365
case RANGE_8_G:
divider = 4096
case RANGE_4_G:
divider = 8190
case RANGE_2_G:
divider = 16380
}
return int32(float32(x) / divider * 1000000), int32(float32(y) / divider * 1000000), int32(float32(z) / divider * 1000000), nil
rawX, rawY, rawZ := d.ReadRawAcceleration()
x, y, z := normalizeRange(rawX, rawY, rawZ, d.r)
return x, y, z, nil
}
// ReadRawAcceleration returns the raw x, y and z axis from the LIS3DH
func (d *Device) ReadRawAcceleration() (x int16, y int16, z int16) {
legacy.WriteRegister(d.bus, uint8(d.Address), REG_OUT_X_L|0x80, nil)
legacy.WriteRegister(d.bus, uint8(d.address), REG_OUT_X_L|0x80, nil)
data := []byte{0, 0, 0, 0, 0, 0}
d.bus.Tx(d.Address, nil, data)
d.bus.Tx(d.address, nil, data)
x = int16((uint16(data[1]) << 8) | uint16(data[0]))
y = int16((uint16(data[3]) << 8) | uint16(data[2]))
@@ -125,3 +139,68 @@ func (d *Device) ReadRawAcceleration() (x int16, y int16, z int16) {
return
}
// Update the sensor values of the 'which' parameter. Only acceleration is
// supported at the moment.
func (d *Device) Update(which drivers.Measurement) error {
if which&drivers.Acceleration != 0 {
// Read raw acceleration values and store them in the driver.
err := legacy.WriteRegister(d.bus, uint8(d.address), REG_OUT_X_L|0x80, nil)
if err != nil {
return err
}
err = d.bus.Tx(d.address, nil, d.accel[:])
if err != nil {
return err
}
}
return nil
}
// Acceleration returns the last read acceleration in µg (micro-gravity).
// When one of the axes is pointing straight to Earth and the sensor is not
// moving the returned value will be around 1000000 or -1000000.
func (d *Device) Acceleration() (x, y, z int32) {
// Extract the raw 16-bit values.
rawX := int16((uint16(d.accel[1]) << 8) | uint16(d.accel[0]))
rawY := int16((uint16(d.accel[3]) << 8) | uint16(d.accel[2]))
rawZ := int16((uint16(d.accel[5]) << 8) | uint16(d.accel[4]))
// Normalize these values, to be in µg (micro-gravity).
return normalizeRange(rawX, rawY, rawZ, d.r)
}
// Convert raw 16-bit values to normalized 32-bit values while avoiding floats
// and divisions.
func normalizeRange(rawX, rawY, rawZ int16, r Range) (x, y, z int32) {
// We're going to convert the 16-bit raw values to values in the range
// -1000_000..1000_000. For now we're going to assume a range of 16G, we'll
// adjust that range later.
// The formula is derived as follows, and carefully selected to avoid
// overflow and integer divisions (the division will be optimized to a
// bitshift):
// x = x * 1000_000 / 2048
// x = x * (1000_000/64) / (2048/64)
// x = x * 15625 / 32
x = int32(rawX) * 15625 / 32
y = int32(rawY) * 15625 / 32
z = int32(rawZ) * 15625 / 32
// Now we need to normalize the three values, since we assumed 16G before.
shift := uint32(0)
switch r {
case RANGE_16_G:
shift = 0
case RANGE_8_G:
shift = 1
case RANGE_4_G:
shift = 2
case RANGE_2_G:
shift = 3
}
x >>= shift
y >>= shift
z >>= shift
return
}
+1 -1
View File
@@ -36,7 +36,7 @@ type Configuration struct {
MagDataRate uint8
}
var errNotConnected = errors.New("lsm303agr: failed to communicate with either acel or magnet sensor")
var errNotConnected = errors.New("lsm303agr: failed to communicate with either accel or magnet sensor")
// New creates a new LSM303AGR connection. The I2C bus must already be configured.
//
+214
View File
@@ -0,0 +1,214 @@
// Package lsm303dlhc implements a driver for the LSM303dlhc,
// a 3 axis accelerometer/magnetic sensor typically available on breakout boards.
//
// Datasheet: https://www.st.com/resource/en/datasheet/lsm303dlhc.pdf
package lsm303dlhc // import "tinygo.org/x/drivers/lsm303dlhc"
import (
"math"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/legacy"
)
// Device wraps an I2C connection to a LSM303dlhc device.
type Device struct {
bus drivers.I2C
AccelAddress uint8
MagAddress uint8
AccelPowerMode uint8
AccelRange uint8
AccelDataRate uint8
MagPowerMode uint8
MagSystemMode uint8
MagDataRate uint8
buf [6]uint8
}
// Configuration for LSM303dlhc device.
type Configuration struct {
AccelPowerMode uint8
AccelRange uint8
AccelDataRate uint8
MagPowerMode uint8
MagSystemMode uint8
MagDataRate uint8
}
// New creates a new LSM303DLHC connection. The I2C bus must already be configured.
// This function only creates the Device object, it does not touch the device.
func New(bus drivers.I2C) *Device {
return &Device{
bus: bus,
AccelAddress: ACCEL_ADDRESS,
MagAddress: MAG_ADDRESS,
}
}
// Configure sets up the LSM303dlhc device for communication.
func (d *Device) Configure(cfg Configuration) (err error) {
if cfg.AccelDataRate != 0 {
d.AccelDataRate = cfg.AccelDataRate
} else {
d.AccelDataRate = ACCEL_DATARATE_100HZ
}
if cfg.AccelPowerMode != 0 {
d.AccelPowerMode = cfg.AccelPowerMode
} else {
d.AccelPowerMode = ACCEL_POWER_NORMAL
}
if cfg.AccelRange != 0 {
d.AccelRange = cfg.AccelRange
} else {
d.AccelRange = ACCEL_RANGE_2G
}
if cfg.MagPowerMode != 0 {
d.MagPowerMode = cfg.MagPowerMode
} else {
d.MagPowerMode = MAG_POWER_NORMAL
}
if cfg.MagDataRate != 0 {
d.MagDataRate = cfg.MagDataRate
} else {
d.MagDataRate = MAG_DATARATE_10HZ
}
if cfg.MagSystemMode != 0 {
d.MagSystemMode = cfg.MagSystemMode
} else {
d.MagSystemMode = MAG_SYSTEM_CONTINUOUS
}
data := d.buf[:1]
data[0] = byte(d.AccelDataRate<<4 | d.AccelPowerMode | 0x07)
err = legacy.WriteRegister(d.bus, uint8(d.AccelAddress), ACCEL_CTRL_REG1_A, data)
if err != nil {
return
}
data[0] = byte(0x80 | d.AccelRange<<4)
err = legacy.WriteRegister(d.bus, uint8(d.AccelAddress), ACCEL_CTRL_REG4_A, data)
if err != nil {
return
}
data[0] = byte(0xC0)
err = legacy.WriteRegister(d.bus, uint8(d.AccelAddress), CRA_REG_M, data)
if err != nil {
return
}
// Temperature compensation is on for magnetic sensor
data[0] = byte(0x80 | d.MagPowerMode<<4 | d.MagDataRate<<2 | d.MagSystemMode)
err = legacy.WriteRegister(d.bus, uint8(d.MagAddress), MAG_MR_REG_M, data)
if err != nil {
return
}
return nil
}
// ReadAcceleration reads the current acceleration from the device and returns
// it in µg (micro-gravity). When one of the axes is pointing straight to Earth
// and the sensor is not moving the returned value will be around 1000000 or
// -1000000.
func (d *Device) ReadAcceleration() (x, y, z int32, err error) {
data := d.buf[:6]
err = legacy.ReadRegister(d.bus, uint8(d.AccelAddress), ACCEL_OUT_AUTO_INC, data)
if err != nil {
return
}
rangeFactor := int16(0)
switch d.AccelRange {
case ACCEL_RANGE_2G:
rangeFactor = 1
case ACCEL_RANGE_4G:
rangeFactor = 2
case ACCEL_RANGE_8G:
rangeFactor = 4
case ACCEL_RANGE_16G:
rangeFactor = 12 // the readings in 16G are a bit lower
}
x = int32(int32(int16((uint16(data[1])<<8|uint16(data[0])))>>4*rangeFactor) * 1000000 / 1024)
y = int32(int32(int16((uint16(data[3])<<8|uint16(data[2])))>>4*rangeFactor) * 1000000 / 1024)
z = int32(int32(int16((uint16(data[5])<<8|uint16(data[4])))>>4*rangeFactor) * 1000000 / 1024)
return
}
// ReadPitchRoll reads the current pitch and roll angles from the device and
// returns it in micro-degrees. When the z axis is pointing straight to Earth
// the returned values of pitch and roll would be zero.
func (d *Device) ReadPitchRoll() (pitch, roll int32, err error) {
x, y, z, err := d.ReadAcceleration()
if err != nil {
return
}
xf, yf, zf := float64(x), float64(y), float64(z)
pitch = int32((math.Round(math.Atan2(yf, math.Sqrt(math.Pow(xf, 2)+math.Pow(zf, 2)))*(180/math.Pi)*100) / 100) * 1000000)
roll = int32((math.Round(math.Atan2(xf, math.Sqrt(math.Pow(yf, 2)+math.Pow(zf, 2)))*(180/math.Pi)*100) / 100) * 1000000)
return
}
// ReadMagneticField reads the current magnetic field from the device and returns
// it in mG (milligauss). 1 mG = 0.1 µT (microtesla).
func (d *Device) ReadMagneticField() (x, y, z int32, err error) {
if d.MagSystemMode == MAG_SYSTEM_SINGLE {
cmd := d.buf[:1]
cmd[0] = byte(0x80 | d.MagPowerMode<<4 | d.MagDataRate<<2 | d.MagSystemMode)
err = legacy.WriteRegister(d.bus, uint8(d.MagAddress), MAG_MR_REG_M, cmd)
if err != nil {
return
}
}
data := d.buf[0:6]
legacy.ReadRegister(d.bus, uint8(d.MagAddress), MAG_OUT_AUTO_INC, data)
x = int32(int16((uint16(data[1])<<8 | uint16(data[0]))))
y = int32(int16((uint16(data[3])<<8 | uint16(data[2]))))
z = int32(int16((uint16(data[5])<<8 | uint16(data[4]))))
return
}
// ReadCompass reads the current compass heading from the device and returns
// it in micro-degrees. When the z axis is pointing straight to Earth and
// the y axis is pointing to North, the heading would be zero.
//
// However, the heading may be off due to electronic compasses would be effected
// by strong magnetic fields and require constant calibration.
func (d *Device) ReadCompass() (h int32, err error) {
x, y, _, err := d.ReadMagneticField()
if err != nil {
return
}
xf, yf := float64(x), float64(y)
h = int32(float32((180/math.Pi)*math.Atan2(yf, xf)) * 1000000)
return
}
// ReadTemperature returns the temperature in Celsius milli degrees (°C/1000)
func (d *Device) ReadTemperature() (t int32, err error) {
data := d.buf[:2]
err = legacy.ReadRegister(d.bus, uint8(d.MagAddress), TEMP_OUT_AUTO_INC, data)
if err != nil {
return
}
r := int16((uint16(data[1])<<8 | uint16(data[0]))) >> 4 // temperature offset from 25 °C
t = 25000 + int32((float32(r)/8)*1000)
return
}
+75
View File
@@ -0,0 +1,75 @@
package lsm303dlhc
const (
// Constants/addresses used for I2C.
ACCEL_ADDRESS = 0x19
MAG_ADDRESS = 0x1E
// i2C 8-bit subaddress (SUB): the 7 LSb represent the actual register address
// while the MSB enables address auto increment.
// If the MSb of the SUB field is 1, the SUB (register address) is
// automatically increased to allow multiple data read/writes.
ADDR_AUTO_INC_MASK = 0x80
// accelerometer registers.
ACCEL_CTRL_REG1_A = 0x20
ACCEL_CTRL_REG4_A = 0x23
ACCEL_OUT_X_L_A = 0x28
ACCEL_OUT_X_H_A = 0x29
ACCEL_OUT_Y_L_A = 0x2A
ACCEL_OUT_Y_H_A = 0x2B
ACCEL_OUT_Z_L_A = 0x2C
ACCEL_OUT_Z_H_A = 0x2D
ACCEL_OUT_AUTO_INC = ACCEL_OUT_X_L_A | ADDR_AUTO_INC_MASK
// magnetic sensor registers.
MAG_MR_REG_M = 0x02
MAG_OUT_X_L_M = 0x68
MAG_OUT_X_H_M = 0x69
MAG_OUT_Y_L_M = 0x6A
MAG_OUT_Y_H_M = 0x6B
MAG_OUT_Z_L_M = 0x6C
MAG_OUT_Z_H_M = 0x6D
MAG_OUT_AUTO_INC = MAG_OUT_X_L_M | ADDR_AUTO_INC_MASK
// temperature sensor registers.
CRA_REG_M = 0x80
TEMP_OUT_L_M = 0x32
TEMP_OUT_H_M = 0x31
TEMP_OUT_AUTO_INC = TEMP_OUT_L_M | ADDR_AUTO_INC_MASK
// accelerometer power mode.
ACCEL_POWER_NORMAL = 0x00 // default
ACCEL_POWER_LOW = 0x08
// accelerometer range.
ACCEL_RANGE_2G = 0x00 // default
ACCEL_RANGE_4G = 0x01
ACCEL_RANGE_8G = 0x02
ACCEL_RANGE_16G = 0x03
// accelerometer data rate.
ACCEL_DATARATE_1HZ = 0x01
ACCEL_DATARATE_10HZ = 0x02
ACCEL_DATARATE_25HZ = 0x03
ACCEL_DATARATE_50HZ = 0x04
ACCEL_DATARATE_100HZ = 0x05 // default
ACCEL_DATARATE_200HZ = 0x06
ACCEL_DATARATE_400HZ = 0x07
ACCEL_DATARATE_1344HZ = 0x09 // 5376Hz in low-power mode
// magnetic sensor power mode.
MAG_POWER_NORMAL = 0x00 // default
MAG_POWER_LOW = 0x01
// magnetic sensor operate mode.
MAG_SYSTEM_CONTINUOUS = 0x00 // default
MAG_SYSTEM_SINGLE = 0x01
// magnetic sensor data rate
MAG_DATARATE_10HZ = 0x00 // default
MAG_DATARATE_20HZ = 0x01
MAG_DATARATE_50HZ = 0x02
MAG_DATARATE_100HZ = 0x03
)
+35 -24
View File
@@ -8,7 +8,6 @@ import (
"errors"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/legacy"
)
type AccelRange uint8
@@ -26,7 +25,7 @@ type Device struct {
accelSampleRate AccelSampleRate
gyroRange GyroRange
gyroSampleRate GyroSampleRate
buf [6]uint8
buf [7]uint8 // up to 6 bytes for read + 1 byte for the register address
}
// Configuration for LSM6DS3TR device.
@@ -84,30 +83,20 @@ func (d *Device) doConfigure(cfg Configuration) (err error) {
d.gyroSampleRate = GYRO_SR_104
}
data := d.buf[:1]
// Configure accelerometer
data[0] = uint8(d.accelRange) | uint8(d.accelSampleRate)
err = legacy.WriteRegister(d.bus, uint8(d.Address), CTRL1_XL, data)
err = d.writeByte(CTRL1_XL, uint8(d.accelRange)|uint8(d.accelSampleRate))
if err != nil {
return
}
// Set ODR bit
err = legacy.ReadRegister(d.bus, uint8(d.Address), CTRL4_C, data)
if err != nil {
return
}
data[0] = data[0] &^ BW_SCAL_ODR_ENABLED
data[0] |= BW_SCAL_ODR_ENABLED
err = legacy.WriteRegister(d.bus, uint8(d.Address), CTRL4_C, data)
// Enable ODR scaling
err = d.setBits(CTRL4_C, BW_SCAL_ODR_ENABLED)
if err != nil {
return
}
// Configure gyroscope
data[0] = uint8(d.gyroRange) | uint8(d.gyroSampleRate)
err = legacy.WriteRegister(d.bus, uint8(d.Address), CTRL2_G, data)
err = d.writeByte(CTRL2_G, uint8(d.gyroRange)|uint8(d.gyroSampleRate))
if err != nil {
return
}
@@ -118,8 +107,10 @@ func (d *Device) doConfigure(cfg Configuration) (err error) {
// Connected returns whether a LSM6DS3TR has been found.
// It does a "who am I" request and checks the response.
func (d *Device) Connected() bool {
data := d.buf[:1]
legacy.ReadRegister(d.bus, uint8(d.Address), WHO_AM_I, data)
data, err := d.readBytes(WHO_AM_I, 1)
if err != nil {
return false
}
return data[0] == 0x6A
}
@@ -128,8 +119,7 @@ func (d *Device) Connected() bool {
// and the sensor is not moving the returned value will be around 1000000 or
// -1000000.
func (d *Device) ReadAcceleration() (x, y, z int32, err error) {
data := d.buf[:6]
err = legacy.ReadRegister(d.bus, uint8(d.Address), OUTX_L_XL, data)
data, err := d.readBytes(OUTX_L_XL, 6)
if err != nil {
return
}
@@ -153,8 +143,7 @@ func (d *Device) ReadAcceleration() (x, y, z int32, err error) {
// rotation along one axis and while doing so integrate all values over time,
// you would get a value close to 360000000.
func (d *Device) ReadRotation() (x, y, z int32, err error) {
data := d.buf[:6]
err = legacy.ReadRegister(d.bus, uint8(d.Address), OUTX_L_G, data)
data, err := d.readBytes(OUTX_L_G, 6)
if err != nil {
return
}
@@ -177,8 +166,7 @@ func (d *Device) ReadRotation() (x, y, z int32, err error) {
// ReadTemperature returns the temperature in celsius milli degrees (°C/1000)
func (d *Device) ReadTemperature() (t int32, err error) {
data := d.buf[:2]
err = legacy.ReadRegister(d.bus, uint8(d.Address), OUT_TEMP_L, data)
data, err := d.readBytes(OUT_TEMP_L, 2)
if err != nil {
return
}
@@ -187,3 +175,26 @@ func (d *Device) ReadTemperature() (t int32, err error) {
t = 25000 + (int32(int16((int16(data[1])<<8)|int16(data[0])))*125)/32
return
}
func (d *Device) readBytes(reg, size uint8) ([]byte, error) {
d.buf[0] = reg
err := d.bus.Tx(d.Address, d.buf[0:1], d.buf[1:size+1])
if err != nil {
return nil, err
}
return d.buf[1 : size+1], nil
}
func (d *Device) writeByte(reg, value uint8) error {
d.buf[0] = reg
d.buf[1] = value
return d.bus.Tx(d.Address, d.buf[0:2], nil)
}
func (d *Device) setBits(reg, bits uint8) error {
data, err := d.readBytes(reg, 1)
if err != nil {
return err
}
return d.writeByte(reg, (data[0]&^bits)|bits)
}
+35 -28
View File
@@ -7,7 +7,6 @@ import (
"errors"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/legacy"
)
type AccelRange uint8
@@ -28,7 +27,7 @@ type Device struct {
accelMultiplier int32
gyroMultiplier int32
magMultiplier int32
buf [6]uint8
buf [7]uint8 // up to 6 bytes for read + 1 byte for the register address
}
// Configuration for LSM9DS1 device.
@@ -61,10 +60,15 @@ func New(bus drivers.I2C) *Device {
// Case of boolean false and error nil means I2C is up,
// but "who am I" responses have unexpected values.
func (d *Device) Connected() bool {
data1, data2 := d.buf[:1], d.buf[1:2]
legacy.ReadRegister(d.bus, d.AccelAddress, WHO_AM_I, data1)
legacy.ReadRegister(d.bus, d.MagAddress, WHO_AM_I_M, data2)
return data1[0] == 0x68 && data2[0] == 0x3D
data, err := d.readBytes(d.AccelAddress, WHO_AM_I, 1)
if err != nil || data[0] != 0x68 {
return false
}
data, err = d.readBytes(d.MagAddress, WHO_AM_I_M, 1)
if err != nil || data[0] != 0x3D {
return false
}
return true
}
// ReadAcceleration reads the current acceleration from the device and returns
@@ -72,8 +76,7 @@ func (d *Device) Connected() bool {
// and the sensor is not moving the returned value will be around 1000000 or
// -1000000.
func (d *Device) ReadAcceleration() (x, y, z int32, err error) {
data := d.buf[:6]
err = legacy.ReadRegister(d.bus, uint8(d.AccelAddress), OUT_X_L_XL, data)
data, err := d.readBytes(d.AccelAddress, OUT_X_L_XL, 6)
if err != nil {
return
}
@@ -88,8 +91,7 @@ func (d *Device) ReadAcceleration() (x, y, z int32, err error) {
// rotation along one axis and while doing so integrate all values over time,
// you would get a value close to 360000000.
func (d *Device) ReadRotation() (x, y, z int32, err error) {
data := d.buf[:6]
err = legacy.ReadRegister(d.bus, uint8(d.AccelAddress), OUT_X_L_G, data)
data, err := d.readBytes(d.AccelAddress, OUT_X_L_G, 6)
if err != nil {
return
}
@@ -102,8 +104,7 @@ func (d *Device) ReadRotation() (x, y, z int32, err error) {
// ReadMagneticField reads the current magnetic field from the device and returns
// it in nT (nanotesla). 1 G (gauss) = 100_000 nT (nanotesla).
func (d *Device) ReadMagneticField() (x, y, z int32, err error) {
data := d.buf[:6]
err = legacy.ReadRegister(d.bus, uint8(d.MagAddress), OUT_X_L_M, data)
data, err := d.readBytes(d.MagAddress, OUT_X_L_M, 6)
if err != nil {
return
}
@@ -115,8 +116,7 @@ func (d *Device) ReadMagneticField() (x, y, z int32, err error) {
// ReadTemperature returns the temperature in Celsius milli degrees (°C/1000)
func (d *Device) ReadTemperature() (t int32, err error) {
data := d.buf[:2]
err = legacy.ReadRegister(d.bus, uint8(d.AccelAddress), OUT_TEMP_L, data)
data, err := d.readBytes(d.AccelAddress, OUT_TEMP_L, 2)
if err != nil {
return
}
@@ -167,20 +167,16 @@ func (d *Device) doConfigure(cfg Configuration) (err error) {
d.magMultiplier = 58
}
data := d.buf[:1]
// Configure accelerometer
// Sample rate & measurement range
data[0] = uint8(cfg.AccelSampleRate)<<5 | uint8(cfg.AccelRange)<<3
err = legacy.WriteRegister(d.bus, d.AccelAddress, CTRL_REG6_XL, data)
err = d.writeByte(d.AccelAddress, CTRL_REG6_XL, uint8(cfg.AccelSampleRate)<<5|uint8(cfg.AccelRange)<<3)
if err != nil {
return
}
// Configure gyroscope
// Sample rate & measurement range
data[0] = uint8(cfg.GyroSampleRate)<<5 | uint8(cfg.GyroRange)<<3
err = legacy.WriteRegister(d.bus, d.AccelAddress, CTRL_REG1_G, data)
err = d.writeByte(d.AccelAddress, CTRL_REG1_G, uint8(cfg.GyroSampleRate)<<5|uint8(cfg.GyroRange)<<3)
if err != nil {
return
}
@@ -190,33 +186,44 @@ func (d *Device) doConfigure(cfg Configuration) (err error) {
// Temperature compensation enabled
// High-performance mode XY axis
// Sample rate
data[0] = 0b10000000 | 0b01000000 | uint8(cfg.MagSampleRate)<<2
err = legacy.WriteRegister(d.bus, d.MagAddress, CTRL_REG1_M, data)
err = d.writeByte(d.MagAddress, CTRL_REG1_M, 0b10000000|0b01000000|uint8(cfg.MagSampleRate)<<2)
if err != nil {
return
}
// Measurement range
data[0] = uint8(cfg.MagRange) << 5
err = legacy.WriteRegister(d.bus, d.MagAddress, CTRL_REG2_M, data)
err = d.writeByte(d.MagAddress, CTRL_REG2_M, uint8(cfg.MagRange)<<5)
if err != nil {
return
}
// Continuous-conversion mode
// https://electronics.stackexchange.com/questions/237397/continuous-conversion-vs-single-conversion-mode
data[0] = 0b00000000
err = legacy.WriteRegister(d.bus, d.MagAddress, CTRL_REG3_M, data)
err = d.writeByte(d.MagAddress, CTRL_REG3_M, 0b00000000)
if err != nil {
return
}
// High-performance mode Z axis
data[0] = 0b00001000
err = legacy.WriteRegister(d.bus, d.MagAddress, CTRL_REG4_M, data)
err = d.writeByte(d.MagAddress, CTRL_REG4_M, 0b00001000)
if err != nil {
return
}
return nil
}
func (d *Device) readBytes(addr, reg, size uint8) ([]byte, error) {
d.buf[0] = reg
err := d.bus.Tx(uint16(addr), d.buf[0:1], d.buf[1:size+1])
if err != nil {
return nil, err
}
return d.buf[1 : size+1], nil
}
func (d *Device) writeByte(addr, reg, value uint8) error {
d.buf[0] = reg
d.buf[1] = value
return d.bus.Tx(uint16(addr), d.buf[0:2], nil)
}
+4 -4
View File
@@ -3,9 +3,9 @@ package max6675
import (
"errors"
"machine"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/pin"
)
// ErrThermocoupleOpen is returned when the thermocouple input is open.
@@ -14,16 +14,16 @@ var ErrThermocoupleOpen = errors.New("thermocouple input open")
type Device struct {
bus drivers.SPI
cs machine.Pin
cs pin.OutputFunc
}
// Create a new Device to read from a MAX6675 thermocouple.
// Pins must be configured before use. Frequency for SPI
// should be 4.3MHz maximum.
func NewDevice(bus drivers.SPI, cs machine.Pin) *Device {
func NewDevice(bus drivers.SPI, cs pin.Output) *Device {
return &Device{
bus: bus,
cs: cs,
cs: cs.Set,
}
}
+14 -9
View File
@@ -3,31 +3,36 @@
package max72xx
import (
"machine"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/legacy"
"tinygo.org/x/drivers/internal/pin"
)
type Device struct {
bus drivers.SPI
cs machine.Pin
bus drivers.SPI
cs pin.OutputFunc
configurePins func()
}
// NewDriver creates a new max7219 connection. The SPI wire must already be configured
// The SPI frequency must not be higher than 10MHz.
// parameter cs: the datasheet also refers to this pin as "load" pin.
func NewDevice(bus drivers.SPI, cs machine.Pin) *Device {
func NewDevice(bus drivers.SPI, cs pin.Output) *Device {
return &Device{
bus: bus,
cs: cs,
cs: cs.Set,
configurePins: func() {
legacy.ConfigurePinOut(cs)
},
}
}
// Configure setups the pins.
func (driver *Device) Configure() {
outPutConfig := machine.PinConfig{Mode: machine.PinOutput}
driver.cs.Configure(outPutConfig)
if driver.configurePins == nil {
panic(legacy.ErrConfigBeforeInstantiated)
}
driver.configurePins()
}
// SetScanLimit sets the scan limit. Maximum is 8.
+16 -8
View File
@@ -8,18 +8,20 @@ package mcp2515 // import "tinygo.org/x/drivers/mcp2515"
import (
"errors"
"fmt"
"machine"
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/legacy"
"tinygo.org/x/drivers/internal/pin"
)
// Device wraps MCP2515 SPI CAN Module.
type Device struct {
spi SPI
cs machine.Pin
msg *CANMsg
mcpMode byte
spi SPI
cs pin.OutputFunc
msg *CANMsg
mcpMode byte
configurePins func()
}
// CANMsg stores CAN message fields.
@@ -36,15 +38,18 @@ const (
)
// New returns a new MCP2515 driver. Pass in a fully configured SPI bus.
func New(b drivers.SPI, csPin machine.Pin) *Device {
func New(b drivers.SPI, csPin pin.Output) *Device {
d := &Device{
spi: SPI{
bus: b,
tx: make([]byte, 0, bufferSize),
rx: make([]byte, 0, bufferSize),
},
cs: csPin,
cs: csPin.Set,
msg: &CANMsg{},
configurePins: func() {
legacy.ConfigurePinOut(csPin)
},
}
return d
@@ -52,7 +57,10 @@ func New(b drivers.SPI, csPin machine.Pin) *Device {
// Configure sets up the device for communication.
func (d *Device) Configure() {
d.cs.Configure(machine.PinConfig{Mode: machine.PinOutput})
if d.configurePins == nil {
panic(legacy.ErrConfigBeforeInstantiated)
}
d.configurePins()
}
const beginTimeoutValue int = 10
+24 -16
View File
@@ -5,8 +5,9 @@ package onewire // import "tinygo.org/x/drivers/onewire"
import (
"errors"
"machine"
"time"
"tinygo.org/x/drivers/internal/pin"
)
// OneWire ROM commands
@@ -19,7 +20,8 @@ const (
// Device wraps a connection to an 1-Wire devices.
type Device struct {
p machine.Pin
set pin.OutputFunc
get pin.InputFunc
}
// Config wraps a configuration to an 1-Wire devices.
@@ -32,24 +34,30 @@ var (
errReadAddress = errors.New("Error: OneWire. Read address error: CRC mismatch.")
)
// New creates a new GPIO 1-Wire connection.
// The pin must be pulled up to the VCC via a resistor greater than 500 ohms (default 4.7k).
func New(p machine.Pin) Device {
return Device{
p: p,
// NewFromFuncs expects pin setter and getter for DQ line. Ideally this driver should receive
// a one-wire bus HAL abstraction, but I was asked to show how this could be done using pin HAL so here goes.
func NewFromFuncs(getPinLevel pin.InputFunc, setPinLevel pin.OutputFunc) *Device {
return &Device{
set: setPinLevel,
get: getPinLevel,
}
}
// Configure initializes the protocol.
func (d *Device) Configure(config Config) {}
// By setting the pin value one should configure as output and also expect a more
// consistent behaviour across all tinygo hosts by pulling DQ line low consistently. Win-win.
func (d *Device) cfgOut() { d.set.Low() }
func (d *Device) cfgIn() { d.get() }
// Reset pull DQ line low, then up.
func (d Device) Reset() error {
d.p.Configure(machine.PinConfig{Mode: machine.PinOutput})
d.cfgOut()
time.Sleep(480 * time.Microsecond)
d.p.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
d.cfgIn()
time.Sleep(70 * time.Microsecond)
precence := d.p.Get()
precence := d.get()
time.Sleep(410 * time.Microsecond)
if precence {
return errNoPresence
@@ -59,14 +67,14 @@ func (d Device) Reset() error {
// WriteBit transmits a bit to 1-Wire bus.
func (d Device) WriteBit(data uint8) {
d.p.Configure(machine.PinConfig{Mode: machine.PinOutput})
d.cfgOut()
if data&1 == 1 { // Send '1'
time.Sleep(5 * time.Microsecond)
d.p.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
d.cfgIn()
time.Sleep(60 * time.Microsecond)
} else { // Send '0'
time.Sleep(60 * time.Microsecond)
d.p.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
d.cfgIn()
time.Sleep(5 * time.Microsecond)
}
}
@@ -81,11 +89,11 @@ func (d Device) Write(data uint8) {
// ReadBit receives a bit from 1-Wire bus.
func (d Device) ReadBit() (data uint8) {
d.p.Configure(machine.PinConfig{Mode: machine.PinOutput})
d.cfgOut()
time.Sleep(3 * time.Microsecond)
d.p.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
d.cfgIn()
time.Sleep(8 * time.Microsecond)
if d.p.Get() {
if d.get() {
data = 1
}
time.Sleep(60 * time.Microsecond)
+31
View File
@@ -0,0 +1,31 @@
//go:build tinygo
package onewire
import (
"machine"
"tinygo.org/x/drivers/internal/legacy"
)
// New creates a new GPIO 1-Wire connection.
// The pin must be pulled up to the VCC via a resistor greater than 500 ohms (default 4.7k).
func New(p machine.Pin) Device {
isOut := false
return Device{
set: func(level bool) {
if !isOut {
legacy.ConfigurePinOut(p)
isOut = true
}
p.Set(level)
},
get: func() (level bool) {
if isOut {
legacy.ConfigurePinInputPullup(p)
isOut = false
}
return p.Get()
},
}
}
+8 -8
View File
@@ -6,18 +6,18 @@ package pcd8544 // import "tinygo.org/x/drivers/pcd8544"
import (
"errors"
"image/color"
"machine"
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/pin"
)
// Device wraps an SPI connection.
type Device struct {
bus drivers.SPI
dcPin machine.Pin
rstPin machine.Pin
scePin machine.Pin
dcPin pin.OutputFunc
rstPin pin.OutputFunc
scePin pin.OutputFunc
buffer []byte
width int16
height int16
@@ -30,12 +30,12 @@ type Config struct {
}
// New creates a new PCD8544 connection. The SPI bus must already be configured.
func New(bus drivers.SPI, dcPin, rstPin, scePin machine.Pin) *Device {
func New(bus drivers.SPI, dcPin, rstPin, scePin pin.Output) *Device {
return &Device{
bus: bus,
dcPin: dcPin,
rstPin: rstPin,
scePin: scePin,
dcPin: dcPin.Set,
rstPin: rstPin.Set,
scePin: scePin.Set,
}
}
+45 -3
View File
@@ -9,9 +9,30 @@ import (
"tinygo.org/x/drivers/pixel"
)
func TestImageRGB888(t *testing.T) {
image := pixel.NewImage[pixel.RGB888](5, 3)
if width, height := image.Size(); width != 5 || height != 3 {
t.Errorf("image.Size(): expected 5, 3 but got %d, %d", width, height)
}
for _, c := range []color.RGBA{
{R: 0xff, A: 0xff},
{G: 0xff, A: 0xff},
{B: 0xff, A: 0xff},
{R: 0x10, A: 0xff},
{G: 0x10, A: 0xff},
{B: 0x10, A: 0xff},
} {
image.Set(4, 2, pixel.NewColor[pixel.RGB888](c.R, c.G, c.B))
c2 := image.Get(4, 2).RGBA()
if c2 != c {
t.Errorf("failed to roundtrip color: expected %v but got %v", c, c2)
}
}
}
func TestImageRGB565BE(t *testing.T) {
image := pixel.NewImage[pixel.RGB565BE](5, 3)
if width, height := image.Size(); width != 5 && height != 3 {
if width, height := image.Size(); width != 5 || height != 3 {
t.Errorf("image.Size(): expected 5, 3 but got %d, %d", width, height)
}
for _, c := range []color.RGBA{
@@ -30,9 +51,30 @@ func TestImageRGB565BE(t *testing.T) {
}
}
func TestImageRGB555(t *testing.T) {
image := pixel.NewImage[pixel.RGB555](5, 3)
if width, height := image.Size(); width != 5 || height != 3 {
t.Errorf("image.Size(): expected 5, 3 but got %d, %d", width, height)
}
for _, c := range []color.RGBA{
{R: 0xff, A: 0xff},
{G: 0xff, A: 0xff},
{B: 0xff, A: 0xff},
{R: 0x10, A: 0xff},
{G: 0x10, A: 0xff},
{B: 0x10, A: 0xff},
} {
image.Set(4, 2, pixel.NewColor[pixel.RGB555](c.R, c.G, c.B))
c2 := image.Get(4, 2).RGBA()
if c2 != c {
t.Errorf("failed to roundtrip color: expected %v but got %v", c, c2)
}
}
}
func TestImageRGB444BE(t *testing.T) {
image := pixel.NewImage[pixel.RGB444BE](5, 3)
if width, height := image.Size(); width != 5 && height != 3 {
if width, height := image.Size(); width != 5 || height != 3 {
t.Errorf("image.Size(): expected 5, 3 but got %d, %d", width, height)
}
for _, c := range []color.RGBA{
@@ -67,7 +109,7 @@ func TestImageRGB444BE(t *testing.T) {
func TestImageMonochrome(t *testing.T) {
image := pixel.NewImage[pixel.Monochrome](128, 64)
if width, height := image.Size(); width != 128 && height != 64 {
if width, height := image.Size(); width != 128 || height != 64 {
t.Errorf("image.Size(): expected 128, 64 but got %d, %d", width, height)
}
for _, expected := range []color.RGBA{
+3 -3
View File
@@ -161,9 +161,9 @@ func (c RGB555) BitsPerPixel() int {
func (c RGB555) RGBA() color.RGBA {
color := color.RGBA{
R: uint8(c>>10) << 3,
G: uint8(c>>5) << 3,
B: uint8(c) << 3,
R: (uint8(c) & 0x1F) << 3,
G: (uint8(c>>5) & 0x1F) << 3,
B: (uint8(c>>10) & 0x1F) << 3,
A: 255,
}
// Correct color rounding, so that 0xff roundtrips back to 0xff.
+49
View File
@@ -0,0 +1,49 @@
package seesaw
import (
"errors"
)
var errInvalidEncoderNumber = errors.New("invalid encoder choice, 0-15 are supported")
// GetEncoderPosition returns the absolute position (or delta since the previous call) of the specified rotary encoder.
func (d *Device) GetEncoderPosition(encoder uint, asDelta bool) (int32, error) {
if encoder >= 16 {
return 0, errInvalidEncoderNumber
}
// The function address' upper nibble is the function, the lower nibble selects which encoder to communicate with
fnAddr := FunctionAddress(encoder)
if asDelta {
fnAddr |= FunctionEncoderDelta
} else {
fnAddr |= FunctionEncoderPosition
}
var buf [4]byte
err := d.Read(ModuleEncoderBase, fnAddr, buf[:])
if err != nil {
return 0, err
}
return int32(buf[0])<<24 | int32(buf[1])<<16 | int32(buf[2])<<8 | int32(buf[3]), nil
}
// SetEncoderPosition calibrate's the encoder's current absolute position to be whatever the provided position is.
func (d *Device) SetEncoderPosition(encoder uint, position int32) error {
if encoder >= 16 {
return errInvalidEncoderNumber
}
// The function address' upper nibble is the function, the lower nibble selects which encoder to communicate with
fnAddr := FunctionEncoderPosition | FunctionAddress(encoder)
buf := [4]byte{
byte(position >> 24),
byte(position >> 16),
byte(position >> 8),
byte(position),
}
return d.Write(ModuleEncoderBase, fnAddr, buf[:])
}
+10
View File
@@ -98,3 +98,13 @@ const (
FunctionKeypadCount FunctionAddress = 0x04
FunctionKeypadFifo FunctionAddress = 0x10
)
// encoder module function address registers
// these are the defaults for encoder 0, change the lower nibble to address other encoders
// see the Device.GetEncoderPosition and SetEncoderPosition methods for examples.
const (
FunctionEncoderIntenset FunctionAddress = 0x10
FunctionEncoderIntenclr FunctionAddress = 0x20
FunctionEncoderPosition FunctionAddress = 0x30
FunctionEncoderDelta FunctionAddress = 0x40
)
+19 -12
View File
@@ -2,7 +2,8 @@
package shiftregister
import (
"machine"
"tinygo.org/x/drivers/internal/legacy"
"tinygo.org/x/drivers/internal/pin"
)
type NumberBit int8
@@ -16,9 +17,10 @@ const (
// Device holds pin number
type Device struct {
latch, clock, out machine.Pin // IC wiring
bits NumberBit // Pin number
mask uint32 // keep all pins state
latch, clock, out pin.OutputFunc // IC wiring
config func()
bits NumberBit // Pin number
mask uint32 // keep all pins state
}
// ShiftPin is the implementation of the ShiftPin interface.
@@ -29,20 +31,25 @@ type ShiftPin struct {
}
// New returns a new shift output register device
func New(Bits NumberBit, Latch, Clock, Out machine.Pin) *Device {
func New(Bits NumberBit, Latch, Clock, Out pin.Output) *Device {
return &Device{
latch: Latch,
clock: Clock,
out: Out,
latch: Latch.Set,
clock: Clock.Set,
out: Out.Set,
bits: Bits,
config: func() {
legacy.ConfigurePinOut(Latch)
legacy.ConfigurePinOut(Clock)
legacy.ConfigurePinOut(Out)
},
}
}
// Configure set hardware configuration
func (d *Device) Configure() {
d.latch.Configure(machine.PinConfig{Mode: machine.PinOutput})
d.clock.Configure(machine.PinConfig{Mode: machine.PinOutput})
d.out.Configure(machine.PinConfig{Mode: machine.PinOutput})
if d.config == nil {
panic(legacy.ErrConfigBeforeInstantiated)
}
d.latch.High()
}
@@ -53,7 +60,7 @@ func (d *Device) WriteMask(mask uint32) {
d.latch.Low()
for i := 0; i < int(d.bits); i++ {
d.clock.Low()
d.out.Set(mask&1 != 0)
d.out(mask&1 != 0)
mask = mask >> 1
d.clock.High()
}
+64
View File
@@ -0,0 +1,64 @@
package si5351
// The I2C address which this device listens to.
const AddressDefault = 0x60 // Assumes ADDR pin is low
const AddressAlternative = 0x61 // Assumes ADDR pin is high
const (
OUTPUT_ENABLE_CONTROL = 3
CLK0_CONTROL = 16
CLK1_CONTROL = 17
CLK2_CONTROL = 18
CLK3_CONTROL = 19
CLK4_CONTROL = 20
CLK5_CONTROL = 21
CLK6_CONTROL = 22
CLK7_CONTROL = 23
MULTISYNTH0_PARAMETERS_1 = 42
MULTISYNTH0_PARAMETERS_3 = 44
MULTISYNTH1_PARAMETERS_1 = 50
MULTISYNTH1_PARAMETERS_3 = 52
MULTISYNTH2_PARAMETERS_1 = 58
MULTISYNTH2_PARAMETERS_3 = 60
SPREAD_SPECTRUM_PARAMETERS = 149
PLL_RESET = 177
CRYSTAL_INTERNAL_LOAD_CAPACITANCE = 183
)
const (
CRYSTAL_LOAD_6PF = (1 << 6)
CRYSTAL_LOAD_8PF = (2 << 6)
CRYSTAL_LOAD_10PF = (3 << 6)
)
const (
CRYSTAL_FREQ_25MHZ = 25000000
CRYSTAL_FREQ_27MHZ = 27000000
)
const (
PLL_A = iota
PLL_B
)
const (
R_DIV_1 = iota
R_DIV_2
R_DIV_4
R_DIV_8
R_DIV_16
R_DIV_32
R_DIV_64
R_DIV_128
)
const (
MULTISYNTH_DIV_4 = 4
MULTISYNTH_DIV_6 = 6
MULTISYNTH_DIV_8 = 8
)
+448
View File
@@ -0,0 +1,448 @@
package si5351
import (
"encoding/binary"
"errors"
"fmt"
"math"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/regmap"
)
// Device wraps an I2C connection to a SI5351 device.
type Device struct {
bus drivers.I2C
Address uint8
rw regmap.Device8I2C
initialised bool
crystalFreq uint32
crystalLoad uint8
pllaConfigured bool
pllaFreq uint32
pllbConfigured bool
pllbFreq uint32
lastRdivValue [3]uint8
}
var ErrNotInitialised = errors.New("Si5351 not initialised")
var ErrInvalidParameter = errors.New("Si5351 invalid parameter")
// New creates a new SI5351 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 {
rw := regmap.Device8I2C{}
rw.SetBus(bus, AddressDefault, binary.BigEndian)
return Device{
bus: bus,
rw: rw,
Address: AddressDefault,
crystalFreq: CRYSTAL_FREQ_25MHZ,
crystalLoad: CRYSTAL_LOAD_10PF,
}
}
// Configure sets up the device for communication
// TODO error handling
func (d *Device) Configure() error {
// // Disable all outputs setting CLKx_DIS high
d.rw.Write8(OUTPUT_ENABLE_CONTROL, 0xFF)
// Set the load capacitance for the XTAL
d.rw.Write8(CRYSTAL_INTERNAL_LOAD_CAPACITANCE, d.crystalLoad)
// Power down all output drivers
buf := []byte{CLK0_CONTROL, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}
d.bus.Tx(uint16(d.Address), buf, nil)
// Disable spread spectrum output.
if err := d.DisableSpreadSpectrum(); err != nil {
return err
}
d.initialised = true
return nil
}
// Connected returns whether a device at SI5351 address has been found.
func (d *Device) Connected() (bool, error) {
if err := d.bus.Tx(uint16(d.Address), []byte{}, []byte{0}); err != nil {
return false, err
}
return true, nil
}
// EnableSpreadSpectrum enables spread spectrum modulation to reduce EMI.
func (d *Device) EnableSpreadSpectrum() error {
data, err := d.rw.Read8(SPREAD_SPECTRUM_PARAMETERS)
if err != nil {
return err
}
data |= 0x80
return d.rw.Write8(SPREAD_SPECTRUM_PARAMETERS, data)
}
func (d *Device) DisableSpreadSpectrum() error {
data, err := d.rw.Read8(SPREAD_SPECTRUM_PARAMETERS)
if err != nil {
return err
}
data &^= 0x80
return d.rw.Write8(SPREAD_SPECTRUM_PARAMETERS, data)
}
func (d *Device) OutputEnable(output uint8, enable bool) error {
if !d.initialised {
return ErrNotInitialised
}
// Read the current value of the OUTPUT_ENABLE_CONTROL register
regVal, err := d.rw.Read8(OUTPUT_ENABLE_CONTROL)
if err != nil {
return err
}
// Modify regVal based on clk and enable
if enable {
regVal &= ^(1 << output)
} else {
regVal |= (1 << output)
}
// Write the modified value back to the OUTPUT_ENABLE_CONTROL register
return d.rw.Write8(OUTPUT_ENABLE_CONTROL, regVal)
}
func (d *Device) EnableOutputs() error {
if !d.initialised {
return ErrNotInitialised
}
return d.rw.Write8(OUTPUT_ENABLE_CONTROL, 0x00)
}
func (d *Device) DisableOutputs() error {
if !d.initialised {
return ErrNotInitialised
}
return d.rw.Write8(OUTPUT_ENABLE_CONTROL, 0xFF)
}
// ConfigurePLL sets the multiplier for the specified PLL
// pll The PLL to configure, which must be one of the following:
// - PLL_A
// - PLL_B
//
// mult The PLL integer multiplier (must be between 15 and 90)
//
// num The 20-bit numerator for fractional output (0..1,048,575).
// Set this to '0' for integer output.
//
// denom The 20-bit denominator for fractional output (1..1,048,575).
// Set this to '1' or higher to avoid divider by zero errors.
//
// PLL Configuration
// fVCO is the PLL output, and must be between 600..900MHz, where:
//
// fVCO = fXTAL * (a+(b/c))
//
// fXTAL = the crystal input frequency
// a = an integer between 15 and 90
// b = the fractional numerator (0..1,048,575)
// c = the fractional denominator (1..1,048,575)
//
// NOTE: Try to use integers whenever possible to avoid clock jitter
// (only use the a part, setting b to '0' and c to '1').
//
// See: http://www.silabs.com/Support%20Documents/TechnicalDocs/AN619.pdf
func (d *Device) ConfigurePLL(pll uint8, mult uint8, num uint32, denom uint32) error {
// Basic validation
if !d.initialised {
return ErrNotInitialised
}
// mult = 15..90
if !((mult > 14) && (mult < 91)) {
return ErrInvalidParameter
}
// Avoid divide by zero
if !(denom > 0) {
return ErrInvalidParameter
}
// 20-bit limit
if !(num <= 0xFFFFF) {
return ErrInvalidParameter
}
// 20-bit limit
if !(denom <= 0xFFFFF) {
return ErrInvalidParameter
}
// PLL Multiplier Equations
//
// P1 register is an 18-bit value using following formula:
//
// P1[17:0] = 128 * mult + floor(128*(num/denom)) - 512
//
// P2 register is a 20-bit value using the following formula:
//
// P2[19:0] = 128 * num - denom * floor(128*(num/denom))
//
// P3 register is a 20-bit value using the following formula:
//
// P3[19:0] = denom
//
// Set PLL config registers
var p1, p2, p3 uint32
if num == 0 {
// Integer mode
p1 = 128*uint32(mult) - 512
p2 = num
p3 = denom
} else {
// Fractional mode
p1 = uint32(128*float64(mult) + math.Floor(128*(float64(num)/float64(denom))) - 512)
p2 = uint32(128*float64(num) - float64(denom)*math.Floor(128*(float64(num)/float64(denom))))
p3 = denom
}
// Get the appropriate starting point for the PLL registers
baseaddr := uint8(26)
if pll == PLL_B {
baseaddr = 34
}
// The datasheet is a nightmare of typos and inconsistencies here!
data := [8]byte{}
data[0] = uint8((p3 & 0x0000FF00) >> 8)
data[1] = uint8(p3 & 0x000000FF)
data[2] = uint8((p1 & 0x00030000) >> 16)
data[3] = uint8((p1 & 0x0000FF00) >> 8)
data[4] = uint8(p1 & 0x000000FF)
data[5] = uint8(((p3 & 0x000F0000) >> 12) | ((p2 & 0x000F0000) >> 16))
data[6] = uint8((p2 & 0x0000FF00) >> 8)
data[7] = uint8(p2 & 0x000000FF)
if err := d.bus.Tx(uint16(baseaddr), data[:], nil); err != nil {
return err
}
// Reset both PLLs
if err := d.rw.Write8(PLL_RESET, (1<<7)|(1<<5)); err != nil {
return err
}
// Store the frequency settings for use with the Multisynth helper
fvco := float64(d.crystalFreq) * (float64(mult) + (float64(num) / float64(denom)))
if pll == PLL_A {
d.pllaConfigured = true
d.pllaFreq = uint32(math.Floor(fvco))
} else {
d.pllbConfigured = true
d.pllbFreq = uint32(math.Floor(fvco))
}
return nil
}
// ConfigureMultisynth divider, which determines the
// output clock frequency based on the specified PLL input.
//
// output The output channel to use (0..2)
//
// pll The PLL input source to use, which must be one of:
// - PLL_A
// - PLL_B
//
// div The integer divider for the Multisynth output.
//
// If pure integer values are used, this value must be one of:
// - MULTISYNTH_DIV_4
// - MULTISYNTH_DIV_6
// - MULTISYNTH_DIV_8
// If fractional output is used, this value must be between 8 and 900.
//
// num The 20-bit numerator for fractional output (0..1,048,575).
//
// Set this to '0' for integer output.
//
// denom The 20-bit denominator for fractional output (1..1,048,575).
//
// Set this to '1' or higher to avoid divide by zero errors.
//
// # Output Clock Configuration
//
// The multisynth dividers are applied to the specified PLL output,
// and are used to reduce the PLL output to a valid range (500kHz
// to 160MHz). The relationship can be seen in this formula, where
// fVCO is the PLL output frequency and MSx is the multisynth divider:
//
// fOUT = fVCO / MSx
//
// Valid multisynth dividers are 4, 6, or 8 when using integers,
// or any fractional values between 8 + 1/1,048,575 and 900 + 0/1
// The following formula is used for the fractional mode divider:
//
// a + b / c
//
// a = The integer value, which must be 4, 6 or 8 in integer mode (MSx_INT=1) or 8..900 in fractional mode (MSx_INT=0).
// b = The fractional numerator (0..1,048,575)
// c = The fractional denominator (1..1,048,575)
//
// NOTE: Try to use integers whenever possible to avoid clock jitter
// NOTE: For output frequencies > 150MHz, you must set the divider
//
// to 4 and adjust to PLL to generate the frequency (for example
// a PLL of 640 to generate a 160MHz output clock). This is not
// yet supported in the driver, which limits frequencies to 500kHz .. 150MHz.
//
// NOTE: For frequencies below 500kHz (down to 8kHz) Rx_DIV must be
//
// used, but this isn't currently implemented in the driver.
func (d *Device) ConfigureMultisynth(output uint8, pll uint8, div uint32, num uint32, denom uint32) error {
// Basic validation
if !d.initialised {
return ErrNotInitialised
}
// Channel range
if !(output < 3) {
return fmt.Errorf("output channel must be between 0 and 2")
}
// Divider integer value
if !((div > 3) && (div < 2049)) {
return ErrInvalidParameter
}
// Avoid divide by zero
if !(denom > 0) {
return ErrInvalidParameter
}
// 20-bit limit
if !(num <= 0xFFFFF) {
return ErrInvalidParameter
}
// 20-bit limit
if !(denom <= 0xFFFFF) {
return ErrInvalidParameter
}
// Make sure the requested PLL has been initialised
if pll == PLL_A && !d.pllaConfigured {
return ErrInvalidParameter
}
if pll == PLL_B && !d.pllbConfigured {
return ErrInvalidParameter
}
// Output Multisynth Divider Equations
//
// where: a = div, b = num and c = denom
//
// P1 register is an 18-bit value using following formula:
//
// P1[17:0] = 128 * a + floor(128*(b/c)) - 512
//
// P2 register is a 20-bit value using the following formula:
//
// P2[19:0] = 128 * b - c * floor(128*(b/c))
//
// P3 register is a 20-bit value using the following formula:
//
// P3[19:0] = c
//
// Set PLL config registers
var p1, p2, p3 uint32
if num == 0 {
// Integer mode
p1 = 128*div - 512
p2 = 0
p3 = denom
} else if denom == 1 {
// Fractional mode, simplified calculations
p1 = 128*div + 128*num - 512
p2 = 128*num - 128
p3 = 1
} else {
// Fractional mode
p1 = uint32(128*float64(div) + math.Floor(128*(float64(num)/float64(denom))) - 512)
p2 = uint32(128*float64(num) - float64(denom)*math.Floor(128*(float64(num)/float64(denom))))
p3 = denom
}
// Get the appropriate starting point for the PLL registers
baseaddr := uint8(0)
switch output {
case 0:
baseaddr = MULTISYNTH0_PARAMETERS_1
case 1:
baseaddr = MULTISYNTH1_PARAMETERS_1
case 2:
baseaddr = MULTISYNTH2_PARAMETERS_1
}
// Set the MSx config registers
data := [8]byte{}
data[0] = uint8((p3 & 0xFF00) >> 8)
data[1] = uint8(p3 & 0xFF)
data[2] = uint8(((p1 & 0x30000) >> 16)) | d.lastRdivValue[output]
data[3] = uint8((p1 & 0xFF00) >> 8)
data[4] = uint8(p1 & 0xFF)
data[5] = uint8(((p3 & 0xF0000) >> 12) | ((p2 & 0xF0000) >> 16))
data[6] = uint8((p2 & 0xFF00) >> 8)
data[7] = uint8(p2 & 0xFF)
if err := d.bus.Tx(uint16(baseaddr), data[:], nil); err != nil {
return err
}
// Configure the clk control and enable the output
// TODO: Check if the clk control byte needs to be updated.
clkControlReg := uint8(0x0F) // 8mA drive strength, MS0 as CLK0 source, Clock not inverted, powered up
if pll == PLL_B {
clkControlReg |= (1 << 5) // Uses PLLB
}
if num == 0 {
clkControlReg |= (1 << 6) // Integer mode
}
var register uint8
switch output {
case 0:
register = CLK0_CONTROL
case 1:
register = CLK1_CONTROL
case 2:
register = CLK2_CONTROL
}
return d.rw.Write8(register, clkControlReg)
}
func (d *Device) ConfigureRdiv(output uint8, div uint8) error {
// Channel range
if !(output < 3) {
return ErrInvalidParameter
}
var register uint8
switch output {
case 0:
register = MULTISYNTH0_PARAMETERS_3
case 1:
register = MULTISYNTH1_PARAMETERS_3
case 2:
register = MULTISYNTH2_PARAMETERS_3
}
data, err := d.rw.Read8(register)
if err != nil {
return err
}
d.lastRdivValue[output] = (div & 0x07) << 4
data = (data & 0x0F) | d.lastRdivValue[output]
return d.rw.Write8(register, data)
}
+8 -3
View File
@@ -44,6 +44,7 @@ tinygo build -size short -o ./build/test.hex -target=pyportal ./examples/ili9341
tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/lis3dh/main.go
tinygo build -size short -o ./build/test.hex -target=nano-33-ble ./examples/lps22hb/main.go
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/lsm303agr/main.go
tinygo build -size short -o ./build/test.hex -target=feather-m4 ./examples/lsm303dlhc/main.go
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/lsm6ds3/main.go
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/mag3110/main.go
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/mcp23017/main.go
@@ -58,15 +59,17 @@ tinygo build -size short -o ./build/test.hex -target=p1am-100 ./examples/p1am/ma
tinygo build -size short -o ./build/test.hex -target=pico ./examples/pca9685/main.go
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/pcd8544/setbuffer/main.go
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/pcd8544/setpixel/main.go
tinygo build -size short -o ./build/test.hex -target=feather-rp2040 ./examples/seesaw
tinygo build -size short -o ./build/test.hex -target=feather-rp2040 ./examples/seesaw/soil-sensor
tinygo build -size short -o ./build/test.hex -target=qtpy-rp2040 ./examples/seesaw/rotary-encoder
tinygo build -size short -o ./build/test.hex -target=arduino ./examples/servo
tinygo build -size short -o ./build/test.hex -target=pico ./examples/sgp30
tinygo build -size short -o ./build/test.hex -target=pybadge ./examples/shifter/main.go
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/sht3x/main.go
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/sht4x/main.go
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/shtc3/main.go
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/ssd1306/i2c_128x32/main.go
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/ssd1306/spi_128x64/main.go
tinygo build -size short -o ./build/test.hex -target=xiao-ble ./examples/ssd1306/
tinygo build -size short -o ./build/test.hex -target=xiao-rp2040 ./examples/ssd1306/
tinygo build -size short -o ./build/test.hex -target=thumby ./examples/ssd1306/
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/ssd1331/main.go
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/st7735/main.go
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/st7789/main.go
@@ -140,6 +143,8 @@ 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
tinygo build -size short -o ./build/test.hex -target=pico ./examples/si5351/main.go
# network examples (espat)
tinygo build -size short -o ./build/test.hex -target=challenger-rp2040 ./examples/net/ntpclient/
# network examples (wifinina)
+2
View File
@@ -1,3 +1,5 @@
//go:build tinygo
package ssd1289
import "machine"
+18 -16
View File
@@ -7,6 +7,9 @@ import (
"image/color"
"machine"
"time"
"tinygo.org/x/drivers/internal/legacy"
"tinygo.org/x/drivers/internal/pin"
)
type Bus interface {
@@ -14,10 +17,10 @@ type Bus interface {
}
type Device struct {
rs machine.Pin
wr machine.Pin
cs machine.Pin
rst machine.Pin
rs pin.OutputFunc
wr pin.OutputFunc
cs pin.OutputFunc
rst pin.OutputFunc
bus Bus
}
@@ -26,21 +29,20 @@ const height = int16(320)
func New(rs machine.Pin, wr machine.Pin, cs machine.Pin, rst machine.Pin, bus Bus) Device {
d := Device{
rs: rs,
wr: wr,
cs: cs,
rst: rst,
rs: rs.Set,
wr: wr.Set,
cs: cs.Set,
rst: rst.Set,
bus: bus,
}
legacy.ConfigurePinOut(rs)
legacy.ConfigurePinOut(wr)
legacy.ConfigurePinOut(cs)
legacy.ConfigurePinOut(rst)
rs.Configure(machine.PinConfig{Mode: machine.PinOutput})
wr.Configure(machine.PinConfig{Mode: machine.PinOutput})
cs.Configure(machine.PinConfig{Mode: machine.PinOutput})
rst.Configure(machine.PinConfig{Mode: machine.PinOutput})
cs.High()
rst.High()
wr.High()
cs.Set(true)
rst.Set(true)
wr.Set(true)
return d
}
+29 -137
View File
@@ -6,11 +6,9 @@ package ssd1306 // import "tinygo.org/x/drivers/ssd1306"
import (
"errors"
"image/color"
"machine"
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/legacy"
"tinygo.org/x/drivers/pixel"
)
@@ -23,16 +21,15 @@ type ResetValue [2]byte
// Device wraps I2C or SPI connection.
type Device struct {
bus Buser
buffer []byte
width int16
height int16
bufferSize int16
vccState VccMode
canReset bool
resetCol ResetValue
resetPage ResetValue
rotation drivers.Rotation
bus Buser
buffer []byte
width int16
height int16
vccState VccMode
canReset bool
resetCol ResetValue
resetPage ResetValue
rotation drivers.Rotation
}
// Config is the configuration for the display
@@ -51,51 +48,15 @@ type Config struct {
Rotation drivers.Rotation
}
type I2CBus struct {
wire drivers.I2C
Address uint16
}
type SPIBus struct {
wire drivers.SPI
dcPin machine.Pin
resetPin machine.Pin
csPin machine.Pin
}
type Buser interface {
configure() error
tx(data []byte, isCommand bool) error
setAddress(address uint16) error
configure(address uint16, size int16) []byte // configure the bus and return the image buffer to use
command(cmd uint8) error // send a command to the display
flush() error // send the image to the display, faster than "tx()" in i2c case since avoids slice copy
tx(data []byte, isCommand bool) error // generic transmit function
}
type VccMode uint8
// NewI2C creates a new SSD1306 connection. The I2C wire must already be configured.
func NewI2C(bus drivers.I2C) Device {
return Device{
bus: &I2CBus{
wire: bus,
Address: Address,
},
}
}
// NewSPI creates a new SSD1306 connection. The SPI wire must already be configured.
func NewSPI(bus drivers.SPI, dcPin, resetPin, csPin machine.Pin) Device {
dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
resetPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
return Device{
bus: &SPIBus{
wire: bus,
dcPin: dcPin,
resetPin: resetPin,
csPin: csPin,
},
}
}
// Configure initializes the display with default configuration
func (d *Device) Configure(cfg Config) {
var zeroReset ResetValue
@@ -109,9 +70,6 @@ func (d *Device) Configure(cfg Config) {
} else {
d.height = 64
}
if cfg.Address != 0 {
d.bus.setAddress(cfg.Address)
}
if cfg.VccState != 0 {
d.vccState = cfg.VccState
} else {
@@ -127,11 +85,9 @@ func (d *Device) Configure(cfg Config) {
} else {
d.resetPage = ResetValue{0, uint8(d.height/8) - 1}
}
d.bufferSize = d.width * d.height / 8
d.buffer = make([]byte, d.bufferSize)
d.canReset = cfg.Address != 0 || d.width != 128 || d.height != 64 // I2C or not 128x64
d.bus.configure()
d.buffer = d.bus.configure(cfg.Address, d.width*d.height/8)
time.Sleep(100 * time.Nanosecond)
d.Command(DISPLAYOFF)
@@ -193,11 +149,22 @@ func (d *Device) Configure(cfg Config) {
d.Command(NORMALDISPLAY)
d.Command(DEACTIVATE_SCROLL)
d.Command(DISPLAYON)
}
// Command sends a command to the display
func (d *Device) Command(command uint8) {
d.bus.command(command)
}
// Tx sends data to the display; if isCommand is false, this also updates the image buffer.
func (d *Device) Tx(data []byte, isCommand bool) error {
return d.bus.tx(data, isCommand)
}
// ClearBuffer clears the image buffer
func (d *Device) ClearBuffer() {
for i := int16(0); i < d.bufferSize; i++ {
for i := 0; i < len(d.buffer); i++ {
d.buffer[i] = 0
}
}
@@ -223,7 +190,7 @@ func (d *Device) Display() error {
d.Command(d.resetPage[1])
}
return d.Tx(d.buffer, false)
return d.bus.flush()
}
// SetPixel enables or disables a pixel in the buffer
@@ -252,12 +219,10 @@ func (d *Device) GetPixel(x int16, y int16) bool {
// SetBuffer changes the whole buffer at once
func (d *Device) SetBuffer(buffer []byte) error {
if int16(len(buffer)) != d.bufferSize {
if len(buffer) != len(d.buffer) {
return errBufferSize
}
for i := int16(0); i < d.bufferSize; i++ {
d.buffer[i] = buffer[i]
}
copy(d.buffer, buffer)
return nil
}
@@ -266,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
+52
View File
@@ -0,0 +1,52 @@
package ssd1306
import (
"tinygo.org/x/drivers"
)
type I2CBus struct {
wire drivers.I2C
address uint16
buffer []byte // buffer to avoid heap allocations
}
// NewI2C creates a new SSD1306 connection. The I2C wire must already be configured.
func NewI2C(bus drivers.I2C) *Device {
return &Device{
bus: &I2CBus{
wire: bus,
address: Address,
},
}
}
// configure address for the I2C bus and allocate the buffer
func (b *I2CBus) configure(address uint16, size int16) []byte {
if address != 0 {
b.address = address
}
b.buffer = make([]byte, size+2) // +1 for the mode and +1 for a command
return b.buffer[2:] // return the image buffer
}
// command sends a command to the display
func (b *I2CBus) command(cmd uint8) error {
b.buffer[0] = 0x00 // Command mode
b.buffer[1] = cmd
return b.wire.Tx(b.address, b.buffer[:2], nil)
}
// flush sends the image to the display
func (b *I2CBus) flush() error {
b.buffer[1] = 0x40 // Data mode
return b.wire.Tx(b.address, b.buffer[1:], nil)
}
// tx sends data to the display
func (b *I2CBus) tx(data []byte, isCommand bool) error {
if isCommand {
return b.command(data[0])
}
copy(b.buffer[2:], data)
return b.flush()
}
+68
View File
@@ -0,0 +1,68 @@
package ssd1306
import (
"machine"
"time"
"tinygo.org/x/drivers"
)
type SPIBus struct {
wire drivers.SPI
dcPin machine.Pin
resetPin machine.Pin
csPin machine.Pin
buffer []byte // buffer to avoid heap allocations
}
// NewSPI creates a new SSD1306 connection. The SPI wire must already be configured.
func NewSPI(bus drivers.SPI, dcPin, resetPin, csPin machine.Pin) *Device {
dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
resetPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
return &Device{
bus: &SPIBus{
wire: bus,
dcPin: dcPin,
resetPin: resetPin,
csPin: csPin,
},
}
}
// configure pins with the SPI bus and allocate the buffer
func (b *SPIBus) configure(address uint16, size int16) []byte {
b.csPin.Low()
b.dcPin.Low()
b.resetPin.Low()
b.resetPin.High()
time.Sleep(1 * time.Millisecond)
b.resetPin.Low()
time.Sleep(10 * time.Millisecond)
b.resetPin.High()
b.buffer = make([]byte, size+1) // +1 for a command
return b.buffer[1:] // return the image buffer
}
// command sends a command to the display
func (b *SPIBus) command(cmd uint8) error {
b.buffer[0] = cmd
return b.tx(b.buffer[:1], true)
}
// flush sends the image to the display
func (b *SPIBus) flush() error {
return b.tx(b.buffer[1:], false)
}
// tx sends data to the display
func (b *SPIBus) tx(data []byte, isCommand bool) error {
b.csPin.High()
b.dcPin.Set(!isCommand)
b.csPin.Low()
err := b.wire.Tx(data, nil)
b.csPin.High()
return err
}
+13 -12
View File
@@ -5,12 +5,13 @@ package ssd1331 // import "tinygo.org/x/drivers/ssd1331"
import (
"image/color"
"machine"
"errors"
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/legacy"
"tinygo.org/x/drivers/internal/pin"
)
type Model uint8
@@ -19,9 +20,9 @@ type Rotation uint8
// Device wraps an SPI connection.
type Device struct {
bus drivers.SPI
dcPin machine.Pin
resetPin machine.Pin
csPin machine.Pin
dcPin pin.OutputFunc
resetPin pin.OutputFunc
csPin pin.OutputFunc
width int16
height int16
batchLength int16
@@ -36,15 +37,15 @@ type Config struct {
}
// New creates a new SSD1331 connection. The SPI wire must already be configured.
func New(bus drivers.SPI, resetPin, dcPin, csPin machine.Pin) Device {
dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
resetPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
func New(bus drivers.SPI, resetPin, dcPin, csPin pin.Output) Device {
legacy.ConfigurePinOut(dcPin)
legacy.ConfigurePinOut(resetPin)
legacy.ConfigurePinOut(csPin)
return Device{
bus: bus,
dcPin: dcPin,
resetPin: resetPin,
csPin: csPin,
dcPin: dcPin.Set,
resetPin: resetPin.Set,
csPin: csPin.Set,
}
}
@@ -251,7 +252,7 @@ func (d *Device) Data(data uint8) {
// Tx sends data to the display
func (d *Device) Tx(data []byte, isCommand bool) {
d.dcPin.Set(!isCommand)
d.dcPin(!isCommand)
d.bus.Tx(data, nil)
}
+32 -24
View File
@@ -6,10 +6,11 @@ package ssd1351 // import "tinygo.org/x/drivers/ssd1351"
import (
"errors"
"image/color"
"machine"
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/legacy"
"tinygo.org/x/drivers/internal/pin"
)
var (
@@ -19,17 +20,18 @@ var (
// Device wraps an SPI connection.
type Device struct {
bus drivers.SPI
dcPin machine.Pin
resetPin machine.Pin
csPin machine.Pin
enPin machine.Pin
rwPin machine.Pin
width int16
height int16
rowOffset int16
columnOffset int16
bufferLength int16
bus drivers.SPI
dcPin pin.OutputFunc
resetPin pin.OutputFunc
csPin pin.OutputFunc
enPin pin.OutputFunc
rwPin pin.OutputFunc
configurePins func()
width int16
height int16
rowOffset int16
columnOffset int16
bufferLength int16
}
// Config is the configuration for the display
@@ -41,19 +43,29 @@ type Config struct {
}
// New creates a new SSD1351 connection. The SPI wire must already be configured.
func New(bus drivers.SPI, resetPin, dcPin, csPin, enPin, rwPin machine.Pin) Device {
func New(bus drivers.SPI, resetPin, dcPin, csPin, enPin, rwPin pin.Output) Device {
return Device{
bus: bus,
dcPin: dcPin,
resetPin: resetPin,
csPin: csPin,
enPin: enPin,
rwPin: rwPin,
dcPin: dcPin.Set,
resetPin: resetPin.Set,
csPin: csPin.Set,
enPin: enPin.Set,
rwPin: rwPin.Set,
configurePins: func() {
legacy.ConfigurePinOut(dcPin)
legacy.ConfigurePinOut(resetPin)
legacy.ConfigurePinOut(csPin)
legacy.ConfigurePinOut(enPin)
legacy.ConfigurePinOut(rwPin)
},
}
}
// Configure initializes the display with default configuration
func (d *Device) Configure(cfg Config) {
if d.configurePins == nil {
panic(legacy.ErrConfigBeforeInstantiated)
}
if cfg.Width == 0 {
cfg.Width = 128
}
@@ -73,11 +85,7 @@ func (d *Device) Configure(cfg Config) {
}
// configure GPIO pins
d.dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
d.resetPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
d.csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
d.enPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
d.rwPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
d.configurePins()
// reset the device
d.resetPin.High()
@@ -278,7 +286,7 @@ func (d *Device) Data(data uint8) {
// Tx sends data to the display
func (d *Device) Tx(data []byte, isCommand bool) {
d.dcPin.Set(!isCommand)
d.dcPin(!isCommand)
d.csPin.Low()
d.bus.Tx(data, nil)
d.csPin.High()
+17 -16
View File
@@ -5,12 +5,13 @@ package st7735 // import "tinygo.org/x/drivers/st7735"
import (
"image/color"
"machine"
"time"
"errors"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/legacy"
"tinygo.org/x/drivers/internal/pin"
"tinygo.org/x/drivers/pixel"
)
@@ -39,10 +40,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 pin.OutputFunc
resetPin pin.OutputFunc
csPin pin.OutputFunc
blPin pin.OutputFunc
width int16
height int16
columnOffset int16
@@ -65,23 +66,23 @@ type Config struct {
}
// New creates a new ST7735 connection. The SPI wire must already be configured.
func New(bus drivers.SPI, resetPin, dcPin, csPin, blPin machine.Pin) Device {
func New(bus drivers.SPI, resetPin, dcPin, csPin, blPin pin.Output) 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 pin.Output) DeviceOf[T] {
legacy.ConfigurePinOut(dcPin)
legacy.ConfigurePinOut(resetPin)
legacy.ConfigurePinOut(csPin)
legacy.ConfigurePinOut(blPin)
return DeviceOf[T]{
bus: bus,
dcPin: dcPin,
resetPin: resetPin,
csPin: csPin,
blPin: blPin,
dcPin: dcPin.Set,
resetPin: resetPin.Set,
csPin: csPin.Set,
blPin: blPin.Set,
}
}
@@ -423,7 +424,7 @@ func (d *DeviceOf[T]) Data(data uint8) {
// Tx sends data to the display
func (d *DeviceOf[T]) Tx(data []byte, isCommand bool) {
d.dcPin.Set(!isCommand)
d.dcPin(!isCommand)
d.bus.Tx(data, nil)
}
+22 -17
View File
@@ -7,13 +7,14 @@ package st7789 // import "tinygo.org/x/drivers/st7789"
import (
"image/color"
"machine"
"math"
"time"
"errors"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/legacy"
"tinygo.org/x/drivers/internal/pin"
"tinygo.org/x/drivers/pixel"
)
@@ -46,10 +47,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 pin.OutputFunc
resetPin pin.OutputFunc
csPin pin.OutputFunc
blPin pin.OutputFunc
width int16
height int16
columnOffsetCfg int16
@@ -83,23 +84,27 @@ type Config struct {
}
// New creates a new ST7789 connection. The SPI wire must already be configured.
func New(bus drivers.SPI, resetPin, dcPin, csPin, blPin machine.Pin) Device {
func New(bus drivers.SPI, resetPin, dcPin, csPin, blPin pin.Output) Device {
return NewOf[pixel.RGB565BE](bus, resetPin, dcPin, csPin, blPin)
}
// NewOf creates a new ST7789 connection with a particular pixel format. The SPI
// wire must already be configured.
func NewOf[T Color](bus drivers.SPI, resetPin, dcPin, csPin, blPin machine.Pin) DeviceOf[T] {
dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
resetPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
blPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
func NewOf[T Color](bus drivers.SPI, resetPin, dcPin, csPin, blPin pin.Output) DeviceOf[T] {
legacy.ConfigurePinOut(dcPin)
legacy.ConfigurePinOut(resetPin)
legacy.ConfigurePinOut(csPin)
legacy.ConfigurePinOut(blPin)
var cs pin.OutputFunc
if !legacy.PinIsNoPin(csPin) {
cs = csPin.Set
}
return DeviceOf[T]{
bus: bus,
dcPin: dcPin,
resetPin: resetPin,
csPin: csPin,
blPin: blPin,
dcPin: dcPin.Set,
resetPin: resetPin.Set,
csPin: cs,
blPin: blPin.Set,
}
}
@@ -229,7 +234,7 @@ func (d *DeviceOf[T]) sendCommand(command uint8, data []byte) error {
// startWrite must be called at the beginning of all exported methods to set the
// chip select pin low.
func (d *DeviceOf[T]) startWrite() {
if d.csPin != machine.NoPin {
if d.csPin != nil {
d.csPin.Low()
}
}
@@ -237,7 +242,7 @@ func (d *DeviceOf[T]) startWrite() {
// endWrite must be called at the end of all exported methods to set the chip
// select pin high.
func (d *DeviceOf[T]) endWrite() {
if d.csPin != machine.NoPin {
if d.csPin != nil {
d.csPin.High()
}
}
+2
View File
@@ -1,3 +1,5 @@
//go:build tinygo
package sx127x
import (
+4 -4
View File
@@ -6,10 +6,10 @@ package sx127x
import (
"errors"
"machine"
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/pin"
"tinygo.org/x/drivers/lora"
)
@@ -22,7 +22,7 @@ const (
// Device wraps an SPI connection to a SX127x device.
type Device struct {
spi drivers.SPI // SPI bus for module communication
rstPin machine.Pin // GPIO for reset
rstPin pin.OutputFunc // GPIO for reset
radioEventChan chan lora.RadioEvent // Channel for Receiving events
loraConf lora.Config // Current Lora configuration
controller RadioController // to manage interactions with the radio
@@ -43,10 +43,10 @@ func (d *Device) GetRadioEventChan() chan lora.RadioEvent {
}
// New creates a new SX127x connection. The SPI bus must already be configured.
func New(spi drivers.SPI, rstPin machine.Pin) *Device {
func New(spi drivers.SPI, rstPin pin.Output) *Device {
k := Device{
spi: spi,
rstPin: rstPin,
rstPin: rstPin.Set,
radioEventChan: make(chan lora.RadioEvent, RADIOEVENTCHAN_SIZE),
spiTxBuf: make([]byte, SPI_BUFFER_SIZE),
spiRxBuf: make([]byte, SPI_BUFFER_SIZE),
+18 -17
View File
@@ -8,10 +8,11 @@ package uc8151 // import "tinygo.org/x/drivers/uc8151"
import (
"errors"
"image/color"
"machine"
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/legacy"
"tinygo.org/x/drivers/internal/pin"
"tinygo.org/x/drivers/pixel"
)
@@ -31,10 +32,10 @@ type Config struct {
type Device struct {
bus drivers.SPI
cs machine.Pin
dc machine.Pin
rst machine.Pin
busy machine.Pin
cs pin.OutputFunc
dc pin.OutputFunc
rst pin.OutputFunc
isBusy pin.InputFunc
width int16
height int16
buffer []uint8
@@ -49,17 +50,17 @@ type Device struct {
type Speed uint8
// New returns a new uc8151 driver. Pass in a fully configured SPI bus.
func New(bus drivers.SPI, csPin, dcPin, rstPin, busyPin machine.Pin) Device {
csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
rstPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
busyPin.Configure(machine.PinConfig{Mode: machine.PinInput})
func New(bus drivers.SPI, csPin, dcPin, rstPin pin.Output, busyPin pin.Input) Device {
legacy.ConfigurePinOut(csPin)
legacy.ConfigurePinOut(dcPin)
legacy.ConfigurePinOut(rstPin)
legacy.ConfigurePinInput(busyPin)
return Device{
bus: bus,
cs: csPin,
dc: dcPin,
rst: rstPin,
busy: busyPin,
bus: bus,
cs: csPin.Set,
dc: dcPin.Set,
rst: rstPin.Set,
isBusy: busyPin.Get,
}
}
@@ -313,14 +314,14 @@ func (d *Device) ClearDisplay() {
// WaitUntilIdle waits until the display is ready
func (d *Device) WaitUntilIdle() {
for !d.busy.Get() {
for !d.isBusy() {
time.Sleep(10 * time.Millisecond)
}
}
// IsBusy returns the busy status of the display
func (d *Device) IsBusy() bool {
return d.busy.Get()
return d.isBusy()
}
// ClearBuffer sets the buffer to 0xFF (white)
+1 -1
View File
@@ -2,4 +2,4 @@ package drivers
// Version returns a user-readable string showing the version of the drivers package for support purposes.
// Update this value before release of new version of software.
const Version = "0.32.0"
const Version = "0.33.0"
+32 -23
View File
@@ -12,6 +12,9 @@ import (
"image/color"
"machine"
"time"
"tinygo.org/x/drivers/internal/legacy"
"tinygo.org/x/drivers/internal/pin"
)
type Config struct {
@@ -22,14 +25,14 @@ type Config struct {
}
type Device struct {
bus *machine.SPI
cs machine.Pin
dc machine.Pin
rst machine.Pin
busy machine.Pin
buffer []uint8
rotation Rotation
bus *machine.SPI
cs pin.OutputFunc
dc pin.OutputFunc
rst pin.OutputFunc
isBusy pin.InputFunc
configurePins func()
buffer []uint8
rotation Rotation
}
type Rotation uint8
@@ -79,22 +82,28 @@ var partialRefresh = [159]uint8{
}
// New returns a new epd1in54 driver. Pass in a fully configured SPI bus.
func New(bus *machine.SPI, csPin, dcPin, rstPin, busyPin machine.Pin) Device {
func New(bus *machine.SPI, csPin, dcPin, rstPin pin.Output, busyPin pin.Input) Device {
return Device{
buffer: make([]uint8, (uint32(Width)*uint32(Height))/8),
bus: bus,
cs: csPin,
dc: dcPin,
rst: rstPin,
busy: busyPin,
cs: csPin.Set,
dc: dcPin.Set,
rst: rstPin.Set,
isBusy: busyPin.Get,
configurePins: func() {
legacy.ConfigurePinOut(csPin)
legacy.ConfigurePinOut(dcPin)
legacy.ConfigurePinOut(rstPin)
legacy.ConfigurePinInput(busyPin)
},
}
}
func (d *Device) LDirInit(cfg Config) {
d.cs.Configure(machine.PinConfig{Mode: machine.PinOutput})
d.rst.Configure(machine.PinConfig{Mode: machine.PinOutput})
d.dc.Configure(machine.PinConfig{Mode: machine.PinOutput})
d.busy.Configure(machine.PinConfig{Mode: machine.PinInput})
if d.configurePins == nil {
panic(legacy.ErrConfigBeforeInstantiated)
}
d.configurePins()
d.bus.Configure(machine.SPIConfig{
Frequency: 2000000,
@@ -150,10 +159,10 @@ func (d *Device) LDirInit(cfg Config) {
}
func (d *Device) HDirInit(cfg Config) {
d.cs.Configure(machine.PinConfig{Mode: machine.PinOutput})
d.rst.Configure(machine.PinConfig{Mode: machine.PinOutput})
d.dc.Configure(machine.PinConfig{Mode: machine.PinOutput})
d.busy.Configure(machine.PinConfig{Mode: machine.PinInput})
if d.configurePins == nil {
panic(legacy.ErrConfigBeforeInstantiated)
}
d.configurePins()
d.bus.Configure(machine.SPIConfig{
Frequency: 2000000,
@@ -369,7 +378,7 @@ func (d *Device) Clear() {
// WaitUntilIdle waits until the display is ready
func (d *Device) WaitUntilIdle() {
for d.busy.Get() {
for d.isBusy() {
time.Sleep(100 * time.Millisecond)
}
time.Sleep(200 * time.Millisecond)
@@ -377,7 +386,7 @@ func (d *Device) WaitUntilIdle() {
// IsBusy returns the busy status of the display
func (d *Device) IsBusy() bool {
return d.busy.Get()
return d.isBusy()
}
// ClearBuffer sets the buffer to 0xFF (white)
+18 -17
View File
@@ -6,10 +6,11 @@ package epd2in13 // import "tinygo.org/x/drivers/waveshare-epd/epd2in13"
import (
"errors"
"image/color"
"machine"
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/legacy"
"tinygo.org/x/drivers/internal/pin"
)
type Config struct {
@@ -21,10 +22,10 @@ type Config struct {
type Device struct {
bus drivers.SPI
cs machine.Pin
dc machine.Pin
rst machine.Pin
busy machine.Pin
cs pin.OutputFunc
dc pin.OutputFunc
rst pin.OutputFunc
isBusy pin.InputFunc
logicalWidth int16
width int16
height int16
@@ -53,17 +54,17 @@ var lutPartialUpdate = [30]uint8{
}
// New returns a new epd2in13x driver. Pass in a fully configured SPI bus.
func New(bus drivers.SPI, csPin, dcPin, rstPin, busyPin machine.Pin) Device {
csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
rstPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
busyPin.Configure(machine.PinConfig{Mode: machine.PinInput})
func New(bus drivers.SPI, csPin, dcPin, rstPin pin.Output, busyPin pin.Input) Device {
legacy.ConfigurePinOut(csPin)
legacy.ConfigurePinOut(dcPin)
legacy.ConfigurePinOut(rstPin)
legacy.ConfigurePinInput(busyPin)
return Device{
bus: bus,
cs: csPin,
dc: dcPin,
rst: rstPin,
busy: busyPin,
bus: bus,
cs: csPin.Set,
dc: dcPin.Set,
rst: rstPin.Set,
isBusy: busyPin.Get,
}
}
@@ -298,14 +299,14 @@ func (d *Device) setMemoryPointer(x int16, y int16) {
// WaitUntilIdle waits until the display is ready
func (d *Device) WaitUntilIdle() {
for d.busy.Get() {
for d.isBusy() {
time.Sleep(100 * time.Millisecond)
}
}
// IsBusy returns the busy status of the display
func (d *Device) IsBusy() bool {
return d.busy.Get()
return d.isBusy()
}
// ClearBuffer sets the buffer to 0xFF (white)
+18 -17
View File
@@ -6,10 +6,11 @@ package epd2in13x // import "tinygo.org/x/drivers/waveshare-epd/epd2in13x"
import (
"errors"
"image/color"
"machine"
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/legacy"
"tinygo.org/x/drivers/internal/pin"
)
type Config struct {
@@ -20,10 +21,10 @@ type Config struct {
type Device struct {
bus drivers.SPI
cs machine.Pin
dc machine.Pin
rst machine.Pin
busy machine.Pin
cs pin.OutputFunc
dc pin.OutputFunc
rst pin.OutputFunc
isBusy pin.InputFunc
width int16
height int16
buffer [][]uint8
@@ -33,17 +34,17 @@ type Device struct {
type Color uint8
// New returns a new epd2in13x driver. Pass in a fully configured SPI bus.
func New(bus drivers.SPI, csPin, dcPin, rstPin, busyPin machine.Pin) Device {
csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
rstPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
busyPin.Configure(machine.PinConfig{Mode: machine.PinInput})
func New(bus drivers.SPI, csPin, dcPin, rstPin pin.Output, busyPin pin.Input) Device {
legacy.ConfigurePinOut(csPin)
legacy.ConfigurePinOut(dcPin)
legacy.ConfigurePinOut(rstPin)
legacy.ConfigurePinInput(busyPin)
return Device{
bus: bus,
cs: csPin,
dc: dcPin,
rst: rstPin,
busy: busyPin,
bus: bus,
cs: csPin.Set,
dc: dcPin.Set,
rst: rstPin.Set,
isBusy: busyPin.Get,
}
}
@@ -277,14 +278,14 @@ func (d *Device) ClearDisplay() {
// WaitUntilIdle waits until the display is ready
func (d *Device) WaitUntilIdle() {
for !d.busy.Get() {
for !d.isBusy() {
time.Sleep(100 * time.Millisecond)
}
}
// IsBusy returns the busy status of the display
func (d *Device) IsBusy() bool {
return d.busy.Get()
return d.isBusy()
}
// ClearBuffer sets the buffer to 0xFF (white)
+23 -25
View File
@@ -5,10 +5,11 @@ package epd2in66b
import (
"image/color"
"machine"
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/legacy"
"tinygo.org/x/drivers/internal/pin"
)
const (
@@ -18,20 +19,12 @@ const (
const Baudrate = 4_000_000 // 4 MHz
type Config struct {
ResetPin machine.Pin
DataPin machine.Pin
ChipSelectPin machine.Pin
BusyPin machine.Pin
}
type Device struct {
bus drivers.SPI
cs machine.Pin
dc machine.Pin
rst machine.Pin
busy machine.Pin
bus drivers.SPI
cs pin.OutputFunc
dc pin.OutputFunc
rst pin.OutputFunc
isBusy pin.InputFunc
blackBuffer []byte
redBuffer []byte
}
@@ -50,18 +43,23 @@ func New(bus drivers.SPI) Device {
}
}
type Config struct {
ResetPin pin.Output
DataPin pin.Output
ChipSelectPin pin.Output
BusyPin pin.Input
}
// Configure configures the device and its pins.
func (d *Device) Configure(c Config) error {
d.cs = c.ChipSelectPin
d.dc = c.DataPin
d.rst = c.ResetPin
d.busy = c.BusyPin
d.cs.Configure(machine.PinConfig{Mode: machine.PinOutput})
d.dc.Configure(machine.PinConfig{Mode: machine.PinOutput})
d.rst.Configure(machine.PinConfig{Mode: machine.PinOutput})
d.busy.Configure(machine.PinConfig{Mode: machine.PinInput})
d.cs = c.ChipSelectPin.Set
d.dc = c.DataPin.Set
d.rst = c.ResetPin.Set
d.isBusy = c.BusyPin.Get
legacy.ConfigurePinOut(c.ChipSelectPin)
legacy.ConfigurePinOut(c.DataPin)
legacy.ConfigurePinOut(c.ResetPin)
legacy.ConfigurePinInput(c.BusyPin)
return nil
}
@@ -229,7 +227,7 @@ func (d *Device) WaitUntilIdle() {
// give it some time to get busy
time.Sleep(50 * time.Millisecond)
for d.busy.Get() { // high = busy
for d.isBusy() { // high = busy
time.Sleep(10 * time.Millisecond)
}
+18 -17
View File
@@ -13,10 +13,11 @@ package epd2in9 // import "tinygo.org/x/drivers/waveshare-epd/epd2in9"
import (
"image/color"
"machine"
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/legacy"
"tinygo.org/x/drivers/internal/pin"
)
type Config struct {
@@ -28,10 +29,10 @@ type Config struct {
type Device struct {
bus drivers.SPI
cs machine.Pin
dc machine.Pin
rst machine.Pin
busy machine.Pin
cs pin.OutputFunc
dc pin.OutputFunc
rst pin.OutputFunc
isBusy pin.InputFunc
logicalWidth int16
width int16
height int16
@@ -61,17 +62,17 @@ var lutPartialUpdate = [30]uint8{
}
// New returns a new epd2in9 driver. Pass in a fully configured SPI bus.
func New(bus drivers.SPI, csPin, dcPin, rstPin, busyPin machine.Pin) Device {
csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
rstPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
busyPin.Configure(machine.PinConfig{Mode: machine.PinInput})
func New(bus drivers.SPI, csPin, dcPin, rstPin pin.Output, busyPin pin.Input) Device {
legacy.ConfigurePinOut(csPin)
legacy.ConfigurePinOut(dcPin)
legacy.ConfigurePinOut(rstPin)
legacy.ConfigurePinInput(busyPin)
return Device{
bus: bus,
cs: csPin,
dc: dcPin,
rst: rstPin,
busy: busyPin,
bus: bus,
cs: csPin.Set,
dc: dcPin.Set,
rst: rstPin.Set,
isBusy: busyPin.Get,
}
}
@@ -245,14 +246,14 @@ func (d *Device) setMemoryPointer(x int16, y int16) {
// WaitUntilIdle waits until the display is ready
func (d *Device) WaitUntilIdle() {
for d.busy.Get() {
for d.isBusy() {
time.Sleep(100 * time.Millisecond)
}
}
// IsBusy returns the busy status of the display
func (d *Device) IsBusy() bool {
return d.busy.Get()
return d.isBusy()
}
// ClearBuffer sets the buffer to 0xFF (white)
+18 -17
View File
@@ -10,10 +10,11 @@ package epd4in2
import (
"image/color"
"machine"
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/legacy"
"tinygo.org/x/drivers/internal/pin"
)
type Config struct {
@@ -25,10 +26,10 @@ type Config struct {
type Device struct {
bus drivers.SPI
cs machine.Pin
dc machine.Pin
rst machine.Pin
busy machine.Pin
cs pin.OutputFunc
dc pin.OutputFunc
rst pin.OutputFunc
isBusy pin.InputFunc
logicalWidth int16
width int16
height int16
@@ -40,17 +41,17 @@ type Device struct {
type Rotation uint8
// New returns a new epd4in2 driver. Pass in a fully configured SPI bus.
func New(bus drivers.SPI, csPin, dcPin, rstPin, busyPin machine.Pin) Device {
csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
rstPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
busyPin.Configure(machine.PinConfig{Mode: machine.PinInput})
func New(bus drivers.SPI, csPin, dcPin, rstPin pin.Output, busyPin pin.Input) Device {
legacy.ConfigurePinOut(csPin)
legacy.ConfigurePinOut(dcPin)
legacy.ConfigurePinOut(rstPin)
legacy.ConfigurePinInput(busyPin)
return Device{
bus: bus,
cs: csPin,
dc: dcPin,
rst: rstPin,
busy: busyPin,
bus: bus,
cs: csPin.Set,
dc: dcPin.Set,
rst: rstPin.Set,
isBusy: busyPin.Get,
}
}
@@ -311,14 +312,14 @@ func (d *Device) ClearDisplay() {
// WaitUntilIdle waits until the display is ready
func (d *Device) WaitUntilIdle() {
for d.busy.Get() {
for d.isBusy() {
time.Sleep(100 * time.Millisecond)
}
}
// IsBusy returns the busy status of the display
func (d *Device) IsBusy() bool {
return d.busy.Get()
return d.isBusy()
}
// ClearBuffer sets the buffer to 0xFF (white)
+360
View File
@@ -931,6 +931,356 @@ void ws2812_writeByte125(char c, uint32_t *portSet, uint32_t *portClear, uint32_
[portClear]"m"(*portClear));
}
__attribute__((always_inline))
void ws2812_writeByte150(char c, uint32_t *portSet, uint32_t *portClear, uint32_t maskSet, uint32_t maskClear) {
// Timings:
// T0H: 53 - 55 cycles or 353.3ns - 366.7ns
// T1H: 158 - 160 cycles or 1053.3ns - 1066.7ns
// TLD: 173 - cycles or 1153.3ns -
uint32_t value = (uint32_t)c << 24;
char i = 8;
__asm__ __volatile__(
"1: @ send_bit\n"
"\t str %[maskSet], %[portSet] @ [2] T0H and T0L start here\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t lsls %[value], #1 @ [1]\n"
"\t bcs.n 2f @ [1/3] skip_store\n"
"\t str %[maskClear], %[portClear] @ [2] T0H -> T0L transition\n"
"\t2: @ skip_store\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t str %[maskClear], %[portClear] @ [2] T1H -> T1L transition\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t subs %[i], #1 @ [1]\n"
"\t beq.n 3f @ [1/3] end\n"
"\t b 1b @ [1/3] send_bit\n"
"\t3: @ end\n"
: [value]"+r"(value),
[i]"+r"(i)
: [maskSet]"r"(maskSet),
[portSet]"m"(*portSet),
[maskClear]"r"(maskClear),
[portClear]"m"(*portClear));
}
__attribute__((always_inline))
void ws2812_writeByte168(char c, uint32_t *portSet, uint32_t *portClear, uint32_t maskSet, uint32_t maskClear) {
// Timings:
@@ -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
View File
@@ -1,7 +1,7 @@
// Package ws2812 implements a driver for WS2812 and SK6812 RGB LED strips.
package ws2812 // import "tinygo.org/x/drivers/ws2812"
//go:generate go run gen-ws2812.go -arch=cortexm 16 48 64 120 125 168 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 (
+3
View File
@@ -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