Compare commits

..

13 Commits

Author SHA1 Message Date
Ron Evans 21b8d953f4 Update for 0.6.0 release
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-09-09 12:48:57 +02:00
BCG d1b917b835 Support software SPI for APA102 (Itsy Bitsy M0 on-board "Dotstar" LED as example) (#86)
* Added implementation and example to support software-based SPI for APA102, for use with boards like Adafruit Itsy Bitsy M0 for instance.
2019-09-09 12:31:17 +02:00
Ron Evans 2cd73e3204 release: update changelog for 0.5.0
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-08-26 11:34:59 +02:00
Daniel Esteban 3ae5895183 Initial support for LSM6DS3 IMU (#83)
* Initial support for LSM6DS3 IMU
2019-08-26 08:56:48 +02:00
Ayke van Laethem d80f619c9f ws2812: fix timings for the nrf51
The timings needed to be changed slightly for ws2811 chips which are
slightly slower.
2019-08-19 10:55:00 +02:00
Brad Erickson c91888a099 ws2812: Add build tag for Arduino Nano33 IoT
Uses `arduino_nano33` to handle only this board. Other boards with
the same chip will need a separate tag.
2019-08-15 17:38:27 +02:00
Ron Evans b4dbac3a67 release: update CHANGELOG for v0.4.0 release
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-08-07 15:44:00 +02:00
Daniel Esteban bf077c8249 SSD1331 TFT color display (#77) 2019-08-05 17:54:23 +02:00
Daniel Esteban 4867abcbba added driver for ST7789 TFT color display (#76) 2019-08-05 14:52:39 +02:00
Daniel Esteban 45922f6524 Driver ST7735 for TFT color displays (#72)
* Driver ST7735 for TFT color displays
2019-08-03 11:13:15 +02:00
Daniel Esteban eb040dde9c Merge pull request #78 from conejoninja/typo
typo
2019-08-02 19:57:51 +02:00
Daniel Esteban 04bfa6fa70 typo 2019-08-02 19:49:19 +02:00
Ayke van Laethem 8453611d1f espat: update README with how to install dependencies 2019-07-24 15:48:19 -07:00
23 changed files with 1816 additions and 9 deletions
+24
View File
@@ -1,3 +1,27 @@
0.6.0
---
- **new devices**
- Support software SPI for APA102 (Itsy Bitsy M0 on-board "Dotstar" LED as example)
0.5.0
---
- **new devices**
- LSM6DS3 accelerometer
- **bugfixes**
- ws2812: fix timings for the nrf51
- **enhancements**
- ws2812: Add build tag for Arduino Nano33 IoT
0.4.0
---
- **new devices**
- SSD1331 TFT color display
- ST7735 TFT color display
- ST7789 TFT color display
- **docs**
- espat
- complete list of dependencies for flashing NINA-W102 as used in Arduino Nano33 IoT board.
0.3.0
---
- **new devices**
+5
View File
@@ -11,6 +11,7 @@ smoke-test:
@mkdir -p build
tinygo build -size short -o ./build/test.elf -target=itsybitsy-m0 ./examples/adxl345/main.go
tinygo build -size short -o ./build/test.elf -target=itsybitsy-m0 ./examples/apa102/main.go
tinygo build -size short -o ./build/test.elf -target=itsybitsy-m0 ./examples/apa102/itsybitsy-m0/main.go
tinygo build -size short -o ./build/test.elf -target=microbit ./examples/at24cx/main.go
tinygo build -size short -o ./build/test.elf -target=itsybitsy-m0 ./examples/bh1750/main.go
tinygo build -size short -o ./build/test.elf -target=itsybitsy-m0 ./examples/blinkm/main.go
@@ -28,6 +29,7 @@ smoke-test:
tinygo build -size short -o ./build/test.elf -target=microbit ./examples/hd44780/text/main.go
tinygo build -size short -o ./build/test.elf -target=microbit ./examples/hub75/main.go
tinygo build -size short -o ./build/test.elf -target=circuitplay-express ./examples/lis3dh/main.go
tinygo build -size short -o ./build/test.elf -target=arduino-nano33 ./examples/lsm6ds3/main.go
tinygo build -size short -o ./build/test.elf -target=itsybitsy-m0 ./examples/mag3110/main.go
tinygo build -size short -o ./build/test.elf -target=microbit ./examples/microbitmatrix/main.go
tinygo build -size short -o ./build/test.elf -target=itsybitsy-m0 ./examples/mma8653/main.go
@@ -37,6 +39,9 @@ smoke-test:
tinygo build -size short -o ./build/test.elf -target=microbit ./examples/sht3x/main.go
tinygo build -size short -o ./build/test.elf -target=microbit ./examples/ssd1306/i2c_128x32/main.go
tinygo build -size short -o ./build/test.elf -target=microbit ./examples/ssd1306/spi_128x64/main.go
tinygo build -size short -o ./build/test.elf -target=microbit ./examples/ssd1331/main.go
tinygo build -size short -o ./build/test.elf -target=microbit ./examples/st7735/main.go
tinygo build -size short -o ./build/test.elf -target=microbit ./examples/st7789/main.go
tinygo build -size short -o ./build/test.elf -target=circuitplay-express ./examples/thermistor/main.go
tinygo build -size short -o ./build/test.elf -target=itsybitsy-m0 ./examples/vl53l1x/main.go
tinygo build -size short -o ./build/test.elf -target=microbit ./examples/waveshare-epd/epd2in13/main.go
+4
View File
@@ -69,6 +69,7 @@ func main() {
| [GPS module](https://www.u-blox.com/en/product/neo-6-series) | I2C/UART |
| [HUB75 RGB led matrix](https://cdn-learn.adafruit.com/downloads/pdf/32x16-32x32-rgb-led-matrix.pdf) | SPI |
| [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 |
| [BBC micro:bit LED matrix](https://github.com/bbcmicrobit/hardware/blob/master/SCH_BBC-Microbit_V1.3B.pdf) | GPIO |
| [Microphone - PDM](https://cdn-learn.adafruit.com/assets/assets/000/049/977/original/MP34DT01-M.pdf) | I2S/PDM |
@@ -77,6 +78,9 @@ func main() {
| [PCD8544 display](http://eia.udg.edu/~forest/PCD8544_1.pdf) | SPI |
| [SHT3x Digital Humidity Sensor](https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/0_Datasheets/Humidity/Sensirion_Humidity_Sensors_SHT3x_Datasheet_digital.pdf) | I2C |
| [SSD1306 OLED display](https://cdn-shop.adafruit.com/datasheets/SSD1306.pdf) | I2C / SPI |
| [SSD1331 TFT color display](https://www.crystalfontz.com/controllers/SolomonSystech/SSD1331/381/) | SPI |
| [ST7735 TFT color display](https://www.crystalfontz.com/controllers/Sitronix/ST7735R/319/) | SPI |
| [ST7789 TFT color display](https://cdn-shop.adafruit.com/product-files/3787/3787_tft_QT154H2201__________20190228182902.pdf) | SPI |
| [Thermistor](https://www.farnell.com/datasheets/33552.pdf) | ADC |
| [VL53L1X time-of-flight distance sensor](https://www.st.com/resource/en/datasheet/vl53l1x.pdf) | I2C |
| [Waveshare 2.13" e-paper display](https://www.waveshare.com/w/upload/e/e6/2.13inch_e-Paper_Datasheet.pdf) | SPI |
+15 -2
View File
@@ -21,15 +21,28 @@ const (
// Device wraps APA102 SPI LEDs.
type Device struct {
bus machine.SPI
bus SPI
Order int
}
// The SPI interface specifies the minimum functionality that a bus
// implementation needs to provide for use by the APA102 driver. Hardware
// SPI from the TinyGo "machine" package implements this already.
type SPI interface {
Tx(w, r []byte) error
}
// New returns a new APA102 driver. Pass in a fully configured SPI bus.
func New(b machine.SPI) Device {
func New(b SPI) Device {
return Device{bus: b, Order: BGR}
}
// NewSoftwareSPI returns a new APA102 driver that will use a software based
// implementation of the SPI protocol.
func NewSoftwareSPI(sckPin, mosiPin machine.Pin, delay uint32) Device {
return New(&bbSPI{SCK: sckPin, MOSI: mosiPin, Delay: delay})
}
// WriteColors writes the given RGBA color slice out using the APA102 protocol.
// The A value (Alpha channel) is used for brightness, set to 0xff (255) for maximum.
func (d Device) WriteColors(cs []color.RGBA) (n int, err error) {
+68
View File
@@ -0,0 +1,68 @@
package apa102
import "machine"
// bbSPI is a dumb bit-bang implementation of SPI protocol that is hardcoded
// to mode 0 and ignores trying to receive data. Just enough for the APA102.
// Note: making this unexported for now because it is probable not suitable
// most purposes other than the APA102 package. It might be desirable to make
// this more generic and include it in the TinyGo "machine" package instead.
type bbSPI struct {
SCK machine.Pin
MOSI machine.Pin
Delay uint32
}
// Configure sets up the SCK and MOSI pins as outputs and sets them low
func (s *bbSPI) Configure() {
s.SCK.Configure(machine.PinConfig{Mode: machine.PinOutput})
s.MOSI.Configure(machine.PinConfig{Mode: machine.PinOutput})
s.SCK.Low()
s.MOSI.Low()
if s.Delay == 0 {
s.Delay = 1
}
}
// Tx matches signature of machine.SPI.Tx() and is used to send multiple bytes.
// The r slice is ignored and no error will ever be returned.
func (s *bbSPI) Tx(w []byte, r []byte) error {
s.Configure()
for _, b := range w {
s.Transfer(b)
}
return nil
}
// delay represents a quarter of the clock cycle
func (s *bbSPI) delay() {
for i := uint32(0); i < s.Delay; {
i++
}
}
// Transfer is used to send a single byte.
func (s *bbSPI) Transfer(b byte) {
for i := uint8(0); i < 8; i++ {
// half clock cycle high to start
s.SCK.High()
s.delay()
// write the value to MOSI (MSB first)
if b&(1<<(7-i)) == 0 {
s.MOSI.Low()
} else {
s.MOSI.High()
}
s.delay()
// half clock cycle low
s.SCK.Low()
s.delay()
// for actual SPI would try to read the MISO value here
s.delay()
}
}
+3 -1
View File
@@ -16,7 +16,9 @@ https://github.com/hybridgroup/esp32-at
To flash this firmware on the Arduino Nano33 IoT you will need to follow the following procedure:
- Using the normal Arduino software, load the `SerialNINAPassthrough` sketch on to the board.
- Install _Arduino SAMD Boards_ from the Boards Manager.
- Install _WiFiNANO_ from the Library Manager.
- Using the normal Arduino software, load the `SerialNINAPassthrough` sketch on to the board (in File -> Examples -> WiFiNINA-> Tools).
- Flash the NINA 102 firmware using the `make flash` command in the https://github.com/hybridgroup/esp32-at repo.
You only need to do this one time, and then the correct ESP-AT firmware will be on the NINA chip, and you can just flash the Arduino Nano33 IoT board using TinyGo. We should be able to remove some of these step in a future release of this software.
+84
View File
@@ -0,0 +1,84 @@
// This example demostrates how to control the "Dotstar" (APA102) LED included
// on the Adafruit Itsy Bitsy M0 board. It implements a "rainbow effect" based
// on the following example:
// https://github.com/adafruit/Adafruit_Learning_System_Guides/blob/master/CircuitPython_Essentials/CircuitPython_Internal_RGB_LED_rainbow.py
package main
import (
"image/color"
"machine"
"time"
"tinygo.org/x/drivers/apa102"
)
var (
apa apa102.Device
led = machine.PWM{machine.LED}
leds = make([]color.RGBA, 1)
wheel = &Wheel{Brightness: 0x10}
)
func init() {
// APA102 on Itsy Bitsy is connected to pins that require a software-based
// SPI implementation.
apa = apa102.NewSoftwareSPI(machine.PA00, machine.PA01, 1)
// Configure the regular on-board LED for PWM fading
machine.InitPWM()
led.Configure()
}
func main() {
// We'll fade the on-board LED in a goroutine to show/ensure that the APA102
// works fine with the scheduler enabled. Comment this out to test this code
// with the scheduler disabled.
go func() {
for i, brightening := uint8(0), false; ; i++ {
if i == 0 {
brightening = !brightening
continue
}
var brightness uint16 = uint16(i) << 8
if !brightening {
brightness = 0xFFFF - brightness
}
led.Set(brightness)
time.Sleep(5 * time.Millisecond)
}
}()
// Use the "wheel" function from Adafruit's example to cycle the APA102
for {
leds[0] = wheel.Next()
apa.WriteColors(leds)
time.Sleep(25 * time.Millisecond)
}
}
// Wheel is a port of Adafruit's Circuit Python example referenced above.
type Wheel struct {
Brightness uint8
pos uint8
}
// Next increments the internal state of the color and returns the new RGBA
func (w *Wheel) Next() (c color.RGBA) {
pos := w.pos
if w.pos < 85 {
c = color.RGBA{R: 0xFF - pos*3, G: pos * 3, B: 0x0, A: w.Brightness}
} else if w.pos < 170 {
pos -= 85
c = color.RGBA{R: 0x0, G: 0xFF - pos*3, B: pos * 3, A: w.Brightness}
} else {
pos -= 170
c = color.RGBA{R: pos * 3, G: 0x0, B: 0xFF - pos*3, A: w.Brightness}
}
w.pos++
return
}
+30
View File
@@ -0,0 +1,30 @@
// Connects to an LSM6DS3 I2C a 6 axis Inertial Measurement Unit (IMU)
package main
import (
"machine"
"time"
"tinygo.org/x/drivers/lsm6ds3"
)
func main() {
machine.I2C0.Configure(machine.I2CConfig{})
accel := lsm6ds3.New(machine.I2C0)
accel.Configure(lsm6ds3.Configuration{})
if !accel.Connected() {
println("LSM6DS3 not connected")
return
}
for {
x, y, z := accel.ReadAcceleration()
println("Acceleration:", float32(x)/1000000, float32(y)/1000000, float32(z)/1000000)
x, y, z = accel.ReadRotation()
println("Gyroscope:", float32(x)/1000000, float32(y)/1000000, float32(z)/1000000)
x, _ = accel.ReadTemperature()
println("Degrees C", float32(x)/1000, "\n\n")
time.Sleep(time.Millisecond * 1000)
}
}
+33
View File
@@ -0,0 +1,33 @@
package ssd1331
import (
"machine"
"image/color"
"tinygo.org/x/drivers/ssd1331"
)
func main() {
machine.SPI0.Configure(machine.SPIConfig{
Frequency: 8000000,
})
display := ssd1331.New(machine.SPI0, machine.P6, machine.P7, machine.P8)
display.Configure(ssd1331.Config{})
display.SetContrast(0x30, 0x20, 0x30)
width, height := display.Size()
white := color.RGBA{255, 255, 255, 255}
red := color.RGBA{255, 0, 0, 255}
blue := color.RGBA{0, 0, 255, 255}
green := color.RGBA{0, 255, 0, 255}
black := color.RGBA{0, 0, 0, 255}
display.FillScreen(black)
display.FillRectangle(0, 0, width/2, height/2, white)
display.FillRectangle(width/2, 0, width/2, height/2, red)
display.FillRectangle(0, height/2, width/2, height/2, green)
display.FillRectangle(width/2, height/2, width/2, height/2, blue)
display.FillRectangle(width/4, height/4, width/2, height/2, black)
}
+33
View File
@@ -0,0 +1,33 @@
package main
import (
"machine"
"image/color"
"tinygo.org/x/drivers/st7735"
)
func main() {
machine.SPI0.Configure(machine.SPIConfig{
Frequency: 8000000,
})
display := st7735.New(machine.SPI0, machine.P6, machine.P7, machine.P8, machine.P9)
display.Configure(st7735.Config{})
width, height := display.Size()
white := color.RGBA{255, 255, 255, 255}
red := color.RGBA{255, 0, 0, 255}
blue := color.RGBA{0, 0, 255, 255}
green := color.RGBA{0, 255, 0, 255}
black := color.RGBA{0, 0, 0, 255}
display.FillScreen(black)
display.FillRectangle(0, 0, width/2, height/2, white)
display.FillRectangle(width/2, 0, width/2, height/2, red)
display.FillRectangle(0, height/2, width/2, height/2, green)
display.FillRectangle(width/2, height/2, width/2, height/2, blue)
display.FillRectangle(width/4, height/4, width/2, height/2, black)
}
+34
View File
@@ -0,0 +1,34 @@
package main
import (
"machine"
"image/color"
"tinygo.org/x/drivers/st7789"
)
func main() {
machine.SPI0.Configure(machine.SPIConfig{
Frequency: 8000000,
Mode: 3,
})
display := st7789.New(machine.SPI0, machine.P6, machine.P7, machine.P8)
display.Configure(st7789.Config{Rotation: st7789.NO_ROTATION})
width, height := display.Size()
white := color.RGBA{255, 255, 255, 255}
red := color.RGBA{255, 0, 0, 255}
blue := color.RGBA{0, 0, 255, 255}
green := color.RGBA{0, 255, 0, 255}
black := color.RGBA{0, 0, 0, 255}
display.FillScreen(black)
display.FillRectangle(0, 0, width/2, height/2, white)
display.FillRectangle(width/2, 0, width/2, height/2, red)
display.FillRectangle(0, height/2, width/2, height/2, green)
display.FillRectangle(width/2, height/2, width/2, height/2, blue)
display.FillRectangle(width/4, height/4, width/2, height/2, black)
}
+1 -1
View File
@@ -1,4 +1,4 @@
// Package lis3dh provides a driver for the HD44780 LCD controller.
// Package hd44780 provides a driver for the HD44780 LCD controller.
//
// Datasheet: https://www.sparkfun.com/datasheets/LCD/HD44780.pdf
//
+183
View File
@@ -0,0 +1,183 @@
// Package lsm6ds3 implements a driver for the LSM6DS3 a 6 axis Inertial
// Measurement Unit (IMU)
//
// Datasheet: https://www.st.com/resource/en/datasheet/lsm6ds3.pdf
//
package lsm6ds3 // import "tinygo.org/x/drivers/lsm6ds3"
import (
"machine"
)
type AccelRange uint8
type AccelSampleRate uint8
type AccelBandwidth uint8
type GyroRange uint8
type GyroSampleRate uint8
// Device wraps an I2C connection to a LSM6DS3 device.
type Device struct {
bus machine.I2C
Address uint16
accelRange AccelRange
accelSampleRate AccelSampleRate
accelBandWidth AccelBandwidth
gyroRange GyroRange
gyroSampleRate GyroSampleRate
dataBufferSix []uint8
dataBufferTwo []uint8
}
// Configuration for LSM6DS3 device.
type Configuration struct {
AccelRange AccelRange
AccelSampleRate AccelSampleRate
AccelBandWidth AccelBandwidth
GyroRange GyroRange
GyroSampleRate GyroSampleRate
IsPedometer bool
ResetStepCounter bool
}
// New creates a new LSM6DS3 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 {
return Device{bus: bus, Address: Address}
}
// Configure sets up the device for communication.
func (d *Device) Configure(cfg Configuration) {
if cfg.AccelRange != 0 {
d.accelRange = cfg.AccelRange
} else {
d.accelRange = ACCEL_2G
}
if cfg.AccelSampleRate != 0 {
d.accelSampleRate = cfg.AccelSampleRate
} else {
d.accelSampleRate = ACCEL_SR_104
}
if cfg.AccelBandWidth != 0 {
d.accelBandWidth = cfg.AccelBandWidth
} else {
d.accelBandWidth = ACCEL_BW_100
}
if cfg.GyroRange != 0 {
d.gyroRange = cfg.GyroRange
} else {
d.gyroRange = GYRO_2000DPS
}
if cfg.GyroSampleRate != 0 {
d.gyroSampleRate = cfg.GyroSampleRate
} else {
d.gyroSampleRate = GYRO_SR_104
}
d.dataBufferSix = make([]uint8, 6)
d.dataBufferTwo = make([]uint8, 2)
if cfg.IsPedometer { // CONFIGURE AS PEDOMETER
// Configure accelerometer: 2G + 26Hz
d.bus.WriteRegister(uint8(d.Address), CTRL1_XL, []byte{uint8(ACCEL_2G) | uint8(ACCEL_SR_26)})
// Configure Zen_G, Yen_G, Xen_G, reset steps
if cfg.ResetStepCounter {
d.bus.WriteRegister(uint8(d.Address), CTRL10_C, []byte{0x3E})
} else {
d.bus.WriteRegister(uint8(d.Address), CTRL10_C, []byte{0x3C})
}
// Enable pedometer
d.bus.WriteRegister(uint8(d.Address), TAP_CFG, []byte{0x40})
} else { // NORMAL USE
// Configure accelerometer
data := make([]uint8, 1)
data[0] = uint8(d.accelRange) | uint8(d.accelSampleRate) | uint8(d.accelBandWidth)
d.bus.WriteRegister(uint8(d.Address), CTRL1_XL, data)
// Set ODR bit
d.bus.ReadRegister(uint8(d.Address), CTRL4_C, data)
data[0] = data[0] &^ BW_SCAL_ODR_ENABLED
data[0] |= BW_SCAL_ODR_ENABLED
d.bus.WriteRegister(uint8(d.Address), CTRL4_C, data)
// Configure gyroscope
data[0] = uint8(d.gyroRange) | uint8(d.gyroSampleRate)
d.bus.WriteRegister(uint8(d.Address), CTRL2_G, data)
}
}
// Connected returns whether a LSM6DS3 has been found.
// It does a "who am I" request and checks the response.
func (d *Device) Connected() bool {
data := []byte{0}
d.bus.ReadRegister(uint8(d.Address), WHO_AM_I, data)
return data[0] == 0x69
}
// ReadAcceleration reads the current acceleration from the device and returns
// it in µg (micro-gravity). When one of the axes is pointing straight to Earth
// and the sensor is not moving the returned value will be around 1000000 or
// -1000000.
func (d *Device) ReadAcceleration() (x int32, y int32, z int32) {
d.bus.ReadRegister(uint8(d.Address), OUTX_L_XL, d.dataBufferSix)
// k comes from "Table 3. Mechanical characteristics" 3 of the datasheet * 1000
k := int32(61) // 2G
if d.accelRange == ACCEL_4G {
k = 122
} else if d.accelRange == ACCEL_8G {
k = 244
} else if d.accelRange == ACCEL_16G {
k = 488
}
x = int32(int16((uint16(d.dataBufferSix[1])<<8)|uint16(d.dataBufferSix[0]))) * k
y = int32(int16((uint16(d.dataBufferSix[3])<<8)|uint16(d.dataBufferSix[2]))) * k
z = int32(int16((uint16(d.dataBufferSix[5])<<8)|uint16(d.dataBufferSix[4]))) * k
return
}
// ReadRotation reads the current rotation from the device and returns it in
// µ°/s (micro-degrees/sec). This means that if you were to do a complete
// rotation along one axis and while doing so integrate all values over time,
// you would get a value close to 360000000.
func (d *Device) ReadRotation() (x int32, y int32, z int32) {
d.bus.ReadRegister(uint8(d.Address), OUTX_L_G, d.dataBufferSix)
// k comes from "Table 3. Mechanical characteristics" 3 of the datasheet * 1000
k := int32(4375) // 125DPS
if d.gyroRange == GYRO_250DPS {
k = 8750
} else if d.gyroRange == GYRO_500DPS {
k = 17500
} else if d.gyroRange == GYRO_1000DPS {
k = 35000
} else if d.gyroRange == GYRO_2000DPS {
k = 70000
}
x = int32(int16((uint16(d.dataBufferSix[1])<<8)|uint16(d.dataBufferSix[0]))) * k
y = int32(int16((uint16(d.dataBufferSix[3])<<8)|uint16(d.dataBufferSix[2]))) * k
z = int32(int16((uint16(d.dataBufferSix[5])<<8)|uint16(d.dataBufferSix[4]))) * k
return
}
// ReadTemperature returns the temperature in celsius milli degrees (ºC/1000)
func (d *Device) ReadTemperature() (int32, error) {
d.bus.ReadRegister(uint8(d.Address), OUT_TEMP_L, d.dataBufferTwo)
// From "Table 5. Temperature sensor characteristics"
// temp = value/16 + 25
t := 25000 + (int32(int16((int16(d.dataBufferTwo[1])<<8)|int16(d.dataBufferTwo[0])))*125)/2
return t, nil
}
// ReadSteps returns the steps of the pedometer
func (d *Device) ReadSteps() int32 {
d.bus.ReadRegister(uint8(d.Address), STEP_COUNTER_L, d.dataBufferTwo)
return int32(int16((uint16(d.dataBufferTwo[1]) << 8) | uint16(d.dataBufferTwo[0])))
}
+83
View File
@@ -0,0 +1,83 @@
package lsm6ds3
// Constants/addresses used for I2C.
// The I2C address which this device listens to.
const Address = 0x6A
const (
WHO_AM_I = 0x0F
STATUS = 0x1E
CTRL1_XL = 0x10
CTRL2_G = 0x11
CTRL3_C = 0x12
CTRL4_C = 0x13
CTRL5_C = 0x14
CTRL6_C = 0x15
CTRL7_G = 0x16
CTRL8_XL = 0x17
CTRL9_XL = 0x18
CTRL10_C = 0x19
OUTX_L_G = 0x22
OUTX_H_G = 0x23
OUTY_L_G = 0x24
OUTY_H_G = 0x25
OUTZ_L_G = 0x26
OUTZ_H_G = 0x27
OUTX_L_XL = 0x28
OUTX_H_XL = 0x29
OUTY_L_XL = 0x2A
OUTY_H_XL = 0x2B
OUTZ_L_XL = 0x2C
OUTZ_H_XL = 0x2D
OUT_TEMP_L = 0x20
OUT_TEMP_H = 0x21
BW_SCAL_ODR_DISABLED = 0x00
BW_SCAL_ODR_ENABLED = 0x80
STEP_TIMESTAMP_L = 0x49
STEP_TIMESTAMP_H = 0x4A
STEP_COUNTER_L = 0x4B
STEP_COUNTER_H = 0x4C
STEP_COUNT_DELTA = 0x15
TAP_CFG = 0x58
INT1_CTRL = 0x0D
ACCEL_2G AccelRange = 0x00
ACCEL_4G AccelRange = 0x08
ACCEL_8G AccelRange = 0x0C
ACCEL_16G AccelRange = 0x04
ACCEL_SR_OFF AccelSampleRate = 0x00
ACCEL_SR_13 AccelSampleRate = 0x10
ACCEL_SR_26 AccelSampleRate = 0x20
ACCEL_SR_52 AccelSampleRate = 0x30
ACCEL_SR_104 AccelSampleRate = 0x40
ACCEL_SR_208 AccelSampleRate = 0x50
ACCEL_SR_416 AccelSampleRate = 0x60
ACCEL_SR_833 AccelSampleRate = 0x70
ACCEL_SR_1666 AccelSampleRate = 0x80
ACCEL_SR_3332 AccelSampleRate = 0x90
ACCEL_SR_6664 AccelSampleRate = 0xA0
ACCEL_SR_13330 AccelSampleRate = 0xB0
ACCEL_BW_50 AccelBandwidth = 0x03
ACCEL_BW_100 AccelBandwidth = 0x02
ACCEL_BW_200 AccelBandwidth = 0x01
ACCEL_BW_400 AccelBandwidth = 0x00
//GYRO_125DPS GyroRange = 0x01
GYRO_250DPS GyroRange = 0x00
GYRO_500DPS GyroRange = 0x04
GYRO_1000DPS GyroRange = 0x08
GYRO_2000DPS GyroRange = 0x0C
GYRO_SR_OFF GyroSampleRate = 0x00
GYRO_SR_13 GyroSampleRate = 0x10
GYRO_SR_26 GyroSampleRate = 0x20
GYRO_SR_52 GyroSampleRate = 0x30
GYRO_SR_104 GyroSampleRate = 0x40
GYRO_SR_208 GyroSampleRate = 0x50
GYRO_SR_416 GyroSampleRate = 0x60
GYRO_SR_833 GyroSampleRate = 0x70
GYRO_SR_1666 GyroSampleRate = 0x80
)
+33
View File
@@ -0,0 +1,33 @@
package ssd1331
// Registers
const (
DRAWLINE = 0x21
DRAWRECT = 0x22
FILL = 0x26
SETCOLUMN = 0x15
SETROW = 0x75
CONTRASTA = 0x81
CONTRASTB = 0x82
CONTRASTC = 0x83
MASTERCURRENT = 0x87
SETREMAP = 0xA0
STARTLINE = 0xA1
DISPLAYOFFSET = 0xA2
NORMALDISPLAY = 0xA4
DISPLAYALLON = 0xA5
DISPLAYALLOFF = 0xA6
INVERTDISPLAY = 0xA7
SETMULTIPLEX = 0xA8
SETMASTER = 0xAD
DISPLAYOFF = 0xAE
DISPLAYON = 0xAF
POWERMODE = 0xB0
PRECHARGE = 0xB1
CLOCKDIV = 0xB3
PRECHARGEA = 0x8A
PRECHARGEB = 0x8B
PRECHARGEC = 0x8C
PRECHARGELEVEL = 0xBB
VCOMH = 0xBE
)
+286
View File
@@ -0,0 +1,286 @@
// Package ssd1331 implements a driver for the SSD1331 TFT color displays.
//
// Datasheet: https://www.crystalfontz.com/controllers/SolomonSystech/SSD1331/381/
//
package ssd1331 // import "tinygo.org/x/drivers/ssd1331"
import (
"image/color"
"machine"
"errors"
"time"
)
type Model uint8
type Rotation uint8
// Device wraps an SPI connection.
type Device struct {
bus machine.SPI
dcPin machine.Pin
resetPin machine.Pin
csPin machine.Pin
width int16
height int16
batchLength int16
isBGR bool
batchData []uint8
}
// Config is the configuration for the display
type Config struct {
Width int16
Height int16
}
// New creates a new ST7735 connection. The SPI wire must already be configured.
func New(bus machine.SPI, resetPin, dcPin, csPin machine.Pin) Device {
dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
resetPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
return Device{
bus: bus,
dcPin: dcPin,
resetPin: resetPin,
csPin: csPin,
}
}
// Configure initializes the display with default configuration
func (d *Device) Configure(cfg Config) {
if cfg.Width != 0 {
d.width = cfg.Width
} else {
d.width = 96
}
if cfg.Height != 0 {
d.height = cfg.Height
} else {
d.height = 64
}
d.batchLength = d.width
if d.height > d.width {
d.batchLength = d.height
}
d.batchLength += d.batchLength & 1
d.batchData = make([]uint8, d.batchLength*2)
// reset the device
d.resetPin.High()
time.Sleep(100 * time.Millisecond)
d.resetPin.Low()
time.Sleep(100 * time.Millisecond)
d.resetPin.High()
time.Sleep(200 * time.Millisecond)
// Initialization
d.Command(DISPLAYOFF)
d.Command(SETREMAP)
d.Command(0x72) // RGB
//d.Command(0x76) // BGR
d.Command(STARTLINE)
d.Command(0x0)
d.Command(DISPLAYOFFSET)
d.Command(0x0)
d.Command(NORMALDISPLAY)
d.Command(SETMULTIPLEX)
d.Command(0x3F)
d.Command(SETMASTER)
d.Command(0x8E)
d.Command(POWERMODE)
d.Command(0x0B)
d.Command(PRECHARGE)
d.Command(0x31)
d.Command(CLOCKDIV)
d.Command(0xF0)
d.Command(PRECHARGEA)
d.Command(0x64)
d.Command(PRECHARGEB)
d.Command(0x78)
d.Command(PRECHARGEC)
d.Command(0x64)
d.Command(PRECHARGELEVEL)
d.Command(0x3A)
d.Command(VCOMH)
d.Command(0x3E)
d.Command(MASTERCURRENT)
d.Command(0x06)
d.Command(CONTRASTA)
d.Command(0x91)
d.Command(CONTRASTB)
d.Command(0x50)
d.Command(CONTRASTC)
d.Command(0x7D)
d.Command(DISPLAYON)
}
// Display does nothing, there's no buffer as it might be too big for some boards
func (d *Device) Display() error {
return nil
}
// SetPixel sets a pixel in the screen
func (d *Device) SetPixel(x int16, y int16, c color.RGBA) {
if x < 0 || y < 0 || x >= d.width || y >= d.height {
return
}
d.FillRectangle(x, y, 1, 1, c)
}
// setWindow prepares the screen to be modified at a given rectangle
func (d *Device) setWindow(x, y, w, h int16) {
/*d.Tx([]uint8{SETCOLUMN}, true)
d.Tx([]uint8{uint8(x), uint8(x + w - 1)}, false)
d.Tx([]uint8{SETROW}, true)
d.Tx([]uint8{uint8(y), uint8(y + h - 1)}, false)*/
d.Command(SETCOLUMN)
d.Command(uint8(x))
d.Command(uint8(x + w - 1))
d.Command(SETROW)
d.Command(uint8(y))
d.Command(uint8(y + h - 1))
}
// FillRectangle fills a rectangle at a given coordinates with a color
func (d *Device) FillRectangle(x, y, width, height int16, c color.RGBA) error {
if x < 0 || y < 0 || width <= 0 || height <= 0 ||
x >= d.width || (x+width) > d.width || y >= d.height || (y+height) > d.height {
return errors.New("rectangle coordinates outside display area")
}
d.setWindow(x, y, width, height)
c565 := RGBATo565(c)
c1 := uint8(c565 >> 8)
c2 := uint8(c565)
var i int16
for i = 0; i < d.batchLength; i++ {
d.batchData[i*2] = c1
d.batchData[i*2+1] = c2
}
i = width * height
for i > 0 {
if i >= d.batchLength {
d.Tx(d.batchData, false)
} else {
d.Tx(d.batchData[:i*2], false)
}
i -= d.batchLength
}
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 {
if x < 0 || y < 0 || width <= 0 || height <= 0 ||
x >= d.width || (x+width) > d.width || y >= d.height || (y+height) > d.height {
return errors.New("rectangle coordinates outside display area")
}
k := width * height
l := int16(len(buffer))
if k != l {
return errors.New("buffer length does not match with rectangle size")
}
d.setWindow(x, y, width, height)
offset := int16(0)
for k > 0 {
for i := int16(0); i < d.batchLength; i++ {
if offset+i < l {
c565 := RGBATo565(buffer[offset+i])
c1 := uint8(c565 >> 8)
c2 := uint8(c565)
d.batchData[i*2] = c1
d.batchData[i*2+1] = c2
}
}
if k >= d.batchLength {
d.Tx(d.batchData, false)
} else {
d.Tx(d.batchData[:k*2], false)
}
k -= d.batchLength
offset += d.batchLength
}
return nil
}
// DrawFastVLine draws a vertical line faster than using SetPixel
func (d *Device) DrawFastVLine(x, y0, y1 int16, c color.RGBA) {
if y0 > y1 {
y0, y1 = y1, y0
}
d.FillRectangle(x, y0, 1, y1-y0+1, c)
}
// DrawFastHLine draws a horizontal line faster than using SetPixel
func (d *Device) DrawFastHLine(x0, x1, y int16, c color.RGBA) {
if x0 > x1 {
x0, x1 = x1, x0
}
d.FillRectangle(x0, y, x1-x0+1, y, c)
}
// FillScreen fills the screen with a given color
func (d *Device) FillScreen(c color.RGBA) {
d.FillRectangle(0, 0, d.width, d.height, c)
}
// SetContrast sets the three contrast values (A, B & C)
func (d *Device) SetContrast(contrastA, contrastB, contrastC uint8) {
d.Command(CONTRASTA)
d.Command(contrastA)
d.Command(CONTRASTB)
d.Command(contrastB)
d.Command(CONTRASTC)
d.Command(contrastC)
}
// Command sends a command to the display
func (d *Device) Command(command uint8) {
d.Tx([]byte{command}, true)
}
// Command sends a data to the display
func (d *Device) Data(data uint8) {
d.Tx([]byte{data}, false)
}
// Tx sends data to the display
func (d *Device) Tx(data []byte, isCommand bool) {
if isCommand {
d.csPin.High()
d.dcPin.Low()
d.csPin.Low()
d.bus.Tx(data, nil)
d.csPin.High()
} else {
d.csPin.High()
d.dcPin.High()
d.csPin.Low()
d.bus.Tx(data, nil)
d.csPin.High()
}
}
// Size returns the current size of the display.
func (d *Device) Size() (w, h int16) {
return d.width, d.height
}
// IsBGR changes the color mode (RGB/BGR)
func (d *Device) IsBGR(bgr bool) {
d.isBGR = bgr
}
// RGBATo565 converts a color.RGBA to uint16 used in the display
func RGBATo565(c color.RGBA) uint16 {
r, g, b, _ := c.RGBA()
return uint16((r & 0xF800) +
((g & 0xFC00) >> 5) +
((b & 0xF800) >> 11))
}
+9
View File
@@ -0,0 +1,9 @@
# ST7735 driver
There are multiple devices using the ST7735 chip, and there are multiple versions ST7735B, ST7735R & ST7735S. Two apparently identical displays might have different configurations. The most common issues are:
* Colors are inverted (black is white and viceversa), invert the colors with display.InvertColors(true)
* Colors are not right (red is blue and viceversa, but green is ok), some displays uses BRG instead of RGB for defining colors, change the mode with display.IsBGR(true)
* There is noise/snow/confetti in the screen, probably rows and columns offsets are wrong, configure them with st7735.Config{RowOffset:XX, ColumnOffset:YY}
If nothing of the above works, your device may need a different boot-up process.
+57
View File
@@ -0,0 +1,57 @@
package st7735
// Registers
const (
NOP = 0x00
SWRESET = 0x01
RDDID = 0x04
RDDST = 0x09
SLPIN = 0x10
SLPOUT = 0x11
PTLON = 0x12
NORON = 0x13
INVOFF = 0x20
INVON = 0x21
DISPOFF = 0x28
DISPON = 0x29
CASET = 0x2A
RASET = 0x2B
RAMWR = 0x2C
RAMRD = 0x2E
PTLAR = 0x30
COLMOD = 0x3A
MADCTL = 0x36
MADCTL_MY = 0x80
MADCTL_MX = 0x40
MADCTL_MV = 0x20
MADCTL_ML = 0x10
MADCTL_RGB = 0x00
MADCTL_BGR = 0x08
MADCTL_MH = 0x04
RDID1 = 0xDA
RDID2 = 0xDB
RDID3 = 0xDC
RDID4 = 0xDD
FRMCTR1 = 0xB1
FRMCTR2 = 0xB2
FRMCTR3 = 0xB3
INVCTR = 0xB4
DISSET5 = 0xB6
PWCTR1 = 0xC0
PWCTR2 = 0xC1
PWCTR3 = 0xC2
PWCTR4 = 0xC3
PWCTR5 = 0xC4
VMCTR1 = 0xC5
PWCTR6 = 0xFC
GMCTRP1 = 0xE0
GMCTRN1 = 0xE1
GREENTAB Model = 0
MINI80x160 Model = 1
NO_ROTATION Rotation = 0
ROTATION_90 Rotation = 1 // 90 degrees clock-wise rotation
ROTATION_180 Rotation = 2
ROTATION_270 Rotation = 3
)
+440
View File
@@ -0,0 +1,440 @@
// Package st7735 implements a driver for the ST7735 TFT displays, it comes in various screen sizes.
//
// Datasheet: https://www.crystalfontz.com/controllers/Sitronix/ST7735R/319/
//
package st7735 // import "tinygo.org/x/drivers/st7735"
import (
"image/color"
"machine"
"time"
"errors"
)
type Model uint8
type Rotation 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
columnOffset int16
rowOffset int16
rotation Rotation
batchLength int16
model Model
isBGR bool
batchData []uint8
}
// Config is the configuration for the display
type Config struct {
Width int16
Height int16
Rotation Rotation
Model Model
RowOffset int16
ColumnOffset int16
}
// New creates a new ST7735 connection. The SPI wire must already be configured.
func New(bus machine.SPI, resetPin, dcPin, csPin, blPin machine.Pin) Device {
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,
}
}
// Configure initializes the display with default configuration
func (d *Device) Configure(cfg Config) {
d.model = cfg.Model
if cfg.Width != 0 {
d.width = cfg.Width
} else {
if d.model == MINI80x160 {
d.width = 80
} else {
d.width = 128
}
}
if cfg.Height != 0 {
d.height = cfg.Height
} else {
d.height = 160
}
d.rotation = cfg.Rotation
if cfg.RowOffset != 0 {
d.rowOffset = cfg.RowOffset
} else {
d.rowOffset = 1
}
if cfg.ColumnOffset != 0 {
d.columnOffset = cfg.ColumnOffset
} else {
if d.model == MINI80x160 {
d.columnOffset = 26
} else {
d.columnOffset = 2
}
}
d.batchLength = d.width
if d.height > d.width {
d.batchLength = d.height
}
d.batchLength += d.batchLength & 1
d.batchData = make([]uint8, d.batchLength*2)
// reset the device
d.resetPin.High()
time.Sleep(5 * time.Millisecond)
d.resetPin.Low()
time.Sleep(20 * time.Millisecond)
d.resetPin.High()
time.Sleep(150 * time.Millisecond)
// Common initialization
d.Command(SWRESET)
time.Sleep(150 * time.Millisecond)
d.Command(SLPOUT)
time.Sleep(500 * time.Millisecond)
d.Command(FRMCTR1)
d.Data(0x01)
d.Data(0x2C)
d.Data(0x2D)
d.Command(FRMCTR2)
d.Data(0x01)
d.Data(0x2C)
d.Data(0x2D)
d.Command(FRMCTR3)
d.Data(0x01)
d.Data(0x2C)
d.Data(0x2D)
d.Data(0x01)
d.Data(0x2C)
d.Data(0x2D)
d.Command(INVCTR)
d.Data(0x07)
d.Command(PWCTR1)
d.Data(0xA2)
d.Data(0x02)
d.Data(0x84)
d.Command(PWCTR2)
d.Data(0xC5)
d.Command(PWCTR3)
d.Data(0x0A)
d.Data(0x00)
d.Command(PWCTR4)
d.Data(0x8A)
d.Data(0x2A)
d.Command(PWCTR5)
d.Data(0x8A)
d.Data(0xEE)
d.Command(VMCTR1)
d.Data(0x0E)
d.Command(COLMOD)
d.Data(0x05)
if d.model == GREENTAB {
d.InvertColors(false)
d.Command(CASET)
d.Data(0x00)
d.Data(0x02)
d.Data(0x00)
d.Data(0x7F + 0x02)
d.Command(RASET)
d.Data(0x00)
d.Data(0x01)
d.Data(0x00)
d.Data(0x9F + 0x01)
} else if d.model == MINI80x160 {
d.isBGR = true
d.InvertColors(true)
d.Command(CASET)
d.Data(0x00)
d.Data(0x00)
d.Data(0x00)
d.Data(0x7F)
d.Command(RASET)
d.Data(0x00)
d.Data(0x00)
d.Data(0x00)
d.Data(0x9F)
}
// common color adjustment
d.Command(GMCTRP1)
d.Data(0x02)
d.Data(0x1C)
d.Data(0x07)
d.Data(0x12)
d.Data(0x37)
d.Data(0x32)
d.Data(0x29)
d.Data(0x2D)
d.Data(0x29)
d.Data(0x25)
d.Data(0x2B)
d.Data(0x39)
d.Data(0x00)
d.Data(0x01)
d.Data(0x03)
d.Data(0x10)
d.Command(GMCTRN1)
d.Data(0x03)
d.Data(0x1D)
d.Data(0x07)
d.Data(0x06)
d.Data(0x2E)
d.Data(0x2C)
d.Data(0x29)
d.Data(0x2D)
d.Data(0x2E)
d.Data(0x2E)
d.Data(0x37)
d.Data(0x3F)
d.Data(0x00)
d.Data(0x00)
d.Data(0x02)
d.Data(0x10)
d.Command(NORON)
time.Sleep(10 * time.Millisecond)
d.Command(DISPON)
time.Sleep(500 * time.Millisecond)
if cfg.Model == MINI80x160 {
d.Command(MADCTL)
d.Data(0xC0)
}
d.SetRotation(d.rotation)
d.blPin.High()
}
// Display does nothing, there's no buffer as it might be too big for some boards
func (d *Device) Display() error {
return nil
}
// SetPixel sets a pixel in the screen
func (d *Device) SetPixel(x int16, y int16, c color.RGBA) {
w, h := d.Size()
if x < 0 || y < 0 || x >= w || y >= h {
return
}
d.FillRectangle(x, y, 1, 1, c)
}
// setWindow prepares the screen to be modified at a given rectangle
func (d *Device) setWindow(x, y, w, h int16) {
if d.rotation == NO_ROTATION || d.rotation == ROTATION_180 {
x += d.columnOffset
y += d.rowOffset
} else {
x += d.rowOffset
y += d.columnOffset
}
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{RASET}, true)
d.Tx([]uint8{uint8(y >> 8), uint8(y), uint8((y + h - 1) >> 8), uint8(y + h - 1)}, false)
d.Command(RAMWR)
}
// FillRectangle fills a rectangle at a 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 ||
x >= k || (x+width) > k || y >= i || (y+height) > i {
return errors.New("rectangle coordinates outside display area")
}
d.setWindow(x, y, width, height)
c565 := RGBATo565(c)
c1 := uint8(c565 >> 8)
c2 := uint8(c565)
for i = 0; i < d.batchLength; i++ {
d.batchData[i*2] = c1
d.batchData[i*2+1] = c2
}
i = width * height
for i > 0 {
if i >= d.batchLength {
d.Tx(d.batchData, false)
} else {
d.Tx(d.batchData[:i*2], false)
}
i -= d.batchLength
}
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 {
k, l := d.Size()
if x < 0 || y < 0 || width <= 0 || height <= 0 ||
x >= k || (x+width) > k || y >= l || (y+height) > l {
return errors.New("rectangle coordinates outside display area")
}
k = width * height
l = int16(len(buffer))
if k != l {
return errors.New("buffer length does not match with rectangle size")
}
d.setWindow(x, y, width, height)
offset := int16(0)
for k > 0 {
for i := int16(0); i < d.batchLength; i++ {
if offset+i < l {
c565 := RGBATo565(buffer[offset+i])
c1 := uint8(c565 >> 8)
c2 := uint8(c565)
d.batchData[i*2] = c1
d.batchData[i*2+1] = c2
}
}
if k >= d.batchLength {
d.Tx(d.batchData, false)
} else {
d.Tx(d.batchData[:k*2], false)
}
k -= d.batchLength
offset += d.batchLength
}
return nil
}
// DrawFastVLine draws a vertical line faster than using SetPixel
func (d *Device) DrawFastVLine(x, y0, y1 int16, c color.RGBA) {
if y0 > y1 {
y0, y1 = y1, y0
}
d.FillRectangle(x, y0, 1, y1-y0+1, c)
}
// DrawFastHLine draws a horizontal line faster than using SetPixel
func (d *Device) DrawFastHLine(x0, x1, y int16, c color.RGBA) {
if x0 > x1 {
x0, x1 = x1, x0
}
d.FillRectangle(x0, y, x1-x0+1, y, c)
}
// FillScreen fills the screen with a given color
func (d *Device) FillScreen(c color.RGBA) {
if d.rotation == NO_ROTATION || d.rotation == ROTATION_180 {
d.FillRectangle(0, 0, d.width, d.height, c)
} else {
d.FillRectangle(0, 0, d.height, d.width, c)
}
}
// SetRotation changes the rotation of the device (clock-wise)
func (d *Device) SetRotation(rotation Rotation) {
madctl := uint8(0)
switch rotation % 4 {
case 0:
madctl = MADCTL_MX | MADCTL_MY
break
case 1:
madctl = MADCTL_MY | MADCTL_MV
break
case 2:
break
case 3:
madctl = MADCTL_MX | MADCTL_MV
break
}
if d.isBGR {
madctl |= MADCTL_BGR
}
d.Command(MADCTL)
d.Data(madctl)
}
// Command sends a command to the display
func (d *Device) Command(command uint8) {
d.Tx([]byte{command}, true)
}
// Command sends a data to the display
func (d *Device) Data(data uint8) {
d.Tx([]byte{data}, false)
}
// Tx sends data to the display
func (d *Device) Tx(data []byte, isCommand bool) {
if isCommand {
d.csPin.High()
d.dcPin.Low()
d.csPin.Low()
d.bus.Tx(data, nil)
d.csPin.High()
} else {
d.csPin.High()
d.dcPin.High()
d.csPin.Low()
d.bus.Tx(data, nil)
d.csPin.High()
}
}
// Size returns the current size of the display.
func (d *Device) Size() (w, h int16) {
if d.rotation == NO_ROTATION || d.rotation == ROTATION_180 {
return d.width, d.height
}
return d.height, d.width
}
// EnableBacklight enables or disables the backlight
func (d *Device) EnableBacklight(enable bool) {
if enable {
d.blPin.High()
} else {
d.blPin.Low()
}
}
// InverColors inverts the colors of the screen
func (d *Device) InvertColors(invert bool) {
if invert {
d.Command(INVON)
} else {
d.Command(INVOFF)
}
}
// IsBGR changes the color mode (RGB/BGR)
func (d *Device) IsBGR(bgr bool) {
d.isBGR = bgr
}
// RGBATo565 converts a color.RGBA to uint16 used in the display
func RGBATo565(c color.RGBA) uint16 {
r, g, b, _ := c.RGBA()
return uint16((r & 0xF800) +
((g & 0xFC00) >> 5) +
((b & 0xF800) >> 11))
}
+54
View File
@@ -0,0 +1,54 @@
package st7789
// Registers
const (
NOP = 0x00
SWRESET = 0x01
RDDID = 0x04
RDDST = 0x09
SLPIN = 0x10
SLPOUT = 0x11
PTLON = 0x12
NORON = 0x13
INVOFF = 0x20
INVON = 0x21
DISPOFF = 0x28
DISPON = 0x29
CASET = 0x2A
RASET = 0x2B
RAMWR = 0x2C
RAMRD = 0x2E
PTLAR = 0x30
COLMOD = 0x3A
MADCTL = 0x36
MADCTL_MY = 0x80
MADCTL_MX = 0x40
MADCTL_MV = 0x20
MADCTL_ML = 0x10
MADCTL_RGB = 0x00
MADCTL_BGR = 0x08
MADCTL_MH = 0x04
RDID1 = 0xDA
RDID2 = 0xDB
RDID3 = 0xDC
RDID4 = 0xDD
FRMCTR1 = 0xB1
FRMCTR2 = 0xB2
FRMCTR3 = 0xB3
INVCTR = 0xB4
DISSET5 = 0xB6
PWCTR1 = 0xC0
PWCTR2 = 0xC1
PWCTR3 = 0xC2
PWCTR4 = 0xC3
PWCTR5 = 0xC4
VMCTR1 = 0xC5
PWCTR6 = 0xFC
GMCTRP1 = 0xE0
GMCTRN1 = 0xE1
NO_ROTATION Rotation = 0
ROTATION_90 Rotation = 1 // 90 degrees clock-wise rotation
ROTATION_180 Rotation = 2
ROTATION_270 Rotation = 3
)
+330
View File
@@ -0,0 +1,330 @@
// 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
//
package st7789 // import "tinygo.org/x/drivers/st7789"
import (
"image/color"
"machine"
"time"
"errors"
)
type Rotation uint8
// Device wraps an SPI connection.
type Device struct {
bus machine.SPI
dcPin machine.Pin
resetPin machine.Pin
blPin machine.Pin
width int16
height int16
columnOffsetCfg int16
rowOffsetCfg int16
columnOffset int16
rowOffset int16
rotation Rotation
batchLength int32
isBGR bool
}
// Config is the configuration for the display
type Config struct {
Width int16
Height int16
Rotation Rotation
RowOffset int16
ColumnOffset 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 {
dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
resetPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
blPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
return Device{
bus: bus,
dcPin: dcPin,
resetPin: resetPin,
blPin: blPin,
}
}
// Configure initializes the display with default configuration
func (d *Device) Configure(cfg Config) {
if cfg.Width != 0 {
d.width = cfg.Width
} else {
d.width = 240
}
if cfg.Height != 0 {
d.height = cfg.Height
} else {
d.height = 240
}
d.rotation = cfg.Rotation
if cfg.RowOffset != 0 {
d.rowOffsetCfg = cfg.RowOffset
} else {
d.rowOffsetCfg = 80
}
if cfg.ColumnOffset != 0 {
d.columnOffsetCfg = cfg.ColumnOffset
}
d.batchLength = int32(d.width)
if d.height > d.width {
d.batchLength = int32(d.height)
}
d.batchLength += d.batchLength & 1
// reset the device
d.resetPin.High()
time.Sleep(5 * time.Millisecond)
d.resetPin.Low()
time.Sleep(20 * time.Millisecond)
d.resetPin.High()
time.Sleep(150 * 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.SetRotation(d.rotation)
d.Command(CASET)
d.Data(0x00)
d.Data(uint8(d.columnOffset))
d.Data((240 + uint8(d.columnOffset)) >> 8)
d.Data(((240 + uint8(d.columnOffset)) >> 8) & 0xFF)
d.Command(RASET)
d.Data(0x00)
d.Data(uint8(d.rowOffset))
d.Data((240 + uint8(d.rowOffset)) >> 8)
d.Data(((240 + uint8(d.rowOffset)) >> 8) & 0xFF)
d.InvertColors(true)
d.Command(NORON)
time.Sleep(10 * time.Millisecond)
d.Command(DISPON)
time.Sleep(500 * time.Millisecond)
d.blPin.High()
}
// Display does nothing, there's no buffer as it might be too big for some boards
func (d *Device) Display() error {
return nil
}
// SetPixel sets a pixel in the screen
func (d *Device) SetPixel(x int16, y int16, c color.RGBA) {
if x < 0 || y < 0 ||
(((d.rotation == NO_ROTATION || d.rotation == ROTATION_180) && (x >= d.width || y >= d.height)) ||
((d.rotation == ROTATION_90 || d.rotation == ROTATION_270) && (x >= d.height || y >= d.width))) {
return
}
d.FillRectangle(x, y, 1, 1, c)
}
// setWindow prepares the screen to be modified at a given rectangle
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{RASET}, true)
d.Tx([]uint8{uint8(y >> 8), uint8(y), uint8((y + h - 1) >> 8), uint8(y + h - 1)}, false)
d.Command(RAMWR)
}
// FillRectangle fills a rectangle at a 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 ||
x >= k || (x+width) > k || y >= i || (y+height) > i {
return errors.New("rectangle coordinates outside display area")
}
d.setWindow(x, y, width, height)
c565 := RGBATo565(c)
c1 := uint8(c565 >> 8)
c2 := uint8(c565)
data := make([]uint8, d.batchLength*2)
for i := int32(0); i < d.batchLength; i++ {
data[i*2] = c1
data[i*2+1] = c2
}
j := int32(width) * int32(height)
for j > 0 {
if j >= d.batchLength {
d.Tx(data, false)
} else {
d.Tx(data[:j*2], false)
}
j -= d.batchLength
}
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()
if x < 0 || y < 0 || width <= 0 || height <= 0 ||
x >= i || (x+width) > i || y >= j || (y+height) > j {
return errors.New("rectangle coordinates outside display area")
}
if int32(width)*int32(height) != int32(len(buffer)) {
return errors.New("buffer length does not match with rectangle size")
}
d.setWindow(x, y, width, height)
k := int32(width) * int32(height)
data := make([]uint8, d.batchLength*2)
offset := int32(0)
for k > 0 {
for i := int32(0); i < d.batchLength; i++ {
c565 := RGBATo565(buffer[offset+i])
c1 := uint8(c565 >> 8)
c2 := uint8(c565)
data[i*2] = c1
data[i*2+1] = c2
}
if k >= d.batchLength {
d.Tx(data, false)
} else {
d.Tx(data[:k*2], false)
}
k -= d.batchLength
offset += d.batchLength
}
return nil
}
// DrawFastVLine draws a vertical line faster than using SetPixel
func (d *Device) DrawFastVLine(x, y0, y1 int16, c color.RGBA) {
if y0 > y1 {
y0, y1 = y1, y0
}
d.FillRectangle(x, y0, 1, y1-y0+1, c)
}
// DrawFastHLine draws a horizontal line faster than using SetPixel
func (d *Device) DrawFastHLine(x0, x1, y int16, c color.RGBA) {
if x0 > x1 {
x0, x1 = x1, x0
}
d.FillRectangle(x0, y, x1-x0+1, y, c)
}
// FillScreen fills the screen with a given color
func (d *Device) FillScreen(c color.RGBA) {
if d.rotation == NO_ROTATION || d.rotation == ROTATION_180 {
d.FillRectangle(0, 0, d.width, d.height, c)
} else {
d.FillRectangle(0, 0, d.height, d.width, c)
}
}
// SetRotation changes the rotation of the device (clock-wise)
func (d *Device) SetRotation(rotation Rotation) {
madctl := uint8(0)
switch rotation % 4 {
case 0:
madctl = MADCTL_MX | MADCTL_MY
d.rowOffset = d.rowOffsetCfg
d.columnOffset = d.columnOffsetCfg
break
case 1:
madctl = MADCTL_MY | MADCTL_MV
d.rowOffset = d.columnOffsetCfg
d.columnOffset = d.rowOffsetCfg
break
case 2:
d.rowOffset = 0
d.columnOffset = 0
break
case 3:
madctl = MADCTL_MX | MADCTL_MV
d.rowOffset = 0
d.columnOffset = 0
break
}
if d.isBGR {
madctl |= MADCTL_BGR
}
d.Command(MADCTL)
d.Data(madctl)
}
// Command sends a command to the display
func (d *Device) Command(command uint8) {
d.Tx([]byte{command}, true)
}
// Command sends a data to the display
func (d *Device) Data(data uint8) {
d.Tx([]byte{data}, false)
}
// Tx sends data to the display
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)
}
}
// Size returns the current size of the display.
func (d *Device) Size() (w, h int16) {
if d.rotation == NO_ROTATION || d.rotation == ROTATION_180 {
return d.width, d.height
}
return d.height, d.width
}
// EnableBacklight enables or disables the backlight
func (d *Device) EnableBacklight(enable bool) {
if enable {
d.blPin.High()
} else {
d.blPin.Low()
}
}
// InverColors inverts the colors of the screen
func (d *Device) InvertColors(invert bool) {
if invert {
d.Command(INVON)
} else {
d.Command(INVOFF)
}
}
// IsBGR changes the color mode (RGB/BGR)
func (d *Device) IsBGR(bgr bool) {
d.isBGR = bgr
}
// RGBATo565 converts a color.RGBA to uint16 used in the display
func RGBATo565(c color.RGBA) uint16 {
r, g, b, _ := c.RGBA()
return uint16((r & 0xF800) +
((g & 0xFC00) >> 5) +
((b & 0xF800) >> 11))
}
+6 -4
View File
@@ -17,14 +17,16 @@ func (d Device) WriteByte(c byte) error {
// See:
// https://wp.josh.com/2014/05/13/ws2812-neopixels-are-not-so-finicky-once-you-get-to-know-them/
// T0H: 4 cycles or 250ns
// T0L: 14 cycles or 875ns -> together 18 cycles or 1125ns
// T1H: 10 cycles or 625ns
// T1H: 8 cycles or 500ns -> together 18 cycles or 1125ns
// Note: timings have been increased slightly to also support ws2811 LEDs.
// T0H: 5 cycles or 312.5ns
// T0L: 14 cycles or 875.0ns -> together 19 cycles or 1187.5ns
// T1H: 11 cycles or 687.5ns
// T1H: 8 cycles or 500.0ns -> together 19 cycles or 1187.5ns
value := uint32(c) << 24
arm.AsmFull(`
send_bit:
str {maskSet}, {portSet} @ [2] T0H and T0L start here
nop @ [1]
lsls {value}, #1 @ [1]
bcs.n skip_store @ [1/3]
str {maskClear}, {portClear} @ [2] T0H -> T0L transition
+1 -1
View File
@@ -1,4 +1,4 @@
// +build circuitplay_express itsybitsy_m0
// +build circuitplay_express itsybitsy_m0 arduino_nano33
package ws2812