Compare commits

...

22 Commits

Author SHA1 Message Date
deadprogram 27ef18930e Prepare for drivers release 0.15.0
Signed-off-by: deadprogram <ron@hybridgroup.com>
2021-03-06 13:41:58 +01:00
deadprogram e9a6d96ddd docs: update count of supported drivers to add latest contribution
Signed-off-by: deadprogram <ron@hybridgroup.com>
2021-03-05 09:26:56 +01:00
pleomaxx3002 9a7cb1a22f DHTXX driver (#235)
dhtXX: add new driver for dht thermometer
2021-03-05 09:24:45 +01:00
deadprogram d170ec8d81 docs: add missing new drivers added since last release
Signed-off-by: deadprogram <ron@hybridgroup.com>
2021-03-05 00:36:50 +01:00
deadprogram 0fc2d28ca8 docs: update year in license to 2021
Signed-off-by: deadprogram <ron@hybridgroup.com>
2021-03-05 00:09:22 +01:00
Nick Craig-Wood ef34c13cc1 hd44780: add a mode to work with boards where the RW pin is grounded
On some HD44780 boards (eg the Keyestudio LCD1602 expansion shield),
the RW pin isn't brought out and is permanently grounded.

This means that the board can't be read from, and in particular the
busy status can't be read.

This patch adapts the package to work with boards like these.

To signal this to the package, set the RW pin to machine.NoPin.

The package will then disallow all reading and use adjustable timing
based writing. The timing can be adjusted the configuration.
2021-02-23 12:28:38 +01:00
deadprogram c64d7920dc adc: update drivers with ADC to use new config struct
Signed-off-by: deadprogram <ron@hybridgroup.com>
2021-02-01 14:35:51 +01:00
deadprogram 008157b6c9 st7789: correct errors on various godoc comments
Signed-off-by: deadprogram <ron@hybridgroup.com>
2021-01-31 16:57:21 +01:00
deadprogram 0ed9683a52 st7789: add scrolling functions to match st7735
Signed-off-by: deadprogram <ron@hybridgroup.com>
2021-01-31 16:57:21 +01:00
deadprogram 01acd977f3 microbitmatrix: refactor to eliminate duplicate code with microbit v1/v2
Signed-off-by: deadprogram <ron@hybridgroup.com>
2021-01-28 10:09:01 +01:00
deadprogram 231ec57202 microbitmatrix: matrix now working on microbit v2
Signed-off-by: deadprogram <ron@hybridgroup.com>
2021-01-28 10:09:01 +01:00
Ayke van Laethem 5d3ad4ba52 all: fix main package in examples
The package with the main function should always have the name main.
This was not the case in three packages.

This was silently allowed before, but since a TinyGo change
(https://github.com/tinygo-org/tinygo/pull/1592) this now results in a
linker failure.

Perhaps this should result in a better error message in TinyGo. However,
the example code also needs to be fixed, so hence this PR.
2021-01-27 23:50:58 +01:00
Roger Peppe 8cb226938b mcp23017: use new tester package
Also change the `tester` package slightly to use an exposed `Registers`
array rather adding yet another accessor method to retrieve a register
value. This seems to me more transparent and "obvious" - we aren't trying
to hide the fact that there's just a simple memory store there.
Also unexport the `assertRegisterRange` method which was never intended
to be part of the public API.
2021-01-20 12:56:22 +01:00
Roger Peppe ce5e443084 mcp23017: implement pin toggling
Also add an example for using multiple devices.
2021-01-20 12:56:22 +01:00
Roger Peppe edf9ba92be mcp23017: new driver for MCP23017 (I2C port expander) 2021-01-20 12:56:22 +01:00
Austen LeBeau c1c05cbef7 Add bmp388 support (#219)
bmp388: add support
2021-01-08 11:32:49 +01:00
Daniel Esteban c338348d2b Better interface "ReadTime" instead of "Time" for DS1307 2020-12-22 08:16:13 +01:00
deadprogram b6aa674b2a test: run unit tests against i2c drivers and any spi drivers without direct gpio
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-12-15 06:08:59 +01:00
deadprogram cc7079b0cd drivers/flash: restore previous calls directly to machine package until we implement SetClockSpeed()
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-12-15 06:08:59 +01:00
deadprogram 42a907035b spi: incorporate change from GH issue feedback
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-12-15 06:08:59 +01:00
deadprogram 46c9ba9595 spi: remove machine.SPI and replace with drivers.SPI interface for almost all SPI drivers
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-12-15 06:08:59 +01:00
Ayke van Laethem df64ce8f50 ws2812: make AVR support more robust
* Merge AVR support for Digispark and non-Digispark
* Fix the error "inline assembly requires more registers than available"
* Use a single file for all AVR targets, in a similar style as the
  Xtensa support.
2020-10-16 18:36:32 +02:00
69 changed files with 2656 additions and 372 deletions
+3
View File
@@ -12,6 +12,9 @@ jobs:
- run:
name: "Enforce Go Formatted Code"
command: make fmt-check
- run:
name: "Run unit tests"
command: make unit-test
- run:
name: "Run build and smoke tests"
command: make smoke-test
+22
View File
@@ -1,3 +1,25 @@
0.15.0
---
- **new devices**
- dht: add DHTXX thermometer
- mcp23017: new driver for MCP23017 (I2C port expander)
- bmp388: Add bmp388 support (#219)
- **enhancements**
- hd44780: add a mode to work with boards where the RW pin is grounded
- st7789: add scrolling functions to match st7735
- microbitmatrix: matrix now working on microbit v2
- ds1307: Better interface "ReadTime" instead of "Time"
- ws2812: make AVR support more robust
- **bugfixes**
- all: fix main package in examples
- **core**
- adc: update all drivers with ADC to use new config struct
- spi: remove machine.SPI and replace with drivers.SPI interface for almost all SPI drivers
- **testing**
- test: run unit tests against i2c drivers and any spi drivers without direct gpio
- **docs**
- st7789: correct errors on various godoc comments
0.14.0
---
- **new devices**
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2018-2020 The TinyGo Authors. All rights reserved.
Copyright (c) 2018-2021 The TinyGo Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
+20 -1
View File
@@ -31,6 +31,8 @@ smoke-test:
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/bmp280/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=trinket-m0 ./examples/bmp388/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=bluepill ./examples/ds1307/sram/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=bluepill ./examples/ds1307/time/main.go
@@ -81,6 +83,10 @@ smoke-test:
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/mag3110/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/mcp23017/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/mcp23017-multiple/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/mcp3008/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/microbitmatrix/main.go
@@ -132,6 +138,8 @@ smoke-test:
tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/ws2812
@md5sum ./build/test.hex
ifneq ($(AVR), 0)
tinygo build -size short -o ./build/test.hex -target=arduino ./examples/ws2812
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=digispark ./examples/ws2812
@md5sum ./build/test.hex
endif
@@ -157,5 +165,16 @@ endif
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/lis2mdl/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=feather-m0 ./examples/dht/main.go
@md5sum ./build/test.hex
test: clean fmt-check smoke-test
DRIVERS = $(wildcard */)
NOTESTS = build examples flash semihosting pcd8544 shiftregister st7789 microphone mcp3008 gps microbitmatrix \
hcsr04 ssd1331 ws2812 thermistor apa102 easystepper ssd1351 ili9341 wifinina shifter hub75 \
hd44780 buzzer ssd1306 espat l9110x st7735 bmi160 l293x dht
TESTS = $(filter-out $(addsuffix /%,$(NOTESTS)),$(DRIVERS))
unit-test:
@go test -v $(addprefix ./,$(TESTS))
test: clean fmt-check unit-test smoke-test
+5 -2
View File
@@ -3,7 +3,7 @@
[![PkgGoDev](https://pkg.go.dev/badge/tinygo.org/x/drivers)](https://pkg.go.dev/tinygo.org/x/drivers) [![CircleCI](https://circleci.com/gh/tinygo-org/drivers/tree/dev.svg?style=svg)](https://circleci.com/gh/tinygo-org/drivers/tree/dev)
This package provides a collection of hardware drivers for devices that can be used together with [TinyGo](https://tinygo.org).
This package provides a collection of hardware drivers for devices such as sensors and displays that can be used together with [TinyGo](https://tinygo.org).
## Installing
@@ -52,7 +52,7 @@ func main() {
## Currently supported devices
The following 53 devices are supported.
The following 56 devices are supported.
| Device Name | Interface Type |
|----------|-------------|
@@ -68,7 +68,9 @@ The following 53 devices are supported.
| [BMI160 accelerometer/gyroscope](https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmi160-ds000.pdf) | SPI |
| [BMP180 barometer](https://cdn-shop.adafruit.com/datasheets/BST-BMP180-DS000-09.pdf) | I2C |
| [BMP280 temperature/barometer](https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp280-ds001.pdf) | I2C |
| [BMP388 pressure sensor](https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp388-ds001.pdf) | I2C |
| [Buzzer](https://en.wikipedia.org/wiki/Buzzer#Piezoelectric) | GPIO |
| [DHTXX thermometer and humidity sensor](https://cdn-shop.adafruit.com/datasheets/Digital+humidity+and+temperature+sensor+AM2302.pdf) | GPIO |
| [DS1307 real time clock](https://datasheets.maximintegrated.com/en/ds/DS1307.pdf) | I2C |
| [DS3231 real time clock](https://datasheets.maximintegrated.com/en/ds/DS3231.pdf) | I2C |
| [ESP32 as WiFi Coprocessor with Arduino nina-fw](https://github.com/arduino/nina-fw) | SPI |
@@ -84,6 +86,7 @@ The following 53 devices are supported.
| [LIS3DH accelerometer](https://www.st.com/resource/en/datasheet/lis3dh.pdf) | I2C |
| [LSM6DS3 accelerometer](https://www.st.com/resource/en/datasheet/lsm6ds3.pdf) | I2C |
| [MAG3110 magnetometer](https://www.nxp.com/docs/en/data-sheet/MAG3110.pdf) | I2C |
| [MCP23017 port expander](https://ww1.microchip.com/downloads/en/DeviceDoc/20001952C.pdf) | I2C |
| [MCP3008 analog to digital converter (ADC)](http://ww1.microchip.com/downloads/en/DeviceDoc/21295d.pdf) | SPI |
| [Microphone - PDM](https://cdn-learn.adafruit.com/assets/assets/000/049/977/original/MP34DT01-M.pdf) | I2S/PDM |
| [MMA8653 accelerometer](https://www.nxp.com/docs/en/data-sheet/MMA8653FC.pdf) | I2C |
+2 -2
View File
@@ -18,13 +18,13 @@ func TestWhoAmI(t *testing.T) {
c := qt.New(t)
bus := tester.NewI2CBus(c)
fake := tester.NewI2CDevice(c, Address)
fake.SetupRegisters(defaultRegisters())
copy(fake.Registers[:], defaultRegisters())
bus.AddDevice(fake)
dev := New(bus)
c.Assert(dev.Connected(), qt.Equals, true)
fake.SetupRegister(RegID, 0x99)
fake.Registers[RegID] = 0x99
c.Assert(dev.Connected(), qt.Equals, false)
}
+4 -10
View File
@@ -6,6 +6,8 @@ package apa102 // import "tinygo.org/x/drivers/apa102"
import (
"image/color"
"machine"
"tinygo.org/x/drivers"
)
const (
@@ -23,20 +25,12 @@ var startFrame = []byte{0x00, 0x00, 0x00, 0x00}
// Device wraps APA102 SPI LEDs.
type Device struct {
bus SPI
bus drivers.SPI
Order int
}
// The SPI interface specifies the minimum functionality that a bus
// implementation needs to provide for use by the APA102 driver. Hardware
// SPI from the TinyGo "machine" package implements this already.
type SPI interface {
Tx(w, r []byte) error
Transfer(b byte) (byte, error)
}
// New returns a new APA102 driver. Pass in a fully configured SPI bus.
func New(b SPI) Device {
func New(b drivers.SPI) Device {
return Device{bus: b, Order: BGR}
}
+7 -4
View File
@@ -1,8 +1,11 @@
package bmi160
import "machine"
import (
"machine"
"time"
import "time"
"tinygo.org/x/drivers"
)
// DeviceSPI is the SPI interface to a BMI160 accelerometer/gyroscope. There is
// also an I2C interface, but it is not yet supported.
@@ -11,13 +14,13 @@ type DeviceSPI struct {
CSB machine.Pin
// SPI bus (requires chip select to be usable).
Bus machine.SPI
Bus drivers.SPI
}
// NewSPI returns a new device driver. The pin and SPI interface are not
// touched, provide a fully configured SPI object and call Configure to start
// using this device.
func NewSPI(csb machine.Pin, spi machine.SPI) *DeviceSPI {
func NewSPI(csb machine.Pin, spi drivers.SPI) *DeviceSPI {
return &DeviceSPI{
CSB: csb, // chip select
Bus: spi,
+249
View File
@@ -0,0 +1,249 @@
package bmp388
import (
"errors"
"tinygo.org/x/drivers"
)
var (
errConfigWrite = errors.New("bmp388: failed to configure sensor, check connection")
errConfig = errors.New("bmp388: there is a problem with the configuration, try reducing ODR")
errCaliRead = errors.New("bmp388: failed to read calibration coefficient register")
errSoftReset = errors.New("bmp388: failed to perform a soft reset")
errNotConnected = errors.New("bmp388: not connected")
)
type Oversampling byte
type Mode byte
type OutputDataRate byte
type FilterCoefficient byte
// Config contains settings for filtering, sampling, and modes of operation
type Config struct {
Pressure Oversampling
Temperature Oversampling
Mode Mode
ODR OutputDataRate
IIR FilterCoefficient
}
// Device wraps the I2C connection and configuration values for the BMP388
type Device struct {
bus drivers.I2C
Address uint8
cali calibrationCoefficients
Config Config
}
type calibrationCoefficients struct {
// Temperature compensation
t1 uint16
t2 uint16
t3 int8
// Pressure compensation
p1 int16
p2 int16
p3 int8
p4 int8
p5 uint16
p6 uint16
p7 int8
p8 int8
p9 int16
p10 int8
p11 int8
}
// New returns a bmp388 struct with the default I2C address. Configure must also be called after instanting
func New(bus drivers.I2C) Device {
return Device{
bus: bus,
Address: Address,
}
}
// Configure can enable settings on the BMP388 and reads the calibration coefficients
func (d *Device) Configure(config Config) (err error) {
d.Config = config
if d.Config == (Config{}) {
d.Config.Mode = Normal
}
// Turning on the pressure and temperature sensors and setting the measurement mode
err = d.writeRegister(RegPwrCtrl, PwrPress|PwrTemp|byte(d.Config.Mode))
// Configure the oversampling, output data rate, and iir filter coefficient settings
err = d.writeRegister(RegOSR, byte(d.Config.Pressure|d.Config.Temperature<<3))
err = d.writeRegister(RegODR, byte(d.Config.ODR))
err = d.writeRegister(RegIIR, byte(d.Config.IIR<<1))
if err != nil {
return errConfigWrite
}
// Check if there is a problem with the given configuration
if d.configurationError() {
return errConfig
}
// Reading the builtin calibration coefficients and parsing them per the datasheet. The compensation formula given
// in the datasheet is implemented in floating point
buffer, err := d.readRegister(RegCali, 21)
if err != nil {
return errCaliRead
}
d.cali.t1 = uint16(buffer[1])<<8 | uint16(buffer[0])
d.cali.t2 = uint16(buffer[3])<<8 | uint16(buffer[2])
d.cali.t3 = int8(buffer[4])
d.cali.p1 = int16(buffer[6])<<8 | int16(buffer[5])
d.cali.p2 = int16(buffer[8])<<8 | int16(buffer[7])
d.cali.p3 = int8(buffer[9])
d.cali.p4 = int8(buffer[10])
d.cali.p5 = uint16(buffer[12])<<8 | uint16(buffer[11])
d.cali.p6 = uint16(buffer[14])<<8 | uint16(buffer[13])
d.cali.p7 = int8(buffer[15])
d.cali.p8 = int8(buffer[16])
d.cali.p9 = int16(buffer[18])<<8 | int16(buffer[17])
d.cali.p10 = int8(buffer[19])
d.cali.p11 = int8(buffer[20])
return nil
}
// Read the temperature registers and compute a compensation value for the temperature and pressure compensation
// calculations. This is not the temperature itself.
func (d *Device) tlinCompensate() (int64, error) {
rawTemp, err := d.readSensorData(RegTemp)
if err != nil {
return 0, err
}
// pulled from C driver: https://github.com/BoschSensortec/BMP3-Sensor-API/blob/master/bmp3.c
partialData1 := rawTemp - (256 * int64(d.cali.t1))
partialData2 := int64(d.cali.t2) * partialData1
partialData3 := (partialData1 * partialData1)
partialData4 := partialData3 * int64(d.cali.t3)
partialData5 := (partialData2 * 262144) + partialData4
return partialData5 / 4294967296, nil
}
// ReadTemperature returns the temperature in centicelsius, i.e 2426 / 100 = 24.26 C
func (d *Device) ReadTemperature() (int32, error) {
tlin, err := d.tlinCompensate()
if err != nil {
return 0, err
}
temp := (tlin * 25) / 16384
return int32(temp), nil
}
// ReadPressure returns the pressure in centipascals, i.e 10132520 / 100 = 101325.20 Pa
func (d *Device) ReadPressure() (int32, error) {
tlin, err := d.tlinCompensate()
if err != nil {
return 0, err
}
rawPress, err := d.readSensorData(RegPress)
if err != nil {
return 0, err
}
// code pulled from bmp388 C driver: https://github.com/BoschSensortec/BMP3-Sensor-API/blob/master/bmp3.c
partialData1 := tlin * tlin
partialData2 := partialData1 / 64
partialData3 := (partialData2 * tlin) / 256
partialData4 := (int64(d.cali.p8) * partialData3) / 32
partialData5 := (int64(d.cali.p7) * partialData1) * 16
partialData6 := (int64(d.cali.p6) * tlin) * 4194304
offset := (int64(d.cali.p5) * 140737488355328) + partialData4 + partialData5 + partialData6
partialData2 = (int64(d.cali.p4) * partialData3) / 32
partialData4 = (int64(d.cali.p3) * partialData1) * 4
partialData5 = (int64(d.cali.p2) - 16384) * tlin * 2097152
sensitivity := ((int64(d.cali.p1) - 16384) * 70368744177664) + partialData2 + partialData4 + partialData5
partialData1 = (sensitivity / 16777216) * rawPress
partialData2 = int64(d.cali.p10) * tlin
partialData3 = partialData2 + (65536 * int64(d.cali.p9))
partialData4 = (partialData3 * rawPress) / 8192
// dividing by 10 followed by multiplying by 10
// To avoid overflow caused by (pressure * partial_data4)
partialData5 = (rawPress * (partialData4 / 10)) / 512
partialData5 = partialData5 * 10
partialData6 = (int64)(uint64(rawPress) * uint64(rawPress))
partialData2 = (int64(d.cali.p11) * partialData6) / 65536
partialData3 = (partialData2 * rawPress) / 128
partialData4 = (offset / 4) + partialData1 + partialData5 + partialData3
compPress := ((uint64(partialData4) * 25) / uint64(1099511627776))
return int32(compPress), nil
}
// SoftReset commands the BMP388 to reset of all user configuration settings
func (d *Device) SoftReset() error {
err := d.writeRegister(RegCmd, SoftReset)
if err != nil {
return errSoftReset
}
return nil
}
// Connected tries to reach the bmp388 and check its chip id register. Returns true if it was able to successfully
// communicate over i2c and returns the correct value
func (d *Device) Connected() bool {
data, err := d.readRegister(RegChipId, 1)
return err == nil && data[0] == ChipId // returns true if i2c comm was good and response equals 0x50
}
// SetMode changes the run mode of the sensor, NORMAL is the one to use for most cases. Use FORCED if you plan to take
// measurements infrequently and want to conserve power. SLEEP will of course put the sensor to sleep
func (d *Device) SetMode(mode Mode) error {
d.Config.Mode = mode
return d.writeRegister(RegPwrCtrl, PwrPress|PwrTemp|byte(d.Config.Mode))
}
func (d *Device) readSensorData(register byte) (data int64, err error) {
if !d.Connected() {
return 0, errNotConnected
}
// put the sensor back into forced mode to get a reading, the sensor goes back to sleep after taking one read in
// forced mode
if d.Config.Mode != Normal {
err = d.SetMode(Forced)
if err != nil {
return
}
}
bytes, err := d.readRegister(register, 3)
if err != nil {
return
}
data = int64(bytes[2])<<16 | int64(bytes[1])<<8 | int64(bytes[0])
return
}
// configurationError checks the register error for the configuration error bit. The bit is cleared on read by the bmp.
func (d *Device) configurationError() bool {
data, err := d.readRegister(RegErr, 1)
return err == nil && (data[0]&0x04) != 0
}
func (d *Device) readRegister(register byte, len int) (data []byte, err error) {
data = make([]byte, len)
err = d.bus.ReadRegister(d.Address, register, data)
return
}
func (d *Device) writeRegister(register byte, data byte) error {
return d.bus.WriteRegister(d.Address, register, []byte{data})
}
+84
View File
@@ -0,0 +1,84 @@
// Package bmp388 provides a driver for Bosch's BMP388 digital temperature & pressure sensor.
// The datasheet can be found here: https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp388-ds001.pdf
package bmp388
const Address byte = 0x77 // default I2C address
const (
RegChipId byte = 0x00 // useful for checking the connection
RegCali byte = 0x31 // pressure & temperature compensation calibration coefficients
RegPress byte = 0x04 // start of pressure data registers
RegTemp byte = 0x07 // start of temperature data registers
RegPwrCtrl byte = 0x1B // measurement mode & pressure/temperature sensor power register
RegOSR byte = 0x1C // oversampling settings register
RegODR byte = 0x1D //
RegCmd byte = 0x7E // miscellaneous command register
RegStat byte = 0x03 // sensor status register
RegErr byte = 0x02 // error status register
RegIIR byte = 0x1F
)
const (
ChipId byte = 0x50 // correct response if reading from chip id register
PwrPress byte = 0x01 // power on pressure sensor
PwrTemp byte = 0x02 // power on temperature sensor
SoftReset byte = 0xB6 // command to reset all user configuration
DRDYPress byte = 0x20 // for checking if pressure data is ready
DRDYTemp byte = 0x40 // for checking if pressure data is ready
)
// The difference between forced and normal mode is the bmp388 goes to sleep after taking a measurement in forced mode.
// Set it to forced if you intend to take measurements sporadically and want to save power. The driver will handle
// waking the sensor up when the sensor is in forced mode.
const (
Normal Mode = 0x30
Forced Mode = 0x16
Sleep Mode = 0x00
)
// Increasing sampling rate increases precision but also the wait time for measurements. The datasheet has a table of
// suggested values for oversampling, output data rates, and iir filter coefficients by use case.
const (
Sampling1X Oversampling = iota
Sampling2X
Sampling4X
Sampling8X
Sampling16X
Sampling32X
)
// Output data rates in Hz. If increasing the sampling rates you need to decrease the output data rates, else the bmp388
// will freeze and Configure() will return a configuration error message. In that case keep decreasing the data rate
// until the bmp is happy
const (
Odr200 OutputDataRate = iota
Odr100
Odr50
Odr25
Odr12p5
Odr6p25
Odr3p1
Odr1p5
Odr0p78
Odr0p39
Odr0p2
Odr0p1
Odr0p05
Odr0p02
Odr0p01
Odr0p006
Odr0p003
Odr0p0015
)
// IIR filter coefficients, higher values means steadier measurements but slower reaction times
const (
Coeff0 FilterCoefficient = iota
Coeff1
Coeff3
Coeff7
Coeff15
Coeff31
Coeff63
Coeff127
)
+116
View File
@@ -0,0 +1,116 @@
// Package dht provides a driver for DHTXX family temperature and humidity sensors.
//
// [1] Datasheet DHT11: https://www.mouser.com/datasheet/2/758/DHT11-Technical-Data-Sheet-Translated-Version-1143054.pdf
// [2] Datasheet DHT22: https://cdn-shop.adafruit.com/datasheets/Digital+humidity+and+temperature+sensor+AM2302.pdf
// Adafruit C++ driver: https://github.com/adafruit/DHT-sensor-library
package dht // import "tinygo.org/x/drivers/dht"
import (
"encoding/binary"
"machine"
"time"
)
// enum type for device type
type DeviceType uint8
// DeviceType specific parsing of information received from the sensor
func (d DeviceType) extractData(buf []byte) (temp int16, hum uint16) {
if d == DHT11 {
temp = int16(buf[2])
if buf[3]&0x80 > 0 {
temp = -1 - temp
}
temp *= 10
temp += int16(buf[3] & 0x0f)
hum = 10*uint16(buf[0]) + uint16(buf[1])
} else {
hum = binary.LittleEndian.Uint16(buf[0:2])
temp = int16(buf[3])<<8 + int16(buf[2]&0x7f)
if buf[2]&0x80 > 0 {
temp = -temp
}
}
return
}
// Celsius and Fahrenheit temperature scales
type TemperatureScale uint8
func (t TemperatureScale) convertToFloat(temp int16) float32 {
if t == C {
return float32(temp) / 10
} else {
// Fahrenheit
return float32(temp)*(9.0/50.) + 32.
}
}
// All functions return ErrorCode instance as error. This class can be used for more efficient error processing
type ErrorCode uint8
const (
startTimeout = time.Millisecond * 200
startingLow = time.Millisecond * 20
DHT11 DeviceType = iota
DHT22
C TemperatureScale = iota
F
ChecksumError ErrorCode = iota
NoSignalError
NoDataError
UpdateError
UninitializedDataError
)
// error interface implementation for ErrorCode
func (e ErrorCode) Error() string {
switch e {
case ChecksumError:
// DHT returns ChecksumError if all the data from the sensor was received, but the checksum does not match.
return "checksum mismatch"
case NoSignalError:
// DHT returns NoSignalError if there was no reply from the sensor. Check sensor connection or the correct pin
// sis chosen,
return "no signal"
case NoDataError:
// DHT returns NoDataError if the connection was successfully initialized, but not all 40 bits from
// the sensor is received
return "no data"
case UpdateError:
// DHT returns UpdateError if ReadMeasurements function is called before time specified in UpdatePolicy or
// less than 2 seconds after past measurement
return "cannot update now"
case UninitializedDataError:
// DHT returns UninitializedDataError if user attempts to access data before first measurement
return "no measurements done"
}
// should never be reached
return "unknown error"
}
// Update policy of the DHT device. UpdateTime cannot be shorter than 2 seconds. According to dht specification sensor
// will return undefined data if update requested less than 2 seconds before last usage
type UpdatePolicy struct {
UpdateTime time.Duration
UpdateAutomatically bool
}
var (
// timeout counter equal to number of ticks per 1 millisecond
timeout counter
)
func init() {
timeout = cyclesPerMillisecond()
}
func cyclesPerMillisecond() counter {
freq := machine.CPUFrequency()
freq /= 1000
return counter(freq)
}
+6
View File
@@ -0,0 +1,6 @@
// +build mimxrt1062 stm32f405 atsamd51 stm32f103xx k210 stm32f407
package dht // import "tinygo.org/x/drivers/dht"
// This file provides a definition of the counter for boards with frequency higher than 2^8 ticks per millisecond (>64MHz)
type counter uint32
+6
View File
@@ -0,0 +1,6 @@
// +build arduino atmega1284p nrf52840 digispark nrf52 arduino_nano nrf51 atsamd21 fe310 arduino_nano33 circuitplay_express arduino_mega2560
package dht // import "tinygo.org/x/drivers/dht"
// This file provides a definition of the counter for boards with frequency lower than 2^8 ticks per millisecond (<64MHz)
type counter uint16
+218
View File
@@ -0,0 +1,218 @@
// Package dht provides a driver for DHTXX family temperature and humidity sensors.
//
// [1] Datasheet DHT11: https://www.mouser.com/datasheet/2/758/DHT11-Technical-Data-Sheet-Translated-Version-1143054.pdf
// [2] Datasheet DHT22: https://cdn-shop.adafruit.com/datasheets/Digital+humidity+and+temperature+sensor+AM2302.pdf
// Adafruit C++ driver: https://github.com/adafruit/DHT-sensor-library
package dht // import "tinygo.org/x/drivers/dht"
import (
"machine"
"time"
)
// DummyDevice provides a basic interface for DHT devices.
type DummyDevice interface {
ReadMeasurements() error
Measurements() (temperature int16, humidity uint16, err error)
Temperature() (int16, error)
TemperatureFloat(scale TemperatureScale) (float32, error)
Humidity() (uint16, error)
HumidityFloat() (float32, error)
}
// Basic implementation of the DummyDevice
// This implementation takes measurements from sensor only with ReadMeasurements function
// and does not provide a protection from too frequent calls for measurements.
// Since taking measurements from the sensor is time consuming procedure and blocks interrupts,
// user can avoid any hidden calls to the sensor.
type device struct {
pin machine.Pin
measurements DeviceType
initialized bool
temperature int16
humidity uint16
}
// ReadMeasurements reads data from the sensor.
// According to documentation pin should be always, but the t *device restores pin to the state before call.
func (t *device) ReadMeasurements() error {
// initial waiting
state := powerUp(t.pin)
defer t.pin.Set(state)
err := t.read()
if err == nil {
t.initialized = true
}
return err
}
// Getter for temperature. Temperature method returns temperature as it is sent by device.
// The temperature is measured temperature in Celsius multiplied by 10.
// If no successful measurements for this device was performed, returns UninitializedDataError.
func (t *device) Temperature() (int16, error) {
if !t.initialized {
return 0, UninitializedDataError
}
return t.temperature, nil
}
// Getter for temperature. TemperatureFloat returns temperature in a given scale.
// If no successful measurements for this device was performed, returns UninitializedDataError.
func (t *device) TemperatureFloat(scale TemperatureScale) (float32, error) {
if !t.initialized {
return 0, UninitializedDataError
}
return scale.convertToFloat(t.temperature), nil
}
// Getter for humidity. Humidity returns humidity as it is sent by device.
// The humidity is measured in percentages multiplied by 10.
// If no successful measurements for this device was performed, returns UninitializedDataError.
func (t *device) Humidity() (uint16, error) {
if !t.initialized {
return 0, UninitializedDataError
}
return t.humidity, nil
}
// Getter for humidity. HumidityFloat returns humidity in percentages.
// If no successful measurements for this device was performed, returns UninitializedDataError.
func (t *device) HumidityFloat() (float32, error) {
if !t.initialized {
return 0, UninitializedDataError
}
return float32(t.humidity) / 10., nil
}
// Perform initialization of the communication protocol.
// Device lowers the voltage on pin for startingLow=20ms and starts listening for response
// Section 5.2 in [1]
func initiateCommunication(p machine.Pin) {
// Send low signal to the device
p.Configure(machine.PinConfig{Mode: machine.PinOutput})
p.Low()
time.Sleep(startingLow)
// Set pin to high and wait for reply
p.High()
p.Configure(machine.PinConfig{Mode: machine.PinInput})
}
// Measurements returns both measurements: temperature and humidity as they sent by the device.
// If no successful measurements for this device was performed, returns UninitializedDataError.
func (t *device) Measurements() (temperature int16, humidity uint16, err error) {
if !t.initialized {
return 0, 0, UninitializedDataError
}
temperature = t.temperature
humidity = t.humidity
err = nil
return
}
// Main routine that performs communication with the sensor
func (t *device) read() error {
// initialize loop variables
// buffer for the data sent by the sensor. Sensor sends 40 bits = 5 bytes
bufferData := [5]byte{}
buf := bufferData[:]
// We perform measurements of the signal from the sensor by counting low and high cycles.
// The bit is determined by the relative length of the high signal to low signal.
// For 1, high signal will be longer than low, for 0---low is longer.
// See section 5.3 [1]
signalsData := [80]counter{}
signals := signalsData[:]
// Start communication protocol with sensor
initiateCommunication(t.pin)
// Wait for sensor's response and abort if sensor does not reply
err := waitForDataTransmission(t.pin)
if err != nil {
return err
}
// count low and high cycles for sensor's reply
receiveSignals(t.pin, signals)
// process received signals and store the result in the buffer. Abort if data transmission was interrupted and not
// all 40 bits were received
err = t.extractData(signals[:], buf)
if err != nil {
return err
}
// Compute checksum and compare it to the one in data. Abort if checksum is incorrect
if !isValid(buf[:]) {
return ChecksumError
}
// Extract temperature and humidity data from buffer
t.temperature, t.humidity = t.measurements.extractData(buf)
return nil
}
// receiveSignals counts number of low and high cycles. The execution is time critical, so the function disables
// interrupts
func receiveSignals(pin machine.Pin, result []counter) {
i := uint8(0)
machine.UART1.Interrupt.Disable()
defer machine.UART1.Interrupt.Enable()
for ; i < 40; i++ {
result[i*2] = expectChange(pin, false)
result[i*2+1] = expectChange(pin, true)
}
}
// extractData process signal counters and transforms them into bits.
// if any of the bits were not received (timed-out), returns NoDataError
func (t *device) extractData(signals []counter, buf []uint8) error {
for i := uint8(0); i < 40; i++ {
lowCycle := signals[i*2]
highCycle := signals[i*2+1]
if lowCycle == timeout || highCycle == timeout {
return NoDataError
}
byteN := i >> 3
buf[byteN] <<= 1
if highCycle > lowCycle {
buf[byteN] |= 1
}
}
return nil
}
// waitForDataTransmission waits for reply from the sensor.
// If no reply received, returns NoSignalError.
// For more details, see section 5.2 in [1]
func waitForDataTransmission(p machine.Pin) error {
// wait for thermometer to pull down
if expectChange(p, true) == timeout {
return NoSignalError
}
//wait for thermometer to pull up
if expectChange(p, false) == timeout {
return NoSignalError
}
// wait for thermometer to pull down and start sending the data
if expectChange(p, true) == timeout {
return NoSignalError
}
return nil
}
// Constructor function for a DummyDevice implementation.
// This device provides full control to the user.
// It does not do any hidden measurements calls and does not check
// for 2 seconds delay between measurements.
func NewDummyDevice(pin machine.Pin, deviceType DeviceType) DummyDevice {
pin.High()
return &device{
pin: pin,
measurements: deviceType,
initialized: false,
temperature: 0,
humidity: 0,
}
}
+154
View File
@@ -0,0 +1,154 @@
// Package dht provides a driver for DHTXX family temperature and humidity sensors.
//
// [1] Datasheet DHT11: https://www.mouser.com/datasheet/2/758/DHT11-Technical-Data-Sheet-Translated-Version-1143054.pdf
// [2] Datasheet DHT22: https://cdn-shop.adafruit.com/datasheets/Digital+humidity+and+temperature+sensor+AM2302.pdf
// Adafruit C++ driver: https://github.com/adafruit/DHT-sensor-library
package dht // import "tinygo.org/x/drivers/dht"
import (
"machine"
"time"
)
// Device interface provides main functionality of the DHTXX sensors.
type Device interface {
DummyDevice
Configure(policy UpdatePolicy)
}
// managedDevice struct provides time control and optional automatic data retrieval from the sensor.
// It delegates all the functionality to device
type managedDevice struct {
t device
lastUpdate time.Time
policy UpdatePolicy
}
// Measurements returns both measurements: temperature and humidity as they sent by the device.
// Depending on the UpdatePolicy of the device may update cached measurements.
func (m *managedDevice) Measurements() (temperature int16, humidity uint16, err error) {
err = m.checkForUpdateOnDataRequest()
if err != nil {
return 0, 0, err
}
return m.t.Measurements()
}
// Getter for temperature. Temperature method returns temperature as it is sent by device.
// The temperature is measured temperature in Celsius multiplied by 10.
// Depending on the UpdatePolicy of the device may update cached measurements.
func (m *managedDevice) Temperature() (temp int16, err error) {
err = m.checkForUpdateOnDataRequest()
if err != nil {
return 0, err
}
temp, err = m.t.Temperature()
return
}
func (m *managedDevice) checkForUpdateOnDataRequest() (err error) {
// update if necessary
if m.policy.UpdateAutomatically {
err = m.ReadMeasurements()
}
// ignore error if the data was updated recently
// interface comparison does not work in tinygo. Therefore need to cast to explicit type
if code, ok := err.(ErrorCode); ok && code == UpdateError {
err = nil
}
// add error if the data is not initialized
if !m.t.initialized {
err = UninitializedDataError
}
return err
}
// Getter for temperature. TemperatureFloat returns temperature in a given scale.
// Depending on the UpdatePolicy of the device may update cached measurements.
func (m *managedDevice) TemperatureFloat(scale TemperatureScale) (float32, error) {
err := m.checkForUpdateOnDataRequest()
if err != nil {
return 0, err
}
return m.t.TemperatureFloat(scale)
}
// Getter for humidity. Humidity returns humidity as it is sent by device.
// The humidity is measured in percentages multiplied by 10.
// Depending on the UpdatePolicy of the device may update cached measurements.
func (m *managedDevice) Humidity() (hum uint16, err error) {
err = m.checkForUpdateOnDataRequest()
if err != nil {
return 0, err
}
return m.t.Humidity()
}
// Getter for humidity. HumidityFloat returns humidity in percentages.
// Depending on the UpdatePolicy of the device may update cached measurements.
func (m *managedDevice) HumidityFloat() (float32, error) {
err := m.checkForUpdateOnDataRequest()
if err != nil {
return 0, err
}
return m.t.HumidityFloat()
}
// ReadMeasurements reads data from the sensor.
// The function will return UpdateError if it is called more frequently than specified in UpdatePolicy
func (m *managedDevice) ReadMeasurements() (err error) {
timestamp := time.Now()
if !m.t.initialized || timestamp.Sub(m.lastUpdate) > m.policy.UpdateTime {
err = m.t.ReadMeasurements()
} else {
err = UpdateError
}
if err == nil {
m.lastUpdate = timestamp
}
return
}
// Configure configures UpdatePolicy for Device.
// Configure checks for policy.UpdateTime and prevent from updating more frequently than specified in [1][2]
// to prevent undefined behaviour of the sensor.
func (m *managedDevice) Configure(policy UpdatePolicy) {
if policy.UpdateAutomatically && policy.UpdateTime < time.Second*2 {
policy.UpdateTime = time.Second * 2
}
m.policy = policy
}
// Constructor of the Device implementation.
// This implementation updates data every 2 seconds during data access.
func New(pin machine.Pin, deviceType DeviceType) Device {
pin.High()
return &managedDevice{
t: device{
pin: pin,
measurements: deviceType,
initialized: false,
},
lastUpdate: time.Time{},
policy: UpdatePolicy{
UpdateTime: time.Second * 2,
UpdateAutomatically: true,
},
}
}
// Constructor of the Device implementation with given UpdatePolicy
func NewWithPolicy(pin machine.Pin, deviceType DeviceType, updatePolicy UpdatePolicy) Device {
pin.High()
result := &managedDevice{
t: device{
pin: pin,
measurements: deviceType,
initialized: false,
},
lastUpdate: time.Time{},
}
result.Configure(updatePolicy)
return result
}
+34
View File
@@ -0,0 +1,34 @@
package dht // import "tinygo.org/x/drivers/dht"
import (
"machine"
"time"
)
// Check if the pin is disabled
func powerUp(p machine.Pin) bool {
state := p.Get()
if !state {
p.High()
time.Sleep(startTimeout)
}
return state
}
func expectChange(p machine.Pin, oldState bool) counter {
cnt := counter(0)
for ; p.Get() == oldState && cnt != timeout; cnt++ {
}
return cnt
}
func checksum(buf []uint8) uint8 {
return buf[4]
}
func computeChecksum(buf []uint8) uint8 {
return buf[0] + buf[1] + buf[2] + buf[3]
}
func isValid(buf []uint8) bool {
return checksum(buf) == computeChecksum(buf)
}
+2 -2
View File
@@ -42,8 +42,8 @@ func (d *Device) SetTime(t time.Time) error {
return err
}
// Time returns the time and date
func (d *Device) Time() (time.Time, error) {
// ReadTime returns the date and time
func (d *Device) ReadTime() (time.Time, error) {
data := make([]byte, 8)
err := d.bus.ReadRegister(d.Address, uint8(TimeDate), data)
if err != nil {
+53
View File
@@ -0,0 +1,53 @@
package main
import (
"machine"
"strconv"
"time"
"tinygo.org/x/drivers/bmp388"
)
func main() {
machine.I2C0.Configure(machine.I2CConfig{})
sensor := bmp388.New(machine.I2C0)
if !sensor.Connected() {
println("Uh oh, BMP388 not detected")
return
}
// The accuracy of the sensor can be increased, at the cost of a slower output rate. Table 9 in Section 3.5 of the
// datasheet has recommended settings for common use cases. If increasing the sampling rate, the output data rate
// (ODR) will likely have to be decreased. Configure() will return an error if there's a problem with the
// configuration settings - keep decreasing the ODR and cycling the power to the sensor until it is happy.
err := sensor.Configure(bmp388.Config{
Pressure: bmp388.Sampling8X,
Temperature: bmp388.Sampling2X,
ODR: bmp388.Odr25,
IIR: bmp388.Coeff0,
Mode: bmp388.Normal,
})
// This is also fine
// err := sensor.Configure(bmp388.BMP388Config{})
if err != nil {
println(err)
}
for {
temp, err := sensor.ReadTemperature() // returns the temperature in centicelsius
press, err := sensor.ReadPressure() // returns the pressure in centipascals
if err != nil {
println(err)
} else {
println("Temperature: " + strconv.FormatInt(int64(temp), 10) + " cC")
println("Pressure: " + strconv.FormatInt(int64(press), 10) + " cPa\n")
}
time.Sleep(time.Second)
}
}
+23
View File
@@ -0,0 +1,23 @@
package main
import (
"fmt"
"machine"
"time"
"tinygo.org/x/drivers/dht"
)
func main() {
pin := machine.D6
dhtSensor := dht.New(pin, dht.DHT11)
for {
temp, hum, err := dhtSensor.Measurements()
if err != nil {
fmt.Printf("Temperature: %02d.%d°C, Humidity: %02d.%d%%\n", temp/10, temp%10, hum/10, hum%10)
} else {
fmt.Printf("Could not take measurements from the sensor: %s\n", err.Error())
}
// Measurements cannot be updated only 2 seconds. More frequent calls will return the same value
time.Sleep(time.Second * 2)
}
}
+1 -1
View File
@@ -13,7 +13,7 @@ func main() {
rtc.SetTime(time.Date(2019, 5, 15, 20, 34, 12, 0, time.UTC))
for {
t, err := rtc.Time()
t, err := rtc.ReadTime()
if err != nil {
println("Error reading date:", err)
break
+1 -1
View File
@@ -1,4 +1,4 @@
package hcsr04
package main
import (
"machine"
+45
View File
@@ -0,0 +1,45 @@
// This example demonstrates putting several mcp23017 devices together into
// a single virtual I/O array.
package main
import (
"machine"
"tinygo.org/x/drivers/mcp23017"
)
func main() {
err := machine.I2C0.Configure(machine.I2CConfig{
Frequency: machine.TWI_FREQ_400KHZ,
})
if err != nil {
panic(err)
}
// Assume the devices are at addresses 0x20, 0x21
dev, err := mcp23017.NewI2CDevices(machine.I2C0, 0x20, 0x21)
if err != nil {
panic(err)
}
// Configure pin 0 for input and all the others for output.
if err := dev.SetModes([]mcp23017.PinMode{
mcp23017.Input | mcp23017.Pullup,
mcp23017.Output,
}); err != nil {
panic(err)
}
input := dev.Pin(0)
// Make a mask that represents all the output pins.
// Note that this leverages the driver behaviour which replicates the highest bit in
// the last slice element (1 in this case) to all other pins
outputMask := mcp23017.PinSlice{^mcp23017.Pins(1 << 0)} // All except pin 0
inputVal, err := input.Get()
if err != nil {
panic(err)
}
println("input value: ", inputVal)
// Set the values of all the output pins.
err = dev.SetPins(mcp23017.PinSlice{0b1011011_01101110, 0b11111101_11100110}, outputMask)
if err != nil {
panic(err)
}
}
+39
View File
@@ -0,0 +1,39 @@
package main
import (
"machine"
"tinygo.org/x/drivers/mcp23017"
)
func main() {
err := machine.I2C0.Configure(machine.I2CConfig{
Frequency: machine.TWI_FREQ_400KHZ,
})
if err != nil {
panic(err)
}
dev, err := mcp23017.NewI2C(machine.I2C0, 0x20)
if err != nil {
panic(err)
}
// Configure pin 0 for input and all the others for output.
if err := dev.SetModes([]mcp23017.PinMode{
mcp23017.Input | mcp23017.Pullup,
mcp23017.Output,
}); err != nil {
panic(err)
}
input := dev.Pin(0)
outputMask := ^mcp23017.Pins(1 << 0) // All except pin 0
inputVal, err := input.Get()
if err != nil {
panic(err)
}
println("input value: ", inputVal)
// Set the values of all the output pins.
err = dev.SetPins(0b1011011_01101110, outputMask)
if err != nil {
panic(err)
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
package ssd1331
package main
import (
"machine"
+1 -1
View File
@@ -1,4 +1,4 @@
package ssd1351
package main
import (
"machine"
+6 -9
View File
@@ -41,15 +41,7 @@ var (
spi = machine.NINA_SPI
// this is the ESP chip that has the WIFININA firmware flashed on it
adaptor = &wifinina.Device{
SPI: spi,
CS: machine.NINA_CS,
ACK: machine.NINA_ACK,
GPIO0: machine.NINA_GPIO0,
RESET: machine.NINA_RESETN,
}
console = machine.UART0
adaptor *wifinina.Device
topic = "tinygo"
)
@@ -68,6 +60,11 @@ func main() {
})
// Init esp8266/esp32
adaptor = wifinina.New(spi,
machine.NINA_CS,
machine.NINA_ACK,
machine.NINA_GPIO0,
machine.NINA_RESETN)
adaptor.Configure()
connectToAP()
+6 -9
View File
@@ -40,15 +40,7 @@ var (
spi = machine.NINA_SPI
// this is the ESP chip that has the WIFININA firmware flashed on it
adaptor = &wifinina.Device{
SPI: spi,
CS: machine.NINA_CS,
ACK: machine.NINA_ACK,
GPIO0: machine.NINA_GPIO0,
RESET: machine.NINA_RESETN,
}
console = machine.UART0
adaptor *wifinina.Device
cl mqtt.Client
topicTx = "tinygo/tx"
@@ -75,6 +67,11 @@ func main() {
})
// Init esp8266/esp32
adaptor = wifinina.New(spi,
machine.NINA_CS,
machine.NINA_ACK,
machine.NINA_GPIO0,
machine.NINA_RESETN)
adaptor.Configure()
connectToAP()
+10 -13
View File
@@ -24,20 +24,9 @@ const ntpHost = "129.6.15.29"
const NTP_PACKET_SIZE = 48
var (
// this is the ESP chip that has the WIFININA firmware flashed on it
// these are the default pins for the Arduino Nano33 IoT.
adaptor = wifinina.Device{
SPI: machine.NINA_SPI,
CS: machine.NINA_CS,
ACK: machine.NINA_ACK,
GPIO0: machine.NINA_GPIO0,
RESET: machine.NINA_RESETN,
}
b = make([]byte, NTP_PACKET_SIZE)
console = machine.UART0
adaptor *wifinina.Device
b = make([]byte, NTP_PACKET_SIZE)
)
func main() {
@@ -50,6 +39,14 @@ func main() {
SDI: machine.NINA_SDI,
SCK: machine.NINA_SCK,
})
// these are the default pins for the Arduino Nano33 IoT.
adaptor = wifinina.New(machine.NINA_SPI,
machine.NINA_CS,
machine.NINA_ACK,
machine.NINA_GPIO0,
machine.NINA_RESETN)
adaptor.Configure()
// connect to access point
+6 -9
View File
@@ -35,15 +35,7 @@ var (
spi = machine.NINA_SPI
// this is the ESP chip that has the WIFININA firmware flashed on it
adaptor = &wifinina.Device{
SPI: spi,
CS: machine.NINA_CS,
ACK: machine.NINA_ACK,
GPIO0: machine.NINA_GPIO0,
RESET: machine.NINA_RESETN,
}
console = machine.UART0
adaptor *wifinina.Device
)
var buf = &bytes.Buffer{}
@@ -60,6 +52,11 @@ func main() {
SCK: machine.NINA_SCK,
})
adaptor = wifinina.New(spi,
machine.NINA_CS,
machine.NINA_ACK,
machine.NINA_GPIO0,
machine.NINA_RESETN)
adaptor.Configure()
connectToAP()
+9 -11
View File
@@ -22,19 +22,9 @@ const pass = ""
// IP address of the server aka "hub". Replace with your own info.
const hubIP = ""
// these are the default pins for the Arduino Nano33 IoT.
// change these to connect to a different UART or pins for the ESP8266/ESP32
var (
// this is the ESP chip that has the WIFININA firmware flashed on it
// these are the default pins for the Arduino Nano33 IoT.
adaptor = &wifinina.Device{
SPI: machine.NINA_SPI,
CS: machine.NINA_CS,
ACK: machine.NINA_ACK,
GPIO0: machine.NINA_GPIO0,
RESET: machine.NINA_RESETN,
}
adaptor *wifinina.Device
)
func main() {
@@ -47,6 +37,14 @@ func main() {
SDI: machine.NINA_SDI,
SCK: machine.NINA_SCK,
})
// these are the default pins for the Arduino Nano33 IoT.
// change these to connect to a different UART or pins for the ESP8266/ESP32
adaptor = wifinina.New(machine.NINA_SPI,
machine.NINA_CS,
machine.NINA_ACK,
machine.NINA_GPIO0,
machine.NINA_RESETN)
adaptor.Configure()
// connect to access point
+6 -9
View File
@@ -33,15 +33,7 @@ var (
spi = machine.NINA_SPI
// this is the ESP chip that has the WIFININA firmware flashed on it
adaptor = &wifinina.Device{
SPI: spi,
CS: machine.NINA_CS,
ACK: machine.NINA_ACK,
GPIO0: machine.NINA_GPIO0,
RESET: machine.NINA_RESETN,
}
console = machine.UART0
adaptor *wifinina.Device
)
var buf [256]byte
@@ -61,6 +53,11 @@ func main() {
SCK: machine.NINA_SCK,
})
adaptor = wifinina.New(spi,
machine.NINA_CS,
machine.NINA_ACK,
machine.NINA_GPIO0,
machine.NINA_RESETN)
adaptor.Configure()
connectToAP()
+9
View File
@@ -0,0 +1,9 @@
// +build arduino
package main
import "machine"
// Replace neo in the code below to match the pin
// that you are using if different.
var neo = machine.D2
+1 -1
View File
@@ -1,4 +1,4 @@
// +build !digispark
// +build !digispark,!arduino
package main
+2
View File
@@ -133,6 +133,8 @@ func (dev *Device) Configure(config *DeviceConfig) (err error) {
time.Sleep(30 * time.Microsecond)
// Speed up to max device frequency
// I propose a check here for max frequency, but not put that functionality directly into the driver.
// Either that or we have to change the signature of the SPI interface in the machine package itself.
if dev.attrs.MaxClockSpeedMHz > 0 {
err := dev.trans.setClockSpeed(uint32(dev.attrs.MaxClockSpeedMHz) * 1e6)
if err != nil {
+3 -1
View File
@@ -1,6 +1,8 @@
package flash
import "machine"
import (
"machine"
)
type transport interface {
configure(config *DeviceConfig)
+11 -1
View File
@@ -57,9 +57,16 @@ func (g *GPIO) SetCommandMode(set bool) {
}
}
// WriteOnly is true if you passed rw in as machine.NoPin
func (g *GPIO) WriteOnly() bool {
return g.rw == machine.NoPin
}
// Write writes len(data) bytes from data to display driver
func (g *GPIO) Write(data []byte) (n int, err error) {
g.rw.Low()
if !g.WriteOnly() {
g.rw.Low()
}
for _, d := range data {
g.write(d)
n++
@@ -89,6 +96,9 @@ func (g *GPIO) Read(data []byte) (n int, err error) {
if len(data) == 0 {
return 0, errors.New("length greater than 0 is required")
}
if g.WriteOnly() {
return 0, errors.New("Read not supported if RW not wired")
}
g.rw.High()
g.reconfigureGPIOMode(machine.PinInput)
for i := 0; i < len(data); i++ {
+60 -9
View File
@@ -11,9 +11,23 @@ import (
"time"
)
const (
// These are the default execution times for the Clear and
// Home commands and everything else.
//
// These are used if RW is passed as machine.NoPin and ignored
// otherwise.
//
// They are set conservatively here and can be tweaked in the
// Config structure.
DefaultClearHomeTime = 80 * time.Millisecond
DefaultInstrExecTime = 80 * time.Microsecond
)
type Buser interface {
io.ReadWriter
SetCommandMode(set bool)
WriteOnly() bool
}
type Device struct {
@@ -28,6 +42,9 @@ type Device struct {
cursor cursor
busyStatus []byte
clearHomeTime time.Duration // time clear/home instructions might take
instrExecTime time.Duration // time all other instructions might take
}
type cursor struct {
@@ -35,14 +52,18 @@ type cursor struct {
}
type Config struct {
Width int16
Height int16
CursorBlink bool
CursorOnOff bool
Font uint8
Width int16
Height int16
CursorBlink bool
CursorOnOff bool
Font uint8
ClearHomeTime time.Duration // time clear/home instructions might take - use 0 for the default
InstrExecTime time.Duration // time all other instructions might take - use 0 for the default
}
// NewGPIO4Bit returns 4bit data length HD44780 driver. Datapins are LCD DB pins starting from DB4 to DB7
//
// If your device has RW set permanently to ground then pass in rw as machine.NoPin
func NewGPIO4Bit(dataPins []machine.Pin, e, rs, rw machine.Pin) (Device, error) {
const fourBitMode = 4
if len(dataPins) != fourBitMode {
@@ -52,6 +73,8 @@ func NewGPIO4Bit(dataPins []machine.Pin, e, rs, rw machine.Pin) (Device, error)
}
// NewGPIO8Bit returns 8bit data length HD44780 driver. Datapins are LCD DB pins starting from DB0 to DB7
//
// If your device has RW set permanently to ground then pass in rw as machine.NoPin
func NewGPIO8Bit(dataPins []machine.Pin, e, rs, rw machine.Pin) (Device, error) {
const eightBitMode = 8
if len(dataPins) != eightBitMode {
@@ -68,6 +91,8 @@ func (d *Device) Configure(cfg Config) error {
if d.width == 0 || d.height == 0 {
return errors.New("width and height must be set")
}
d.clearHomeTime = cfg.ClearHomeTime
d.instrExecTime = cfg.InstrExecTime
memoryMap := uint8(ONE_LINE)
if d.height > 1 {
memoryMap = TWO_LINE
@@ -186,7 +211,7 @@ func (d *Device) SendCommand(command byte) {
d.bus.SetCommandMode(true)
d.bus.Write([]byte{command})
for d.Busy() {
for d.busy(command == DISPLAY_CLEAR || command == CURSOR_HOME) {
}
}
@@ -195,7 +220,7 @@ func (d *Device) sendData(data byte) {
d.bus.SetCommandMode(false)
d.bus.Write([]byte{data})
for d.Busy() {
for d.busy(false) {
}
}
@@ -207,13 +232,39 @@ func (d *Device) CreateCharacter(cgramAddr uint8, data []byte) {
}
}
// Busy returns true when hd447890 is busy
func (d *Device) Busy() bool {
// busy returns true when hd447890 is busy
// or after the timeout specified
func (d *Device) busy(longDelay bool) bool {
if d.bus.WriteOnly() {
// Can't read busy flag if write only, so sleep a bit then return
if longDelay {
// Note that we sleep like this so the default
// time.Sleep is time.Sleep(constant) as
// time.Sleep(variable) doesn't seem to work on AVR yet
if d.clearHomeTime != 0 {
time.Sleep(d.clearHomeTime)
} else {
time.Sleep(DefaultClearHomeTime)
}
} else {
if d.instrExecTime != 0 {
time.Sleep(d.instrExecTime)
} else {
time.Sleep(DefaultInstrExecTime)
}
}
return false
}
d.bus.SetCommandMode(true)
d.bus.Read(d.busyStatus)
return (d.busyStatus[0] & BUSY) > 0
}
// Busy returns true when hd447890 is busy
func (d *Device) Busy() bool {
return d.busy(false)
}
// Size returns the current size of the display.
func (d *Device) Size() (w, h int16) {
return int16(d.width), int16(d.height)
+4 -2
View File
@@ -9,6 +9,8 @@ import (
"image/color"
"machine"
"time"
"tinygo.org/x/drivers"
)
type Config struct {
@@ -21,7 +23,7 @@ type Config struct {
}
type Device struct {
bus machine.SPI
bus drivers.SPI
a machine.Pin
b machine.Pin
c machine.Pin
@@ -52,7 +54,7 @@ type Device struct {
}
// New returns a new HUB75 driver. Pass in a fully configured SPI bus.
func New(b machine.SPI, latPin, oePin, aPin, bPin, cPin, dPin machine.Pin) Device {
func New(b drivers.SPI, latPin, oePin, aPin, bPin, cPin, dPin machine.Pin) Device {
aPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
bPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
cPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
+2 -2
View File
@@ -18,13 +18,13 @@ func TestWhoAmI(t *testing.T) {
c := qt.New(t)
bus := tester.NewI2CBus(c)
fake := tester.NewI2CDevice(c, ADDRESS)
fake.SetupRegisters(defaultRegisters())
copy(fake.Registers[:], defaultRegisters())
bus.AddDevice(fake)
dev := New(bus)
c.Assert(dev.Connected(), qt.Equals, true)
fake.SetupRegister(WHO_AM_I, 0x99)
fake.Registers[WHO_AM_I] = 0x99
c.Assert(dev.Connected(), qt.Equals, false)
}
+381
View File
@@ -0,0 +1,381 @@
// Package mcp23017 implements a driver for the MCP23017
// I2C port expander chip. See https://www.microchip.com/wwwproducts/en/MCP23017
// for details of the interface.
//
// It also provides a way of joining several such devices into one logical
// device (see the Devices type).
package mcp23017
import (
"errors"
)
const (
// hwAddressFixed holds the bits of the hardware address
// that are fixed by the chip. Bits 0-3 (those in hwAddressMask)
// are user-defined by the A0-A2 pins on the chip.
hwAddress = uint8(0b010_0000)
// hwAddressMask holds the bits that are significant in hwAddress.
hwAddressMask = uint8(0b111_1000)
)
type register uint8
const (
// The following registers all refer to port A (except
// rIOCON with is port-agnostic).
// ORing them with portB makes them refer to port B.
rIODIR = register(0x00) // I/O direction. 0=output; 1=input.
rIOPOL = register(0x02) // Invert input values. 0=normal; 1=inverted.
rGPINTEN = register(0x04)
rDEFVAL = register(0x06)
rINTCON = register(0x08)
rIOCON = register(0x0A)
rGPPU = register(0x0C) // Pull up; 0=no pull-up; 1=pull-up.
rINTF = register(0x0E)
rINTCAP = register(0x10)
rGPIO = register(0x12) // GPIO pin values.
rOLAT = register(0x14)
registerCount = 0x16
portB = register(0x1)
)
// PinCount is the number of GPIO pins available on the chip.
const PinCount = 16
// PinMode represents a possible I/O mode for a pin.
// The zero value represents the default value
// after the chip is reset (input).
type PinMode uint8
const (
// Input configures a pin as an input.
Input = PinMode(0)
// Output configures a pin as an output.
Output = PinMode(1)
// Direction is the bit mask of the pin mode representing
// the I/O direction.
Direction = PinMode(1)
// Pullup can be bitwise-or'd with Input
// to cause the pull-up resistor on the pin to
// be enabled.
Pullup = PinMode(2)
// Invert can be bitwise-or'd with Input to
// cause the pin value to reflect the inverted
// value on the pin.
Invert = PinMode(4)
)
// ErrInvalidHWAddress is returned when the hardware address
// of the device is not valid (only some bits can be set by the
// address pins).
var ErrInvalidHWAddress = errors.New("invalid hardware address")
// I2C represents an I2C bus. It is notably implemented by the
// machine.I2C type.
type I2C interface {
ReadRegister(addr uint8, r uint8, buf []byte) error
WriteRegister(addr uint8, r uint8, buf []byte) error
}
// New returns a new MCP23017 device at the given I2C address
// on the given bus.
// It returns ErrInvalidHWAddress if the address isn't possible for the device.
//
// By default all pins are configured as inputs.
func NewI2C(bus I2C, address uint8) (*Device, error) {
if address&hwAddressMask != hwAddress {
return nil, ErrInvalidHWAddress
}
d := &Device{
bus: bus,
addr: address,
}
pins, err := d.GetPins()
if err != nil {
return nil, errors.New("cannot initialize mcp23017 device at " + hex(address) + ": " + err.Error())
}
d.pins = pins
return d, nil
}
func hex(x uint8) string {
digits := "0123456789abcdef"
return "0x" + digits[x>>4:x>>4+1] + digits[x&0xf:x&0xf+1]
}
// Device represents an MCP23017 device.
type Device struct {
// TODO would it be good to have a mutex here so that independent goroutines
// could change pins without needing to do the locking themselves?
// bus holds the reference the I2C bus that the device lives on.
// It's an interface so that we can write tests for it.
bus I2C
addr uint8
// pins caches the most recent pin values that have been set.
// This enables us to change individual pin values without
// doing a read followed by a write.
pins Pins
}
// GetPins reads all 16 pins from ports A and B.
func (d *Device) GetPins() (Pins, error) {
return d.readRegisterAB(rGPIO)
}
// SetPins sets all the pins for which mask is high
// to their respective values in pins.
//
// That is, it does the equivalent of:
//
// for i := 0; i < PinCount; i++ {
// if mask.Get(i) {
// d.Pin(i).Set(pins.Get(i))
// }
// }
func (d *Device) SetPins(pins, mask Pins) error {
if mask == 0 {
return nil
}
newPins := (d.pins &^ mask) | (pins & mask)
if newPins == d.pins {
return nil
}
err := d.writeRegisterAB(rGPIO, newPins)
if err != nil {
return err
}
d.pins = newPins
return nil
}
// TogglePins inverts the values on all pins for
// which mask is high.
func (d *Device) TogglePins(mask Pins) error {
if mask == 0 {
return nil
}
return d.SetPins(^d.pins, mask)
}
// Pin returns a Pin representing the given pin number (from 0 to 15).
// Pin numbers from 0 to 7 represent port A pins 0 to 7.
// Pin numbers from 8 to 15 represent port B pins 0 to 7.
func (d *Device) Pin(pin int) Pin {
if pin < 0 || pin >= PinCount {
panic("pin out of range")
}
var mask Pins
mask.High(pin)
return Pin{
dev: d,
mask: mask,
pin: uint8(pin),
}
}
// SetAllModes sets the mode of all the pins in a single operation.
// If len(modes) is less than PinCount, all remaining pins
// will be set fo modes[len(modes)-1], or PinMode(0) if
// modes is empty.
//
// If len(modes) is greater than PinCount, the excess entries
// will be ignored.
func (d *Device) SetModes(modes []PinMode) error {
defaultMode := PinMode(0)
if len(modes) > 0 {
defaultMode = modes[len(modes)-1]
}
var dir, pullup, invert Pins
for i := 0; i < PinCount; i++ {
mode := defaultMode
if i < len(modes) {
mode = modes[i]
}
if mode&Direction == Input {
dir.High(i)
}
if mode&Pullup != 0 {
pullup.High(i)
}
if mode&Invert != 0 {
invert.High(i)
}
}
if err := d.writeRegisterAB(rIODIR, dir); err != nil {
return err
}
if err := d.writeRegisterAB(rGPPU, pullup); err != nil {
return err
}
if err := d.writeRegisterAB(rIOPOL, invert); err != nil {
return err
}
return nil
}
// GetModes reads the modes of all the pins into modes.
// It's OK if len(modes) is not PinCount - excess entries
// will be left unset.
func (d *Device) GetModes(modes []PinMode) error {
dir, err := d.readRegisterAB(rIODIR)
if err != nil {
return err
}
pullup, err := d.readRegisterAB(rGPPU)
if err != nil {
return err
}
invert, err := d.readRegisterAB(rIOPOL)
if err != nil {
return err
}
if len(modes) > PinCount {
modes = modes[:PinCount]
}
for i := range modes {
mode := Output
if dir.Get(i) {
mode = Input
}
if pullup.Get(i) {
mode |= Pullup
}
if invert.Get(i) {
mode |= Invert
}
modes[i] = mode
}
return nil
}
func (d *Device) writeRegisterAB(r register, val Pins) error {
// We rely on the auto-incrementing sequential write
// and the fact that registers alternate between A and B
// to write both ports in a single operation.
buf := [2]byte{uint8(val), uint8(val >> 8)}
return d.bus.WriteRegister(d.addr, uint8(r&^portB), buf[:])
}
func (d *Device) readRegisterAB(r register) (Pins, error) {
// We rely on the auto-incrementing sequential write
// and the fact that registers alternate between A and B
// to read both ports in a single operation.
var buf [2]byte
if err := d.bus.ReadRegister(d.addr, uint8(r), buf[:]); err != nil {
return Pins(0), err
}
return Pins(buf[0]) | (Pins(buf[1]) << 8), nil
}
// Pin represents a single GPIO pin on the device.
type Pin struct {
// mask holds the mask of the pin.
mask Pins
// pin holds the actual pin number.
pin uint8
dev *Device
}
// Set sets the pin to the given value.
func (p Pin) Set(value bool) error {
// TODO currently this always writes both registers when
// technically it only needs to write one. We could potentially
// optimize that.
if value {
return p.dev.SetPins(^Pins(0), p.mask)
} else {
return p.dev.SetPins(0, p.mask)
}
}
// High is short for p.Set(true).
func (p Pin) High() error {
return p.Set(true)
}
// High is short for p.Set(false).
func (p Pin) Low() error {
return p.Set(false)
}
// Toggle inverts the value output on the pin.
func (p Pin) Toggle() error {
return p.dev.TogglePins(p.mask)
}
// Get returns the current value of the given pin.
func (p Pin) Get() (bool, error) {
// TODO this reads 2 registers when we could read just one.
pins, err := p.dev.GetPins()
if err != nil {
return false, err
}
return pins&p.mask != 0, nil
}
// SetMode configures the pin to the given mode.
func (p Pin) SetMode(mode PinMode) error {
// We could use a more efficient single-register
// read/write pattern but setting pin modes isn't an
// operation that's likely to need to be efficient, so
// use less code and use Get/SetModes directly.
modes := make([]PinMode, PinCount)
if err := p.dev.GetModes(modes); err != nil {
return err
}
modes[p.pin] = mode
return p.dev.SetModes(modes)
}
// GetMode returns the mode of the pin.
func (p Pin) GetMode() (PinMode, error) {
modes := make([]PinMode, PinCount)
if err := p.dev.GetModes(modes); err != nil {
return 0, err
}
return modes[p.pin], nil
}
// Pins represents a bitmask of pin values.
// Port A values are in bits 0-8 (numbered from least significant bit)
// Port B values are in bits 9-15.
type Pins uint16
// Set sets the value for the given pin.
func (p *Pins) Set(pin int, value bool) {
if value {
p.High(pin)
} else {
p.Low(pin)
}
}
// Get returns the value for the given pin.
func (p Pins) Get(pin int) bool {
return (p & pinMask(pin)) != 0
}
// High is short for p.Set(pin, true).
func (p *Pins) High(pin int) {
*p |= pinMask(pin)
}
// Low is short for p.Set(pin, false).
func (p *Pins) Low(pin int) {
*p &^= pinMask(pin)
}
// Toggle inverts the value of the given pin.
func (p *Pins) Toggle(pin int) {
*p ^= pinMask(pin)
}
func pinMask(pin int) Pins {
return 1 << pin
}
+220
View File
@@ -0,0 +1,220 @@
package mcp23017
import (
"fmt"
"testing"
qt "github.com/frankban/quicktest"
"tinygo.org/x/drivers/tester"
)
func TestGetPins(t *testing.T) {
c := qt.New(t)
bus := tester.NewI2CBus(c)
fdev := newDevice(bus, 0x20)
fdev.Registers[rGPIO] = 0b10101100
fdev.Registers[rGPIO|portB] = 0b01010011
dev, err := NewI2C(bus, 0x20)
c.Assert(err, qt.IsNil)
pins, err := dev.GetPins()
c.Assert(err, qt.IsNil)
c.Assert(pins, qt.Equals, Pins(0b01010011_10101100))
}
func TestSetPins(t *testing.T) {
c := qt.New(t)
bus := tester.NewI2CBus(c)
fdev := newDevice(bus, 0x20)
fdev.Registers[rGPIO] = 0b00001111
fdev.Registers[rGPIO|portB] = 0b11110000
dev, err := NewI2C(bus, 0x20)
c.Assert(err, qt.IsNil)
pins, err := dev.GetPins()
c.Assert(err, qt.IsNil)
c.Assert(pins, qt.Equals, Pins(0b11110000_00001111))
err = dev.SetPins(0b01100000_00110000, 0b10101010_01010101)
c.Assert(err, qt.IsNil)
pins, err = dev.GetPins()
c.Assert(err, qt.IsNil)
c.Assert(pins, qt.Equals, Pins(0b01110000_0001_1010))
// The logic uses the cached value of the pins rather than
// reading it from the registers each time.
fdev.Registers[rGPIO] = 0
fdev.Registers[rGPIO|portB] = 0
err = dev.SetPins(0b01000000_00110000, 0b01100000_00000000)
c.Assert(err, qt.IsNil)
pins, err = dev.GetPins()
c.Assert(err, qt.IsNil)
c.Assert(pins, qt.Equals, Pins(0b01010000_00011010))
}
func TestTogglePins(t *testing.T) {
c := qt.New(t)
bus := tester.NewI2CBus(c)
fdev := newDevice(bus, 0x20)
fdev.Registers[rGPIO] = 0b00001111
fdev.Registers[rGPIO|portB] = 0b11110000
dev, err := NewI2C(bus, 0x20)
c.Assert(err, qt.IsNil)
pins, err := dev.GetPins()
c.Assert(err, qt.IsNil)
c.Assert(pins, qt.Equals, Pins(0b11110000_00001111))
err = dev.TogglePins(0b10101010_01010101)
c.Assert(err, qt.IsNil)
pins, err = dev.GetPins()
c.Assert(err, qt.IsNil)
c.Assert(pins, qt.Equals, Pins(0b01011010_01011010))
}
func TestSetGetModes(t *testing.T) {
c := qt.New(t)
bus := tester.NewI2CBus(c)
fdev := newDevice(bus, 0x20)
dev, err := NewI2C(bus, 0x20)
c.Assert(err, qt.IsNil)
// Calling SetModes with less items in than there are
// pins should use the last item for all the unspecified ones.
err = dev.SetModes([]PinMode{Input | Invert, Output})
c.Assert(err, qt.IsNil)
c.Assert(fdev.Registers[rIODIR], qt.Equals, uint8(0b00000001))
c.Assert(fdev.Registers[rIOPOL], qt.Equals, uint8(0b00000001))
c.Assert(fdev.Registers[rGPPU], qt.Equals, uint8(0))
modes := make([]PinMode, 17)
err = dev.GetModes(modes)
c.Assert(err, qt.IsNil)
c.Assert(modes[0], qt.Equals, Input|Invert)
for i, m := range modes[1:16] {
c.Assert(m, qt.Equals, Output, qt.Commentf("index %d", i))
}
c.Assert(modes[16], qt.Equals, PinMode(0))
// Using an empty slice should reset all the modes to the initial state.
err = dev.SetModes(nil)
c.Assert(err, qt.IsNil)
c.Assert(fdev.Registers[rIODIR], qt.Equals, uint8(0b11111111))
c.Assert(fdev.Registers[rIOPOL], qt.Equals, uint8(0))
c.Assert(fdev.Registers[rGPPU], qt.Equals, uint8(0))
}
func TestPinSetGet(t *testing.T) {
c := qt.New(t)
bus := tester.NewI2CBus(c)
fdev := newDevice(bus, 0x20)
dev, err := NewI2C(bus, 0x20)
c.Assert(err, qt.IsNil)
pin := dev.Pin(1)
v, err := pin.Get()
c.Assert(err, qt.Equals, nil)
c.Assert(v, qt.Equals, false)
err = pin.Set(true)
c.Assert(err, qt.Equals, nil)
c.Assert(fdev.Registers[rGPIO], qt.Equals, uint8(0b10))
v, err = pin.Get()
c.Assert(err, qt.Equals, nil)
c.Assert(v, qt.Equals, true)
err = pin.Set(false)
c.Assert(err, qt.Equals, nil)
c.Assert(fdev.Registers[rGPIO], qt.Equals, uint8(0))
err = pin.High()
c.Assert(err, qt.Equals, nil)
c.Assert(fdev.Registers[rGPIO], qt.Equals, uint8(0b10))
err = pin.Low()
c.Assert(err, qt.Equals, nil)
c.Assert(fdev.Registers[rGPIO], qt.Equals, uint8(0))
}
func TestPinToggle(t *testing.T) {
c := qt.New(t)
bus := tester.NewI2CBus(c)
fdev := newDevice(bus, 0x20)
dev, err := NewI2C(bus, 0x20)
c.Assert(err, qt.IsNil)
pin := dev.Pin(1)
v, err := pin.Get()
c.Assert(err, qt.Equals, nil)
c.Assert(v, qt.Equals, false)
err = pin.Toggle()
c.Assert(err, qt.Equals, nil)
c.Assert(fdev.Registers[rGPIO], qt.Equals, uint8(0b10))
err = pin.Toggle()
c.Assert(err, qt.Equals, nil)
c.Assert(fdev.Registers[rGPIO], qt.Equals, uint8(0))
}
func TestPinMode(t *testing.T) {
c := qt.New(t)
bus := tester.NewI2CBus(c)
fdev := newDevice(bus, 0x20)
dev, err := NewI2C(bus, 0x20)
c.Assert(err, qt.IsNil)
pin := dev.Pin(1)
mode, err := pin.GetMode()
c.Assert(err, qt.IsNil)
c.Assert(mode, qt.Equals, PinMode(0))
c.Assert(mode&Direction, qt.Equals, Input)
err = pin.SetMode(Input | Pullup | Invert)
c.Assert(err, qt.IsNil)
c.Assert(fdev.Registers[rIODIR], qt.Equals, uint8(0b11111111))
c.Assert(fdev.Registers[rIOPOL], qt.Equals, uint8(0b10))
c.Assert(fdev.Registers[rGPPU], qt.Equals, uint8(0b10))
mode, err = pin.GetMode()
c.Assert(err, qt.IsNil)
c.Assert(mode, qt.Equals, Input|Pullup|Invert)
// Set another pin to output.
err = dev.Pin(2).SetMode(Output)
c.Assert(err, qt.IsNil)
c.Assert(fdev.Registers[rIODIR], qt.Equals, uint8(0b11111011))
c.Assert(fdev.Registers[rIOPOL], qt.Equals, uint8(0b10))
c.Assert(fdev.Registers[rGPPU], qt.Equals, uint8(0b10))
// Check that changing a pin in port B works too.
err = dev.Pin(8).SetMode(Output)
c.Assert(err, qt.IsNil)
c.Assert(fdev.Registers[rIODIR], qt.Equals, uint8(0b11111011))
c.Assert(fdev.Registers[rIODIR|portB], qt.Equals, uint8(0b11111110))
c.Assert(fdev.Registers[rIOPOL], qt.Equals, uint8(0b10))
c.Assert(fdev.Registers[rIOPOL|portB], qt.Equals, uint8(0))
c.Assert(fdev.Registers[rGPPU], qt.Equals, uint8(0b10))
c.Assert(fdev.Registers[rGPPU|portB], qt.Equals, uint8(0))
}
func TestPins(t *testing.T) {
c := qt.New(t)
var p Pins
p.Set(1, true)
c.Assert(p, qt.Equals, Pins(0b10))
c.Assert(p.Get(1), qt.Equals, true)
c.Assert(p.Get(0), qt.Equals, false)
c.Assert(p.Get(16), qt.Equals, false)
p.High(2)
c.Assert(p, qt.Equals, Pins(0b110))
p.Low(1)
c.Assert(p, qt.Equals, Pins(0b100))
}
func TestInitWithError(t *testing.T) {
c := qt.New(t)
bus := tester.NewI2CBus(c)
fdev := newDevice(bus, 0x20)
fdev.Err = fmt.Errorf("some error")
dev, err := NewI2C(bus, 0x20)
c.Assert(err, qt.ErrorMatches, `cannot initialize mcp23017 device at 0x20: some error`)
c.Assert(dev, qt.IsNil)
}
func newDevice(bus *tester.I2CBus, addr uint8) *tester.I2CDevice {
fdev := bus.NewDevice(addr)
// IODIRA and IODIRB are all ones by default.
fdev.Registers[rIODIR] = 0xff
fdev.Registers[rIODIR|portB] = 0xff
return fdev
}
+211
View File
@@ -0,0 +1,211 @@
package mcp23017
// All is a convenience value that represents all pins high (or all mask bits one).
var All = PinSlice{0xffff}
// Devices holds a slice of devices that can be treated as one
// contiguous set of devices. Earlier entries in the slice have
// lower-numbered pins, so index 0 holds pins 0-7, index 1 holds
// pins 8-15, etc.
type Devices []*Device
// NewI2CDevices returns a Devices slice holding the Device values
// for all the given addresses on the given bus.
// When more than one bus is in use, create the slice yourself.
func NewI2CDevices(bus I2C, addrs ...uint8) (Devices, error) {
devs := make(Devices, len(addrs))
for i, addr := range addrs {
dev, err := NewI2C(bus, addr)
if err != nil {
// TODO return a more informative error.
return nil, err
}
devs[i] = dev
}
return devs, nil
}
// SetModes sets the pin modes of all the pins on all the devices in devs.
// If there are less entries in modes than there are pins, the
// last entry is replicated to all of them (or PinMode(0) if modes
// is empty).
func (devs Devices) SetModes(modes []PinMode) error {
var defaultModes []PinMode
if len(modes) > 0 {
defaultModes = modes[len(modes)-1:]
}
for i, dev := range devs {
pinStart := i * PinCount
var devModes []PinMode
if pinStart < len(modes) {
devModes = modes[pinStart:]
} else {
devModes = defaultModes
}
if err := dev.SetModes(devModes); err != nil {
return err
}
}
return nil
}
// GetModes gets the pin modes from the devices.
// It's OK if modes isn't the same length as all the pins:
// extra entries will be left unchanged.
func (devs Devices) GetModes(modes []PinMode) error {
for i, dev := range devs {
pinStart := i * PinCount
if pinStart >= len(modes) {
break
}
if err := dev.GetModes(modes[pinStart:]); err != nil {
return err
}
}
return nil
}
// Pin returns the pin for the given number.
func (devs Devices) Pin(pin int) Pin {
if pin < 0 || pin >= len(devs)*PinCount {
panic("pin out of range")
}
return devs[pin/PinCount].Pin(pin % PinCount)
}
// GetPins returns pin values for all the pins.
func (devs Devices) GetPins(pins PinSlice) error {
for i, dev := range devs {
if i >= len(pins) {
break
}
devPins, err := dev.GetPins()
if err != nil {
return err
}
pins[i] = devPins
}
return nil
}
// SetPins sets all the pins for which mask is high
// to their respective values in pins.
//
// That is, it does the equivalent of:
//
// for i := 0; i < PinCount*len(devs); i++ {
// if mask.Get(i) {
// d.Pin(i).Set(pins.Get(i))
// }
// }
func (devs Devices) SetPins(pins, mask PinSlice) error {
defaultPins := pins.extra()
defaultMask := mask.extra()
for i, dev := range devs {
devPins := defaultPins
if i < len(pins) {
devPins = pins[i]
}
devMask := defaultMask
if i < len(mask) {
devMask = mask[i]
}
if err := dev.SetPins(devPins, devMask); err != nil {
return err
}
}
return nil
}
// TogglePins inverts the values on all pins for
// which mask is high.
func (devs Devices) TogglePins(mask PinSlice) error {
defaultMask := mask.extra()
for i, dev := range devs {
devMask := defaultMask
if i < len(mask) {
devMask = mask[i]
}
if err := dev.TogglePins(devMask); err != nil {
return err
}
}
return nil
}
// PinSlice represents an arbitrary nunber of pins, each element corresponding
// to the pins for one device. The value of the highest numbered pin in the
// slice is extended to all other pins beyond the end of the slice.
type PinSlice []Pins
// Get returns the value for the given pin. If the length of pins is too short
// for the pin number, the value of the highest available pin is returned.
// That is, the highest numbered pin in the last element of pins
// is effectively replicated to all other elements.
//
// This means that PinSlice{} means "all pins high" and
// PinSlice{0xffff} means "all pins low".
func (pins PinSlice) Get(i int) bool {
if len(pins) == 0 || i < 0 {
return false
}
if i >= len(pins)*PinCount {
return pins[len(pins)-1].Get(PinCount - 1)
}
return pins[i/PinCount].Get(i % PinCount)
}
// Set sets the value for the given pin.
func (pins PinSlice) Set(i int, value bool) {
pins[i/PinCount].Set(i%PinCount, value)
}
// High is short for p.Set(pin, true).
func (pins PinSlice) High(pin int) {
pins[pin/PinCount].High(pin % PinCount)
}
// High is short for p.Set(pin, false).
func (pins PinSlice) Low(pin int) {
pins[pin/PinCount].Low(pin % PinCount)
}
// Toggle inverts the value of the given pin.
func (pins PinSlice) Toggle(pin int) {
pins[pin/PinCount].Toggle(pin % PinCount)
}
// Ensure checks that pins has enough space to store
// at least length pins. If it does, it returns pins unchanged.
// Otherwise, it returns pins with elements appended as needed,
// populating additonal elements by replicating the
// highest pin (mirroring the behavior of PinSlice.Get).
func (pins PinSlice) Ensure(length int) PinSlice {
if length == 0 {
return pins
}
n := length/PinCount + 1
if len(pins) >= n {
return pins
}
// TODO we could potentially make use of additional
// extra capacity in pins when available instead
// of allocating a new slice always.
newPins := make(PinSlice, n)
copy(newPins, pins)
if extend := pins.extra(); extend != 0 {
for i := len(pins); i < n; i++ {
newPins[i] = extend
}
}
return newPins
}
// extra returns the value of implied extra elements beyond
// the end of pins.
func (pins PinSlice) extra() Pins {
if len(pins) == 0 || !pins[len(pins)-1].Get(PinCount-1) {
return 0
}
return ^Pins(0)
}
+190
View File
@@ -0,0 +1,190 @@
package mcp23017
import (
"testing"
qt "github.com/frankban/quicktest"
"tinygo.org/x/drivers/tester"
)
func TestDevicesGetPins(t *testing.T) {
c := qt.New(t)
bus := tester.NewI2CBus(c)
fdev0 := newDevice(bus, 0x20)
fdev1 := newDevice(bus, 0x21)
fdev0.Registers[rGPIO] = 0b10101100
fdev0.Registers[rGPIO|portB] = 0b01010011
fdev1.Registers[rGPIO] = 0b10101101
fdev1.Registers[rGPIO|portB] = 0b01010010
devs, err := NewI2CDevices(bus, 0x20, 0x21)
c.Assert(err, qt.IsNil)
pins := make(PinSlice, 2)
err = devs.GetPins(pins)
c.Assert(err, qt.IsNil)
c.Assert(pins, qt.DeepEquals, PinSlice{0b01010011_10101100, 0b01010010_10101101})
// It's OK to pass less elements than there are devices.
pins = make(PinSlice, 1)
err = devs.GetPins(pins)
c.Assert(err, qt.IsNil)
c.Assert(pins, qt.DeepEquals, PinSlice{0b01010011_10101100})
}
func TestDevicesSetPinsAllOff(t *testing.T) {
c := qt.New(t)
bus := tester.NewI2CBus(c)
fdev0 := newDevice(bus, 0x20)
fdev1 := newDevice(bus, 0x21)
fdev0.Registers[rGPIO] = 0b10101100
fdev0.Registers[rGPIO|portB] = 0b01010011
fdev1.Registers[rGPIO] = 0b10101101
fdev1.Registers[rGPIO|portB] = 0b01010010
devs, err := NewI2CDevices(bus, 0x20, 0x21)
c.Assert(err, qt.IsNil)
err = devs.SetPins(nil, PinSlice{0xffff})
c.Assert(err, qt.IsNil)
pins := make(PinSlice, 2)
err = devs.GetPins(pins)
c.Assert(err, qt.IsNil)
c.Assert(pins, qt.DeepEquals, PinSlice{0, 0})
}
func TestDevicesSetPinsAllOn(t *testing.T) {
c := qt.New(t)
bus := tester.NewI2CBus(c)
fdev0 := newDevice(bus, 0x20)
fdev1 := newDevice(bus, 0x21)
fdev0.Registers[rGPIO] = 0b10101100
fdev0.Registers[rGPIO|portB] = 0b01010011
fdev1.Registers[rGPIO] = 0b10101101
fdev1.Registers[rGPIO|portB] = 0b01010010
devs, err := NewI2CDevices(bus, 0x20, 0x21)
c.Assert(err, qt.IsNil)
err = devs.SetPins(PinSlice{0xffff}, PinSlice{0xffff})
c.Assert(err, qt.IsNil)
pins := make(PinSlice, 2)
err = devs.GetPins(pins)
c.Assert(err, qt.IsNil)
c.Assert(pins, qt.DeepEquals, PinSlice{0xffff, 0xffff})
}
func TestDevicesSetPinsMask(t *testing.T) {
c := qt.New(t)
bus := tester.NewI2CBus(c)
fdev0 := newDevice(bus, 0x20)
fdev1 := newDevice(bus, 0x21)
fdev0.Registers[rGPIO] = 0b10101100
fdev0.Registers[rGPIO|portB] = 0b01010011
fdev1.Registers[rGPIO] = 0b10101101
fdev1.Registers[rGPIO|portB] = 0b01010010
devs, err := NewI2CDevices(bus, 0x20, 0x21)
c.Assert(err, qt.IsNil)
// Sanity check the original value of the pins.
pins := make(PinSlice, 2)
err = devs.GetPins(pins)
c.Assert(err, qt.IsNil)
c.Assert(pins, qt.DeepEquals, PinSlice{0b01010011_10101100, 0b01010010_10101101})
pins = make(PinSlice, 2)
pins.High(0)
pins.High(1)
mask := make(PinSlice, 2)
mask.High(0)
mask.High(16)
err = devs.SetPins(pins, mask)
c.Assert(err, qt.IsNil)
pins = make(PinSlice, 2)
err = devs.GetPins(pins)
c.Assert(err, qt.IsNil)
c.Assert(pins, qt.DeepEquals, PinSlice{0b01010011_10101101, 0b01010010_10101100})
}
func TestDevicesTogglePins(t *testing.T) {
c := qt.New(t)
bus := tester.NewI2CBus(c)
newDevice(bus, 0x20)
newDevice(bus, 0x21)
devs, err := NewI2CDevices(bus, 0x20, 0x21)
c.Assert(err, qt.IsNil)
mask := make(PinSlice, 2)
mask.High(0)
mask.High(16)
err = devs.TogglePins(mask)
c.Assert(err, qt.IsNil)
pins := make(PinSlice, 2)
err = devs.GetPins(pins)
c.Assert(err, qt.IsNil)
c.Assert(pins, qt.DeepEquals, PinSlice{0b00000000_00000001, 0b00000000_00000001})
}
func TestDevicesSetGetModes(t *testing.T) {
c := qt.New(t)
bus := tester.NewI2CBus(c)
fdev0 := newDevice(bus, 0x20)
fdev1 := newDevice(bus, 0x21)
devs, err := NewI2CDevices(bus, 0x20, 0x21)
c.Assert(err, qt.IsNil)
// Sanity check that IODIR registers start off all ones.
c.Assert(fdev0.Registers[rIODIR], qt.Equals, uint8(0xff))
// The last entry is replicated to fill them all.
err = devs.SetModes([]PinMode{Input | Pullup, Output})
c.Assert(err, qt.IsNil)
c.Assert(fdev0.Registers[rIODIR], qt.Equals, uint8(1))
c.Assert(fdev0.Registers[rIODIR|portB], qt.Equals, uint8(0))
c.Assert(fdev1.Registers[rIODIR], qt.Equals, uint8(0))
c.Assert(fdev1.Registers[rIODIR|portB], qt.Equals, uint8(0))
modes := make([]PinMode, 2)
err = devs.GetModes(modes)
c.Assert(err, qt.Equals, nil)
c.Assert(modes, qt.DeepEquals, []PinMode{Input | Pullup, Output})
// It's OK to pass a smaller slice to GetModes.
modes = make([]PinMode, 1)
err = devs.GetModes(modes)
c.Assert(err, qt.Equals, nil)
c.Assert(modes, qt.DeepEquals, []PinMode{Input | Pullup})
}
func TestDevicesPin(t *testing.T) {
c := qt.New(t)
bus := tester.NewI2CBus(c)
newDevice(bus, 0x20)
fdev1 := newDevice(bus, 0x21)
devs, err := NewI2CDevices(bus, 0x20, 0x21)
c.Assert(err, qt.IsNil)
pin := devs.Pin(16)
v, err := pin.Get()
c.Assert(err, qt.Equals, nil)
c.Assert(v, qt.Equals, false)
err = pin.High()
c.Assert(err, qt.Equals, nil)
c.Assert(fdev1.Registers[rGPIO], qt.Equals, uint8(1))
}
func TestPinSlice(t *testing.T) {
c := qt.New(t)
pins := PinSlice(nil).Ensure(20)
pins.Set(16, true)
c.Assert(pins, qt.DeepEquals, PinSlice{0, 1})
pins.Set(31, true)
c.Assert(pins, qt.DeepEquals, PinSlice{0, 0b10000000_00000001})
c.Assert(pins.Get(0), qt.Equals, false)
c.Assert(pins.Get(16), qt.Equals, true)
pins = pins.Ensure(40)
c.Assert(pins, qt.DeepEquals, PinSlice{0, 0b10000000_00000001, 0xffff})
pins.Low(16)
c.Assert(pins.Get(16), qt.Equals, false)
pins.High(16)
c.Assert(pins.Get(16), qt.Equals, true)
pins.Toggle(16)
c.Assert(pins.Get(16), qt.Equals, false)
}
+4 -2
View File
@@ -7,11 +7,13 @@ package mcp3008 // import "tinygo.org/x/drivers/mcp3008"
import (
"errors"
"machine"
"tinygo.org/x/drivers"
)
// Device wraps MCP3008 SPI ADC.
type Device struct {
bus machine.SPI
bus drivers.SPI
cs machine.Pin
tx []byte
rx []byte
@@ -32,7 +34,7 @@ type ADCPin struct {
}
// New returns a new MCP3008 driver. Pass in a fully configured SPI bus.
func New(b machine.SPI, csPin machine.Pin) *Device {
func New(b drivers.SPI, csPin machine.Pin) *Device {
d := &Device{bus: b,
cs: csPin,
tx: make([]byte, 3),
+112
View File
@@ -0,0 +1,112 @@
// +build microbit
// Package microbitmatrix implements a driver for the BBC micro:bit's LED matrix.
//
// Schematic: https://github.com/bbcmicrobit/hardware/blob/master/SCH_BBC-Microbit_V1.3B.pdf
//
package microbitmatrix // import "tinygo.org/x/drivers/microbitmatrix"
import (
"machine"
"time"
)
var matrixRotations = [4][5][5][2]uint8{
{ // 0
{{0, 0}, {1, 3}, {0, 1}, {1, 4}, {0, 2}},
{{2, 3}, {2, 4}, {2, 5}, {2, 6}, {2, 7}},
{{1, 1}, {0, 8}, {1, 2}, {2, 8}, {1, 0}},
{{0, 7}, {0, 6}, {0, 5}, {0, 4}, {0, 3}},
{{2, 2}, {1, 6}, {2, 0}, {1, 5}, {2, 1}},
},
{ // 90 CCW
{{0, 2}, {2, 7}, {1, 0}, {0, 3}, {2, 1}},
{{1, 4}, {2, 6}, {2, 8}, {0, 4}, {1, 5}},
{{0, 1}, {2, 5}, {1, 2}, {0, 5}, {2, 0}},
{{1, 3}, {2, 4}, {0, 8}, {0, 6}, {1, 6}},
{{0, 0}, {2, 3}, {1, 1}, {0, 7}, {2, 2}},
},
{ // 180
{{2, 1}, {1, 5}, {2, 0}, {1, 6}, {2, 2}},
{{0, 3}, {0, 4}, {0, 5}, {0, 6}, {0, 7}},
{{1, 0}, {2, 8}, {1, 2}, {0, 8}, {1, 1}},
{{2, 7}, {2, 6}, {2, 5}, {2, 4}, {2, 3}},
{{0, 2}, {1, 4}, {0, 1}, {1, 3}, {0, 0}},
},
{ // 270
{{2, 2}, {0, 7}, {1, 1}, {2, 3}, {0, 0}},
{{1, 6}, {0, 6}, {0, 8}, {2, 4}, {1, 3}},
{{2, 0}, {0, 5}, {1, 2}, {2, 5}, {0, 1}},
{{1, 5}, {0, 4}, {2, 8}, {2, 6}, {1, 4}},
{{2, 1}, {0, 3}, {1, 0}, {2, 7}, {0, 2}},
},
}
type Device struct {
pin [12]machine.Pin
buffer [3][9]bool
rotation uint8
}
// Configure sets up the device.
func (d *Device) Configure(cfg Config) {
d.SetRotation(cfg.Rotation)
for i := machine.LED_COL_1; i <= machine.LED_ROW_3; i++ {
d.pin[i-machine.LED_COL_1] = i
d.pin[i-machine.LED_COL_1].Configure(machine.PinConfig{Mode: machine.PinOutput})
}
d.ClearDisplay()
d.DisableAll()
}
// Display sends the buffer (if any) to the screen.
func (d *Device) Display() error {
for row := 0; row < 3; row++ {
d.DisableAll()
d.pin[9+row].High()
for col := 0; col < 9; col++ {
if d.buffer[row][col] {
d.pin[col].Low()
}
}
time.Sleep(time.Millisecond * 2)
}
return nil
}
// ClearDisplay erases the internal buffer
func (d *Device) ClearDisplay() {
for row := 0; row < 3; row++ {
for col := 0; col < 9; col++ {
d.buffer[row][col] = false
}
}
}
// DisableAll disables all the LEDs without modifying the buffer
func (d *Device) DisableAll() {
for i := machine.LED_COL_1; i <= machine.LED_COL_9; i++ {
d.pin[i-machine.LED_COL_1].High()
}
for i := machine.LED_ROW_1; i <= machine.LED_ROW_3; i++ {
d.pin[i-machine.LED_COL_1].Low()
}
}
// EnableAll enables all the LEDs without modifying the buffer
func (d *Device) EnableAll() {
for i := machine.LED_COL_1; i <= machine.LED_COL_9; i++ {
d.pin[i-machine.LED_COL_1].Low()
}
for i := machine.LED_ROW_1; i <= machine.LED_ROW_3; i++ {
d.pin[i-machine.LED_COL_1].High()
}
}
// Size returns the current size of the display.
func (d *Device) Size() (w, h int16) {
return 5, 5
}
+122
View File
@@ -0,0 +1,122 @@
// +build microbit_v2
// Package microbitmatrix implements a driver for the BBC micro:bit version 2 LED matrix.
//
// Schematic:
//
package microbitmatrix // import "tinygo.org/x/drivers/microbitmatrix"
import (
"machine"
"time"
)
var matrixRotations = [4][5][5][2]uint8{
{ // 0
{{0, 0}, {1, 0}, {2, 0}, {3, 0}, {4, 0}},
{{0, 1}, {1, 1}, {2, 1}, {3, 1}, {4, 1}},
{{0, 2}, {1, 2}, {2, 2}, {3, 2}, {4, 2}},
{{0, 3}, {1, 3}, {2, 3}, {3, 3}, {4, 3}},
{{0, 4}, {1, 4}, {2, 4}, {3, 4}, {4, 4}},
},
{ // 90 CCW
{{4, 0}, {4, 1}, {4, 2}, {4, 3}, {4, 4}},
{{3, 0}, {3, 1}, {3, 2}, {3, 3}, {3, 4}},
{{2, 0}, {2, 1}, {2, 2}, {2, 3}, {2, 4}},
{{1, 0}, {1, 1}, {1, 2}, {1, 3}, {1, 4}},
{{0, 0}, {0, 1}, {0, 2}, {0, 3}, {0, 4}},
},
{ // 180
{{4, 4}, {3, 4}, {2, 4}, {1, 4}, {0, 4}},
{{4, 3}, {3, 3}, {2, 3}, {1, 3}, {0, 3}},
{{4, 2}, {3, 2}, {2, 2}, {1, 2}, {0, 2}},
{{4, 1}, {3, 1}, {2, 1}, {1, 1}, {0, 1}},
{{4, 0}, {3, 0}, {2, 0}, {1, 0}, {0, 0}},
},
{ // 270
{{0, 4}, {0, 3}, {0, 2}, {0, 1}, {0, 0}},
{{1, 4}, {1, 3}, {1, 2}, {1, 1}, {1, 0}},
{{2, 4}, {2, 3}, {2, 2}, {2, 1}, {2, 0}},
{{3, 4}, {3, 3}, {3, 2}, {3, 1}, {3, 0}},
{{4, 4}, {4, 3}, {4, 2}, {4, 1}, {4, 0}},
},
}
type Device struct {
pin [10]machine.Pin
buffer [5][5]bool
rotation uint8
}
// Configure sets up the device.
func (d *Device) Configure(cfg Config) {
d.SetRotation(cfg.Rotation)
d.pin[0] = machine.LED_COL_1
d.pin[1] = machine.LED_COL_2
d.pin[2] = machine.LED_COL_3
d.pin[3] = machine.LED_COL_4
d.pin[4] = machine.LED_COL_5
d.pin[5] = machine.LED_ROW_1
d.pin[6] = machine.LED_ROW_2
d.pin[7] = machine.LED_ROW_3
d.pin[8] = machine.LED_ROW_4
d.pin[9] = machine.LED_ROW_5
for i := 0; i < 10; i++ {
d.pin[i].Configure(machine.PinConfig{Mode: machine.PinOutput})
}
d.ClearDisplay()
d.DisableAll()
}
// Display sends the buffer (if any) to the screen.
func (d *Device) Display() error {
for x := 0; x < 5; x++ {
d.DisableAll()
d.pin[x].Low()
for y := 0; y < 5; y++ {
if d.buffer[x][y] {
d.pin[5+y].High()
} else {
d.pin[5+y].Low()
}
}
time.Sleep(time.Millisecond * 4)
}
return nil
}
// ClearDisplay erases the internal buffer
func (d *Device) ClearDisplay() {
for row := 0; row < 5; row++ {
for col := 0; col < 5; col++ {
d.buffer[row][col] = false
}
}
}
// DisableAll disables all the LEDs without modifying the buffer
func (d *Device) DisableAll() {
for i := 0; i < 5; i++ {
d.pin[i].High()
d.pin[5+i].Low()
}
}
// EnableAll enables all the LEDs without modifying the buffer
func (d *Device) EnableAll() {
for i := 0; i < 5; i++ {
d.pin[i].Low()
d.pin[5+i].High()
}
}
// Size returns the current size of the display.
func (d *Device) Size() (w, h int16) {
return 5, 5
}
-102
View File
@@ -6,68 +6,17 @@ package microbitmatrix // import "tinygo.org/x/drivers/microbitmatrix"
import (
"image/color"
"machine"
"time"
)
var matrixRotations = [4][5][5][2]uint8{
{ // 0
{{0, 0}, {1, 3}, {0, 1}, {1, 4}, {0, 2}},
{{2, 3}, {2, 4}, {2, 5}, {2, 6}, {2, 7}},
{{1, 1}, {0, 8}, {1, 2}, {2, 8}, {1, 0}},
{{0, 7}, {0, 6}, {0, 5}, {0, 4}, {0, 3}},
{{2, 2}, {1, 6}, {2, 0}, {1, 5}, {2, 1}},
},
{ // 90 CCW
{{0, 2}, {2, 7}, {1, 0}, {0, 3}, {2, 1}},
{{1, 4}, {2, 6}, {2, 8}, {0, 4}, {1, 5}},
{{0, 1}, {2, 5}, {1, 2}, {0, 5}, {2, 0}},
{{1, 3}, {2, 4}, {0, 8}, {0, 6}, {1, 6}},
{{0, 0}, {2, 3}, {1, 1}, {0, 7}, {2, 2}},
},
{ // 180
{{2, 1}, {1, 5}, {2, 0}, {1, 6}, {2, 2}},
{{0, 3}, {0, 4}, {0, 5}, {0, 6}, {0, 7}},
{{1, 0}, {2, 8}, {1, 2}, {0, 8}, {1, 1}},
{{2, 7}, {2, 6}, {2, 5}, {2, 4}, {2, 3}},
{{0, 2}, {1, 4}, {0, 1}, {1, 3}, {0, 0}},
},
{ // 270
{{2, 2}, {0, 7}, {1, 1}, {2, 3}, {0, 0}},
{{1, 6}, {0, 6}, {0, 8}, {2, 4}, {1, 3}},
{{2, 0}, {0, 5}, {1, 2}, {2, 5}, {0, 1}},
{{1, 5}, {0, 4}, {2, 8}, {2, 6}, {1, 4}},
{{2, 1}, {0, 3}, {1, 0}, {2, 7}, {0, 2}},
},
}
type Config struct {
Rotation uint8
}
type Device struct {
pin [12]machine.Pin
buffer [3][9]bool
rotation uint8
}
// New returns a new microbitmatrix driver.
func New() Device {
return Device{}
}
// Configure sets up the device.
func (d *Device) Configure(cfg Config) {
d.SetRotation(cfg.Rotation)
for i := machine.LED_COL_1; i <= machine.LED_ROW_3; i++ {
d.pin[i-machine.LED_COL_1] = i
d.pin[i-machine.LED_COL_1].Configure(machine.PinConfig{Mode: machine.PinOutput})
}
d.ClearDisplay()
d.DisableAll()
}
// SetRotation changes the rotation of the LED matrix
func (d *Device) SetRotation(rotation uint8) {
d.rotation = rotation % 4
@@ -92,54 +41,3 @@ func (d *Device) GetPixel(x int16, y int16) bool {
}
return d.buffer[matrixRotations[d.rotation][x][y][0]][matrixRotations[d.rotation][x][y][1]]
}
// Display sends the buffer (if any) to the screen.
func (d *Device) Display() error {
for row := 0; row < 3; row++ {
d.DisableAll()
d.pin[9+row].High()
for col := 0; col < 9; col++ {
if d.buffer[row][col] {
d.pin[col].Low()
}
}
time.Sleep(time.Millisecond * 2)
}
return nil
}
// ClearDisplay erases the internal buffer
func (d *Device) ClearDisplay() {
for row := 0; row < 3; row++ {
for col := 0; col < 9; col++ {
d.buffer[row][col] = false
}
}
}
// DisableAll disables all the LEDs without modifying the buffer
func (d *Device) DisableAll() {
for i := machine.LED_COL_1; i <= machine.LED_COL_9; i++ {
d.pin[i-machine.LED_COL_1].High()
}
for i := machine.LED_ROW_1; i <= machine.LED_ROW_3; i++ {
d.pin[i-machine.LED_COL_1].Low()
}
}
// EnableAll enables all the LEDs without modifying the buffer
func (d *Device) EnableAll() {
for i := machine.LED_COL_1; i <= machine.LED_COL_9; i++ {
d.pin[i-machine.LED_COL_1].Low()
}
for i := machine.LED_ROW_1; i <= machine.LED_ROW_3; i++ {
d.pin[i-machine.LED_COL_1].High()
}
}
// Size returns the current size of the display.
func (d *Device) Size() (w, h int16) {
return 5, 5
}
+4 -2
View File
@@ -9,11 +9,13 @@ import (
"image/color"
"machine"
"time"
"tinygo.org/x/drivers"
)
// Device wraps an SPI connection.
type Device struct {
bus machine.SPI
bus drivers.SPI
dcPin machine.Pin
rstPin machine.Pin
scePin machine.Pin
@@ -29,7 +31,7 @@ type Config struct {
}
// New creates a new PCD8544 connection. The SPI bus must already be configured.
func New(bus machine.SPI, dcPin, rstPin, scePin machine.Pin) *Device {
func New(bus drivers.SPI, dcPin, rstPin, scePin machine.Pin) *Device {
return &Device{
bus: bus,
dcPin: dcPin,
+13
View File
@@ -0,0 +1,13 @@
package drivers
// SPI represents a SPI bus. It is implemented by the machine.SPI type.
type SPI interface {
// Tx transmits the given buffer w and receives at the same time the buffer r.
// The two buffers must be the same length. The only exception is when w or r are nil,
// in which case Tx only transmits (without receiving) or only receives (while sending 0 bytes).
Tx(w, r []byte) error
// Transfer writes a single byte out on the SPI bus and receives a byte at the same time.
// If you want to transfer multiple bytes, it is more efficient to use Tx instead.
Transfer(b byte) (byte, error)
}
+2 -2
View File
@@ -37,7 +37,7 @@ type I2CBus struct {
}
type SPIBus struct {
wire machine.SPI
wire drivers.SPI
dcPin machine.Pin
resetPin machine.Pin
csPin machine.Pin
@@ -62,7 +62,7 @@ func NewI2C(bus drivers.I2C) Device {
}
// NewSPI creates a new SSD1306 connection. The SPI wire must already be configured.
func NewSPI(bus machine.SPI, dcPin, resetPin, csPin machine.Pin) Device {
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})
+4 -2
View File
@@ -10,6 +10,8 @@ import (
"errors"
"time"
"tinygo.org/x/drivers"
)
type Model uint8
@@ -17,7 +19,7 @@ type Rotation uint8
// Device wraps an SPI connection.
type Device struct {
bus machine.SPI
bus drivers.SPI
dcPin machine.Pin
resetPin machine.Pin
csPin machine.Pin
@@ -35,7 +37,7 @@ type Config struct {
}
// New creates a new SSD1331 connection. The SPI wire must already be configured.
func New(bus machine.SPI, resetPin, dcPin, csPin machine.Pin) Device {
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})
+4 -2
View File
@@ -9,6 +9,8 @@ import (
"image/color"
"machine"
"time"
"tinygo.org/x/drivers"
)
var (
@@ -18,7 +20,7 @@ var (
// Device wraps an SPI connection.
type Device struct {
bus machine.SPI
bus drivers.SPI
dcPin machine.Pin
resetPin machine.Pin
csPin machine.Pin
@@ -40,7 +42,7 @@ type Config struct {
}
// New creates a new SSD1351 connection. The SPI wire must already be configured.
func New(bus machine.SPI, resetPin, dcPin, csPin, enPin, rwPin machine.Pin) Device {
func New(bus drivers.SPI, resetPin, dcPin, csPin, enPin, rwPin machine.Pin) Device {
return Device{
bus: bus,
dcPin: dcPin,
+4 -2
View File
@@ -10,6 +10,8 @@ import (
"time"
"errors"
"tinygo.org/x/drivers"
)
type Model uint8
@@ -17,7 +19,7 @@ type Rotation uint8
// Device wraps an SPI connection.
type Device struct {
bus machine.SPI
bus drivers.SPI
dcPin machine.Pin
resetPin machine.Pin
csPin machine.Pin
@@ -44,7 +46,7 @@ type Config struct {
}
// New creates a new ST7735 connection. The SPI wire must already be configured.
func New(bus machine.SPI, resetPin, dcPin, csPin, blPin machine.Pin) Device {
func New(bus drivers.SPI, resetPin, dcPin, csPin, blPin machine.Pin) Device {
dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
resetPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
+2
View File
@@ -50,6 +50,8 @@ const (
GMCTRP1 = 0xE0
GMCTRN1 = 0xE1
GSCAN = 0x45
VSCRDEF = 0x33
VSCRSADD = 0x37
NO_ROTATION Rotation = 0
ROTATION_90 Rotation = 1 // 90 degrees clock-wise rotation
+34 -10
View File
@@ -12,15 +12,19 @@ import (
"time"
"errors"
"tinygo.org/x/drivers"
)
// Rotation controls the rotation used by the display.
type Rotation uint8
// FrameRate controls the frame rate used by the display.
type FrameRate uint8
// Device wraps an SPI connection.
type Device struct {
bus machine.SPI
bus drivers.SPI
dcPin machine.Pin
resetPin machine.Pin
csPin machine.Pin
@@ -50,7 +54,7 @@ type Config struct {
}
// New creates a new ST7789 connection. The SPI wire must already be configured.
func New(bus machine.SPI, resetPin, dcPin, csPin, blPin machine.Pin) Device {
func New(bus drivers.SPI, resetPin, dcPin, csPin, blPin machine.Pin) Device {
dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
resetPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
@@ -276,8 +280,7 @@ func (d *Device) FillRectangle(x, y, width, height int16, c color.RGBA) error {
return nil
}
// FillRectangle fills a rectangle at a given coordinates with a buffer
// FillRectangleWithBuffer fills buffer with a rectangle at a given coordinates.
func (d *Device) FillRectangleWithBuffer(x, y, width, height int16, buffer []color.RGBA) error {
i, j := d.Size()
if x < 0 || y < 0 || width <= 0 || height <= 0 ||
@@ -369,12 +372,12 @@ func (d *Device) SetRotation(rotation Rotation) {
d.Data(madctl)
}
// Command sends a command to the display
// Command sends a command to the display.
func (d *Device) Command(command uint8) {
d.Tx([]byte{command}, true)
}
// Command sends a data to the display
// Data sends data to the display.
func (d *Device) Data(data uint8) {
d.Tx([]byte{data}, false)
}
@@ -392,13 +395,13 @@ func (d *Device) Tx(data []byte, isCommand bool) {
}
// Rx reads data from the display
func (d *Device) Rx(command uint8, read_bytes []byte) {
func (d *Device) Rx(command uint8, data []byte) {
d.dcPin.Low()
d.csPin.Low()
d.bus.Transfer(command)
d.dcPin.High()
for i := range read_bytes {
read_bytes[i], _ = d.bus.Transfer(0xFF)
for i := range data {
data[i], _ = d.bus.Transfer(0xFF)
}
d.csPin.High()
}
@@ -420,7 +423,7 @@ func (d *Device) EnableBacklight(enable bool) {
}
}
// InverColors inverts the colors of the screen
// InvertColors inverts the colors of the screen
func (d *Device) InvertColors(invert bool) {
if invert {
d.Command(INVON)
@@ -434,6 +437,27 @@ func (d *Device) IsBGR(bgr bool) {
d.isBGR = bgr
}
// SetScrollArea sets an area to scroll with fixed top and bottom parts of the display.
func (d *Device) SetScrollArea(topFixedArea, bottomFixedArea int16) {
d.Command(VSCRDEF)
d.Tx([]uint8{
uint8(topFixedArea >> 8), uint8(topFixedArea),
uint8(d.height - topFixedArea - bottomFixedArea>>8), uint8(d.height - topFixedArea - bottomFixedArea),
uint8(bottomFixedArea >> 8), uint8(bottomFixedArea)},
false)
}
// SetScroll sets the vertical scroll address of the display.
func (d *Device) SetScroll(line int16) {
d.Command(VSCRSADD)
d.Tx([]uint8{uint8(line >> 8), uint8(line)}, false)
}
// StopScroll returns the display to its normal state.
func (d *Device) StopScroll() {
d.Command(NORON)
}
// RGBATo565 converts a color.RGBA to uint16 used in the display
func RGBATo565(c color.RGBA) uint16 {
r, g, b, _ := c.RGBA()
+9 -31
View File
@@ -10,7 +10,7 @@ type I2CDevice struct {
addr uint8
// Registers holds the device registers. It can be inspected
// or changed as desired for testing.
registers [MaxRegisters]uint8
Registers [MaxRegisters]uint8
// If Err is non-nil, it will be returned as the error from the
// I2C methods.
Err error
@@ -29,35 +29,13 @@ func (d *I2CDevice) Addr() uint8 {
return d.addr
}
// SetupRegisters sets all of the Device registers.
// It is intended to be used when setting up a fake device
// for testing expected vs. actual values.
func (d *I2CDevice) SetupRegisters(regs []uint8) {
if len(regs) > MaxRegisters {
panic("exceeded maximum number of registers for fake device")
}
for k, v := range regs {
d.registers[k] = v
}
}
// SetupRegister sets one of the Device registers.
// It is intended to be used when setting up a fake device
// for testing expected vs. actual values.
func (d *I2CDevice) SetupRegister(r, v uint8) {
if r > MaxRegisters {
panic("exceeded maximum number of registers for fake device")
}
d.registers[r] = v
}
// ReadRegister implements I2C.ReadRegister.
func (d *I2CDevice) ReadRegister(r uint8, buf []byte) error {
if d.Err != nil {
return d.Err
}
d.AssertRegisterRange(r, buf)
copy(buf, d.registers[r:])
d.assertRegisterRange(r, buf)
copy(buf, d.Registers[r:])
return nil
}
@@ -66,18 +44,18 @@ func (d *I2CDevice) WriteRegister(r uint8, buf []byte) error {
if d.Err != nil {
return d.Err
}
d.AssertRegisterRange(r, buf)
copy(d.registers[r:], buf)
d.assertRegisterRange(r, buf)
copy(d.Registers[r:], buf)
return nil
}
// AssertRegisterRange asserts that reading or writing the given
// assertRegisterRange asserts that reading or writing the given
// register and subsequent registers is in range of the available registers.
func (d *I2CDevice) AssertRegisterRange(r uint8, buf []byte) {
if int(r) >= len(d.registers) {
func (d *I2CDevice) assertRegisterRange(r uint8, buf []byte) {
if int(r) >= len(d.Registers) {
d.c.Fatalf("register read/write [%#x, %#x] start out of range", r, int(r)+len(buf))
}
if int(r)+len(buf) > len(d.registers) {
if int(r)+len(buf) > len(d.Registers) {
d.c.Fatalf("register read/write [%#x, %#x] end out of range", r, int(r)+len(buf))
}
}
+16
View File
@@ -1,5 +1,7 @@
package tester
import "fmt"
// I2CBus implements the I2C interface in memory for testing.
type I2CBus struct {
c Failer
@@ -16,10 +18,24 @@ func NewI2CBus(c Failer) *I2CBus {
}
// AddDevice adds a new mock device to the mock I2C bus.
// It panics if a device with the same address is added more than once.
func (bus *I2CBus) AddDevice(d *I2CDevice) {
for _, dev := range bus.devices {
if dev.Addr() == d.addr {
panic(fmt.Errorf("device already added at address %#x", d))
}
}
bus.devices = append(bus.devices, d)
}
// NewDevice creates a new device with the given address
// and adds it to the mock I2C bus.
func (bus *I2CBus) NewDevice(addr uint8) *I2CDevice {
dev := NewI2CDevice(bus.c, addr)
bus.AddDevice(dev)
return dev
}
// ReadRegister implements I2C.ReadRegister.
func (bus *I2CBus) ReadRegister(addr uint8, r uint8, buf []byte) error {
return bus.FindDevice(addr).ReadRegister(r, buf)
+1 -1
View File
@@ -57,7 +57,7 @@ func New(pin machine.Pin) Device {
// Configure configures the ADC pin used for the thermistor.
func (d *Device) Configure() {
d.adc.Configure()
d.adc.Configure(machine.ADCConfig{})
}
// ReadTemperature returns the temperature in celsius milli degrees (°C/1000)
+4 -4
View File
@@ -86,7 +86,7 @@ func (res *FourWire) ReadX() uint16 {
res.xm.Pin.Configure(machine.PinConfig{Mode: machine.PinOutput})
res.xm.Pin.Low()
res.yp.Configure()
res.yp.Configure(machine.ADCConfig{})
return 0xFFFF - res.yp.Get()
}
@@ -101,7 +101,7 @@ func (res *FourWire) ReadY() uint16 {
res.ym.Pin.Configure(machine.PinConfig{Mode: machine.PinOutput})
res.ym.Pin.Low()
res.xp.Configure()
res.xp.Configure(machine.ADCConfig{})
return 0xFFFF - res.xp.Get()
}
@@ -114,8 +114,8 @@ func (res *FourWire) ReadZ() uint16 {
res.ym.Pin.Configure(machine.PinConfig{Mode: machine.PinOutput})
res.ym.Pin.High()
res.xm.Configure()
res.yp.Configure()
res.xm.Configure(machine.ADCConfig{})
res.yp.Configure(machine.ADCConfig{})
z1 := res.xm.Get()
z2 := res.yp.Get()
+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.14.0"
const Version = "0.15.0"
+4 -2
View File
@@ -9,6 +9,8 @@ import (
"image/color"
"machine"
"time"
"tinygo.org/x/drivers"
)
type Config struct {
@@ -19,7 +21,7 @@ type Config struct {
}
type Device struct {
bus machine.SPI
bus drivers.SPI
cs machine.Pin
dc machine.Pin
rst machine.Pin
@@ -51,7 +53,7 @@ var lutPartialUpdate = [30]uint8{
}
// New returns a new epd2in13x driver. Pass in a fully configured SPI bus.
func New(bus machine.SPI, csPin, dcPin, rstPin, busyPin machine.Pin) Device {
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})
+4 -2
View File
@@ -9,6 +9,8 @@ import (
"image/color"
"machine"
"time"
"tinygo.org/x/drivers"
)
type Config struct {
@@ -18,7 +20,7 @@ type Config struct {
}
type Device struct {
bus machine.SPI
bus drivers.SPI
cs machine.Pin
dc machine.Pin
rst machine.Pin
@@ -32,7 +34,7 @@ type Device struct {
type Color uint8
// New returns a new epd2in13x driver. Pass in a fully configured SPI bus.
func New(bus machine.SPI, csPin, dcPin, rstPin, busyPin machine.Pin) Device {
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})
+4 -2
View File
@@ -12,6 +12,8 @@ import (
"image/color"
"machine"
"time"
"tinygo.org/x/drivers"
)
type Config struct {
@@ -22,7 +24,7 @@ type Config struct {
}
type Device struct {
bus machine.SPI
bus drivers.SPI
cs machine.Pin
dc machine.Pin
rst machine.Pin
@@ -38,7 +40,7 @@ type Device struct {
type Rotation uint8
// New returns a new epd4in2 driver. Pass in a fully configured SPI bus.
func New(bus machine.SPI, csPin, dcPin, rstPin, busyPin machine.Pin) Device {
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})
+13 -1
View File
@@ -13,6 +13,7 @@ import (
"machine"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/net"
)
@@ -258,7 +259,7 @@ type command struct {
}
type Device struct {
SPI machine.SPI
SPI drivers.SPI
CS machine.Pin
ACK machine.Pin
GPIO0 machine.Pin
@@ -268,6 +269,17 @@ type Device struct {
ssids [10]string
}
// New returns a new Wifinina driver.
func New(bus drivers.SPI, csPin, ackPin, gpio0Pin, resetPin machine.Pin) *Device {
return &Device{
SPI: bus,
CS: csPin,
ACK: ackPin,
GPIO0: gpio0Pin,
RESET: resetPin,
}
}
func (d *Device) Configure() {
net.UseDriver(d.NewDriver())
+3
View File
@@ -2,10 +2,13 @@
package ws2812 // import "tinygo.org/x/drivers/ws2812"
import (
"errors"
"image/color"
"machine"
)
var errUnknownClockSpeed = errors.New("ws2812: unknown CPU clock speed")
// Device wraps a pin object for an easy driver interface.
type Device struct {
Pin machine.Pin
+58
View File
@@ -0,0 +1,58 @@
// +build avr
package ws2812
// This file implements the WS2812 protocol for AVR microcontrollers.
import (
"device/avr"
"machine"
)
// Send a single byte using the WS2812 protocol.
func (d Device) WriteByte(c byte) error {
// On AVR, the port is always the same for setting and clearing a register
// so use only one. This avoids the following error:
// error: inline assembly requires more registers than available
// Probably this is about pointer registers, which are very limited on AVR.
port, maskSet := d.Pin.PortMaskSet()
_, maskClear := d.Pin.PortMaskClear()
switch machine.CPUFrequency() {
case 16e6: // 16MHz
// See:
// https://wp.josh.com/2014/05/13/ws2812-neopixels-are-not-so-finicky-once-you-get-to-know-them/
// T0H: 4 cycles or 250ns
// T0L: 14 cycles or 875ns -> together 18 cycles or 1125ns
// T1H: 9 cycles or 562ns
// T1L: 8 cycles or 500ns -> together 17 cycles or 1062ns
avr.AsmFull(`
send_bit:
st {portSet}, {maskSet} ; [2] set output high
lsl {value} ; [1] shift off the next bit, store it in C
brcs skip_store ; [1/2] branch if this bit is high (long pulse)
st {portClear}, {maskClear} ; [2] set output low (short pulse)
skip_store:
nop ; [4] wait before changing the output again
nop
nop
nop
st {portClear}, {maskClear} ; [2] set output low (end of pulse)
nop ; [3]
nop
nop
subi {i}, 1 ; [1] subtract one (for the loop)
brne send_bit ; [1/2] send the next bit, if not at the end of the loop
`, map[string]interface{}{
"value": c,
"i": byte(8),
"maskSet": maskSet,
"portSet": port,
"maskClear": maskClear,
"portClear": port,
})
return nil
default:
return errUnknownClockSpeed
}
}
-49
View File
@@ -1,49 +0,0 @@
// +build atmega328p
package ws2812
// This file implements the WS2812 protocol for 16MHz AVR microcontrollers.
import (
"device/avr"
)
// Send a single byte using the WS2812 protocol.
func (d Device) WriteByte(c byte) error {
// For the AVR at 16MHz
portSet, maskSet := d.Pin.PortMaskSet()
portClear, maskClear := d.Pin.PortMaskClear()
// See:
// https://wp.josh.com/2014/05/13/ws2812-neopixels-are-not-so-finicky-once-you-get-to-know-them/
// T0H: 4 cycles or 250ns
// T0L: 14 cycles or 875ns -> together 18 cycles or 1125ns
// T1H: 9 cycles or 562ns
// T1L: 8 cycles or 500ns -> together 17 cycles or 1062ns
avr.AsmFull(`
send_bit:
st {portSet}, {maskSet} ; [2] set output high
lsl {value} ; [1] shift off the next bit, store it in C
brcs skip_store ; [1/2] branch if this bit is high (long pulse)
st {portClear}, {maskClear} ; [2] set output low (short pulse)
skip_store:
nop ; [4] wait before changing the output again
nop
nop
nop
st {portClear}, {maskClear} ; [2] set output low (end of pulse)
nop ; [3]
nop
nop
subi {i}, 1 ; [1] subtract one (for the loop)
brne send_bit ; [1/2] send the next bit, if not at the end of the loop
`, map[string]interface{}{
"value": c,
"i": 8,
"maskSet": maskSet,
"portSet": portSet,
"maskClear": maskClear,
"portClear": portClear,
})
return nil
}
-50
View File
@@ -1,50 +0,0 @@
// +build digispark
package ws2812
// This file implements the WS2812 protocol for 16.5MHz Digispark AVR microcontrollers.
// This is a slightly different implementation than the one for the atmega to work around a compiler bug.
import (
"device/avr"
)
// Send a single byte using the WS2812 protocol.
func (d Device) WriteByte(c byte) error {
// For the AVR at 16MHz
portSet, maskSet := d.Pin.PortMaskSet()
portClear, maskClear := d.Pin.PortMaskClear()
// See:
// https://wp.josh.com/2014/05/13/ws2812-neopixels-are-not-so-finicky-once-you-get-to-know-them/
// T0H: 4 cycles or 250ns
// T0L: 14 cycles or 875ns -> together 18 cycles or 1125ns
// T1H: 9 cycles or 562ns
// T1L: 8 cycles or 500ns -> together 17 cycles or 1062ns
avr.AsmFull(`
send_bit:
st {portSet}, {maskSet} ; [2] set output high
lsl {value} ; [1] shift off the next bit, store it in C
brcs skip_store ; [1/2] branch if this bit is high (long pulse)
st {portClear}, {maskClear} ; [2] set output low (short pulse)
skip_store:
nop ; [4] wait before changing the output again
nop
nop
nop
st {portClear}, {maskClear} ; [2] set output low (end of pulse)
nop ; [3]
nop
nop
subi {i}, 1 ; [1] subtract one (for the loop)
brne send_bit ; [1/2] send the next bit, if not at the end of the loop
`, map[string]interface{}{
"value": c,
"i": byte(8),
"maskSet": maskSet,
"portSet": portSet,
"maskClear": maskClear,
"portClear": portClear,
})
return nil
}
-3
View File
@@ -4,13 +4,10 @@ package ws2812
import (
"device"
"errors"
"machine"
"unsafe"
)
var errUnknownClockSpeed = errors.New("ws2812: unknown CPU clock speed")
func (d Device) WriteByte(c byte) error {
portSet, maskSet := d.Pin.PortMaskSet()
portClear, maskClear := d.Pin.PortMaskClear()