Compare commits

..

3 Commits

Author SHA1 Message Date
sago35 d9700779f7 samd5x spi: use standard spi driver 2020-10-26 18:35:55 +09:00
deadprogram 06ab27300c spi: incorporate change from GH issue feedback
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-10-18 21:22:56 +02:00
deadprogram 39e9e209ec spi: remove machine.SPI and replace with drivers.SPI interface for almost all SPI drivers
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-10-18 18:03:12 +02:00
42 changed files with 287 additions and 1973 deletions
-3
View File
@@ -12,9 +12,6 @@ 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 -18
View File
@@ -31,8 +31,6 @@ 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
@@ -83,10 +81,6 @@ 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
@@ -138,8 +132,6 @@ 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
@@ -166,13 +158,4 @@ endif
tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/lis2mdl/main.go
@md5sum ./build/test.hex
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
TESTS = $(filter-out $(addsuffix /%,$(NOTESTS)),$(DRIVERS))
unit-test:
@go test -v $(addprefix ./,$(TESTS))
test: clean fmt-check unit-test smoke-test
test: clean fmt-check smoke-test
+2 -2
View File
@@ -18,13 +18,13 @@ func TestWhoAmI(t *testing.T) {
c := qt.New(t)
bus := tester.NewI2CBus(c)
fake := tester.NewI2CDevice(c, Address)
copy(fake.Registers[:], defaultRegisters())
fake.SetupRegisters(defaultRegisters())
bus.AddDevice(fake)
dev := New(bus)
c.Assert(dev.Connected(), qt.Equals, true)
fake.Registers[RegID] = 0x99
fake.SetupRegister(RegID, 0x99)
c.Assert(dev.Connected(), qt.Equals, false)
}
-249
View File
@@ -1,249 +0,0 @@
package bmp388
import (
"errors"
"tinygo.org/x/drivers"
)
var (
errConfigWrite = errors.New("bmp388: failed to configure sensor, check connection")
errConfig = errors.New("bmp388: there is a problem with the configuration, try reducing ODR")
errCaliRead = errors.New("bmp388: failed to read calibration coefficient register")
errSoftReset = errors.New("bmp388: failed to perform a soft reset")
errNotConnected = errors.New("bmp388: not connected")
)
type Oversampling byte
type Mode byte
type OutputDataRate byte
type FilterCoefficient byte
// Config contains settings for filtering, sampling, and modes of operation
type Config struct {
Pressure Oversampling
Temperature Oversampling
Mode Mode
ODR OutputDataRate
IIR FilterCoefficient
}
// Device wraps the I2C connection and configuration values for the BMP388
type Device struct {
bus drivers.I2C
Address uint8
cali calibrationCoefficients
Config Config
}
type calibrationCoefficients struct {
// Temperature compensation
t1 uint16
t2 uint16
t3 int8
// Pressure compensation
p1 int16
p2 int16
p3 int8
p4 int8
p5 uint16
p6 uint16
p7 int8
p8 int8
p9 int16
p10 int8
p11 int8
}
// New returns a bmp388 struct with the default I2C address. Configure must also be called after instanting
func New(bus drivers.I2C) Device {
return Device{
bus: bus,
Address: Address,
}
}
// Configure can enable settings on the BMP388 and reads the calibration coefficients
func (d *Device) Configure(config Config) (err error) {
d.Config = config
if d.Config == (Config{}) {
d.Config.Mode = Normal
}
// Turning on the pressure and temperature sensors and setting the measurement mode
err = d.writeRegister(RegPwrCtrl, PwrPress|PwrTemp|byte(d.Config.Mode))
// Configure the oversampling, output data rate, and iir filter coefficient settings
err = d.writeRegister(RegOSR, byte(d.Config.Pressure|d.Config.Temperature<<3))
err = d.writeRegister(RegODR, byte(d.Config.ODR))
err = d.writeRegister(RegIIR, byte(d.Config.IIR<<1))
if err != nil {
return errConfigWrite
}
// Check if there is a problem with the given configuration
if d.configurationError() {
return errConfig
}
// Reading the builtin calibration coefficients and parsing them per the datasheet. The compensation formula given
// in the datasheet is implemented in floating point
buffer, err := d.readRegister(RegCali, 21)
if err != nil {
return errCaliRead
}
d.cali.t1 = uint16(buffer[1])<<8 | uint16(buffer[0])
d.cali.t2 = uint16(buffer[3])<<8 | uint16(buffer[2])
d.cali.t3 = int8(buffer[4])
d.cali.p1 = int16(buffer[6])<<8 | int16(buffer[5])
d.cali.p2 = int16(buffer[8])<<8 | int16(buffer[7])
d.cali.p3 = int8(buffer[9])
d.cali.p4 = int8(buffer[10])
d.cali.p5 = uint16(buffer[12])<<8 | uint16(buffer[11])
d.cali.p6 = uint16(buffer[14])<<8 | uint16(buffer[13])
d.cali.p7 = int8(buffer[15])
d.cali.p8 = int8(buffer[16])
d.cali.p9 = int16(buffer[18])<<8 | int16(buffer[17])
d.cali.p10 = int8(buffer[19])
d.cali.p11 = int8(buffer[20])
return nil
}
// Read the temperature registers and compute a compensation value for the temperature and pressure compensation
// calculations. This is not the temperature itself.
func (d *Device) tlinCompensate() (int64, error) {
rawTemp, err := d.readSensorData(RegTemp)
if err != nil {
return 0, err
}
// pulled from C driver: https://github.com/BoschSensortec/BMP3-Sensor-API/blob/master/bmp3.c
partialData1 := rawTemp - (256 * int64(d.cali.t1))
partialData2 := int64(d.cali.t2) * partialData1
partialData3 := (partialData1 * partialData1)
partialData4 := partialData3 * int64(d.cali.t3)
partialData5 := (partialData2 * 262144) + partialData4
return partialData5 / 4294967296, nil
}
// ReadTemperature returns the temperature in centicelsius, i.e 2426 / 100 = 24.26 C
func (d *Device) ReadTemperature() (int32, error) {
tlin, err := d.tlinCompensate()
if err != nil {
return 0, err
}
temp := (tlin * 25) / 16384
return int32(temp), nil
}
// ReadPressure returns the pressure in centipascals, i.e 10132520 / 100 = 101325.20 Pa
func (d *Device) ReadPressure() (int32, error) {
tlin, err := d.tlinCompensate()
if err != nil {
return 0, err
}
rawPress, err := d.readSensorData(RegPress)
if err != nil {
return 0, err
}
// code pulled from bmp388 C driver: https://github.com/BoschSensortec/BMP3-Sensor-API/blob/master/bmp3.c
partialData1 := tlin * tlin
partialData2 := partialData1 / 64
partialData3 := (partialData2 * tlin) / 256
partialData4 := (int64(d.cali.p8) * partialData3) / 32
partialData5 := (int64(d.cali.p7) * partialData1) * 16
partialData6 := (int64(d.cali.p6) * tlin) * 4194304
offset := (int64(d.cali.p5) * 140737488355328) + partialData4 + partialData5 + partialData6
partialData2 = (int64(d.cali.p4) * partialData3) / 32
partialData4 = (int64(d.cali.p3) * partialData1) * 4
partialData5 = (int64(d.cali.p2) - 16384) * tlin * 2097152
sensitivity := ((int64(d.cali.p1) - 16384) * 70368744177664) + partialData2 + partialData4 + partialData5
partialData1 = (sensitivity / 16777216) * rawPress
partialData2 = int64(d.cali.p10) * tlin
partialData3 = partialData2 + (65536 * int64(d.cali.p9))
partialData4 = (partialData3 * rawPress) / 8192
// dividing by 10 followed by multiplying by 10
// To avoid overflow caused by (pressure * partial_data4)
partialData5 = (rawPress * (partialData4 / 10)) / 512
partialData5 = partialData5 * 10
partialData6 = (int64)(uint64(rawPress) * uint64(rawPress))
partialData2 = (int64(d.cali.p11) * partialData6) / 65536
partialData3 = (partialData2 * rawPress) / 128
partialData4 = (offset / 4) + partialData1 + partialData5 + partialData3
compPress := ((uint64(partialData4) * 25) / uint64(1099511627776))
return int32(compPress), nil
}
// SoftReset commands the BMP388 to reset of all user configuration settings
func (d *Device) SoftReset() error {
err := d.writeRegister(RegCmd, SoftReset)
if err != nil {
return errSoftReset
}
return nil
}
// Connected tries to reach the bmp388 and check its chip id register. Returns true if it was able to successfully
// communicate over i2c and returns the correct value
func (d *Device) Connected() bool {
data, err := d.readRegister(RegChipId, 1)
return err == nil && data[0] == ChipId // returns true if i2c comm was good and response equals 0x50
}
// SetMode changes the run mode of the sensor, NORMAL is the one to use for most cases. Use FORCED if you plan to take
// measurements infrequently and want to conserve power. SLEEP will of course put the sensor to sleep
func (d *Device) SetMode(mode Mode) error {
d.Config.Mode = mode
return d.writeRegister(RegPwrCtrl, PwrPress|PwrTemp|byte(d.Config.Mode))
}
func (d *Device) readSensorData(register byte) (data int64, err error) {
if !d.Connected() {
return 0, errNotConnected
}
// put the sensor back into forced mode to get a reading, the sensor goes back to sleep after taking one read in
// forced mode
if d.Config.Mode != Normal {
err = d.SetMode(Forced)
if err != nil {
return
}
}
bytes, err := d.readRegister(register, 3)
if err != nil {
return
}
data = int64(bytes[2])<<16 | int64(bytes[1])<<8 | int64(bytes[0])
return
}
// configurationError checks the register error for the configuration error bit. The bit is cleared on read by the bmp.
func (d *Device) configurationError() bool {
data, err := d.readRegister(RegErr, 1)
return err == nil && (data[0]&0x04) != 0
}
func (d *Device) readRegister(register byte, len int) (data []byte, err error) {
data = make([]byte, len)
err = d.bus.ReadRegister(d.Address, register, data)
return
}
func (d *Device) writeRegister(register byte, data byte) error {
return d.bus.WriteRegister(d.Address, register, []byte{data})
}
-84
View File
@@ -1,84 +0,0 @@
// 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
)
+2 -2
View File
@@ -42,8 +42,8 @@ func (d *Device) SetTime(t time.Time) error {
return err
}
// ReadTime returns the date and time
func (d *Device) ReadTime() (time.Time, error) {
// Time returns the time and date
func (d *Device) Time() (time.Time, error) {
data := make([]byte, 8)
err := d.bus.ReadRegister(d.Address, uint8(TimeDate), data)
if err != nil {
+1 -1
View File
@@ -15,7 +15,7 @@ var (
func main() {
i2c.Configure(machine.I2CConfig{Frequency: 400e3})
i2c.Configure(machine.I2CConfig{Frequency: machine.TWI_FREQ_400KHZ})
sensor.Configure()
for {
-53
View File
@@ -1,53 +0,0 @@
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)
}
}
+1 -1
View File
@@ -13,7 +13,7 @@ func main() {
rtc.SetTime(time.Date(2019, 5, 15, 20, 34, 12, 0, time.UTC))
for {
t, err := rtc.ReadTime()
t, err := rtc.Time()
if err != nil {
println("Error reading date:", err)
break
+1 -1
View File
@@ -1,4 +1,4 @@
package main
package hcsr04
import (
"machine"
+1 -1
View File
@@ -14,7 +14,7 @@ func main() {
// use 3.3V (and may be damaged by 5V).
machine.I2C0.Configure(machine.I2CConfig{
Frequency: 400e3,
Frequency: machine.TWI_FREQ_400KHZ,
})
lcd := hd44780i2c.New(machine.I2C0, 0x27) // some modules have address 0x3F
-45
View File
@@ -1,45 +0,0 @@
// 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: 400e3,
})
if err != nil {
panic(err)
}
// Assume the devices are at addresses 0x20, 0x21
dev, err := mcp23017.NewI2CDevices(machine.I2C0, 0x20, 0x21)
if err != nil {
panic(err)
}
// Configure pin 0 for input and all the others for output.
if err := dev.SetModes([]mcp23017.PinMode{
mcp23017.Input | mcp23017.Pullup,
mcp23017.Output,
}); err != nil {
panic(err)
}
input := dev.Pin(0)
// Make a mask that represents all the output pins.
// Note that this leverages the driver behaviour which replicates the highest bit in
// the last slice element (1 in this case) to all other pins
outputMask := mcp23017.PinSlice{^mcp23017.Pins(1 << 0)} // All except pin 0
inputVal, err := input.Get()
if err != nil {
panic(err)
}
println("input value: ", inputVal)
// Set the values of all the output pins.
err = dev.SetPins(mcp23017.PinSlice{0b1011011_01101110, 0b11111101_11100110}, outputMask)
if err != nil {
panic(err)
}
}
-39
View File
@@ -1,39 +0,0 @@
package main
import (
"machine"
"tinygo.org/x/drivers/mcp23017"
)
func main() {
err := machine.I2C0.Configure(machine.I2CConfig{
Frequency: 400e3,
})
if err != nil {
panic(err)
}
dev, err := mcp23017.NewI2C(machine.I2C0, 0x20)
if err != nil {
panic(err)
}
// Configure pin 0 for input and all the others for output.
if err := dev.SetModes([]mcp23017.PinMode{
mcp23017.Input | mcp23017.Pullup,
mcp23017.Output,
}); err != nil {
panic(err)
}
input := dev.Pin(0)
outputMask := ^mcp23017.Pins(1 << 0) // All except pin 0
inputVal, err := input.Get()
if err != nil {
panic(err)
}
println("input value: ", inputVal)
// Set the values of all the output pins.
err = dev.SetPins(0b1011011_01101110, outputMask)
if err != nil {
panic(err)
}
}
+1 -1
View File
@@ -11,7 +11,7 @@ import (
func main() {
machine.I2C0.Configure(machine.I2CConfig{
Frequency: 400e3,
Frequency: machine.TWI_FREQ_400KHZ,
})
display := ssd1306.NewI2C(machine.I2C0)
+1 -1
View File
@@ -1,4 +1,4 @@
package main
package ssd1331
import (
"machine"
+1 -1
View File
@@ -1,4 +1,4 @@
package main
package ssd1351
import (
"machine"
+1 -1
View File
@@ -10,7 +10,7 @@ import (
func main() {
machine.I2C0.Configure(machine.I2CConfig{
Frequency: 400e3,
Frequency: machine.TWI_FREQ_400KHZ,
})
thermo := tmp102.New(machine.I2C0)
-9
View File
@@ -1,9 +0,0 @@
// +build arduino
package main
import "machine"
// Replace neo in the code below to match the pin
// that you are using if different.
var neo = machine.D2
+1 -1
View File
@@ -1,4 +1,4 @@
// +build !digispark,!arduino
// +build !digispark
package main
+6 -6
View File
@@ -135,12 +135,12 @@ func (dev *Device) Configure(config *DeviceConfig) (err error) {
// 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 {
return err
}
}
// if dev.attrs.MaxClockSpeedMHz > 0 {
// err := dev.trans.setClockSpeed(uint32(dev.attrs.MaxClockSpeedMHz) * 1e6)
// if err != nil {
// return err
// }
// }
// Enable Quad Mode if available
if dev.trans.supportQuadMode() && dev.attrs.QuadEnableBitMask > 0 {
+4 -23
View File
@@ -2,12 +2,13 @@ package flash
import (
"machine"
"tinygo.org/x/drivers"
)
type transport interface {
configure(config *DeviceConfig)
supportQuadMode() bool
setClockSpeed(hz uint32) (err error)
runCommand(cmd byte) (err error)
readCommand(cmd byte, rsp []byte) (err error)
writeCommand(cmd byte, data []byte) (err error)
@@ -18,7 +19,7 @@ type transport interface {
// NewSPI returns a pointer to a flash device that uses a SPI peripheral to
// communicate with a serial memory chip.
func NewSPI(spi *machine.SPI, sdo, sdi, sck, cs machine.Pin) *Device {
func NewSPI(spi drivers.SPI, sdo, sdi, sck, cs machine.Pin) *Device {
return &Device{
trans: &spiTransport{
spi: spi,
@@ -31,7 +32,7 @@ func NewSPI(spi *machine.SPI, sdo, sdi, sck, cs machine.Pin) *Device {
}
type spiTransport struct {
spi *machine.SPI
spi drivers.SPI
sdo machine.Pin
sdi machine.Pin
sck machine.Pin
@@ -39,31 +40,11 @@ type spiTransport struct {
}
func (tr *spiTransport) configure(config *DeviceConfig) {
// Configure spi bus
tr.setClockSpeed(5000000)
// Configure chip select pin
tr.ss.Configure(machine.PinConfig{Mode: machine.PinOutput})
tr.ss.High()
}
func (tr *spiTransport) setClockSpeed(hz uint32) error {
// TODO: un-hardcode this max speed; it is probably a sensible
// default maximum for atsamd and nrf at least
if hz > 24*1e6 {
hz = 24 * 1e6
}
tr.spi.Configure(machine.SPIConfig{
Frequency: hz,
SDI: tr.sdi,
SDO: tr.sdo,
SCK: tr.sck,
LSBFirst: false,
Mode: 0,
})
return nil
}
func (tr *spiTransport) supportQuadMode() bool {
return false
}
+3 -1
View File
@@ -300,7 +300,9 @@ func (d *Device) sendCommand(cmd byte, data []byte) {
d.dc.Low()
d.driver.write8(cmd)
d.dc.High()
d.driver.write8sl(data)
if data != nil && len(data) > 0 {
d.driver.write8sl(data)
}
d.endWrite()
}
+11 -69
View File
@@ -3,7 +3,6 @@
package ili9341
import (
"device/sam"
"machine"
)
@@ -11,6 +10,8 @@ type spiDriver struct {
bus machine.SPI
}
var txBuf [2]uint8
func NewSPI(bus machine.SPI, dc, cs, rst machine.Pin) *Device {
return &Device{
dc: dc,
@@ -27,90 +28,31 @@ func (pd *spiDriver) configure(config *Config) {
}
func (pd *spiDriver) write8(b byte) {
pd.bus.Bus.CTRLB.ClearBits(sam.SERCOM_SPIM_CTRLB_RXEN)
for !pd.bus.Bus.INTFLAG.HasBits(sam.SERCOM_SPIM_INTFLAG_DRE) {
}
pd.bus.Bus.DATA.Set(uint32(b))
pd.bus.Bus.CTRLB.SetBits(sam.SERCOM_SPIM_CTRLB_RXEN)
for pd.bus.Bus.SYNCBUSY.HasBits(sam.SERCOM_SPIM_SYNCBUSY_CTRLB) {
}
pd.bus.Transfer(b)
}
func (pd *spiDriver) write8n(b byte, n int) {
pd.bus.Bus.CTRLB.ClearBits(sam.SERCOM_SPIM_CTRLB_RXEN)
for i, c := 0, n; i < c; i++ {
for !pd.bus.Bus.INTFLAG.HasBits(sam.SERCOM_SPIM_INTFLAG_DRE) {
}
pd.bus.Bus.DATA.Set(uint32(b))
}
pd.bus.Bus.CTRLB.SetBits(sam.SERCOM_SPIM_CTRLB_RXEN)
for pd.bus.Bus.SYNCBUSY.HasBits(sam.SERCOM_SPIM_SYNCBUSY_CTRLB) {
}
panic("not impl")
}
func (pd *spiDriver) write8sl(b []byte) {
pd.bus.Bus.CTRLB.ClearBits(sam.SERCOM_SPIM_CTRLB_RXEN)
for i, c := 0, len(b); i < c; i++ {
for !pd.bus.Bus.INTFLAG.HasBits(sam.SERCOM_SPIM_INTFLAG_DRE) {
}
pd.bus.Bus.DATA.Set(uint32(b[i]))
}
pd.bus.Bus.CTRLB.SetBits(sam.SERCOM_SPIM_CTRLB_RXEN)
for pd.bus.Bus.SYNCBUSY.HasBits(sam.SERCOM_SPIM_SYNCBUSY_CTRLB) {
}
pd.bus.Tx(b, nil)
}
func (pd *spiDriver) write16(data uint16) {
pd.bus.Bus.CTRLB.ClearBits(sam.SERCOM_SPIM_CTRLB_RXEN)
for !pd.bus.Bus.INTFLAG.HasBits(sam.SERCOM_SPIM_INTFLAG_DRE) {
}
pd.bus.Bus.DATA.Set(uint32(uint8(data >> 8)))
for !pd.bus.Bus.INTFLAG.HasBits(sam.SERCOM_SPIM_INTFLAG_DRE) {
}
pd.bus.Bus.DATA.Set(uint32(uint8(data)))
pd.bus.Bus.CTRLB.SetBits(sam.SERCOM_SPIM_CTRLB_RXEN)
for pd.bus.Bus.SYNCBUSY.HasBits(sam.SERCOM_SPIM_SYNCBUSY_CTRLB) {
}
txBuf[0] = uint8(data >> 8)
txBuf[1] = uint8(data)
pd.bus.Tx(txBuf[:], nil)
}
func (pd *spiDriver) write16n(data uint16, n int) {
pd.bus.Bus.CTRLB.ClearBits(sam.SERCOM_SPIM_CTRLB_RXEN)
for i := 0; i < n; i++ {
for !pd.bus.Bus.INTFLAG.HasBits(sam.SERCOM_SPIM_INTFLAG_DRE) {
}
pd.bus.Bus.DATA.Set(uint32(uint8(data >> 8)))
for !pd.bus.Bus.INTFLAG.HasBits(sam.SERCOM_SPIM_INTFLAG_DRE) {
}
pd.bus.Bus.DATA.Set(uint32(uint8(data)))
}
pd.bus.Bus.CTRLB.SetBits(sam.SERCOM_SPIM_CTRLB_RXEN)
for pd.bus.Bus.SYNCBUSY.HasBits(sam.SERCOM_SPIM_SYNCBUSY_CTRLB) {
for i, c := 0, n; i < c; i++ {
pd.write16(data)
}
}
func (pd *spiDriver) write16sl(data []uint16) {
pd.bus.Bus.CTRLB.ClearBits(sam.SERCOM_SPIM_CTRLB_RXEN)
for i, c := 0, len(data); i < c; i++ {
for !pd.bus.Bus.INTFLAG.HasBits(sam.SERCOM_SPIM_INTFLAG_DRE) {
}
pd.bus.Bus.DATA.Set(uint32(uint8(data[i] >> 8)))
for !pd.bus.Bus.INTFLAG.HasBits(sam.SERCOM_SPIM_INTFLAG_DRE) {
}
pd.bus.Bus.DATA.Set(uint32(uint8(data[i])))
}
pd.bus.Bus.CTRLB.SetBits(sam.SERCOM_SPIM_CTRLB_RXEN)
for pd.bus.Bus.SYNCBUSY.HasBits(sam.SERCOM_SPIM_SYNCBUSY_CTRLB) {
pd.write16(data[i])
}
}
+2 -2
View File
@@ -18,13 +18,13 @@ func TestWhoAmI(t *testing.T) {
c := qt.New(t)
bus := tester.NewI2CBus(c)
fake := tester.NewI2CDevice(c, ADDRESS)
copy(fake.Registers[:], defaultRegisters())
fake.SetupRegisters(defaultRegisters())
bus.AddDevice(fake)
dev := New(bus)
c.Assert(dev.Connected(), qt.Equals, true)
fake.Registers[WHO_AM_I] = 0x99
fake.SetupRegister(WHO_AM_I, 0x99)
c.Assert(dev.Connected(), qt.Equals, false)
}
-381
View File
@@ -1,381 +0,0 @@
// Package mcp23017 implements a driver for the MCP23017
// I2C port expander chip. See https://www.microchip.com/wwwproducts/en/MCP23017
// for details of the interface.
//
// It also provides a way of joining several such devices into one logical
// device (see the Devices type).
package mcp23017
import (
"errors"
)
const (
// hwAddressFixed holds the bits of the hardware address
// that are fixed by the chip. Bits 0-3 (those in hwAddressMask)
// are user-defined by the A0-A2 pins on the chip.
hwAddress = uint8(0b010_0000)
// hwAddressMask holds the bits that are significant in hwAddress.
hwAddressMask = uint8(0b111_1000)
)
type register uint8
const (
// The following registers all refer to port A (except
// rIOCON with is port-agnostic).
// ORing them with portB makes them refer to port B.
rIODIR = register(0x00) // I/O direction. 0=output; 1=input.
rIOPOL = register(0x02) // Invert input values. 0=normal; 1=inverted.
rGPINTEN = register(0x04)
rDEFVAL = register(0x06)
rINTCON = register(0x08)
rIOCON = register(0x0A)
rGPPU = register(0x0C) // Pull up; 0=no pull-up; 1=pull-up.
rINTF = register(0x0E)
rINTCAP = register(0x10)
rGPIO = register(0x12) // GPIO pin values.
rOLAT = register(0x14)
registerCount = 0x16
portB = register(0x1)
)
// PinCount is the number of GPIO pins available on the chip.
const PinCount = 16
// PinMode represents a possible I/O mode for a pin.
// The zero value represents the default value
// after the chip is reset (input).
type PinMode uint8
const (
// Input configures a pin as an input.
Input = PinMode(0)
// Output configures a pin as an output.
Output = PinMode(1)
// Direction is the bit mask of the pin mode representing
// the I/O direction.
Direction = PinMode(1)
// Pullup can be bitwise-or'd with Input
// to cause the pull-up resistor on the pin to
// be enabled.
Pullup = PinMode(2)
// Invert can be bitwise-or'd with Input to
// cause the pin value to reflect the inverted
// value on the pin.
Invert = PinMode(4)
)
// ErrInvalidHWAddress is returned when the hardware address
// of the device is not valid (only some bits can be set by the
// address pins).
var ErrInvalidHWAddress = errors.New("invalid hardware address")
// I2C represents an I2C bus. It is notably implemented by the
// machine.I2C type.
type I2C interface {
ReadRegister(addr uint8, r uint8, buf []byte) error
WriteRegister(addr uint8, r uint8, buf []byte) error
}
// New returns a new MCP23017 device at the given I2C address
// on the given bus.
// It returns ErrInvalidHWAddress if the address isn't possible for the device.
//
// By default all pins are configured as inputs.
func NewI2C(bus I2C, address uint8) (*Device, error) {
if address&hwAddressMask != hwAddress {
return nil, ErrInvalidHWAddress
}
d := &Device{
bus: bus,
addr: address,
}
pins, err := d.GetPins()
if err != nil {
return nil, errors.New("cannot initialize mcp23017 device at " + hex(address) + ": " + err.Error())
}
d.pins = pins
return d, nil
}
func hex(x uint8) string {
digits := "0123456789abcdef"
return "0x" + digits[x>>4:x>>4+1] + digits[x&0xf:x&0xf+1]
}
// Device represents an MCP23017 device.
type Device struct {
// TODO would it be good to have a mutex here so that independent goroutines
// could change pins without needing to do the locking themselves?
// bus holds the reference the I2C bus that the device lives on.
// It's an interface so that we can write tests for it.
bus I2C
addr uint8
// pins caches the most recent pin values that have been set.
// This enables us to change individual pin values without
// doing a read followed by a write.
pins Pins
}
// GetPins reads all 16 pins from ports A and B.
func (d *Device) GetPins() (Pins, error) {
return d.readRegisterAB(rGPIO)
}
// SetPins sets all the pins for which mask is high
// to their respective values in pins.
//
// That is, it does the equivalent of:
//
// for i := 0; i < PinCount; i++ {
// if mask.Get(i) {
// d.Pin(i).Set(pins.Get(i))
// }
// }
func (d *Device) SetPins(pins, mask Pins) error {
if mask == 0 {
return nil
}
newPins := (d.pins &^ mask) | (pins & mask)
if newPins == d.pins {
return nil
}
err := d.writeRegisterAB(rGPIO, newPins)
if err != nil {
return err
}
d.pins = newPins
return nil
}
// TogglePins inverts the values on all pins for
// which mask is high.
func (d *Device) TogglePins(mask Pins) error {
if mask == 0 {
return nil
}
return d.SetPins(^d.pins, mask)
}
// Pin returns a Pin representing the given pin number (from 0 to 15).
// Pin numbers from 0 to 7 represent port A pins 0 to 7.
// Pin numbers from 8 to 15 represent port B pins 0 to 7.
func (d *Device) Pin(pin int) Pin {
if pin < 0 || pin >= PinCount {
panic("pin out of range")
}
var mask Pins
mask.High(pin)
return Pin{
dev: d,
mask: mask,
pin: uint8(pin),
}
}
// SetAllModes sets the mode of all the pins in a single operation.
// If len(modes) is less than PinCount, all remaining pins
// will be set fo modes[len(modes)-1], or PinMode(0) if
// modes is empty.
//
// If len(modes) is greater than PinCount, the excess entries
// will be ignored.
func (d *Device) SetModes(modes []PinMode) error {
defaultMode := PinMode(0)
if len(modes) > 0 {
defaultMode = modes[len(modes)-1]
}
var dir, pullup, invert Pins
for i := 0; i < PinCount; i++ {
mode := defaultMode
if i < len(modes) {
mode = modes[i]
}
if mode&Direction == Input {
dir.High(i)
}
if mode&Pullup != 0 {
pullup.High(i)
}
if mode&Invert != 0 {
invert.High(i)
}
}
if err := d.writeRegisterAB(rIODIR, dir); err != nil {
return err
}
if err := d.writeRegisterAB(rGPPU, pullup); err != nil {
return err
}
if err := d.writeRegisterAB(rIOPOL, invert); err != nil {
return err
}
return nil
}
// GetModes reads the modes of all the pins into modes.
// It's OK if len(modes) is not PinCount - excess entries
// will be left unset.
func (d *Device) GetModes(modes []PinMode) error {
dir, err := d.readRegisterAB(rIODIR)
if err != nil {
return err
}
pullup, err := d.readRegisterAB(rGPPU)
if err != nil {
return err
}
invert, err := d.readRegisterAB(rIOPOL)
if err != nil {
return err
}
if len(modes) > PinCount {
modes = modes[:PinCount]
}
for i := range modes {
mode := Output
if dir.Get(i) {
mode = Input
}
if pullup.Get(i) {
mode |= Pullup
}
if invert.Get(i) {
mode |= Invert
}
modes[i] = mode
}
return nil
}
func (d *Device) writeRegisterAB(r register, val Pins) error {
// We rely on the auto-incrementing sequential write
// and the fact that registers alternate between A and B
// to write both ports in a single operation.
buf := [2]byte{uint8(val), uint8(val >> 8)}
return d.bus.WriteRegister(d.addr, uint8(r&^portB), buf[:])
}
func (d *Device) readRegisterAB(r register) (Pins, error) {
// We rely on the auto-incrementing sequential write
// and the fact that registers alternate between A and B
// to read both ports in a single operation.
var buf [2]byte
if err := d.bus.ReadRegister(d.addr, uint8(r), buf[:]); err != nil {
return Pins(0), err
}
return Pins(buf[0]) | (Pins(buf[1]) << 8), nil
}
// Pin represents a single GPIO pin on the device.
type Pin struct {
// mask holds the mask of the pin.
mask Pins
// pin holds the actual pin number.
pin uint8
dev *Device
}
// Set sets the pin to the given value.
func (p Pin) Set(value bool) error {
// TODO currently this always writes both registers when
// technically it only needs to write one. We could potentially
// optimize that.
if value {
return p.dev.SetPins(^Pins(0), p.mask)
} else {
return p.dev.SetPins(0, p.mask)
}
}
// High is short for p.Set(true).
func (p Pin) High() error {
return p.Set(true)
}
// High is short for p.Set(false).
func (p Pin) Low() error {
return p.Set(false)
}
// Toggle inverts the value output on the pin.
func (p Pin) Toggle() error {
return p.dev.TogglePins(p.mask)
}
// Get returns the current value of the given pin.
func (p Pin) Get() (bool, error) {
// TODO this reads 2 registers when we could read just one.
pins, err := p.dev.GetPins()
if err != nil {
return false, err
}
return pins&p.mask != 0, nil
}
// SetMode configures the pin to the given mode.
func (p Pin) SetMode(mode PinMode) error {
// We could use a more efficient single-register
// read/write pattern but setting pin modes isn't an
// operation that's likely to need to be efficient, so
// use less code and use Get/SetModes directly.
modes := make([]PinMode, PinCount)
if err := p.dev.GetModes(modes); err != nil {
return err
}
modes[p.pin] = mode
return p.dev.SetModes(modes)
}
// GetMode returns the mode of the pin.
func (p Pin) GetMode() (PinMode, error) {
modes := make([]PinMode, PinCount)
if err := p.dev.GetModes(modes); err != nil {
return 0, err
}
return modes[p.pin], nil
}
// Pins represents a bitmask of pin values.
// Port A values are in bits 0-8 (numbered from least significant bit)
// Port B values are in bits 9-15.
type Pins uint16
// Set sets the value for the given pin.
func (p *Pins) Set(pin int, value bool) {
if value {
p.High(pin)
} else {
p.Low(pin)
}
}
// Get returns the value for the given pin.
func (p Pins) Get(pin int) bool {
return (p & pinMask(pin)) != 0
}
// High is short for p.Set(pin, true).
func (p *Pins) High(pin int) {
*p |= pinMask(pin)
}
// Low is short for p.Set(pin, false).
func (p *Pins) Low(pin int) {
*p &^= pinMask(pin)
}
// Toggle inverts the value of the given pin.
func (p *Pins) Toggle(pin int) {
*p ^= pinMask(pin)
}
func pinMask(pin int) Pins {
return 1 << pin
}
-220
View File
@@ -1,220 +0,0 @@
package mcp23017
import (
"fmt"
"testing"
qt "github.com/frankban/quicktest"
"tinygo.org/x/drivers/tester"
)
func TestGetPins(t *testing.T) {
c := qt.New(t)
bus := tester.NewI2CBus(c)
fdev := newDevice(bus, 0x20)
fdev.Registers[rGPIO] = 0b10101100
fdev.Registers[rGPIO|portB] = 0b01010011
dev, err := NewI2C(bus, 0x20)
c.Assert(err, qt.IsNil)
pins, err := dev.GetPins()
c.Assert(err, qt.IsNil)
c.Assert(pins, qt.Equals, Pins(0b01010011_10101100))
}
func TestSetPins(t *testing.T) {
c := qt.New(t)
bus := tester.NewI2CBus(c)
fdev := newDevice(bus, 0x20)
fdev.Registers[rGPIO] = 0b00001111
fdev.Registers[rGPIO|portB] = 0b11110000
dev, err := NewI2C(bus, 0x20)
c.Assert(err, qt.IsNil)
pins, err := dev.GetPins()
c.Assert(err, qt.IsNil)
c.Assert(pins, qt.Equals, Pins(0b11110000_00001111))
err = dev.SetPins(0b01100000_00110000, 0b10101010_01010101)
c.Assert(err, qt.IsNil)
pins, err = dev.GetPins()
c.Assert(err, qt.IsNil)
c.Assert(pins, qt.Equals, Pins(0b01110000_0001_1010))
// The logic uses the cached value of the pins rather than
// reading it from the registers each time.
fdev.Registers[rGPIO] = 0
fdev.Registers[rGPIO|portB] = 0
err = dev.SetPins(0b01000000_00110000, 0b01100000_00000000)
c.Assert(err, qt.IsNil)
pins, err = dev.GetPins()
c.Assert(err, qt.IsNil)
c.Assert(pins, qt.Equals, Pins(0b01010000_00011010))
}
func TestTogglePins(t *testing.T) {
c := qt.New(t)
bus := tester.NewI2CBus(c)
fdev := newDevice(bus, 0x20)
fdev.Registers[rGPIO] = 0b00001111
fdev.Registers[rGPIO|portB] = 0b11110000
dev, err := NewI2C(bus, 0x20)
c.Assert(err, qt.IsNil)
pins, err := dev.GetPins()
c.Assert(err, qt.IsNil)
c.Assert(pins, qt.Equals, Pins(0b11110000_00001111))
err = dev.TogglePins(0b10101010_01010101)
c.Assert(err, qt.IsNil)
pins, err = dev.GetPins()
c.Assert(err, qt.IsNil)
c.Assert(pins, qt.Equals, Pins(0b01011010_01011010))
}
func TestSetGetModes(t *testing.T) {
c := qt.New(t)
bus := tester.NewI2CBus(c)
fdev := newDevice(bus, 0x20)
dev, err := NewI2C(bus, 0x20)
c.Assert(err, qt.IsNil)
// Calling SetModes with less items in than there are
// pins should use the last item for all the unspecified ones.
err = dev.SetModes([]PinMode{Input | Invert, Output})
c.Assert(err, qt.IsNil)
c.Assert(fdev.Registers[rIODIR], qt.Equals, uint8(0b00000001))
c.Assert(fdev.Registers[rIOPOL], qt.Equals, uint8(0b00000001))
c.Assert(fdev.Registers[rGPPU], qt.Equals, uint8(0))
modes := make([]PinMode, 17)
err = dev.GetModes(modes)
c.Assert(err, qt.IsNil)
c.Assert(modes[0], qt.Equals, Input|Invert)
for i, m := range modes[1:16] {
c.Assert(m, qt.Equals, Output, qt.Commentf("index %d", i))
}
c.Assert(modes[16], qt.Equals, PinMode(0))
// Using an empty slice should reset all the modes to the initial state.
err = dev.SetModes(nil)
c.Assert(err, qt.IsNil)
c.Assert(fdev.Registers[rIODIR], qt.Equals, uint8(0b11111111))
c.Assert(fdev.Registers[rIOPOL], qt.Equals, uint8(0))
c.Assert(fdev.Registers[rGPPU], qt.Equals, uint8(0))
}
func TestPinSetGet(t *testing.T) {
c := qt.New(t)
bus := tester.NewI2CBus(c)
fdev := newDevice(bus, 0x20)
dev, err := NewI2C(bus, 0x20)
c.Assert(err, qt.IsNil)
pin := dev.Pin(1)
v, err := pin.Get()
c.Assert(err, qt.Equals, nil)
c.Assert(v, qt.Equals, false)
err = pin.Set(true)
c.Assert(err, qt.Equals, nil)
c.Assert(fdev.Registers[rGPIO], qt.Equals, uint8(0b10))
v, err = pin.Get()
c.Assert(err, qt.Equals, nil)
c.Assert(v, qt.Equals, true)
err = pin.Set(false)
c.Assert(err, qt.Equals, nil)
c.Assert(fdev.Registers[rGPIO], qt.Equals, uint8(0))
err = pin.High()
c.Assert(err, qt.Equals, nil)
c.Assert(fdev.Registers[rGPIO], qt.Equals, uint8(0b10))
err = pin.Low()
c.Assert(err, qt.Equals, nil)
c.Assert(fdev.Registers[rGPIO], qt.Equals, uint8(0))
}
func TestPinToggle(t *testing.T) {
c := qt.New(t)
bus := tester.NewI2CBus(c)
fdev := newDevice(bus, 0x20)
dev, err := NewI2C(bus, 0x20)
c.Assert(err, qt.IsNil)
pin := dev.Pin(1)
v, err := pin.Get()
c.Assert(err, qt.Equals, nil)
c.Assert(v, qt.Equals, false)
err = pin.Toggle()
c.Assert(err, qt.Equals, nil)
c.Assert(fdev.Registers[rGPIO], qt.Equals, uint8(0b10))
err = pin.Toggle()
c.Assert(err, qt.Equals, nil)
c.Assert(fdev.Registers[rGPIO], qt.Equals, uint8(0))
}
func TestPinMode(t *testing.T) {
c := qt.New(t)
bus := tester.NewI2CBus(c)
fdev := newDevice(bus, 0x20)
dev, err := NewI2C(bus, 0x20)
c.Assert(err, qt.IsNil)
pin := dev.Pin(1)
mode, err := pin.GetMode()
c.Assert(err, qt.IsNil)
c.Assert(mode, qt.Equals, PinMode(0))
c.Assert(mode&Direction, qt.Equals, Input)
err = pin.SetMode(Input | Pullup | Invert)
c.Assert(err, qt.IsNil)
c.Assert(fdev.Registers[rIODIR], qt.Equals, uint8(0b11111111))
c.Assert(fdev.Registers[rIOPOL], qt.Equals, uint8(0b10))
c.Assert(fdev.Registers[rGPPU], qt.Equals, uint8(0b10))
mode, err = pin.GetMode()
c.Assert(err, qt.IsNil)
c.Assert(mode, qt.Equals, Input|Pullup|Invert)
// Set another pin to output.
err = dev.Pin(2).SetMode(Output)
c.Assert(err, qt.IsNil)
c.Assert(fdev.Registers[rIODIR], qt.Equals, uint8(0b11111011))
c.Assert(fdev.Registers[rIOPOL], qt.Equals, uint8(0b10))
c.Assert(fdev.Registers[rGPPU], qt.Equals, uint8(0b10))
// Check that changing a pin in port B works too.
err = dev.Pin(8).SetMode(Output)
c.Assert(err, qt.IsNil)
c.Assert(fdev.Registers[rIODIR], qt.Equals, uint8(0b11111011))
c.Assert(fdev.Registers[rIODIR|portB], qt.Equals, uint8(0b11111110))
c.Assert(fdev.Registers[rIOPOL], qt.Equals, uint8(0b10))
c.Assert(fdev.Registers[rIOPOL|portB], qt.Equals, uint8(0))
c.Assert(fdev.Registers[rGPPU], qt.Equals, uint8(0b10))
c.Assert(fdev.Registers[rGPPU|portB], qt.Equals, uint8(0))
}
func TestPins(t *testing.T) {
c := qt.New(t)
var p Pins
p.Set(1, true)
c.Assert(p, qt.Equals, Pins(0b10))
c.Assert(p.Get(1), qt.Equals, true)
c.Assert(p.Get(0), qt.Equals, false)
c.Assert(p.Get(16), qt.Equals, false)
p.High(2)
c.Assert(p, qt.Equals, Pins(0b110))
p.Low(1)
c.Assert(p, qt.Equals, Pins(0b100))
}
func TestInitWithError(t *testing.T) {
c := qt.New(t)
bus := tester.NewI2CBus(c)
fdev := newDevice(bus, 0x20)
fdev.Err = fmt.Errorf("some error")
dev, err := NewI2C(bus, 0x20)
c.Assert(err, qt.ErrorMatches, `cannot initialize mcp23017 device at 0x20: some error`)
c.Assert(dev, qt.IsNil)
}
func newDevice(bus *tester.I2CBus, addr uint8) *tester.I2CDevice {
fdev := bus.NewDevice(addr)
// IODIRA and IODIRB are all ones by default.
fdev.Registers[rIODIR] = 0xff
fdev.Registers[rIODIR|portB] = 0xff
return fdev
}
-211
View File
@@ -1,211 +0,0 @@
package mcp23017
// All is a convenience value that represents all pins high (or all mask bits one).
var All = PinSlice{0xffff}
// Devices holds a slice of devices that can be treated as one
// contiguous set of devices. Earlier entries in the slice have
// lower-numbered pins, so index 0 holds pins 0-7, index 1 holds
// pins 8-15, etc.
type Devices []*Device
// NewI2CDevices returns a Devices slice holding the Device values
// for all the given addresses on the given bus.
// When more than one bus is in use, create the slice yourself.
func NewI2CDevices(bus I2C, addrs ...uint8) (Devices, error) {
devs := make(Devices, len(addrs))
for i, addr := range addrs {
dev, err := NewI2C(bus, addr)
if err != nil {
// TODO return a more informative error.
return nil, err
}
devs[i] = dev
}
return devs, nil
}
// SetModes sets the pin modes of all the pins on all the devices in devs.
// If there are less entries in modes than there are pins, the
// last entry is replicated to all of them (or PinMode(0) if modes
// is empty).
func (devs Devices) SetModes(modes []PinMode) error {
var defaultModes []PinMode
if len(modes) > 0 {
defaultModes = modes[len(modes)-1:]
}
for i, dev := range devs {
pinStart := i * PinCount
var devModes []PinMode
if pinStart < len(modes) {
devModes = modes[pinStart:]
} else {
devModes = defaultModes
}
if err := dev.SetModes(devModes); err != nil {
return err
}
}
return nil
}
// GetModes gets the pin modes from the devices.
// It's OK if modes isn't the same length as all the pins:
// extra entries will be left unchanged.
func (devs Devices) GetModes(modes []PinMode) error {
for i, dev := range devs {
pinStart := i * PinCount
if pinStart >= len(modes) {
break
}
if err := dev.GetModes(modes[pinStart:]); err != nil {
return err
}
}
return nil
}
// Pin returns the pin for the given number.
func (devs Devices) Pin(pin int) Pin {
if pin < 0 || pin >= len(devs)*PinCount {
panic("pin out of range")
}
return devs[pin/PinCount].Pin(pin % PinCount)
}
// GetPins returns pin values for all the pins.
func (devs Devices) GetPins(pins PinSlice) error {
for i, dev := range devs {
if i >= len(pins) {
break
}
devPins, err := dev.GetPins()
if err != nil {
return err
}
pins[i] = devPins
}
return nil
}
// SetPins sets all the pins for which mask is high
// to their respective values in pins.
//
// That is, it does the equivalent of:
//
// for i := 0; i < PinCount*len(devs); i++ {
// if mask.Get(i) {
// d.Pin(i).Set(pins.Get(i))
// }
// }
func (devs Devices) SetPins(pins, mask PinSlice) error {
defaultPins := pins.extra()
defaultMask := mask.extra()
for i, dev := range devs {
devPins := defaultPins
if i < len(pins) {
devPins = pins[i]
}
devMask := defaultMask
if i < len(mask) {
devMask = mask[i]
}
if err := dev.SetPins(devPins, devMask); err != nil {
return err
}
}
return nil
}
// TogglePins inverts the values on all pins for
// which mask is high.
func (devs Devices) TogglePins(mask PinSlice) error {
defaultMask := mask.extra()
for i, dev := range devs {
devMask := defaultMask
if i < len(mask) {
devMask = mask[i]
}
if err := dev.TogglePins(devMask); err != nil {
return err
}
}
return nil
}
// PinSlice represents an arbitrary nunber of pins, each element corresponding
// to the pins for one device. The value of the highest numbered pin in the
// slice is extended to all other pins beyond the end of the slice.
type PinSlice []Pins
// Get returns the value for the given pin. If the length of pins is too short
// for the pin number, the value of the highest available pin is returned.
// That is, the highest numbered pin in the last element of pins
// is effectively replicated to all other elements.
//
// This means that PinSlice{} means "all pins high" and
// PinSlice{0xffff} means "all pins low".
func (pins PinSlice) Get(i int) bool {
if len(pins) == 0 || i < 0 {
return false
}
if i >= len(pins)*PinCount {
return pins[len(pins)-1].Get(PinCount - 1)
}
return pins[i/PinCount].Get(i % PinCount)
}
// Set sets the value for the given pin.
func (pins PinSlice) Set(i int, value bool) {
pins[i/PinCount].Set(i%PinCount, value)
}
// High is short for p.Set(pin, true).
func (pins PinSlice) High(pin int) {
pins[pin/PinCount].High(pin % PinCount)
}
// High is short for p.Set(pin, false).
func (pins PinSlice) Low(pin int) {
pins[pin/PinCount].Low(pin % PinCount)
}
// Toggle inverts the value of the given pin.
func (pins PinSlice) Toggle(pin int) {
pins[pin/PinCount].Toggle(pin % PinCount)
}
// Ensure checks that pins has enough space to store
// at least length pins. If it does, it returns pins unchanged.
// Otherwise, it returns pins with elements appended as needed,
// populating additonal elements by replicating the
// highest pin (mirroring the behavior of PinSlice.Get).
func (pins PinSlice) Ensure(length int) PinSlice {
if length == 0 {
return pins
}
n := length/PinCount + 1
if len(pins) >= n {
return pins
}
// TODO we could potentially make use of additional
// extra capacity in pins when available instead
// of allocating a new slice always.
newPins := make(PinSlice, n)
copy(newPins, pins)
if extend := pins.extra(); extend != 0 {
for i := len(pins); i < n; i++ {
newPins[i] = extend
}
}
return newPins
}
// extra returns the value of implied extra elements beyond
// the end of pins.
func (pins PinSlice) extra() Pins {
if len(pins) == 0 || !pins[len(pins)-1].Get(PinCount-1) {
return 0
}
return ^Pins(0)
}
-190
View File
@@ -1,190 +0,0 @@
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)
}
-112
View File
@@ -1,112 +0,0 @@
// +build microbit
// Package microbitmatrix implements a driver for the BBC micro:bit's LED matrix.
//
// Schematic: https://github.com/bbcmicrobit/hardware/blob/master/SCH_BBC-Microbit_V1.3B.pdf
//
package microbitmatrix // import "tinygo.org/x/drivers/microbitmatrix"
import (
"machine"
"time"
)
var matrixRotations = [4][5][5][2]uint8{
{ // 0
{{0, 0}, {1, 3}, {0, 1}, {1, 4}, {0, 2}},
{{2, 3}, {2, 4}, {2, 5}, {2, 6}, {2, 7}},
{{1, 1}, {0, 8}, {1, 2}, {2, 8}, {1, 0}},
{{0, 7}, {0, 6}, {0, 5}, {0, 4}, {0, 3}},
{{2, 2}, {1, 6}, {2, 0}, {1, 5}, {2, 1}},
},
{ // 90 CCW
{{0, 2}, {2, 7}, {1, 0}, {0, 3}, {2, 1}},
{{1, 4}, {2, 6}, {2, 8}, {0, 4}, {1, 5}},
{{0, 1}, {2, 5}, {1, 2}, {0, 5}, {2, 0}},
{{1, 3}, {2, 4}, {0, 8}, {0, 6}, {1, 6}},
{{0, 0}, {2, 3}, {1, 1}, {0, 7}, {2, 2}},
},
{ // 180
{{2, 1}, {1, 5}, {2, 0}, {1, 6}, {2, 2}},
{{0, 3}, {0, 4}, {0, 5}, {0, 6}, {0, 7}},
{{1, 0}, {2, 8}, {1, 2}, {0, 8}, {1, 1}},
{{2, 7}, {2, 6}, {2, 5}, {2, 4}, {2, 3}},
{{0, 2}, {1, 4}, {0, 1}, {1, 3}, {0, 0}},
},
{ // 270
{{2, 2}, {0, 7}, {1, 1}, {2, 3}, {0, 0}},
{{1, 6}, {0, 6}, {0, 8}, {2, 4}, {1, 3}},
{{2, 0}, {0, 5}, {1, 2}, {2, 5}, {0, 1}},
{{1, 5}, {0, 4}, {2, 8}, {2, 6}, {1, 4}},
{{2, 1}, {0, 3}, {1, 0}, {2, 7}, {0, 2}},
},
}
type Device struct {
pin [12]machine.Pin
buffer [3][9]bool
rotation uint8
}
// Configure sets up the device.
func (d *Device) Configure(cfg Config) {
d.SetRotation(cfg.Rotation)
for i := machine.LED_COL_1; i <= machine.LED_ROW_3; i++ {
d.pin[i-machine.LED_COL_1] = i
d.pin[i-machine.LED_COL_1].Configure(machine.PinConfig{Mode: machine.PinOutput})
}
d.ClearDisplay()
d.DisableAll()
}
// Display sends the buffer (if any) to the screen.
func (d *Device) Display() error {
for row := 0; row < 3; row++ {
d.DisableAll()
d.pin[9+row].High()
for col := 0; col < 9; col++ {
if d.buffer[row][col] {
d.pin[col].Low()
}
}
time.Sleep(time.Millisecond * 2)
}
return nil
}
// ClearDisplay erases the internal buffer
func (d *Device) ClearDisplay() {
for row := 0; row < 3; row++ {
for col := 0; col < 9; col++ {
d.buffer[row][col] = false
}
}
}
// DisableAll disables all the LEDs without modifying the buffer
func (d *Device) DisableAll() {
for i := machine.LED_COL_1; i <= machine.LED_COL_9; i++ {
d.pin[i-machine.LED_COL_1].High()
}
for i := machine.LED_ROW_1; i <= machine.LED_ROW_3; i++ {
d.pin[i-machine.LED_COL_1].Low()
}
}
// EnableAll enables all the LEDs without modifying the buffer
func (d *Device) EnableAll() {
for i := machine.LED_COL_1; i <= machine.LED_COL_9; i++ {
d.pin[i-machine.LED_COL_1].Low()
}
for i := machine.LED_ROW_1; i <= machine.LED_ROW_3; i++ {
d.pin[i-machine.LED_COL_1].High()
}
}
// Size returns the current size of the display.
func (d *Device) Size() (w, h int16) {
return 5, 5
}
-122
View File
@@ -1,122 +0,0 @@
// +build microbit_v2
// Package microbitmatrix implements a driver for the BBC micro:bit version 2 LED matrix.
//
// Schematic:
//
package microbitmatrix // import "tinygo.org/x/drivers/microbitmatrix"
import (
"machine"
"time"
)
var matrixRotations = [4][5][5][2]uint8{
{ // 0
{{0, 0}, {1, 0}, {2, 0}, {3, 0}, {4, 0}},
{{0, 1}, {1, 1}, {2, 1}, {3, 1}, {4, 1}},
{{0, 2}, {1, 2}, {2, 2}, {3, 2}, {4, 2}},
{{0, 3}, {1, 3}, {2, 3}, {3, 3}, {4, 3}},
{{0, 4}, {1, 4}, {2, 4}, {3, 4}, {4, 4}},
},
{ // 90 CCW
{{4, 0}, {4, 1}, {4, 2}, {4, 3}, {4, 4}},
{{3, 0}, {3, 1}, {3, 2}, {3, 3}, {3, 4}},
{{2, 0}, {2, 1}, {2, 2}, {2, 3}, {2, 4}},
{{1, 0}, {1, 1}, {1, 2}, {1, 3}, {1, 4}},
{{0, 0}, {0, 1}, {0, 2}, {0, 3}, {0, 4}},
},
{ // 180
{{4, 4}, {3, 4}, {2, 4}, {1, 4}, {0, 4}},
{{4, 3}, {3, 3}, {2, 3}, {1, 3}, {0, 3}},
{{4, 2}, {3, 2}, {2, 2}, {1, 2}, {0, 2}},
{{4, 1}, {3, 1}, {2, 1}, {1, 1}, {0, 1}},
{{4, 0}, {3, 0}, {2, 0}, {1, 0}, {0, 0}},
},
{ // 270
{{0, 4}, {0, 3}, {0, 2}, {0, 1}, {0, 0}},
{{1, 4}, {1, 3}, {1, 2}, {1, 1}, {1, 0}},
{{2, 4}, {2, 3}, {2, 2}, {2, 1}, {2, 0}},
{{3, 4}, {3, 3}, {3, 2}, {3, 1}, {3, 0}},
{{4, 4}, {4, 3}, {4, 2}, {4, 1}, {4, 0}},
},
}
type Device struct {
pin [10]machine.Pin
buffer [5][5]bool
rotation uint8
}
// Configure sets up the device.
func (d *Device) Configure(cfg Config) {
d.SetRotation(cfg.Rotation)
d.pin[0] = machine.LED_COL_1
d.pin[1] = machine.LED_COL_2
d.pin[2] = machine.LED_COL_3
d.pin[3] = machine.LED_COL_4
d.pin[4] = machine.LED_COL_5
d.pin[5] = machine.LED_ROW_1
d.pin[6] = machine.LED_ROW_2
d.pin[7] = machine.LED_ROW_3
d.pin[8] = machine.LED_ROW_4
d.pin[9] = machine.LED_ROW_5
for i := 0; i < 10; i++ {
d.pin[i].Configure(machine.PinConfig{Mode: machine.PinOutput})
}
d.ClearDisplay()
d.DisableAll()
}
// Display sends the buffer (if any) to the screen.
func (d *Device) Display() error {
for x := 0; x < 5; x++ {
d.DisableAll()
d.pin[x].Low()
for y := 0; y < 5; y++ {
if d.buffer[x][y] {
d.pin[5+y].High()
} else {
d.pin[5+y].Low()
}
}
time.Sleep(time.Millisecond * 4)
}
return nil
}
// ClearDisplay erases the internal buffer
func (d *Device) ClearDisplay() {
for row := 0; row < 5; row++ {
for col := 0; col < 5; col++ {
d.buffer[row][col] = false
}
}
}
// DisableAll disables all the LEDs without modifying the buffer
func (d *Device) DisableAll() {
for i := 0; i < 5; i++ {
d.pin[i].High()
d.pin[5+i].Low()
}
}
// EnableAll enables all the LEDs without modifying the buffer
func (d *Device) EnableAll() {
for i := 0; i < 5; i++ {
d.pin[i].Low()
d.pin[5+i].High()
}
}
// Size returns the current size of the display.
func (d *Device) Size() (w, h int16) {
return 5, 5
}
+102
View File
@@ -6,17 +6,68 @@ 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
@@ -41,3 +92,54 @@ 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
}
-2
View File
@@ -50,8 +50,6 @@ const (
GMCTRP1 = 0xE0
GMCTRN1 = 0xE1
GSCAN = 0x45
VSCRDEF = 0x33
VSCRSADD = 0x37
NO_ROTATION Rotation = 0
ROTATION_90 Rotation = 1 // 90 degrees clock-wise rotation
+7 -30
View File
@@ -16,10 +16,8 @@ import (
"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.
@@ -280,7 +278,7 @@ func (d *Device) FillRectangle(x, y, width, height int16, c color.RGBA) error {
return nil
}
// FillRectangleWithBuffer fills buffer with a rectangle at a given coordinates.
// FillRectangle fills a rectangle at a given coordinates with a buffer
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 ||
@@ -372,12 +370,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)
}
// Data sends data to the display.
// Command sends a data to the display
func (d *Device) Data(data uint8) {
d.Tx([]byte{data}, false)
}
@@ -395,13 +393,13 @@ func (d *Device) Tx(data []byte, isCommand bool) {
}
// Rx reads data from the display
func (d *Device) Rx(command uint8, data []byte) {
func (d *Device) Rx(command uint8, read_bytes []byte) {
d.dcPin.Low()
d.csPin.Low()
d.bus.Transfer(command)
d.dcPin.High()
for i := range data {
data[i], _ = d.bus.Transfer(0xFF)
for i := range read_bytes {
read_bytes[i], _ = d.bus.Transfer(0xFF)
}
d.csPin.High()
}
@@ -423,7 +421,7 @@ func (d *Device) EnableBacklight(enable bool) {
}
}
// InvertColors inverts the colors of the screen
// InverColors inverts the colors of the screen
func (d *Device) InvertColors(invert bool) {
if invert {
d.Command(INVON)
@@ -437,27 +435,6 @@ 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()
+31 -9
View File
@@ -10,7 +10,7 @@ type I2CDevice struct {
addr uint8
// Registers holds the device registers. It can be inspected
// or changed as desired for testing.
Registers [MaxRegisters]uint8
registers [MaxRegisters]uint8
// If Err is non-nil, it will be returned as the error from the
// I2C methods.
Err error
@@ -29,13 +29,35 @@ 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
}
@@ -44,18 +66,18 @@ func (d *I2CDevice) WriteRegister(r uint8, buf []byte) error {
if d.Err != nil {
return d.Err
}
d.assertRegisterRange(r, buf)
copy(d.Registers[r:], buf)
d.AssertRegisterRange(r, buf)
copy(d.registers[r:], buf)
return nil
}
// assertRegisterRange asserts that reading or writing the given
// AssertRegisterRange asserts that reading or writing the given
// register and subsequent registers is in range of the available registers.
func (d *I2CDevice) assertRegisterRange(r uint8, buf []byte) {
if int(r) >= len(d.Registers) {
func (d *I2CDevice) AssertRegisterRange(r uint8, buf []byte) {
if int(r) >= len(d.registers) {
d.c.Fatalf("register read/write [%#x, %#x] start out of range", r, int(r)+len(buf))
}
if int(r)+len(buf) > len(d.Registers) {
if int(r)+len(buf) > len(d.registers) {
d.c.Fatalf("register read/write [%#x, %#x] end out of range", r, int(r)+len(buf))
}
}
-16
View File
@@ -1,7 +1,5 @@
package tester
import "fmt"
// I2CBus implements the I2C interface in memory for testing.
type I2CBus struct {
c Failer
@@ -18,24 +16,10 @@ func NewI2CBus(c Failer) *I2CBus {
}
// AddDevice adds a new mock device to the mock I2C bus.
// It panics if a device with the same address is added more than once.
func (bus *I2CBus) AddDevice(d *I2CDevice) {
for _, dev := range bus.devices {
if dev.Addr() == d.addr {
panic(fmt.Errorf("device already added at address %#x", d))
}
}
bus.devices = append(bus.devices, d)
}
// NewDevice creates a new device with the given address
// and adds it to the mock I2C bus.
func (bus *I2CBus) NewDevice(addr uint8) *I2CDevice {
dev := NewI2CDevice(bus.c, addr)
bus.AddDevice(dev)
return dev
}
// ReadRegister implements I2C.ReadRegister.
func (bus *I2CBus) ReadRegister(addr uint8, r uint8, buf []byte) error {
return bus.FindDevice(addr).ReadRegister(r, buf)
+1 -1
View File
@@ -57,7 +57,7 @@ func New(pin machine.Pin) Device {
// Configure configures the ADC pin used for the thermistor.
func (d *Device) Configure() {
d.adc.Configure(machine.ADCConfig{})
d.adc.Configure()
}
// ReadTemperature returns the temperature in celsius milli degrees (°C/1000)
+4 -4
View File
@@ -86,7 +86,7 @@ func (res *FourWire) ReadX() uint16 {
res.xm.Pin.Configure(machine.PinConfig{Mode: machine.PinOutput})
res.xm.Pin.Low()
res.yp.Configure(machine.ADCConfig{})
res.yp.Configure()
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(machine.ADCConfig{})
res.xp.Configure()
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(machine.ADCConfig{})
res.yp.Configure(machine.ADCConfig{})
res.xm.Configure()
res.yp.Configure()
z1 := res.xm.Get()
z2 := res.yp.Get()
-3
View File
@@ -2,13 +2,10 @@
package ws2812 // import "tinygo.org/x/drivers/ws2812"
import (
"errors"
"image/color"
"machine"
)
var errUnknownClockSpeed = errors.New("ws2812: unknown CPU clock speed")
// Device wraps a pin object for an easy driver interface.
type Device struct {
Pin machine.Pin
-58
View File
@@ -1,58 +0,0 @@
// +build avr
package ws2812
// This file implements the WS2812 protocol for AVR microcontrollers.
import (
"device/avr"
"machine"
)
// Send a single byte using the WS2812 protocol.
func (d Device) WriteByte(c byte) error {
// On AVR, the port is always the same for setting and clearing a register
// so use only one. This avoids the following error:
// error: inline assembly requires more registers than available
// Probably this is about pointer registers, which are very limited on AVR.
port, maskSet := d.Pin.PortMaskSet()
_, maskClear := d.Pin.PortMaskClear()
switch machine.CPUFrequency() {
case 16e6: // 16MHz
// See:
// https://wp.josh.com/2014/05/13/ws2812-neopixels-are-not-so-finicky-once-you-get-to-know-them/
// T0H: 4 cycles or 250ns
// T0L: 14 cycles or 875ns -> together 18 cycles or 1125ns
// T1H: 9 cycles or 562ns
// T1L: 8 cycles or 500ns -> together 17 cycles or 1062ns
avr.AsmFull(`
send_bit:
st {portSet}, {maskSet} ; [2] set output high
lsl {value} ; [1] shift off the next bit, store it in C
brcs skip_store ; [1/2] branch if this bit is high (long pulse)
st {portClear}, {maskClear} ; [2] set output low (short pulse)
skip_store:
nop ; [4] wait before changing the output again
nop
nop
nop
st {portClear}, {maskClear} ; [2] set output low (end of pulse)
nop ; [3]
nop
nop
subi {i}, 1 ; [1] subtract one (for the loop)
brne send_bit ; [1/2] send the next bit, if not at the end of the loop
`, map[string]interface{}{
"value": c,
"i": byte(8),
"maskSet": maskSet,
"portSet": port,
"maskClear": maskClear,
"portClear": port,
})
return nil
default:
return errUnknownClockSpeed
}
}
+49
View File
@@ -0,0 +1,49 @@
// +build atmega328p
package ws2812
// This file implements the WS2812 protocol for 16MHz AVR microcontrollers.
import (
"device/avr"
)
// Send a single byte using the WS2812 protocol.
func (d Device) WriteByte(c byte) error {
// For the AVR at 16MHz
portSet, maskSet := d.Pin.PortMaskSet()
portClear, maskClear := d.Pin.PortMaskClear()
// See:
// https://wp.josh.com/2014/05/13/ws2812-neopixels-are-not-so-finicky-once-you-get-to-know-them/
// T0H: 4 cycles or 250ns
// T0L: 14 cycles or 875ns -> together 18 cycles or 1125ns
// T1H: 9 cycles or 562ns
// T1L: 8 cycles or 500ns -> together 17 cycles or 1062ns
avr.AsmFull(`
send_bit:
st {portSet}, {maskSet} ; [2] set output high
lsl {value} ; [1] shift off the next bit, store it in C
brcs skip_store ; [1/2] branch if this bit is high (long pulse)
st {portClear}, {maskClear} ; [2] set output low (short pulse)
skip_store:
nop ; [4] wait before changing the output again
nop
nop
nop
st {portClear}, {maskClear} ; [2] set output low (end of pulse)
nop ; [3]
nop
nop
subi {i}, 1 ; [1] subtract one (for the loop)
brne send_bit ; [1/2] send the next bit, if not at the end of the loop
`, map[string]interface{}{
"value": c,
"i": 8,
"maskSet": maskSet,
"portSet": portSet,
"maskClear": maskClear,
"portClear": portClear,
})
return nil
}
+50
View File
@@ -0,0 +1,50 @@
// +build digispark
package ws2812
// This file implements the WS2812 protocol for 16.5MHz Digispark AVR microcontrollers.
// This is a slightly different implementation than the one for the atmega to work around a compiler bug.
import (
"device/avr"
)
// Send a single byte using the WS2812 protocol.
func (d Device) WriteByte(c byte) error {
// For the AVR at 16MHz
portSet, maskSet := d.Pin.PortMaskSet()
portClear, maskClear := d.Pin.PortMaskClear()
// See:
// https://wp.josh.com/2014/05/13/ws2812-neopixels-are-not-so-finicky-once-you-get-to-know-them/
// T0H: 4 cycles or 250ns
// T0L: 14 cycles or 875ns -> together 18 cycles or 1125ns
// T1H: 9 cycles or 562ns
// T1L: 8 cycles or 500ns -> together 17 cycles or 1062ns
avr.AsmFull(`
send_bit:
st {portSet}, {maskSet} ; [2] set output high
lsl {value} ; [1] shift off the next bit, store it in C
brcs skip_store ; [1/2] branch if this bit is high (long pulse)
st {portClear}, {maskClear} ; [2] set output low (short pulse)
skip_store:
nop ; [4] wait before changing the output again
nop
nop
nop
st {portClear}, {maskClear} ; [2] set output low (end of pulse)
nop ; [3]
nop
nop
subi {i}, 1 ; [1] subtract one (for the loop)
brne send_bit ; [1/2] send the next bit, if not at the end of the loop
`, map[string]interface{}{
"value": c,
"i": byte(8),
"maskSet": maskSet,
"portSet": portSet,
"maskClear": maskClear,
"portClear": portClear,
})
return nil
}
+3
View File
@@ -4,10 +4,13 @@ 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()