mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-08-01 21:47:46 +00:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 27ef18930e | |||
| e9a6d96ddd | |||
| 9a7cb1a22f | |||
| d170ec8d81 | |||
| 0fc2d28ca8 | |||
| ef34c13cc1 | |||
| c64d7920dc | |||
| 008157b6c9 | |||
| 0ed9683a52 | |||
| 01acd977f3 | |||
| 231ec57202 | |||
| 5d3ad4ba52 | |||
| 8cb226938b | |||
| ce5e443084 | |||
| edf9ba92be | |||
| c1c05cbef7 | |||
| c338348d2b | |||
| b6aa674b2a | |||
| cc7079b0cd | |||
| 42a907035b | |||
| 46c9ba9595 | |||
| df64ce8f50 |
@@ -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
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
[](https://pkg.go.dev/tinygo.org/x/drivers) [](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 |
|
||||
|
||||
@@ -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
@@ -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
@@ -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,
|
||||
|
||||
@@ -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})
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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,4 +1,4 @@
|
||||
package hcsr04
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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,4 +1,4 @@
|
||||
package ssd1331
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package ssd1351
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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,4 +1,4 @@
|
||||
// +build !digispark
|
||||
// +build !digispark,!arduino
|
||||
|
||||
package main
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package flash
|
||||
|
||||
import "machine"
|
||||
import (
|
||||
"machine"
|
||||
)
|
||||
|
||||
type transport interface {
|
||||
configure(config *DeviceConfig)
|
||||
|
||||
+11
-1
@@ -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
@@ -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
@@ -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})
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
@@ -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),
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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})
|
||||
|
||||
@@ -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
@@ -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
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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"
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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
@@ -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())
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user