mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-07-26 10:38:41 +00:00
Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 71eef4196f | |||
| 6301627338 | |||
| b7bbecc456 | |||
| 30f540c29f | |||
| 6d431e0726 | |||
| f308f8fce0 | |||
| 1c2b802f47 | |||
| ce80e7582f | |||
| 109cab4f3b | |||
| 07216d3051 | |||
| d688fa3f3f | |||
| dd44f9220b | |||
| ee3842f639 | |||
| 1bf1a11067 | |||
| 848f5def03 | |||
| 4a0bcba87c | |||
| 7d983647ad | |||
| 831982ad33 | |||
| 9063f46313 | |||
| 2a621dc3b1 | |||
| 7dbca2a543 | |||
| 50b6f18cc2 | |||
| bb0e6b8ba8 | |||
| 5eeba138a4 | |||
| 1095629f62 | |||
| bf0b383176 | |||
| f206f6f56f | |||
| a0293643b3 | |||
| 60801ba852 | |||
| a74770b2e6 | |||
| 3ed09d3fe1 | |||
| 22581dfd58 |
@@ -0,0 +1,2 @@
|
||||
# These are supported funding model platforms
|
||||
open_collective: tinygo
|
||||
@@ -1,3 +1,45 @@
|
||||
0.28.0
|
||||
---
|
||||
- **new devices**
|
||||
- **epd2in66b**
|
||||
- Waveshare 2.66inch E-Paper Display Module (B) for Raspberry Pi Pico (#673)
|
||||
- **mcp9808**
|
||||
- Add driver for MCP9808 i2c temperature sensor (#676)
|
||||
|
||||
- **enhancements**
|
||||
- **encoders**
|
||||
- add atsamd21, atsamd51, atsame5x
|
||||
- **pixel**
|
||||
- add support for Monochrome types such as the SSD1306 display
|
||||
- **rtl8720dn**
|
||||
- implement ConnectModeAP
|
||||
- **servo**
|
||||
- add function SetAngle() to simplify API for most common use case
|
||||
- **ssd1306**
|
||||
- add DrawBitmap() function to complete Displayer interface
|
||||
- add rotation functions for Displayer interface
|
||||
- add Sleep() function for Displayer interface
|
||||
- **uc8151**
|
||||
- improvements to speed and also add flicker-free mode based on @antirez code example
|
||||
- update to support all functions needed by tinygl and board package Displayer interface
|
||||
- **wifinina**
|
||||
- implement ConnectModeAP
|
||||
|
||||
- **bugfixes**
|
||||
- **ft6336**
|
||||
- ignore bogus touch events
|
||||
- **pixel**
|
||||
- fix Image[Monochrome].Set for larger images
|
||||
- **uc8151**
|
||||
- correct DrawBitmap() also refactor SendCommand() and SendData() for clarity
|
||||
- **ws2812**
|
||||
- Fix typo and move initialization of neo to init()
|
||||
|
||||
- **examples**
|
||||
- **ws2812**
|
||||
- Simplify examples/ws2812
|
||||
|
||||
|
||||
0.27.0
|
||||
---
|
||||
- **core**
|
||||
|
||||
@@ -19,7 +19,7 @@ rwildcard=$(foreach d,$(wildcard $1*),$(call rwildcard,$d/,$2) $(filter $(subst
|
||||
# Recursively find all *_test.go files from cwd & reduce to unique dir names
|
||||
HAS_TESTS = $(sort $(dir $(call rwildcard,,*_test.go)))
|
||||
# Exclude anything we explicitly don't want to test for whatever reason
|
||||
EXCLUDE_TESTS = image
|
||||
EXCLUDE_TESTS = image waveshare-epd/epd2in66b
|
||||
TESTS = $(filter-out $(addsuffix /%,$(EXCLUDE_TESTS)),$(HAS_TESTS))
|
||||
|
||||
unit-test:
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
[](https://pkg.go.dev/tinygo.org/x/drivers) [](https://github.com/tinygo-org/drivers/actions/workflows/build.yml)
|
||||
|
||||
|
||||
This package provides a collection of 102 different hardware drivers for devices such as sensors and displays that can be used together with [TinyGo](https://tinygo.org).
|
||||
This package provides a collection of over 100 different hardware drivers for devices such as sensors, displays, wireless adaptors, and actuators, that can be used together with [TinyGo](https://tinygo.org).
|
||||
|
||||
For the complete list, please see:
|
||||
https://tinygo.org/docs/reference/devices/
|
||||
@@ -16,7 +16,7 @@ go get tinygo.org/x/drivers
|
||||
|
||||
## How to use
|
||||
|
||||
Here is an example in TinyGo that uses the BMP180 digital barometer:
|
||||
Here is an example in TinyGo that uses the BMP180 digital barometer. This example should work on any board that supports I2C:
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -53,6 +53,28 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
## Examples Using GPIO or SPI
|
||||
|
||||
If compiling these examples directly you are likely to need to make minor changes to the defined variables to map the pins for the board you are using. For example, this block in main.go:
|
||||
|
||||
```golang
|
||||
var (
|
||||
spi = machine.SPI0
|
||||
csPin = machine.D5
|
||||
)
|
||||
```
|
||||
|
||||
It might not be obvious, but you need to change these to match how you wired your specific board. Constants are [defined for each supported microcontroller](https://tinygo.org/docs/reference/microcontrollers/).
|
||||
|
||||
For example, to change the definitions for use on a Raspberry Pi Pico using typical wiring, you might need to do this:
|
||||
|
||||
```golang
|
||||
var (
|
||||
spi = machine.SPI0
|
||||
csPin = machine.GP17
|
||||
)
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
Your contributions are welcome!
|
||||
|
||||
+2
-2
@@ -19,7 +19,7 @@ type OneWireDevice interface {
|
||||
Write(uint8)
|
||||
Read() uint8
|
||||
Select([]uint8) error
|
||||
Сrc8([]uint8, int) uint8
|
||||
Сrc8([]uint8) uint8
|
||||
}
|
||||
|
||||
// Device wraps a connection to an 1-Wire devices.
|
||||
@@ -69,7 +69,7 @@ func (d Device) ReadTemperatureRaw(romid []uint8) ([]uint8, error) {
|
||||
for i := 0; i < 9; i++ {
|
||||
spb[i] = d.owd.Read()
|
||||
}
|
||||
if d.owd.Сrc8(spb, 8) != spb[8] {
|
||||
if d.owd.Сrc8(spb) != 0 {
|
||||
return nil, errReadTemperature
|
||||
}
|
||||
return spb[:2:2], nil
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build tinygo && (rp2040 || stm32 || k210 || esp32c3 || nrf || (avr && (atmega328p || atmega328pb)))
|
||||
//go:build tinygo && (rp2040 || stm32 || k210 || esp32c3 || nrf || sam || (avr && (atmega328p || atmega328pb)))
|
||||
|
||||
// Implementation based on:
|
||||
// https://gist.github.com/aykevl/3fc1683ed77bb0a9c07559dfe857304a
|
||||
|
||||
+1
-2
@@ -122,7 +122,7 @@ func (d *Device) NetDisconnect() {
|
||||
}
|
||||
|
||||
func (d *Device) NetNotify(cb func(netlink.Event)) {
|
||||
// Not supported
|
||||
fmt.Printf("\r\n%s\r\n", netlink.ErrNotSupported)
|
||||
}
|
||||
|
||||
func (d *Device) GetHostByName(name string) (netip.Addr, error) {
|
||||
@@ -473,7 +473,6 @@ func (d *Device) parseIPD(end int) error {
|
||||
}
|
||||
|
||||
// load up the socket data
|
||||
//d.data = append(d.data, d.response[e+1:end]...)
|
||||
d.data = append(d.data, d.response[e+1:e+1+v]...)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/ina219"
|
||||
)
|
||||
|
||||
func main() {
|
||||
machine.I2C0.Configure(machine.I2CConfig{})
|
||||
|
||||
dev := ina219.New(machine.I2C0)
|
||||
dev.Configure()
|
||||
|
||||
for {
|
||||
busVoltage, shuntVoltage, current, power, err := dev.Measurements()
|
||||
if err != nil {
|
||||
println("Error reading measurements", err)
|
||||
}
|
||||
|
||||
println("Bus Voltage:", busVoltage, "V")
|
||||
println("Shunt Voltage:", shuntVoltage/100, "mV")
|
||||
println("Current:", current, "mA")
|
||||
println("Power:", power, "mW")
|
||||
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/mcp9808"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
//tinygo monitor
|
||||
time.Sleep(time.Millisecond * 5000)
|
||||
|
||||
//Configure I2C (in this case, I2C0 on RPI Pico), and wire the module accordingly
|
||||
machine.I2C0.Configure(machine.I2CConfig{
|
||||
SCL: machine.GP1,
|
||||
SDA: machine.GP0,
|
||||
})
|
||||
|
||||
//Create sensor
|
||||
sensor := mcp9808.New(machine.I2C0)
|
||||
if !sensor.Connected() {
|
||||
println("MCP9808 not found")
|
||||
return
|
||||
} else {
|
||||
println("MCP9808 found")
|
||||
}
|
||||
|
||||
time.Sleep(time.Millisecond * 1000)
|
||||
|
||||
//Set resolution
|
||||
sensor.SetResolution(mcp9808.Maximum)
|
||||
|
||||
time.Sleep(time.Millisecond * 1000)
|
||||
|
||||
//Read temp.
|
||||
temp, err := sensor.ReadTemperature()
|
||||
if err != nil {
|
||||
println("MCP9808 error reading temperature")
|
||||
println(err.Error())
|
||||
return
|
||||
} else {
|
||||
fmt.Printf("Temperature: %.2f \n", temp)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// Connects to a pcf8591 ADC via I2C.
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/pcf8591"
|
||||
)
|
||||
|
||||
var (
|
||||
i2c = machine.I2C0
|
||||
)
|
||||
|
||||
func main() {
|
||||
i2c.Configure(machine.I2CConfig{})
|
||||
adc := pcf8591.New(i2c)
|
||||
adc.Configure()
|
||||
|
||||
// get "CH0" aka "machine.ADC" interface to channel 0 from ADC.
|
||||
p := adc.CH0
|
||||
|
||||
for {
|
||||
val := p.Get()
|
||||
println(val)
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
+19
-13
@@ -25,19 +25,25 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
println("setting to 0°")
|
||||
s.SetMicroseconds(1000)
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
println("setting to 45°")
|
||||
s.SetMicroseconds(1500)
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
println("setting to 90°")
|
||||
s.SetMicroseconds(2000)
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
for {
|
||||
time.Sleep(time.Second)
|
||||
println("setting to 0°")
|
||||
s.SetAngle(0)
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
println("setting to 45°")
|
||||
s.SetAngle(45)
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
println("setting to 90°")
|
||||
s.SetAngle(90)
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
println("setting to 135°")
|
||||
s.SetAngle(135)
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
println("setting to 180°")
|
||||
s.SetAngle(180)
|
||||
time.Sleep(3 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
// Capacitive touch sensing example.
|
||||
//
|
||||
// This capacitive touch sensor works by charging a normal GPIO pin, then slowly
|
||||
// discharging it through a 1MΩ resistor and seeing how long it takes to go from
|
||||
// high to low.
|
||||
//
|
||||
// Use as follows:
|
||||
// - Change touchPin below as needed.
|
||||
// - Connect this pin to some metal surface, like a piece of aluminimum foil.
|
||||
// Make sure this surface is covered (using paper, Scotch tape, etc).
|
||||
// - Also connect this same pin to ground through a 1MΩ resistor.
|
||||
//
|
||||
// This sensor is very sensitive to noise on the power source, so you should
|
||||
// probably try to limit it by running from a battery for example. Especially
|
||||
// phone chargers can produce a lot of noise.
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/touch/capacitive"
|
||||
)
|
||||
|
||||
const touchPin = machine.GP16 // Raspberry Pi Pico
|
||||
|
||||
func main() {
|
||||
time.Sleep(time.Second * 2)
|
||||
println("start")
|
||||
|
||||
led := machine.LED
|
||||
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
led.Low()
|
||||
|
||||
// Configure the array of GPIO pins used for capacitive touch sensing.
|
||||
// We're using only one pin.
|
||||
array := capacitive.NewArray([]machine.Pin{touchPin})
|
||||
|
||||
// Use a dynamic threshold, meaning the GPIO pin is automatically calibrated
|
||||
// and re-calibrated to adjust for varying environments (e.g. changing
|
||||
// humidity).
|
||||
array.SetDynamicThreshold(100)
|
||||
|
||||
wasTouching := false
|
||||
for i := uint32(0); ; i++ {
|
||||
// Update the GPIO pin. This must be called very often.
|
||||
array.Update()
|
||||
touching := array.Touching(0)
|
||||
|
||||
// Indicate whether the pin is touched via the LED.
|
||||
led.Set(touching)
|
||||
|
||||
// Print something when the touch state changed.
|
||||
if wasTouching != touching {
|
||||
wasTouching = touching
|
||||
if touching {
|
||||
println(" touch!")
|
||||
} else {
|
||||
println(" release!")
|
||||
}
|
||||
}
|
||||
|
||||
// Print the current value, as a debugging aid. It's not really meant to
|
||||
// be used directly.
|
||||
if i%128 == 32 {
|
||||
println("touch value:", array.RawValue(0))
|
||||
}
|
||||
}
|
||||
}
|
||||
+40
-12
@@ -3,7 +3,9 @@ package main
|
||||
import (
|
||||
"image/color"
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
"tinygo.org/x/drivers/uc8151"
|
||||
)
|
||||
|
||||
@@ -14,30 +16,56 @@ func main() {
|
||||
led = machine.LED
|
||||
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
machine.SPI0.Configure(machine.SPIConfig{
|
||||
Frequency: 12000000,
|
||||
Frequency: 12 * machine.MHz,
|
||||
SCK: machine.EPD_SCK_PIN,
|
||||
SDO: machine.EPD_SDO_PIN,
|
||||
})
|
||||
|
||||
display = uc8151.New(machine.SPI0, machine.EPD_CS_PIN, machine.EPD_DC_PIN, machine.EPD_RESET_PIN, machine.EPD_BUSY_PIN)
|
||||
display.Configure(uc8151.Config{
|
||||
Rotation: uc8151.ROTATION_270,
|
||||
Speed: uc8151.MEDIUM,
|
||||
Blocking: true,
|
||||
Rotation: drivers.Rotation270,
|
||||
Speed: uc8151.TURBO,
|
||||
FlickerFree: true,
|
||||
Blocking: false,
|
||||
})
|
||||
black := color.RGBA{1, 1, 1, 255}
|
||||
|
||||
display.ClearBuffer()
|
||||
display.Display()
|
||||
for i := int16(0); i < 37; i++ {
|
||||
for j := int16(0); j < 16; j++ {
|
||||
if (i+j)%2 == 0 {
|
||||
showRect(i*8, j*8, 8, 8, black)
|
||||
display.ClearDisplay()
|
||||
|
||||
mod := int16(1)
|
||||
for {
|
||||
// checkerboard
|
||||
for i := int16(0); i < 11; i++ {
|
||||
if mod == 1 {
|
||||
mod = 0
|
||||
} else {
|
||||
mod = 1
|
||||
}
|
||||
|
||||
display.ClearBuffer()
|
||||
for i := int16(0); i < 37; i++ {
|
||||
for j := int16(0); j < 16; j++ {
|
||||
if (i+j)%2 == mod {
|
||||
showRect(i*8, j*8, 8, 8, black)
|
||||
}
|
||||
}
|
||||
}
|
||||
display.Display()
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
|
||||
// moving line
|
||||
for i := int16(16); i < 21; i++ {
|
||||
display.ClearBuffer()
|
||||
for j := int16(0); j < 16; j++ {
|
||||
if (i+j)%2 == 0 {
|
||||
showRect(i*8, j*8, 8, 8, black)
|
||||
}
|
||||
display.Display()
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
display.Display()
|
||||
}
|
||||
|
||||
func showRect(x int16, y int16, w int16, h int16, c color.RGBA) {
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"machine"
|
||||
|
||||
"tinygo.org/x/drivers/waveshare-epd/epd1in54"
|
||||
"tinygo.org/x/tinyfont"
|
||||
"tinygo.org/x/tinyfont/gophers"
|
||||
)
|
||||
|
||||
var (
|
||||
spi0 = machine.SPI0
|
||||
cs = machine.D10
|
||||
dc = machine.D9
|
||||
rst = machine.D6
|
||||
busy = machine.D5
|
||||
|
||||
black = color.RGBA{R: 1, G: 1, B: 1, A: 255}
|
||||
)
|
||||
|
||||
func main() {
|
||||
display := epd1in54.New(spi0, cs, dc, rst, busy)
|
||||
|
||||
display.LDirInit(epd1in54.Config{})
|
||||
display.Clear()
|
||||
display.ClearBuffer()
|
||||
|
||||
tinyfont.WriteLineRotated(&display, &gophers.Regular58pt, 150, 0, "A B C", black, tinyfont.ROTATION_90)
|
||||
tinyfont.WriteLineRotated(&display, &gophers.Regular58pt, 100, 0, "D E F", black, tinyfont.ROTATION_90)
|
||||
tinyfont.WriteLineRotated(&display, &gophers.Regular58pt, 50, 0, "G H I", black, tinyfont.ROTATION_90)
|
||||
tinyfont.WriteLineRotated(&display, &gophers.Regular58pt, 0, 0, "J K L", black, tinyfont.ROTATION_90)
|
||||
|
||||
display.Display()
|
||||
display.Sleep()
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
"tinygo.org/x/drivers/waveshare-epd/epd2in66b"
|
||||
"tinygo.org/x/tinyfont"
|
||||
"tinygo.org/x/tinyfont/freemono"
|
||||
)
|
||||
|
||||
var (
|
||||
black = color.RGBA{0, 0, 0, 0xff}
|
||||
white = color.RGBA{0xff, 0xff, 0xff, 0xff}
|
||||
red = color.RGBA{0xff, 0, 0, 0xff}
|
||||
)
|
||||
|
||||
func main() {
|
||||
machine.Serial.Configure(machine.UARTConfig{})
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
machine.SPI1.Configure(machine.SPIConfig{
|
||||
Frequency: epd2in66b.Baudrate,
|
||||
})
|
||||
|
||||
println("started")
|
||||
|
||||
// in case you have a Pico module, you can directly use
|
||||
// dev, err := epd2in66b.NewPicoModule()
|
||||
|
||||
display := epd2in66b.New(machine.SPI1)
|
||||
|
||||
cfg := epd2in66b.Config{
|
||||
DataPin: machine.GP8,
|
||||
ChipSelectPin: machine.GP9,
|
||||
ResetPin: machine.GP12,
|
||||
BusyPin: machine.GP13,
|
||||
}
|
||||
err := display.Configure(cfg)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = display.Reset()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
println("draw checkerboard")
|
||||
drawCheckerBoard(&display)
|
||||
|
||||
println("draw 'hello'")
|
||||
tinyfont.WriteLineRotated(&display, &freemono.Bold24pt7b, 40, 10, "Hello!", white, tinyfont.ROTATION_90)
|
||||
tinyfont.WriteLineRotated(&display, &freemono.Bold12pt7b, 10, 10, "tinygo rocks", white, tinyfont.ROTATION_90)
|
||||
err = display.Display()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func drawCheckerBoard(display drivers.Displayer) {
|
||||
s := 8
|
||||
width, height := display.Size()
|
||||
for x := 0; x <= int(width)-s; x += s {
|
||||
for y := 0; y <= int(height)-s; y += s {
|
||||
c := red
|
||||
if (x/s)%2 == (y/s)%2 {
|
||||
c = black
|
||||
}
|
||||
|
||||
showRect(display, x, y, s, s, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func showRect(display drivers.Displayer, x int, y int, w int, h int, c color.RGBA) {
|
||||
for i := x; i < x+w; i++ {
|
||||
for j := y; j < y+h; j++ {
|
||||
display.SetPixel(int16(i), int16(j), c)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,8 @@ package main
|
||||
|
||||
import "machine"
|
||||
|
||||
// Replace neo and led in the code below to match the pin
|
||||
// that you are using if different.
|
||||
var neo = machine.D2
|
||||
var led = machine.LED
|
||||
func init() {
|
||||
// Replace neo in the code below to match the pin
|
||||
// that you are using if different.
|
||||
neo = machine.D2
|
||||
}
|
||||
|
||||
@@ -4,8 +4,9 @@ package main
|
||||
|
||||
import "machine"
|
||||
|
||||
// This is the pin assignment for the Digispark only.
|
||||
// Replace neo and led in the code below to match the pin
|
||||
// that you are using if different.
|
||||
var neo machine.Pin = 0
|
||||
var led = machine.LED
|
||||
func init() {
|
||||
// This is the pin assignment for the Digispark only.
|
||||
// Replace neo and led in the code below to match the pin
|
||||
// that you are using if different.
|
||||
neo = machine.Pin(0)
|
||||
}
|
||||
|
||||
@@ -12,11 +12,12 @@ import (
|
||||
"tinygo.org/x/drivers/ws2812"
|
||||
)
|
||||
|
||||
var leds [10]color.RGBA
|
||||
var (
|
||||
neo machine.Pin
|
||||
leds [10]color.RGBA
|
||||
)
|
||||
|
||||
func main() {
|
||||
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
|
||||
neo.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
|
||||
ws := ws2812.NewWS2812(neo)
|
||||
@@ -35,7 +36,6 @@ func main() {
|
||||
}
|
||||
|
||||
ws.WriteColors(leds[:])
|
||||
led.Set(rg)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
//go:build !digispark && !arduino && !qtpy && !m5stamp_c3 && !thingplus_rp2040
|
||||
//go:build !digispark && !arduino
|
||||
|
||||
package main
|
||||
|
||||
import "machine"
|
||||
|
||||
// Replace neo and led in the code below to match the pin
|
||||
// that you are using if different.
|
||||
var neo machine.Pin = machine.WS2812
|
||||
var led = machine.LED
|
||||
func init() {
|
||||
// Replace neo in the code below to match the pin
|
||||
// that you are using if different.
|
||||
neo = machine.WS2812
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
//go:build qtpy || m5stamp_c3
|
||||
|
||||
package main
|
||||
|
||||
import "machine"
|
||||
|
||||
// Replace neo and led in the code below to match the pin
|
||||
// that you are using if different.
|
||||
var neo machine.Pin = machine.WS2812
|
||||
var led = machine.NoPin
|
||||
@@ -1,12 +0,0 @@
|
||||
//go:build thingplus_rp2040
|
||||
|
||||
package main
|
||||
|
||||
import "machine"
|
||||
|
||||
// This is the pin assignment for the internal neopixel of the
|
||||
// Sparkfun thingplus rp2040.
|
||||
// Replace neo and led in the code below to match the pin
|
||||
// that you are using if different.
|
||||
var neo machine.Pin = machine.GPIO8
|
||||
var led = machine.LED
|
||||
+2
-1
@@ -80,7 +80,8 @@ func (d *Device) Read() []byte {
|
||||
func (d *Device) ReadTouchPoint() touch.Point {
|
||||
d.Read()
|
||||
z := 0xFFFFF
|
||||
if d.buf[0] == 0 {
|
||||
switch d.buf[0] {
|
||||
case 0, 255:
|
||||
z = 0
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
package ina219
|
||||
|
||||
type Config struct {
|
||||
// BusVoltageRange sets the bus voltage range.
|
||||
BusVoltageRange BusVoltageRange
|
||||
|
||||
// PGA sets the programmable gain amplifier.
|
||||
PGA PGA
|
||||
|
||||
// BusADC sets the bus ADC resolution.
|
||||
BusADC BusADC
|
||||
|
||||
// ShuntADC sets the shunt ADC resolution.
|
||||
ShuntADC ShuntADC
|
||||
|
||||
// Mode sets the operating mode.
|
||||
Mode Mode
|
||||
|
||||
// Calibration sets the calibration value for the expected
|
||||
// voltage and current values.
|
||||
Calibration Calibration
|
||||
|
||||
// 1000 / uA per bit
|
||||
CurrentDivider float32
|
||||
|
||||
// 1mW per bit
|
||||
PowerMultiplier float32
|
||||
}
|
||||
|
||||
// RegisterValue returns the register value of the configuration.
|
||||
func (c *Config) RegisterValue() uint16 {
|
||||
return c.BusVoltageRange.RegisterValue() |
|
||||
c.PGA.RegisterValue() |
|
||||
c.BusADC.RegisterValue() |
|
||||
c.ShuntADC.RegisterValue() |
|
||||
c.Mode.RegisterValue()
|
||||
}
|
||||
|
||||
// Generate a new configuration from a register value.
|
||||
func NewConfig(config int16, calibration int16) Config {
|
||||
return Config{
|
||||
BusVoltageRange: BusVoltageRange(config >> 13 & 0x1),
|
||||
PGA: PGA(config >> 11 & 0x3),
|
||||
BusADC: BusADC(config >> 7 & 0xF),
|
||||
ShuntADC: ShuntADC(config >> 3 & 0xF),
|
||||
Mode: Mode(config & 0x7),
|
||||
Calibration: Calibration(calibration),
|
||||
}
|
||||
}
|
||||
|
||||
// Configurations from
|
||||
// https://github.com/adafruit/Adafruit_INA219/blob/master/Adafruit_INA219.cpp
|
||||
var (
|
||||
// Config32V2A is a configuration for a 32V 2A range.
|
||||
Config32V2A = Config{
|
||||
BusVoltageRange: Range32V,
|
||||
PGA: PGA8,
|
||||
BusADC: ADC12,
|
||||
ShuntADC: SADC12,
|
||||
Mode: ModeContShuntBus,
|
||||
Calibration: Calibration16V400mA,
|
||||
CurrentDivider: 10.0,
|
||||
PowerMultiplier: 2.0,
|
||||
}
|
||||
|
||||
// Config32V1A is a configuration for a 32V 1A range.
|
||||
Config32V1A = Config{
|
||||
BusVoltageRange: Range32V,
|
||||
PGA: PGA8,
|
||||
BusADC: ADC12,
|
||||
ShuntADC: SADC12,
|
||||
Mode: ModeContShuntBus,
|
||||
Calibration: Calibration32V1A,
|
||||
CurrentDivider: 25.0,
|
||||
PowerMultiplier: 0.8,
|
||||
}
|
||||
|
||||
// Config16V400mA is a configuration for a 16V 400mA range.
|
||||
Config16V400mA = Config{
|
||||
BusVoltageRange: Range16V,
|
||||
PGA: PGA1,
|
||||
BusADC: ADC12,
|
||||
ShuntADC: SADC12,
|
||||
Mode: ModeContShuntBus,
|
||||
Calibration: Calibration16V400mA,
|
||||
CurrentDivider: 20.0,
|
||||
PowerMultiplier: 1.0,
|
||||
}
|
||||
)
|
||||
|
||||
// BusVoltageRange is the bus voltage range.
|
||||
type BusVoltageRange int8
|
||||
|
||||
const (
|
||||
Range16V BusVoltageRange = 0 // 0-16V
|
||||
Range32V BusVoltageRange = 1 // 0-32V
|
||||
)
|
||||
|
||||
func (r BusVoltageRange) RegisterValue() uint16 {
|
||||
return uint16(r) << 13
|
||||
}
|
||||
|
||||
// PGA is the programmable gain amplifier.
|
||||
type PGA int8
|
||||
|
||||
const (
|
||||
PGA1 PGA = 0 // 40mV
|
||||
PGA2 PGA = 1 // 80mV
|
||||
PGA4 PGA = 2 // 160mV
|
||||
PGA8 PGA = 3 // 320mV
|
||||
)
|
||||
|
||||
func (p PGA) RegisterValue() uint16 {
|
||||
return uint16(p) << 11
|
||||
}
|
||||
|
||||
// BusADC is the bus ADC resolution.
|
||||
type BusADC int8
|
||||
|
||||
const (
|
||||
ADC9 BusADC = 0 // 9-bit
|
||||
ADC10 BusADC = 1 // 10-bit
|
||||
ADC11 BusADC = 2 // 11-bit
|
||||
ADC12 BusADC = 3 // 12-bit
|
||||
)
|
||||
|
||||
func (b BusADC) RegisterValue() uint16 {
|
||||
return uint16(b) << 7
|
||||
}
|
||||
|
||||
// ShuntADC is the shunt ADC resolution.
|
||||
type ShuntADC int8
|
||||
|
||||
const (
|
||||
SADC9 ShuntADC = 0 // 9-bit
|
||||
SADC10 ShuntADC = 1 // 10-bit
|
||||
SADC11 ShuntADC = 2 // 11-bit
|
||||
SADC12 ShuntADC = 3 // 12-bit
|
||||
)
|
||||
|
||||
func (s ShuntADC) RegisterValue() uint16 {
|
||||
return uint16(s) << 3
|
||||
}
|
||||
|
||||
// Mode is the operating mode.
|
||||
type Mode int8
|
||||
|
||||
const (
|
||||
ModePowerDown Mode = 0 // power-down
|
||||
ModeTrigShunt Mode = 1 // triggered shunt voltage
|
||||
ModeTrigBus Mode = 2 // triggered bus voltage
|
||||
ModeTrigShuntBus Mode = 3 // triggered shunt and bus voltage
|
||||
ModeADCOff Mode = 4 // ADC off
|
||||
ModeContShunt Mode = 5 // continuous shunt voltage
|
||||
ModeContBus Mode = 6 // continuous bus voltage
|
||||
ModeContShuntBus Mode = 7 // continuous shunt and bus voltage
|
||||
)
|
||||
|
||||
// ModeTriggered is a mask for triggered modes.
|
||||
const ModeTriggeredMask Mode = 0x4
|
||||
|
||||
// ModeTriggered returns true if the mode is a triggered mode.
|
||||
func ModeTriggered(m Mode) bool {
|
||||
return m != ModePowerDown && m&ModeTriggeredMask == 0
|
||||
}
|
||||
|
||||
func (m Mode) RegisterValue() uint16 {
|
||||
return uint16(m)
|
||||
}
|
||||
|
||||
// Calibration is the calibration register for the INA219. Values from:
|
||||
// https://github.com/adafruit/Adafruit_INA219/blob/master/Adafruit_INA219.cpp
|
||||
type Calibration uint16
|
||||
|
||||
const (
|
||||
Calibration32V2A Calibration = 4096
|
||||
Calibration32V1A Calibration = 10240
|
||||
Calibration16V400mA Calibration = 8192
|
||||
)
|
||||
|
||||
func (c Calibration) RegisterValue() uint16 {
|
||||
return uint16(c)
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package ina219
|
||||
|
||||
type ErrOverflow struct{}
|
||||
|
||||
func (e ErrOverflow) Error() string { return "overflow" }
|
||||
|
||||
type ErrNotReady struct{}
|
||||
|
||||
func (e ErrNotReady) Error() string { return "not ready" }
|
||||
|
||||
type ErrConfigMismatch struct{}
|
||||
|
||||
func (e ErrConfigMismatch) Error() string { return "config mismatch" }
|
||||
@@ -0,0 +1,202 @@
|
||||
package ina219
|
||||
|
||||
import (
|
||||
"tinygo.org/x/drivers"
|
||||
"tinygo.org/x/drivers/internal/legacy"
|
||||
)
|
||||
|
||||
// An INA219 device.
|
||||
type Device struct {
|
||||
bus drivers.I2C
|
||||
Address uint16
|
||||
config Config
|
||||
}
|
||||
|
||||
// Create a new INA219 device with the default configuration
|
||||
// and the given I2C bus at the default address.
|
||||
//
|
||||
// Set Address after New to change the address.
|
||||
//
|
||||
// Call Configure after New to write the configuration to the
|
||||
// device. If you don't call Configure, the device may have a
|
||||
// different configuration and the power divider and current
|
||||
// multiplier are probably wrong.
|
||||
func New(bus drivers.I2C) Device {
|
||||
return Device{
|
||||
bus: bus,
|
||||
Address: Address,
|
||||
config: Config32V2A,
|
||||
}
|
||||
}
|
||||
|
||||
// Set the configuration for the device. This only changes the
|
||||
// configuration in memory, not on the device. Call Configure
|
||||
// to write the configuration to the device.
|
||||
func (d *Device) SetConfig(config Config) {
|
||||
d.config = config
|
||||
}
|
||||
|
||||
// Write the current configuration to the device.
|
||||
func (d *Device) Configure() (err error) {
|
||||
if err = d.WriteRegister(
|
||||
RegConfig,
|
||||
d.config.RegisterValue(),
|
||||
); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = d.WriteRegister(
|
||||
RegCalibration,
|
||||
d.config.Calibration.RegisterValue(),
|
||||
); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var readConfig Config
|
||||
// make sure the configuration is read back correctly
|
||||
if readConfig, err = d.ReadConfig(); err != nil {
|
||||
return
|
||||
} else if readConfig.RegisterValue() != d.config.RegisterValue() {
|
||||
err = ErrConfigMismatch{}
|
||||
} else if readConfig.Calibration.RegisterValue() != d.config.Calibration.RegisterValue() {
|
||||
err = ErrConfigMismatch{}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Trigger a conversion. This is only necessary if the device is in
|
||||
// trigger mode. In continuous mode (the default), the device will
|
||||
// automatically trigger conversions and this has no effect. See
|
||||
// config.go.
|
||||
//
|
||||
// Triggering a conversion or reading the "power" register resets
|
||||
// the conversion ready bit.
|
||||
func (d *Device) Trigger() (err error) {
|
||||
// Only trigger if the mode is one of the triggered modes.
|
||||
if ModeTriggered(d.config.Mode) {
|
||||
err = d.WriteRegister(RegConfig, d.config.RegisterValue())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Measurements reads the bus voltage, shunt voltage, current, and power
|
||||
// from the device.
|
||||
func (d *Device) Measurements() (
|
||||
busVoltage int16,
|
||||
shuntVoltage int16,
|
||||
current float32,
|
||||
power float32,
|
||||
err error,
|
||||
) {
|
||||
// Attempt to read bus voltage first, so we can check for overflow
|
||||
// or conversion not ready.
|
||||
if busVoltage, err = d.BusVoltage(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Read the rest of the values, reading Power last, which resets
|
||||
// the conversion ready bit (relevant for triggered modes).
|
||||
if shuntVoltage, err = d.ShuntVoltage(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if current, err = d.Current(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if power, err = d.Power(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// BusVoltage reads the "bus" voltage in millivolts.
|
||||
//
|
||||
// It returns an error if the value is invalid due to overflow
|
||||
// or if the conversion is not ready yet. In a continuous mode
|
||||
// there should always be a measurement available after the
|
||||
// device is ready. See above notes on Trigger.
|
||||
func (d *Device) BusVoltage() (voltage int16, err error) {
|
||||
val, err := d.ReadRegister(RegBusVoltage)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// The overflow bit is set, so the values are invalid.
|
||||
if val&(1<<0) != 0 {
|
||||
err = ErrOverflow{}
|
||||
return
|
||||
}
|
||||
|
||||
// The conversion is not ready yet.
|
||||
if ModeTriggered(d.config.Mode) && val&(1<<1) != 0 {
|
||||
err = ErrNotReady{}
|
||||
return
|
||||
}
|
||||
|
||||
voltage = (int16(val) >> 3) * 4
|
||||
return
|
||||
}
|
||||
|
||||
// ShuntVoltage reads the "shunt" voltage in 100ths of a millivolt.
|
||||
func (d *Device) ShuntVoltage() (voltage int16, err error) {
|
||||
return d.ReadRegister(RegShuntVoltage)
|
||||
}
|
||||
|
||||
// Current reads the current in milliamps.
|
||||
func (d *Device) Current() (current float32, err error) {
|
||||
val, err := d.ReadRegister(RegCurrent)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
current = float32(val) / d.config.CurrentDivider
|
||||
return
|
||||
}
|
||||
|
||||
// Power reads the power in milliwatts.
|
||||
func (d *Device) Power() (power float32, err error) {
|
||||
val, err := d.ReadRegister(RegPower)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
power = float32(val) * d.config.PowerMultiplier
|
||||
return
|
||||
}
|
||||
|
||||
// Read the configuration from the device.
|
||||
func (d *Device) ReadConfig() (config Config, err error) {
|
||||
var cfg, cal int16
|
||||
if cfg, err = d.ReadRegister(RegConfig); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if cal, err = d.ReadRegister(RegCalibration); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
config = NewConfig(cfg, cal)
|
||||
return
|
||||
}
|
||||
|
||||
// Read a register from the device.
|
||||
func (d *Device) ReadRegister(reg uint8) (val int16, err error) {
|
||||
buf := make([]byte, 2)
|
||||
|
||||
err = legacy.ReadRegister(d.bus, uint8(d.Address), reg, buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
val = int16(buf[0])<<8 | int16(buf[1]&0xff)
|
||||
return
|
||||
}
|
||||
|
||||
// Write to a register on the device.
|
||||
func (d *Device) WriteRegister(reg uint8, val uint16) error {
|
||||
buf := []byte{byte(val >> 8), byte(val & 0xff)}
|
||||
return legacy.WriteRegister(d.bus, uint8(d.Address), reg, buf)
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
package ina219
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
"tinygo.org/x/drivers/tester"
|
||||
)
|
||||
|
||||
func TestDefaultAddress(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := tester.NewI2CBus(c)
|
||||
dev := New(bus)
|
||||
c.Assert(dev.Address, qt.Equals, uint16(Address))
|
||||
}
|
||||
|
||||
func TestBusVoltage(t *testing.T) {
|
||||
t.Run("valid", func(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := tester.NewI2CBus(c)
|
||||
fake := tester.NewI2CDevice16(c, Address)
|
||||
fake.Registers = map[uint8]uint16{
|
||||
RegBusVoltage: (4200 << 3) / 4, // 4.2V
|
||||
}
|
||||
bus.AddDevice(fake)
|
||||
|
||||
dev := New(bus)
|
||||
voltage, err := dev.BusVoltage()
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(voltage, qt.Equals, int16(4200))
|
||||
})
|
||||
|
||||
t.Run("overflow", func(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := tester.NewI2CBus(c)
|
||||
fake := tester.NewI2CDevice16(c, Address)
|
||||
fake.Registers = map[uint8]uint16{
|
||||
RegBusVoltage: (1 >> 0), // overflow
|
||||
}
|
||||
bus.AddDevice(fake)
|
||||
|
||||
dev := New(bus)
|
||||
_, err := dev.BusVoltage()
|
||||
c.Assert(err, qt.Not(qt.IsNil))
|
||||
c.Assert(err, qt.ErrorMatches, ErrOverflow{}.Error())
|
||||
})
|
||||
|
||||
t.Run("not ready", func(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := tester.NewI2CBus(c)
|
||||
fake := tester.NewI2CDevice16(c, Address)
|
||||
fake.Registers = map[uint8]uint16{
|
||||
RegBusVoltage: ((4200 << 3) / 4) | (1 << 1), // not ready
|
||||
}
|
||||
bus.AddDevice(fake)
|
||||
|
||||
dev := New(bus)
|
||||
dev.config.Mode = ModeTrigBus
|
||||
_, err := dev.BusVoltage()
|
||||
c.Assert(err, qt.Not(qt.IsNil))
|
||||
c.Assert(err, qt.ErrorMatches, ErrNotReady{}.Error())
|
||||
})
|
||||
}
|
||||
|
||||
func TestShuntVoltage(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := tester.NewI2CBus(c)
|
||||
fake := tester.NewI2CDevice16(c, Address)
|
||||
fake.Registers = map[uint8]uint16{
|
||||
RegShuntVoltage: 0x1234,
|
||||
}
|
||||
bus.AddDevice(fake)
|
||||
|
||||
dev := New(bus)
|
||||
voltage, err := dev.ShuntVoltage()
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(voltage, qt.Equals, int16(0x1234))
|
||||
}
|
||||
|
||||
func TestCurrent(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := tester.NewI2CBus(c)
|
||||
fake := tester.NewI2CDevice16(c, Address)
|
||||
fake.Registers = map[uint8]uint16{
|
||||
RegCurrent: 420 * 6.9, // 420mA
|
||||
}
|
||||
bus.AddDevice(fake)
|
||||
|
||||
dev := New(bus)
|
||||
dev.config.CurrentDivider = 6.9
|
||||
current, err := dev.Current()
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(current, qt.Equals, float32(420))
|
||||
}
|
||||
|
||||
func TestPower(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := tester.NewI2CBus(c)
|
||||
fake := tester.NewI2CDevice16(c, Address)
|
||||
fake.Registers = map[uint8]uint16{
|
||||
RegPower: 420 / 0.8, // 420mW
|
||||
}
|
||||
bus.AddDevice(fake)
|
||||
|
||||
dev := New(bus)
|
||||
dev.config.PowerMultiplier = 0.8
|
||||
power, err := dev.Power()
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(power, qt.Equals, float32(420))
|
||||
}
|
||||
|
||||
func TestReadConfig(t *testing.T) {
|
||||
// use the default configurations
|
||||
for _, tc := range []Config{
|
||||
Config16V400mA,
|
||||
Config32V2A,
|
||||
Config32V1A,
|
||||
} {
|
||||
n := fmt.Sprintf("%x/%x", tc.RegisterValue(), tc.Calibration.RegisterValue())
|
||||
t.Run(n, func(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := tester.NewI2CBus(c)
|
||||
fake := tester.NewI2CDevice16(c, Address)
|
||||
fake.Registers = map[uint8]uint16{
|
||||
RegConfig: tc.RegisterValue(),
|
||||
RegCalibration: tc.Calibration.RegisterValue(),
|
||||
}
|
||||
bus.AddDevice(fake)
|
||||
|
||||
dev := New(bus)
|
||||
config, err := dev.ReadConfig()
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(config.BusADC, qt.Equals, tc.BusADC)
|
||||
c.Assert(config.BusVoltageRange, qt.Equals, tc.BusVoltageRange)
|
||||
c.Assert(config.Calibration, qt.Equals, tc.Calibration)
|
||||
c.Assert(config.Mode, qt.Equals, tc.Mode)
|
||||
c.Assert(config.PGA, qt.Equals, tc.PGA)
|
||||
c.Assert(config.ShuntADC, qt.Equals, tc.ShuntADC)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteConfig(t *testing.T) {
|
||||
// use the default configurations
|
||||
for _, tc := range []Config{
|
||||
Config16V400mA,
|
||||
Config32V2A,
|
||||
Config32V1A,
|
||||
} {
|
||||
n := fmt.Sprintf("%x/%x", tc.RegisterValue(), tc.Calibration.RegisterValue())
|
||||
t.Run(n, func(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := tester.NewI2CBus(c)
|
||||
fake := tester.NewI2CDevice16(c, Address)
|
||||
bus.AddDevice(fake)
|
||||
fake.Registers = map[uint8]uint16{
|
||||
RegConfig: 0,
|
||||
RegCalibration: 0,
|
||||
}
|
||||
|
||||
dev := New(bus)
|
||||
dev.config = tc
|
||||
err := dev.Configure()
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(fake.Registers[RegConfig], qt.Equals, tc.RegisterValue())
|
||||
c.Assert(fake.Registers[RegCalibration], qt.Equals, tc.Calibration.RegisterValue())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetConfig(t *testing.T) {
|
||||
for _, tc := range []Config{
|
||||
Config16V400mA,
|
||||
Config32V2A,
|
||||
Config32V1A,
|
||||
} {
|
||||
n := fmt.Sprintf("%x/%x", tc.RegisterValue(), tc.Calibration.RegisterValue())
|
||||
t.Run(n, func(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := tester.NewI2CBus(c)
|
||||
dev := New(bus)
|
||||
dev.SetConfig(tc)
|
||||
c.Assert(dev.config, qt.Equals, tc)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrigger(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := tester.NewI2CBus(c)
|
||||
fake := tester.NewI2CDevice16(c, Address)
|
||||
bus.AddDevice(fake)
|
||||
fake.Registers = map[uint8]uint16{
|
||||
RegConfig: Config32V2A.RegisterValue(),
|
||||
}
|
||||
|
||||
dev := New(bus)
|
||||
dev.config = Config32V2A
|
||||
dev.config.Mode = ModeTrigBus
|
||||
err := dev.Trigger()
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(fake.Registers[RegConfig], qt.Equals, dev.config.RegisterValue())
|
||||
}
|
||||
|
||||
func TestMeasurements(t *testing.T) {
|
||||
bvVal := int16(4200)
|
||||
svVal := int16(1234)
|
||||
iVal := float32(420)
|
||||
pVal := float32(420)
|
||||
|
||||
c := qt.New(t)
|
||||
bus := tester.NewI2CBus(c)
|
||||
fake := tester.NewI2CDevice16(c, Address)
|
||||
bus.AddDevice(fake)
|
||||
fake.Registers = map[uint8]uint16{
|
||||
RegBusVoltage: uint16(((4200 << 3) / 4) | (1 << 1)),
|
||||
RegShuntVoltage: uint16(svVal),
|
||||
RegCurrent: uint16(iVal * Config16V400mA.CurrentDivider),
|
||||
RegPower: uint16(pVal / Config16V400mA.PowerMultiplier),
|
||||
}
|
||||
|
||||
dev := New(bus)
|
||||
dev.config = Config16V400mA
|
||||
bv, sv, i, p, err := dev.Measurements()
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(bv, qt.Equals, bvVal)
|
||||
c.Assert(sv, qt.Equals, svVal)
|
||||
c.Assert(i, qt.Equals, iVal)
|
||||
c.Assert(p, qt.Equals, pVal)
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package ina219
|
||||
|
||||
// The default I2C address for this device.
|
||||
const Address = 0x40
|
||||
|
||||
const (
|
||||
RegConfig uint8 = 0x0
|
||||
RegShuntVoltage uint8 = 0x1
|
||||
RegBusVoltage uint8 = 0x2
|
||||
RegPower uint8 = 0x3
|
||||
RegCurrent uint8 = 0x4
|
||||
RegCalibration uint8 = 0x5
|
||||
)
|
||||
@@ -0,0 +1,98 @@
|
||||
// Package mcp9808 implements a driver for the MCP9808 High Accuracy I2C Temperature Sensor
|
||||
//
|
||||
// Datasheet: https://cdn-shop.adafruit.com/datasheets/MCP9808.pdf
|
||||
// Module: https://www.adafruit.com/product/1782
|
||||
// Only implemented: temperature reading, resolution read & set
|
||||
package mcp9808
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
type Device struct {
|
||||
bus drivers.I2C
|
||||
Address uint16
|
||||
}
|
||||
|
||||
func New(bus drivers.I2C) Device {
|
||||
return Device{bus, MCP9808_I2CADDR_DEFAULT}
|
||||
}
|
||||
|
||||
func (d *Device) Connected() bool {
|
||||
data := make([]byte, 2)
|
||||
d.Read(MCP9808_REG_DEVICE_ID, &data)
|
||||
return binary.BigEndian.Uint16(data) == MCP9808_DEVICE_ID
|
||||
}
|
||||
|
||||
func (d *Device) ReadTemperature() (float64, error) {
|
||||
data := make([]byte, 2)
|
||||
var temp float64
|
||||
if err := d.Read(MCP9808_REG_AMBIENT_TEMP, &data); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
data[0] = data[0] & 0x1F
|
||||
if data[0]&0x10 == 0x10 {
|
||||
data[0] = data[0] & 0x0F
|
||||
temp = float64(data[0])*16 + float64(data[1])/16.0 - 256
|
||||
}
|
||||
temp = float64(data[0])*16 + float64(data[1])/16.0
|
||||
return temp, nil
|
||||
}
|
||||
|
||||
func (d *Device) ReadResolution() (resolution, error) {
|
||||
data := make([]byte, 2)
|
||||
err := d.Read(MCP9808_REG_RESOLUTION, &data)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
switch data[0] {
|
||||
case 0:
|
||||
return Low, nil
|
||||
case 1:
|
||||
return Medium, nil
|
||||
case 2:
|
||||
return High, nil
|
||||
case 3:
|
||||
return Maximum, nil
|
||||
|
||||
default:
|
||||
return 0, errors.New("unknown resolution")
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Device) SetResolution(r resolution) error {
|
||||
switch r {
|
||||
case Low:
|
||||
if err := d.Write(MCP9808_REG_RESOLUTION, []byte{0x00}); err != nil {
|
||||
return err
|
||||
}
|
||||
case Medium:
|
||||
if err := d.Write(MCP9808_REG_RESOLUTION, []byte{0x01}); err != nil {
|
||||
return err
|
||||
}
|
||||
case High:
|
||||
if err := d.Write(MCP9808_REG_RESOLUTION, []byte{0x02}); err != nil {
|
||||
return err
|
||||
}
|
||||
case Maximum:
|
||||
if err := d.Write(MCP9808_REG_RESOLUTION, []byte{0x03}); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Device) Write(register byte, data []byte) error {
|
||||
buf := append([]byte{register}, data...)
|
||||
return d.bus.Tx(d.Address, buf, nil)
|
||||
}
|
||||
|
||||
func (d *Device) Read(register byte, data *[]byte) error {
|
||||
return d.bus.Tx(d.Address, []byte{register}, *data)
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// Package mcp9808 implements a driver for the MCP9808 High Accuracy I2C Temperature Sensor
|
||||
//
|
||||
// Datasheet: https://cdn-shop.adafruit.com/datasheets/MCP9808.pdf
|
||||
// Module: https://www.adafruit.com/product/1782
|
||||
package mcp9808
|
||||
|
||||
// Constants/addresses used for I2C.
|
||||
const (
|
||||
MCP9808_DEVICE_ID = 0x0400
|
||||
MCP9808_MANUF_ID = 0x0054
|
||||
|
||||
MCP9808_I2CADDR_DEFAULT = 0x18 //default I2C address
|
||||
MCP9808_REG_CONFIG = 0x01 //MCP9808 config register
|
||||
|
||||
MCP9808_REG_CONFIG_SHUTDOWN = 0x0100 //shutdown config
|
||||
MCP9808_REG_CONFIG_CRITLOCKED = 0x0080 //critical trip lock
|
||||
MCP9808_REG_CONFIG_WINLOCKED = 0x0040 //alarm window lock
|
||||
MCP9808_REG_CONFIG_INTCLR = 0x0020 //interrupt clear
|
||||
MCP9808_REG_CONFIG_ALERTSTAT = 0x0010 //alert output status
|
||||
MCP9808_REG_CONFIG_ALERTCTRL = 0x0008 //alert output control
|
||||
MCP9808_REG_CONFIG_ALERTSEL = 0x0004 //alert output select
|
||||
MCP9808_REG_CONFIG_ALERTPOL = 0x0002 //alert output polarity
|
||||
MCP9808_REG_CONFIG_ALERTMODE = 0x0001 //alert output mode
|
||||
|
||||
MCP9808_REG_UPPER_TEMP = 0x02 //upper alert boundary
|
||||
MCP9808_REG_LOWER_TEMP = 0x03 //lower alert boundery
|
||||
MCP9808_REG_CRIT_TEMP = 0x04 //critical temperature
|
||||
MCP9808_REG_AMBIENT_TEMP = 0x05 //ambient temperature
|
||||
MCP9808_REG_MANUF_ID = 0x06 //manufacturer ID
|
||||
MCP9808_REG_DEVICE_ID = 0x07 //device ID
|
||||
MCP9808_REG_RESOLUTION = 0x08 //resolution
|
||||
)
|
||||
|
||||
/*
|
||||
======= ============ ==============
|
||||
value resolution reading Time
|
||||
======= ============ ==============
|
||||
|
||||
0 0.5°C 30 ms
|
||||
1 0.25°C 65 ms
|
||||
2 0.125°C 130 ms
|
||||
3 0.0625°C 250 ms
|
||||
|
||||
======= ============ ==============
|
||||
*/
|
||||
type resolution uint8
|
||||
|
||||
const (
|
||||
Low resolution = iota
|
||||
Medium
|
||||
High
|
||||
Maximum
|
||||
)
|
||||
@@ -36,6 +36,7 @@ var (
|
||||
ErrFamilyNotSupported = errors.New("Address family not supported")
|
||||
ErrProtocolNotSupported = errors.New("Socket protocol/type not supported")
|
||||
ErrStartingDHCPClient = errors.New("Error starting DHPC client")
|
||||
ErrStartingDHCPServer = errors.New("Error starting DHPC server")
|
||||
ErrNoMoreSockets = errors.New("No more sockets")
|
||||
ErrClosingSocket = errors.New("Error closing socket")
|
||||
ErrNotSupported = errors.New("Not supported")
|
||||
|
||||
+17
-9
@@ -27,8 +27,9 @@ type Config struct{}
|
||||
|
||||
// Errors list
|
||||
var (
|
||||
errNoPresence = errors.New("Error: OneWire. No devices on the bus.")
|
||||
errReadAddress = errors.New("Error: OneWire. Read address error: CRC mismatch.")
|
||||
errNoPresence = errors.New("Error: OneWire. No devices on the bus.")
|
||||
errTooManyDevices = errors.New("Error: OneWire. Too many devices on the bus.")
|
||||
errReadAddress = errors.New("Error: OneWire. Read address error: CRC mismatch.")
|
||||
)
|
||||
|
||||
// New creates a new GPIO 1-Wire connection.
|
||||
@@ -46,7 +47,7 @@ func (d *Device) Configure(config Config) {}
|
||||
func (d Device) Reset() error {
|
||||
d.p.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
time.Sleep(480 * time.Microsecond)
|
||||
d.p.Configure(machine.PinConfig{Mode: machine.PinInput})
|
||||
d.p.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
|
||||
time.Sleep(70 * time.Microsecond)
|
||||
precence := d.p.Get()
|
||||
time.Sleep(410 * time.Microsecond)
|
||||
@@ -61,11 +62,11 @@ func (d Device) WriteBit(data uint8) {
|
||||
d.p.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
if data&1 == 1 { // Send '1'
|
||||
time.Sleep(5 * time.Microsecond)
|
||||
d.p.Configure(machine.PinConfig{Mode: machine.PinInput})
|
||||
d.p.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
|
||||
time.Sleep(60 * time.Microsecond)
|
||||
} else { // Send '0'
|
||||
time.Sleep(60 * time.Microsecond)
|
||||
d.p.Configure(machine.PinConfig{Mode: machine.PinInput})
|
||||
d.p.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
|
||||
time.Sleep(5 * time.Microsecond)
|
||||
}
|
||||
}
|
||||
@@ -82,7 +83,7 @@ func (d Device) Write(data uint8) {
|
||||
func (d Device) ReadBit() (data uint8) {
|
||||
d.p.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
time.Sleep(3 * time.Microsecond)
|
||||
d.p.Configure(machine.PinConfig{Mode: machine.PinInput})
|
||||
d.p.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
|
||||
time.Sleep(8 * time.Microsecond)
|
||||
if d.p.Get() {
|
||||
data = 1
|
||||
@@ -111,7 +112,7 @@ func (d Device) ReadAddress() ([]uint8, error) {
|
||||
for i := 0; i < 8; i++ {
|
||||
romid[i] = d.Read()
|
||||
}
|
||||
if d.Сrc8(romid, 7) != romid[7] {
|
||||
if d.Сrc8(romid) != 0 {
|
||||
return nil, errReadAddress
|
||||
}
|
||||
return romid, nil
|
||||
@@ -187,15 +188,22 @@ func (d Device) Search(cmd uint8) ([][]uint8, error) {
|
||||
}
|
||||
d.WriteBit(bit)
|
||||
}
|
||||
if d.Сrc8(lastAddress) != 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
lastFork = lastZero
|
||||
copy(romIDs[romIndex], lastAddress)
|
||||
romIndex++
|
||||
if romIndex >= 32 {
|
||||
return romIDs, errTooManyDevices
|
||||
}
|
||||
}
|
||||
return romIDs[:romIndex:romIndex], nil
|
||||
}
|
||||
|
||||
// Crc8 compute a Dallas Semiconductor 8 bit CRC.
|
||||
func (d Device) Сrc8(buffer []uint8, size int) (crc uint8) {
|
||||
func (_ Device) Сrc8(buffer []uint8) (crc uint8) {
|
||||
// Dow-CRC using polynomial X^8 + X^5 + X^4 + X^0
|
||||
// Tiny 2x16 entry CRC table created by Arjen Lentz
|
||||
// See http://lentz.com.au/blog/calculating-crc-with-a-tiny-32-entry-lookup-table
|
||||
@@ -205,7 +213,7 @@ func (d Device) Сrc8(buffer []uint8, size int) (crc uint8) {
|
||||
0x00, 0x9D, 0x23, 0xBE, 0x46, 0xDB, 0x65, 0xF8,
|
||||
0x8C, 0x11, 0xAF, 0x32, 0xCA, 0x57, 0xE9, 0x74,
|
||||
}
|
||||
for i := 0; i < size; i++ {
|
||||
for i := 0; i < len(buffer); i++ {
|
||||
crc = buffer[i] ^ crc // just re-using crc as intermediate
|
||||
crc = crc8_table[crc&0x0f] ^ crc8_table[16+((crc>>4)&0x0f)]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
// Package pcf8591 implements a driver for the PCF8591 Analog to Digital/Digital to Analog Converter.
|
||||
//
|
||||
// Datasheet: https://www.nxp.com/docs/en/data-sheet/PCF8591.pdf
|
||||
package pcf8591 // import "tinygo.org/x/drivers/pcf8591"
|
||||
|
||||
import (
|
||||
"machine"
|
||||
|
||||
"errors"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
// Device wraps PCF8591 ADC functions.
|
||||
type Device struct {
|
||||
bus drivers.I2C
|
||||
Address uint16
|
||||
CH0 ADCPin
|
||||
CH1 ADCPin
|
||||
CH2 ADCPin
|
||||
CH3 ADCPin
|
||||
}
|
||||
|
||||
// ADCPin is the implementation of the ADConverter interface.
|
||||
type ADCPin struct {
|
||||
machine.Pin
|
||||
d *Device
|
||||
}
|
||||
|
||||
// New returns a new PCF8591 driver. Pass in a fully configured I2C bus.
|
||||
func New(b drivers.I2C) *Device {
|
||||
d := &Device{
|
||||
bus: b,
|
||||
Address: defaultAddress,
|
||||
}
|
||||
|
||||
// setup all channels
|
||||
d.CH0 = d.GetADC(0)
|
||||
d.CH1 = d.GetADC(1)
|
||||
d.CH2 = d.GetADC(2)
|
||||
d.CH3 = d.GetADC(3)
|
||||
|
||||
return d
|
||||
}
|
||||
|
||||
// Configure here just for interface compatibility.
|
||||
func (d *Device) Configure() {
|
||||
}
|
||||
|
||||
// Read analog data from channel
|
||||
func (d *Device) Read(ch int) (uint16, error) {
|
||||
if ch < 0 || ch > 3 {
|
||||
return 0, errors.New("invalid channel for pcf8591 Read")
|
||||
}
|
||||
|
||||
return d.GetADC(ch).Get(), nil
|
||||
}
|
||||
|
||||
// GetADC returns an ADC for a specific channel.
|
||||
func (d *Device) GetADC(ch int) ADCPin {
|
||||
return ADCPin{machine.Pin(ch), d}
|
||||
}
|
||||
|
||||
// Get the current reading for a specific ADCPin.
|
||||
func (p ADCPin) Get() uint16 {
|
||||
// TODO: also implement DAC
|
||||
tx := make([]byte, 2)
|
||||
tx[0] = byte(p.Pin)
|
||||
|
||||
rx := make([]byte, 2)
|
||||
|
||||
// The result from the measurement triggered by the first write,
|
||||
// however, the second write is required to get the result.
|
||||
// See section 8.4 "A/D Conversion" in the datasheet for more info
|
||||
p.d.bus.Tx(p.d.Address, tx, rx)
|
||||
p.d.bus.Tx(p.d.Address, tx, rx)
|
||||
|
||||
// scale result to 16bit value like other ADCs
|
||||
return uint16(rx[1] << 8)
|
||||
}
|
||||
|
||||
// Configure here just for interface compatibility.
|
||||
func (p ADCPin) Configure() {
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package pcf8591
|
||||
|
||||
// PCF8591 Default Address
|
||||
const defaultAddress = 0x48
|
||||
|
||||
// control bit for DAC
|
||||
const PCF8591_ENABLE_DAC = 0x40
|
||||
+85
-8
@@ -22,12 +22,13 @@ func NewImage[T Color](width, height int) Image[T] {
|
||||
}
|
||||
var zeroColor T
|
||||
var data unsafe.Pointer
|
||||
if zeroColor.BitsPerPixel()%8 == 0 {
|
||||
switch {
|
||||
case zeroColor.BitsPerPixel()%8 == 0:
|
||||
// Typical formats like RGB888 and RGB565.
|
||||
// Each color starts at a whole byte offset from the start.
|
||||
buf := make([]T, width*height)
|
||||
data = unsafe.Pointer(&buf[0])
|
||||
} else {
|
||||
default:
|
||||
// Formats like RGB444 that have 12 bits per pixel.
|
||||
// We access these as bytes, so allocate the buffer as a byte slice.
|
||||
bufBits := width * height * zeroColor.BitsPerPixel()
|
||||
@@ -42,6 +43,40 @@ func NewImage[T Color](width, height int) Image[T] {
|
||||
}
|
||||
}
|
||||
|
||||
// NewImageFromBytes creates a new image of the given size using an existing data slice of bytes.
|
||||
func NewImageFromBytes[T Color](width, height int, buf []byte) Image[T] {
|
||||
if width < 0 || height < 0 || int(int16(width)) != width || int(int16(height)) != height {
|
||||
// The width/height are stored as 16-bit integers and should never be
|
||||
// negative.
|
||||
panic("NewImageFromBytes: width/height out of bounds")
|
||||
}
|
||||
var zeroColor T
|
||||
var data unsafe.Pointer
|
||||
switch {
|
||||
case zeroColor.BitsPerPixel()%8 == 0:
|
||||
// Typical formats like RGB888 and RGB565.
|
||||
// Each color starts at a whole byte offset from the start.
|
||||
if len(buf) != width*height*int(unsafe.Sizeof(zeroColor)) {
|
||||
panic("NewImageFromBytes: data slice size mismatch")
|
||||
}
|
||||
data = unsafe.Pointer(&buf[0])
|
||||
default:
|
||||
// Formats like RGB444 that have 12 bits per pixel.
|
||||
// We access these as bytes, so allocate the buffer as a byte slice.
|
||||
bufBits := width * height * zeroColor.BitsPerPixel()
|
||||
bufBytes := (bufBits + 7) / 8
|
||||
if len(buf) != bufBytes {
|
||||
panic("NewImageFromBytes: data slice size mismatch")
|
||||
}
|
||||
data = unsafe.Pointer(&buf[0])
|
||||
}
|
||||
return Image[T]{
|
||||
width: int16(width),
|
||||
height: int16(height),
|
||||
data: data,
|
||||
}
|
||||
}
|
||||
|
||||
// Rescale returns a new Image buffer based on the img buffer.
|
||||
// The contents is undefined after the Rescale operation, and any modification
|
||||
// to the returned image will overwrite the underlying image buffer in undefined
|
||||
@@ -80,10 +115,11 @@ func (img Image[T]) Len() int {
|
||||
func (img Image[T]) RawBuffer() []uint8 {
|
||||
var zeroColor T
|
||||
var numBytes int
|
||||
if zeroColor.BitsPerPixel()%8 == 0 {
|
||||
switch {
|
||||
case zeroColor.BitsPerPixel()%8 == 0:
|
||||
// Each color starts at a whole byte offset.
|
||||
numBytes = int(unsafe.Sizeof(zeroColor)) * int(img.width) * int(img.height)
|
||||
} else {
|
||||
default:
|
||||
// Formats like RGB444 that aren't a whole number of bytes.
|
||||
numBits := zeroColor.BitsPerPixel() * int(img.width) * int(img.height)
|
||||
numBytes = (numBits + 7) / 8 // round up (see NewImage)
|
||||
@@ -99,7 +135,21 @@ func (img Image[T]) Size() (int, int) {
|
||||
func (img Image[T]) setPixel(index int, c T) {
|
||||
var zeroColor T
|
||||
|
||||
if zeroColor.BitsPerPixel()%8 == 0 {
|
||||
switch {
|
||||
case zeroColor.BitsPerPixel() == 1:
|
||||
// Monochrome.
|
||||
x := index % int(img.width)
|
||||
offset := index / 8
|
||||
|
||||
ptr := (*byte)(unsafe.Add(img.data, offset))
|
||||
if c != zeroColor {
|
||||
*((*byte)(ptr)) |= (1 << (7 - uint8(x%8)))
|
||||
} else {
|
||||
*((*byte)(ptr)) &^= (1 << (7 - uint8(x%8)))
|
||||
}
|
||||
|
||||
return
|
||||
case zeroColor.BitsPerPixel()%8 == 0:
|
||||
// Each color starts at a whole byte offset.
|
||||
// This is the easy case.
|
||||
offset := index * int(unsafe.Sizeof(zeroColor))
|
||||
@@ -147,7 +197,16 @@ func (img Image[T]) Get(x, y int) T {
|
||||
var zeroColor T
|
||||
index := y*int(img.width) + x // index into img.data
|
||||
|
||||
if zeroColor.BitsPerPixel()%8 == 0 {
|
||||
switch {
|
||||
case zeroColor.BitsPerPixel() == 1:
|
||||
// Monochrome.
|
||||
var c Monochrome
|
||||
offset := index / 8
|
||||
bits := index - (offset * 8)
|
||||
ptr := (*byte)(unsafe.Add(img.data, offset))
|
||||
c = ((*ptr >> (7 - uint8(bits))) & 0x1) > 0
|
||||
return any(c).(T)
|
||||
case zeroColor.BitsPerPixel()%8 == 0:
|
||||
// Colors like RGB565, RGB888, etc.
|
||||
offset := index * int(unsafe.Sizeof(zeroColor))
|
||||
ptr := unsafe.Add(img.data, offset)
|
||||
@@ -181,8 +240,26 @@ func (img Image[T]) Get(x, y int) T {
|
||||
func (img Image[T]) FillSolidColor(color T) {
|
||||
var zeroColor T
|
||||
|
||||
// Fast pass for colors of 8, 16, 24, etc bytes in size.
|
||||
if zeroColor.BitsPerPixel()%8 == 0 {
|
||||
switch {
|
||||
case zeroColor.BitsPerPixel() == 1:
|
||||
// Monochrome.
|
||||
var colorByte uint8
|
||||
if color != zeroColor {
|
||||
colorByte = 0xff
|
||||
}
|
||||
numBytes := int(img.width) * int(img.height) / 8
|
||||
for i := 0; i < numBytes; i++ {
|
||||
// TODO: this can be optimized a lot.
|
||||
// - The store can be done as a 32-bit integer, after checking for
|
||||
// alignment.
|
||||
// - Perhaps the loop can be unrolled to improve copy performance.
|
||||
ptr := (*byte)(unsafe.Add(img.data, i))
|
||||
*((*byte)(ptr)) = colorByte
|
||||
}
|
||||
return
|
||||
|
||||
case zeroColor.BitsPerPixel()%8 == 0:
|
||||
// Fast pass for colors of 8, 16, 24, etc bytes in size.
|
||||
ptr := img.data
|
||||
for i := 0; i < img.Len(); i++ {
|
||||
// TODO: this can be optimized a lot.
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package pixel_test
|
||||
|
||||
import (
|
||||
goimage "image"
|
||||
"image/color"
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
"tinygo.org/x/drivers/pixel"
|
||||
@@ -62,3 +64,183 @@ func TestImageRGB444BE(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestImageMonochrome(t *testing.T) {
|
||||
image := pixel.NewImage[pixel.Monochrome](128, 64)
|
||||
if width, height := image.Size(); width != 128 && height != 64 {
|
||||
t.Errorf("image.Size(): expected 128, 64 but got %d, %d", width, height)
|
||||
}
|
||||
for _, expected := range []color.RGBA{
|
||||
{R: 0xff, G: 0xff, B: 0xff},
|
||||
{G: 0xff},
|
||||
{R: 0xff, G: 0xff},
|
||||
{G: 0xff, B: 0xff},
|
||||
{R: 0x00},
|
||||
{G: 0x00, A: 0xff},
|
||||
{B: 0x00, A: 0xff},
|
||||
} {
|
||||
encoded := pixel.NewColor[pixel.Monochrome](expected.R, expected.G, expected.B)
|
||||
image.Set(5, 3, encoded)
|
||||
actual := image.Get(5, 3).RGBA()
|
||||
switch {
|
||||
case int(expected.R)+int(expected.G)+int(expected.B) > 128*3:
|
||||
// should be true eg white
|
||||
if actual.R == 0 || actual.G == 0 || actual.B == 0 {
|
||||
t.Errorf("failed to roundtrip color: expected %v but got %v", expected, actual)
|
||||
}
|
||||
default:
|
||||
// should be false eg black
|
||||
if actual.R != 0 || actual.G != 0 || actual.B != 0 {
|
||||
t.Errorf("failed to roundtrip color: expected %v but got %v", expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 128x128
|
||||
var rprofile = []byte{
|
||||
0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00,
|
||||
0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00,
|
||||
0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00,
|
||||
0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE8, 0x17, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0,
|
||||
0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE,
|
||||
0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x3F, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFE,
|
||||
0x3F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFE, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC,
|
||||
0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFE, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFE,
|
||||
0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC,
|
||||
0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFE,
|
||||
0x3F, 0xFF, 0xFC, 0x01, 0xF8, 0x00, 0x5F, 0xFF, 0xFF, 0xFC, 0x00, 0x02, 0x80, 0x1F, 0xFF, 0xFE, 0x3F, 0xFF, 0xFC, 0x03, 0xFE, 0x03, 0xFF, 0xFD, 0xBF, 0xFF, 0x80, 0x1F, 0xE0, 0x3F, 0xFF, 0xFC,
|
||||
0x3F, 0xFF, 0xFC, 0x03, 0xFE, 0x01, 0xFF, 0xF7, 0x6B, 0xFF, 0x80, 0x1F, 0xC0, 0x1F, 0xFF, 0xFE, 0x02, 0xFF, 0xFC, 0x03, 0xDF, 0x17, 0xFA, 0x00, 0x00, 0x37, 0xF0, 0x3F, 0xE0, 0x1F, 0xFF, 0xD0,
|
||||
0x00, 0x07, 0xFC, 0x07, 0x07, 0xBF, 0x00, 0x00, 0x00, 0x01, 0xFE, 0xF8, 0x78, 0x3F, 0xF8, 0x00, 0x00, 0x01, 0xFC, 0x06, 0x1B, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xEA, 0x78, 0x1F, 0x80, 0x00,
|
||||
0x00, 0x01, 0xFC, 0x03, 0x1B, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xE2, 0x68, 0x1F, 0xC0, 0x00, 0x00, 0x01, 0xFC, 0x07, 0x5B, 0xE8, 0x00, 0x00, 0x00, 0x00, 0x07, 0xC4, 0x38, 0x1F, 0x80, 0x00,
|
||||
0x00, 0x01, 0xF8, 0x03, 0x3F, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF7, 0x78, 0x3F, 0xC0, 0x00, 0x00, 0x01, 0xFC, 0x03, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0x68, 0x1F, 0x80, 0x00,
|
||||
0x00, 0x01, 0xFC, 0x07, 0x9F, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x70, 0x1F, 0x80, 0x00, 0x00, 0x01, 0xFC, 0x03, 0x9E, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3E, 0xE8, 0x1F, 0xC0, 0x00,
|
||||
0x00, 0x01, 0xFC, 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xE0, 0x1F, 0x80, 0x00, 0x00, 0x01, 0xF8, 0x01, 0xFF, 0x55, 0xF8, 0x00, 0x00, 0x03, 0xEA, 0xFF, 0xC0, 0x1F, 0xC0, 0x00,
|
||||
0x00, 0x01, 0xFC, 0x01, 0xFE, 0xAF, 0xF0, 0x00, 0x00, 0x03, 0xFD, 0xBF, 0xE0, 0x1F, 0x80, 0x00, 0x00, 0x01, 0xFC, 0x01, 0xF8, 0x00, 0x7C, 0x00, 0x00, 0x07, 0x80, 0x03, 0xE0, 0x1F, 0xC0, 0x00,
|
||||
0x00, 0x01, 0xFC, 0x03, 0xC1, 0xE0, 0x3E, 0x00, 0x00, 0x1F, 0x03, 0xC0, 0xF0, 0x1F, 0x80, 0x00, 0x00, 0x00, 0xF8, 0x07, 0x82, 0xF8, 0x0F, 0x00, 0x00, 0x3E, 0x05, 0xE0, 0x7C, 0x1F, 0x80, 0x00,
|
||||
0x00, 0x01, 0xFC, 0x07, 0x01, 0xF8, 0x17, 0x00, 0x00, 0x3C, 0x09, 0xE0, 0x78, 0x1F, 0xC0, 0x00, 0x00, 0x03, 0xFC, 0x0F, 0x06, 0xFC, 0x07, 0x80, 0x00, 0x7C, 0x1D, 0xE0, 0x3C, 0x1F, 0xC0, 0x00,
|
||||
0x00, 0xFF, 0xFC, 0x1E, 0x03, 0xFC, 0x03, 0x80, 0x00, 0x78, 0x1F, 0xE0, 0x1E, 0x1F, 0xFE, 0x80, 0x2F, 0xFF, 0xFC, 0x1C, 0x07, 0xF8, 0x01, 0x80, 0x00, 0x60, 0x1F, 0xE0, 0x16, 0x1F, 0xFF, 0xF8,
|
||||
0x1F, 0xFF, 0xF8, 0x3E, 0x07, 0xFC, 0x01, 0xC0, 0x00, 0xE8, 0x1F, 0xE0, 0x1E, 0x1F, 0xFF, 0xEC, 0x3F, 0xFF, 0xFC, 0x3C, 0x03, 0xF8, 0x01, 0xC0, 0x00, 0xE0, 0x17, 0xE0, 0x07, 0x1F, 0xFF, 0xFE,
|
||||
0x3F, 0xFF, 0xFC, 0x38, 0x03, 0xE8, 0x00, 0xC0, 0x00, 0xC0, 0x07, 0xC0, 0x03, 0x1F, 0xFF, 0xFC, 0x3F, 0xFF, 0xFC, 0x38, 0x00, 0xC0, 0x00, 0xE0, 0x00, 0xC0, 0x00, 0x00, 0x03, 0x9F, 0xFF, 0xFE,
|
||||
0x3F, 0xFF, 0xFC, 0x78, 0x01, 0xE0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0x00, 0x03, 0x1F, 0xFF, 0xFC, 0x3F, 0xFF, 0xFC, 0x38, 0x00, 0x00, 0x00, 0xE0, 0x01, 0xC0, 0x00, 0x00, 0x03, 0x9F, 0xFF, 0xFE,
|
||||
0x3F, 0xFF, 0xF8, 0x78, 0x00, 0x00, 0x00, 0xE0, 0x01, 0xC0, 0x00, 0x00, 0x03, 0x9F, 0xFF, 0xFE, 0x3F, 0xFF, 0xFC, 0x68, 0x00, 0x00, 0x00, 0xE0, 0x01, 0xC0, 0x00, 0x00, 0x03, 0x9F, 0xFF, 0xFC,
|
||||
0x3F, 0xFF, 0xFC, 0x70, 0x00, 0x00, 0x00, 0xE0, 0x01, 0xC0, 0x00, 0x00, 0x01, 0x9F, 0xFF, 0xFE, 0x3F, 0xFF, 0xFC, 0x78, 0x00, 0x00, 0x00, 0xE0, 0x01, 0xC0, 0x00, 0x00, 0x03, 0x9F, 0xFF, 0xFE,
|
||||
0x3F, 0xFF, 0xFC, 0x68, 0x00, 0x00, 0x00, 0xE0, 0x01, 0xC0, 0x00, 0x00, 0x03, 0x9F, 0xFF, 0xFE, 0x3F, 0xFF, 0xF8, 0x70, 0x00, 0x00, 0x00, 0xC0, 0x01, 0xC0, 0x00, 0x00, 0x01, 0x9F, 0xFF, 0xFC,
|
||||
0x3F, 0xFF, 0xFC, 0x68, 0x00, 0x00, 0x00, 0xE0, 0x01, 0xC0, 0x00, 0x00, 0x03, 0x9F, 0xFF, 0xFE, 0x3F, 0xFF, 0xFC, 0x38, 0x00, 0x00, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0x00, 0x03, 0x9F, 0xFF, 0xFE,
|
||||
0x07, 0xFF, 0xFC, 0x78, 0x00, 0x00, 0x00, 0xC0, 0x80, 0xC0, 0x00, 0x00, 0x03, 0x1F, 0xFF, 0xF8, 0x00, 0x37, 0xF8, 0x38, 0x00, 0x00, 0x01, 0xC7, 0xF0, 0xE0, 0x00, 0x00, 0x03, 0x9F, 0xFE, 0x00,
|
||||
0x00, 0x5F, 0xFC, 0x38, 0x00, 0x00, 0x01, 0xC3, 0xE8, 0xE0, 0x00, 0x00, 0x03, 0x1F, 0xFB, 0x00, 0x00, 0x01, 0xFC, 0x3C, 0x00, 0x00, 0x01, 0xC7, 0xF8, 0xE0, 0x00, 0x00, 0x07, 0x1F, 0xC0, 0x00,
|
||||
0x00, 0x01, 0xFC, 0x3C, 0x00, 0x00, 0x03, 0x9F, 0xFC, 0x70, 0x00, 0x00, 0x07, 0x1F, 0x80, 0x00, 0x00, 0x01, 0xF8, 0x1E, 0x00, 0x00, 0x03, 0xBF, 0xFE, 0x78, 0x00, 0x00, 0x1E, 0x1F, 0xC0, 0x00,
|
||||
0x00, 0x01, 0xFC, 0x1E, 0x00, 0x00, 0x07, 0x9F, 0xFF, 0x78, 0x00, 0x00, 0x16, 0x1F, 0x80, 0x00, 0x00, 0x01, 0xFC, 0x1E, 0x00, 0x00, 0x07, 0x73, 0xC3, 0x3C, 0x00, 0x00, 0x1E, 0x1F, 0xC0, 0x00,
|
||||
0x00, 0x01, 0xFC, 0x1F, 0x00, 0x00, 0x1E, 0x60, 0x01, 0x9E, 0x00, 0x00, 0x3C, 0x1F, 0x80, 0x00, 0x00, 0x01, 0xF8, 0x1F, 0x80, 0x00, 0x7E, 0x40, 0x01, 0x17, 0x80, 0x00, 0x7E, 0x1F, 0xC0, 0x00,
|
||||
0x00, 0x01, 0xFC, 0x1F, 0x80, 0x00, 0x3C, 0x40, 0x01, 0x9F, 0x00, 0x00, 0x7C, 0x1F, 0x80, 0x00, 0x00, 0x01, 0xFC, 0x1F, 0xC0, 0x00, 0xFC, 0x40, 0x01, 0x07, 0xC0, 0x00, 0xFC, 0x1F, 0xC0, 0x00,
|
||||
0x00, 0x01, 0xFC, 0x1F, 0xF8, 0x0B, 0xE8, 0x7B, 0xFF, 0x81, 0xF8, 0x03, 0xFC, 0x1F, 0x80, 0x00, 0x00, 0x03, 0xF8, 0x1C, 0xFF, 0xFF, 0xC0, 0x3F, 0xFE, 0x00, 0xFF, 0xFF, 0xDE, 0x1F, 0xC0, 0x00,
|
||||
0x00, 0x05, 0xFC, 0x1C, 0xFF, 0xFF, 0x80, 0x7F, 0xDE, 0x00, 0xFF, 0x7F, 0xDC, 0x1F, 0x80, 0x00, 0x00, 0xFF, 0xFC, 0x1C, 0x3F, 0xFE, 0x00, 0x0E, 0xB8, 0x00, 0x3F, 0xFF, 0x1C, 0x1F, 0xFC, 0x00,
|
||||
0x2F, 0xFF, 0xF8, 0x1C, 0x00, 0x00, 0x00, 0x04, 0x98, 0x00, 0x01, 0x00, 0x1E, 0x1F, 0xFF, 0xF4, 0x3F, 0xFF, 0xFC, 0x3C, 0x00, 0x00, 0x00, 0x06, 0x98, 0x00, 0x00, 0x00, 0x1C, 0x1F, 0xFF, 0xFE,
|
||||
0x3F, 0xFF, 0xFC, 0x1C, 0x00, 0x00, 0x00, 0x06, 0x98, 0x00, 0x00, 0x00, 0x1E, 0x1F, 0xFF, 0xFE, 0x7F, 0xFF, 0xF8, 0x3C, 0x00, 0x00, 0x00, 0x02, 0xB8, 0x00, 0x00, 0x00, 0x1C, 0x1F, 0xFF, 0xFE,
|
||||
0x3F, 0xFF, 0xFC, 0x1C, 0x00, 0x00, 0x00, 0x03, 0xE8, 0x00, 0x00, 0x00, 0x1C, 0x1F, 0xFF, 0xFE, 0x7F, 0xFF, 0xFC, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1E, 0x1F, 0xFF, 0xFE,
|
||||
0x3F, 0xFF, 0xFC, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x1C, 0x1F, 0xFF, 0xFE, 0x7F, 0xFF, 0xF8, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1E, 0x1F, 0xFF, 0xFE,
|
||||
0x3F, 0xFF, 0xFC, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x1F, 0xFF, 0xFE, 0x7F, 0xFF, 0xFC, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1E, 0x1F, 0xFF, 0xFE,
|
||||
0x3F, 0xFF, 0xFC, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x1F, 0xFF, 0xFE, 0x7F, 0xFF, 0xF8, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x1F, 0xFF, 0xFE,
|
||||
0x7F, 0xFF, 0xFC, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1E, 0x1F, 0xFF, 0xFE, 0x3F, 0xFF, 0xFC, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x1F, 0xFF, 0xFE,
|
||||
0x7F, 0xFF, 0xFC, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x1F, 0xFF, 0xFE, 0x0B, 0xFF, 0xF8, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x1F, 0xFF, 0xF8,
|
||||
0x00, 0x7F, 0xFC, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1E, 0x1F, 0xFE, 0x00, 0x00, 0x01, 0xFC, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x1F, 0xC0, 0x00,
|
||||
0x00, 0x03, 0xFC, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x1F, 0x80, 0x00, 0x00, 0x01, 0xF8, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x1F, 0xC0, 0x00,
|
||||
0x00, 0x01, 0xFC, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1E, 0x1F, 0x80, 0x00, 0x00, 0x01, 0xFC, 0x38, 0x15, 0xB7, 0x80, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x1C, 0x1F, 0xC0, 0x00,
|
||||
0x00, 0x01, 0xF8, 0x3C, 0x16, 0xAB, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x16, 0x1F, 0x80, 0x00, 0x00, 0x01, 0xFC, 0x3C, 0x3F, 0xFB, 0x80, 0x00, 0x00, 0xFF, 0x80, 0x00, 0x1C, 0x1F, 0xC0, 0x00,
|
||||
0x00, 0x01, 0xFC, 0x3C, 0x1F, 0xE9, 0x00, 0x00, 0x01, 0xF7, 0x80, 0x00, 0x1E, 0x1F, 0x80, 0x00, 0x00, 0x01, 0xF8, 0x3C, 0x03, 0x82, 0x1D, 0xC6, 0x39, 0xC0, 0x07, 0x00, 0x14, 0x1F, 0xC0, 0x00,
|
||||
0x00, 0x01, 0xFC, 0x3C, 0x03, 0x87, 0x14, 0x85, 0x15, 0xC1, 0x03, 0x80, 0x1E, 0x1F, 0x80, 0x00, 0x00, 0x01, 0xFC, 0x3C, 0x03, 0x87, 0x9F, 0xE6, 0x3D, 0xC0, 0x1F, 0xC0, 0x1C, 0x1F, 0xC0, 0x00,
|
||||
0x00, 0xBF, 0xF8, 0x3C, 0x03, 0x83, 0x9F, 0xE7, 0x3F, 0x8D, 0x1E, 0xC0, 0x16, 0x1F, 0xD4, 0x00, 0x17, 0xFF, 0xFC, 0x3C, 0x03, 0x83, 0x9E, 0xE7, 0x79, 0xD7, 0xBC, 0xE0, 0x1C, 0x1F, 0xFF, 0xE8,
|
||||
0x1F, 0xFF, 0xFC, 0x3C, 0x03, 0x83, 0x9C, 0xE3, 0x3F, 0x9F, 0xBC, 0xE0, 0x1E, 0x1F, 0xFF, 0xD0, 0x3F, 0xFF, 0xF8, 0x3C, 0x03, 0x83, 0x9E, 0xE7, 0x79, 0xC7, 0xBC, 0xE0, 0x16, 0x1F, 0xFF, 0xFE,
|
||||
0x3F, 0xFF, 0xFC, 0x3C, 0x03, 0x83, 0xBC, 0xE3, 0xF9, 0xC3, 0xBC, 0xE0, 0x1C, 0x1F, 0xFF, 0xFC, 0x3F, 0xFF, 0xFC, 0x3C, 0x03, 0x83, 0x9E, 0xEB, 0xE9, 0xE3, 0xBD, 0xE0, 0x1E, 0x1F, 0xFF, 0xFE,
|
||||
0x3F, 0xFF, 0xF8, 0x3C, 0x03, 0x83, 0x9C, 0xE3, 0xF1, 0xC3, 0x9C, 0xC0, 0x1C, 0x1F, 0xFF, 0xFC, 0x3F, 0xFF, 0xFC, 0x3C, 0x03, 0x83, 0x9E, 0xE9, 0xE0, 0xFF, 0x9F, 0xC0, 0x16, 0x1F, 0xFF, 0xFE,
|
||||
0x3F, 0xFF, 0xFC, 0x3C, 0x03, 0x83, 0xBC, 0x61, 0xE0, 0xFF, 0x07, 0xC0, 0x1E, 0x1F, 0xFF, 0xFE, 0x3F, 0xFF, 0xFC, 0x3C, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x1C, 0x3F, 0xFF, 0xFC,
|
||||
0x3F, 0xFF, 0xF8, 0x3C, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00, 0x1E, 0x1F, 0xFF, 0xFE, 0x3F, 0xFF, 0xFC, 0x3C, 0x00, 0x00, 0x00, 0x07, 0xC0, 0x00, 0x00, 0x00, 0x1C, 0x1F, 0xFF, 0xFC,
|
||||
0x3F, 0xFF, 0xFC, 0x3C, 0x00, 0x00, 0x00, 0x17, 0xC0, 0x00, 0x00, 0x00, 0x16, 0x3F, 0xFF, 0xFE, 0x3F, 0xFF, 0xFC, 0x3C, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x1E, 0x3F, 0xFF, 0xFE,
|
||||
0x3F, 0xFF, 0xFC, 0x3C, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x1E, 0x3F, 0xFF, 0xFC, 0x3F, 0xFF, 0xFE, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x7F, 0xFF, 0xFE,
|
||||
0x2F, 0xFF, 0xFF, 0xBC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0x80, 0x5F, 0xFF, 0xFF, 0xF8, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00,
|
||||
0x01, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xA0, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00,
|
||||
0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00,
|
||||
0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
}
|
||||
|
||||
func TestImageFromBytesMonochrome(t *testing.T) {
|
||||
image := pixel.NewImageFromBytes[pixel.Monochrome](128, 128, rprofile)
|
||||
if width, height := image.Size(); width != 128 && height != 128 {
|
||||
t.Errorf("image.Size(): expected 128, 128 but got %d, %d", width, height)
|
||||
}
|
||||
|
||||
raw := image.RawBuffer()
|
||||
for i, b := range raw {
|
||||
if b != rprofile[i] {
|
||||
t.Fatalf("failed to roundtrip image. expected %v but got %v", rprofile[i], b)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test pixel formats by filling them with noise and checking whether they
|
||||
// contain the same data afterwards.
|
||||
func TestImageNoise(t *testing.T) {
|
||||
t.Run("RGB888", func(t *testing.T) {
|
||||
testImageNoise[pixel.RGB888](t)
|
||||
})
|
||||
t.Run("RGB565BE", func(t *testing.T) {
|
||||
testImageNoise[pixel.RGB565BE](t)
|
||||
})
|
||||
t.Run("RGB555", func(t *testing.T) {
|
||||
testImageNoise[pixel.RGB555](t)
|
||||
})
|
||||
t.Run("RGB444BE", func(t *testing.T) {
|
||||
testImageNoise[pixel.RGB444BE](t)
|
||||
})
|
||||
t.Run("Monochrome", func(t *testing.T) {
|
||||
testImageNoise[pixel.Monochrome](t)
|
||||
})
|
||||
}
|
||||
|
||||
func testImageNoise[T pixel.Color](t *testing.T) {
|
||||
// Create an image of a random width/height for extra testing.
|
||||
width := rand.Int()%500 + 10
|
||||
height := rand.Int()%500 + 10
|
||||
t.Log("image size:", width, height)
|
||||
|
||||
// Create two images: the to-be-tested image object and a reference image.
|
||||
img := pixel.NewImage[T](width, height)
|
||||
ref := goimage.NewRGBA(goimage.Rect(0, 0, width, height))
|
||||
|
||||
// Fill the two images with noise.
|
||||
for y := 0; y < height; y++ {
|
||||
for x := 0; x < width; x++ {
|
||||
// Set a random color in both images.
|
||||
c := pixel.NewColor[T](uint8(rand.Uint32()), uint8(rand.Uint32()), uint8(rand.Uint32()))
|
||||
img.Set(x, y, c)
|
||||
ref.Set(x, y, c.RGBA())
|
||||
}
|
||||
}
|
||||
|
||||
// Compare the two images. They should match.
|
||||
mismatch := 0
|
||||
firstX := 0
|
||||
firstY := 0
|
||||
var firstExpected, firstActual color.RGBA
|
||||
for y := 0; y < height; y++ {
|
||||
for x := 0; x < width; x++ {
|
||||
c := img.Get(x, y).RGBA()
|
||||
r2, g2, b2, _ := ref.At(x, y).RGBA()
|
||||
c2 := color.RGBA{R: uint8(r2 >> 8), G: uint8(g2 >> 8), B: uint8(b2 >> 8), A: 255}
|
||||
if c != c2 {
|
||||
mismatch++
|
||||
if mismatch == 1 {
|
||||
firstX = x
|
||||
firstY = y
|
||||
firstExpected = c
|
||||
firstActual = c2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if mismatch != 0 {
|
||||
t.Errorf("mismatch found: %d pixels are different (first diff at (%d, %d), expected %v, actual %v)", mismatch, firstX, firstY, firstExpected, firstActual)
|
||||
}
|
||||
}
|
||||
|
||||
+33
-2
@@ -14,9 +14,9 @@ import (
|
||||
|
||||
// Pixel with a particular color, matching the underlying hardware of a
|
||||
// particular display. Each pixel is at least 1 byte in size.
|
||||
// The color format is sRGB (or close to it) in all cases.
|
||||
// The color format is sRGB (or close to it) in all cases except for 1-bit.
|
||||
type Color interface {
|
||||
RGB888 | RGB565BE | RGB555 | RGB444BE
|
||||
RGB888 | RGB565BE | RGB555 | RGB444BE | Monochrome
|
||||
|
||||
BaseColor
|
||||
}
|
||||
@@ -50,6 +50,8 @@ func NewColor[T Color](r, g, b uint8) T {
|
||||
return any(NewRGB555(r, g, b)).(T)
|
||||
case RGB444BE:
|
||||
return any(NewRGB444BE(r, g, b)).(T)
|
||||
case Monochrome:
|
||||
return any(NewMonochrome(r, g, b)).(T)
|
||||
default:
|
||||
panic("unknown color format")
|
||||
}
|
||||
@@ -202,6 +204,35 @@ func (c RGB444BE) RGBA() color.RGBA {
|
||||
return color
|
||||
}
|
||||
|
||||
type Monochrome bool
|
||||
|
||||
func NewMonochrome(r, g, b uint8) Monochrome {
|
||||
// Very simple black/white split.
|
||||
// This isn't very accurate (especially for sRGB colors) but is close enough.
|
||||
if int(r)+int(g)+int(b) > 128*3 { // light, convert to white
|
||||
return Monochrome(true)
|
||||
}
|
||||
// dark, convert to black
|
||||
return Monochrome(false)
|
||||
}
|
||||
|
||||
func (c Monochrome) BitsPerPixel() int {
|
||||
return 1
|
||||
}
|
||||
|
||||
func (c Monochrome) RGBA() color.RGBA {
|
||||
value := uint8(0)
|
||||
if c {
|
||||
value = 255
|
||||
}
|
||||
return color.RGBA{
|
||||
R: value,
|
||||
G: value,
|
||||
B: value,
|
||||
A: 255,
|
||||
}
|
||||
}
|
||||
|
||||
// Gamma brightness lookup table:
|
||||
// https://victornpb.github.io/gamma-table-generator
|
||||
// gamma = 0.45 steps = 256 range = 0-255
|
||||
|
||||
+66
-7
@@ -32,8 +32,9 @@ var (
|
||||
)
|
||||
|
||||
const (
|
||||
O_NONBLOCK = 1 // note: different value than syscall.O_NONBLOCK (0x800)
|
||||
RTW_MODE_STA = 0x00000001
|
||||
O_NONBLOCK = 1 // note: different value than syscall.O_NONBLOCK (0x800)
|
||||
RTW_MODE_STA = 0x00000001
|
||||
defaultChannel = 6
|
||||
)
|
||||
|
||||
type sock int32
|
||||
@@ -135,6 +136,51 @@ func (r *rtl8720dn) connectToAP() error {
|
||||
return r.startDhcpc()
|
||||
}
|
||||
|
||||
func (r *rtl8720dn) startDhcps() error {
|
||||
if result := r.rpc_tcpip_adapter_dhcps_start(0); result == -1 {
|
||||
return netdev.ErrStartingDHCPServer
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *rtl8720dn) startAP() error {
|
||||
if len(r.params.Ssid) == 0 {
|
||||
return netlink.ErrMissingSSID
|
||||
}
|
||||
|
||||
if len(r.params.Passphrase) != 0 && len(r.params.Passphrase) < 8 {
|
||||
return netlink.ErrShortPassphrase
|
||||
}
|
||||
|
||||
if debugging(debugBasic) {
|
||||
fmt.Printf("Starting Wifi AP as SSID '%s'...", r.params.Ssid)
|
||||
}
|
||||
|
||||
// Start the connection process
|
||||
securityType := uint32(0) // RTW_SECURITY_OPEN
|
||||
if len(r.params.Passphrase) != 0 {
|
||||
securityType = 0x00400004 // RTW_SECURITY_WPA2_AES_PSK
|
||||
}
|
||||
|
||||
result := r.rpc_wifi_start_ap(r.params.Ssid, r.params.Passphrase, securityType, defaultChannel)
|
||||
if result != 0 {
|
||||
if debugging(debugBasic) {
|
||||
fmt.Printf("FAILED\r\n")
|
||||
}
|
||||
return netlink.ErrConnectFailed
|
||||
}
|
||||
|
||||
if debugging(debugBasic) {
|
||||
fmt.Printf("LISTENING\r\n")
|
||||
}
|
||||
|
||||
if r.notifyCb != nil {
|
||||
r.notifyCb(netlink.EventNetUp)
|
||||
}
|
||||
|
||||
return r.startDhcps()
|
||||
}
|
||||
|
||||
func (r *rtl8720dn) showDriver() {
|
||||
if r.driverShown {
|
||||
return
|
||||
@@ -246,14 +292,27 @@ func (r *rtl8720dn) netConnect(reset bool) error {
|
||||
}
|
||||
r.showDevice()
|
||||
|
||||
retry:
|
||||
for i := 0; r.params.Retries == 0 || i < r.params.Retries; i++ {
|
||||
if err := r.connectToAP(); err != nil {
|
||||
if err == netlink.ErrConnectFailed {
|
||||
continue
|
||||
switch r.params.ConnectMode {
|
||||
case netlink.ConnectModeAP:
|
||||
if err := r.startAP(); err != nil {
|
||||
if err == netlink.ErrConnectFailed {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
return err
|
||||
break retry
|
||||
|
||||
default:
|
||||
if err := r.connectToAP(); err != nil {
|
||||
if err == netlink.ErrConnectFailed {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
break retry
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if r.networkDown() {
|
||||
|
||||
+36
-1
@@ -1,6 +1,12 @@
|
||||
package servo
|
||||
|
||||
import "machine"
|
||||
import (
|
||||
"machine"
|
||||
|
||||
"errors"
|
||||
)
|
||||
|
||||
var ErrInvalidAngle = errors.New("servo: invalid angle")
|
||||
|
||||
// PWM is the interface necessary for controlling typical servo motors.
|
||||
type PWM interface {
|
||||
@@ -80,3 +86,32 @@ func (s Servo) SetMicroseconds(microseconds int16) {
|
||||
value := uint64(s.pwm.Top()) * uint64(microseconds) / (pwmPeriod / 1000)
|
||||
s.pwm.Set(s.channel, uint32(value))
|
||||
}
|
||||
|
||||
// SetAngle sets the angle of the servo in degrees. The angle should be between
|
||||
// 0 and 180, where 0 is the minimum angle and 180 is the maximum angle.
|
||||
// This function should work for most servos, but if it doesn't work for yours
|
||||
// you can use SetMicroseconds directly instead.
|
||||
func (s Servo) SetAngle(angle int) error {
|
||||
if angle < 0 || angle > 180 {
|
||||
return ErrInvalidAngle
|
||||
}
|
||||
|
||||
// 0° is 1000µs, 180° is 2000µs. See explanation in SetMicroseconds.
|
||||
microseconds := angle*1000/180 + 1000
|
||||
s.SetMicroseconds(int16(microseconds))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetAngleWithMicroseconds sets the angle of the servo in degrees. The angle should be between
|
||||
// 0 and 180, where 0 is the minimum angle and 180 is the maximum angle.
|
||||
// The high duration can be customized
|
||||
// 0° is lowMicroseconds(us), 180° is highMicroseconds(us)
|
||||
func (s Servo) SetAngleWithMicroseconds(angle int, lowMicroseconds, highMicroseconds int) error {
|
||||
if angle < 0 || angle > 180 {
|
||||
return ErrInvalidAngle
|
||||
}
|
||||
microseconds := lowMicroseconds + (highMicroseconds-lowMicroseconds)*angle/180
|
||||
s.SetMicroseconds(int16(microseconds))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -73,13 +73,16 @@ tinygo build -size short -o ./build/test.hex -target=microbit ./examples/st7789/
|
||||
tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/thermistor/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=circuitplay-bluefruit ./examples/tone
|
||||
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/tm1637/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=pico ./examples/touch/capacitive
|
||||
tinygo build -size short -o ./build/test.hex -target=pyportal ./examples/touch/resistive/fourwire/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=pyportal ./examples/touch/resistive/pyportal_touchpaint/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/vl53l1x/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/vl6180x/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=feather-nrf52840-sense ./examples/waveshare-epd/epd1in54/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/waveshare-epd/epd2in13/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/waveshare-epd/epd2in13x/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/waveshare-epd/epd4in2/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=pico ./examples/waveshare-epd/epd2in66b/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/ws2812
|
||||
tinygo build -size short -o ./build/test.bin -target=m5stamp-c3 ./examples/ws2812
|
||||
tinygo build -size short -o ./build/test.hex -target=feather-nrf52840 ./examples/is31fl3731/main.go
|
||||
@@ -105,7 +108,9 @@ tinygo build -size short -o ./build/test.hex -target=xiao ./examples/pcf8563/clk
|
||||
tinygo build -size short -o ./build/test.hex -target=xiao ./examples/pcf8563/time/
|
||||
tinygo build -size short -o ./build/test.hex -target=xiao ./examples/pcf8563/timer/
|
||||
tinygo build -size short -o ./build/test.hex -target=pico ./examples/qmi8658c/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=feather-rp2040 ./examples/pcf8591/
|
||||
tinygo build -size short -o ./build/test.hex -target=feather-m0 ./examples/ina260/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=feather-m0 ./examples/ina219/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=nucleo-l432kc ./examples/aht20/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=feather-m4 ./examples/sdcard/console/
|
||||
tinygo build -size short -o ./build/test.hex -target=feather-m4 ./examples/i2csoft/adt7410/
|
||||
@@ -130,6 +135,7 @@ tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/n
|
||||
tinygo build -size short -o ./build/test.uf2 -target=pico ./examples/mpu9150/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=macropad-rp2040 ./examples/sh1106/macropad_spi
|
||||
tinygo build -size short -o ./build/test.hex -target=macropad-rp2040 ./examples/encoders/quadrature-interrupt
|
||||
tinygo build -size short -o ./build/test.uf2 -target=pico ./examples/mcp9808/main.go
|
||||
# network examples (espat)
|
||||
tinygo build -size short -o ./build/test.hex -target=challenger-rp2040 ./examples/net/ntpclient/
|
||||
# network examples (wifinina)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package ssd1306
|
||||
|
||||
import "tinygo.org/x/drivers"
|
||||
|
||||
// Registers
|
||||
const (
|
||||
Address = 0x3D
|
||||
@@ -38,4 +40,7 @@ const (
|
||||
|
||||
EXTERNALVCC VccMode = 0x1
|
||||
SWITCHCAPVCC VccMode = 0x2
|
||||
|
||||
NO_ROTATION = drivers.Rotation0
|
||||
ROTATION_180 = drivers.Rotation180
|
||||
)
|
||||
|
||||
+62
-4
@@ -11,6 +11,12 @@ import (
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
"tinygo.org/x/drivers/internal/legacy"
|
||||
"tinygo.org/x/drivers/pixel"
|
||||
)
|
||||
|
||||
var (
|
||||
errBufferSize = errors.New("invalid size buffer")
|
||||
errOutOfRange = errors.New("out of screen range")
|
||||
)
|
||||
|
||||
type ResetValue [2]byte
|
||||
@@ -26,6 +32,7 @@ type Device struct {
|
||||
canReset bool
|
||||
resetCol ResetValue
|
||||
resetPage ResetValue
|
||||
rotation drivers.Rotation
|
||||
}
|
||||
|
||||
// Config is the configuration for the display
|
||||
@@ -41,6 +48,7 @@ type Config struct {
|
||||
// If you're using a different size, you might need to set these values manually.
|
||||
ResetCol ResetValue
|
||||
ResetPage ResetValue
|
||||
Rotation drivers.Rotation
|
||||
}
|
||||
|
||||
type I2CBus struct {
|
||||
@@ -142,8 +150,8 @@ func (d *Device) Configure(cfg Config) {
|
||||
}
|
||||
d.Command(MEMORYMODE)
|
||||
d.Command(0x00)
|
||||
d.Command(SEGREMAP | 0x1)
|
||||
d.Command(COMSCANDEC)
|
||||
|
||||
d.SetRotation(cfg.Rotation)
|
||||
|
||||
if (d.width == 128 && d.height == 64) || (d.width == 64 && d.height == 48) { // 128x64 or 64x48
|
||||
d.Command(SETCOMPINS)
|
||||
@@ -245,8 +253,7 @@ func (d *Device) GetPixel(x int16, y int16) bool {
|
||||
// SetBuffer changes the whole buffer at once
|
||||
func (d *Device) SetBuffer(buffer []byte) error {
|
||||
if int16(len(buffer)) != d.bufferSize {
|
||||
//return ErrBuffer
|
||||
return errors.New("wrong size buffer")
|
||||
return errBufferSize
|
||||
}
|
||||
for i := int16(0); i < d.bufferSize; i++ {
|
||||
d.buffer[i] = buffer[i]
|
||||
@@ -338,3 +345,54 @@ func (b *SPIBus) tx(data []byte, isCommand bool) error {
|
||||
func (d *Device) Size() (w, h int16) {
|
||||
return d.width, d.height
|
||||
}
|
||||
|
||||
// DrawBitmap copies the bitmap to the screen at the given coordinates.
|
||||
func (d *Device) DrawBitmap(x, y int16, bitmap pixel.Image[pixel.Monochrome]) error {
|
||||
width, height := bitmap.Size()
|
||||
if x < 0 || x+int16(width) > d.width || y < 0 || y+int16(height) > d.height {
|
||||
return errOutOfRange
|
||||
}
|
||||
|
||||
for i := 0; i < width; i++ {
|
||||
for j := 0; j < height; j++ {
|
||||
d.SetPixel(x+int16(i), y+int16(j), bitmap.Get(i, j).RGBA())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Rotation returns the currently configured rotation.
|
||||
func (d *Device) Rotation() drivers.Rotation {
|
||||
return d.rotation
|
||||
}
|
||||
|
||||
// SetRotation changes the rotation of the device (clock-wise).
|
||||
func (d *Device) SetRotation(rotation drivers.Rotation) error {
|
||||
d.rotation = rotation
|
||||
switch d.rotation {
|
||||
case drivers.Rotation0:
|
||||
d.Command(SEGREMAP | 0x1) // Reverse horizontal mapping
|
||||
d.Command(COMSCANDEC) // Reverse vertical mapping
|
||||
case drivers.Rotation180:
|
||||
d.Command(SEGREMAP) // Normal horizontal mapping
|
||||
d.Command(COMSCANINC) // Normal vertical mapping
|
||||
// nothing to do
|
||||
default:
|
||||
d.Command(SEGREMAP | 0x1) // Reverse horizontal mapping
|
||||
d.Command(COMSCANDEC) // Reverse vertical mapping
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Set the sleep mode for this display. When sleeping, the panel uses a lot
|
||||
// less power. The display won't show an image anymore, but the memory contents
|
||||
// should be kept.
|
||||
func (d *Device) Sleep(sleepEnabled bool) error {
|
||||
if sleepEnabled {
|
||||
d.Command(DISPLAYOFF)
|
||||
} else {
|
||||
d.Command(DISPLAYON)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,313 @@
|
||||
package capacitive
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"runtime/interrupt"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// How often to measure.
|
||||
// The Update function will wait until this amount of time has passed.
|
||||
measurementFrequency = 200
|
||||
minTimeBetweenMeasurements = time.Second / measurementFrequency
|
||||
|
||||
// How much to multiply values before averaging. A value higher than 1 will
|
||||
// help to avoid integer rounding errors and may improve accuracy slightly.
|
||||
oversampling = 8
|
||||
|
||||
// How many samples to use for the moving average.
|
||||
movingAverageWindow = 16
|
||||
|
||||
// After how many samples should the touch sensor be recalibrated?
|
||||
// This should be a power of two (for efficient division) and be a multiple
|
||||
// of movingAverageWindow. Ideally it should cause a recalibration every 5s
|
||||
// or so.
|
||||
recalibrationSamples = 1024
|
||||
)
|
||||
|
||||
type Array struct {
|
||||
// Time when the last update finished. This is used to make sure we call
|
||||
// Update() the expected number of times per second.
|
||||
lastUpdate time.Time
|
||||
|
||||
// List of pins to measure each time.
|
||||
pins []machine.Pin
|
||||
|
||||
// Raw values (non-smoothed) from the last read.
|
||||
values []uint16
|
||||
|
||||
hasFirstMeasurement bool
|
||||
|
||||
// Static threshold. Zero if using a dynamic threshold.
|
||||
staticThreshold uint16
|
||||
|
||||
// How long to measure.
|
||||
measureCycles uint16
|
||||
|
||||
// Sensitivity (in promille) for the dynamic threshold.
|
||||
sensitivity uint16
|
||||
|
||||
// Capacitance trackers for dynamic capacitance measurement.
|
||||
trackers []capacitanceTracker
|
||||
}
|
||||
|
||||
// Create a new array of pins to be used as touch sensors.
|
||||
// The pins do not need to be initialized. The array is immediately ready to
|
||||
// use.
|
||||
//
|
||||
// By default, NewArray configures a static threshold that is not very
|
||||
// sensitive. If you want the touch inputs to be more sensitive, use
|
||||
// SetDynamicThreshold.
|
||||
func NewArray(pins []machine.Pin) *Array {
|
||||
for _, pin := range pins {
|
||||
pin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
pin.High()
|
||||
}
|
||||
array := &Array{
|
||||
pins: pins,
|
||||
values: make([]uint16, len(pins)),
|
||||
measureCycles: uint16(machine.CPUFrequency() / 125000), // 1000 on the RP2040 (which is 125MHz)
|
||||
lastUpdate: time.Now(),
|
||||
}
|
||||
|
||||
// A threshold of 500 works well on the RP2040. Scale this number to
|
||||
// something similar on other chips.
|
||||
array.SetStaticThreshold(int(machine.CPUFrequency() / 250000))
|
||||
|
||||
return array
|
||||
}
|
||||
|
||||
// Use a static threshold. This works well on simple touch surfaces where you'll
|
||||
// directly touch the metal.
|
||||
func (a *Array) SetStaticThreshold(threshold int) {
|
||||
if threshold > 0xffff {
|
||||
threshold = 0xffff
|
||||
}
|
||||
a.staticThreshold = uint16(threshold)
|
||||
a.trackers = nil
|
||||
}
|
||||
|
||||
// Use a dynamic threshold (as promille), that will calibrate automatically.
|
||||
// This is needed when you want to be able to detect touches through a
|
||||
// non-conducting surface for example. Something like 100‰ (10%) will probably
|
||||
// work in many cases, though you may need to try different value to reliably
|
||||
// detect touches.
|
||||
func (a *Array) SetDynamicThreshold(sensitivity int) {
|
||||
a.sensitivity = uint16(sensitivity)
|
||||
a.staticThreshold = 0
|
||||
a.trackers = make([]capacitanceTracker, len(a.pins))
|
||||
}
|
||||
|
||||
// Measure all GPIO pins. This function must be called very often, ideally about
|
||||
// 100-200 times per second (it will delay a bit when called more than 200 times
|
||||
// per second).
|
||||
func (a *Array) Update() {
|
||||
// Wait until enough time has passed to charge all pins.
|
||||
now := time.Now()
|
||||
timeSinceLastUpdate := now.Sub(a.lastUpdate)
|
||||
sleepTime := minTimeBetweenMeasurements - timeSinceLastUpdate
|
||||
time.Sleep(sleepTime)
|
||||
a.lastUpdate = now.Add(sleepTime) // should be ~equivalent to time.Now()
|
||||
|
||||
// Measure each pin in turn.
|
||||
for i, pin := range a.pins {
|
||||
// Interrupts must be disabled during measuring for accurate results.
|
||||
mask := interrupt.Disable()
|
||||
|
||||
// Switch to input. This will stop the charging, and let it discharge
|
||||
// through the resistor.
|
||||
pin.Configure(machine.PinConfig{Mode: machine.PinInput})
|
||||
|
||||
// Wait for the pin to go low again.
|
||||
// A longer duration means more capacitance, which means something is
|
||||
// touching it (finger, banana, etc).
|
||||
count := uint32(i)
|
||||
for i := 0; i < int(a.measureCycles); i++ {
|
||||
if !pin.Get() {
|
||||
break
|
||||
}
|
||||
count++
|
||||
}
|
||||
|
||||
interrupt.Restore(mask)
|
||||
|
||||
a.values[i] = uint16(count)
|
||||
|
||||
// Set the pin to high, to charge it for the next measurement.
|
||||
pin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
pin.High()
|
||||
}
|
||||
|
||||
// The first measurement tends to be slightly off (too low value) so ignore
|
||||
// that one.
|
||||
if !a.hasFirstMeasurement {
|
||||
a.hasFirstMeasurement = true
|
||||
return
|
||||
}
|
||||
|
||||
for i := 0; i < len(a.trackers); i++ {
|
||||
a.trackers[i].addValue(int(a.values[i]), int(a.sensitivity))
|
||||
}
|
||||
}
|
||||
|
||||
// Return the raw value of the given pin index of the most recent call to
|
||||
// Update. This value is not smoothed in any way.
|
||||
func (a *Array) RawValue(index int) int {
|
||||
return int(a.values[index])
|
||||
}
|
||||
|
||||
// Return the value from the moving average. This value is only available when a
|
||||
// dynamic threshold has been set, it will panic otherwise.
|
||||
func (a *Array) SmoothedValue(index int) int {
|
||||
return int(a.trackers[index].avg) / oversampling
|
||||
}
|
||||
|
||||
// Return whether the given pin index is currently being touched.
|
||||
func (a *Array) Touching(index int) bool {
|
||||
if a.staticThreshold != 0 {
|
||||
// Using a static threshold.
|
||||
return a.values[index] > a.staticThreshold
|
||||
}
|
||||
|
||||
return a.trackers[index].touching
|
||||
}
|
||||
|
||||
// Separate object to store calibration data and track capacitance over time.
|
||||
type capacitanceTracker struct {
|
||||
recentValues [movingAverageWindow]uint16
|
||||
sum uint32
|
||||
avg uint16
|
||||
|
||||
baseline uint16
|
||||
noise uint16
|
||||
valueCount uint8
|
||||
touching bool
|
||||
|
||||
recalibrationCount uint8
|
||||
recalibrationPrevAvg uint16
|
||||
recalibrationNoiseSum int32
|
||||
recalibrationSum uint32
|
||||
}
|
||||
|
||||
func (ct *capacitanceTracker) addValue(value int, sensitivity int) {
|
||||
// Maybe increase the resolution slightly by oversampling. This should
|
||||
// increase the resolution a little bit after averaging and should reduce
|
||||
// rounding errors.
|
||||
// Typical input values on the RP2040 are 100-200 (or up to 1000 or so when
|
||||
// touching the metal) so multiplying by 4-8 should be fine. Other chips
|
||||
// generally have much lower values.
|
||||
value *= oversampling
|
||||
if value > 0xffff {
|
||||
value = 0xffff // unlikely, but make sure we don't overflow
|
||||
}
|
||||
|
||||
// This does a number of things at the same time:
|
||||
// * Add the new value to the recentValues array.
|
||||
// * Calculate the moving sum (and average) of recentValues using a
|
||||
// recursive moving average algorithm:
|
||||
// https://www.dspguide.com/ch15/5.htm
|
||||
ptr := &ct.recentValues[ct.valueCount%movingAverageWindow]
|
||||
ct.sum -= uint32(*ptr)
|
||||
ct.sum += uint32(value)
|
||||
ct.avg = uint16(ct.sum / movingAverageWindow)
|
||||
*ptr = uint16(value)
|
||||
ct.valueCount++
|
||||
|
||||
// Do an initial calibration once the first values have been read.
|
||||
if ct.baseline == 0 && ct.valueCount == movingAverageWindow {
|
||||
ct.baseline = ct.avg
|
||||
|
||||
// Calculate initial noise as an average absolute deviation:
|
||||
// https://en.wikipedia.org/wiki/Average_absolute_deviation
|
||||
// This is a quick and imprecise way to find the noise, better noise
|
||||
// detection happens during recalibration.
|
||||
var diffSum uint32
|
||||
for _, sample := range ct.recentValues {
|
||||
diff := int(ct.avg) - int(sample)
|
||||
if diff < 0 {
|
||||
diff = -diff
|
||||
}
|
||||
diffSum += uint32(diff)
|
||||
}
|
||||
ct.noise = uint16(diffSum / (movingAverageWindow / 2))
|
||||
}
|
||||
|
||||
// Now determine whether the touch pad is being touched.
|
||||
|
||||
if ct.baseline == 0 {
|
||||
// Not yet calibrated.
|
||||
ct.touching = false
|
||||
return
|
||||
}
|
||||
|
||||
// Calculate the threshold.
|
||||
// Divide by 65536 (instead of 65500) to avoid a potentially expensive
|
||||
// division while still being close enough.
|
||||
threshold := (uint32(ct.baseline) * uint32(sensitivity+1000) * 65) / 65536
|
||||
|
||||
// Add noise to the threshold, to avoid toggling quickly. This mainly
|
||||
// filters out mains noise.
|
||||
threshold += uint32(ct.noise)
|
||||
|
||||
// Implement some hysteresis: if the touch pad was previously touched, lower
|
||||
// the threshold a little to avoid bouncing effects.
|
||||
// TODO: let this hysteresis depend on the amount of noise.
|
||||
if ct.touching {
|
||||
threshold = (threshold*3 + uint32(ct.baseline)) / 4 // lower the threshold by 25%
|
||||
}
|
||||
|
||||
// Is the pad being touched?
|
||||
ct.touching = uint32(ct.avg) > threshold
|
||||
|
||||
// Do a recalibration after the sensor hasn't been touched for ~5s, to
|
||||
// account for drift over time (humidity etc).
|
||||
if ct.touching {
|
||||
// Reset calibration (start from zero).
|
||||
ct.recalibrationCount = 0
|
||||
ct.recalibrationSum = 0
|
||||
ct.recalibrationNoiseSum = 0
|
||||
} else {
|
||||
// Add the last batch of samples to the sum.
|
||||
if ct.valueCount%movingAverageWindow == 0 {
|
||||
ct.recalibrationCount++
|
||||
|
||||
// Wait a few cycles before starting data collection for
|
||||
// calibration.
|
||||
cycle := int(ct.recalibrationCount) - 3
|
||||
|
||||
if cycle < 0 {
|
||||
// Store the previous average, to calculate the noise value.
|
||||
ct.recalibrationPrevAvg = ct.avg
|
||||
|
||||
} else if cycle >= 0 {
|
||||
// Collect data for recalibration.
|
||||
ct.recalibrationSum += ct.sum
|
||||
|
||||
// Add difference between two (averaged) samples as a measure of
|
||||
// the noise.
|
||||
diff := int32(ct.recalibrationPrevAvg) - int32(ct.avg)
|
||||
if diff < 0 {
|
||||
diff = -diff
|
||||
}
|
||||
ct.recalibrationNoiseSum += diff
|
||||
ct.recalibrationPrevAvg = ct.avg
|
||||
|
||||
}
|
||||
|
||||
// Do the recalibration after enough samples have been collected.
|
||||
// Note: the noise is basically the average of absolute differences
|
||||
// between two averaging windows. I don't know whether this
|
||||
// algorithm has a name, but it seems to work here to detect the
|
||||
// amount of noise.
|
||||
const totalRecalibrationCount = recalibrationSamples / movingAverageWindow
|
||||
if cycle == totalRecalibrationCount {
|
||||
ct.baseline = uint16(ct.recalibrationSum / recalibrationSamples)
|
||||
ct.noise = uint16(ct.recalibrationNoiseSum / (totalRecalibrationCount / 2))
|
||||
ct.recalibrationCount = 0
|
||||
ct.recalibrationSum = 0
|
||||
ct.recalibrationNoiseSum = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package uc8151
|
||||
|
||||
// LUTType is the look-up table for the display
|
||||
type LUTType [42]uint8
|
||||
|
||||
type LUTSet struct {
|
||||
VCOM LUTType
|
||||
WW LUTType
|
||||
BW LUTType
|
||||
WB LUTType
|
||||
BB LUTType
|
||||
}
|
||||
|
||||
func (lut *LUTType) Clear() {
|
||||
for i := range lut {
|
||||
lut[i] = 0
|
||||
}
|
||||
}
|
||||
|
||||
func (lut *LUTType) SetRow(row int, pat uint8, dur [4]uint8, rep uint8) error {
|
||||
index := row * 6
|
||||
lut[index] = pat
|
||||
lut[index+1] = dur[0]
|
||||
lut[index+2] = dur[1]
|
||||
lut[index+3] = dur[2]
|
||||
lut[index+4] = dur[3]
|
||||
lut[index+5] = rep
|
||||
|
||||
return nil
|
||||
}
|
||||
+12
-7
@@ -1,5 +1,7 @@
|
||||
package uc8151
|
||||
|
||||
import "tinygo.org/x/drivers"
|
||||
|
||||
// Registers
|
||||
const (
|
||||
// Display resolution
|
||||
@@ -141,13 +143,16 @@ const (
|
||||
HZ_100 = 0b00111010
|
||||
HZ_200 = 0b00111001
|
||||
|
||||
NO_ROTATION Rotation = 0
|
||||
ROTATION_90 Rotation = 1 // 90 degrees clock-wise rotation
|
||||
ROTATION_180 Rotation = 2
|
||||
ROTATION_270 Rotation = 3
|
||||
// deprecated constants, just here for backward compatibility.
|
||||
NO_ROTATION = drivers.Rotation0
|
||||
ROTATION_90 = drivers.Rotation90
|
||||
ROTATION_180 = drivers.Rotation180
|
||||
ROTATION_270 = drivers.Rotation270
|
||||
|
||||
DEFAULT Speed = 0
|
||||
MEDIUM Speed = 1
|
||||
FAST Speed = 2
|
||||
TURBO Speed = 3
|
||||
SLOW Speed = 1
|
||||
MEDIUM Speed = 2
|
||||
FAST Speed = 3
|
||||
FASTER Speed = 4
|
||||
TURBO Speed = 5
|
||||
)
|
||||
|
||||
+224
-349
@@ -1,6 +1,7 @@
|
||||
// Package uc8151 implements a driver for e-ink displays controlled by UC8151
|
||||
//
|
||||
// Inspired by https://github.com/pimoroni/pimoroni-pico/blob/main/drivers/uc8151/uc8151.cpp
|
||||
// Additional inspiration from https://github.com/antirez/uc8151_micropython
|
||||
// Datasheet: https://www.buydisplay.com/download/ic/UC8151C.pdf
|
||||
package uc8151 // import "tinygo.org/x/drivers/uc8151"
|
||||
|
||||
@@ -11,35 +12,43 @@ import (
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
"tinygo.org/x/drivers/pixel"
|
||||
)
|
||||
|
||||
var (
|
||||
errOutOfRange = errors.New("out of screen range")
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Width int16
|
||||
Height int16
|
||||
Rotation Rotation // Rotation is clock-wise
|
||||
Speed Speed // Value from DEFAULT, MEDIUM, FAST, TURBO
|
||||
Blocking bool
|
||||
Width int16
|
||||
Height int16
|
||||
Rotation drivers.Rotation // Rotation is clock-wise
|
||||
Speed Speed // Value from DEFAULT, SLOW, MEDIUM, FAST, FASTER, TURBO
|
||||
Blocking bool // block on calls to display or return immediately
|
||||
FlickerFree bool // if we should avoid flickering
|
||||
UpdateAfter int // if we are using flicker-free mode, how often we should update the screen
|
||||
}
|
||||
|
||||
type Device struct {
|
||||
bus drivers.SPI
|
||||
cs machine.Pin
|
||||
dc machine.Pin
|
||||
rst machine.Pin
|
||||
busy machine.Pin
|
||||
width int16
|
||||
height int16
|
||||
buffer []uint8
|
||||
bufferLength uint32
|
||||
rotation Rotation
|
||||
speed Speed
|
||||
blocking bool
|
||||
bus drivers.SPI
|
||||
cs machine.Pin
|
||||
dc machine.Pin
|
||||
rst machine.Pin
|
||||
busy machine.Pin
|
||||
width int16
|
||||
height int16
|
||||
buffer []uint8
|
||||
bufferLength uint32
|
||||
rotation drivers.Rotation
|
||||
speed Speed
|
||||
blocking bool
|
||||
flickerFree bool
|
||||
updateCount, updateAfter int
|
||||
}
|
||||
|
||||
type Rotation uint8
|
||||
type Speed uint8
|
||||
|
||||
// New returns a new epd2in13x driver. Pass in a fully configured SPI bus.
|
||||
// New returns a new uc8151 driver. Pass in a fully configured SPI bus.
|
||||
func New(bus drivers.SPI, csPin, dcPin, rstPin, busyPin machine.Pin) Device {
|
||||
csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
@@ -69,6 +78,8 @@ func (d *Device) Configure(cfg Config) {
|
||||
d.rotation = cfg.Rotation
|
||||
d.speed = cfg.Speed
|
||||
d.blocking = cfg.Blocking
|
||||
d.flickerFree = cfg.FlickerFree
|
||||
d.updateAfter = cfg.UpdateAfter
|
||||
d.bufferLength = (uint32(d.width) * uint32(d.height)) / 8
|
||||
d.buffer = make([]uint8, d.bufferLength)
|
||||
for i := uint32(0); i < d.bufferLength; i++ {
|
||||
@@ -84,14 +95,14 @@ func (d *Device) Configure(cfg Config) {
|
||||
d.SendData(RES_128x296 | LUT_REG | FORMAT_BW | SHIFT_RIGHT | BOOSTER_ON | RESET_NONE | SCAN_UP)
|
||||
}
|
||||
|
||||
d.SetLUT(d.speed)
|
||||
d.SetLUT(d.speed, d.flickerFree)
|
||||
|
||||
d.SendCommand(PWR)
|
||||
d.SendData(VDS_INTERNAL | VDG_INTERNAL)
|
||||
d.SendData(VCOM_VG | VGHL_16V)
|
||||
d.SendData(0x2B)
|
||||
d.SendData(0x2B)
|
||||
d.SendData(0x2B)
|
||||
d.SendData(VCOM_VD | VGHL_16V)
|
||||
d.SendData(0b100110) // +10v VDH
|
||||
d.SendData(0b100110) // -10v VDL
|
||||
d.SendData(0b000011) // VDHR default (For red pixels, not used here)
|
||||
|
||||
d.SendCommand(PON)
|
||||
d.WaitUntilIdle()
|
||||
@@ -102,7 +113,7 @@ func (d *Device) Configure(cfg Config) {
|
||||
d.SendData(START_10MS | STRENGTH_3 | OFF_6_58US)
|
||||
|
||||
d.SendCommand(PFS)
|
||||
d.SendData(FRAMES_1)
|
||||
d.SendData(FRAMES_4)
|
||||
|
||||
d.SendCommand(TSE)
|
||||
d.SendData(TEMP_INTERNAL | OFFSET_0)
|
||||
@@ -111,14 +122,13 @@ func (d *Device) Configure(cfg Config) {
|
||||
d.SendData(0x22)
|
||||
|
||||
d.SendCommand(CDI)
|
||||
d.SendData(0x4C) // 4C //5C
|
||||
d.SendData(0b11_00_1100)
|
||||
|
||||
d.SendCommand(PLL)
|
||||
d.SendData(HZ_100)
|
||||
|
||||
d.SendCommand(POF)
|
||||
d.WaitUntilIdle()
|
||||
|
||||
}
|
||||
|
||||
// Reset resets the device
|
||||
@@ -135,31 +145,30 @@ func (d *Device) PowerOff() {
|
||||
d.SendCommand(POF)
|
||||
}
|
||||
|
||||
// PowerOn power on the device
|
||||
func (d *Device) PowerOn() {
|
||||
d.SendCommand(PON)
|
||||
}
|
||||
|
||||
// SendCommand sends a command to the display
|
||||
func (d *Device) SendCommand(command uint8) {
|
||||
d.sendDataCommand(true, command)
|
||||
d.dc.Low()
|
||||
d.cs.Low()
|
||||
d.bus.Transfer(command)
|
||||
d.cs.High()
|
||||
}
|
||||
|
||||
// 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()
|
||||
}
|
||||
func (d *Device) SendData(data ...uint8) {
|
||||
d.dc.High()
|
||||
d.cs.Low()
|
||||
d.bus.Transfer(data)
|
||||
d.bus.Tx(data, nil)
|
||||
d.cs.High()
|
||||
}
|
||||
|
||||
// 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)
|
||||
// We use RGBA(0, 0, 0) as white (transparent)
|
||||
// Anything else as black
|
||||
func (d *Device) SetPixel(x int16, y int16, c color.RGBA) {
|
||||
x, y = d.xy(x, y)
|
||||
@@ -168,31 +177,60 @@ func (d *Device) SetPixel(x int16, y int16, c color.RGBA) {
|
||||
return
|
||||
}
|
||||
byteIndex := x/8 + y*(d.width/8)
|
||||
if c.R == 0 && c.G == 0 && c.B == 0 { // TRANSPARENT / WHITE
|
||||
d.buffer[byteIndex] &^= 0x80 >> uint8(x%8)
|
||||
} else { // WHITE / EMPTY
|
||||
if c.R != 0 || c.G != 0 || c.B != 0 {
|
||||
d.buffer[byteIndex] |= 0x80 >> uint8(x%8)
|
||||
} else {
|
||||
d.buffer[byteIndex] &^= 0x80 >> uint8(x%8)
|
||||
}
|
||||
}
|
||||
|
||||
// DrawBitmap copies the bitmap to the screen at the given coordinates.
|
||||
func (d *Device) DrawBitmap(x, y int16, bitmap pixel.Image[pixel.Monochrome]) error {
|
||||
dw, dh := d.Size()
|
||||
bw, bh := bitmap.Size()
|
||||
if x < 0 || x+int16(bw) > dw || y < 0 || y+int16(bh) > dh {
|
||||
return errOutOfRange
|
||||
}
|
||||
|
||||
for i := 0; i < bw; i++ {
|
||||
for j := 0; j < bh; j++ {
|
||||
d.SetPixel(x+int16(i), y+int16(j), bitmap.Get(i, j).RGBA())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Display sends the buffer to the screen.
|
||||
func (d *Device) Display() error {
|
||||
if d.blocking {
|
||||
d.WaitUntilIdle()
|
||||
}
|
||||
d.SendCommand(PON)
|
||||
|
||||
if d.flickerFree && d.updateAfter != 0 && d.updateCount%d.updateAfter == 0 {
|
||||
// we need full refresh here
|
||||
d.SetLUT(MEDIUM, false)
|
||||
} else {
|
||||
d.SetLUT(d.speed, d.flickerFree)
|
||||
}
|
||||
d.updateCount++
|
||||
|
||||
d.PowerOn()
|
||||
|
||||
d.SendCommand(PTOU)
|
||||
d.SendCommand(DTM2)
|
||||
for i := uint32(0); i < d.bufferLength; i++ {
|
||||
d.SendData(d.buffer[i])
|
||||
}
|
||||
d.SendData(d.buffer...)
|
||||
|
||||
d.SendCommand(DSP)
|
||||
d.SendCommand(DRF)
|
||||
|
||||
d.SetLUT(d.speed, d.flickerFree)
|
||||
|
||||
if d.blocking {
|
||||
d.WaitUntilIdle()
|
||||
d.PowerOff()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -208,13 +246,14 @@ func (d *Device) DisplayRect(x int16, y int16, width int16, height int16) error
|
||||
if x < 0 || y < 0 || x >= d.width || y >= d.height || width < 0 || height < 0 {
|
||||
return errors.New("wrong rectangle")
|
||||
}
|
||||
if d.rotation == ROTATION_90 {
|
||||
switch d.rotation {
|
||||
case drivers.Rotation0:
|
||||
width, height = height, width
|
||||
x -= width
|
||||
} else if d.rotation == ROTATION_180 {
|
||||
case drivers.Rotation90:
|
||||
x -= width - 1
|
||||
y -= height - 1
|
||||
} else if d.rotation == ROTATION_270 {
|
||||
case drivers.Rotation180:
|
||||
width, height = height, width
|
||||
y -= height
|
||||
}
|
||||
@@ -262,6 +301,12 @@ func (d *Device) DisplayRect(x int16, y int16, width int16, height int16) error
|
||||
|
||||
// ClearDisplay erases the device SRAM
|
||||
func (d *Device) ClearDisplay() {
|
||||
ff := d.flickerFree
|
||||
d.flickerFree = false
|
||||
defer func() {
|
||||
d.flickerFree = ff
|
||||
}()
|
||||
|
||||
d.ClearBuffer()
|
||||
d.Display()
|
||||
}
|
||||
@@ -269,7 +314,7 @@ func (d *Device) ClearDisplay() {
|
||||
// WaitUntilIdle waits until the display is ready
|
||||
func (d *Device) WaitUntilIdle() {
|
||||
for !d.busy.Get() {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,15 +332,32 @@ func (d *Device) ClearBuffer() {
|
||||
|
||||
// Size returns the current size of the display.
|
||||
func (d *Device) Size() (w, h int16) {
|
||||
if d.rotation == ROTATION_90 || d.rotation == ROTATION_270 {
|
||||
if d.rotation == drivers.Rotation90 || d.rotation == drivers.Rotation270 {
|
||||
return d.height, d.width
|
||||
}
|
||||
return d.width, d.height
|
||||
}
|
||||
|
||||
// Rotation returns the currently configured rotation.
|
||||
func (d *Device) Rotation() drivers.Rotation {
|
||||
return d.rotation
|
||||
}
|
||||
|
||||
// SetRotation changes the rotation (clock-wise) of the device
|
||||
func (d *Device) SetRotation(rotation Rotation) {
|
||||
func (d *Device) SetRotation(rotation drivers.Rotation) error {
|
||||
d.rotation = rotation
|
||||
return nil
|
||||
}
|
||||
|
||||
// Set the sleep mode for this display.
|
||||
func (d *Device) Sleep(sleepEnabled bool) error {
|
||||
if sleepEnabled {
|
||||
d.PowerOff()
|
||||
return nil
|
||||
}
|
||||
|
||||
d.PowerOn()
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetBlocking changes the blocking flag of the device
|
||||
@@ -303,16 +365,16 @@ func (d *Device) SetBlocking(blocking bool) {
|
||||
d.blocking = blocking
|
||||
}
|
||||
|
||||
// xy chages the coordinates according to the rotation
|
||||
// xy changes the coordinates according to the rotation
|
||||
func (d *Device) xy(x, y int16) (int16, int16) {
|
||||
switch d.rotation {
|
||||
case NO_ROTATION:
|
||||
case drivers.Rotation0:
|
||||
return x, y
|
||||
case ROTATION_90:
|
||||
case drivers.Rotation90:
|
||||
return d.width - y - 1, x
|
||||
case ROTATION_180:
|
||||
case drivers.Rotation180:
|
||||
return d.width - x - 1, d.height - y - 1
|
||||
case ROTATION_270:
|
||||
case drivers.Rotation270:
|
||||
return y, d.height - x - 1
|
||||
}
|
||||
return x, y
|
||||
@@ -338,296 +400,109 @@ func (d *Device) Invert(invert bool) {
|
||||
}
|
||||
}
|
||||
|
||||
// SetLUT sets the look up tables for full or partial updates
|
||||
func (d *Device) SetLUT(speed Speed) {
|
||||
switch speed {
|
||||
case MEDIUM:
|
||||
var lut = [44]uint8{
|
||||
0x00, 0x16, 0x16, 0x0d, 0x00, 0x01,
|
||||
0x00, 0x23, 0x23, 0x00, 0x00, 0x02,
|
||||
0x00, 0x16, 0x16, 0x0d, 0x00, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00,
|
||||
}
|
||||
d.SendCommand(LUT_VCOM)
|
||||
for i := 0; i < 44; i++ {
|
||||
d.SendData(lut[i])
|
||||
}
|
||||
lut = [44]uint8{
|
||||
0x54, 0x16, 0x16, 0x0d, 0x00, 0x01,
|
||||
0x60, 0x23, 0x23, 0x00, 0x00, 0x02,
|
||||
0xa8, 0x16, 0x16, 0x0d, 0x00, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
|
||||
d.SendCommand(LUT_WW)
|
||||
// do not send last two bytes
|
||||
for i := 0; i < 42; i++ {
|
||||
d.SendData(lut[i])
|
||||
}
|
||||
// SetLUT sets the look up tables for full or partial updates based on
|
||||
// the speed and flicker-free mode.
|
||||
// Based on code from https://github.com/antirez/uc8151_micropython
|
||||
func (d *Device) SetLUT(speed Speed, flickerFree bool) error {
|
||||
var lut LUTSet
|
||||
|
||||
lut = [44]uint8{
|
||||
0x54, 0x16, 0x16, 0x0d, 0x00, 0x01,
|
||||
0x60, 0x23, 0x23, 0x00, 0x00, 0x02,
|
||||
0xa8, 0x16, 0x16, 0x0d, 0x00, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
|
||||
d.SendCommand(LUT_BW)
|
||||
// do not send last two bytes
|
||||
for i := 0; i < 42; i++ {
|
||||
d.SendData(lut[i])
|
||||
}
|
||||
|
||||
lut = [44]uint8{
|
||||
0xa8, 0x16, 0x16, 0x0d, 0x00, 0x01,
|
||||
0x60, 0x23, 0x23, 0x00, 0x00, 0x02,
|
||||
0x54, 0x16, 0x16, 0x0d, 0x00, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
|
||||
d.SendCommand(LUT_WB)
|
||||
// do not send last two bytes
|
||||
for i := 0; i < 42; i++ {
|
||||
d.SendData(lut[i])
|
||||
}
|
||||
|
||||
lut = [44]uint8{
|
||||
0xa8, 0x16, 0x16, 0x0d, 0x00, 0x01,
|
||||
0x60, 0x23, 0x23, 0x00, 0x00, 0x02,
|
||||
0x54, 0x16, 0x16, 0x0d, 0x00, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
|
||||
d.SendCommand(LUT_BB)
|
||||
// do not send last two bytes
|
||||
for i := 0; i < 42; i++ {
|
||||
d.SendData(lut[i])
|
||||
}
|
||||
|
||||
break
|
||||
case FAST:
|
||||
var lut = [44]uint8{
|
||||
0x00, 0x04, 0x04, 0x07, 0x00, 0x01,
|
||||
0x00, 0x0c, 0x0c, 0x00, 0x00, 0x02,
|
||||
0x00, 0x04, 0x04, 0x07, 0x00, 0x02,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00,
|
||||
}
|
||||
d.SendCommand(LUT_VCOM)
|
||||
for i := 0; i < 44; i++ {
|
||||
d.SendData(lut[i])
|
||||
}
|
||||
lut = [44]uint8{
|
||||
0x54, 0x04, 0x04, 0x07, 0x00, 0x01,
|
||||
0x60, 0x0c, 0x0c, 0x00, 0x00, 0x02,
|
||||
0xa8, 0x04, 0x04, 0x07, 0x00, 0x02,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
|
||||
d.SendCommand(LUT_WW)
|
||||
// do not send last two bytes
|
||||
for i := 0; i < 42; i++ {
|
||||
d.SendData(lut[i])
|
||||
}
|
||||
|
||||
lut = [44]uint8{
|
||||
0x54, 0x04, 0x04, 0x07, 0x00, 0x01,
|
||||
0x60, 0x0c, 0x0c, 0x00, 0x00, 0x02,
|
||||
0xa8, 0x04, 0x04, 0x07, 0x00, 0x02,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
|
||||
d.SendCommand(LUT_BW)
|
||||
// do not send last two bytes
|
||||
for i := 0; i < 42; i++ {
|
||||
d.SendData(lut[i])
|
||||
}
|
||||
|
||||
lut = [44]uint8{
|
||||
0xa8, 0x04, 0x04, 0x07, 0x00, 0x01,
|
||||
0x60, 0x0c, 0x0c, 0x00, 0x00, 0x02,
|
||||
0x54, 0x04, 0x04, 0x07, 0x00, 0x02,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
|
||||
d.SendCommand(LUT_WB)
|
||||
// do not send last two bytes
|
||||
for i := 0; i < 42; i++ {
|
||||
d.SendData(lut[i])
|
||||
}
|
||||
|
||||
lut = [44]uint8{
|
||||
0xa8, 0x04, 0x04, 0x07, 0x00, 0x01,
|
||||
0x60, 0x0c, 0x0c, 0x00, 0x00, 0x02,
|
||||
0x54, 0x04, 0x04, 0x07, 0x00, 0x02,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
|
||||
d.SendCommand(LUT_BB)
|
||||
// do not send last two bytes
|
||||
for i := 0; i < 42; i++ {
|
||||
d.SendData(lut[i])
|
||||
}
|
||||
|
||||
break
|
||||
case TURBO:
|
||||
var lut = [44]uint8{
|
||||
0x00, 0x01, 0x01, 0x02, 0x00, 0x01,
|
||||
0x00, 0x02, 0x02, 0x00, 0x00, 0x02,
|
||||
0x00, 0x02, 0x02, 0x03, 0x00, 0x02,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00,
|
||||
}
|
||||
d.SendCommand(LUT_VCOM)
|
||||
for i := 0; i < 44; i++ {
|
||||
d.SendData(lut[i])
|
||||
}
|
||||
lut = [44]uint8{
|
||||
0x54, 0x01, 0x01, 0x02, 0x00, 0x01,
|
||||
0x60, 0x02, 0x02, 0x00, 0x00, 0x02,
|
||||
0xa8, 0x02, 0x02, 0x03, 0x00, 0x02,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
|
||||
d.SendCommand(LUT_WW)
|
||||
// do not send last two bytes
|
||||
for i := 0; i < 42; i++ {
|
||||
d.SendData(lut[i])
|
||||
}
|
||||
|
||||
lut = [44]uint8{
|
||||
0x54, 0x01, 0x01, 0x02, 0x00, 0x01,
|
||||
0x60, 0x02, 0x02, 0x00, 0x00, 0x02,
|
||||
0xa8, 0x02, 0x02, 0x03, 0x00, 0x02,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
|
||||
d.SendCommand(LUT_BW)
|
||||
// do not send last two bytes
|
||||
for i := 0; i < 42; i++ {
|
||||
d.SendData(lut[i])
|
||||
}
|
||||
|
||||
lut = [44]uint8{
|
||||
0xa8, 0x01, 0x01, 0x02, 0x00, 0x01,
|
||||
0x60, 0x02, 0x02, 0x00, 0x00, 0x02,
|
||||
0x54, 0x02, 0x02, 0x03, 0x00, 0x02,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
|
||||
d.SendCommand(LUT_WB)
|
||||
// do not send last two bytes
|
||||
for i := 0; i < 42; i++ {
|
||||
d.SendData(lut[i])
|
||||
}
|
||||
|
||||
lut = [44]uint8{
|
||||
0xa8, 0x01, 0x01, 0x02, 0x00, 0x01,
|
||||
0x60, 0x02, 0x02, 0x00, 0x00, 0x02,
|
||||
0x54, 0x02, 0x02, 0x03, 0x00, 0x02,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
|
||||
d.SendCommand(LUT_BB)
|
||||
// do not send last two bytes
|
||||
for i := 0; i < 42; i++ {
|
||||
d.SendData(lut[i])
|
||||
}
|
||||
|
||||
break
|
||||
default:
|
||||
var lut = [44]uint8{
|
||||
0x00, 0x64, 0x64, 0x37, 0x00, 0x01,
|
||||
0x00, 0x8c, 0x8c, 0x00, 0x00, 0x04,
|
||||
0x00, 0x64, 0x64, 0x37, 0x00, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00,
|
||||
}
|
||||
d.SendCommand(LUT_VCOM)
|
||||
for i := 0; i < 44; i++ {
|
||||
d.SendData(lut[i])
|
||||
}
|
||||
lut = [44]uint8{
|
||||
0x54, 0x64, 0x64, 0x37, 0x00, 0x01,
|
||||
0x60, 0x8c, 0x8c, 0x00, 0x00, 0x04,
|
||||
0xa8, 0x64, 0x64, 0x37, 0x00, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
|
||||
d.SendCommand(LUT_WW)
|
||||
// do not send last two bytes
|
||||
for i := 0; i < 42; i++ {
|
||||
d.SendData(lut[i])
|
||||
}
|
||||
|
||||
lut = [44]uint8{
|
||||
0x54, 0x64, 0x64, 0x37, 0x00, 0x01,
|
||||
0x60, 0x8c, 0x8c, 0x00, 0x00, 0x04,
|
||||
0xa8, 0x64, 0x64, 0x37, 0x00, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
|
||||
d.SendCommand(LUT_BW)
|
||||
// do not send last two bytes
|
||||
for i := 0; i < 42; i++ {
|
||||
d.SendData(lut[i])
|
||||
}
|
||||
|
||||
lut = [44]uint8{
|
||||
0xa8, 0x64, 0x64, 0x37, 0x00, 0x01,
|
||||
0x60, 0x8c, 0x8c, 0x00, 0x00, 0x04,
|
||||
0x54, 0x64, 0x64, 0x37, 0x00, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
|
||||
d.SendCommand(LUT_WB)
|
||||
// do not send last two bytes
|
||||
for i := 0; i < 42; i++ {
|
||||
d.SendData(lut[i])
|
||||
}
|
||||
|
||||
lut = [44]uint8{
|
||||
0xa8, 0x64, 0x64, 0x37, 0x00, 0x01,
|
||||
0x60, 0x8c, 0x8c, 0x00, 0x00, 0x04,
|
||||
0x54, 0x64, 0x64, 0x37, 0x00, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
|
||||
d.SendCommand(LUT_BB)
|
||||
// do not send last two bytes
|
||||
for i := 0; i < 42; i++ {
|
||||
d.SendData(lut[i])
|
||||
}
|
||||
|
||||
break
|
||||
// Num. of frames for single direction change.
|
||||
period := 64
|
||||
p := uint8(period / (2 ^ (int(speed) - 1)))
|
||||
if p < 1 {
|
||||
p = 1
|
||||
}
|
||||
|
||||
// Num. of frames for back-and-forth change.
|
||||
hperiod := period % 2
|
||||
hp := uint8(hperiod / (2 ^ (int(speed) - 1)))
|
||||
if hp < 1 {
|
||||
hp = 1
|
||||
}
|
||||
|
||||
if speed < FAST && !flickerFree {
|
||||
// For low speed everything is charge-neutral, even WB/BW.
|
||||
|
||||
// Phase 1: long go-inverted-color.
|
||||
lut.VCOM.SetRow(0, 0x00, [4]uint8{p, 0x00, 0x00, 0x00}, 0x02)
|
||||
lut.BW.SetRow(0, 0b01_000000, [4]uint8{p, 0x00, 0x00, 0x00}, 0x02)
|
||||
lut.WB.SetRow(0, 0b10_000000, [4]uint8{p, 0x00, 0x00, 0x00}, 0x02)
|
||||
|
||||
// Phase 2: short ping/pong.
|
||||
lut.VCOM.SetRow(1, 0x00, [4]uint8{hp, hp, 0x00, 0x00}, 0x02)
|
||||
lut.BW.SetRow(1, 0b10_01_0000, [4]uint8{hp, hp, 0x00, 0x00}, 0x01)
|
||||
lut.WB.SetRow(1, 0b01_10_0000, [4]uint8{hp, hp, 0x00, 0x00}, 0x01)
|
||||
|
||||
// Phase 3: long go-target-color.
|
||||
lut.VCOM.SetRow(2, 0x00, [4]uint8{p, 0x00, 0x00, 0x00}, 0x02)
|
||||
lut.BW.SetRow(2, 0b10_000000, [4]uint8{p, 0x00, 0x00, 0x00}, 0x02)
|
||||
lut.WB.SetRow(2, 0b01_000000, [4]uint8{p, 0x00, 0x00, 0x00}, 0x02)
|
||||
|
||||
// For this speed, we use the same LUTs for WW/BB as well.
|
||||
copy(lut.WW[:], lut.BW[:])
|
||||
copy(lut.BB[:], lut.WB[:])
|
||||
} else {
|
||||
// Speed >= FAST
|
||||
// For greater than 3 we use non charge-neutral LUTs for WB/BW
|
||||
// since the inpulse is short and it gets reversed when the
|
||||
// pixel changes color, so that's not a problem for the display,
|
||||
// however we still need to use charge-neutral LUTs for WW/BB.
|
||||
lut.VCOM.SetRow(0, 0x00, [4]uint8{p, p, p, p}, 0x01)
|
||||
lut.BW.SetRow(0, 0b10_00_00_00, [4]uint8{p * 4, 0x00, 0x00, 0x00}, 0x01)
|
||||
lut.WB.SetRow(0, 0b01_00_00_00, [4]uint8{p * 4, 0x00, 0x00, 0x00}, 0x01)
|
||||
lut.WW.SetRow(0, 0b01_10_00_00, [4]uint8{p * 2, p * 2, 0x00, 0x00}, 0x01)
|
||||
lut.BB.SetRow(0, 0b10_01_00_00, [4]uint8{p * 2, p * 2, 0x00, 0x00}, 0x01)
|
||||
}
|
||||
|
||||
if flickerFree {
|
||||
// If no flickering mode is enabled, we use an empty
|
||||
// waveform BB and WW. The screen will need to be periodically fully refreshed.
|
||||
lut.WW.Clear()
|
||||
lut.BB.Clear()
|
||||
}
|
||||
|
||||
d.SendCommand(LUT_VCOM)
|
||||
d.SendData(append(lut.VCOM[:], []uint8{0, 0}...)...)
|
||||
|
||||
d.SendCommand(LUT_BW)
|
||||
d.SendData(lut.BW[:]...)
|
||||
|
||||
d.SendCommand(LUT_WB)
|
||||
d.SendData(lut.WB[:]...)
|
||||
|
||||
d.SendCommand(LUT_WW)
|
||||
d.SendData(lut.WW[:]...)
|
||||
|
||||
d.SendCommand(LUT_BB)
|
||||
d.SendData(lut.BB[:]...)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// FillRectangle fills a rectangle at a given coordinates with a color
|
||||
func (d *Device) FillRectangle(x, y, width, height int16, c color.RGBA) error {
|
||||
dw, dh := d.Size()
|
||||
|
||||
if x < 0 || y < 0 || width <= 0 || height <= 0 ||
|
||||
x >= d.width || (x+width) > dw || y >= dh || (y+height) > dh {
|
||||
return errOutOfRange
|
||||
}
|
||||
|
||||
if x+width == dw && y+height == dh && c.R == 0 && c.G == 0 && c.B == 0 {
|
||||
d.ClearDisplay()
|
||||
return nil
|
||||
}
|
||||
|
||||
for i := x; i < x+width; i++ {
|
||||
for j := y; j < y+height; j++ {
|
||||
d.SetPixel(i, j, c)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetScroll sets the vertical scrolling for the display, which is a NOP for this display.
|
||||
func (d *Device) SetScroll(line int16) {
|
||||
return
|
||||
}
|
||||
|
||||
+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.27.0"
|
||||
const Version = "0.28.0"
|
||||
|
||||
@@ -0,0 +1,424 @@
|
||||
// Package epd1in54 implements a driver for Waveshare 1.54in 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/epd1in54_V2/epd1in54_V2.cpp
|
||||
//
|
||||
// Datasheet: https://www.waveshare.com/w/upload/e/e5/1.54inch_e-paper_V2_Datasheet.pdf
|
||||
package epd1in54
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"machine"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Width int16
|
||||
Height int16
|
||||
LogicalWidth int16
|
||||
Rotation Rotation
|
||||
}
|
||||
|
||||
type Device struct {
|
||||
bus machine.SPI
|
||||
cs machine.Pin
|
||||
dc machine.Pin
|
||||
rst machine.Pin
|
||||
busy machine.Pin
|
||||
|
||||
buffer []uint8
|
||||
rotation Rotation
|
||||
}
|
||||
|
||||
type Rotation uint8
|
||||
|
||||
var fullRefresh = [159]uint8{
|
||||
0x80, 0x48, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x40, 0x48, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x80, 0x48, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x40, 0x48, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0xA, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x8, 0x1, 0x0, 0x8, 0x1, 0x0, 0x2,
|
||||
0xA, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x0, 0x0, 0x0,
|
||||
0x22, 0x17, 0x41, 0x0, 0x32, 0x20,
|
||||
}
|
||||
|
||||
var partialRefresh = [159]uint8{
|
||||
0x0, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x80, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x40, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0xF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x0, 0x0, 0x0,
|
||||
0x02, 0x17, 0x41, 0xB0, 0x32, 0x28,
|
||||
}
|
||||
|
||||
// New returns a new epd1in54 driver. Pass in a fully configured SPI bus.
|
||||
func New(bus machine.SPI, csPin, dcPin, rstPin, busyPin machine.Pin) Device {
|
||||
return Device{
|
||||
buffer: make([]uint8, (uint32(Width)*uint32(Height))/8),
|
||||
bus: bus,
|
||||
cs: csPin,
|
||||
dc: dcPin,
|
||||
rst: rstPin,
|
||||
busy: busyPin,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Device) LDirInit(cfg Config) {
|
||||
d.cs.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
d.rst.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
d.dc.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
d.busy.Configure(machine.PinConfig{Mode: machine.PinInput})
|
||||
|
||||
d.bus.Configure(machine.SPIConfig{
|
||||
Frequency: 2000000,
|
||||
Mode: 0,
|
||||
LSBFirst: false,
|
||||
})
|
||||
|
||||
d.Reset()
|
||||
d.WaitUntilIdle()
|
||||
|
||||
d.SendCommand(0x12)
|
||||
d.WaitUntilIdle()
|
||||
|
||||
d.SendCommand(0x01)
|
||||
d.SendData(0xC7)
|
||||
d.SendData(0x00)
|
||||
d.SendData(0x00)
|
||||
|
||||
d.SendCommand(0x11)
|
||||
d.SendData(0x03)
|
||||
|
||||
d.SendCommand(0x44)
|
||||
/* x point must be the multiple of 8 or the last 3 bits will be ignored */
|
||||
d.SendData((0 >> 3) & 0xFF)
|
||||
d.SendData((199 >> 3) & 0xFF)
|
||||
|
||||
d.SendCommand(0x45)
|
||||
d.SendData(0 & 0xFF)
|
||||
d.SendData((0 >> 8) & 0xFF)
|
||||
d.SendData(199 & 0xFF)
|
||||
d.SendData((199 >> 8) & 0xFF)
|
||||
|
||||
d.SendCommand(0x3C)
|
||||
d.SendData(0x01)
|
||||
|
||||
d.SendCommand(0x18)
|
||||
d.SendData(0x80)
|
||||
|
||||
d.SendCommand(0x22)
|
||||
d.SendData(0xB1)
|
||||
|
||||
d.SendCommand(0x20)
|
||||
|
||||
d.SendCommand(0x4E)
|
||||
d.SendData(0x00)
|
||||
|
||||
d.SendCommand(0x4F)
|
||||
d.SendData(0xC7)
|
||||
d.SendData(0x00)
|
||||
|
||||
d.WaitUntilIdle()
|
||||
d.setLUT(fullRefresh)
|
||||
}
|
||||
|
||||
func (d *Device) HDirInit(cfg Config) {
|
||||
d.cs.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
d.rst.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
d.dc.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
d.busy.Configure(machine.PinConfig{Mode: machine.PinInput})
|
||||
|
||||
d.bus.Configure(machine.SPIConfig{
|
||||
Frequency: 2000000,
|
||||
Mode: 0,
|
||||
LSBFirst: false,
|
||||
})
|
||||
|
||||
d.Reset()
|
||||
d.WaitUntilIdle()
|
||||
|
||||
d.SendCommand(0x12)
|
||||
d.WaitUntilIdle()
|
||||
|
||||
d.SendCommand(0x01)
|
||||
d.SendData(0xC7)
|
||||
d.SendData(0x00)
|
||||
d.SendData(0x01)
|
||||
|
||||
d.SendCommand(0x11)
|
||||
d.SendData(0x01)
|
||||
|
||||
d.SendCommand(0x44)
|
||||
d.SendData(0x00)
|
||||
d.SendData(0x18)
|
||||
|
||||
d.SendCommand(0x45)
|
||||
d.SendData(0xC7)
|
||||
d.SendData(0x00)
|
||||
d.SendData(0x00)
|
||||
d.SendData(0x00)
|
||||
|
||||
d.SendCommand(0x3C)
|
||||
d.SendData(0x01)
|
||||
|
||||
d.SendCommand(0x18)
|
||||
d.SendData(0x80)
|
||||
|
||||
d.SendCommand(0x22)
|
||||
d.SendData(0xB1)
|
||||
|
||||
d.SendCommand(0x20)
|
||||
|
||||
d.SendCommand(0x4E)
|
||||
d.SendData(0x00)
|
||||
|
||||
d.SendCommand(0x4F)
|
||||
d.SendData(0xC7)
|
||||
d.SendData(0x00)
|
||||
|
||||
d.WaitUntilIdle()
|
||||
d.setLUT(fullRefresh)
|
||||
}
|
||||
|
||||
func (d *Device) setLUT(lut [159]uint8) {
|
||||
d.SendCommand(0x32)
|
||||
for i := 0; i < 153; i++ {
|
||||
d.SendData(lut[i])
|
||||
}
|
||||
d.WaitUntilIdle()
|
||||
|
||||
d.SendCommand(0x3F)
|
||||
d.SendData(lut[153])
|
||||
|
||||
d.SendCommand(0x03)
|
||||
d.SendData(lut[154])
|
||||
|
||||
d.SendCommand(0x04)
|
||||
d.SendData(lut[155])
|
||||
d.SendData(lut[156])
|
||||
d.SendData(lut[157])
|
||||
|
||||
d.SendCommand(0x2C)
|
||||
d.SendData(lut[158])
|
||||
}
|
||||
|
||||
// Reset resets the display.
|
||||
func (d *Device) Reset() {
|
||||
d.rst.High()
|
||||
time.Sleep(20 * time.Millisecond)
|
||||
d.rst.Low()
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
d.rst.High()
|
||||
time.Sleep(20 * time.Millisecond)
|
||||
}
|
||||
|
||||
// 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()
|
||||
}
|
||||
|
||||
// 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 >= Width || y < 0 || y >= Height {
|
||||
return
|
||||
}
|
||||
byteIndex := (uint32(x) + uint32(y)*uint32(Width)) / 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)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Device) DisplayImage(image []uint8) {
|
||||
var w, h int
|
||||
if Width%8 == 0 {
|
||||
w = int(Width / 8)
|
||||
} else {
|
||||
w = int(Width/8 + 1)
|
||||
}
|
||||
h = int(Height)
|
||||
|
||||
d.SendCommand(0x24)
|
||||
for j := 0; j < h; j++ {
|
||||
for i := 0; i < w; i++ {
|
||||
d.SendData(image[i+j*w])
|
||||
}
|
||||
}
|
||||
|
||||
d.SendCommand(0x26)
|
||||
for j := 0; j < h; j++ {
|
||||
for i := 0; i < w; i++ {
|
||||
d.SendData(image[i+j*w])
|
||||
}
|
||||
}
|
||||
|
||||
d.displayFrame()
|
||||
}
|
||||
|
||||
func (d *Device) Display() error {
|
||||
var w, h int
|
||||
if Width%8 == 0 {
|
||||
w = int(Width / 8)
|
||||
} else {
|
||||
w = int(Width/8 + 1)
|
||||
}
|
||||
h = int(Height)
|
||||
|
||||
d.SendCommand(0x24)
|
||||
for j := 0; j < h; j++ {
|
||||
for i := 0; i < w; i++ {
|
||||
x := i + j*w
|
||||
d.SendData(d.buffer[x])
|
||||
}
|
||||
}
|
||||
|
||||
d.SendCommand(0x26)
|
||||
for j := 0; j < h; j++ {
|
||||
for i := 0; i < w; i++ {
|
||||
x := i + j*w
|
||||
d.SendData(d.buffer[x])
|
||||
}
|
||||
}
|
||||
|
||||
d.displayFrame()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Device) displayFrame() {
|
||||
d.SendCommand(0x22)
|
||||
d.SendData(0xC7)
|
||||
d.SendCommand(0x20)
|
||||
d.WaitUntilIdle()
|
||||
}
|
||||
|
||||
func (d *Device) Clear() {
|
||||
var w, h int
|
||||
if Width%8 == 0 {
|
||||
w = int(Width / 8)
|
||||
} else {
|
||||
w = int(Width/8 + 1)
|
||||
}
|
||||
h = int(Height)
|
||||
|
||||
d.SendCommand(0x24)
|
||||
for j := 0; j < h; j++ {
|
||||
for i := 0; i < w; i++ {
|
||||
d.SendData(0xff)
|
||||
}
|
||||
}
|
||||
|
||||
d.SendCommand(0x26)
|
||||
for j := 0; j < h; j++ {
|
||||
for i := 0; i < w; i++ {
|
||||
d.SendData(0xff)
|
||||
}
|
||||
}
|
||||
|
||||
d.displayFrame()
|
||||
}
|
||||
|
||||
// WaitUntilIdle waits until the display is ready
|
||||
func (d *Device) WaitUntilIdle() {
|
||||
for d.busy.Get() {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
time.Sleep(200 * 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 := 0; i < len(d.buffer); 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 Height, Width
|
||||
}
|
||||
return Width, 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 Width - y - 1, x
|
||||
case ROTATION_180:
|
||||
return Width - x - 1, Height - y - 1
|
||||
case ROTATION_270:
|
||||
return y, Height - x - 1
|
||||
}
|
||||
return x, y
|
||||
}
|
||||
|
||||
func (d *Device) Sleep() {
|
||||
d.SendCommand(0x10)
|
||||
d.SendData(0x01)
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
|
||||
d.rst.Low()
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package epd1in54
|
||||
|
||||
// Derived from https://github.com/waveshare/e-Paper/blob/master/Arduino/epd4in2/epd4in2.h
|
||||
|
||||
const (
|
||||
Width = 200
|
||||
Height = 200
|
||||
|
||||
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
|
||||
ROTATION_180 Rotation = 2
|
||||
ROTATION_270 Rotation = 3
|
||||
)
|
||||
@@ -0,0 +1,287 @@
|
||||
// Package epd2in66b implements a driver for the Waveshare 2.66inch E-Paper E-Ink Display Module (B)
|
||||
// for Raspberry Pi Pico, 296×152, Red / Black / White
|
||||
// Datasheet: https://files.waveshare.com/upload/e/ec/2.66inch-e-paper-b-specification.pdf
|
||||
package epd2in66b
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
const (
|
||||
displayWidth = 152
|
||||
displayHeight = 296
|
||||
)
|
||||
|
||||
const Baudrate = 4_000_000 // 4 MHz
|
||||
|
||||
type Config struct {
|
||||
ResetPin machine.Pin
|
||||
DataPin machine.Pin
|
||||
ChipSelectPin machine.Pin
|
||||
BusyPin machine.Pin
|
||||
}
|
||||
|
||||
type Device struct {
|
||||
bus drivers.SPI
|
||||
cs machine.Pin
|
||||
dc machine.Pin
|
||||
rst machine.Pin
|
||||
busy machine.Pin
|
||||
|
||||
blackBuffer []byte
|
||||
redBuffer []byte
|
||||
}
|
||||
|
||||
// New allocates a new device.
|
||||
// The bus is expected to be configured and ready for use.
|
||||
func New(bus drivers.SPI) Device {
|
||||
pixelCount := displayWidth * displayHeight
|
||||
|
||||
bufLen := pixelCount / 8
|
||||
|
||||
return Device{
|
||||
bus: bus,
|
||||
blackBuffer: make([]byte, bufLen),
|
||||
redBuffer: make([]byte, bufLen),
|
||||
}
|
||||
}
|
||||
|
||||
// Configure configures the device and its pins.
|
||||
func (d *Device) Configure(c Config) error {
|
||||
d.cs = c.ChipSelectPin
|
||||
d.dc = c.DataPin
|
||||
d.rst = c.ResetPin
|
||||
d.busy = c.BusyPin
|
||||
|
||||
d.cs.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
d.dc.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
d.rst.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
d.busy.Configure(machine.PinConfig{Mode: machine.PinInput})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Device) Size() (x, y int16) {
|
||||
return displayWidth, displayHeight
|
||||
}
|
||||
|
||||
// SetPixel modifies the internal buffer in a single pixel.
|
||||
// The display has 3 colors: red, black and white
|
||||
//
|
||||
// - white = RGBA(255,255,255, 1-255)
|
||||
// - red = RGBA(1-255,0,0,1-255)
|
||||
// - Anything else as black
|
||||
func (d *Device) SetPixel(x int16, y int16, c color.RGBA) {
|
||||
if x < 0 || x >= displayWidth || y < 0 || y >= displayHeight {
|
||||
return
|
||||
}
|
||||
|
||||
bytePos, bitPos := pos(x, y, displayWidth)
|
||||
|
||||
if c.R == 0xff && c.G == 0xff && c.B == 0xff && c.A > 0 { // white
|
||||
set(d.blackBuffer, bytePos, bitPos)
|
||||
unset(d.redBuffer, bytePos, bitPos)
|
||||
} else if c.R != 0 && c.G == 0 && c.B == 0 && c.A > 0 { // red-ish
|
||||
set(d.blackBuffer, bytePos, bitPos)
|
||||
set(d.redBuffer, bytePos, bitPos)
|
||||
} else { // black or other
|
||||
unset(d.blackBuffer, bytePos, bitPos)
|
||||
unset(d.redBuffer, bytePos, bitPos)
|
||||
}
|
||||
}
|
||||
|
||||
func set(buf []byte, bytePos, bitPos int) {
|
||||
buf[bytePos] |= 0x1 << bitPos
|
||||
}
|
||||
|
||||
func unset(buf []byte, bytePos, bitPos int) {
|
||||
buf[bytePos] &^= 0x1 << bitPos
|
||||
}
|
||||
|
||||
func pos(x, y, stride int16) (bytePos int, bitPos int) {
|
||||
p := int(x) + int(y)*int(stride)
|
||||
bytePos = p / 8
|
||||
|
||||
// reverse bit position as it is reversed on the device's buffer
|
||||
bitPos = 7 - p%8
|
||||
|
||||
return bytePos, bitPos
|
||||
}
|
||||
|
||||
func (d *Device) Display() error {
|
||||
// Write RAM (Black White) / RAM 0x24
|
||||
// 1 == white, 0 == black
|
||||
if err := d.sendCommandByte(0x24); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := d.sendData(d.blackBuffer); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Write RAM (RED) / RAM 0x26)
|
||||
// 0 == blank, 1 == red
|
||||
if err := d.sendCommandByte(0x26); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := d.sendData(d.redBuffer); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return d.turnOnDisplay()
|
||||
}
|
||||
|
||||
func (d *Device) ClearBuffer() {
|
||||
fill(d.redBuffer, 0x00)
|
||||
fill(d.blackBuffer, 0xff)
|
||||
}
|
||||
|
||||
func (d *Device) turnOnDisplay() error {
|
||||
// also documented as 'Master Activation'
|
||||
if err := d.sendCommandByte(0x20); err != nil {
|
||||
return err
|
||||
}
|
||||
d.WaitUntilIdle()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Device) Reset() error {
|
||||
d.hwReset()
|
||||
d.WaitUntilIdle()
|
||||
|
||||
// soft reset & set defaults
|
||||
if err := d.sendCommandByte(0x12); err != nil {
|
||||
return err
|
||||
}
|
||||
d.WaitUntilIdle()
|
||||
|
||||
// data entry mode setting
|
||||
if err := d.sendCommandSequence([]byte{0x11, 0x03}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := d.setWindow(0, displayWidth-1, 0, displayHeight-1); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// display update control 1 - resolution setting
|
||||
if err := d.sendCommandSequence([]byte{0x21, 0x00, 0x80}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := d.setCursor(0, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
d.WaitUntilIdle()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Device) setCursor(x, y uint16) error {
|
||||
// Set RAM X address counter
|
||||
if err := d.sendCommandSequence([]byte{0x4e, byte(x & 0x1f)}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Set RAM Y address counter
|
||||
yLo := byte(y)
|
||||
yHi := byte(y>>8) & 0x1
|
||||
if err := d.sendCommandSequence([]byte{0x4f, yLo, yHi}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Device) hwReset() {
|
||||
d.rst.High()
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
d.rst.Low()
|
||||
time.Sleep(2 * time.Millisecond)
|
||||
d.rst.High()
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
|
||||
func (d *Device) setWindow(xstart, xend, ystart, yend int16) error {
|
||||
// set RAM X-address start / end position
|
||||
d1 := byte((xstart >> 3) & 0x1f)
|
||||
d2 := byte((xend >> 3) & 0x1f)
|
||||
if err := d.sendCommandSequence([]byte{0x44, d1, d2}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// set RAM Y-address start / end position
|
||||
ystartLo := byte(ystart)
|
||||
ystartHi := byte(ystart>>8) & 0x1
|
||||
|
||||
yendLo := byte(yend)
|
||||
yendHi := byte(yend>>8) & 0x1
|
||||
|
||||
return d.sendCommandSequence([]byte{0x45, ystartLo, ystartHi, yendLo, yendHi})
|
||||
}
|
||||
|
||||
func (d *Device) WaitUntilIdle() {
|
||||
// give it some time to get busy
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
for d.busy.Get() { // high = busy
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
|
||||
// give it some extra time
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
|
||||
// sendCommandSequence sends the first byte in the buffer as a 'command' and all following bytes as data
|
||||
func (d *Device) sendCommandSequence(seq []byte) error {
|
||||
err := d.sendCommandByte(seq[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i := 1; i < len(seq); i++ {
|
||||
err = d.sendDataByte(seq[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Device) sendCommandByte(b byte) error {
|
||||
d.dc.Low()
|
||||
d.cs.Low()
|
||||
_, err := d.bus.Transfer(b)
|
||||
d.cs.High()
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Device) sendDataByte(b byte) error {
|
||||
d.dc.High()
|
||||
d.cs.Low()
|
||||
_, err := d.bus.Transfer(b)
|
||||
d.cs.High()
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Device) sendData(b []byte) error {
|
||||
d.dc.High()
|
||||
d.cs.Low()
|
||||
err := d.bus.Tx(b, nil)
|
||||
d.cs.High()
|
||||
return err
|
||||
}
|
||||
|
||||
// fill quickly fills a slice with a given value
|
||||
func fill(s []byte, b byte) {
|
||||
s[0] = b
|
||||
for j := 1; j < len(s); j *= 2 {
|
||||
copy(s[j:], s[:j])
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
//go:build pico
|
||||
|
||||
package epd2in66b
|
||||
|
||||
import (
|
||||
"machine"
|
||||
)
|
||||
|
||||
// DefaultConfig contains the default config for the https://www.waveshare.com/wiki/Pico-ePaper-2.66 module
|
||||
var DefaultConfig = Config{
|
||||
DataPin: machine.GP8,
|
||||
ChipSelectPin: machine.GP9,
|
||||
ResetPin: machine.GP12,
|
||||
BusyPin: machine.GP13,
|
||||
}
|
||||
|
||||
// NewPicoModule allocates a new device backed by the https://www.waveshare.com/wiki/Pico-ePaper-2.66 module
|
||||
// This will also configure the SPI1 bus and configure the device with the DefaultConfig
|
||||
func NewPicoModule() (Device, error) {
|
||||
spi := machine.SPI1
|
||||
|
||||
if err := spi.Configure(machine.SPIConfig{
|
||||
Frequency: Baudrate,
|
||||
}); err != nil {
|
||||
return Device{}, err
|
||||
}
|
||||
|
||||
dev := New(spi)
|
||||
if err := dev.Configure(DefaultConfig); err != nil {
|
||||
return dev, err
|
||||
}
|
||||
|
||||
return dev, nil
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package epd2in66b
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/draw"
|
||||
"image/png"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
"tinygo.org/x/tinyfont"
|
||||
"tinygo.org/x/tinyfont/freemono"
|
||||
)
|
||||
|
||||
type mockBus struct{}
|
||||
|
||||
func (m *mockBus) Tx(w, r []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockBus) Transfer(b byte) (byte, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func TestBufferDrawing(t *testing.T) {
|
||||
dev := New(&mockBus{})
|
||||
|
||||
tinyfont.WriteLine(&dev, &freemono.Bold9pt7b, 10, 40, "Hello World!", color.RGBA{0xff, 0xff, 0xff, 0xff})
|
||||
|
||||
red := color.RGBA{0xff, 0, 0, 0xff}
|
||||
black := color.RGBA{0xff, 0xff, 0xff, 0xff}
|
||||
showRect(&dev, 10, 10, 10, 10, black)
|
||||
showRect(&dev, 10, 20, 10, 10, red)
|
||||
|
||||
img := toImage(&dev)
|
||||
writeImage(img)
|
||||
}
|
||||
|
||||
func toImage(dev *Device) *image.RGBA {
|
||||
red := color.RGBA{0xff, 0, 0, 0xff}
|
||||
|
||||
xMax, yMax := dev.Size()
|
||||
r := image.Rect(0, 0, int(xMax), int(yMax))
|
||||
container := image.NewRGBA(r)
|
||||
draw.Draw(container, container.Bounds(), image.NewUniform(color.White), image.Point{}, draw.Over)
|
||||
|
||||
for x := 0; x < int(xMax); x++ {
|
||||
for y := 0; y < int(yMax); y++ {
|
||||
|
||||
bytePos, bitPos := pos(int16(x), int16(y), displayWidth)
|
||||
|
||||
if isSet(dev.redBuffer, bytePos, bitPos) {
|
||||
container.Set(x, y, red)
|
||||
} else if isSet(dev.blackBuffer, bytePos, bitPos) {
|
||||
container.Set(x, y, color.Black)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return container
|
||||
}
|
||||
|
||||
func isSet(buf []byte, bytePos, bitPos int) bool {
|
||||
return (buf[bytePos])&(0x1<<bitPos) != 0
|
||||
}
|
||||
|
||||
func showRect(display drivers.Displayer, 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func writeImage(img image.Image) string {
|
||||
fn := fmt.Sprintf("%d.png", time.Now().Unix())
|
||||
f, err := os.OpenFile(fn, os.O_RDWR|os.O_CREATE, 0o644)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
err = png.Encode(f, img)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return fn
|
||||
}
|
||||
+83
-7
@@ -47,6 +47,9 @@ const (
|
||||
statusConnectFailed connectionStatus = 4
|
||||
statusConnectionLost connectionStatus = 5
|
||||
statusDisconnected connectionStatus = 6
|
||||
statusAPListening connectionStatus = 7
|
||||
statusAPConnected connectionStatus = 8
|
||||
statusAPFailed connectionStatus = 9
|
||||
|
||||
encTypeTKIP encryptionType = 2
|
||||
encTypeCCMP encryptionType = 4
|
||||
@@ -299,6 +302,61 @@ func (w *wifinina) connectToAP() error {
|
||||
return netlink.ErrConnectTimeout
|
||||
}
|
||||
|
||||
func (w *wifinina) startAP() error {
|
||||
timeout := w.params.ConnectTimeout
|
||||
if timeout == 0 {
|
||||
timeout = netlink.DefaultConnectTimeout
|
||||
}
|
||||
|
||||
if len(w.params.Ssid) == 0 {
|
||||
return netlink.ErrMissingSSID
|
||||
}
|
||||
|
||||
if debugging(debugBasic) {
|
||||
fmt.Printf("Starting Wifi AP as SSID '%s'...", w.params.Ssid)
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
|
||||
// Start the connection process
|
||||
switch {
|
||||
case w.params.Passphrase != "":
|
||||
w.setPassphraseForAP(w.params.Ssid, w.params.Passphrase)
|
||||
default:
|
||||
w.setNetworkForAP(w.params.Ssid)
|
||||
}
|
||||
|
||||
// Check if we are listening
|
||||
for {
|
||||
status := w.getConnectionStatus()
|
||||
switch status {
|
||||
case statusAPListening:
|
||||
if debugging(debugBasic) {
|
||||
fmt.Printf("LISTENING\r\n")
|
||||
}
|
||||
if w.notifyCb != nil {
|
||||
w.notifyCb(netlink.EventNetUp)
|
||||
}
|
||||
return nil
|
||||
case statusAPFailed:
|
||||
if debugging(debugBasic) {
|
||||
fmt.Printf("FAILED (%s)\r\n", w.reason())
|
||||
}
|
||||
return netlink.ErrConnectFailed
|
||||
}
|
||||
if time.Since(start) > timeout {
|
||||
break
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
||||
if debugging(debugBasic) {
|
||||
fmt.Printf("FAILED (timed out)\r\n")
|
||||
}
|
||||
|
||||
return netlink.ErrConnectTimeout
|
||||
}
|
||||
|
||||
func (w *wifinina) netDisconnect() {
|
||||
w.disconnect()
|
||||
}
|
||||
@@ -380,7 +438,12 @@ func (w *wifinina) showIP() {
|
||||
}
|
||||
|
||||
func (w *wifinina) networkDown() bool {
|
||||
return w.getConnectionStatus() != statusConnected
|
||||
switch w.getConnectionStatus() {
|
||||
case statusConnected, statusAPListening, statusAPConnected:
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func (w *wifinina) watchdog() {
|
||||
@@ -418,15 +481,28 @@ func (w *wifinina) netConnect(reset bool) error {
|
||||
}
|
||||
w.showDevice()
|
||||
|
||||
retry:
|
||||
for i := 0; w.params.Retries == 0 || i < w.params.Retries; i++ {
|
||||
if err := w.connectToAP(); err != nil {
|
||||
switch err {
|
||||
case netlink.ErrConnectTimeout, netlink.ErrConnectFailed:
|
||||
continue
|
||||
switch w.params.ConnectMode {
|
||||
case netlink.ConnectModeAP:
|
||||
if err := w.startAP(); err != nil {
|
||||
switch err {
|
||||
case netlink.ErrConnectTimeout, netlink.ErrConnectFailed:
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
return err
|
||||
break retry
|
||||
default:
|
||||
if err := w.connectToAP(); err != nil {
|
||||
switch err {
|
||||
case netlink.ErrConnectTimeout, netlink.ErrConnectFailed:
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
break retry
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if w.networkDown() {
|
||||
|
||||
Reference in New Issue
Block a user