mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-08-01 13:37:49 +00:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e376785596 | |||
| 4c98d0bc34 | |||
| 65f8299153 | |||
| a689aef543 | |||
| 7cd7df7bb8 | |||
| 705f474897 | |||
| ce9c93f228 | |||
| db02cbb8a4 | |||
| a07b20f2f9 | |||
| 4ad210060f | |||
| 2ae950e96d | |||
| dd34b83e9f | |||
| ce81b66fe2 | |||
| b1ae52d1b9 | |||
| 794a9c202f | |||
| 38f97a8b45 | |||
| 35a146d60e | |||
| 4f82c06df9 | |||
| af4efceac1 | |||
| 5df96c8138 | |||
| 88aeec9f69 |
@@ -1,3 +1,35 @@
|
||||
0.14.0
|
||||
---
|
||||
- **new devices**
|
||||
- lis2mdl: add LIS2MDL magnetometer (#187)
|
||||
- waveshare: add Waveshare 4.2in B/W e-paper driver (#183)
|
||||
- **enhancements**
|
||||
- adt7410: add connection test and for that matter connection method
|
||||
- gps
|
||||
- add speed and heading to fix, as parsed from RMC NMEA sentence
|
||||
- improvements and bugfixes (#186)
|
||||
- ili9341
|
||||
- add support for setting framerate, vsync pause, and reading scanline data.
|
||||
- renamed NewSpi() to NewSPI() in accordance with Go naming conventions
|
||||
- ws2812
|
||||
- add support for ESP8266
|
||||
- add support for ESP32
|
||||
- **bugfixes**
|
||||
- ili9341
|
||||
- rix setWindow bug, add CS pin for Clue compatibility. (#180)
|
||||
- bugfix for RAMWR bug
|
||||
- lis2mdl: turn on read mode on every read, to ensure that magnetometer data is updated
|
||||
- **core**
|
||||
- i2c
|
||||
- switch all i2c drivers definitions to use i2c bus interface type instead of machine package concrete type
|
||||
- correct interface definition for I2C Tx function
|
||||
- **testing**
|
||||
- fix smoke-test unless avr-gcc installed
|
||||
- add very basic mock structs for testing i2c devices, based on work done by @rogpeppe
|
||||
- improve API surface and implement one more test function in lis2mdl driver
|
||||
- **docs**
|
||||
- replace README badge for godocs with pkgdocs
|
||||
|
||||
0.13.0
|
||||
---
|
||||
- **new devices**
|
||||
|
||||
@@ -119,6 +119,8 @@ smoke-test:
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/waveshare-epd/epd2in13x/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/waveshare-epd/epd4in2/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/wifinina/ntpclient/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/wifinina/udpstation/main.go
|
||||
@@ -129,8 +131,10 @@ smoke-test:
|
||||
@md5sum ./build/test.hex
|
||||
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=digispark ./examples/ws2812
|
||||
@md5sum ./build/test.hex
|
||||
endif
|
||||
tinygo build -size short -o ./build/test.hex -target=trinket-m0 ./examples/bme280/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/microphone/main.go
|
||||
@@ -151,5 +155,7 @@ smoke-test:
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=hifive1b ./examples/ssd1351/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/lis2mdl/main.go
|
||||
@md5sum ./build/test.hex
|
||||
|
||||
test: clean fmt-check smoke-test
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# TinyGo Drivers
|
||||
|
||||
[](https://godoc.org/tinygo.org/x/drivers) [](https://circleci.com/gh/tinygo-org/drivers/tree/dev)
|
||||
[](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).
|
||||
@@ -52,7 +52,7 @@ func main() {
|
||||
|
||||
## Currently supported devices
|
||||
|
||||
The following 51 devices are supported.
|
||||
The following 53 devices are supported.
|
||||
|
||||
| Device Name | Interface Type |
|
||||
|----------|-------------|
|
||||
@@ -80,6 +80,7 @@ The following 51 devices are supported.
|
||||
| [ILI9341 TFT color display](https://cdn-shop.adafruit.com/datasheets/ILI9341.pdf) | SPI |
|
||||
| [L293x motor driver](https://www.ti.com/lit/ds/symlink/l293d.pdf) | GPIO/PWM |
|
||||
| [L9110x motor driver](https://www.elecrow.com/download/datasheet-l9110.pdf) | GPIO/PWM |
|
||||
| [LIS2MDL magnetometer](https://www.st.com/resource/en/datasheet/lis2mdl.pdf) | I2C |
|
||||
| [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 |
|
||||
@@ -106,6 +107,7 @@ The following 51 devices are supported.
|
||||
| [VL53L1X time-of-flight distance sensor](https://www.st.com/resource/en/datasheet/vl53l1x.pdf) | I2C |
|
||||
| [Waveshare 2.13" (B & C) e-paper display](https://www.waveshare.com/w/upload/d/d3/2.13inch-e-paper-b-Specification.pdf) | SPI |
|
||||
| [Waveshare 2.13" e-paper display](https://www.waveshare.com/w/upload/e/e6/2.13inch_e-Paper_Datasheet.pdf) | SPI |
|
||||
| [Waveshare 4.2" e-paper B/W display](https://www.waveshare.com/w/upload/6/6a/4.2inch-e-paper-specification.pdf) | SPI |
|
||||
| [WS2812 RGB LED](https://cdn-shop.adafruit.com/datasheets/WS2812.pdf) | GPIO |
|
||||
|
||||
## Contributing
|
||||
|
||||
+29
-27
@@ -1,8 +1,13 @@
|
||||
// Package adt7410 provides a driver for the adt7410 I2C Temperature Sensor.
|
||||
//
|
||||
// Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/ADT7410.pdf
|
||||
//
|
||||
package adt7410 // import "tinygo.org/x/drivers/adt7410"
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
type Error uint8
|
||||
@@ -21,40 +26,37 @@ func (e Error) Error() string {
|
||||
}
|
||||
|
||||
type Device struct {
|
||||
bus *machine.I2C
|
||||
buf []byte
|
||||
addr uint8
|
||||
bus drivers.I2C
|
||||
buf []byte
|
||||
Address uint8
|
||||
}
|
||||
|
||||
// New returns ADT7410 device for the provided I2C bus and address. The ADT7410
|
||||
// has a default address of 0x48 (1001000). The last 2 bits of the address
|
||||
// New returns ADT7410 device for the provided I2C bus using default address.
|
||||
// of 0x48 (1001000). To use multiple ADT7410 devices, the last 2 bits of the address
|
||||
// can be set using by connecting to the A1 and A0 pins to VDD or GND (for a
|
||||
// total of up to 4 devices on a I2C bus). Also note that 10k pullups are
|
||||
// recommended for the SDA and SCL lines.
|
||||
func New(i2c *machine.I2C, addressBits uint8) *Device {
|
||||
func New(i2c drivers.I2C) *Device {
|
||||
return &Device{
|
||||
bus: i2c,
|
||||
buf: make([]byte, 2),
|
||||
addr: Address | (addressBits & 0x3),
|
||||
bus: i2c,
|
||||
buf: make([]byte, 2),
|
||||
Address: Address,
|
||||
}
|
||||
}
|
||||
|
||||
func (dev *Device) Configure() (err error) {
|
||||
|
||||
// verify the chip ID
|
||||
// TODO: According to datasheet, the check below should work; however
|
||||
// this does not seem to be working right, but is not exactly
|
||||
// necessary, so can revisit later to see if there is a bug
|
||||
//id := dev.ReadByte(RegID) & 0xF8
|
||||
//if id != 0xC8 {
|
||||
// err = ErrInvalidID
|
||||
//}
|
||||
|
||||
// Configure the ADT7410 device.
|
||||
func (d *Device) Configure() (err error) {
|
||||
// reset the chip
|
||||
dev.writeByte(RegReset, 0xFF)
|
||||
d.writeByte(RegReset, 0xFF)
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
return
|
||||
}
|
||||
|
||||
// Connected returns whether sensor has been found.
|
||||
func (d *Device) Connected() bool {
|
||||
data := []byte{0}
|
||||
d.bus.ReadRegister(uint8(d.Address), RegID, data)
|
||||
return data[0]&0xF8 == 0xC8
|
||||
}
|
||||
|
||||
// ReadTemperature returns the temperature in celsius milli degrees (°C/1000)
|
||||
@@ -62,13 +64,13 @@ func (d *Device) ReadTemperature() (temperature int32, err error) {
|
||||
return (int32(d.readUint16(RegTempValueMSB)) * 1000) / 128, nil
|
||||
}
|
||||
|
||||
// ReadTempC returns the value in the temperature value register, in Celcius
|
||||
// ReadTempC returns the value in the temperature value register, in Celsius.
|
||||
func (d *Device) ReadTempC() float32 {
|
||||
t := d.readUint16(RegTempValueMSB)
|
||||
return float32(int(t)) / 128.0
|
||||
}
|
||||
|
||||
// ReadTempF returns the value in the temperature value register, in Fahrenheit
|
||||
// ReadTempF returns the value in the temperature value register, in Fahrenheit.
|
||||
func (d *Device) ReadTempF() float32 {
|
||||
return d.ReadTempC()*1.8 + 32.0
|
||||
}
|
||||
@@ -76,15 +78,15 @@ func (d *Device) ReadTempF() float32 {
|
||||
func (d *Device) writeByte(reg uint8, data byte) {
|
||||
d.buf[0] = reg
|
||||
d.buf[1] = data
|
||||
d.bus.Tx(uint16(d.addr), d.buf, nil)
|
||||
d.bus.Tx(uint16(d.Address), d.buf, nil)
|
||||
}
|
||||
|
||||
func (d *Device) readByte(reg uint8) byte {
|
||||
d.bus.ReadRegister(d.addr, reg, d.buf)
|
||||
d.bus.ReadRegister(d.Address, reg, d.buf)
|
||||
return d.buf[0]
|
||||
}
|
||||
|
||||
func (d *Device) readUint16(reg uint8) uint16 {
|
||||
d.bus.ReadRegister(d.addr, reg, d.buf)
|
||||
d.bus.ReadRegister(d.Address, reg, d.buf)
|
||||
return uint16(d.buf[0])<<8 | uint16(d.buf[1])
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package adt7410
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
"tinygo.org/x/drivers/tester"
|
||||
)
|
||||
|
||||
func TestDefaultI2CAddress(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := tester.NewI2CBus(c)
|
||||
dev := New(bus)
|
||||
c.Assert(dev.Address, qt.Equals, uint8(Address))
|
||||
}
|
||||
|
||||
func TestWhoAmI(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := tester.NewI2CBus(c)
|
||||
fake := tester.NewI2CDevice(c, Address)
|
||||
fake.SetupRegisters(defaultRegisters())
|
||||
bus.AddDevice(fake)
|
||||
|
||||
dev := New(bus)
|
||||
c.Assert(dev.Connected(), qt.Equals, true)
|
||||
|
||||
fake.SetupRegister(RegID, 0x99)
|
||||
c.Assert(dev.Connected(), qt.Equals, false)
|
||||
}
|
||||
|
||||
// defaultRegisters returns the default values for all of the device's registers.
|
||||
// see table 22 on page 27 of the datasheet.
|
||||
func defaultRegisters() []uint8 {
|
||||
return []uint8{
|
||||
RegTempValueMSB: 0,
|
||||
RegTempValueLSB: 0,
|
||||
RegStatus: 0,
|
||||
RegConfig: 0,
|
||||
RegTHIGHMsbReg: 0x20,
|
||||
RegTHIGHLsbReg: 0,
|
||||
RegTLOWMsbReg: 0x05,
|
||||
RegTLOWLsbReg: 0,
|
||||
RegTCRITMsbReg: 0x49,
|
||||
RegTCRITLsbReg: 0x80,
|
||||
RegTHYSTReg: 0x05,
|
||||
RegID: 0xC8,
|
||||
RegReset: 0,
|
||||
}
|
||||
}
|
||||
+48
-2
@@ -1,8 +1,33 @@
|
||||
package adt7410
|
||||
|
||||
// 0x00 Temperature value most significant byte 0x00
|
||||
// 0x01 Temperature value least significant byte 0x00
|
||||
// 0x02 Status 0x00
|
||||
// 0x03 Configuration 0x00
|
||||
// 0x04 THIGH setpoint most significant byte 0x20 (64°C)
|
||||
// 0x05 THIGH setpoint least significant byte 0x00 (64°C)
|
||||
// 0x06 TLOW setpoint most significant byte 0x05 (10°C)
|
||||
// 0x07 TLOW setpoint least significant byte 0x00 (10°C)
|
||||
// 0x08 TCRIT setpoint most significant byte 0x49 (147°C)
|
||||
// 0x09 TCRIT setpoint least significant byte 0x80 (147°C)
|
||||
// 0x0A THYST setpoint 0x05 (5°C)
|
||||
// 0x0B ID 0xCX
|
||||
// 0x0C Reserved 0xXX
|
||||
// 0x0D Reserved 0xXX
|
||||
// 0x2E Reserved 0xXX
|
||||
// 0x2F Software reset 0xXX
|
||||
|
||||
const (
|
||||
// Default I2C address
|
||||
// Address is default I2C address.
|
||||
Address = 0x48
|
||||
// Address1 is for first device, aka the default.
|
||||
Address1 = Address
|
||||
// Address2 is for second device.
|
||||
Address2 = 0x49
|
||||
// Address3 is for third device.
|
||||
Address3 = 0x4A
|
||||
// Address4 is for fourth device.
|
||||
Address4 = 0x4B
|
||||
|
||||
// Temperature Value MSB Register
|
||||
RegTempValueMSB = 0x0
|
||||
@@ -16,7 +41,28 @@ const (
|
||||
// Config Register
|
||||
RegConfig = 0x3
|
||||
|
||||
// ID Register
|
||||
// THIGH setpoint most significant byte 0x20 (64°C)
|
||||
RegTHIGHMsbReg = 0x4
|
||||
|
||||
// THIGH setpoint least significant byte 0x00 (64°C)
|
||||
RegTHIGHLsbReg = 0x5
|
||||
|
||||
// TLOW setpoint most significant byte 0x05 (10°C)
|
||||
RegTLOWMsbReg = 0x6
|
||||
|
||||
// TLOW setpoint least significant byte 0x00 (10°C)
|
||||
RegTLOWLsbReg = 0x7
|
||||
|
||||
// TCRIT setpoint most significant byte 0x49 (147°C)
|
||||
RegTCRITMsbReg = 0x8
|
||||
|
||||
// TCRIT setpoint least significant byte 0x80 (147°C)
|
||||
RegTCRITLsbReg = 0x9
|
||||
|
||||
// THYST setpoint 0x05 (5°C)
|
||||
RegTHYSTReg = 0xA
|
||||
|
||||
// ID Register (0xCx)
|
||||
RegID = 0x0B
|
||||
|
||||
// Software Reset Register
|
||||
|
||||
+3
-5
@@ -6,9 +6,7 @@
|
||||
//
|
||||
package adxl345 // import "tinygo.org/x/drivers/adxl345"
|
||||
|
||||
import (
|
||||
"machine"
|
||||
)
|
||||
import "tinygo.org/x/drivers"
|
||||
|
||||
type Range uint8
|
||||
type Rate uint8
|
||||
@@ -40,7 +38,7 @@ type bwRate struct {
|
||||
|
||||
// Device wraps an I2C connection to a ADXL345 device.
|
||||
type Device struct {
|
||||
bus machine.I2C
|
||||
bus drivers.I2C
|
||||
Address uint16
|
||||
powerCtl powerCtl
|
||||
dataFormat dataFormat
|
||||
@@ -52,7 +50,7 @@ type Device struct {
|
||||
//
|
||||
// This function only creates the Device object, it does not init the device.
|
||||
// To do that you must call the Configure() method on the Device before using it.
|
||||
func New(bus machine.I2C) Device {
|
||||
func New(bus drivers.I2C) Device {
|
||||
return Device{
|
||||
bus: bus,
|
||||
powerCtl: powerCtl{
|
||||
|
||||
+4
-3
@@ -5,13 +5,14 @@
|
||||
package amg88xx // import "tinygo.org/x/drivers/amg88xx"
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
// Device wraps an I2C connection to a AMG88xx device.
|
||||
type Device struct {
|
||||
bus machine.I2C
|
||||
bus drivers.I2C
|
||||
Address uint16
|
||||
data []uint8
|
||||
interruptMode InterruptMode
|
||||
@@ -27,7 +28,7 @@ type Config struct {
|
||||
// configured.
|
||||
//
|
||||
// This function only creates the Device object, it does not touch the device.
|
||||
func New(bus machine.I2C) Device {
|
||||
func New(bus drivers.I2C) Device {
|
||||
return Device{
|
||||
bus: bus,
|
||||
Address: AddressHigh,
|
||||
|
||||
+4
-3
@@ -6,13 +6,14 @@ package at24cx // import "tinygo.org/x/drivers/at24cx"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
// Device wraps an I2C connection to a DS3231 device.
|
||||
type Device struct {
|
||||
bus machine.I2C
|
||||
bus drivers.I2C
|
||||
Address uint16
|
||||
pageSize uint16
|
||||
currentRAMAddress uint16
|
||||
@@ -30,7 +31,7 @@ type Config struct {
|
||||
// configured.
|
||||
//
|
||||
// This function only creates the Device object, it does not touch the device.
|
||||
func New(bus machine.I2C) Device {
|
||||
func New(bus drivers.I2C) Device {
|
||||
return Device{
|
||||
bus: bus,
|
||||
Address: Address,
|
||||
|
||||
+3
-3
@@ -8,7 +8,7 @@ package bh1750 // import "tinygo.org/x/drivers/bh1750"
|
||||
import (
|
||||
"time"
|
||||
|
||||
"machine"
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
// SamplingMode is the sampling's resolution of the measurement
|
||||
@@ -16,7 +16,7 @@ type SamplingMode byte
|
||||
|
||||
// Device wraps an I2C connection to a bh1750 device.
|
||||
type Device struct {
|
||||
bus machine.I2C
|
||||
bus drivers.I2C
|
||||
Address uint16
|
||||
mode SamplingMode
|
||||
}
|
||||
@@ -25,7 +25,7 @@ type Device struct {
|
||||
// configured.
|
||||
//
|
||||
// This function only creates the Device object, it does not touch the device.
|
||||
func New(bus machine.I2C) Device {
|
||||
func New(bus drivers.I2C) Device {
|
||||
return Device{
|
||||
bus: bus,
|
||||
Address: Address,
|
||||
|
||||
+4
-5
@@ -1,15 +1,14 @@
|
||||
// Package blinkm implements a driver for the BlinkM I2C RGB LED.
|
||||
//
|
||||
// Datasheet: http://thingm.com/fileadmin/thingm/downloads/BlinkM_datasheet.pdf
|
||||
//
|
||||
package blinkm // import "tinygo.org/x/drivers/blinkm"
|
||||
|
||||
import (
|
||||
"machine"
|
||||
)
|
||||
import "tinygo.org/x/drivers"
|
||||
|
||||
// Device wraps an I2C connection to a BlinkM device.
|
||||
type Device struct {
|
||||
bus machine.I2C
|
||||
bus drivers.I2C
|
||||
Address uint16
|
||||
}
|
||||
|
||||
@@ -17,7 +16,7 @@ type Device struct {
|
||||
// configured.
|
||||
//
|
||||
// This function only creates the Device object, it does not touch the device.
|
||||
func New(bus machine.I2C) Device {
|
||||
func New(bus drivers.I2C) Device {
|
||||
return Device{bus, Address}
|
||||
}
|
||||
|
||||
|
||||
+4
-3
@@ -7,8 +7,9 @@
|
||||
package bme280
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"math"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
// calibrationCoefficients reads at startup and stores the calibration coefficients
|
||||
@@ -35,7 +36,7 @@ type calibrationCoefficients struct {
|
||||
|
||||
// Device wraps an I2C connection to a BME280 device.
|
||||
type Device struct {
|
||||
bus machine.I2C
|
||||
bus drivers.I2C
|
||||
Address uint16
|
||||
calibrationCoefficients calibrationCoefficients
|
||||
}
|
||||
@@ -44,7 +45,7 @@ type Device struct {
|
||||
// configured.
|
||||
//
|
||||
// This function only creates the Device object, it does not touch the device.
|
||||
func New(bus machine.I2C) Device {
|
||||
func New(bus drivers.I2C) Device {
|
||||
return Device{
|
||||
bus: bus,
|
||||
Address: Address,
|
||||
|
||||
+3
-3
@@ -9,7 +9,7 @@ package bmp180 // import "tinygo.org/x/drivers/bmp180"
|
||||
import (
|
||||
"time"
|
||||
|
||||
"machine"
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
// OversamplingMode is the oversampling ratio of the pressure measurement.
|
||||
@@ -32,7 +32,7 @@ type calibrationCoefficients struct {
|
||||
|
||||
// Device wraps an I2C connection to a BMP180 device.
|
||||
type Device struct {
|
||||
bus machine.I2C
|
||||
bus drivers.I2C
|
||||
Address uint16
|
||||
mode OversamplingMode
|
||||
calibrationCoefficients calibrationCoefficients
|
||||
@@ -43,7 +43,7 @@ type Device struct {
|
||||
//
|
||||
// This function only creates the Device object, it does not initialize the device.
|
||||
// You must call Configure() first in order to use the device itself.
|
||||
func New(bus machine.I2C) Device {
|
||||
func New(bus drivers.I2C) Device {
|
||||
return Device{
|
||||
bus: bus,
|
||||
Address: Address,
|
||||
|
||||
+4
-3
@@ -1,8 +1,9 @@
|
||||
package bmp280
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
// OversamplingMode is the oversampling ratio of the temperature or pressure measurement.
|
||||
@@ -19,7 +20,7 @@ type Filter uint
|
||||
|
||||
// Device wraps an I2C connection to a BMP280 device.
|
||||
type Device struct {
|
||||
bus machine.I2C
|
||||
bus drivers.I2C
|
||||
Address uint16
|
||||
cali calibrationCoefficients
|
||||
Temperature Oversampling
|
||||
@@ -52,7 +53,7 @@ type calibrationCoefficients struct {
|
||||
//
|
||||
// This function only creates the Device object, it does not initialize the device.
|
||||
// You must call Configure() first in order to use the device itself.
|
||||
func New(bus machine.I2C) Device {
|
||||
func New(bus drivers.I2C) Device {
|
||||
return Device{
|
||||
bus: bus,
|
||||
Address: Address,
|
||||
|
||||
+3
-3
@@ -9,18 +9,18 @@ import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"machine"
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
// Device wraps an I2C connection to a DS1307 device.
|
||||
type Device struct {
|
||||
bus machine.I2C
|
||||
bus drivers.I2C
|
||||
Address uint8
|
||||
AddressSRAM uint8
|
||||
}
|
||||
|
||||
// New creates a new DS1307 connection. I2C bus must be already configured.
|
||||
func New(bus machine.I2C) Device {
|
||||
func New(bus drivers.I2C) Device {
|
||||
return Device{bus: bus,
|
||||
Address: uint8(I2CAddress),
|
||||
AddressSRAM: SRAMBeginAddres,
|
||||
|
||||
+4
-3
@@ -5,15 +5,16 @@
|
||||
package ds3231 // import "tinygo.org/x/drivers/ds3231"
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
type Mode uint8
|
||||
|
||||
// Device wraps an I2C connection to a DS3231 device.
|
||||
type Device struct {
|
||||
bus machine.I2C
|
||||
bus drivers.I2C
|
||||
Address uint16
|
||||
}
|
||||
|
||||
@@ -21,7 +22,7 @@ type Device struct {
|
||||
// configured.
|
||||
//
|
||||
// This function only creates the Device object, it does not touch the device.
|
||||
func New(bus machine.I2C) Device {
|
||||
func New(bus drivers.I2C) Device {
|
||||
return Device{
|
||||
bus: bus,
|
||||
Address: Address,
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
|
||||
var (
|
||||
i2c = &machine.I2C0
|
||||
sensor = adt7410.New(i2c, 0)
|
||||
sensor = adt7410.New(i2c)
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/gps"
|
||||
)
|
||||
@@ -11,19 +11,40 @@ func main() {
|
||||
println("GPS I2C Example")
|
||||
machine.I2C0.Configure(machine.I2CConfig{})
|
||||
ublox := gps.NewI2C(&machine.I2C0)
|
||||
parser := gps.Parser(ublox)
|
||||
parser := gps.NewParser()
|
||||
var fix gps.Fix
|
||||
for {
|
||||
fix = parser.NextFix()
|
||||
s, err := ublox.NextSentence()
|
||||
if err != nil {
|
||||
println(err)
|
||||
continue
|
||||
}
|
||||
|
||||
fix, err = parser.Parse(s)
|
||||
if err != nil {
|
||||
println(err)
|
||||
continue
|
||||
}
|
||||
if fix.Valid {
|
||||
print(fix.Time.Format("15:04:05"))
|
||||
print(", lat=", fmt.Sprintf("%f", fix.Latitude))
|
||||
print(", long=", fmt.Sprintf("%f", fix.Longitude))
|
||||
print(", altitude:=", fix.Altitude)
|
||||
print(", lat=")
|
||||
print(fix.Latitude)
|
||||
print(", long=")
|
||||
print(fix.Longitude)
|
||||
print(", altitude=", fix.Altitude)
|
||||
print(", satellites=", fix.Satellites)
|
||||
if fix.Speed != 0 {
|
||||
print(", speed=")
|
||||
print(fix.Speed)
|
||||
}
|
||||
if fix.Heading != 0 {
|
||||
print(", heading=")
|
||||
print(fix.Heading)
|
||||
}
|
||||
println()
|
||||
} else {
|
||||
println("No fix")
|
||||
}
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/gps"
|
||||
)
|
||||
@@ -11,19 +11,40 @@ func main() {
|
||||
println("GPS UART Example")
|
||||
machine.UART1.Configure(machine.UARTConfig{BaudRate: 9600})
|
||||
ublox := gps.NewUART(&machine.UART1)
|
||||
parser := gps.Parser(ublox)
|
||||
parser := gps.NewParser()
|
||||
var fix gps.Fix
|
||||
for {
|
||||
fix = parser.NextFix()
|
||||
s, err := ublox.NextSentence()
|
||||
if err != nil {
|
||||
println(err)
|
||||
continue
|
||||
}
|
||||
|
||||
fix, err = parser.Parse(s)
|
||||
if err != nil {
|
||||
println(err)
|
||||
continue
|
||||
}
|
||||
if fix.Valid {
|
||||
print(fix.Time.Format("15:04:05"))
|
||||
print(", lat=", fmt.Sprintf("%f", fix.Latitude))
|
||||
print(", long=", fmt.Sprintf("%f", fix.Longitude))
|
||||
print(", altitude:=", fix.Altitude)
|
||||
print(", lat=")
|
||||
print(fix.Latitude)
|
||||
print(", long=")
|
||||
print(fix.Longitude)
|
||||
print(", altitude=", fix.Altitude)
|
||||
print(", satellites=", fix.Satellites)
|
||||
if fix.Speed != 0 {
|
||||
print(", speed=")
|
||||
print(fix.Speed)
|
||||
}
|
||||
if fix.Heading != 0 {
|
||||
print(", heading=")
|
||||
print(fix.Heading)
|
||||
}
|
||||
println()
|
||||
} else {
|
||||
println("No fix")
|
||||
}
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
display = ili9341.NewSpi(
|
||||
display = ili9341.NewSPI(
|
||||
machine.SPI0,
|
||||
machine.D0,
|
||||
machine.D1,
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
display = ili9341.NewSpi(
|
||||
display = ili9341.NewSPI(
|
||||
machine.SPI3,
|
||||
machine.LCD_DC,
|
||||
machine.LCD_SS_PIN,
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
display = ili9341.NewSpi(
|
||||
display = ili9341.NewSPI(
|
||||
machine.SPI3,
|
||||
machine.LCD_DC,
|
||||
machine.LCD_SS_PIN,
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
display = ili9341.NewSpi(
|
||||
display = ili9341.NewSPI(
|
||||
machine.SPI0,
|
||||
machine.D0,
|
||||
machine.D1,
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
display = ili9341.NewSpi(
|
||||
display = ili9341.NewSPI(
|
||||
machine.SPI3,
|
||||
machine.LCD_DC,
|
||||
machine.LCD_SS_PIN,
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/lis2mdl"
|
||||
)
|
||||
|
||||
func main() {
|
||||
machine.I2C0.Configure(machine.I2CConfig{})
|
||||
compass := lis2mdl.New(machine.I2C0)
|
||||
|
||||
if !compass.Connected() {
|
||||
for {
|
||||
println("LIS2MDL not connected!")
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
compass.Configure(lis2mdl.Configuration{}) //default settings
|
||||
|
||||
for {
|
||||
heading := compass.ReadCompass()
|
||||
println("Heading:", heading)
|
||||
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
}
|
||||
}
|
||||
+28
-3
@@ -9,12 +9,37 @@ import (
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
// Example configuration for Adafruit Clue
|
||||
// machine.SPI1.Configure(machine.SPIConfig{
|
||||
// Frequency: 8000000,
|
||||
// SCK: machine.TFT_SCK,
|
||||
// SDO: machine.TFT_SDO,
|
||||
// SDI: machine.TFT_SDO,
|
||||
// Mode: 0,
|
||||
// })
|
||||
// display := st7789.New(machine.SPI1,
|
||||
// machine.TFT_RESET,
|
||||
// machine.TFT_DC,
|
||||
// machine.TFT_CS,
|
||||
// machine.TFT_LITE)
|
||||
|
||||
machine.SPI0.Configure(machine.SPIConfig{
|
||||
Frequency: 8000000,
|
||||
Mode: 3,
|
||||
Mode: 0,
|
||||
})
|
||||
display := st7789.New(machine.SPI0,
|
||||
machine.P6, // TFT_RESET
|
||||
machine.P7, // TFT_DC
|
||||
machine.P8, // TFT_CS
|
||||
machine.P9) // TFT_LITE
|
||||
|
||||
display.Configure(st7789.Config{
|
||||
Rotation: st7789.NO_ROTATION,
|
||||
RowOffset: 80,
|
||||
FrameRate: st7789.FRAMERATE_111,
|
||||
VSyncLines: st7789.MAX_VSYNC_SCANLINES,
|
||||
})
|
||||
display := st7789.New(machine.SPI0, machine.P6, machine.P7, machine.P8)
|
||||
display.Configure(st7789.Config{Rotation: st7789.NO_ROTATION})
|
||||
|
||||
width, height := display.Size()
|
||||
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
|
||||
"image/color"
|
||||
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/waveshare-epd/epd4in2"
|
||||
)
|
||||
|
||||
var display epd4in2.Device
|
||||
|
||||
func main() {
|
||||
machine.SPI0.Configure(machine.SPIConfig{
|
||||
Frequency: 8000000,
|
||||
Mode: 0,
|
||||
})
|
||||
|
||||
display = epd4in2.New(machine.SPI0, machine.P6, machine.P7, machine.P8, machine.P9)
|
||||
display.Configure(epd4in2.Config{})
|
||||
|
||||
black := color.RGBA{1, 1, 1, 255}
|
||||
|
||||
display.ClearBuffer()
|
||||
println("Clear the display")
|
||||
display.ClearDisplay()
|
||||
display.WaitUntilIdle()
|
||||
println("Waiting for 2 seconds")
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
// Show a checkered board
|
||||
for i := int16(0); i < 16; i++ {
|
||||
for j := int16(0); j < 25; j++ {
|
||||
if (i+j)%2 == 0 {
|
||||
showRect(i*8, j*10, 8, 10, black)
|
||||
}
|
||||
}
|
||||
}
|
||||
println("Show checkered board")
|
||||
display.Display()
|
||||
display.WaitUntilIdle()
|
||||
println("Waiting for 2 seconds")
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
println("You could remove power now")
|
||||
}
|
||||
|
||||
func showRect(x int16, y int16, w int16, h int16, c color.RGBA) {
|
||||
for i := x; i < x+w; i++ {
|
||||
for j := y; j < y+h; j++ {
|
||||
display.SetPixel(i, j, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
module tinygo.org/x/drivers
|
||||
|
||||
go 1.14
|
||||
go 1.15
|
||||
|
||||
require github.com/eclipse/paho.mqtt.golang v1.2.0
|
||||
require (
|
||||
github.com/eclipse/paho.mqtt.golang v1.2.0
|
||||
github.com/frankban/quicktest v1.10.2
|
||||
)
|
||||
|
||||
@@ -1,2 +1,13 @@
|
||||
github.com/eclipse/paho.mqtt.golang v1.2.0 h1:1F8mhG9+aO5/xpdtFkW4SxOJB67ukuDC3t2y2qayIX0=
|
||||
github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts=
|
||||
github.com/frankban/quicktest v1.10.2 h1:19ARM85nVi4xH7xPXuc5eM/udya5ieh7b/Sv+d844Tk=
|
||||
github.com/frankban/quicktest v1.10.2/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s=
|
||||
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
||||
+33
-21
@@ -3,24 +3,32 @@ package gps // import "tinygo.org/x/drivers/gps"
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"machine"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
var (
|
||||
errInvalidNMEASentenceLength = errors.New("invalid NMEA sentence length")
|
||||
errInvalidNMEAChecksum = errors.New("invalid NMEA sentence checksum")
|
||||
)
|
||||
|
||||
// Device wraps a connection to a GPS device.
|
||||
type GPSDevice struct {
|
||||
type Device struct {
|
||||
buffer []byte
|
||||
bufIdx int
|
||||
sentence strings.Builder
|
||||
uart *machine.UART
|
||||
bus *machine.I2C
|
||||
bus drivers.I2C
|
||||
address uint16
|
||||
}
|
||||
|
||||
// NewUART creates a new UART GPS connection. The UART must already be configured.
|
||||
func NewUART(uart *machine.UART) GPSDevice {
|
||||
return GPSDevice{
|
||||
func NewUART(uart *machine.UART) Device {
|
||||
return Device{
|
||||
uart: uart,
|
||||
buffer: make([]byte, bufferSize),
|
||||
bufIdx: bufferSize,
|
||||
@@ -29,8 +37,8 @@ func NewUART(uart *machine.UART) GPSDevice {
|
||||
}
|
||||
|
||||
// NewI2C creates a new I2C GPS connection.
|
||||
func NewI2C(bus *machine.I2C) GPSDevice {
|
||||
return GPSDevice{
|
||||
func NewI2C(bus drivers.I2C) Device {
|
||||
return Device{
|
||||
bus: bus,
|
||||
address: I2C_ADDRESS,
|
||||
buffer: make([]byte, bufferSize),
|
||||
@@ -39,17 +47,17 @@ func NewI2C(bus *machine.I2C) GPSDevice {
|
||||
}
|
||||
}
|
||||
|
||||
// ReadNextSentence returns the next valid NMEA sentence from the GPS device.
|
||||
func (gps *GPSDevice) NextSentence() (sentence string) {
|
||||
// NextSentence returns the next valid NMEA sentence from the GPS device.
|
||||
func (gps *Device) NextSentence() (sentence string, err error) {
|
||||
sentence = gps.readNextSentence()
|
||||
for !validSentence(sentence) {
|
||||
sentence = gps.readNextSentence()
|
||||
if err = validSentence(sentence); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return sentence
|
||||
return sentence, nil
|
||||
}
|
||||
|
||||
// readNextSentence returns the next sentence from the GPS device.
|
||||
func (gps *GPSDevice) readNextSentence() (sentence string) {
|
||||
func (gps *Device) readNextSentence() (sentence string) {
|
||||
gps.sentence.Reset()
|
||||
var b byte = ' '
|
||||
|
||||
@@ -69,7 +77,7 @@ func (gps *GPSDevice) readNextSentence() (sentence string) {
|
||||
return sentence
|
||||
}
|
||||
|
||||
func (gps *GPSDevice) readNextByte() (b byte) {
|
||||
func (gps *Device) readNextByte() (b byte) {
|
||||
gps.bufIdx += 1
|
||||
if gps.bufIdx >= bufferSize {
|
||||
gps.fillBuffer()
|
||||
@@ -77,7 +85,7 @@ func (gps *GPSDevice) readNextByte() (b byte) {
|
||||
return gps.buffer[gps.bufIdx]
|
||||
}
|
||||
|
||||
func (gps *GPSDevice) fillBuffer() {
|
||||
func (gps *Device) fillBuffer() {
|
||||
if gps.uart != nil {
|
||||
gps.uartFillBuffer()
|
||||
} else {
|
||||
@@ -85,7 +93,7 @@ func (gps *GPSDevice) fillBuffer() {
|
||||
}
|
||||
}
|
||||
|
||||
func (gps *GPSDevice) uartFillBuffer() {
|
||||
func (gps *Device) uartFillBuffer() {
|
||||
for gps.uart.Buffered() < bufferSize {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
@@ -93,7 +101,7 @@ func (gps *GPSDevice) uartFillBuffer() {
|
||||
gps.bufIdx = 0
|
||||
}
|
||||
|
||||
func (gps *GPSDevice) i2cFillBuffer() {
|
||||
func (gps *Device) i2cFillBuffer() {
|
||||
for gps.available() < bufferSize {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
@@ -102,7 +110,7 @@ func (gps *GPSDevice) i2cFillBuffer() {
|
||||
}
|
||||
|
||||
// Available returns how many bytes of GPS data are currently available.
|
||||
func (gps *GPSDevice) available() (available int) {
|
||||
func (gps *Device) available() (available int) {
|
||||
var lengthBytes [2]byte
|
||||
gps.bus.Tx(gps.address, []byte{BYTES_AVAIL_REG}, lengthBytes[0:2])
|
||||
available = int(lengthBytes[0])*256 + int(lengthBytes[1])
|
||||
@@ -110,7 +118,7 @@ func (gps *GPSDevice) available() (available int) {
|
||||
}
|
||||
|
||||
// WriteBytes sends data/commands to the GPS device
|
||||
func (gps *GPSDevice) WriteBytes(bytes []byte) {
|
||||
func (gps *Device) WriteBytes(bytes []byte) {
|
||||
if gps.uart != nil {
|
||||
gps.uart.Write(bytes)
|
||||
} else {
|
||||
@@ -119,14 +127,18 @@ func (gps *GPSDevice) WriteBytes(bytes []byte) {
|
||||
}
|
||||
|
||||
// validSentence checks if a sentence has been received uncorrupted
|
||||
func validSentence(sentence string) bool {
|
||||
func validSentence(sentence string) error {
|
||||
if len(sentence) < 4 || sentence[0] != '$' || sentence[len(sentence)-3] != '*' {
|
||||
return false
|
||||
return errInvalidNMEASentenceLength
|
||||
}
|
||||
var cs byte = 0
|
||||
for i := 1; i < len(sentence)-3; i++ {
|
||||
cs ^= sentence[i]
|
||||
}
|
||||
checksum := hex.EncodeToString([]byte{cs})
|
||||
return (checksum[0] == sentence[len(sentence)-2]) && (checksum[1] == sentence[len(sentence)-1])
|
||||
if (checksum[0] != sentence[len(sentence)-2]) || (checksum[1] != sentence[len(sentence)-1]) {
|
||||
return errInvalidNMEAChecksum
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
+120
-63
@@ -1,92 +1,131 @@
|
||||
package gps
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type GPSParser struct {
|
||||
gpsDevice GPSDevice
|
||||
var (
|
||||
errEmptyNMEASentence = errors.New("cannot parse empty NMEA sentence")
|
||||
errUnknownNMEASentence = errors.New("unsupported NMEA sentence type")
|
||||
errInvalidGGASentence = errors.New("invalid GGA NMEA sentence")
|
||||
errInvalidRMCSentence = errors.New("invalid RMC NMEA sentence")
|
||||
)
|
||||
|
||||
// Parser for GPS NMEA sentences.
|
||||
type Parser struct {
|
||||
}
|
||||
|
||||
// fix is a GPS location fix
|
||||
// Fix is a GPS location fix
|
||||
type Fix struct {
|
||||
Valid bool
|
||||
Time time.Time
|
||||
Latitude float32
|
||||
Longitude float32
|
||||
Altitude int32
|
||||
// Valid if the fix was valid.
|
||||
Valid bool
|
||||
|
||||
// Time that the fix was taken, in UTC time.
|
||||
Time time.Time
|
||||
|
||||
// Latitude is the decimal latitude. Negative numbers indicate S.
|
||||
Latitude float32
|
||||
|
||||
// Longitude is the decimal longitude. Negative numbers indicate E.
|
||||
Longitude float32
|
||||
|
||||
// Altitude is only returned for GGA sentences.
|
||||
Altitude int32
|
||||
|
||||
// Satellites is the number of visible satellites, but is only returned for GGA sentences.
|
||||
Satellites int16
|
||||
|
||||
// Speed based on reported movement. Only returned for RMC sentences.
|
||||
Speed float32
|
||||
|
||||
// Heading based on reported movement. Only returned for RMC sentences.
|
||||
Heading float32
|
||||
}
|
||||
|
||||
func Parser(gpsDevice GPSDevice) GPSParser {
|
||||
return GPSParser{
|
||||
gpsDevice: gpsDevice,
|
||||
// NewParser returns a GPS NMEA Parser.
|
||||
func NewParser() Parser {
|
||||
return Parser{}
|
||||
}
|
||||
|
||||
// Parse parses a NMEA sentence looking for fix info.
|
||||
func (parser *Parser) Parse(sentence string) (fix Fix, err error) {
|
||||
if sentence == "" {
|
||||
err = errEmptyNMEASentence
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// NextFix returns the next GPS location Fix from the GPS device
|
||||
func (parser *GPSParser) NextFix() (fix Fix) {
|
||||
var ggaSentence = nextGGA(parser.gpsDevice)
|
||||
var ggaFields = strings.Split(ggaSentence, ",")
|
||||
fix.Altitude = findAltitude(ggaFields)
|
||||
fix.Satellites = findSatellites(ggaFields)
|
||||
fix.Longitude = findLongitude(ggaFields)
|
||||
fix.Latitude = findLatitude(ggaFields)
|
||||
fix.Time = findTime(ggaFields)
|
||||
fix.Valid = (fix.Altitude != -99999) && (fix.Satellites > 0)
|
||||
return fix
|
||||
}
|
||||
|
||||
// nextGGA returns the next GGA type sentence from the GPS device
|
||||
// $--GGA,,,,,,,,,,,,,,*hh
|
||||
func nextGGA(gpsDevice GPSDevice) (sentence string) {
|
||||
for {
|
||||
sentence = gpsDevice.NextSentence()
|
||||
if sentence[3:6] == "GGA" {
|
||||
return sentence
|
||||
typ := sentence[3:6]
|
||||
switch typ {
|
||||
case "GGA":
|
||||
fields := strings.Split(sentence, ",")
|
||||
if len(fields) != 15 {
|
||||
err = errInvalidGGASentence
|
||||
return
|
||||
}
|
||||
|
||||
fix.Altitude = findAltitude(fields[9])
|
||||
fix.Satellites = findSatellites(fields[7])
|
||||
fix.Longitude = findLongitude(fields[4], fields[5])
|
||||
fix.Latitude = findLatitude(fields[2], fields[3])
|
||||
fix.Time = findTime(fields[1])
|
||||
fix.Valid = (fix.Altitude != -99999) && (fix.Satellites > 0)
|
||||
case "RMC":
|
||||
fields := strings.Split(sentence, ",")
|
||||
if len(fields) != 13 {
|
||||
err = errInvalidRMCSentence
|
||||
return
|
||||
}
|
||||
|
||||
fix.Longitude = findLongitude(fields[5], fields[6])
|
||||
fix.Latitude = findLatitude(fields[3], fields[4])
|
||||
fix.Time = findTime(fields[1])
|
||||
fix.Speed = findSpeed(fields[7])
|
||||
fix.Heading = findHeading(fields[8])
|
||||
fix.Valid = (len(fields[2]) > 0 && fields[2][0:1] == "A")
|
||||
default:
|
||||
err = errUnknownNMEASentence
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// findTime returns the time from a GGA sentence:
|
||||
// findTime returns the time from an NMEA sentence:
|
||||
// $--GGA,hhmmss.ss,,,,,,,,,,,,,*xx
|
||||
func findTime(ggaFields []string) time.Time {
|
||||
if len(ggaFields) < 1 || len(ggaFields[1]) < 6 {
|
||||
func findTime(val string) time.Time {
|
||||
if len(val) < 6 {
|
||||
return time.Time{}
|
||||
}
|
||||
ts := strings.Builder{}
|
||||
ts.WriteString(ggaFields[1][0:2])
|
||||
ts.WriteString(":")
|
||||
ts.WriteString(ggaFields[1][2:4])
|
||||
ts.WriteString(":")
|
||||
ts.WriteString(ggaFields[1][4:6])
|
||||
var t, _ = time.Parse("15:04:05", ts.String())
|
||||
|
||||
h, _ := strconv.ParseInt(val[0:2], 10, 8)
|
||||
m, _ := strconv.ParseInt(val[2:4], 10, 8)
|
||||
s, _ := strconv.ParseInt(val[4:6], 10, 8)
|
||||
ms, _ := strconv.ParseInt(val[7:10], 10, 16)
|
||||
t := time.Date(0, 0, 0, int(h), int(m), int(s), int(ms), time.UTC)
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
// findAltitude returns the altitude from a GGA sentence:
|
||||
// findAltitude returns the altitude from an NMEA sentence:
|
||||
// $--GGA,,,,,,,,,25.8,,,,,*63
|
||||
func findAltitude(ggaFields []string) int32 {
|
||||
if len(ggaFields) > 8 && len(ggaFields[9]) > 0 {
|
||||
var v, _ = strconv.ParseFloat(ggaFields[9], 32)
|
||||
func findAltitude(val string) int32 {
|
||||
if len(val) > 0 {
|
||||
var v, _ = strconv.ParseFloat(val, 32)
|
||||
return int32(v)
|
||||
}
|
||||
return -99999
|
||||
}
|
||||
|
||||
// findLatitude returns the Latitude from a GGA sentence:
|
||||
// findLatitude returns the Latitude from an NMEA sentence:
|
||||
// $--GGA,,ddmm.mmmmm,x,,,,,,,,,,,*hh
|
||||
func findLatitude(ggaFields []string) float32 {
|
||||
if len(ggaFields) > 2 && len(ggaFields[2]) > 8 {
|
||||
var dd = ggaFields[2][0:2]
|
||||
var mm = ggaFields[2][2:]
|
||||
func findLatitude(val, hemi string) float32 {
|
||||
if len(val) > 8 {
|
||||
var dd = val[0:2]
|
||||
var mm = val[2:]
|
||||
var d, _ = strconv.ParseFloat(dd, 32)
|
||||
var m, _ = strconv.ParseFloat(mm, 32)
|
||||
var v = float32(d + (m / 60))
|
||||
if ggaFields[3] == "S" {
|
||||
if hemi == "S" {
|
||||
v *= -1
|
||||
}
|
||||
return v
|
||||
@@ -94,16 +133,16 @@ func findLatitude(ggaFields []string) float32 {
|
||||
return 0.0
|
||||
}
|
||||
|
||||
// findLatitude returns the longitude from a GGA sentence:
|
||||
// findLatitude returns the longitude from an NMEA sentence:
|
||||
// $--GGA,,,,dddmm.mmmmm,x,,,,,,,,,*hh
|
||||
func findLongitude(ggaFields []string) float32 {
|
||||
if len(ggaFields) > 4 && len(ggaFields[4]) > 8 {
|
||||
var ddd = ggaFields[4][0:3]
|
||||
var mm = ggaFields[4][3:]
|
||||
func findLongitude(val, hemi string) float32 {
|
||||
if len(val) > 8 {
|
||||
var ddd = val[0:3]
|
||||
var mm = val[3:]
|
||||
var d, _ = strconv.ParseFloat(ddd, 32)
|
||||
var m, _ = strconv.ParseFloat(mm, 32)
|
||||
var v = float32(d + (m / 60))
|
||||
if ggaFields[5] == "W" {
|
||||
if hemi == "W" {
|
||||
v *= -1
|
||||
}
|
||||
return v
|
||||
@@ -111,14 +150,32 @@ func findLongitude(ggaFields []string) float32 {
|
||||
return 0.0
|
||||
}
|
||||
|
||||
// findSatellites returns the satellites from a GGA sentence:
|
||||
// findSatellites returns the satellites from an NMEA sentence:
|
||||
// $--GGA,,,,,,,nn,,,,,,,*hh
|
||||
func findSatellites(ggaFields []string) (n int16) {
|
||||
if len(ggaFields) > 6 && len(ggaFields[7]) > 0 {
|
||||
var nn = ggaFields[7]
|
||||
func findSatellites(val string) (n int16) {
|
||||
if len(val) > 0 {
|
||||
var nn = val
|
||||
var v, _ = strconv.ParseInt(nn, 10, 32)
|
||||
n = int16(v)
|
||||
return n
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// findSpeed returns the speed from an RMC NMEA sentence.
|
||||
func findSpeed(val string) float32 {
|
||||
if len(val) > 0 {
|
||||
var v, _ = strconv.ParseFloat(val, 32)
|
||||
return float32(v)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// findHeading returns the speed from an RMC NMEA sentence.
|
||||
func findHeading(val string) float32 {
|
||||
if len(val) > 0 {
|
||||
var v, _ = strconv.ParseFloat(val, 32)
|
||||
return float32(v)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
+1
-1
@@ -13,5 +13,5 @@ const (
|
||||
)
|
||||
|
||||
const (
|
||||
bufferSize = 32
|
||||
bufferSize = 100
|
||||
)
|
||||
|
||||
+11
-11
@@ -24,25 +24,25 @@ var cfg_gnss_cmd = [...]byte{
|
||||
0x01, 0x01, 0x06, 0x08, 0x0E, 0x00, 0x00, 0x00,
|
||||
0x01, 0x01, 0xFC, 0x11}
|
||||
|
||||
func FlightMode(gpsDevice GPSDevice) (err error) {
|
||||
err = sendCommand(gpsDevice, flight_mode_cmd[:])
|
||||
func FlightMode(d Device) (err error) {
|
||||
err = sendCommand(d, flight_mode_cmd[:])
|
||||
return err
|
||||
}
|
||||
|
||||
func SetCfgGNSS(gpsDevice GPSDevice) (err error) {
|
||||
err = sendCommand(gpsDevice, cfg_gnss_cmd[:])
|
||||
func SetCfgGNSS(d Device) (err error) {
|
||||
err = sendCommand(d, cfg_gnss_cmd[:])
|
||||
return err
|
||||
}
|
||||
|
||||
func sendCommand(gpsDevice GPSDevice, command []byte) (err error) {
|
||||
gpsDevice.WriteBytes(command)
|
||||
func sendCommand(d Device, command []byte) (err error) {
|
||||
d.WriteBytes(command)
|
||||
start := time.Now()
|
||||
for time.Now().Sub(start) < 1000 {
|
||||
if gpsDevice.readNextByte() == '\n' {
|
||||
if gpsDevice.readNextByte() == 0xB5 {
|
||||
gpsDevice.readNextByte()
|
||||
if gpsDevice.readNextByte() == 0x05 {
|
||||
if gpsDevice.readNextByte() == 0x01 {
|
||||
if d.readNextByte() == '\n' {
|
||||
if d.readNextByte() == 0xB5 {
|
||||
d.readNextByte()
|
||||
if d.readNextByte() == 0x05 {
|
||||
if d.readNextByte() == 0x01 {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,13 +7,14 @@ package hd44780i2c
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
// Device wraps an I2C connection to a HD44780 I2C LCD with related data.
|
||||
type Device struct {
|
||||
bus machine.I2C
|
||||
bus drivers.I2C
|
||||
addr uint8
|
||||
width uint8
|
||||
height uint8
|
||||
@@ -41,7 +42,7 @@ type Config struct {
|
||||
// configured.
|
||||
//
|
||||
// This function only creates the Device object, it does not touch the device.
|
||||
func New(bus machine.I2C, addr uint8) Device {
|
||||
func New(bus drivers.I2C, addr uint8) Device {
|
||||
if addr == 0 {
|
||||
addr = 0x27
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package drivers
|
||||
|
||||
// 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
|
||||
Tx(addr uint16, w, r []byte) error
|
||||
}
|
||||
+10
-11
@@ -20,7 +20,7 @@ type Device struct {
|
||||
driver driver
|
||||
|
||||
x0, x1 int16 // cached address window; prevents useless/expensive
|
||||
y0, y1 int16 // syscalls to PASET, CASET, and RAMWR
|
||||
y0, y1 int16 // syscalls to PASET and CASET
|
||||
|
||||
dc machine.Pin
|
||||
cs machine.Pin
|
||||
@@ -28,6 +28,7 @@ type Device struct {
|
||||
rd machine.Pin
|
||||
}
|
||||
|
||||
// Configure prepares display for use
|
||||
func (d *Device) Configure(config Config) {
|
||||
|
||||
if config.Width == 0 {
|
||||
@@ -145,6 +146,7 @@ func (d *Device) Display() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DrawRGBBitmap copies an RGB bitmap to the internal buffer at given coordinates
|
||||
func (d *Device) DrawRGBBitmap(x, y int16, data []uint16, w, h int16) error {
|
||||
k, i := d.Size()
|
||||
if x < 0 || y < 0 || w <= 0 || h <= 0 ||
|
||||
@@ -158,7 +160,7 @@ func (d *Device) DrawRGBBitmap(x, y int16, data []uint16, w, h int16) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// FillRectangle fills a rectangle at a given coordinates with a color
|
||||
// FillRectangle fills a rectangle at given coordinates with a color
|
||||
func (d *Device) FillRectangle(x, y, width, height int16, c color.RGBA) error {
|
||||
k, i := d.Size()
|
||||
if x < 0 || y < 0 || width <= 0 || height <= 0 ||
|
||||
@@ -173,7 +175,7 @@ func (d *Device) FillRectangle(x, y, width, height int16, c color.RGBA) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DrawRectangle fills a rectangle at a given coordinates with a color
|
||||
// DrawRectangle draws a rectangle at given coordinates with a color
|
||||
func (d *Device) DrawRectangle(x, y, w, h int16, c color.RGBA) error {
|
||||
if err := d.DrawFastHLine(x, x+w-1, y, c); err != nil {
|
||||
return err
|
||||
@@ -215,6 +217,7 @@ func (d *Device) FillScreen(c color.RGBA) {
|
||||
}
|
||||
}
|
||||
|
||||
// GetRotation returns the current rotation of the device
|
||||
func (d *Device) GetRotation() Rotation {
|
||||
return d.rotation
|
||||
}
|
||||
@@ -236,7 +239,8 @@ func (d *Device) SetRotation(rotation Rotation) {
|
||||
d.rotation = rotation
|
||||
}
|
||||
|
||||
// SetScrollWindow sets an area to scroll with fixed top and bottom parts of the display
|
||||
// SetScrollArea sets an area to scroll with fixed top/bottom or left/right parts of the display
|
||||
// Rotation affects scroll direction
|
||||
func (d *Device) SetScrollArea(topFixedArea, bottomFixedArea int16) {
|
||||
d.sendCommand(VSCRDEF, []uint8{
|
||||
uint8(topFixedArea >> 8), uint8(topFixedArea),
|
||||
@@ -251,7 +255,7 @@ func (d *Device) SetScroll(line int16) {
|
||||
d.sendCommand(VSCRSADD, []uint8{uint8(line >> 8), uint8(line)})
|
||||
}
|
||||
|
||||
// SpotScroll returns the display to its normal state
|
||||
// StopScroll returns the display to its normal state
|
||||
func (d *Device) StopScroll() {
|
||||
d.sendCommand(NORON, nil)
|
||||
}
|
||||
@@ -260,10 +264,8 @@ func (d *Device) StopScroll() {
|
||||
func (d *Device) setWindow(x, y, w, h int16) {
|
||||
//x += d.columnOffset
|
||||
//y += d.rowOffset
|
||||
wr := false
|
||||
x1 := x + w - 1
|
||||
if x != d.x0 || x1 != d.x1 {
|
||||
wr = true
|
||||
d.sendCommand(CASET, []uint8{
|
||||
uint8(x >> 8), uint8(x), uint8(x1 >> 8), uint8(x1),
|
||||
})
|
||||
@@ -271,15 +273,12 @@ func (d *Device) setWindow(x, y, w, h int16) {
|
||||
}
|
||||
y1 := y + h - 1
|
||||
if y != d.y0 || y1 != d.y1 {
|
||||
wr = true
|
||||
d.sendCommand(PASET, []uint8{
|
||||
uint8(y >> 8), uint8(y), uint8(y1 >> 8), uint8(y1),
|
||||
})
|
||||
d.y0, d.y1 = y, y1
|
||||
}
|
||||
if wr {
|
||||
d.sendCommand(RAMWR, nil)
|
||||
}
|
||||
d.sendCommand(RAMWR, nil)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
|
||||
@@ -11,7 +11,7 @@ type spiDriver struct {
|
||||
bus machine.SPI
|
||||
}
|
||||
|
||||
func NewSpi(bus machine.SPI, dc, cs, rst machine.Pin) *Device {
|
||||
func NewSPI(bus machine.SPI, dc, cs, rst machine.Pin) *Device {
|
||||
return &Device{
|
||||
dc: dc,
|
||||
cs: cs,
|
||||
|
||||
@@ -11,7 +11,7 @@ type spiDriver struct {
|
||||
bus machine.SPI
|
||||
}
|
||||
|
||||
func NewSpi(bus machine.SPI, dc, cs, rst machine.Pin) *Device {
|
||||
func NewSPI(bus machine.SPI, dc, cs, rst machine.Pin) *Device {
|
||||
return &Device{
|
||||
dc: dc,
|
||||
cs: cs,
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
// Package lis2mdl implements a driver for the LIS2MDL,
|
||||
// a magnetic sensor which is included on BBC micro:bit v1.5.
|
||||
//
|
||||
// Datasheet: https://www.st.com/resource/en/datasheet/lis2mdl.pdf
|
||||
//
|
||||
package lis2mdl // import "tinygo.org/x/drivers/lis2mdl"
|
||||
|
||||
import (
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
// Device wraps an I2C connection to a LIS2MDL device.
|
||||
type Device struct {
|
||||
bus drivers.I2C
|
||||
Address uint8
|
||||
PowerMode uint8
|
||||
SystemMode uint8
|
||||
DataRate uint8
|
||||
}
|
||||
|
||||
// Configuration for LIS2MDL device.
|
||||
type Configuration struct {
|
||||
PowerMode uint8
|
||||
SystemMode uint8
|
||||
DataRate uint8
|
||||
}
|
||||
|
||||
// New creates a new LIS2MDL connection. The I2C bus must already be
|
||||
// configured.
|
||||
//
|
||||
// This function only creates the Device object, it does not touch the device.
|
||||
func New(bus drivers.I2C) Device {
|
||||
return Device{bus: bus, Address: ADDRESS}
|
||||
}
|
||||
|
||||
// Connected returns whether LIS2MDL sensor has been found.
|
||||
func (d *Device) Connected() bool {
|
||||
data := []byte{0}
|
||||
d.bus.ReadRegister(uint8(d.Address), WHO_AM_I, data)
|
||||
return data[0] == 0x40
|
||||
}
|
||||
|
||||
// Configure sets up the LIS2MDL device for communication.
|
||||
func (d *Device) Configure(cfg Configuration) {
|
||||
if cfg.PowerMode != 0 {
|
||||
d.PowerMode = cfg.PowerMode
|
||||
} else {
|
||||
d.PowerMode = POWER_NORMAL
|
||||
}
|
||||
|
||||
if cfg.DataRate != 0 {
|
||||
d.DataRate = cfg.DataRate
|
||||
} else {
|
||||
d.DataRate = DATARATE_100HZ
|
||||
}
|
||||
|
||||
if cfg.SystemMode != 0 {
|
||||
d.SystemMode = cfg.SystemMode
|
||||
} else {
|
||||
d.SystemMode = SYSTEM_CONTINUOUS
|
||||
}
|
||||
|
||||
cmd := []byte{0}
|
||||
|
||||
// reset
|
||||
cmd[0] = byte(1 << 5)
|
||||
d.bus.WriteRegister(uint8(d.Address), CFG_REG_A, cmd)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
// reboot
|
||||
cmd[0] = byte(1 << 6)
|
||||
d.bus.WriteRegister(uint8(d.Address), CFG_REG_A, cmd)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
// bdu
|
||||
cmd[0] = byte(1 << 4)
|
||||
d.bus.WriteRegister(uint8(d.Address), CFG_REG_C, cmd)
|
||||
|
||||
// Temperature compensation is on for magnetic sensor (0x80)
|
||||
cmd[0] = byte(0x80)
|
||||
d.bus.WriteRegister(uint8(d.Address), CFG_REG_A, cmd)
|
||||
|
||||
// speed
|
||||
cmd[0] = byte(0x80 | d.DataRate)
|
||||
d.bus.WriteRegister(uint8(d.Address), CFG_REG_A, cmd)
|
||||
}
|
||||
|
||||
// ReadMagneticField reads the current magnetic field from the device and returns
|
||||
// it in mG (milligauss). 1 mG = 0.1 µT (microtesla).
|
||||
func (d *Device) ReadMagneticField() (x int32, y int32, z int32) {
|
||||
// turn back on read mode, even though it is supposed to be continuous?
|
||||
cmd := []byte{0}
|
||||
cmd[0] = byte(0x80 | d.PowerMode<<4 | d.DataRate<<2 | d.SystemMode)
|
||||
d.bus.WriteRegister(uint8(d.Address), CFG_REG_A, cmd)
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
data := make([]byte, 6)
|
||||
d.bus.ReadRegister(uint8(d.Address), OUTX_L_REG, data)
|
||||
|
||||
x = int32(int16((uint16(data[0]) << 8) | uint16(data[1])))
|
||||
y = int32(int16((uint16(data[2]) << 8) | uint16(data[3])))
|
||||
z = int32(int16((uint16(data[4]) << 8) | uint16(data[5])))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ReadCompass reads the current compass heading from the device and returns
|
||||
// it in degrees. When the z axis is pointing straight to Earth and
|
||||
// the y axis is pointing to North, the heading would be zero.
|
||||
//
|
||||
// However, the heading may be off due to electronic compasses would be effected
|
||||
// by strong magnetic fields and require constant calibration.
|
||||
func (d *Device) ReadCompass() (h int32) {
|
||||
x, y, _ := d.ReadMagneticField()
|
||||
xf, yf := float64(x)*0.15, float64(y)*0.15
|
||||
|
||||
rh := (math.Atan2(yf, xf) * 180) / math.Pi
|
||||
if rh < 0 {
|
||||
rh = 360 + rh
|
||||
}
|
||||
|
||||
return int32(rh)
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package lis2mdl
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
"tinygo.org/x/drivers/tester"
|
||||
)
|
||||
|
||||
func TestDefaultI2CAddress(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := tester.NewI2CBus(c)
|
||||
dev := New(bus)
|
||||
c.Assert(dev.Address, qt.Equals, uint8(ADDRESS))
|
||||
}
|
||||
|
||||
func TestWhoAmI(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := tester.NewI2CBus(c)
|
||||
fake := tester.NewI2CDevice(c, ADDRESS)
|
||||
fake.SetupRegisters(defaultRegisters())
|
||||
bus.AddDevice(fake)
|
||||
|
||||
dev := New(bus)
|
||||
c.Assert(dev.Connected(), qt.Equals, true)
|
||||
|
||||
fake.SetupRegister(WHO_AM_I, 0x99)
|
||||
c.Assert(dev.Connected(), qt.Equals, false)
|
||||
}
|
||||
|
||||
// defaultRegisters returns the default values for all of the device's registers.
|
||||
// see table 22 on page 27 of the datasheet.
|
||||
func defaultRegisters() []uint8 {
|
||||
return []uint8{
|
||||
OFFSET_X_REG_L: 0,
|
||||
OFFSET_X_REG_H: 0,
|
||||
OFFSET_Y_REG_L: 0,
|
||||
OFFSET_Y_REG_H: 0,
|
||||
OFFSET_Z_REG_L: 0,
|
||||
OFFSET_Z_REG_H: 0,
|
||||
WHO_AM_I: 0x40,
|
||||
CFG_REG_A: 0x03,
|
||||
CFG_REG_B: 0,
|
||||
CFG_REG_C: 0,
|
||||
INT_CRTL_REG: 0xE0,
|
||||
INT_SOURCE_REG: 0,
|
||||
INT_THS_L_REG: 0,
|
||||
INT_THS_H_REG: 0,
|
||||
STATUS_REG: 0,
|
||||
OUTX_L_REG: 0,
|
||||
OUTX_H_REG: 0,
|
||||
OUTY_L_REG: 0,
|
||||
OUTY_H_REG: 0,
|
||||
OUTZ_L_REG: 0,
|
||||
OUTZ_H_REG: 0,
|
||||
TEMP_OUT_L_REG: 0,
|
||||
TEMP_OUT_H_REG: 0,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package lis2mdl
|
||||
|
||||
const (
|
||||
// Constants/addresses used for I2C.
|
||||
ADDRESS = 0x1E
|
||||
|
||||
// magnetic sensor registers.
|
||||
OFFSET_X_REG_L = 0x45
|
||||
OFFSET_X_REG_H = 0x46
|
||||
OFFSET_Y_REG_L = 0x47
|
||||
OFFSET_Y_REG_H = 0x48
|
||||
OFFSET_Z_REG_L = 0x49
|
||||
OFFSET_Z_REG_H = 0x4A
|
||||
WHO_AM_I = 0x4F
|
||||
CFG_REG_A = 0x60
|
||||
CFG_REG_B = 0x61
|
||||
CFG_REG_C = 0x62
|
||||
INT_CRTL_REG = 0x63
|
||||
INT_SOURCE_REG = 0x64
|
||||
INT_THS_L_REG = 0x65
|
||||
INT_THS_H_REG = 0x66
|
||||
STATUS_REG = 0x67
|
||||
OUTX_L_REG = 0x68
|
||||
OUTX_H_REG = 0x69
|
||||
OUTY_L_REG = 0x6A
|
||||
OUTY_H_REG = 0x6B
|
||||
OUTZ_L_REG = 0x6C
|
||||
OUTZ_H_REG = 0x6D
|
||||
TEMP_OUT_L_REG = 0x6E
|
||||
TEMP_OUT_H_REG = 0x6F
|
||||
|
||||
// magnetic sensor power mode.
|
||||
POWER_NORMAL = 0x00 // default
|
||||
POWER_LOW = 0x01
|
||||
|
||||
// magnetic sensor operate mode.
|
||||
SYSTEM_CONTINUOUS = 0x00 // default
|
||||
SYSTEM_SINGLE = 0x01
|
||||
|
||||
// magnetic sensor data rate
|
||||
DATARATE_10HZ = 0x00 // default
|
||||
DATARATE_20HZ = 0x01
|
||||
DATARATE_50HZ = 0x02
|
||||
DATARATE_100HZ = 0x03
|
||||
)
|
||||
+3
-5
@@ -4,13 +4,11 @@
|
||||
//
|
||||
package lis3dh // import "tinygo.org/x/drivers/lis3dh"
|
||||
|
||||
import (
|
||||
"machine"
|
||||
)
|
||||
import "tinygo.org/x/drivers"
|
||||
|
||||
// Device wraps an I2C connection to a LIS3DH device.
|
||||
type Device struct {
|
||||
bus machine.I2C
|
||||
bus drivers.I2C
|
||||
Address uint16
|
||||
r Range
|
||||
}
|
||||
@@ -18,7 +16,7 @@ type Device struct {
|
||||
// New creates a new LIS3DH connection. The I2C bus must already be configured.
|
||||
//
|
||||
// This function only creates the Device object, it does not touch the device.
|
||||
func New(bus machine.I2C) Device {
|
||||
func New(bus drivers.I2C) Device {
|
||||
return Device{bus: bus, Address: Address0}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,13 +6,14 @@
|
||||
package lsm303agr // import "tinygo.org/x/drivers/lsm303agr"
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"math"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
// Device wraps an I2C connection to a LSM303AGR device.
|
||||
type Device struct {
|
||||
bus machine.I2C
|
||||
bus drivers.I2C
|
||||
AccelAddress uint8
|
||||
MagAddress uint8
|
||||
AccelPowerMode uint8
|
||||
@@ -37,7 +38,7 @@ type Configuration struct {
|
||||
// configured.
|
||||
//
|
||||
// This function only creates the Device object, it does not touch the device.
|
||||
func New(bus machine.I2C) Device {
|
||||
func New(bus drivers.I2C) Device {
|
||||
return Device{bus: bus, AccelAddress: ACCEL_ADDRESS, MagAddress: MAG_ADDRESS}
|
||||
}
|
||||
|
||||
|
||||
+3
-5
@@ -5,9 +5,7 @@
|
||||
//
|
||||
package lsm6ds3 // import "tinygo.org/x/drivers/lsm6ds3"
|
||||
|
||||
import (
|
||||
"machine"
|
||||
)
|
||||
import "tinygo.org/x/drivers"
|
||||
|
||||
type AccelRange uint8
|
||||
type AccelSampleRate uint8
|
||||
@@ -18,7 +16,7 @@ type GyroSampleRate uint8
|
||||
|
||||
// Device wraps an I2C connection to a LSM6DS3 device.
|
||||
type Device struct {
|
||||
bus machine.I2C
|
||||
bus drivers.I2C
|
||||
Address uint16
|
||||
accelRange AccelRange
|
||||
accelSampleRate AccelSampleRate
|
||||
@@ -44,7 +42,7 @@ type Configuration struct {
|
||||
// configured.
|
||||
//
|
||||
// This function only creates the Device object, it does not touch the device.
|
||||
func New(bus machine.I2C) Device {
|
||||
func New(bus drivers.I2C) Device {
|
||||
return Device{bus: bus, Address: Address}
|
||||
}
|
||||
|
||||
|
||||
+3
-5
@@ -5,13 +5,11 @@
|
||||
//
|
||||
package mag3110 // import "tinygo.org/x/drivers/mag3110"
|
||||
|
||||
import (
|
||||
"machine"
|
||||
)
|
||||
import "tinygo.org/x/drivers"
|
||||
|
||||
// Device wraps an I2C connection to a MAG3110 device.
|
||||
type Device struct {
|
||||
bus machine.I2C
|
||||
bus drivers.I2C
|
||||
Address uint16
|
||||
}
|
||||
|
||||
@@ -19,7 +17,7 @@ type Device struct {
|
||||
// configured.
|
||||
//
|
||||
// This function only creates the Device object, it does not touch the device.
|
||||
func New(bus machine.I2C) Device {
|
||||
func New(bus drivers.I2C) Device {
|
||||
return Device{bus, Address}
|
||||
}
|
||||
|
||||
|
||||
+3
-5
@@ -6,13 +6,11 @@
|
||||
//
|
||||
package mma8653 // import "tinygo.org/x/drivers/mma8653"
|
||||
|
||||
import (
|
||||
"machine"
|
||||
)
|
||||
import "tinygo.org/x/drivers"
|
||||
|
||||
// Device wraps an I2C connection to a MMA8653 device.
|
||||
type Device struct {
|
||||
bus machine.I2C
|
||||
bus drivers.I2C
|
||||
Address uint16
|
||||
sensitivity Sensitivity
|
||||
}
|
||||
@@ -21,7 +19,7 @@ type Device struct {
|
||||
// configured.
|
||||
//
|
||||
// This function only creates the Device object, it does not touch the device.
|
||||
func New(bus machine.I2C) Device {
|
||||
func New(bus drivers.I2C) Device {
|
||||
return Device{bus, Address, Sensitivity2G}
|
||||
}
|
||||
|
||||
|
||||
+3
-5
@@ -7,13 +7,11 @@
|
||||
//
|
||||
package mpu6050 // import "tinygo.org/x/drivers/mpu6050"
|
||||
|
||||
import (
|
||||
"machine"
|
||||
)
|
||||
import "tinygo.org/x/drivers"
|
||||
|
||||
// Device wraps an I2C connection to a MPU6050 device.
|
||||
type Device struct {
|
||||
bus machine.I2C
|
||||
bus drivers.I2C
|
||||
Address uint16
|
||||
}
|
||||
|
||||
@@ -21,7 +19,7 @@ type Device struct {
|
||||
// configured.
|
||||
//
|
||||
// This function only creates the Device object, it does not touch the device.
|
||||
func New(bus machine.I2C) Device {
|
||||
func New(bus drivers.I2C) Device {
|
||||
return Device{bus, Address}
|
||||
}
|
||||
|
||||
|
||||
+4
-3
@@ -7,13 +7,14 @@
|
||||
package sht3x // import "tinygo.org/x/drivers/sht3x"
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
// Device wraps an I2C connection to a SHT31 device.
|
||||
type Device struct {
|
||||
bus machine.I2C
|
||||
bus drivers.I2C
|
||||
Address uint16
|
||||
}
|
||||
|
||||
@@ -22,7 +23,7 @@ type Device struct {
|
||||
//
|
||||
// This function only creates the Device object, it does not initialize the device.
|
||||
// You must call Configure() first in order to use the device itself.
|
||||
func New(bus machine.I2C) Device {
|
||||
func New(bus drivers.I2C) Device {
|
||||
return Device{
|
||||
bus: bus,
|
||||
Address: AddressA,
|
||||
|
||||
+4
-2
@@ -9,6 +9,8 @@ import (
|
||||
"image/color"
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
// Device wraps an SPI connection.
|
||||
@@ -30,7 +32,7 @@ type Config struct {
|
||||
}
|
||||
|
||||
type I2CBus struct {
|
||||
wire machine.I2C
|
||||
wire drivers.I2C
|
||||
Address uint16
|
||||
}
|
||||
|
||||
@@ -50,7 +52,7 @@ type Buser interface {
|
||||
type VccMode uint8
|
||||
|
||||
// NewI2C creates a new SSD1306 connection. The I2C wire must already be configured.
|
||||
func NewI2C(bus machine.I2C) Device {
|
||||
func NewI2C(bus drivers.I2C) Device {
|
||||
return Device{
|
||||
bus: &I2CBus{
|
||||
wire: bus,
|
||||
|
||||
@@ -33,7 +33,9 @@ const (
|
||||
RDID3 = 0xDC
|
||||
RDID4 = 0xDD
|
||||
FRMCTR1 = 0xB1
|
||||
RGBCTRL = 0xB1
|
||||
FRMCTR2 = 0xB2
|
||||
PORCTRL = 0xB2
|
||||
FRMCTR3 = 0xB3
|
||||
INVCTR = 0xB4
|
||||
DISSET5 = 0xB6
|
||||
@@ -43,12 +45,49 @@ const (
|
||||
PWCTR4 = 0xC3
|
||||
PWCTR5 = 0xC4
|
||||
VMCTR1 = 0xC5
|
||||
FRCTRL2 = 0xC6
|
||||
PWCTR6 = 0xFC
|
||||
GMCTRP1 = 0xE0
|
||||
GMCTRN1 = 0xE1
|
||||
GSCAN = 0x45
|
||||
|
||||
NO_ROTATION Rotation = 0
|
||||
ROTATION_90 Rotation = 1 // 90 degrees clock-wise rotation
|
||||
ROTATION_180 Rotation = 2
|
||||
ROTATION_270 Rotation = 3
|
||||
|
||||
// Allowable frame rate codes for FRCTRL2 (Identifier is in Hz)
|
||||
FRAMERATE_111 FrameRate = 0x01
|
||||
FRAMERATE_105 FrameRate = 0x02
|
||||
FRAMERATE_99 FrameRate = 0x03
|
||||
FRAMERATE_94 FrameRate = 0x04
|
||||
FRAMERATE_90 FrameRate = 0x05
|
||||
FRAMERATE_86 FrameRate = 0x06
|
||||
FRAMERATE_82 FrameRate = 0x07
|
||||
FRAMERATE_78 FrameRate = 0x08
|
||||
FRAMERATE_75 FrameRate = 0x09
|
||||
FRAMERATE_72 FrameRate = 0x0A
|
||||
FRAMERATE_69 FrameRate = 0x0B
|
||||
FRAMERATE_67 FrameRate = 0x0C
|
||||
FRAMERATE_64 FrameRate = 0x0D
|
||||
FRAMERATE_62 FrameRate = 0x0E
|
||||
FRAMERATE_60 FrameRate = 0x0F // 60 is default
|
||||
FRAMERATE_58 FrameRate = 0x10
|
||||
FRAMERATE_57 FrameRate = 0x11
|
||||
FRAMERATE_55 FrameRate = 0x12
|
||||
FRAMERATE_53 FrameRate = 0x13
|
||||
FRAMERATE_52 FrameRate = 0x14
|
||||
FRAMERATE_50 FrameRate = 0x15
|
||||
FRAMERATE_49 FrameRate = 0x16
|
||||
FRAMERATE_48 FrameRate = 0x17
|
||||
FRAMERATE_46 FrameRate = 0x18
|
||||
FRAMERATE_45 FrameRate = 0x19
|
||||
FRAMERATE_44 FrameRate = 0x1A
|
||||
FRAMERATE_43 FrameRate = 0x1B
|
||||
FRAMERATE_42 FrameRate = 0x1C
|
||||
FRAMERATE_41 FrameRate = 0x1D
|
||||
FRAMERATE_40 FrameRate = 0x1E
|
||||
FRAMERATE_39 FrameRate = 0x1F
|
||||
|
||||
MAX_VSYNC_SCANLINES = 254
|
||||
)
|
||||
|
||||
+155
-24
@@ -1,12 +1,14 @@
|
||||
// Package st7789 implements a driver for the ST7789 TFT displays, it comes in various screen sizes.
|
||||
//
|
||||
// Datasheet: https://cdn-shop.adafruit.com/product-files/3787/3787_tft_QT154H2201__________20190228182902.pdf
|
||||
// Datasheets: https://cdn-shop.adafruit.com/product-files/3787/3787_tft_QT154H2201__________20190228182902.pdf
|
||||
// http://www.newhavendisplay.com/appnotes/datasheets/LCDs/ST7789V.pdf
|
||||
//
|
||||
package st7789 // import "tinygo.org/x/drivers/st7789"
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"machine"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"errors"
|
||||
@@ -14,11 +16,14 @@ import (
|
||||
|
||||
type Rotation uint8
|
||||
|
||||
type FrameRate uint8
|
||||
|
||||
// Device wraps an SPI connection.
|
||||
type Device struct {
|
||||
bus machine.SPI
|
||||
dcPin machine.Pin
|
||||
resetPin machine.Pin
|
||||
csPin machine.Pin
|
||||
blPin machine.Pin
|
||||
width int16
|
||||
height int16
|
||||
@@ -27,8 +32,10 @@ type Device struct {
|
||||
columnOffset int16
|
||||
rowOffset int16
|
||||
rotation Rotation
|
||||
frameRate FrameRate
|
||||
batchLength int32
|
||||
isBGR bool
|
||||
vSyncLines int16
|
||||
}
|
||||
|
||||
// Config is the configuration for the display
|
||||
@@ -38,17 +45,21 @@ type Config struct {
|
||||
Rotation Rotation
|
||||
RowOffset int16
|
||||
ColumnOffset int16
|
||||
FrameRate FrameRate
|
||||
VSyncLines int16
|
||||
}
|
||||
|
||||
// New creates a new ST7789 connection. The SPI wire must already be configured.
|
||||
func New(bus machine.SPI, resetPin, dcPin, blPin machine.Pin) Device {
|
||||
func New(bus machine.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})
|
||||
blPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
return Device{
|
||||
bus: bus,
|
||||
dcPin: dcPin,
|
||||
resetPin: resetPin,
|
||||
csPin: csPin,
|
||||
blPin: blPin,
|
||||
}
|
||||
}
|
||||
@@ -65,42 +76,149 @@ func (d *Device) Configure(cfg Config) {
|
||||
} else {
|
||||
d.height = 240
|
||||
}
|
||||
|
||||
d.rotation = cfg.Rotation
|
||||
d.rowOffsetCfg = cfg.RowOffset
|
||||
d.columnOffsetCfg = cfg.ColumnOffset
|
||||
|
||||
if cfg.FrameRate != 0 {
|
||||
d.frameRate = cfg.FrameRate
|
||||
} else {
|
||||
d.frameRate = FRAMERATE_60
|
||||
}
|
||||
|
||||
if cfg.VSyncLines >= 2 && cfg.VSyncLines <= 254 {
|
||||
d.vSyncLines = cfg.VSyncLines
|
||||
} else {
|
||||
d.vSyncLines = 16
|
||||
}
|
||||
|
||||
d.batchLength = int32(d.width)
|
||||
if d.height > d.width {
|
||||
d.batchLength = int32(d.height)
|
||||
}
|
||||
d.batchLength += d.batchLength & 1
|
||||
|
||||
// reset the device
|
||||
// Reset the device
|
||||
d.resetPin.High()
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
d.resetPin.Low()
|
||||
time.Sleep(20 * time.Millisecond)
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
d.resetPin.High()
|
||||
time.Sleep(150 * time.Millisecond)
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
// Common initialization
|
||||
d.Command(SWRESET)
|
||||
time.Sleep(150 * time.Millisecond)
|
||||
d.Command(SLPOUT)
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
d.Command(COLMOD)
|
||||
d.Data(0x55)
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
d.Command(SWRESET) // Soft reset
|
||||
time.Sleep(150 * time.Millisecond) //
|
||||
|
||||
d.SetRotation(d.rotation)
|
||||
d.InvertColors(true)
|
||||
d.Command(SLPOUT) // Exit sleep mode
|
||||
time.Sleep(500 * time.Millisecond) //
|
||||
|
||||
d.Command(NORON)
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
d.Command(DISPON)
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
// Memory initialization
|
||||
d.Command(COLMOD) // Set color mode
|
||||
d.Data(0x55) // 16-bit color
|
||||
time.Sleep(10 * time.Millisecond) //
|
||||
|
||||
d.blPin.High()
|
||||
d.SetRotation(d.rotation) // Memory orientation
|
||||
|
||||
d.setWindow(0, 0, d.width, d.height) // Full draw window
|
||||
d.FillScreen(color.RGBA{0, 0, 0, 255}) // Clear screen
|
||||
|
||||
// Framerate
|
||||
d.Command(FRCTRL2) // Frame rate for normal mode
|
||||
d.Data(uint8(d.frameRate)) // Default is 60Hz
|
||||
|
||||
// Frame vertical sync and "porch"
|
||||
//
|
||||
// Front and back porch controls vertical scanline sync time before and after
|
||||
// a frame, where memory can be safely written without tearing.
|
||||
//
|
||||
fp := uint8(d.vSyncLines / 2) // Split the desired pause half and half
|
||||
bp := uint8(d.vSyncLines - int16(fp)) // between front and back porch.
|
||||
|
||||
d.Command(PORCTRL)
|
||||
d.Data(bp) // Back porch 5bit (0x7F max 0x08 default)
|
||||
d.Data(fp) // Front porch 5bit (0x7F max 0x08 default)
|
||||
d.Data(0x00) // Seprarate porch (TODO: what is this?)
|
||||
d.Data(0x22) // Idle mode porch (4bit-back 4bit-front 0x22 default)
|
||||
d.Data(0x22) // Partial mode porch (4bit-back 4bit-front 0x22 default)
|
||||
|
||||
// Ready to display
|
||||
d.Command(INVON) // Inversion ON
|
||||
time.Sleep(10 * time.Millisecond) //
|
||||
|
||||
d.Command(NORON) // Normal mode ON
|
||||
time.Sleep(10 * time.Millisecond) //
|
||||
|
||||
d.Command(DISPON) // Screen ON
|
||||
time.Sleep(10 * time.Millisecond) //
|
||||
|
||||
d.blPin.High() // Backlight ON
|
||||
}
|
||||
|
||||
// Sync waits for the display to hit the next VSYNC pause
|
||||
func (d *Device) Sync() {
|
||||
d.SyncToScanLine(0)
|
||||
}
|
||||
|
||||
// SyncToScanLine waits for the display to hit a specific scanline
|
||||
//
|
||||
// A scanline value of 0 will forward to the beginning of the next VSYNC,
|
||||
// even if the display is currently in a VSYNC pause.
|
||||
//
|
||||
// Syncline values appear to increment once for every two vertical
|
||||
// lines on the display.
|
||||
//
|
||||
// NOTE: Use GetHighestScanLine and GetLowestScanLine to obtain the highest
|
||||
// and lowest useful values. Values are affected by front and back porch
|
||||
// vsync settings (derived from VSyncLines configuration option).
|
||||
//
|
||||
func (d *Device) SyncToScanLine(scanline uint16) {
|
||||
scan := d.GetScanLine()
|
||||
|
||||
// Sometimes GetScanLine returns erroneous 0 on first call after draw, so double check
|
||||
if scan == 0 {
|
||||
scan = d.GetScanLine()
|
||||
}
|
||||
|
||||
if scanline == 0 {
|
||||
// we dont know where we are in an ongoing vsync so go around
|
||||
for scan < 1 {
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
scan = d.GetScanLine()
|
||||
}
|
||||
for scan > 0 {
|
||||
scan = d.GetScanLine()
|
||||
}
|
||||
} else {
|
||||
// go around unless we're very close to the target
|
||||
for scan > scanline+4 {
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
scan = d.GetScanLine()
|
||||
}
|
||||
for scan < scanline {
|
||||
scan = d.GetScanLine()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetScanLine reads the current scanline value from the display
|
||||
func (d *Device) GetScanLine() uint16 {
|
||||
data := []uint8{0x00, 0x00}
|
||||
d.Rx(GSCAN, data)
|
||||
return uint16(data[0])<<8 + uint16(data[1])
|
||||
}
|
||||
|
||||
// GetHighestScanLine calculates the last scanline id in the frame before VSYNC pause
|
||||
func (d *Device) GetHighestScanLine() uint16 {
|
||||
// Last scanline id appears to be backporch/2 + 320/2
|
||||
return uint16(math.Ceil(float64(d.vSyncLines)/2)/2) + 160
|
||||
}
|
||||
|
||||
// GetLowestScanLine calculate the first scanline id to appear after VSYNC pause
|
||||
func (d *Device) GetLowestScanLine() uint16 {
|
||||
// First scanline id appears to be backporch/2 + 1
|
||||
return uint16(math.Ceil(float64(d.vSyncLines)/2)/2) + 1
|
||||
}
|
||||
|
||||
// Display does nothing, there's no buffer as it might be too big for some boards
|
||||
@@ -123,7 +241,7 @@ func (d *Device) setWindow(x, y, w, h int16) {
|
||||
x += d.columnOffset
|
||||
y += d.rowOffset
|
||||
d.Tx([]uint8{CASET}, true)
|
||||
d.Tx([]uint8{uint8(x << 8), uint8(x), uint8((x + w - 1) >> 8), uint8(x + w - 1)}, false)
|
||||
d.Tx([]uint8{uint8(x >> 8), uint8(x), uint8((x + w - 1) >> 8), uint8(x + w - 1)}, false)
|
||||
d.Tx([]uint8{RASET}, true)
|
||||
d.Tx([]uint8{uint8(y >> 8), uint8(y), uint8((y + h - 1) >> 8), uint8(y + h - 1)}, false)
|
||||
d.Command(RAMWR)
|
||||
@@ -158,6 +276,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
|
||||
func (d *Device) FillRectangleWithBuffer(x, y, width, height int16, buffer []color.RGBA) error {
|
||||
i, j := d.Size()
|
||||
@@ -248,7 +367,6 @@ func (d *Device) SetRotation(rotation Rotation) {
|
||||
}
|
||||
d.Command(MADCTL)
|
||||
d.Data(madctl)
|
||||
|
||||
}
|
||||
|
||||
// Command sends a command to the display
|
||||
@@ -265,11 +383,24 @@ func (d *Device) Data(data uint8) {
|
||||
func (d *Device) Tx(data []byte, isCommand bool) {
|
||||
if isCommand {
|
||||
d.dcPin.Low()
|
||||
d.bus.Tx(data, nil)
|
||||
} else {
|
||||
d.dcPin.High()
|
||||
d.bus.Tx(data, nil)
|
||||
}
|
||||
d.csPin.Low()
|
||||
d.bus.Tx(data, nil)
|
||||
d.csPin.High()
|
||||
}
|
||||
|
||||
// Rx reads data from the display
|
||||
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 read_bytes {
|
||||
read_bytes[i], _ = d.bus.Transfer(0xFF)
|
||||
}
|
||||
d.csPin.High()
|
||||
}
|
||||
|
||||
// Size returns the current size of the display.
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
package tester
|
||||
|
||||
// MaxRegisters is the maximum number of registers supported for a Device.
|
||||
const MaxRegisters = 200
|
||||
|
||||
// I2CDevice represents a mock I2C device on a mock I2C bus.
|
||||
type I2CDevice struct {
|
||||
c Failer
|
||||
// addr is the i2c device address.
|
||||
addr uint8
|
||||
// Registers holds the device registers. It can be inspected
|
||||
// or changed as desired for testing.
|
||||
registers [MaxRegisters]uint8
|
||||
// If Err is non-nil, it will be returned as the error from the
|
||||
// I2C methods.
|
||||
Err error
|
||||
}
|
||||
|
||||
// NewI2CDevice returns a new mock I2C device.
|
||||
func NewI2CDevice(c Failer, addr uint8) *I2CDevice {
|
||||
return &I2CDevice{
|
||||
c: c,
|
||||
addr: addr,
|
||||
}
|
||||
}
|
||||
|
||||
// Addr returns the Device address.
|
||||
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:])
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteRegister implements I2C.WriteRegister.
|
||||
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)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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) {
|
||||
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) {
|
||||
d.c.Fatalf("register read/write [%#x, %#x] end out of range", r, int(r)+len(buf))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package tester
|
||||
|
||||
// I2CBus implements the I2C interface in memory for testing.
|
||||
type I2CBus struct {
|
||||
c Failer
|
||||
devices []*I2CDevice
|
||||
}
|
||||
|
||||
// NewI2CBus returns an I2CBus mock I2C instance that uses c to flag errors
|
||||
// if they happen. After creating a I2C instance, add devices
|
||||
// to it with addDevice before using NewI2CBus interface.
|
||||
func NewI2CBus(c Failer) *I2CBus {
|
||||
return &I2CBus{
|
||||
c: c,
|
||||
}
|
||||
}
|
||||
|
||||
// AddDevice adds a new mock device to the mock I2C bus.
|
||||
func (bus *I2CBus) AddDevice(d *I2CDevice) {
|
||||
bus.devices = append(bus.devices, d)
|
||||
}
|
||||
|
||||
// ReadRegister implements I2C.ReadRegister.
|
||||
func (bus *I2CBus) ReadRegister(addr uint8, r uint8, buf []byte) error {
|
||||
return bus.FindDevice(addr).ReadRegister(r, buf)
|
||||
}
|
||||
|
||||
// WriteRegister implements I2C.WriteRegister.
|
||||
func (bus *I2CBus) WriteRegister(addr uint8, r uint8, buf []byte) error {
|
||||
return bus.FindDevice(addr).WriteRegister(r, buf)
|
||||
}
|
||||
|
||||
// Tx implements I2C.Tx.
|
||||
func (bus *I2CBus) Tx(addr uint16, w, r []byte) error {
|
||||
// TODO: implement this
|
||||
return nil
|
||||
}
|
||||
|
||||
// FindDevice returns the device with the given address.
|
||||
func (bus *I2CBus) FindDevice(addr uint8) *I2CDevice {
|
||||
for _, dev := range bus.devices {
|
||||
if dev.Addr() == addr {
|
||||
return dev
|
||||
}
|
||||
}
|
||||
bus.c.Fatalf("invalid device addr %#x passed to i2c bus", addr)
|
||||
panic("unreachable")
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// Package tester contains mock structs to make it easier to test I2C devices.
|
||||
//
|
||||
// TODO: info on how to use this.
|
||||
//
|
||||
package tester // import "tinygo.org/x/drivers/tester"
|
||||
|
||||
// Failer is used by the I2CDevice type to abort when it's used in
|
||||
// unexpected ways, such as reading an out-of-range register.
|
||||
type Failer interface {
|
||||
// Fatalf prints the Printf-formatted message and exits the current
|
||||
// goroutine.
|
||||
Fatalf(f string, a ...interface{})
|
||||
}
|
||||
+3
-5
@@ -4,13 +4,11 @@
|
||||
|
||||
package tmp102 // import "tinygo.org/x/drivers/tmp102"
|
||||
|
||||
import (
|
||||
"machine"
|
||||
)
|
||||
import "tinygo.org/x/drivers"
|
||||
|
||||
// Device holds the already configured I2C bus and the address of the sensor.
|
||||
type Device struct {
|
||||
bus machine.I2C
|
||||
bus drivers.I2C
|
||||
address uint8
|
||||
}
|
||||
|
||||
@@ -20,7 +18,7 @@ type Config struct {
|
||||
}
|
||||
|
||||
// New creates a new TMP102 connection. The I2C bus must already be configured.
|
||||
func New(bus machine.I2C) Device {
|
||||
func New(bus drivers.I2C) Device {
|
||||
return Device{
|
||||
bus: bus,
|
||||
}
|
||||
|
||||
@@ -11,12 +11,12 @@ package veml6070 // import "tinygo.org/x/drivers/veml6070"
|
||||
import (
|
||||
"time"
|
||||
|
||||
"machine"
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
// Device wraps an I2C connection to a VEML6070 device.
|
||||
type Device struct {
|
||||
bus machine.I2C
|
||||
bus drivers.I2C
|
||||
AddressLow uint16
|
||||
AddressHigh uint16
|
||||
RSET uint32
|
||||
@@ -28,7 +28,7 @@ type Device struct {
|
||||
//
|
||||
// This function only creates the Device object, it does not initialize the device.
|
||||
// You must call Configure() first in order to use the device itself.
|
||||
func New(bus machine.I2C) Device {
|
||||
func New(bus drivers.I2C) Device {
|
||||
return Device{
|
||||
bus: bus,
|
||||
AddressLow: ADDR_L,
|
||||
@@ -119,7 +119,7 @@ func (d *Device) enable() error {
|
||||
|
||||
func (d *Device) readData(address uint16) (byte, error) {
|
||||
data := []byte{0}
|
||||
err := machine.I2C0.Tx(address, []byte{}, data)
|
||||
err := d.bus.Tx(address, []byte{}, data)
|
||||
return data[0], err
|
||||
}
|
||||
|
||||
|
||||
+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.13.0"
|
||||
const Version = "0.14.0"
|
||||
|
||||
+4
-3
@@ -10,8 +10,9 @@
|
||||
package vl53l1x // import "tinygo.org/x/drivers/vl53l1x"
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
type DistanceMode uint8
|
||||
@@ -35,7 +36,7 @@ type resultBuffer struct {
|
||||
|
||||
// Device wraps an I2C connection to a VL53L1X device.
|
||||
type Device struct {
|
||||
bus machine.I2C
|
||||
bus drivers.I2C
|
||||
Address uint16
|
||||
mode DistanceMode
|
||||
timeout uint32
|
||||
@@ -52,7 +53,7 @@ type Device struct {
|
||||
// configured.
|
||||
//
|
||||
// This function only creates the Device object, it does not touch the device.
|
||||
func New(bus machine.I2C) Device {
|
||||
func New(bus drivers.I2C) Device {
|
||||
return Device{
|
||||
bus: bus,
|
||||
Address: Address,
|
||||
|
||||
@@ -0,0 +1,355 @@
|
||||
// Package epd4in2 implements a driver for Waveshare 4.2in black and white e-paper device.
|
||||
//
|
||||
// Derived from:
|
||||
// https://github.com/tinygo-org/drivers/tree/master/waveshare-epd
|
||||
// https://github.com/waveshare/e-Paper/blob/master/Arduino/epd4in2/epd4in2.cpp
|
||||
//
|
||||
// Datasheet: https://www.waveshare.com/wiki/4.2inch_e-Paper_Module
|
||||
//
|
||||
package epd4in2
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"machine"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Width int16 // Width is the display resolution
|
||||
Height int16
|
||||
LogicalWidth int16 // LogicalWidth must be a multiple of 8 and same size or bigger than Width
|
||||
Rotation Rotation // Rotation is clock-wise
|
||||
}
|
||||
|
||||
type Device struct {
|
||||
bus machine.SPI
|
||||
cs machine.Pin
|
||||
dc machine.Pin
|
||||
rst machine.Pin
|
||||
busy machine.Pin
|
||||
logicalWidth int16
|
||||
width int16
|
||||
height int16
|
||||
buffer []uint8
|
||||
bufferLength uint32
|
||||
rotation Rotation
|
||||
}
|
||||
|
||||
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 {
|
||||
csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
rstPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
busyPin.Configure(machine.PinConfig{Mode: machine.PinInput})
|
||||
return Device{
|
||||
bus: bus,
|
||||
cs: csPin,
|
||||
dc: dcPin,
|
||||
rst: rstPin,
|
||||
busy: busyPin,
|
||||
}
|
||||
}
|
||||
|
||||
// Configure sets up the device.
|
||||
func (d *Device) Configure(cfg Config) {
|
||||
if cfg.LogicalWidth != 0 {
|
||||
d.logicalWidth = cfg.LogicalWidth
|
||||
} else {
|
||||
d.logicalWidth = EPD_WIDTH
|
||||
}
|
||||
if cfg.Width != 0 {
|
||||
d.width = cfg.Width
|
||||
} else {
|
||||
d.width = EPD_WIDTH
|
||||
}
|
||||
if cfg.Height != 0 {
|
||||
d.height = cfg.Height
|
||||
} else {
|
||||
d.height = EPD_HEIGHT
|
||||
}
|
||||
d.rotation = cfg.Rotation
|
||||
d.bufferLength = (uint32(d.logicalWidth) * uint32(d.height)) / 8
|
||||
d.buffer = make([]uint8, d.bufferLength)
|
||||
for i := uint32(0); i < d.bufferLength; i++ {
|
||||
d.buffer[i] = 0xFF
|
||||
}
|
||||
|
||||
d.cs.Low()
|
||||
d.dc.Low()
|
||||
d.rst.Low()
|
||||
|
||||
d.Reset()
|
||||
d.SendCommand(POWER_SETTING)
|
||||
d.SendData(0x03) // VDS_EN, VDG_EN
|
||||
d.SendData(0x00) // VCOM_HV, VGHL_LV[1], VGHL_LV[0]
|
||||
d.SendData(0x2b) // VDH
|
||||
d.SendData(0x2b) // VDL
|
||||
d.SendData(0xff) // VDHR
|
||||
d.SendCommand(BOOSTER_SOFT_START)
|
||||
d.SendData(0x17)
|
||||
d.SendData(0x17)
|
||||
d.SendData(0x17) //07 0f 17 1f 27 2F 37 2f
|
||||
d.SendCommand(POWER_ON)
|
||||
d.WaitUntilIdle()
|
||||
d.SendCommand(PANEL_SETTING)
|
||||
d.SendData(0xbf) // KW-BF KWR-AF BWROTP 0f
|
||||
d.SendData(0x0b)
|
||||
d.SendCommand(PLL_CONTROL)
|
||||
d.SendData(0x3c) // 3A 100HZ 29 150Hz 39 200HZ 31 171HZ
|
||||
}
|
||||
|
||||
// Reset resets the device
|
||||
func (d *Device) Reset() {
|
||||
d.rst.Low()
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
d.rst.High()
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
}
|
||||
|
||||
// DeepSleep puts the display into deepsleep
|
||||
func (d *Device) DeepSleep() {
|
||||
d.SendCommand(VCOM_AND_DATA_INTERVAL_SETTING)
|
||||
d.SendData(0x17) //border floating
|
||||
d.SendCommand(VCM_DC_SETTING) //VCOM to 0V
|
||||
d.SendCommand(PANEL_SETTING)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
d.SendCommand(POWER_SETTING) //VG&VS to 0V fast
|
||||
d.SendData(0x00)
|
||||
d.SendData(0x00)
|
||||
d.SendData(0x00)
|
||||
d.SendData(0x00)
|
||||
d.SendData(0x00)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
d.SendCommand(POWER_OFF) //power off
|
||||
d.WaitUntilIdle()
|
||||
d.SendCommand(DEEP_SLEEP) //deep sleep
|
||||
d.SendData(0xA5)
|
||||
}
|
||||
|
||||
// SendCommand sends a command to the display
|
||||
func (d *Device) SendCommand(command uint8) {
|
||||
d.sendDataCommand(true, command)
|
||||
}
|
||||
|
||||
// SendData sends a data byte to the display
|
||||
func (d *Device) SendData(data uint8) {
|
||||
d.sendDataCommand(false, data)
|
||||
}
|
||||
|
||||
// sendDataCommand sends image data or a command to the screen
|
||||
func (d *Device) sendDataCommand(isCommand bool, data uint8) {
|
||||
if isCommand {
|
||||
d.dc.Low()
|
||||
} else {
|
||||
d.dc.High()
|
||||
}
|
||||
d.cs.Low()
|
||||
d.bus.Transfer(data)
|
||||
d.cs.High()
|
||||
}
|
||||
|
||||
// SetLUT sets the look up tables for full or partial updates
|
||||
func (d *Device) SetLUT() {
|
||||
lut_vcom0 := []uint8{
|
||||
0x00, 0x17, 0x00, 0x00, 0x00, 0x02,
|
||||
0x00, 0x17, 0x17, 0x00, 0x00, 0x02,
|
||||
0x00, 0x0A, 0x01, 0x00, 0x00, 0x01,
|
||||
0x00, 0x0E, 0x0E, 0x00, 0x00, 0x02,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, // 44 bytes, unlike the others
|
||||
}
|
||||
lut_ww := []uint8{
|
||||
0x40, 0x17, 0x00, 0x00, 0x00, 0x02,
|
||||
0x90, 0x17, 0x17, 0x00, 0x00, 0x02,
|
||||
0x40, 0x0A, 0x01, 0x00, 0x00, 0x01,
|
||||
0xA0, 0x0E, 0x0E, 0x00, 0x00, 0x02,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
}
|
||||
lut_bw := []uint8{
|
||||
0x40, 0x17, 0x00, 0x00, 0x00, 0x02,
|
||||
0x90, 0x17, 0x17, 0x00, 0x00, 0x02,
|
||||
0x40, 0x0A, 0x01, 0x00, 0x00, 0x01,
|
||||
0xA0, 0x0E, 0x0E, 0x00, 0x00, 0x02,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
}
|
||||
lut_bb := []uint8{
|
||||
0x80, 0x17, 0x00, 0x00, 0x00, 0x02,
|
||||
0x90, 0x17, 0x17, 0x00, 0x00, 0x02,
|
||||
0x80, 0x0A, 0x01, 0x00, 0x00, 0x01,
|
||||
0x50, 0x0E, 0x0E, 0x00, 0x00, 0x02,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
}
|
||||
lut_wb := []uint8{
|
||||
0x80, 0x17, 0x00, 0x00, 0x00, 0x02,
|
||||
0x90, 0x17, 0x17, 0x00, 0x00, 0x02,
|
||||
0x80, 0x0A, 0x01, 0x00, 0x00, 0x01,
|
||||
0x50, 0x0E, 0x0E, 0x00, 0x00, 0x02,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
}
|
||||
|
||||
d.SendCommand(LUT_FOR_VCOM) //vcom
|
||||
for count := 0; count < 44; count++ {
|
||||
d.SendData(lut_vcom0[count])
|
||||
}
|
||||
|
||||
d.SendCommand(LUT_WHITE_TO_WHITE) //ww --
|
||||
for count := 0; count < 42; count++ {
|
||||
d.SendData(lut_ww[count])
|
||||
}
|
||||
|
||||
d.SendCommand(LUT_BLACK_TO_WHITE) //bw r
|
||||
for count := 0; count < 42; count++ {
|
||||
d.SendData(lut_bw[count])
|
||||
}
|
||||
|
||||
d.SendCommand(LUT_WHITE_TO_BLACK) //wb w
|
||||
for count := 0; count < 42; count++ {
|
||||
d.SendData(lut_bb[count])
|
||||
}
|
||||
|
||||
d.SendCommand(LUT_BLACK_TO_BLACK) //bb b
|
||||
for count := 0; count < 42; count++ {
|
||||
d.SendData(lut_wb[count])
|
||||
}
|
||||
}
|
||||
|
||||
// SetPixel modifies the internal buffer in a single pixel.
|
||||
// The display have 2 colors: black and white
|
||||
// We use RGBA(0,0,0, 255) as white (transparent)
|
||||
// Anything else as black
|
||||
func (d *Device) SetPixel(x int16, y int16, c color.RGBA) {
|
||||
x, y = d.xy(x, y)
|
||||
if x < 0 || x >= d.logicalWidth || y < 0 || y >= d.height {
|
||||
return
|
||||
}
|
||||
byteIndex := (uint32(x) + uint32(y)*uint32(d.logicalWidth)) / 8
|
||||
if c.R == 0 && c.G == 0 && c.B == 0 { // TRANSPARENT / WHITE
|
||||
d.buffer[byteIndex] |= 0x80 >> uint8(x%8)
|
||||
} else { // WHITE / EMPTY
|
||||
d.buffer[byteIndex] &^= 0x80 >> uint8(x%8)
|
||||
}
|
||||
}
|
||||
|
||||
// Display sends the buffer to the screen.
|
||||
func (d *Device) Display() error {
|
||||
d.SendCommand(RESOLUTION_SETTING)
|
||||
d.SendData(uint8(d.height >> 8))
|
||||
d.SendData(uint8(d.logicalWidth & 0xff))
|
||||
d.SendData(uint8(d.height >> 8))
|
||||
d.SendData(uint8(d.height & 0xff))
|
||||
|
||||
d.SendCommand(VCM_DC_SETTING)
|
||||
d.SendData(0x12)
|
||||
|
||||
d.SendCommand(VCOM_AND_DATA_INTERVAL_SETTING)
|
||||
d.SendCommand(0x97) //VBDF 17|D7 VBDW 97 VBDB 57 VBDF F7 VBDW 77 VBDB 37 VBDR B7
|
||||
|
||||
d.SendCommand(DATA_START_TRANSMISSION_1)
|
||||
var i int16
|
||||
for i = 0; i < d.logicalWidth/8*d.height; i++ {
|
||||
d.SendData(0xFF) // bit set: white, bit reset: black
|
||||
}
|
||||
time.Sleep(2 * time.Millisecond)
|
||||
d.SendCommand(DATA_START_TRANSMISSION_2)
|
||||
for i = 0; i < d.logicalWidth/8*d.height; i++ {
|
||||
d.SendData(d.buffer[i])
|
||||
}
|
||||
time.Sleep(2 * time.Millisecond)
|
||||
|
||||
d.SetLUT()
|
||||
|
||||
d.SendCommand(DISPLAY_REFRESH)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
d.WaitUntilIdle()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ClearDisplay erases the device SRAM
|
||||
func (d *Device) ClearDisplay() {
|
||||
d.SendCommand(RESOLUTION_SETTING)
|
||||
d.SendData(uint8(d.height >> 8))
|
||||
d.SendData(uint8(d.logicalWidth & 0xff))
|
||||
d.SendData(uint8(d.height >> 8))
|
||||
d.SendData(uint8(d.height & 0xff))
|
||||
|
||||
d.SendCommand(DATA_START_TRANSMISSION_1)
|
||||
time.Sleep(2 * time.Millisecond)
|
||||
var i int16
|
||||
for i = 0; i < d.logicalWidth/8*d.height; i++ {
|
||||
d.SendData(0xFF)
|
||||
}
|
||||
time.Sleep(2 * time.Millisecond)
|
||||
d.SendCommand(DATA_START_TRANSMISSION_2)
|
||||
time.Sleep(2 * time.Millisecond)
|
||||
for i = 0; i < d.logicalWidth/8*d.height; i++ {
|
||||
d.SendData(0xFF)
|
||||
}
|
||||
time.Sleep(2 * time.Millisecond)
|
||||
|
||||
d.SetLUT()
|
||||
d.SendCommand(DISPLAY_REFRESH)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
d.WaitUntilIdle()
|
||||
}
|
||||
|
||||
// WaitUntilIdle waits until the display is ready
|
||||
func (d *Device) WaitUntilIdle() {
|
||||
for d.busy.Get() {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
// IsBusy returns the busy status of the display
|
||||
func (d *Device) IsBusy() bool {
|
||||
return d.busy.Get()
|
||||
}
|
||||
|
||||
// ClearBuffer sets the buffer to 0xFF (white)
|
||||
func (d *Device) ClearBuffer() {
|
||||
for i := uint32(0); i < d.bufferLength; i++ {
|
||||
d.buffer[i] = 0xFF
|
||||
}
|
||||
}
|
||||
|
||||
// Size returns the current size of the display.
|
||||
func (d *Device) Size() (w, h int16) {
|
||||
if d.rotation == ROTATION_90 || d.rotation == ROTATION_270 {
|
||||
return d.height, d.logicalWidth
|
||||
}
|
||||
return d.logicalWidth, d.height
|
||||
}
|
||||
|
||||
// SetRotation changes the rotation (clock-wise) of the device
|
||||
func (d *Device) SetRotation(rotation Rotation) {
|
||||
d.rotation = rotation
|
||||
}
|
||||
|
||||
// xy chages the coordinates according to the rotation
|
||||
func (d *Device) xy(x, y int16) (int16, int16) {
|
||||
switch d.rotation {
|
||||
case NO_ROTATION:
|
||||
return x, y
|
||||
case ROTATION_90:
|
||||
return d.width - y - 1, x
|
||||
case ROTATION_180:
|
||||
return d.width - x - 1, d.height - y - 1
|
||||
case ROTATION_270:
|
||||
return y, d.height - x - 1
|
||||
}
|
||||
return x, y
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package epd4in2
|
||||
|
||||
// Derived from https://github.com/waveshare/e-Paper/blob/master/Arduino/epd4in2/epd4in2.h
|
||||
|
||||
// Registers
|
||||
const (
|
||||
// Display resolution
|
||||
EPD_WIDTH = 400
|
||||
EPD_HEIGHT = 300
|
||||
|
||||
// EPD4IN2 commands
|
||||
PANEL_SETTING = 0x00
|
||||
POWER_SETTING = 0x01
|
||||
POWER_OFF = 0x02
|
||||
POWER_OFF_SEQUENCE_SETTING = 0x03
|
||||
POWER_ON = 0x04
|
||||
POWER_ON_MEASURE = 0x05
|
||||
BOOSTER_SOFT_START = 0x06
|
||||
DEEP_SLEEP = 0x07
|
||||
DATA_START_TRANSMISSION_1 = 0x10
|
||||
DATA_STOP = 0x11
|
||||
DISPLAY_REFRESH = 0x12
|
||||
DATA_START_TRANSMISSION_2 = 0x13
|
||||
LUT_FOR_VCOM = 0x20
|
||||
LUT_WHITE_TO_WHITE = 0x21
|
||||
LUT_BLACK_TO_WHITE = 0x22
|
||||
LUT_WHITE_TO_BLACK = 0x23
|
||||
LUT_BLACK_TO_BLACK = 0x24
|
||||
PLL_CONTROL = 0x30
|
||||
TEMPERATURE_SENSOR_COMMAND = 0x40
|
||||
TEMPERATURE_SENSOR_SELECTION = 0x41
|
||||
TEMPERATURE_SENSOR_WRITE = 0x42
|
||||
TEMPERATURE_SENSOR_READ = 0x43
|
||||
VCOM_AND_DATA_INTERVAL_SETTING = 0x50
|
||||
LOW_POWER_DETECTION = 0x51
|
||||
TCON_SETTING = 0x60
|
||||
RESOLUTION_SETTING = 0x61
|
||||
GSST_SETTING = 0x65
|
||||
GET_STATUS = 0x71
|
||||
AUTO_MEASUREMENT_VCOM = 0x80
|
||||
READ_VCOM_VALUE = 0x81
|
||||
VCM_DC_SETTING = 0x82
|
||||
PARTIAL_WINDOW = 0x90
|
||||
PARTIAL_IN = 0x91
|
||||
PARTIAL_OUT = 0x92
|
||||
PROGRAM_MODE = 0xA0
|
||||
ACTIVE_PROGRAMMING = 0xA1
|
||||
READ_OTP = 0xA2
|
||||
POWER_SAVING = 0xE3
|
||||
|
||||
NO_ROTATION Rotation = 0
|
||||
ROTATION_90 Rotation = 1 // 90 degrees clock-wise rotation
|
||||
ROTATION_180 Rotation = 2
|
||||
ROTATION_270 Rotation = 3
|
||||
)
|
||||
@@ -0,0 +1,346 @@
|
||||
// +build xtensa
|
||||
|
||||
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()
|
||||
|
||||
switch machine.CPUFrequency() {
|
||||
case 160e6: // 160MHz
|
||||
// See:
|
||||
// https://wp.josh.com/2014/05/13/ws2812-neopixels-are-not-so-finicky-once-you-get-to-know-them/
|
||||
// Because I do not know the exact instruction timings, I'm going to
|
||||
// assume that every instruction executes in one cycle. Branches and
|
||||
// load/stores will probably be slower than that, but as long as all
|
||||
// timings are only increased a little bit this should not be a problem
|
||||
// (see above post).
|
||||
// T0H: 40 cycles or 333.3ns
|
||||
// T0L: 131 cycles or 1091.7ns
|
||||
// +: 171 cycles or 1425.0ns
|
||||
// T1H: 95 cycles or 791.7ns
|
||||
// T1L: 75 cycles or 625.0ns
|
||||
// +: 170 cycles or 1416.7ns
|
||||
// Some documentation:
|
||||
// http://cholla.mmto.org/esp8266/xtensa.html
|
||||
// https://0x04.net/~mwk/doc/xtensa.pdf
|
||||
device.AsmFull(`
|
||||
1: // send_bit
|
||||
s32i {maskSet}, {portSet}, 0 // [1] T0H and T1H start here
|
||||
nop // [37]
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
slli {value}, {value}, 1 // [1] shift {value} to the left by 1
|
||||
bbsi {value}, 8, 2f // [1] branch to skip_store if bit 8 is set
|
||||
s32i {maskClear}, {portClear}, 0 // [1] T0H -> T0L transition
|
||||
2: // skip_store
|
||||
nop // [55]
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
s32i {maskClear}, {portClear}, 0 // [1] T1H -> T1L transition
|
||||
nop // [72]
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
addi {i}, {i}, -1 // [1]
|
||||
bnez {i}, 1b // [1] send_bit, T1H and T1L end here
|
||||
|
||||
// Restore original values after modifying them in the inline
|
||||
// assembly. Not doing that would result in undefined behavior as
|
||||
// the compiler doesn't know we're modifying these values.
|
||||
movi.n {i}, 8
|
||||
slli {value}, {value}, 8
|
||||
`, map[string]interface{}{
|
||||
// Note: casting pointers to uintptr here because of what might be
|
||||
// an Xtensa backend bug with inline assembly.
|
||||
"value": uint32(c),
|
||||
"i": 8,
|
||||
"maskSet": maskSet,
|
||||
"portSet": uintptr(unsafe.Pointer(portSet)),
|
||||
"maskClear": maskClear,
|
||||
"portClear": uintptr(unsafe.Pointer(portClear)),
|
||||
})
|
||||
return nil
|
||||
case 80e6: // 80MHz
|
||||
// See docs for 160MHz.
|
||||
// T0H: 21 cycles or 262.5ns
|
||||
// T0L: 67 cycles or 837.5ns
|
||||
// +: 88 cycles or 1100.0ns
|
||||
// T1H: 47 cycles or 587.5ns
|
||||
// T1L: 39 cycles or 487.5ns
|
||||
// +: 86 cycles or 1075.0ns
|
||||
device.AsmFull(`
|
||||
1: // send_bit
|
||||
s32i {maskSet}, {portSet}, 0 // [1] T0H and T1H start here
|
||||
nop // [18]
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
slli {value}, {value}, 1 // [1] shift {value} to the left by 1
|
||||
bbsi {value}, 8, 2f // [1] branch to skip_store if bit 8 is set
|
||||
s32i {maskClear}, {portClear}, 0 // [1] T0H -> T0L transition
|
||||
2: // skip_store
|
||||
nop // [27]
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
s32i {maskClear}, {portClear}, 0 // [1] T1H -> T1L transition
|
||||
nop // [36]
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
addi {i}, {i}, -1 // [1]
|
||||
bnez {i}, 1b // [1] send_bit, T1H and T1L end here
|
||||
|
||||
// Restore original values after modifying them in the inline
|
||||
// assembly. Not doing that would result in undefined behavior as
|
||||
// the compiler doesn't know we're modifying these values.
|
||||
movi.n {i}, 8
|
||||
slli {value}, {value}, 8
|
||||
`, map[string]interface{}{
|
||||
// Note: casting pointers to uintptr here because of what might be
|
||||
// an Xtensa backend bug with inline assembly.
|
||||
"value": uint32(c),
|
||||
"i": 8,
|
||||
"maskSet": maskSet,
|
||||
"portSet": uintptr(unsafe.Pointer(portSet)),
|
||||
"maskClear": maskClear,
|
||||
"portClear": uintptr(unsafe.Pointer(portClear)),
|
||||
})
|
||||
return nil
|
||||
default:
|
||||
return errUnknownClockSpeed
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user