mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-08-19 06:03:57 +00:00
Compare commits
27 Commits
lora-usa
...
gbadisplay
| Author | SHA1 | Date | |
|---|---|---|---|
| 5d1b0c39cc | |||
| 6cf07aff4c | |||
| af33129f41 | |||
| 100aadf585 | |||
| 4a9667ffef | |||
| f96a70915e | |||
| 3e64e754a2 | |||
| 2c2da5c7bb | |||
| 14994a3f31 | |||
| 47dfeb9e94 | |||
| f20d1759d3 | |||
| 6b79a1b386 | |||
| 7dd1067cc1 | |||
| 4edb771c5b | |||
| e95cfe66c1 | |||
| 4edb58c0c5 | |||
| f384e2db48 | |||
| 7c96387845 | |||
| 182d4c6ebb | |||
| dc2de4f9ed | |||
| 36a12dd7a2 | |||
| 00992756eb | |||
| 715c33d4b0 | |||
| fd21e6ac9b | |||
| 485ed702c8 | |||
| cfa50fd3c2 | |||
| 5888bb2ded |
@@ -1,3 +1,40 @@
|
|||||||
|
0.26.0
|
||||||
|
---
|
||||||
|
- **core**
|
||||||
|
- i2c iface refactor: Resolve #559
|
||||||
|
- fix uses of legacy i2c WriteRegister calls
|
||||||
|
- add correct Tx implementation for mock I2C interfaces
|
||||||
|
- bump golang.org/x/net version
|
||||||
|
|
||||||
|
- **new devices**
|
||||||
|
- **bma42x**
|
||||||
|
- add new BMA421/BMA425 driver
|
||||||
|
- **ndir**
|
||||||
|
- add Sandbox Electronics NDIR CO2 sensor driver (#580)
|
||||||
|
- **mpu9150**
|
||||||
|
- implement driver for Mpu9150 (#596)
|
||||||
|
- **sht4x**
|
||||||
|
- implement driver for sht4x (#597)
|
||||||
|
- **pcf8523**
|
||||||
|
- implement driver for pcf8523 (#599)
|
||||||
|
|
||||||
|
- **enhancements**
|
||||||
|
- **ssd1306**
|
||||||
|
- improve bus error handling
|
||||||
|
|
||||||
|
- **bugfixes**
|
||||||
|
- **st7789**
|
||||||
|
- fix scrolling when rotated by 180°
|
||||||
|
- **st7789**
|
||||||
|
- fix incorrect Rotation configuration
|
||||||
|
- fix SetScrollArea
|
||||||
|
- **ili9341**
|
||||||
|
- fix SetScrollArea
|
||||||
|
|
||||||
|
- **build**
|
||||||
|
- use latest tag of tinygo-dev container for running tests
|
||||||
|
|
||||||
|
|
||||||
0.25.0
|
0.25.0
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,10 @@
|
|||||||
[](https://pkg.go.dev/tinygo.org/x/drivers) [](https://github.com/tinygo-org/drivers/actions/workflows/build.yml)
|
[](https://pkg.go.dev/tinygo.org/x/drivers) [](https://github.com/tinygo-org/drivers/actions/workflows/build.yml)
|
||||||
|
|
||||||
|
|
||||||
This package provides a collection of hardware drivers for devices such as sensors and displays that can be used together with [TinyGo](https://tinygo.org).
|
This package provides a collection of 101 different hardware drivers for devices such as sensors and displays that can be used together with [TinyGo](https://tinygo.org).
|
||||||
|
|
||||||
|
For the complete list, please see:
|
||||||
|
https://tinygo.org/docs/reference/devices/
|
||||||
|
|
||||||
## Installing
|
## Installing
|
||||||
|
|
||||||
@@ -50,11 +53,6 @@ func main() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Supported devices
|
|
||||||
|
|
||||||
There are currently 96 devices supported. For the complete list, please see:
|
|
||||||
https://tinygo.org/docs/reference/devices/
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Your contributions are welcome!
|
Your contributions are welcome!
|
||||||
|
|||||||
@@ -0,0 +1,196 @@
|
|||||||
|
// Package adafruit4650 implements a driver for the Adafruit FeatherWing OLED - 128x64 OLED display.
|
||||||
|
// The display is backed itself by a SH1107 driver chip.
|
||||||
|
//
|
||||||
|
// Store: https://www.adafruit.com/product/4650
|
||||||
|
//
|
||||||
|
// Documentation: https://learn.adafruit.com/adafruit-128x64-oled-featherwing
|
||||||
|
package adafruit4650
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image/color"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"tinygo.org/x/drivers"
|
||||||
|
)
|
||||||
|
|
||||||
|
const DefaultAddress = 0x3c
|
||||||
|
|
||||||
|
const (
|
||||||
|
commandSetLowColumn = 0x00
|
||||||
|
commandSetHighColumn = 0x10
|
||||||
|
commandSetPage = 0xb0
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
width = 128
|
||||||
|
height = 64
|
||||||
|
)
|
||||||
|
|
||||||
|
// Device represents an Adafruit 4650 device
|
||||||
|
type Device struct {
|
||||||
|
bus drivers.I2C
|
||||||
|
Address uint8
|
||||||
|
buffer []byte
|
||||||
|
width int16
|
||||||
|
height int16
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new device, not configuring anything yet.
|
||||||
|
func New(bus drivers.I2C) Device {
|
||||||
|
return Device{
|
||||||
|
bus: bus,
|
||||||
|
Address: DefaultAddress,
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure initializes the display with default configuration
|
||||||
|
func (d *Device) Configure() error {
|
||||||
|
|
||||||
|
bufferSize := d.width * d.height / 8
|
||||||
|
d.buffer = make([]byte, bufferSize)
|
||||||
|
|
||||||
|
// This sequence is an amalgamation of the datasheet, official Arduino driver, CircuitPython driver and other drivers
|
||||||
|
initSequence := []byte{
|
||||||
|
0xae, // display off, sleep mode
|
||||||
|
//0xd5, 0x41, // set display clock divider (from original datasheet)
|
||||||
|
0xd5, 0x51, // set display clock divider (from Adafruit driver)
|
||||||
|
0xd9, 0x22, // pre-charge/dis-charge period mode: 2 DCLKs/2 DCLKs (POR)
|
||||||
|
0x20, // memory mode
|
||||||
|
0x81, 0x4f, // contrast setting = 0x4f
|
||||||
|
0xad, 0x8a, // set dc/dc pump
|
||||||
|
0xa0, // segment remap, flip-x
|
||||||
|
0xc0, // common output scan direction
|
||||||
|
0xdc, 0x00, // set display start line 0 (POR=0)
|
||||||
|
0xa8, 0x3f, // multiplex ratio, height - 1 = 0x3f
|
||||||
|
0xd3, 0x60, // set display offset mode = 0x60
|
||||||
|
0xdb, 0x35, // VCOM deselect level = 0.770 (POR)
|
||||||
|
0xa4, // entire display off, retain RAM, normal status (POR)
|
||||||
|
0xa6, // normal (not reversed) display
|
||||||
|
0xaf, // display on
|
||||||
|
}
|
||||||
|
|
||||||
|
err := d.writeCommands(initSequence)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// recommended in the datasheet, same in other drivers
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearDisplay clears the image buffer as well as the actual display
|
||||||
|
func (d *Device) ClearDisplay() error {
|
||||||
|
d.ClearBuffer()
|
||||||
|
return d.Display()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearBuffer clears the buffer
|
||||||
|
func (d *Device) ClearBuffer() {
|
||||||
|
bzero(d.buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPixel modifies the internal buffer. Since this display has a bit-depth of 1 bit any non-zero
|
||||||
|
// color component will be treated as 'on', otherwise 'off'.
|
||||||
|
func (d *Device) SetPixel(x int16, y int16, c color.RGBA) {
|
||||||
|
if x < 0 || x >= d.width || y < 0 || y >= d.height {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// RAM layout
|
||||||
|
// *-----> y
|
||||||
|
// |
|
||||||
|
// x| col0 col1 ... col63
|
||||||
|
// v p0 a0 b0 ..
|
||||||
|
// a1 b1 ..
|
||||||
|
// .. .. ..
|
||||||
|
// a7 b7 ..
|
||||||
|
// p1 a0 b0
|
||||||
|
// a1 b1
|
||||||
|
//
|
||||||
|
|
||||||
|
//flip y - so the display orientation matches the silk screen labeling etc.
|
||||||
|
y = d.height - y - 1
|
||||||
|
|
||||||
|
page := x / 8
|
||||||
|
bytesPerPage := d.height
|
||||||
|
byteIndex := y + bytesPerPage*page
|
||||||
|
bit := x % 8
|
||||||
|
if (c.R | c.G | c.B) != 0 {
|
||||||
|
d.buffer[byteIndex] |= 1 << uint8(bit)
|
||||||
|
} else {
|
||||||
|
d.buffer[byteIndex] &^= 1 << uint8(bit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display sends the whole buffer to the screen
|
||||||
|
func (d *Device) Display() error {
|
||||||
|
|
||||||
|
bytesPerPage := d.height
|
||||||
|
|
||||||
|
pages := (d.width + 7) / 8
|
||||||
|
for page := int16(0); page < pages; page++ {
|
||||||
|
|
||||||
|
err := d.setRAMPosition(uint8(page), 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
offset := page * bytesPerPage
|
||||||
|
err = d.writeRAM(d.buffer[offset : offset+bytesPerPage])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// setRAMPosition updates the device's current page and column position
|
||||||
|
func (d *Device) setRAMPosition(page uint8, column uint8) error {
|
||||||
|
if page > 15 {
|
||||||
|
panic("page out of bounds")
|
||||||
|
}
|
||||||
|
if column > 127 {
|
||||||
|
panic("column out of bounds")
|
||||||
|
}
|
||||||
|
setPage := commandSetPage | (page & 0xF)
|
||||||
|
|
||||||
|
lo := column & 0xF
|
||||||
|
setLowColumn := commandSetLowColumn | lo
|
||||||
|
|
||||||
|
hi := (column >> 4) & 0x7
|
||||||
|
setHighColumn := commandSetHighColumn | hi
|
||||||
|
|
||||||
|
cmds := []byte{
|
||||||
|
setPage,
|
||||||
|
setLowColumn,
|
||||||
|
setHighColumn,
|
||||||
|
}
|
||||||
|
|
||||||
|
return d.writeCommands(cmds)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size returns the current size of the display.
|
||||||
|
func (d *Device) Size() (w, h int16) {
|
||||||
|
return d.width, d.height
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Device) writeCommands(commands []byte) error {
|
||||||
|
onlyCommandsFollowing := byte(0x00)
|
||||||
|
return d.bus.Tx(uint16(d.Address), append([]byte{onlyCommandsFollowing}, commands...), nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Device) writeRAM(data []byte) error {
|
||||||
|
onlyRAMFollowing := byte(0x40)
|
||||||
|
return d.bus.Tx(uint16(d.Address), append([]byte{onlyRAMFollowing}, data...), nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func bzero(buf []byte) {
|
||||||
|
for i := range buf {
|
||||||
|
buf[i] = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
package adafruit4650
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
_ "embed"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
"image/draw"
|
||||||
|
"image/png"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
"tinygo.org/x/drivers"
|
||||||
|
"tinygo.org/x/tinyfont"
|
||||||
|
"tinygo.org/x/tinyfont/freemono"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed expected_hello_world.png
|
||||||
|
var expectedHelloWorld []byte
|
||||||
|
|
||||||
|
// mockBus mocks a fake i2c device adafruit4650 display.
|
||||||
|
// The memory layout assumes that clients set up the device in a particular way and always send complete
|
||||||
|
// pages to the device buffer.
|
||||||
|
type mockBus struct {
|
||||||
|
img draw.Image
|
||||||
|
line int
|
||||||
|
addr uint8
|
||||||
|
currentPage int
|
||||||
|
currentColumn int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockBus) Tx(addr uint16, w, r []byte) error {
|
||||||
|
if addr != uint16(m.addr) {
|
||||||
|
panic("unexpected address")
|
||||||
|
}
|
||||||
|
if r != nil {
|
||||||
|
panic("mock does not support reads")
|
||||||
|
}
|
||||||
|
|
||||||
|
if w[0] == 0x00 {
|
||||||
|
if w[1]&0xf0 == 0xb0 {
|
||||||
|
m.currentPage = int(w[1] & 0x0f)
|
||||||
|
|
||||||
|
lo := w[2] & 0x0f
|
||||||
|
hi := w[2] & 0x07
|
||||||
|
m.currentColumn = int(hi<<4 | lo)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if w[0] != 0x40 {
|
||||||
|
panic("unexpected first byte: " + hex.EncodeToString(w[0:1]))
|
||||||
|
}
|
||||||
|
|
||||||
|
return m.writeRAM(w[1:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMock() *mockBus {
|
||||||
|
|
||||||
|
m := image.NewRGBA(image.Rect(0, 0, width, height))
|
||||||
|
return &mockBus{img: m, addr: DefaultAddress, currentPage: -1, currentColumn: -1}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockBus) writeRAM(data []byte) error {
|
||||||
|
|
||||||
|
// RAM layout
|
||||||
|
// *-----> y
|
||||||
|
// |
|
||||||
|
// x| col0 col1 ... col63
|
||||||
|
// v p0 a0 b0 ..
|
||||||
|
// a1 b1 ..
|
||||||
|
// .. .. ..
|
||||||
|
// a7 b7 ..
|
||||||
|
// p1 a0 b0
|
||||||
|
// a1 b1
|
||||||
|
//
|
||||||
|
|
||||||
|
fmt.Printf("writing page %d\n", m.currentPage)
|
||||||
|
// assuming entire pages will be written
|
||||||
|
for x := 0; x < 8; x++ {
|
||||||
|
for y := 0; y < height; y++ {
|
||||||
|
|
||||||
|
col := data[y]
|
||||||
|
|
||||||
|
c := color.Black
|
||||||
|
if col&(1<<x) != 0 {
|
||||||
|
c = color.White
|
||||||
|
}
|
||||||
|
|
||||||
|
m.img.Set(x+m.currentPage*8, height-y-1, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockBus) toImage() *image.RGBA {
|
||||||
|
|
||||||
|
container := image.NewRGBA(m.img.Bounds().Inset(-1))
|
||||||
|
draw.Draw(container, container.Bounds(), image.NewUniform(color.RGBA{G: 255, A: 255}), image.Point{}, draw.Over)
|
||||||
|
draw.Draw(container, m.img.Bounds(), m.img, image.Point{}, draw.Over)
|
||||||
|
return container
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDevice_Display(t *testing.T) {
|
||||||
|
|
||||||
|
bus := newMock()
|
||||||
|
dev := New(bus)
|
||||||
|
|
||||||
|
dev.Configure()
|
||||||
|
|
||||||
|
drawPlus(&dev)
|
||||||
|
drawHellowWorld(&dev)
|
||||||
|
|
||||||
|
//when
|
||||||
|
dev.Display()
|
||||||
|
|
||||||
|
//then
|
||||||
|
actual := bus.toImage()
|
||||||
|
|
||||||
|
expected, err := png.Decode(bytes.NewReader(expectedHelloWorld))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEqualImages(t, actual, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
func drawPlus(d drivers.Displayer) {
|
||||||
|
for i := int16(0); i < 128; i++ {
|
||||||
|
d.SetPixel(i, 32, color.RGBA{R: 1})
|
||||||
|
}
|
||||||
|
for i := int16(0); i < 64; i++ {
|
||||||
|
d.SetPixel(64, i, color.RGBA{R: 1})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func drawHellowWorld(d drivers.Displayer) {
|
||||||
|
tinyfont.WriteLine(d, &freemono.Regular9pt7b, 0, 32, "Hello World!", color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff})
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertEqualImages(t testing.TB, actual, expected image.Image) {
|
||||||
|
|
||||||
|
if actual.Bounds().Dx() != expected.Bounds().Dx() || actual.Bounds().Dy() != expected.Bounds().Dy() {
|
||||||
|
f := writeImage(actual)
|
||||||
|
t.Fatalf("differing size: was %v, expected %v, saved actual to %s", actual.Bounds(), expected.Bounds(), f)
|
||||||
|
}
|
||||||
|
|
||||||
|
bb := expected.Bounds()
|
||||||
|
for x := bb.Min.X; x < bb.Max.X; x++ {
|
||||||
|
for y := bb.Min.Y; y < bb.Max.Y; y++ {
|
||||||
|
actualBB := actual.Bounds()
|
||||||
|
if actual.At(x+actualBB.Min.X, y+actualBB.Min.Y) != expected.At(x, y) {
|
||||||
|
f := writeImage(actual)
|
||||||
|
t.Fatalf("different pixel at %d/%d: %v != %v, saved actual at %s", x, y, actual.At(x, y), expected.At(x, y), f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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, 0644)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
err = png.Encode(f, img)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return fn
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 449 B |
+1
-1
@@ -11,7 +11,7 @@ import (
|
|||||||
"tinygo.org/x/drivers"
|
"tinygo.org/x/drivers"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Device wraps an I2C connection to a DS3231 device.
|
// Device wraps an I2C connection to an AT24CX device.
|
||||||
type Device struct {
|
type Device struct {
|
||||||
bus drivers.I2C
|
bus drivers.I2C
|
||||||
Address uint16
|
Address uint16
|
||||||
|
|||||||
+1
-1
@@ -49,7 +49,7 @@ func Sleep(duration time.Duration) {
|
|||||||
// * The CPU frequency is lower than 256MHz. If it is higher, long sleep
|
// * The CPU frequency is lower than 256MHz. If it is higher, long sleep
|
||||||
// times (1-16ms) may not work correctly.
|
// times (1-16ms) may not work correctly.
|
||||||
cycles := uint32(duration) * (machine.CPUFrequency() / 1000_000) / 1000
|
cycles := uint32(duration) * (machine.CPUFrequency() / 1000_000) / 1000
|
||||||
slept := C.tinygo_drivers_sleep(cycles)
|
slept := C.tinygo_drivers_sleep(C.uint32_t(cycles))
|
||||||
if !slept {
|
if !slept {
|
||||||
// Fallback for platforms without inline assembly support.
|
// Fallback for platforms without inline assembly support.
|
||||||
time.Sleep(duration)
|
time.Sleep(duration)
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image/color"
|
||||||
|
"machine"
|
||||||
|
"tinygo.org/x/drivers"
|
||||||
|
"tinygo.org/x/drivers/adafruit4650"
|
||||||
|
"tinygo.org/x/tinyfont"
|
||||||
|
"tinygo.org/x/tinyfont/freemono"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
machine.I2C0.Configure(machine.I2CConfig{})
|
||||||
|
|
||||||
|
dev := adafruit4650.New(machine.I2C0)
|
||||||
|
|
||||||
|
err := dev.Configure()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
drawPlus(&dev)
|
||||||
|
drawHelloWorld(&dev)
|
||||||
|
|
||||||
|
err = dev.Display()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func drawPlus(d drivers.Displayer) {
|
||||||
|
for i := int16(0); i < 128; i++ {
|
||||||
|
d.SetPixel(i, 32, color.RGBA{R: 1})
|
||||||
|
}
|
||||||
|
for i := int16(0); i < 64; i++ {
|
||||||
|
d.SetPixel(64, i, color.RGBA{R: 1})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func drawHelloWorld(d drivers.Displayer) {
|
||||||
|
tinyfont.WriteLine(d, &freemono.Regular9pt7b, 0, 32, "Hello World!", color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff})
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// Connects to an MAG3110 I2C magnetometer.
|
// Connects to an DS3231 I2C Real Time Clock (RTC).
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -8,15 +8,16 @@ import (
|
|||||||
"tinygo.org/x/drivers/examples/ili9341/initdisplay"
|
"tinygo.org/x/drivers/examples/ili9341/initdisplay"
|
||||||
"tinygo.org/x/drivers/examples/ili9341/pyportal_boing/graphics"
|
"tinygo.org/x/drivers/examples/ili9341/pyportal_boing/graphics"
|
||||||
"tinygo.org/x/drivers/ili9341"
|
"tinygo.org/x/drivers/ili9341"
|
||||||
|
"tinygo.org/x/drivers/pixel"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
BGCOLOR = 0xAD75
|
BGCOLOR = pixel.RGB565BE(0x75AD)
|
||||||
GRIDCOLOR = 0xA815
|
GRIDCOLOR = pixel.RGB565BE(0x15A8)
|
||||||
BGSHADOW = 0x5285
|
BGSHADOW = pixel.RGB565BE(0x8552)
|
||||||
GRIDSHADOW = 0x600C
|
GRIDSHADOW = pixel.RGB565BE(0x0C60)
|
||||||
RED = 0xF800
|
RED = pixel.RGB565BE(0x00F8)
|
||||||
WHITE = 0xFFFF
|
WHITE = pixel.RGB565BE(0xFFFF)
|
||||||
|
|
||||||
YBOTTOM = 123 // Ball Y coord at bottom
|
YBOTTOM = 123 // Ball Y coord at bottom
|
||||||
YBOUNCE = -3.5 // Upward velocity on ball bounce
|
YBOUNCE = -3.5 // Upward velocity on ball bounce
|
||||||
@@ -25,7 +26,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
frameBuffer = [(graphics.BALLHEIGHT + 8) * (graphics.BALLWIDTH + 8) * 2]uint8{}
|
frameBuffer = pixel.NewImage[pixel.RGB565BE](graphics.BALLWIDTH+8, graphics.BALLHEIGHT+8)
|
||||||
|
|
||||||
startTime int64
|
startTime int64
|
||||||
frame int64
|
frame int64
|
||||||
@@ -41,7 +42,7 @@ var (
|
|||||||
balloldy float32
|
balloldy float32
|
||||||
|
|
||||||
// Color table for ball rotation effect
|
// Color table for ball rotation effect
|
||||||
palette [16]uint16
|
palette [16]pixel.RGB565BE
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -108,6 +109,7 @@ func main() {
|
|||||||
|
|
||||||
width = maxx - minx + 1
|
width = maxx - minx + 1
|
||||||
height = maxy - miny + 1
|
height = maxy - miny + 1
|
||||||
|
buffer := frameBuffer.Rescale(int(width), int(height))
|
||||||
|
|
||||||
// Ball animation frame # is incremented opposite the ball's X velocity
|
// Ball animation frame # is incremented opposite the ball's X velocity
|
||||||
ballframe -= ballvx * 0.5
|
ballframe -= ballvx * 0.5
|
||||||
@@ -128,7 +130,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Only the changed rectangle is drawn into the 'renderbuf' array...
|
// Only the changed rectangle is drawn into the 'renderbuf' array...
|
||||||
var c uint16 //, *destPtr;
|
var c pixel.RGB565BE //, *destPtr;
|
||||||
bx := minx - int16(ballx) // X relative to ball bitmap (can be negative)
|
bx := minx - int16(ballx) // X relative to ball bitmap (can be negative)
|
||||||
by := miny - int16(bally) // Y relative to ball bitmap (can be negative)
|
by := miny - int16(bally) // Y relative to ball bitmap (can be negative)
|
||||||
bgx := minx // X relative to background bitmap (>= 0)
|
bgx := minx // X relative to background bitmap (>= 0)
|
||||||
@@ -149,19 +151,20 @@ func main() {
|
|||||||
(by >= 0) && (by < graphics.BALLHEIGHT) { // inside the ball bitmap area?
|
(by >= 0) && (by < graphics.BALLHEIGHT) { // inside the ball bitmap area?
|
||||||
// Yes, do ball compositing math...
|
// Yes, do ball compositing math...
|
||||||
p = graphics.Ball[int(by*(graphics.BALLWIDTH/2))+int(bx1/2)] // Get packed value (2 pixels)
|
p = graphics.Ball[int(by*(graphics.BALLWIDTH/2))+int(bx1/2)] // Get packed value (2 pixels)
|
||||||
|
var nibble uint8
|
||||||
if (bx1 & 1) != 0 {
|
if (bx1 & 1) != 0 {
|
||||||
c = uint16(p & 0xF)
|
nibble = p & 0xF
|
||||||
} else {
|
} else {
|
||||||
c = uint16(p >> 4)
|
nibble = p >> 4
|
||||||
} // Unpack high or low nybble
|
} // Unpack high or low nybble
|
||||||
if c == 0 { // Outside ball - just draw grid
|
if nibble == 0 { // Outside ball - just draw grid
|
||||||
if graphics.Background[bgidx]&(0x80>>(bgx1&7)) != 0 {
|
if graphics.Background[bgidx]&(0x80>>(bgx1&7)) != 0 {
|
||||||
c = GRIDCOLOR
|
c = GRIDCOLOR
|
||||||
} else {
|
} else {
|
||||||
c = BGCOLOR
|
c = BGCOLOR
|
||||||
}
|
}
|
||||||
} else if c > 1 { // In ball area...
|
} else if nibble > 1 { // In ball area...
|
||||||
c = palette[c]
|
c = palette[nibble]
|
||||||
} else { // In shadow area...
|
} else { // In shadow area...
|
||||||
if graphics.Background[bgidx]&(0x80>>(bgx1&7)) != 0 {
|
if graphics.Background[bgidx]&(0x80>>(bgx1&7)) != 0 {
|
||||||
c = GRIDSHADOW
|
c = GRIDSHADOW
|
||||||
@@ -176,8 +179,7 @@ func main() {
|
|||||||
c = BGCOLOR
|
c = BGCOLOR
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
frameBuffer[(y*int(width)+x)*2] = byte(c >> 8)
|
buffer.Set(x, y, c)
|
||||||
frameBuffer[(y*int(width)+x)*2+1] = byte(c)
|
|
||||||
bx1++ // Increment bitmap position counters (X axis)
|
bx1++ // Increment bitmap position counters (X axis)
|
||||||
bgx1++
|
bgx1++
|
||||||
}
|
}
|
||||||
@@ -188,7 +190,7 @@ func main() {
|
|||||||
bgy++
|
bgy++
|
||||||
}
|
}
|
||||||
|
|
||||||
display.DrawRGBBitmap8(minx, miny, frameBuffer[:width*height*2], width, height)
|
display.DrawBitmap(minx, miny, buffer)
|
||||||
|
|
||||||
// Show approximate frame rate
|
// Show approximate frame rate
|
||||||
frame++
|
frame++
|
||||||
@@ -205,6 +207,7 @@ func DrawBackground() {
|
|||||||
w, h := display.Size()
|
w, h := display.Size()
|
||||||
byteWidth := (w + 7) / 8 // Bitmap scanline pad = whole byte
|
byteWidth := (w + 7) / 8 // Bitmap scanline pad = whole byte
|
||||||
var b uint8
|
var b uint8
|
||||||
|
buffer := frameBuffer.Rescale(int(w), 1)
|
||||||
for j := int16(0); j < h; j++ {
|
for j := int16(0); j < h; j++ {
|
||||||
for k := int16(0); k < w; k++ {
|
for k := int16(0); k < w; k++ {
|
||||||
if k&7 > 0 {
|
if k&7 > 0 {
|
||||||
@@ -213,13 +216,11 @@ func DrawBackground() {
|
|||||||
b = graphics.Background[j*byteWidth+k/8]
|
b = graphics.Background[j*byteWidth+k/8]
|
||||||
}
|
}
|
||||||
if b&0x80 == 0 {
|
if b&0x80 == 0 {
|
||||||
frameBuffer[2*k] = byte(BGCOLOR >> 8)
|
buffer.Set(int(k), 0, BGCOLOR)
|
||||||
frameBuffer[2*k+1] = byte(BGCOLOR & 0xFF)
|
|
||||||
} else {
|
} else {
|
||||||
frameBuffer[2*k] = byte(GRIDCOLOR >> 8)
|
buffer.Set(int(k), 0, GRIDCOLOR)
|
||||||
frameBuffer[2*k+1] = byte(GRIDCOLOR & 0xFF)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
display.DrawRGBBitmap8(0, j, frameBuffer[0:w*2], w, 1)
|
display.DrawBitmap(0, j, buffer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,20 +23,20 @@ Builds/flashes atcmd console application with simulator instead of actual LoRa r
|
|||||||
tinygo flash -target pico ./examples/lora/lorawan/atcmd/
|
tinygo flash -target pico ./examples/lora/lorawan/atcmd/
|
||||||
```
|
```
|
||||||
|
|
||||||
## PyBadge with LoRa Featherwing
|
## PyBadge with LoRa Featherwing for EU868 region
|
||||||
|
|
||||||
Builds/flashes atcmd console application on PyBadge using LoRa Featherwing (RFM95/SX1276).
|
Builds/flashes atcmd console application on PyBadge using LoRa Featherwing (RFM95/SX1276).
|
||||||
|
|
||||||
```
|
```
|
||||||
tinygo flash -target pybadge -tags featherwing ./examples/lora/lorawan/atcmd/
|
tinygo flash -target pybadge -tags featherwing -ldflags="-X main.reg=EU868" ./examples/lora/lorawan/atcmd/
|
||||||
```
|
```
|
||||||
|
|
||||||
## LoRa-E5
|
## LoRa-E5 for US915 region
|
||||||
|
|
||||||
Builds/flashes atcmd console application on Lora-E5 using onboard SX126x.
|
Builds/flashes atcmd console application on Lora-E5 using onboard SX126x.
|
||||||
|
|
||||||
```
|
```
|
||||||
tinygo flash -target lorae5 ./examples/lora/lorawan/atcmd/
|
tinygo flash -target lorae5 -ldflags="-X main.reg=US915" ./examples/lora/lorawan/atcmd/
|
||||||
```
|
```
|
||||||
|
|
||||||
## Joining a Public Lorawan Network
|
## Joining a Public Lorawan Network
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ var (
|
|||||||
defaultTimeout uint32 = 1000
|
defaultTimeout uint32 = 1000
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var reg string
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
uart.Configure(machine.UARTConfig{TX: tx, RX: rx})
|
uart.Configure(machine.UARTConfig{TX: tx, RX: rx})
|
||||||
|
|
||||||
@@ -45,7 +47,16 @@ func main() {
|
|||||||
otaa = &lorawan.Otaa{}
|
otaa = &lorawan.Otaa{}
|
||||||
lorawan.UseRadio(radio)
|
lorawan.UseRadio(radio)
|
||||||
|
|
||||||
lorawan.UseRegionSettings(region.EU868())
|
switch reg {
|
||||||
|
case "AU915":
|
||||||
|
lorawan.UseRegionSettings(region.AU915())
|
||||||
|
case "EU868":
|
||||||
|
lorawan.UseRegionSettings(region.EU868())
|
||||||
|
case "US915":
|
||||||
|
lorawan.UseRegionSettings(region.US915())
|
||||||
|
default:
|
||||||
|
lorawan.UseRegionSettings(region.EU868())
|
||||||
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
if uart.Buffered() > 0 {
|
if uart.Buffered() > 0 {
|
||||||
|
|||||||
@@ -21,16 +21,16 @@ loraConnect: Connected !
|
|||||||
tinygo flash -target pico ./examples/lora/lorawan/basic-demo
|
tinygo flash -target pico ./examples/lora/lorawan/basic-demo
|
||||||
```
|
```
|
||||||
|
|
||||||
## PyBadge with LoRa Featherwing
|
## PyBadge with LoRa Featherwing for EU868 region
|
||||||
|
|
||||||
```
|
```
|
||||||
tinygo flash -target pybadge -tags featherwing ./examples/lora/lorawan/basic-demo
|
tinygo flash -target pybadge -tags featherwing -ldflags="-X main.reg=EU868" ./examples/lora/lorawan/basic-demo
|
||||||
```
|
```
|
||||||
|
|
||||||
## LoRa-E5
|
## LoRa-E5 for US915 region
|
||||||
|
|
||||||
```
|
```
|
||||||
tinygo flash -target lorae5 ./examples/lora/lorawan/basic-demo
|
tinygo flash -target lorae5 -ldflags="-X main.reg=US915" ./examples/lora/lorawan/basic-demo
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,10 @@ import (
|
|||||||
"tinygo.org/x/drivers/lora/lorawan/region"
|
"tinygo.org/x/drivers/lora/lorawan/region"
|
||||||
)
|
)
|
||||||
|
|
||||||
var debug string
|
var (
|
||||||
|
reg string
|
||||||
|
debug string
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
LORAWAN_JOIN_TIMEOUT_SEC = 180
|
LORAWAN_JOIN_TIMEOUT_SEC = 180
|
||||||
@@ -67,8 +70,16 @@ func main() {
|
|||||||
|
|
||||||
// Connect the lorawan with the Lora Radio device.
|
// Connect the lorawan with the Lora Radio device.
|
||||||
lorawan.UseRadio(radio)
|
lorawan.UseRadio(radio)
|
||||||
|
switch reg {
|
||||||
lorawan.UseRegionSettings(region.EU868())
|
case "AU915":
|
||||||
|
lorawan.UseRegionSettings(region.AU915())
|
||||||
|
case "EU868":
|
||||||
|
lorawan.UseRegionSettings(region.EU868())
|
||||||
|
case "US915":
|
||||||
|
lorawan.UseRegionSettings(region.US915())
|
||||||
|
default:
|
||||||
|
lorawan.UseRegionSettings(region.EU868())
|
||||||
|
}
|
||||||
|
|
||||||
// Configure AppEUI, DevEUI, APPKey, and public/private Lorawan Network
|
// Configure AppEUI, DevEUI, APPKey, and public/private Lorawan Network
|
||||||
setLorawanKeys()
|
setLorawanKeys()
|
||||||
|
|||||||
@@ -23,13 +23,18 @@ func (sr *SimLoraRadio) Rx(timeoutMs uint32) ([]uint8, error) {
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sr *SimLoraRadio) SetFrequency(freq uint32) {}
|
func (sr *SimLoraRadio) SetFrequency(freq uint32) {}
|
||||||
func (sr *SimLoraRadio) SetIqMode(mode uint8) {}
|
func (sr *SimLoraRadio) SetIqMode(mode uint8) {}
|
||||||
func (sr *SimLoraRadio) SetCodingRate(cr uint8) {}
|
func (sr *SimLoraRadio) SetCodingRate(cr uint8) {}
|
||||||
func (sr *SimLoraRadio) SetBandwidth(bw uint8) {}
|
func (sr *SimLoraRadio) SetBandwidth(bw uint8) {}
|
||||||
func (sr *SimLoraRadio) SetCrc(enable bool) {}
|
func (sr *SimLoraRadio) SetCrc(enable bool) {}
|
||||||
func (sr *SimLoraRadio) SetSpreadingFactor(sf uint8) {}
|
func (sr *SimLoraRadio) SetSpreadingFactor(sf uint8) {}
|
||||||
func (sr *SimLoraRadio) LoraConfig(cnf lora.Config) {}
|
func (sr *SimLoraRadio) SetHeaderType(headerType uint8) {}
|
||||||
|
func (sr *SimLoraRadio) SetPreambleLength(pLen uint16) {}
|
||||||
|
func (sr *SimLoraRadio) SetPublicNetwork(enabled bool) {}
|
||||||
|
func (sr *SimLoraRadio) SetSyncWord(syncWord uint16) {}
|
||||||
|
func (sr *SimLoraRadio) SetTxPower(txPower int8) {}
|
||||||
|
func (sr *SimLoraRadio) LoraConfig(cnf lora.Config) {}
|
||||||
|
|
||||||
func FirmwareVersion() string {
|
func FirmwareVersion() string {
|
||||||
return "simulator " + CurrentVersion()
|
return "simulator " + CurrentVersion()
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"machine"
|
||||||
|
"time"
|
||||||
|
"tinygo.org/x/drivers/pcf8523"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
machine.I2C0.Configure(machine.I2CConfig{})
|
||||||
|
dev := pcf8523.New(machine.I2C0)
|
||||||
|
|
||||||
|
// make sure the battery takes over if power is lost
|
||||||
|
err := dev.SetPowerManagement(pcf8523.PowerManagement_SwitchOver_ModeStandard)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// set RTC once, i.e. from `date -u +"%Y-%m-%dT%H:%M:%SZ"`
|
||||||
|
now, _ := time.Parse(time.RFC3339, "2023-09-18T20:31:38Z")
|
||||||
|
err = dev.SetTime(now)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
ts, err := dev.ReadTime()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
println("tick-tock, it's: " + ts.String())
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"machine"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"tinygo.org/x/drivers/sht4x"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
machine.I2C0.Configure(machine.I2CConfig{})
|
||||||
|
sensor := sht4x.New(machine.I2C0)
|
||||||
|
|
||||||
|
for {
|
||||||
|
temp, humidity, _ := sensor.ReadTemperatureHumidity()
|
||||||
|
t := fmt.Sprintf("%.2f", float32(temp)/1000)
|
||||||
|
h := fmt.Sprintf("%.2f", float32(humidity)/100)
|
||||||
|
println("Temperature: ", t, "°C")
|
||||||
|
println("Humidity: ", h, "%")
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
// Package gbadisplay implements a simple driver for the GameBoy Advance
|
||||||
|
// display.
|
||||||
|
package gbadisplay
|
||||||
|
|
||||||
|
import (
|
||||||
|
"device/gba"
|
||||||
|
"errors"
|
||||||
|
"image/color"
|
||||||
|
"runtime/volatile"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"tinygo.org/x/drivers/pixel"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Image buffer type used by the GameBoy Advance.
|
||||||
|
type Image = pixel.Image[pixel.RGB555]
|
||||||
|
|
||||||
|
const (
|
||||||
|
displayWidth = 240
|
||||||
|
displayHeight = 160
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errOutOfBounds = errors.New("rectangle coordinates outside display area")
|
||||||
|
)
|
||||||
|
|
||||||
|
type Device struct{}
|
||||||
|
|
||||||
|
// New returns a new GameBoy Advance display object.
|
||||||
|
func New() Device {
|
||||||
|
return Device{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var displayFrameBuffer = (*[160 * 240]volatile.Register16)(unsafe.Pointer(uintptr(gba.MEM_VRAM)))
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
// TODO: add more display modes here.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure the display as a regular 15bpp framebuffer.
|
||||||
|
func (d Device) Configure(config Config) {
|
||||||
|
// Use video mode 3 (in BG2, a 16bpp bitmap in VRAM) and Enable BG2.
|
||||||
|
gba.DISP.DISPCNT.Set(gba.DISPCNT_BGMODE_3<<gba.DISPCNT_BGMODE_Pos |
|
||||||
|
gba.DISPCNT_SCREENDISPLAY_BG2_ENABLE<<gba.DISPCNT_SCREENDISPLAY_BG2_Pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size returns the fixed size of this display.
|
||||||
|
func (d Device) Size() (x, y int16) {
|
||||||
|
return displayWidth, displayHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display is a no-op: the display framebuffer is modified directly.
|
||||||
|
func (d Device) Display() error {
|
||||||
|
// Nothing to do here.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPixel changes the pixel at (x, y) to the given color.
|
||||||
|
func (d Device) SetPixel(x, y int16, c color.RGBA) {
|
||||||
|
if x < 0 || y < 0 || x >= displayWidth || y > displayHeight {
|
||||||
|
// Out of bounds, so ignore.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val := pixel.NewColor[pixel.RGB555](c.R, c.G, c.B)
|
||||||
|
displayFrameBuffer[(int(y))*240+int(x)].Set(uint16(val))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DrawBitmap updates the rectangle at (x, y) to the image stored in buf.
|
||||||
|
func (d Device) DrawBitmap(x, y int16, buf Image) error {
|
||||||
|
width, height := buf.Size()
|
||||||
|
if x < 0 || y < 0 || int(x)+width > displayWidth || int(y)+height > displayHeight {
|
||||||
|
return errOutOfBounds
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: try to do a 4-byte memcpy if possible. That should significantly
|
||||||
|
// speed up the copying of this image.
|
||||||
|
for bufY := 0; bufY < int(height); bufY++ {
|
||||||
|
for bufX := 0; bufX < int(width); bufX++ {
|
||||||
|
val := buf.Get(bufX, bufY)
|
||||||
|
displayFrameBuffer[(int(y)+bufY)*240+int(x)+bufX].Set(uint16(val))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
module tinygo.org/x/drivers
|
module tinygo.org/x/drivers
|
||||||
|
|
||||||
go 1.15
|
go 1.18
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/eclipse/paho.mqtt.golang v1.2.0
|
github.com/eclipse/paho.mqtt.golang v1.2.0
|
||||||
@@ -10,3 +10,11 @@ require (
|
|||||||
tinygo.org/x/tinyfont v0.3.0
|
tinygo.org/x/tinyfont v0.3.0
|
||||||
tinygo.org/x/tinyterm v0.1.0
|
tinygo.org/x/tinyterm v0.1.0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/google/go-cmp v0.5.2 // indirect
|
||||||
|
github.com/kr/pretty v0.2.1 // indirect
|
||||||
|
github.com/kr/text v0.1.0 // indirect
|
||||||
|
golang.org/x/text v0.7.0 // indirect
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 // indirect
|
||||||
|
)
|
||||||
|
|||||||
@@ -7,56 +7,22 @@ github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
|
|||||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
||||||
github.com/hajimehoshi/go-jisx0208 v1.0.0/go.mod h1:yYxEStHL7lt9uL+AbdWgW9gBumwieDoZCiB1f/0X0as=
|
|
||||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/sago35/go-bdf v0.0.0-20200313142241-6c17821c91c4/go.mod h1:rOebXGuMLsXhZAC6mF/TjxONsm45498ZyzVhel++6KM=
|
|
||||||
github.com/valyala/fastjson v1.6.3/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
|
github.com/valyala/fastjson v1.6.3/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
|
||||||
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
|
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
|
||||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
|
||||||
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
|
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
|
||||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
|
||||||
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
|
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
tinygo.org/x/drivers v0.14.0/go.mod h1:uT2svMq3EpBZpKkGO+NQHjxjGf1f42ra4OnMMwQL2aI=
|
tinygo.org/x/drivers v0.14.0/go.mod h1:uT2svMq3EpBZpKkGO+NQHjxjGf1f42ra4OnMMwQL2aI=
|
||||||
tinygo.org/x/drivers v0.15.1/go.mod h1:uT2svMq3EpBZpKkGO+NQHjxjGf1f42ra4OnMMwQL2aI=
|
tinygo.org/x/drivers v0.15.1/go.mod h1:uT2svMq3EpBZpKkGO+NQHjxjGf1f42ra4OnMMwQL2aI=
|
||||||
tinygo.org/x/drivers v0.16.0/go.mod h1:uT2svMq3EpBZpKkGO+NQHjxjGf1f42ra4OnMMwQL2aI=
|
|
||||||
tinygo.org/x/drivers v0.19.0/go.mod h1:uJD/l1qWzxzLx+vcxaW0eY464N5RAgFi1zTVzASFdqI=
|
|
||||||
tinygo.org/x/tinyfont v0.2.1/go.mod h1:eLqnYSrFRjt5STxWaMeOWJTzrKhXqpWw7nU3bPfKOAM=
|
tinygo.org/x/tinyfont v0.2.1/go.mod h1:eLqnYSrFRjt5STxWaMeOWJTzrKhXqpWw7nU3bPfKOAM=
|
||||||
tinygo.org/x/tinyfont v0.3.0 h1:HIRLQoI3oc+2CMhPcfv+Ig88EcTImE/5npjqOnMD4lM=
|
tinygo.org/x/tinyfont v0.3.0 h1:HIRLQoI3oc+2CMhPcfv+Ig88EcTImE/5npjqOnMD4lM=
|
||||||
tinygo.org/x/tinyfont v0.3.0/go.mod h1:+TV5q0KpwSGRWnN+ITijsIhrWYJkoUCp9MYELjKpAXk=
|
tinygo.org/x/tinyfont v0.3.0/go.mod h1:+TV5q0KpwSGRWnN+ITijsIhrWYJkoUCp9MYELjKpAXk=
|
||||||
tinygo.org/x/tinyfs v0.1.0/go.mod h1:ysc8Y92iHfhTXeyEM9+c7zviUQ4fN9UCFgSOFfMWv20=
|
|
||||||
tinygo.org/x/tinyterm v0.1.0 h1:80i+j+KWoxCFa/Xfp6pWbh79x+8zUdMXC1vaKj2QhkY=
|
tinygo.org/x/tinyterm v0.1.0 h1:80i+j+KWoxCFa/Xfp6pWbh79x+8zUdMXC1vaKj2QhkY=
|
||||||
tinygo.org/x/tinyterm v0.1.0/go.mod h1:/DDhNnGwNF2/tNgHywvyZuCGnbH3ov49Z/6e8LPLRR4=
|
tinygo.org/x/tinyterm v0.1.0/go.mod h1:/DDhNnGwNF2/tNgHywvyZuCGnbH3ov49Z/6e8LPLRR4=
|
||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"tinygo.org/x/drivers"
|
"tinygo.org/x/drivers"
|
||||||
|
"tinygo.org/x/drivers/pixel"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
@@ -31,6 +32,9 @@ type Device struct {
|
|||||||
rd machine.Pin
|
rd machine.Pin
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Image buffer type used in the ili9341.
|
||||||
|
type Image = pixel.Image[pixel.RGB565BE]
|
||||||
|
|
||||||
var cmdBuf [6]byte
|
var cmdBuf [6]byte
|
||||||
|
|
||||||
var initCmd = []byte{
|
var initCmd = []byte{
|
||||||
@@ -173,6 +177,8 @@ func (d *Device) EnableTEOutput(on bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DrawRGBBitmap copies an RGB bitmap to the internal buffer at given coordinates
|
// DrawRGBBitmap copies an RGB bitmap to the internal buffer at given coordinates
|
||||||
|
//
|
||||||
|
// Deprecated: use DrawBitmap instead.
|
||||||
func (d *Device) DrawRGBBitmap(x, y int16, data []uint16, w, h int16) error {
|
func (d *Device) DrawRGBBitmap(x, y int16, data []uint16, w, h int16) error {
|
||||||
k, i := d.Size()
|
k, i := d.Size()
|
||||||
if x < 0 || y < 0 || w <= 0 || h <= 0 ||
|
if x < 0 || y < 0 || w <= 0 || h <= 0 ||
|
||||||
@@ -187,6 +193,8 @@ func (d *Device) DrawRGBBitmap(x, y int16, data []uint16, w, h int16) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DrawRGBBitmap8 copies an RGB bitmap to the internal buffer at given coordinates
|
// DrawRGBBitmap8 copies an RGB bitmap to the internal buffer at given coordinates
|
||||||
|
//
|
||||||
|
// Deprecated: use DrawBitmap instead.
|
||||||
func (d *Device) DrawRGBBitmap8(x, y int16, data []uint8, w, h int16) error {
|
func (d *Device) DrawRGBBitmap8(x, y int16, data []uint8, w, h int16) error {
|
||||||
k, i := d.Size()
|
k, i := d.Size()
|
||||||
if x < 0 || y < 0 || w <= 0 || h <= 0 ||
|
if x < 0 || y < 0 || w <= 0 || h <= 0 ||
|
||||||
@@ -200,6 +208,13 @@ func (d *Device) DrawRGBBitmap8(x, y int16, data []uint8, w, h int16) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DrawBitmap copies the bitmap to the internal buffer on the screen at the
|
||||||
|
// given coordinates. It returns once the image data has been sent completely.
|
||||||
|
func (d *Device) DrawBitmap(x, y int16, bitmap Image) error {
|
||||||
|
width, height := bitmap.Size()
|
||||||
|
return d.DrawRGBBitmap8(x, y, bitmap.RawBuffer(), int16(width), int16(height))
|
||||||
|
}
|
||||||
|
|
||||||
// FillRectangle fills a rectangle at given coordinates with a color
|
// FillRectangle fills a rectangle at given coordinates with a color
|
||||||
func (d *Device) FillRectangle(x, y, width, height int16, c color.RGBA) error {
|
func (d *Device) FillRectangle(x, y, width, height int16, c color.RGBA) error {
|
||||||
k, i := d.Size()
|
k, i := d.Size()
|
||||||
|
|||||||
@@ -80,6 +80,9 @@ const (
|
|||||||
const (
|
const (
|
||||||
MHz_868_1 = 868100000
|
MHz_868_1 = 868100000
|
||||||
MHz_868_5 = 868500000
|
MHz_868_5 = 868500000
|
||||||
|
MHz_902_3 = 902300000
|
||||||
|
Mhz_903_0 = 903000000
|
||||||
|
MHZ_915_0 = 915000000
|
||||||
MHz_916_8 = 916800000
|
MHz_916_8 = 916800000
|
||||||
MHz_923_3 = 923300000
|
MHz_923_3 = 923300000
|
||||||
)
|
)
|
||||||
|
|||||||
+31
-25
@@ -30,11 +30,11 @@ const (
|
|||||||
var (
|
var (
|
||||||
ActiveRadio lora.Radio
|
ActiveRadio lora.Radio
|
||||||
Retries = 15
|
Retries = 15
|
||||||
regionSettings region.RegionSettings
|
regionSettings region.Settings
|
||||||
)
|
)
|
||||||
|
|
||||||
// UseRegionSettings sets current Lorawan Regional parameters
|
// UseRegionSettings sets current Lorawan Regional parameters
|
||||||
func UseRegionSettings(rs region.RegionSettings) {
|
func UseRegionSettings(rs region.Settings) {
|
||||||
regionSettings = rs
|
regionSettings = rs
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,13 +52,13 @@ func SetPublicNetwork(enabled bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ApplyChannelConfig sets current Lora modulation according to current regional settings
|
// ApplyChannelConfig sets current Lora modulation according to current regional settings
|
||||||
func applyChannelConfig(ch *region.Channel) {
|
func applyChannelConfig(ch region.Channel) {
|
||||||
ActiveRadio.SetFrequency(ch.Frequency)
|
ActiveRadio.SetFrequency(ch.Frequency())
|
||||||
ActiveRadio.SetBandwidth(ch.Bandwidth)
|
ActiveRadio.SetBandwidth(ch.Bandwidth())
|
||||||
ActiveRadio.SetCodingRate(ch.CodingRate)
|
ActiveRadio.SetCodingRate(ch.CodingRate())
|
||||||
ActiveRadio.SetSpreadingFactor(ch.SpreadingFactor)
|
ActiveRadio.SetSpreadingFactor(ch.SpreadingFactor())
|
||||||
ActiveRadio.SetPreambleLength(ch.PreambleLength)
|
ActiveRadio.SetPreambleLength(ch.PreambleLength())
|
||||||
ActiveRadio.SetTxPower(ch.TxPowerDBm)
|
ActiveRadio.SetTxPower(ch.TxPowerDBm())
|
||||||
// Lorawan defaults to explicit headers
|
// Lorawan defaults to explicit headers
|
||||||
ActiveRadio.SetHeaderType(lora.HeaderExplicit)
|
ActiveRadio.SetHeaderType(lora.HeaderExplicit)
|
||||||
ActiveRadio.SetCrc(true)
|
ActiveRadio.SetCrc(true)
|
||||||
@@ -84,24 +84,30 @@ func Join(otaa *Otaa, session *Session) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare radio for Join Tx
|
for {
|
||||||
applyChannelConfig(regionSettings.JoinRequestChannel())
|
joinRequestChannel := regionSettings.JoinRequestChannel()
|
||||||
ActiveRadio.SetIqMode(lora.IQStandard)
|
joinAcceptChannel := regionSettings.JoinAcceptChannel()
|
||||||
ActiveRadio.Tx(payload, LORA_TX_TIMEOUT)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for JoinAccept
|
// Prepare radio for Join Tx
|
||||||
applyChannelConfig(regionSettings.JoinAcceptChannel())
|
applyChannelConfig(joinRequestChannel)
|
||||||
ActiveRadio.SetIqMode(lora.IQInverted)
|
ActiveRadio.SetIqMode(lora.IQStandard)
|
||||||
resp, err = ActiveRadio.Rx(LORA_RX_TIMEOUT)
|
ActiveRadio.Tx(payload, LORA_TX_TIMEOUT)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp == nil {
|
// Wait for JoinAccept
|
||||||
return ErrNoJoinAcceptReceived
|
if joinAcceptChannel.Frequency() != 0 {
|
||||||
|
applyChannelConfig(joinAcceptChannel)
|
||||||
|
}
|
||||||
|
ActiveRadio.SetIqMode(lora.IQInverted)
|
||||||
|
resp, err = ActiveRadio.Rx(LORA_RX_TIMEOUT)
|
||||||
|
if err == nil && resp != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if !joinAcceptChannel.Next() {
|
||||||
|
return ErrNoJoinAcceptReceived
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = otaa.DecodeJoinAccept(resp, session)
|
err = otaa.DecodeJoinAccept(resp, session)
|
||||||
|
|||||||
@@ -7,43 +7,41 @@ const (
|
|||||||
AU915_DEFAULT_TX_POWER_DBM = 20
|
AU915_DEFAULT_TX_POWER_DBM = 20
|
||||||
)
|
)
|
||||||
|
|
||||||
type RegionSettingsAU915 struct {
|
type ChannelAU struct {
|
||||||
joinRequestChannel *Channel
|
channel
|
||||||
joinAcceptChannel *Channel
|
|
||||||
uplinkChannel *Channel
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func AU915() *RegionSettingsAU915 {
|
func (c *ChannelAU) Next() bool {
|
||||||
return &RegionSettingsAU915{
|
return false
|
||||||
joinRequestChannel: &Channel{lora.MHz_916_8,
|
}
|
||||||
|
|
||||||
|
type SettingsAU915 struct {
|
||||||
|
settings
|
||||||
|
}
|
||||||
|
|
||||||
|
func AU915() *SettingsAU915 {
|
||||||
|
return &SettingsAU915{settings: settings{
|
||||||
|
joinRequestChannel: &ChannelAU{channel: channel{lora.MHz_916_8,
|
||||||
lora.Bandwidth_125_0,
|
lora.Bandwidth_125_0,
|
||||||
lora.SpreadingFactor9,
|
lora.SpreadingFactor9,
|
||||||
lora.CodingRate4_5,
|
lora.CodingRate4_5,
|
||||||
AU915_DEFAULT_PREAMBLE_LEN,
|
AU915_DEFAULT_PREAMBLE_LEN,
|
||||||
AU915_DEFAULT_TX_POWER_DBM},
|
AU915_DEFAULT_TX_POWER_DBM}},
|
||||||
joinAcceptChannel: &Channel{lora.MHz_923_3,
|
joinAcceptChannel: &ChannelAU{channel: channel{lora.MHz_923_3,
|
||||||
lora.Bandwidth_500_0,
|
lora.Bandwidth_500_0,
|
||||||
lora.SpreadingFactor9,
|
lora.SpreadingFactor9,
|
||||||
lora.CodingRate4_5,
|
lora.CodingRate4_5,
|
||||||
AU915_DEFAULT_PREAMBLE_LEN,
|
AU915_DEFAULT_PREAMBLE_LEN,
|
||||||
AU915_DEFAULT_TX_POWER_DBM},
|
AU915_DEFAULT_TX_POWER_DBM}},
|
||||||
uplinkChannel: &Channel{lora.MHz_916_8,
|
uplinkChannel: &ChannelAU{channel: channel{lora.MHz_916_8,
|
||||||
lora.Bandwidth_125_0,
|
lora.Bandwidth_125_0,
|
||||||
lora.SpreadingFactor9,
|
lora.SpreadingFactor9,
|
||||||
lora.CodingRate4_5,
|
lora.CodingRate4_5,
|
||||||
AU915_DEFAULT_PREAMBLE_LEN,
|
AU915_DEFAULT_PREAMBLE_LEN,
|
||||||
AU915_DEFAULT_TX_POWER_DBM},
|
AU915_DEFAULT_TX_POWER_DBM}},
|
||||||
}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RegionSettingsAU915) JoinRequestChannel() *Channel {
|
func Next(c *ChannelAU) bool {
|
||||||
return r.joinRequestChannel
|
return false
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RegionSettingsAU915) JoinAcceptChannel() *Channel {
|
|
||||||
return r.joinAcceptChannel
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RegionSettingsAU915) UplinkChannel() *Channel {
|
|
||||||
return r.uplinkChannel
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package region
|
||||||
|
|
||||||
|
type Channel interface {
|
||||||
|
Next() bool
|
||||||
|
Frequency() uint32
|
||||||
|
Bandwidth() uint8
|
||||||
|
SpreadingFactor() uint8
|
||||||
|
CodingRate() uint8
|
||||||
|
PreambleLength() uint16
|
||||||
|
TxPowerDBm() int8
|
||||||
|
SetFrequency(v uint32)
|
||||||
|
SetBandwidth(v uint8)
|
||||||
|
SetSpreadingFactor(v uint8)
|
||||||
|
SetCodingRate(v uint8)
|
||||||
|
SetPreambleLength(v uint16)
|
||||||
|
SetTxPowerDBm(v int8)
|
||||||
|
}
|
||||||
|
|
||||||
|
type channel struct {
|
||||||
|
frequency uint32
|
||||||
|
bandwidth uint8
|
||||||
|
spreadingFactor uint8
|
||||||
|
codingRate uint8
|
||||||
|
preambleLength uint16
|
||||||
|
txPowerDBm int8
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getter functions
|
||||||
|
func (c *channel) Frequency() uint32 { return c.frequency }
|
||||||
|
func (c *channel) Bandwidth() uint8 { return c.bandwidth }
|
||||||
|
func (c *channel) SpreadingFactor() uint8 { return c.spreadingFactor }
|
||||||
|
func (c *channel) CodingRate() uint8 { return c.codingRate }
|
||||||
|
func (c *channel) PreambleLength() uint16 { return c.preambleLength }
|
||||||
|
func (c *channel) TxPowerDBm() int8 { return c.txPowerDBm }
|
||||||
|
|
||||||
|
// Set functions
|
||||||
|
func (c *channel) SetFrequency(v uint32) { c.frequency = v }
|
||||||
|
func (c *channel) SetBandwidth(v uint8) { c.bandwidth = v }
|
||||||
|
func (c *channel) SetSpreadingFactor(v uint8) { c.spreadingFactor = v }
|
||||||
|
func (c *channel) SetCodingRate(v uint8) { c.codingRate = v }
|
||||||
|
func (c *channel) SetPreambleLength(v uint16) { c.preambleLength = v }
|
||||||
|
func (c *channel) SetTxPowerDBm(v int8) { c.txPowerDBm = v }
|
||||||
@@ -7,43 +7,37 @@ const (
|
|||||||
EU868_DEFAULT_TX_POWER_DBM = 20
|
EU868_DEFAULT_TX_POWER_DBM = 20
|
||||||
)
|
)
|
||||||
|
|
||||||
type RegionSettingsEU868 struct {
|
type ChannelEU struct {
|
||||||
joinRequestChannel *Channel
|
channel
|
||||||
joinAcceptChannel *Channel
|
|
||||||
uplinkChannel *Channel
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func EU868() *RegionSettingsEU868 {
|
func (c *ChannelEU) Next() bool {
|
||||||
return &RegionSettingsEU868{
|
return false
|
||||||
joinRequestChannel: &Channel{lora.MHz_868_1,
|
}
|
||||||
|
|
||||||
|
type SettingsEU868 struct {
|
||||||
|
settings
|
||||||
|
}
|
||||||
|
|
||||||
|
func EU868() *SettingsEU868 {
|
||||||
|
return &SettingsEU868{settings: settings{
|
||||||
|
joinRequestChannel: &ChannelEU{channel: channel{lora.MHz_868_1,
|
||||||
lora.Bandwidth_125_0,
|
lora.Bandwidth_125_0,
|
||||||
lora.SpreadingFactor9,
|
lora.SpreadingFactor9,
|
||||||
lora.CodingRate4_7,
|
lora.CodingRate4_7,
|
||||||
EU868_DEFAULT_PREAMBLE_LEN,
|
EU868_DEFAULT_PREAMBLE_LEN,
|
||||||
EU868_DEFAULT_TX_POWER_DBM},
|
EU868_DEFAULT_TX_POWER_DBM}},
|
||||||
joinAcceptChannel: &Channel{lora.MHz_868_1,
|
joinAcceptChannel: &ChannelEU{channel: channel{lora.MHz_868_1,
|
||||||
lora.Bandwidth_125_0,
|
lora.Bandwidth_125_0,
|
||||||
lora.SpreadingFactor9,
|
lora.SpreadingFactor9,
|
||||||
lora.CodingRate4_7,
|
lora.CodingRate4_7,
|
||||||
EU868_DEFAULT_PREAMBLE_LEN,
|
EU868_DEFAULT_PREAMBLE_LEN,
|
||||||
EU868_DEFAULT_TX_POWER_DBM},
|
EU868_DEFAULT_TX_POWER_DBM}},
|
||||||
uplinkChannel: &Channel{lora.MHz_868_1,
|
uplinkChannel: &ChannelEU{channel: channel{lora.MHz_868_1,
|
||||||
lora.Bandwidth_125_0,
|
lora.Bandwidth_125_0,
|
||||||
lora.SpreadingFactor9,
|
lora.SpreadingFactor9,
|
||||||
lora.CodingRate4_7,
|
lora.CodingRate4_7,
|
||||||
EU868_DEFAULT_PREAMBLE_LEN,
|
EU868_DEFAULT_PREAMBLE_LEN,
|
||||||
EU868_DEFAULT_TX_POWER_DBM},
|
EU868_DEFAULT_TX_POWER_DBM}},
|
||||||
}
|
}}
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RegionSettingsEU868) JoinRequestChannel() *Channel {
|
|
||||||
return r.joinRequestChannel
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RegionSettingsEU868) JoinAcceptChannel() *Channel {
|
|
||||||
return r.joinAcceptChannel
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RegionSettingsEU868) UplinkChannel() *Channel {
|
|
||||||
return r.uplinkChannel
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
package region
|
|
||||||
|
|
||||||
type Channel struct {
|
|
||||||
Frequency uint32
|
|
||||||
Bandwidth uint8
|
|
||||||
SpreadingFactor uint8
|
|
||||||
CodingRate uint8
|
|
||||||
PreambleLength uint16
|
|
||||||
TxPowerDBm int8
|
|
||||||
}
|
|
||||||
|
|
||||||
type RegionSettings interface {
|
|
||||||
JoinRequestChannel() *Channel
|
|
||||||
JoinAcceptChannel() *Channel
|
|
||||||
UplinkChannel() *Channel
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package region
|
||||||
|
|
||||||
|
type Settings interface {
|
||||||
|
JoinRequestChannel() Channel
|
||||||
|
JoinAcceptChannel() Channel
|
||||||
|
UplinkChannel() Channel
|
||||||
|
}
|
||||||
|
|
||||||
|
type settings struct {
|
||||||
|
joinRequestChannel Channel
|
||||||
|
joinAcceptChannel Channel
|
||||||
|
uplinkChannel Channel
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *settings) JoinRequestChannel() Channel {
|
||||||
|
return r.joinRequestChannel
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *settings) JoinAcceptChannel() Channel {
|
||||||
|
return r.joinAcceptChannel
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *settings) UplinkChannel() Channel {
|
||||||
|
return r.uplinkChannel
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
package region
|
||||||
|
|
||||||
|
import "tinygo.org/x/drivers/lora"
|
||||||
|
|
||||||
|
const (
|
||||||
|
US915_DEFAULT_PREAMBLE_LEN = 8
|
||||||
|
US915_DEFAULT_TX_POWER_DBM = 20
|
||||||
|
US915_FREQUENCY_INCREMENT_DR_0 = 200000 // only for 125 kHz Bandwidth
|
||||||
|
US915_FREQUENCY_INCREMENT_DR_4 = 1600000 // only for 500 kHz Bandwidth
|
||||||
|
)
|
||||||
|
|
||||||
|
type ChannelUS struct {
|
||||||
|
channel
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ChannelUS) Next() bool {
|
||||||
|
switch c.Bandwidth() {
|
||||||
|
case lora.Bandwidth_125_0:
|
||||||
|
freq, ok := stepFrequency125(c.frequency)
|
||||||
|
if ok {
|
||||||
|
c.frequency = freq
|
||||||
|
} else {
|
||||||
|
c.frequency = lora.Mhz_903_0
|
||||||
|
c.bandwidth = lora.Bandwidth_500_0
|
||||||
|
}
|
||||||
|
case lora.Bandwidth_500_0:
|
||||||
|
freq, ok := stepFrequency500(c.frequency)
|
||||||
|
if ok {
|
||||||
|
c.frequency = freq
|
||||||
|
} else {
|
||||||
|
// there are no more frequencies to check after sweeping all 8 500 kHz channels
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func stepFrequency125(freq uint32) (uint32, bool) {
|
||||||
|
f := freq + US915_FREQUENCY_INCREMENT_DR_0
|
||||||
|
if f >= lora.MHZ_915_0 {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return f, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func stepFrequency500(freq uint32) (uint32, bool) {
|
||||||
|
f := freq + US915_FREQUENCY_INCREMENT_DR_4
|
||||||
|
if f >= lora.MHZ_915_0 {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return f, true
|
||||||
|
}
|
||||||
|
|
||||||
|
type SettingsUS915 struct {
|
||||||
|
settings
|
||||||
|
}
|
||||||
|
|
||||||
|
func US915() *SettingsUS915 {
|
||||||
|
return &SettingsUS915{settings: settings{
|
||||||
|
joinRequestChannel: &ChannelUS{channel: channel{lora.MHz_902_3,
|
||||||
|
lora.Bandwidth_125_0,
|
||||||
|
lora.SpreadingFactor10,
|
||||||
|
lora.CodingRate4_5,
|
||||||
|
US915_DEFAULT_PREAMBLE_LEN,
|
||||||
|
US915_DEFAULT_TX_POWER_DBM}},
|
||||||
|
joinAcceptChannel: &ChannelUS{channel: channel{0,
|
||||||
|
lora.Bandwidth_500_0,
|
||||||
|
lora.SpreadingFactor9,
|
||||||
|
lora.CodingRate4_5,
|
||||||
|
US915_DEFAULT_PREAMBLE_LEN,
|
||||||
|
US915_DEFAULT_TX_POWER_DBM}},
|
||||||
|
uplinkChannel: &ChannelUS{channel: channel{lora.Mhz_903_0,
|
||||||
|
lora.Bandwidth_500_0,
|
||||||
|
lora.SpreadingFactor9,
|
||||||
|
lora.CodingRate4_5,
|
||||||
|
US915_DEFAULT_PREAMBLE_LEN,
|
||||||
|
US915_DEFAULT_TX_POWER_DBM}},
|
||||||
|
}}
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
// Package pcf8523 implements a driver for the PCF8523 CMOS Real-Time Clock (RTC)
|
||||||
|
//
|
||||||
|
// Datasheet: https://www.nxp.com/docs/en/data-sheet/PCF8523.pdf
|
||||||
|
package pcf8523
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
"tinygo.org/x/drivers"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Device struct {
|
||||||
|
bus drivers.I2C
|
||||||
|
Address uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(i2c drivers.I2C) Device {
|
||||||
|
return Device{
|
||||||
|
bus: i2c,
|
||||||
|
Address: DefaultAddress,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset resets the device according to the datasheet section 8.3
|
||||||
|
// This does not wipe the time registers, but resets control registers.
|
||||||
|
func (d *Device) Reset() (err error) {
|
||||||
|
return d.bus.Tx(uint16(d.Address), []byte{rControl1, 0x58}, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPowerManagement configures how the device makes use of the backup battery, see
|
||||||
|
// datasheet section 8.5
|
||||||
|
func (d *Device) SetPowerManagement(b PowerManagement) error {
|
||||||
|
return d.setRegister(rControl3, byte(b)<<5, 0xE0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Device) setRegister(reg uint8, value, mask uint8) error {
|
||||||
|
var buf [1]byte
|
||||||
|
err := d.bus.Tx(uint16(d.Address), []byte{reg}, buf[:])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
buf[0] = (value & mask) | (buf[0] & (^mask))
|
||||||
|
return d.bus.Tx(uint16(d.Address), []byte{reg, buf[0]}, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTime sets the time and date
|
||||||
|
func (d *Device) SetTime(t time.Time) error {
|
||||||
|
buf := []byte{
|
||||||
|
rSeconds,
|
||||||
|
bin2bcd(t.Second()),
|
||||||
|
bin2bcd(t.Minute()),
|
||||||
|
bin2bcd(t.Hour()),
|
||||||
|
bin2bcd(t.Day()),
|
||||||
|
bin2bcd(int(t.Weekday())),
|
||||||
|
bin2bcd(int(t.Month())),
|
||||||
|
bin2bcd(t.Year() - 2000),
|
||||||
|
}
|
||||||
|
|
||||||
|
return d.bus.Tx(uint16(d.Address), buf, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadTime returns the date and time
|
||||||
|
func (d *Device) ReadTime() (time.Time, error) {
|
||||||
|
buf := make([]byte, 9)
|
||||||
|
err := d.bus.Tx(uint16(d.Address), []byte{rSeconds}, buf)
|
||||||
|
if err != nil {
|
||||||
|
return time.Time{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
seconds := bcd2bin(buf[0] & 0x7F)
|
||||||
|
minute := bcd2bin(buf[1] & 0x7F)
|
||||||
|
hour := bcd2bin(buf[2] & 0x3F)
|
||||||
|
day := bcd2bin(buf[3] & 0x3F)
|
||||||
|
//skipping weekday buf[4]
|
||||||
|
month := time.Month(bcd2bin(buf[5] & 0x1F))
|
||||||
|
year := int(bcd2bin(buf[6])) + 2000
|
||||||
|
|
||||||
|
t := time.Date(year, month, day, hour, minute, seconds, 0, time.UTC)
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// bin2bcd converts binary to BCD
|
||||||
|
func bin2bcd(dec int) uint8 {
|
||||||
|
return uint8(dec + 6*(dec/10))
|
||||||
|
}
|
||||||
|
|
||||||
|
// bcd2bin converts BCD to binary
|
||||||
|
func bcd2bin(bcd uint8) int {
|
||||||
|
return int(bcd - 6*(bcd>>4))
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
package pcf8523
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
"tinygo.org/x/drivers/tester"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDecToBcd_RoundTrip(t *testing.T) {
|
||||||
|
|
||||||
|
for i := 0; i < 60; i++ {
|
||||||
|
a := bcd2bin(bin2bcd(i))
|
||||||
|
if a != i {
|
||||||
|
t.Logf("not equal: %d != %d", a, i)
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDevice_Reset(t *testing.T) {
|
||||||
|
bus := tester.NewI2CBus(t)
|
||||||
|
fake := bus.NewDevice(DefaultAddress)
|
||||||
|
|
||||||
|
dev := New(bus)
|
||||||
|
|
||||||
|
err := dev.Reset()
|
||||||
|
assertNoError(t, err)
|
||||||
|
|
||||||
|
assertEquals(t, fake.Registers[rControl1], 0x58)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDevice_SetPowerManagement(t *testing.T) {
|
||||||
|
bus := tester.NewI2CBus(t)
|
||||||
|
fake := bus.NewDevice(DefaultAddress)
|
||||||
|
|
||||||
|
dev := New(bus)
|
||||||
|
|
||||||
|
err := dev.SetPowerManagement(PowerManagement_SwitchOver_ModeStandard)
|
||||||
|
assertNoError(t, err)
|
||||||
|
|
||||||
|
assertEquals(t, fake.Registers[rControl3], 0b100<<5)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDevice_SetTime(t *testing.T) {
|
||||||
|
bus := tester.NewI2CBus(t)
|
||||||
|
fake := bus.NewDevice(DefaultAddress)
|
||||||
|
|
||||||
|
dev := New(bus)
|
||||||
|
|
||||||
|
pointInTime, _ := time.Parse(time.RFC3339, "2023-09-12T22:35:50Z")
|
||||||
|
err := dev.SetTime(pointInTime)
|
||||||
|
assertNoError(t, err)
|
||||||
|
|
||||||
|
actual := hex.EncodeToString(fake.Registers[rSeconds : rSeconds+7])
|
||||||
|
expected := "50352212020923"
|
||||||
|
assertEquals(t, actual, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDevice_ReadTime(t *testing.T) {
|
||||||
|
bus := tester.NewI2CBus(t)
|
||||||
|
fake := bus.NewDevice(DefaultAddress)
|
||||||
|
|
||||||
|
expectedPointInTime := time.Date(2023, 9, 12, 17, 55, 42, 0, time.UTC)
|
||||||
|
fake.Registers[rSeconds] = 0x42
|
||||||
|
fake.Registers[rMinutes] = 0x55
|
||||||
|
fake.Registers[rHours] = 0x17
|
||||||
|
fake.Registers[rDays] = 0x12
|
||||||
|
fake.Registers[rMonths] = 0x9
|
||||||
|
fake.Registers[rYears] = 0x23
|
||||||
|
|
||||||
|
dev := New(bus)
|
||||||
|
|
||||||
|
//when
|
||||||
|
actualPointInTime, err := dev.ReadTime()
|
||||||
|
|
||||||
|
//then
|
||||||
|
assertNoError(t, err)
|
||||||
|
assertEquals(t, actualPointInTime, expectedPointInTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertNoError(t testing.TB, e error) {
|
||||||
|
if e != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func assertEquals[T comparable](t testing.TB, a, b T) {
|
||||||
|
if a != b {
|
||||||
|
t.Fatalf("%v != %v", a, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package pcf8523
|
||||||
|
|
||||||
|
const DefaultAddress = 0x68
|
||||||
|
|
||||||
|
// datasheet 8.5 Power management functions, table 11
|
||||||
|
type PowerManagement byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
PowerManagement_SwitchOver_ModeStandard_LowDetection PowerManagement = 0b000
|
||||||
|
PowerManagement_SwitchOver_ModeDirect_LowDetection PowerManagement = 0b001
|
||||||
|
PowerManagement_VddOnly_LowDetection PowerManagement = 0b010
|
||||||
|
PowerManagement_SwitchOver_ModeStandard PowerManagement = 0b100
|
||||||
|
PowerManagement_SwitchOver_ModeDirect PowerManagement = 0b101
|
||||||
|
PowerManagement_VddOnly PowerManagement = 0b101
|
||||||
|
)
|
||||||
|
|
||||||
|
// constants for all internal registers
|
||||||
|
const (
|
||||||
|
rControl1 = 0x00 // Control_1
|
||||||
|
rControl2 = 0x01 // Control_2
|
||||||
|
rControl3 = 0x02 // Control_3
|
||||||
|
rSeconds = 0x03 // Seconds
|
||||||
|
rMinutes = 0x04 // Minutes
|
||||||
|
rHours = 0x05 // Hours
|
||||||
|
rDays = 0x06 // Days
|
||||||
|
rWeekdays = 0x07 // Weekdays
|
||||||
|
rMonths = 0x08 // Months
|
||||||
|
rYears = 0x09 // Years
|
||||||
|
rMinuteAlarm = 0x0A // Minute_alarm
|
||||||
|
rHourAlarm = 0x0B // Hour_alarm
|
||||||
|
rDayAlarm = 0x0C // Day_alarm
|
||||||
|
rWeekdayAlarm = 0x0D // Weekday_alarm
|
||||||
|
rOffset = 0x0E // Offset
|
||||||
|
rTimerClkoutControl = 0x0F // Tmr_CLKOUT_ctrl
|
||||||
|
rTimerAFrequencyControl = 0x10 // Tmr_A_freq_ctrl
|
||||||
|
rTimerARegister = 0x11 // Tmr_A_reg
|
||||||
|
rTimerBFrequencyControl = 0x12 // Tmr_B_freq_ctrl
|
||||||
|
rTimerBRegister = 0x13 // Tmr_B_reg
|
||||||
|
)
|
||||||
+223
@@ -0,0 +1,223 @@
|
|||||||
|
package pixel
|
||||||
|
|
||||||
|
import (
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Image buffer, used for working with the native image format of various
|
||||||
|
// displays. It works a lot like a slice: it can be rescaled while reusing the
|
||||||
|
// underlying buffer and should be passed around by value.
|
||||||
|
type Image[T Color] struct {
|
||||||
|
width int16
|
||||||
|
height int16
|
||||||
|
data unsafe.Pointer
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewImage creates a new image of the given size.
|
||||||
|
func NewImage[T Color](width, height int) 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("NewImage: width/height out of bounds")
|
||||||
|
}
|
||||||
|
var zeroColor T
|
||||||
|
var data unsafe.Pointer
|
||||||
|
if 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 {
|
||||||
|
// 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
|
||||||
|
buf := make([]byte, bufBytes)
|
||||||
|
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
|
||||||
|
// ways. It will panic if width*height is larger than img.Len().
|
||||||
|
func (img Image[T]) Rescale(width, height int) Image[T] {
|
||||||
|
if width*height > img.Len() {
|
||||||
|
panic("Image.Rescale size out of bounds")
|
||||||
|
}
|
||||||
|
return Image[T]{
|
||||||
|
width: int16(width),
|
||||||
|
height: int16(height),
|
||||||
|
data: img.data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LimitHeight returns a subimage with the bottom part cut off, as specified by
|
||||||
|
// height.
|
||||||
|
func (img Image[T]) LimitHeight(height int) Image[T] {
|
||||||
|
if height < 0 || height > int(img.height) {
|
||||||
|
panic("Image.LimitHeight: out of bounds")
|
||||||
|
}
|
||||||
|
return Image[T]{
|
||||||
|
width: img.width,
|
||||||
|
height: int16(height),
|
||||||
|
data: img.data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len returns the number of pixels in this image buffer.
|
||||||
|
func (img Image[T]) Len() int {
|
||||||
|
return int(img.width) * int(img.height)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RawBuffer returns a byte slice that can be written directly to the screen
|
||||||
|
// using DrawRGBBitmap8.
|
||||||
|
func (img Image[T]) RawBuffer() []uint8 {
|
||||||
|
var zeroColor T
|
||||||
|
var numBytes int
|
||||||
|
if zeroColor.BitsPerPixel()%8 == 0 {
|
||||||
|
// Each color starts at a whole byte offset.
|
||||||
|
numBytes = int(unsafe.Sizeof(zeroColor)) * int(img.width) * int(img.height)
|
||||||
|
} else {
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
return unsafe.Slice((*byte)(img.data), numBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size returns the image size.
|
||||||
|
func (img Image[T]) Size() (int, int) {
|
||||||
|
return int(img.width), int(img.height)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (img Image[T]) setPixel(index int, c T) {
|
||||||
|
var zeroColor T
|
||||||
|
|
||||||
|
if zeroColor.BitsPerPixel()%8 == 0 {
|
||||||
|
// Each color starts at a whole byte offset.
|
||||||
|
// This is the easy case.
|
||||||
|
offset := index * int(unsafe.Sizeof(zeroColor))
|
||||||
|
ptr := unsafe.Add(img.data, offset)
|
||||||
|
*((*T)(ptr)) = c
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if c, ok := any(c).(RGB444BE); ok {
|
||||||
|
// Special case for RGB444.
|
||||||
|
bitIndex := index * zeroColor.BitsPerPixel()
|
||||||
|
if bitIndex%8 == 0 {
|
||||||
|
byteOffset := bitIndex / 8
|
||||||
|
ptr := (*[2]byte)(unsafe.Add(img.data, byteOffset))
|
||||||
|
ptr[0] = uint8(c >> 4)
|
||||||
|
ptr[1] = ptr[1]&0x0f | uint8(c)<<4 // change top bits
|
||||||
|
} else {
|
||||||
|
byteOffset := bitIndex / 8
|
||||||
|
ptr := (*[2]byte)(unsafe.Add(img.data, byteOffset))
|
||||||
|
ptr[0] = ptr[0]&0xf0 | uint8(c>>8) // change bottom bits
|
||||||
|
ptr[1] = uint8(c)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: the code for RGB444 should be generalized to support any bit size.
|
||||||
|
panic("todo: setPixel for odd bits per pixel")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set sets the pixel at x, y to the given color.
|
||||||
|
// Use FillSolidColor to efficiently fill the entire image buffer.
|
||||||
|
func (img Image[T]) Set(x, y int, c T) {
|
||||||
|
if uint(x) >= uint(int(img.width)) || uint(y) >= uint(int(img.height)) {
|
||||||
|
panic("Image.Set: out of bounds")
|
||||||
|
}
|
||||||
|
index := y*int(img.width) + x
|
||||||
|
img.setPixel(index, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns the color at the given index.
|
||||||
|
func (img Image[T]) Get(x, y int) T {
|
||||||
|
if uint(x) >= uint(int(img.width)) || uint(y) >= uint(int(img.height)) {
|
||||||
|
panic("Image.Get: out of bounds")
|
||||||
|
}
|
||||||
|
var zeroColor T
|
||||||
|
index := y*int(img.width) + x // index into img.data
|
||||||
|
|
||||||
|
if zeroColor.BitsPerPixel()%8 == 0 {
|
||||||
|
// Colors like RGB565, RGB888, etc.
|
||||||
|
offset := index * int(unsafe.Sizeof(zeroColor))
|
||||||
|
ptr := unsafe.Add(img.data, offset)
|
||||||
|
return *((*T)(ptr))
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := any(zeroColor).(RGB444BE); ok {
|
||||||
|
// Special case for RGB444 that isn't stored in a neat byte multiple.
|
||||||
|
bitIndex := index * zeroColor.BitsPerPixel()
|
||||||
|
var c RGB444BE
|
||||||
|
if bitIndex%8 == 0 {
|
||||||
|
byteOffset := bitIndex / 8
|
||||||
|
ptr := (*[2]byte)(unsafe.Add(img.data, byteOffset))
|
||||||
|
c |= RGB444BE(ptr[0]) << 4
|
||||||
|
c |= RGB444BE(ptr[1] >> 4) // load top bits
|
||||||
|
} else {
|
||||||
|
byteOffset := bitIndex / 8
|
||||||
|
ptr := (*[2]byte)(unsafe.Add(img.data, byteOffset))
|
||||||
|
c |= RGB444BE(ptr[0]&0x0f) << 8 // load bottom bits
|
||||||
|
c |= RGB444BE(ptr[1])
|
||||||
|
}
|
||||||
|
return any(c).(T)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: generalize the above code.
|
||||||
|
panic("todo: Image.Get for odd bits per pixel")
|
||||||
|
}
|
||||||
|
|
||||||
|
// FillSolidColor fills the entire image with the given color.
|
||||||
|
// This may be faster than setting individual pixels.
|
||||||
|
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 {
|
||||||
|
ptr := img.data
|
||||||
|
for i := 0; i < img.Len(); 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.
|
||||||
|
*(*T)(ptr) = color
|
||||||
|
ptr = unsafe.Add(ptr, unsafe.Sizeof(zeroColor))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special case for RGB444.
|
||||||
|
if c, ok := any(color).(RGB444BE); ok {
|
||||||
|
// RGB444 can be stored in a more optimized way, by storing two colors
|
||||||
|
// at a time instead of setting each color individually. This avoids
|
||||||
|
// loading and masking the old color bits for the half-bytes.
|
||||||
|
var buf [3]uint8
|
||||||
|
buf[0] = uint8(c >> 4)
|
||||||
|
buf[1] = uint8(c)<<4 | uint8(c>>8)
|
||||||
|
buf[2] = uint8(c)
|
||||||
|
rawBuf := unsafe.Slice((*[3]byte)(img.data), img.Len()/2)
|
||||||
|
for i := 0; i < len(rawBuf); i++ {
|
||||||
|
rawBuf[i] = buf
|
||||||
|
}
|
||||||
|
if img.Len()%2 != 0 {
|
||||||
|
// The image contains an uneven number of pixels.
|
||||||
|
// This is uncommon, but it can happen and we have to handle it.
|
||||||
|
img.setPixel(img.Len()-1, color)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback for other color formats.
|
||||||
|
for i := 0; i < img.Len(); i++ {
|
||||||
|
img.setPixel(i, color)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package pixel_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image/color"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"tinygo.org/x/drivers/pixel"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestImageRGB565BE(t *testing.T) {
|
||||||
|
image := pixel.NewImage[pixel.RGB565BE](5, 3)
|
||||||
|
if width, height := image.Size(); width != 5 && height != 3 {
|
||||||
|
t.Errorf("image.Size(): expected 5, 3 but got %d, %d", width, height)
|
||||||
|
}
|
||||||
|
for _, c := range []color.RGBA{
|
||||||
|
{R: 0xff, A: 0xff},
|
||||||
|
{G: 0xff, A: 0xff},
|
||||||
|
{B: 0xff, A: 0xff},
|
||||||
|
{R: 0x10, A: 0xff},
|
||||||
|
{G: 0x10, A: 0xff},
|
||||||
|
{B: 0x10, A: 0xff},
|
||||||
|
} {
|
||||||
|
image.Set(4, 2, pixel.NewColor[pixel.RGB565BE](c.R, c.G, c.B))
|
||||||
|
c2 := image.Get(4, 2).RGBA()
|
||||||
|
if c2 != c {
|
||||||
|
t.Errorf("failed to roundtrip color: expected %v but got %v", c, c2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestImageRGB444BE(t *testing.T) {
|
||||||
|
image := pixel.NewImage[pixel.RGB444BE](5, 3)
|
||||||
|
if width, height := image.Size(); width != 5 && height != 3 {
|
||||||
|
t.Errorf("image.Size(): expected 5, 3 but got %d, %d", width, height)
|
||||||
|
}
|
||||||
|
for _, c := range []color.RGBA{
|
||||||
|
{R: 0xff, A: 0xff},
|
||||||
|
{G: 0xff, A: 0xff},
|
||||||
|
{B: 0xff, A: 0xff},
|
||||||
|
{R: 0x11, A: 0xff},
|
||||||
|
{G: 0x11, A: 0xff},
|
||||||
|
{B: 0x11, A: 0xff},
|
||||||
|
} {
|
||||||
|
encoded := pixel.NewColor[pixel.RGB444BE](c.R, c.G, c.B)
|
||||||
|
image.Set(0, 0, encoded)
|
||||||
|
image.Set(0, 1, encoded)
|
||||||
|
encoded2 := image.Get(0, 0)
|
||||||
|
encoded3 := image.Get(0, 1)
|
||||||
|
if encoded != encoded2 {
|
||||||
|
t.Errorf("failed to roundtrip color %v: expected %d but got %d", c, encoded, encoded2)
|
||||||
|
}
|
||||||
|
if encoded != encoded3 {
|
||||||
|
t.Errorf("failed to roundtrip color %v: expected %d but got %d", c, encoded, encoded3)
|
||||||
|
}
|
||||||
|
c2 := encoded2.RGBA()
|
||||||
|
if c2 != c {
|
||||||
|
t.Errorf("failed to roundtrip color: expected %v but got %v", c, c2)
|
||||||
|
}
|
||||||
|
c3 := encoded3.RGBA()
|
||||||
|
if c3 != c {
|
||||||
|
t.Errorf("failed to roundtrip color: expected %v but got %v", c, c3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+225
@@ -0,0 +1,225 @@
|
|||||||
|
// Package pixel contains pixel format definitions used in various displays and
|
||||||
|
// fast operations on them.
|
||||||
|
//
|
||||||
|
// This package is just a base for pixel operations, it is _not_ a graphics
|
||||||
|
// library. It doesn't define circles, lines, etc - just the bare minimum
|
||||||
|
// graphics operations needed plus the ones that need to be specialized per
|
||||||
|
// pixel format.
|
||||||
|
package pixel
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image/color"
|
||||||
|
"math/bits"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
type Color interface {
|
||||||
|
RGB888 | RGB565BE | RGB555 | RGB444BE
|
||||||
|
|
||||||
|
BaseColor
|
||||||
|
}
|
||||||
|
|
||||||
|
// BaseColor contains all the methods needed in a color format. This can be used
|
||||||
|
// in display drivers that want to define their own Color type with just the
|
||||||
|
// pixel formats the display supports.
|
||||||
|
type BaseColor interface {
|
||||||
|
// The number of bits when stored.
|
||||||
|
// This means for example that RGB555 (which is still stored as a 16-bit
|
||||||
|
// integer) returns 16, while RGB444 returns 12.
|
||||||
|
BitsPerPixel() int
|
||||||
|
|
||||||
|
// Return the given color in color.RGBA format, which is always sRGB. The
|
||||||
|
// alpha channel is always 255.
|
||||||
|
RGBA() color.RGBA
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewColor returns the given color based on the RGB values passed in the
|
||||||
|
// parameters. The input value is assumed to be in sRGB color space.
|
||||||
|
func NewColor[T Color](r, g, b uint8) T {
|
||||||
|
// Ugly cast from color.RGBA to T. The type switch and interface casts are
|
||||||
|
// trivially optimized away after instantiation.
|
||||||
|
var value T
|
||||||
|
switch any(value).(type) {
|
||||||
|
case RGB888:
|
||||||
|
return any(NewRGB888(r, g, b)).(T)
|
||||||
|
case RGB565BE:
|
||||||
|
return any(NewRGB565BE(r, g, b)).(T)
|
||||||
|
case RGB555:
|
||||||
|
return any(NewRGB555(r, g, b)).(T)
|
||||||
|
case RGB444BE:
|
||||||
|
return any(NewRGB444BE(r, g, b)).(T)
|
||||||
|
default:
|
||||||
|
panic("unknown color format")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLinearColor returns the given color based on the linear RGB values passed
|
||||||
|
// in the parameters. Use this if the RGB values are actually linear colors
|
||||||
|
// (like those that are used in most RGB LEDs) and not when it is in the usual
|
||||||
|
// sRGB color space (which is not linear).
|
||||||
|
//
|
||||||
|
// The input is assumed to be in the linear sRGB color space.
|
||||||
|
func NewLinearColor[T Color](r, g, b uint8) T {
|
||||||
|
r = gammaEncodeTable[r]
|
||||||
|
g = gammaEncodeTable[g]
|
||||||
|
b = gammaEncodeTable[b]
|
||||||
|
return NewColor[T](r, g, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RGB888 format, more commonly used in other places (desktop PC displays, CSS,
|
||||||
|
// etc). Less commonly used on embedded displays due to the higher memory usage.
|
||||||
|
type RGB888 struct {
|
||||||
|
R, G, B uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRGB888(r, g, b uint8) RGB888 {
|
||||||
|
return RGB888{r, g, b}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c RGB888) BitsPerPixel() int {
|
||||||
|
return 24
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c RGB888) RGBA() color.RGBA {
|
||||||
|
return color.RGBA{
|
||||||
|
R: c.R,
|
||||||
|
G: c.G,
|
||||||
|
B: c.B,
|
||||||
|
A: 255,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RGB565 as used in many SPI displays. Stored as a big endian value.
|
||||||
|
//
|
||||||
|
// The color format in integer form is gggbbbbb_rrrrrggg on little endian
|
||||||
|
// systems, which is the standard RGB565 format but with the top and bottom
|
||||||
|
// bytes swapped.
|
||||||
|
//
|
||||||
|
// There are a few alternatives to this weird big-endian format, but they're not
|
||||||
|
// great:
|
||||||
|
// - Storing the value in two 8-bit stores (to make the code endian-agnostic)
|
||||||
|
// incurs too much of a performance penalty.
|
||||||
|
// - Swapping the upper and lower bits just before storing. This is still less
|
||||||
|
// efficient than it could be, since colors are usually constructed once and
|
||||||
|
// then reused in many store operations. Doing the swap once instead of many
|
||||||
|
// times for each store is a performance win.
|
||||||
|
type RGB565BE uint16
|
||||||
|
|
||||||
|
func NewRGB565BE(r, g, b uint8) RGB565BE {
|
||||||
|
val := uint16(r&0xF8)<<8 +
|
||||||
|
uint16(g&0xFC)<<3 +
|
||||||
|
uint16(b&0xF8)>>3
|
||||||
|
// Swap endianness (make big endian).
|
||||||
|
// This is done using a single instruction on ARM (rev16).
|
||||||
|
// TODO: this should only be done on little endian systems, but TinyGo
|
||||||
|
// doesn't currently (2023) support big endian systems so it's difficult to
|
||||||
|
// test. Also, big endian systems don't seem fasionable these days.
|
||||||
|
val = bits.ReverseBytes16(val)
|
||||||
|
return RGB565BE(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c RGB565BE) BitsPerPixel() int {
|
||||||
|
return 16
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c RGB565BE) RGBA() color.RGBA {
|
||||||
|
// Note: on ARM, the compiler uses a rev instruction instead of a rev16
|
||||||
|
// instruction. I wonder whether this can be optimized further to use rev16
|
||||||
|
// instead?
|
||||||
|
c = c<<8 | c>>8
|
||||||
|
color := color.RGBA{
|
||||||
|
R: uint8(c>>11) << 3,
|
||||||
|
G: uint8(c>>5) << 2,
|
||||||
|
B: uint8(c) << 3,
|
||||||
|
A: 255,
|
||||||
|
}
|
||||||
|
// Correct color rounding, so that 0xff roundtrips back to 0xff.
|
||||||
|
color.R |= color.R >> 5
|
||||||
|
color.G |= color.G >> 6
|
||||||
|
color.B |= color.B >> 5
|
||||||
|
return color
|
||||||
|
}
|
||||||
|
|
||||||
|
// Color format used on the GameBoy Advance among others.
|
||||||
|
//
|
||||||
|
// Colors are stored as native endian values, with bits 0bbbbbgg_gggrrrrr (red
|
||||||
|
// is least significant, blue is most significant).
|
||||||
|
type RGB555 uint16
|
||||||
|
|
||||||
|
func NewRGB555(r, g, b uint8) RGB555 {
|
||||||
|
return RGB555(r)>>3 | (RGB555(g)>>3)<<5 | (RGB555(b)>>3)<<10
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c RGB555) BitsPerPixel() int {
|
||||||
|
// 15 bits per pixel, but there are 16 bits when stored
|
||||||
|
return 16
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c RGB555) RGBA() color.RGBA {
|
||||||
|
color := color.RGBA{
|
||||||
|
R: uint8(c>>10) << 3,
|
||||||
|
G: uint8(c>>5) << 3,
|
||||||
|
B: uint8(c) << 3,
|
||||||
|
A: 255,
|
||||||
|
}
|
||||||
|
// Correct color rounding, so that 0xff roundtrips back to 0xff.
|
||||||
|
color.R |= color.R >> 5
|
||||||
|
color.G |= color.G >> 5
|
||||||
|
color.B |= color.B >> 5
|
||||||
|
return color
|
||||||
|
}
|
||||||
|
|
||||||
|
// Color format that is supported by the ST7789 for example.
|
||||||
|
// It may be a bit faster to use than RGB565BE on very slow SPI buses.
|
||||||
|
//
|
||||||
|
// The color format is native endian as a uint16 (0000rrrr_ggggbbbb), not big
|
||||||
|
// endian which you might expect. I tried swapping the bytes, but it didn't have
|
||||||
|
// much of a performance impact and made the code harder to read. It is stored
|
||||||
|
// as a 12-bit big endian value in Image[RGB444BE] though.
|
||||||
|
type RGB444BE uint16
|
||||||
|
|
||||||
|
func NewRGB444BE(r, g, b uint8) RGB444BE {
|
||||||
|
return RGB444BE(r>>4)<<8 | RGB444BE(g>>4)<<4 | RGB444BE(b>>4)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c RGB444BE) BitsPerPixel() int {
|
||||||
|
return 12
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c RGB444BE) RGBA() color.RGBA {
|
||||||
|
color := color.RGBA{
|
||||||
|
R: uint8(c>>8) << 4,
|
||||||
|
G: uint8(c>>4) << 4,
|
||||||
|
B: uint8(c>>0) << 4,
|
||||||
|
A: 255,
|
||||||
|
}
|
||||||
|
// Correct color rounding, so that 0xff roundtrips back to 0xff.
|
||||||
|
color.R |= color.R >> 4
|
||||||
|
color.G |= color.G >> 4
|
||||||
|
color.B |= color.B >> 4
|
||||||
|
return color
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gamma brightness lookup table:
|
||||||
|
// https://victornpb.github.io/gamma-table-generator
|
||||||
|
// gamma = 0.45 steps = 256 range = 0-255
|
||||||
|
var gammaEncodeTable = [256]uint8{
|
||||||
|
0, 21, 28, 34, 39, 43, 46, 50, 53, 56, 59, 61, 64, 66, 68, 70,
|
||||||
|
72, 74, 76, 78, 80, 82, 84, 85, 87, 89, 90, 92, 93, 95, 96, 98,
|
||||||
|
99, 101, 102, 103, 105, 106, 107, 109, 110, 111, 112, 114, 115, 116, 117, 118,
|
||||||
|
119, 120, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135,
|
||||||
|
136, 137, 138, 139, 140, 141, 142, 143, 144, 144, 145, 146, 147, 148, 149, 150,
|
||||||
|
151, 151, 152, 153, 154, 155, 156, 156, 157, 158, 159, 160, 160, 161, 162, 163,
|
||||||
|
164, 164, 165, 166, 167, 167, 168, 169, 170, 170, 171, 172, 173, 173, 174, 175,
|
||||||
|
175, 176, 177, 178, 178, 179, 180, 180, 181, 182, 182, 183, 184, 184, 185, 186,
|
||||||
|
186, 187, 188, 188, 189, 190, 190, 191, 192, 192, 193, 194, 194, 195, 195, 196,
|
||||||
|
197, 197, 198, 199, 199, 200, 200, 201, 202, 202, 203, 203, 204, 205, 205, 206,
|
||||||
|
206, 207, 207, 208, 209, 209, 210, 210, 211, 212, 212, 213, 213, 214, 214, 215,
|
||||||
|
215, 216, 217, 217, 218, 218, 219, 219, 220, 220, 221, 221, 222, 223, 223, 224,
|
||||||
|
224, 225, 225, 226, 226, 227, 227, 228, 228, 229, 229, 230, 230, 231, 231, 232,
|
||||||
|
232, 233, 233, 234, 234, 235, 235, 236, 236, 237, 237, 238, 238, 239, 239, 240,
|
||||||
|
240, 241, 241, 242, 242, 243, 243, 244, 244, 245, 245, 246, 246, 247, 247, 248,
|
||||||
|
248, 249, 249, 249, 250, 250, 251, 251, 252, 252, 253, 253, 254, 254, 255, 255,
|
||||||
|
}
|
||||||
+5
-4
@@ -10,6 +10,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"tinygo.org/x/drivers"
|
"tinygo.org/x/drivers"
|
||||||
|
"tinygo.org/x/drivers/internal/legacy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Device wraps an SPI connection.
|
// Device wraps an SPI connection.
|
||||||
@@ -51,7 +52,7 @@ type Buser interface {
|
|||||||
|
|
||||||
type VccMode uint8
|
type VccMode uint8
|
||||||
|
|
||||||
// NewI2C creates a new SSD1306 connection. The I2C wire must already be configured.
|
// NewI2C creates a new SH1106 connection. The I2C wire must already be configured.
|
||||||
func NewI2C(bus drivers.I2C) Device {
|
func NewI2C(bus drivers.I2C) Device {
|
||||||
return Device{
|
return Device{
|
||||||
bus: &I2CBus{
|
bus: &I2CBus{
|
||||||
@@ -61,7 +62,7 @@ func NewI2C(bus drivers.I2C) Device {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSPI creates a new SSD1306 connection. The SPI wire must already be configured.
|
// NewSPI creates a new SH1106 connection. The SPI wire must already be configured.
|
||||||
func NewSPI(bus drivers.SPI, dcPin, resetPin, csPin machine.Pin) Device {
|
func NewSPI(bus drivers.SPI, dcPin, resetPin, csPin machine.Pin) Device {
|
||||||
dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||||
resetPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
resetPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||||
@@ -283,9 +284,9 @@ func (d *Device) Tx(data []byte, isCommand bool) {
|
|||||||
// tx sends data to the display (I2CBus implementation)
|
// tx sends data to the display (I2CBus implementation)
|
||||||
func (b *I2CBus) tx(data []byte, isCommand bool) {
|
func (b *I2CBus) tx(data []byte, isCommand bool) {
|
||||||
if isCommand {
|
if isCommand {
|
||||||
b.wire.WriteRegister(uint8(b.Address), 0x00, data)
|
legacy.WriteRegister(b.wire, uint8(b.Address), 0x00, data)
|
||||||
} else {
|
} else {
|
||||||
b.wire.WriteRegister(uint8(b.Address), 0x40, data)
|
legacy.WriteRegister(b.wire, uint8(b.Address), 0x40, data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
// Package sht4x provides a driver for the SHT4x digital humidity sensor series by Sensirion.
|
||||||
|
// Datasheet: https://www.sensirion.com/media/documents/33FD6951/64D3B030/Sensirion_Datasheet_SHT4x.pdf
|
||||||
|
package sht4x
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"tinygo.org/x/drivers"
|
||||||
|
)
|
||||||
|
|
||||||
|
const DefaultAddress = 0x44
|
||||||
|
|
||||||
|
const (
|
||||||
|
// single-shot, high-repeatability measurement
|
||||||
|
commandMeasurement = 0xfd
|
||||||
|
)
|
||||||
|
|
||||||
|
// Device represents a SHT4x sensor
|
||||||
|
type Device struct {
|
||||||
|
bus drivers.I2C
|
||||||
|
Address uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new SHT4x connection. The I2C bus must already be
|
||||||
|
// configured.
|
||||||
|
func New(bus drivers.I2C) Device {
|
||||||
|
return Device{
|
||||||
|
bus: bus,
|
||||||
|
Address: DefaultAddress,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadTemperatureHumidity starts a measurement and then reads out the results. This function blocks
|
||||||
|
// while the measurement is in progress.
|
||||||
|
//
|
||||||
|
// Temperature is returned in [degree Celsius], multiplied by 1000,
|
||||||
|
// and relative humidity in [percent relative humidity], multiplied by 1000.
|
||||||
|
func (d *Device) ReadTemperatureHumidity() (temperatureMilliCelsius int32, relativeHumidityMilliPercent int32, err error) {
|
||||||
|
rawTemp, rawHum, err := d.rawReadings()
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// from the reference driver: https://github.com/Sensirion/embedded-sht/blob/fcc8a523210cc1241a2750899ff6b0f68f3ed212/sht4x/sht4x.c#L81
|
||||||
|
temperatureMilliCelsius = ((21875 * int32(rawTemp)) >> 13) - 45000
|
||||||
|
relativeHumidityMilliPercent = ((15625 * int32(rawHum)) >> 13) - 6000
|
||||||
|
|
||||||
|
return temperatureMilliCelsius, relativeHumidityMilliPercent, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// rawReadings returns the sensor's raw values of the temperature and humidity
|
||||||
|
func (d *Device) rawReadings() (uint16, uint16, error) {
|
||||||
|
err := d.bus.Tx(uint16(d.Address), []byte{commandMeasurement}, nil)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// max time for measurement according to datasheet
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
|
||||||
|
var data [6]byte
|
||||||
|
err = d.bus.Tx(uint16(d.Address), nil, data[:])
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tTicks := readUint(data[0], data[1])
|
||||||
|
rhTicks := readUint(data[3], data[4])
|
||||||
|
|
||||||
|
return tTicks, rhTicks, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// readUint converts two bytes to uint16
|
||||||
|
func readUint(msb byte, lsb byte) uint16 {
|
||||||
|
return (uint16(msb) << 8) | uint16(lsb)
|
||||||
|
}
|
||||||
+1
-1
@@ -133,7 +133,7 @@ func runSmokeTest(filename string) error {
|
|||||||
result := <-job.resultChan
|
result := <-job.resultChan
|
||||||
os.Stdout.Write(job.output.Bytes())
|
os.Stdout.Write(job.output.Bytes())
|
||||||
if result != nil {
|
if result != nil {
|
||||||
return err
|
return result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
# get an md5sum).
|
# get an md5sum).
|
||||||
|
|
||||||
|
|
||||||
|
tinygo build -size short -o ./build/test.hex -target=feather-rp2040 ./examples/adafruit4650
|
||||||
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/adt7410/main.go
|
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/adt7410/main.go
|
||||||
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/adxl345/main.go
|
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/adxl345/main.go
|
||||||
tinygo build -size short -o ./build/test.hex -target=pybadge ./examples/amg88xx
|
tinygo build -size short -o ./build/test.hex -target=pybadge ./examples/amg88xx
|
||||||
@@ -63,6 +64,7 @@ tinygo build -size short -o ./build/test.hex -target=microbit ./examples/pcd8544
|
|||||||
tinygo build -size short -o ./build/test.hex -target=arduino ./examples/servo
|
tinygo build -size short -o ./build/test.hex -target=arduino ./examples/servo
|
||||||
tinygo build -size short -o ./build/test.hex -target=pybadge ./examples/shifter/main.go
|
tinygo build -size short -o ./build/test.hex -target=pybadge ./examples/shifter/main.go
|
||||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/sht3x/main.go
|
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/sht3x/main.go
|
||||||
|
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/sht4x/main.go
|
||||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/shtc3/main.go
|
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/shtc3/main.go
|
||||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/ssd1306/i2c_128x32/main.go
|
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/ssd1306/i2c_128x32/main.go
|
||||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/ssd1306/spi_128x64/main.go
|
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/ssd1306/spi_128x64/main.go
|
||||||
@@ -102,6 +104,7 @@ tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examp
|
|||||||
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/max72xx/main.go
|
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/max72xx/main.go
|
||||||
tinygo build -size short -o ./build/test.hex -target=feather-m0 ./examples/dht/main.go
|
tinygo build -size short -o ./build/test.hex -target=feather-m0 ./examples/dht/main.go
|
||||||
# tinygo build -size short -o ./build/test.hex -target=arduino ./examples/keypad4x4/main.go
|
# tinygo build -size short -o ./build/test.hex -target=arduino ./examples/keypad4x4/main.go
|
||||||
|
tinygo build -size short -o ./build/test.hex -target=feather-rp2040 ./examples/pcf8523/
|
||||||
tinygo build -size short -o ./build/test.hex -target=xiao ./examples/pcf8563/alarm/
|
tinygo build -size short -o ./build/test.hex -target=xiao ./examples/pcf8563/alarm/
|
||||||
tinygo build -size short -o ./build/test.hex -target=xiao ./examples/pcf8563/clkout/
|
tinygo build -size short -o ./build/test.hex -target=xiao ./examples/pcf8563/clkout/
|
||||||
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/time/
|
||||||
@@ -133,3 +136,4 @@ tinygo build -size short -o ./build/test.hex -target=pico ./examples/ndir/main_n
|
|||||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/ndir/main_ndir.go
|
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/ndir/main_ndir.go
|
||||||
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/ndir/main_ndir.go
|
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/ndir/main_ndir.go
|
||||||
tinygo build -size short -o ./build/test.uf2 -target=pico ./examples/mpu9150/main.go
|
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
|
||||||
|
|||||||
+70
-52
@@ -11,6 +11,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"tinygo.org/x/drivers"
|
"tinygo.org/x/drivers"
|
||||||
|
"tinygo.org/x/drivers/pixel"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Model uint8
|
type Model uint8
|
||||||
@@ -20,12 +21,23 @@ type Model uint8
|
|||||||
// Deprecated: use drivers.Rotation instead.
|
// Deprecated: use drivers.Rotation instead.
|
||||||
type Rotation = drivers.Rotation
|
type Rotation = drivers.Rotation
|
||||||
|
|
||||||
|
// Pixel formats supported by the st7735 driver.
|
||||||
|
type Color interface {
|
||||||
|
pixel.RGB444BE | pixel.RGB565BE
|
||||||
|
|
||||||
|
pixel.BaseColor
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
errOutOfBounds = errors.New("rectangle coordinates outside display area")
|
errOutOfBounds = errors.New("rectangle coordinates outside display area")
|
||||||
)
|
)
|
||||||
|
|
||||||
// Device wraps an SPI connection.
|
// Device wraps an SPI connection.
|
||||||
type Device struct {
|
type Device = DeviceOf[pixel.RGB565BE]
|
||||||
|
|
||||||
|
// DeviceOf is a generic version of Device, which supports different pixel
|
||||||
|
// formats.
|
||||||
|
type DeviceOf[T Color] struct {
|
||||||
bus drivers.SPI
|
bus drivers.SPI
|
||||||
dcPin machine.Pin
|
dcPin machine.Pin
|
||||||
resetPin machine.Pin
|
resetPin machine.Pin
|
||||||
@@ -39,7 +51,7 @@ type Device struct {
|
|||||||
batchLength int16
|
batchLength int16
|
||||||
model Model
|
model Model
|
||||||
isBGR bool
|
isBGR bool
|
||||||
batchData []uint8
|
batchData pixel.Image[T] // "image" with width, height of (batchLength, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config is the configuration for the display
|
// Config is the configuration for the display
|
||||||
@@ -54,11 +66,17 @@ type Config struct {
|
|||||||
|
|
||||||
// New creates a new ST7735 connection. The SPI wire must already be configured.
|
// New creates a new ST7735 connection. The SPI wire must already be configured.
|
||||||
func New(bus drivers.SPI, resetPin, dcPin, csPin, blPin machine.Pin) Device {
|
func New(bus drivers.SPI, resetPin, dcPin, csPin, blPin machine.Pin) Device {
|
||||||
|
return NewOf[pixel.RGB565BE](bus, resetPin, dcPin, csPin, blPin)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewOf creates a new ST7735 connection with a particular pixel format. The SPI
|
||||||
|
// wire must already be configured.
|
||||||
|
func NewOf[T Color](bus drivers.SPI, resetPin, dcPin, csPin, blPin machine.Pin) DeviceOf[T] {
|
||||||
dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||||
resetPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
resetPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||||
csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||||
blPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
blPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||||
return Device{
|
return DeviceOf[T]{
|
||||||
bus: bus,
|
bus: bus,
|
||||||
dcPin: dcPin,
|
dcPin: dcPin,
|
||||||
resetPin: resetPin,
|
resetPin: resetPin,
|
||||||
@@ -68,7 +86,7 @@ func New(bus drivers.SPI, resetPin, dcPin, csPin, blPin machine.Pin) Device {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Configure initializes the display with default configuration
|
// Configure initializes the display with default configuration
|
||||||
func (d *Device) Configure(cfg Config) {
|
func (d *DeviceOf[T]) Configure(cfg Config) {
|
||||||
d.model = cfg.Model
|
d.model = cfg.Model
|
||||||
if cfg.Width != 0 {
|
if cfg.Width != 0 {
|
||||||
d.width = cfg.Width
|
d.width = cfg.Width
|
||||||
@@ -93,7 +111,7 @@ func (d *Device) Configure(cfg Config) {
|
|||||||
d.batchLength = d.height
|
d.batchLength = d.height
|
||||||
}
|
}
|
||||||
d.batchLength += d.batchLength & 1
|
d.batchLength += d.batchLength & 1
|
||||||
d.batchData = make([]uint8, d.batchLength*2)
|
d.batchData = pixel.NewImage[T](int(d.batchLength), 1)
|
||||||
|
|
||||||
// reset the device
|
// reset the device
|
||||||
d.resetPin.High()
|
d.resetPin.High()
|
||||||
@@ -142,8 +160,16 @@ func (d *Device) Configure(cfg Config) {
|
|||||||
d.Data(0xEE)
|
d.Data(0xEE)
|
||||||
d.Command(VMCTR1)
|
d.Command(VMCTR1)
|
||||||
d.Data(0x0E)
|
d.Data(0x0E)
|
||||||
|
|
||||||
|
// Set the color format depending on the generic type.
|
||||||
d.Command(COLMOD)
|
d.Command(COLMOD)
|
||||||
d.Data(0x05)
|
var zeroColor T
|
||||||
|
switch any(zeroColor).(type) {
|
||||||
|
case pixel.RGB444BE:
|
||||||
|
d.Data(0x03) // 12 bits per pixel
|
||||||
|
default:
|
||||||
|
d.Data(0x05) // 16 bits per pixel
|
||||||
|
}
|
||||||
|
|
||||||
if d.model == GREENTAB {
|
if d.model == GREENTAB {
|
||||||
d.InvertColors(false)
|
d.InvertColors(false)
|
||||||
@@ -204,12 +230,12 @@ func (d *Device) Configure(cfg Config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Display does nothing, there's no buffer as it might be too big for some boards
|
// Display does nothing, there's no buffer as it might be too big for some boards
|
||||||
func (d *Device) Display() error {
|
func (d *DeviceOf[T]) Display() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetPixel sets a pixel in the screen
|
// SetPixel sets a pixel in the screen
|
||||||
func (d *Device) SetPixel(x int16, y int16, c color.RGBA) {
|
func (d *DeviceOf[T]) SetPixel(x int16, y int16, c color.RGBA) {
|
||||||
w, h := d.Size()
|
w, h := d.Size()
|
||||||
if x < 0 || y < 0 || x >= w || y >= h {
|
if x < 0 || y < 0 || x >= w || y >= h {
|
||||||
return
|
return
|
||||||
@@ -218,7 +244,7 @@ func (d *Device) SetPixel(x int16, y int16, c color.RGBA) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// setWindow prepares the screen to be modified at a given rectangle
|
// setWindow prepares the screen to be modified at a given rectangle
|
||||||
func (d *Device) setWindow(x, y, w, h int16) {
|
func (d *DeviceOf[T]) setWindow(x, y, w, h int16) {
|
||||||
if d.rotation == drivers.Rotation0 || d.rotation == drivers.Rotation180 {
|
if d.rotation == drivers.Rotation0 || d.rotation == drivers.Rotation180 {
|
||||||
x += d.columnOffset
|
x += d.columnOffset
|
||||||
y += d.rowOffset
|
y += d.rowOffset
|
||||||
@@ -234,7 +260,7 @@ func (d *Device) setWindow(x, y, w, h int16) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetScrollWindow sets an area to scroll with fixed top and bottom parts of the display
|
// SetScrollWindow sets an area to scroll with fixed top and bottom parts of the display
|
||||||
func (d *Device) SetScrollArea(topFixedArea, bottomFixedArea int16) {
|
func (d *DeviceOf[T]) SetScrollArea(topFixedArea, bottomFixedArea int16) {
|
||||||
// TODO: this code is broken, see the st7789 and ili9341 implementations for
|
// TODO: this code is broken, see the st7789 and ili9341 implementations for
|
||||||
// how to do this correctly.
|
// how to do this correctly.
|
||||||
d.Command(VSCRDEF)
|
d.Command(VSCRDEF)
|
||||||
@@ -246,38 +272,32 @@ func (d *Device) SetScrollArea(topFixedArea, bottomFixedArea int16) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetScroll sets the vertical scroll address of the display.
|
// SetScroll sets the vertical scroll address of the display.
|
||||||
func (d *Device) SetScroll(line int16) {
|
func (d *DeviceOf[T]) SetScroll(line int16) {
|
||||||
d.Command(VSCRSADD)
|
d.Command(VSCRSADD)
|
||||||
d.Tx([]uint8{uint8(line >> 8), uint8(line)}, false)
|
d.Tx([]uint8{uint8(line >> 8), uint8(line)}, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SpotScroll returns the display to its normal state
|
// SpotScroll returns the display to its normal state
|
||||||
func (d *Device) StopScroll() {
|
func (d *DeviceOf[T]) StopScroll() {
|
||||||
d.Command(NORON)
|
d.Command(NORON)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FillRectangle fills a rectangle at a given coordinates with a color
|
// FillRectangle fills a rectangle at a given coordinates with a color
|
||||||
func (d *Device) FillRectangle(x, y, width, height int16, c color.RGBA) error {
|
func (d *DeviceOf[T]) FillRectangle(x, y, width, height int16, c color.RGBA) error {
|
||||||
k, i := d.Size()
|
k, i := d.Size()
|
||||||
if x < 0 || y < 0 || width <= 0 || height <= 0 ||
|
if x < 0 || y < 0 || width <= 0 || height <= 0 ||
|
||||||
x >= k || (x+width) > k || y >= i || (y+height) > i {
|
x >= k || (x+width) > k || y >= i || (y+height) > i {
|
||||||
return errors.New("rectangle coordinates outside display area")
|
return errors.New("rectangle coordinates outside display area")
|
||||||
}
|
}
|
||||||
d.setWindow(x, y, width, height)
|
d.setWindow(x, y, width, height)
|
||||||
c565 := RGBATo565(c)
|
|
||||||
c1 := uint8(c565 >> 8)
|
|
||||||
c2 := uint8(c565)
|
|
||||||
|
|
||||||
for i = 0; i < d.batchLength; i++ {
|
d.batchData.FillSolidColor(pixel.NewColor[T](c.R, c.G, c.B))
|
||||||
d.batchData[i*2] = c1
|
|
||||||
d.batchData[i*2+1] = c2
|
|
||||||
}
|
|
||||||
i = width * height
|
i = width * height
|
||||||
for i > 0 {
|
for i > 0 {
|
||||||
if i >= d.batchLength {
|
if i >= d.batchLength {
|
||||||
d.Tx(d.batchData, false)
|
d.Tx(d.batchData.RawBuffer(), false)
|
||||||
} else {
|
} else {
|
||||||
d.Tx(d.batchData[:i*2], false)
|
d.Tx(d.batchData.Rescale(int(i), 1).RawBuffer(), false)
|
||||||
}
|
}
|
||||||
i -= d.batchLength
|
i -= d.batchLength
|
||||||
}
|
}
|
||||||
@@ -285,7 +305,9 @@ func (d *Device) FillRectangle(x, y, width, height int16, c color.RGBA) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DrawRGBBitmap8 copies an RGB bitmap to the internal buffer at given coordinates
|
// DrawRGBBitmap8 copies an RGB bitmap to the internal buffer at given coordinates
|
||||||
func (d *Device) DrawRGBBitmap8(x, y int16, data []uint8, w, h int16) error {
|
//
|
||||||
|
// Deprecated: use DrawBitmap instead.
|
||||||
|
func (d *DeviceOf[T]) DrawRGBBitmap8(x, y int16, data []uint8, w, h int16) error {
|
||||||
k, i := d.Size()
|
k, i := d.Size()
|
||||||
if x < 0 || y < 0 || w <= 0 || h <= 0 ||
|
if x < 0 || y < 0 || w <= 0 || h <= 0 ||
|
||||||
x >= k || (x+w) > k || y >= i || (y+h) > i {
|
x >= k || (x+w) > k || y >= i || (y+h) > i {
|
||||||
@@ -296,8 +318,15 @@ func (d *Device) DrawRGBBitmap8(x, y int16, data []uint8, w, h int16) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DrawBitmap copies the bitmap to the internal buffer on the screen at the
|
||||||
|
// given coordinates. It returns once the image data has been sent completely.
|
||||||
|
func (d *DeviceOf[T]) DrawBitmap(x, y int16, bitmap pixel.Image[T]) error {
|
||||||
|
width, height := bitmap.Size()
|
||||||
|
return d.DrawRGBBitmap8(x, y, bitmap.RawBuffer(), int16(width), int16(height))
|
||||||
|
}
|
||||||
|
|
||||||
// FillRectangle fills a rectangle at a given coordinates with a buffer
|
// FillRectangle fills a rectangle at a given coordinates with a buffer
|
||||||
func (d *Device) FillRectangleWithBuffer(x, y, width, height int16, buffer []color.RGBA) error {
|
func (d *DeviceOf[T]) FillRectangleWithBuffer(x, y, width, height int16, buffer []color.RGBA) error {
|
||||||
k, l := d.Size()
|
k, l := d.Size()
|
||||||
if x < 0 || y < 0 || width <= 0 || height <= 0 ||
|
if x < 0 || y < 0 || width <= 0 || height <= 0 ||
|
||||||
x >= k || (x+width) > k || y >= l || (y+height) > l {
|
x >= k || (x+width) > k || y >= l || (y+height) > l {
|
||||||
@@ -315,17 +344,14 @@ func (d *Device) FillRectangleWithBuffer(x, y, width, height int16, buffer []col
|
|||||||
for k > 0 {
|
for k > 0 {
|
||||||
for i := int16(0); i < d.batchLength; i++ {
|
for i := int16(0); i < d.batchLength; i++ {
|
||||||
if offset+i < l {
|
if offset+i < l {
|
||||||
c565 := RGBATo565(buffer[offset+i])
|
c := buffer[offset+i]
|
||||||
c1 := uint8(c565 >> 8)
|
d.batchData.Set(int(i), 0, pixel.NewColor[T](c.R, c.G, c.B))
|
||||||
c2 := uint8(c565)
|
|
||||||
d.batchData[i*2] = c1
|
|
||||||
d.batchData[i*2+1] = c2
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if k >= d.batchLength {
|
if k >= d.batchLength {
|
||||||
d.Tx(d.batchData, false)
|
d.Tx(d.batchData.RawBuffer(), false)
|
||||||
} else {
|
} else {
|
||||||
d.Tx(d.batchData[:k*2], false)
|
d.Tx(d.batchData.Rescale(int(k), 1).RawBuffer(), false)
|
||||||
}
|
}
|
||||||
k -= d.batchLength
|
k -= d.batchLength
|
||||||
offset += d.batchLength
|
offset += d.batchLength
|
||||||
@@ -334,7 +360,7 @@ func (d *Device) FillRectangleWithBuffer(x, y, width, height int16, buffer []col
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DrawFastVLine draws a vertical line faster than using SetPixel
|
// DrawFastVLine draws a vertical line faster than using SetPixel
|
||||||
func (d *Device) DrawFastVLine(x, y0, y1 int16, c color.RGBA) {
|
func (d *DeviceOf[T]) DrawFastVLine(x, y0, y1 int16, c color.RGBA) {
|
||||||
if y0 > y1 {
|
if y0 > y1 {
|
||||||
y0, y1 = y1, y0
|
y0, y1 = y1, y0
|
||||||
}
|
}
|
||||||
@@ -342,7 +368,7 @@ func (d *Device) DrawFastVLine(x, y0, y1 int16, c color.RGBA) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DrawFastHLine draws a horizontal line faster than using SetPixel
|
// DrawFastHLine draws a horizontal line faster than using SetPixel
|
||||||
func (d *Device) DrawFastHLine(x0, x1, y int16, c color.RGBA) {
|
func (d *DeviceOf[T]) DrawFastHLine(x0, x1, y int16, c color.RGBA) {
|
||||||
if x0 > x1 {
|
if x0 > x1 {
|
||||||
x0, x1 = x1, x0
|
x0, x1 = x1, x0
|
||||||
}
|
}
|
||||||
@@ -350,7 +376,7 @@ func (d *Device) DrawFastHLine(x0, x1, y int16, c color.RGBA) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FillScreen fills the screen with a given color
|
// FillScreen fills the screen with a given color
|
||||||
func (d *Device) FillScreen(c color.RGBA) {
|
func (d *DeviceOf[T]) FillScreen(c color.RGBA) {
|
||||||
if d.rotation == drivers.Rotation0 || d.rotation == drivers.Rotation180 {
|
if d.rotation == drivers.Rotation0 || d.rotation == drivers.Rotation180 {
|
||||||
d.FillRectangle(0, 0, d.width, d.height, c)
|
d.FillRectangle(0, 0, d.width, d.height, c)
|
||||||
} else {
|
} else {
|
||||||
@@ -359,12 +385,12 @@ func (d *Device) FillScreen(c color.RGBA) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Rotation returns the currently configured rotation.
|
// Rotation returns the currently configured rotation.
|
||||||
func (d *Device) Rotation() drivers.Rotation {
|
func (d *DeviceOf[T]) Rotation() drivers.Rotation {
|
||||||
return d.rotation
|
return d.rotation
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetRotation changes the rotation of the device (clock-wise)
|
// SetRotation changes the rotation of the device (clock-wise)
|
||||||
func (d *Device) SetRotation(rotation drivers.Rotation) error {
|
func (d *DeviceOf[T]) SetRotation(rotation drivers.Rotation) error {
|
||||||
d.rotation = rotation
|
d.rotation = rotation
|
||||||
madctl := uint8(0)
|
madctl := uint8(0)
|
||||||
switch rotation % 4 {
|
switch rotation % 4 {
|
||||||
@@ -386,23 +412,23 @@ func (d *Device) SetRotation(rotation drivers.Rotation) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Command sends a command to the display
|
// Command sends a command to the display
|
||||||
func (d *Device) Command(command uint8) {
|
func (d *DeviceOf[T]) Command(command uint8) {
|
||||||
d.Tx([]byte{command}, true)
|
d.Tx([]byte{command}, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Command sends a data to the display
|
// Command sends a data to the display
|
||||||
func (d *Device) Data(data uint8) {
|
func (d *DeviceOf[T]) Data(data uint8) {
|
||||||
d.Tx([]byte{data}, false)
|
d.Tx([]byte{data}, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tx sends data to the display
|
// Tx sends data to the display
|
||||||
func (d *Device) Tx(data []byte, isCommand bool) {
|
func (d *DeviceOf[T]) Tx(data []byte, isCommand bool) {
|
||||||
d.dcPin.Set(!isCommand)
|
d.dcPin.Set(!isCommand)
|
||||||
d.bus.Tx(data, nil)
|
d.bus.Tx(data, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Size returns the current size of the display.
|
// Size returns the current size of the display.
|
||||||
func (d *Device) Size() (w, h int16) {
|
func (d *DeviceOf[T]) Size() (w, h int16) {
|
||||||
if d.rotation == drivers.Rotation0 || d.rotation == drivers.Rotation180 {
|
if d.rotation == drivers.Rotation0 || d.rotation == drivers.Rotation180 {
|
||||||
return d.width, d.height
|
return d.width, d.height
|
||||||
}
|
}
|
||||||
@@ -410,7 +436,7 @@ func (d *Device) Size() (w, h int16) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// EnableBacklight enables or disables the backlight
|
// EnableBacklight enables or disables the backlight
|
||||||
func (d *Device) EnableBacklight(enable bool) {
|
func (d *DeviceOf[T]) EnableBacklight(enable bool) {
|
||||||
if enable {
|
if enable {
|
||||||
d.blPin.High()
|
d.blPin.High()
|
||||||
} else {
|
} else {
|
||||||
@@ -421,7 +447,7 @@ func (d *Device) EnableBacklight(enable bool) {
|
|||||||
// Set the sleep mode for this LCD panel. When sleeping, the panel uses a lot
|
// Set the sleep mode for this LCD panel. When sleeping, the panel uses a lot
|
||||||
// less power. The LCD won't display an image anymore, but the memory contents
|
// less power. The LCD won't display an image anymore, but the memory contents
|
||||||
// will be kept.
|
// will be kept.
|
||||||
func (d *Device) Sleep(sleepEnabled bool) error {
|
func (d *DeviceOf[T]) Sleep(sleepEnabled bool) error {
|
||||||
if sleepEnabled {
|
if sleepEnabled {
|
||||||
// Shut down LCD panel.
|
// Shut down LCD panel.
|
||||||
d.Command(SLPIN)
|
d.Command(SLPIN)
|
||||||
@@ -437,7 +463,7 @@ func (d *Device) Sleep(sleepEnabled bool) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// InverColors inverts the colors of the screen
|
// InverColors inverts the colors of the screen
|
||||||
func (d *Device) InvertColors(invert bool) {
|
func (d *DeviceOf[T]) InvertColors(invert bool) {
|
||||||
if invert {
|
if invert {
|
||||||
d.Command(INVON)
|
d.Command(INVON)
|
||||||
} else {
|
} else {
|
||||||
@@ -446,14 +472,6 @@ func (d *Device) InvertColors(invert bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// IsBGR changes the color mode (RGB/BGR)
|
// IsBGR changes the color mode (RGB/BGR)
|
||||||
func (d *Device) IsBGR(bgr bool) {
|
func (d *DeviceOf[T]) IsBGR(bgr bool) {
|
||||||
d.isBGR = bgr
|
d.isBGR = bgr
|
||||||
}
|
}
|
||||||
|
|
||||||
// RGBATo565 converts a color.RGBA to uint16 used in the display
|
|
||||||
func RGBATo565(c color.RGBA) uint16 {
|
|
||||||
r, g, b, _ := c.RGBA()
|
|
||||||
return uint16((r & 0xF800) +
|
|
||||||
((g & 0xFC00) >> 5) +
|
|
||||||
((b & 0xF800) >> 11))
|
|
||||||
}
|
|
||||||
|
|||||||
+99
-72
@@ -14,6 +14,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"tinygo.org/x/drivers"
|
"tinygo.org/x/drivers"
|
||||||
|
"tinygo.org/x/drivers/pixel"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Rotation controls the rotation used by the display.
|
// Rotation controls the rotation used by the display.
|
||||||
@@ -24,6 +25,13 @@ type Rotation = drivers.Rotation
|
|||||||
// The color format used on the display, like RGB565, RGB666, and RGB444.
|
// The color format used on the display, like RGB565, RGB666, and RGB444.
|
||||||
type ColorFormat uint8
|
type ColorFormat uint8
|
||||||
|
|
||||||
|
// Pixel formats supported by the st7789 driver.
|
||||||
|
type Color interface {
|
||||||
|
pixel.RGB444BE | pixel.RGB565BE
|
||||||
|
|
||||||
|
pixel.BaseColor
|
||||||
|
}
|
||||||
|
|
||||||
// FrameRate controls the frame rate used by the display.
|
// FrameRate controls the frame rate used by the display.
|
||||||
type FrameRate uint8
|
type FrameRate uint8
|
||||||
|
|
||||||
@@ -32,7 +40,11 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Device wraps an SPI connection.
|
// Device wraps an SPI connection.
|
||||||
type Device struct {
|
type Device = DeviceOf[pixel.RGB565BE]
|
||||||
|
|
||||||
|
// DeviceOf is a generic version of Device. It supports multiple different pixel
|
||||||
|
// formats.
|
||||||
|
type DeviceOf[T Color] struct {
|
||||||
bus drivers.SPI
|
bus drivers.SPI
|
||||||
dcPin machine.Pin
|
dcPin machine.Pin
|
||||||
resetPin machine.Pin
|
resetPin machine.Pin
|
||||||
@@ -47,6 +59,7 @@ type Device struct {
|
|||||||
rotation drivers.Rotation
|
rotation drivers.Rotation
|
||||||
frameRate FrameRate
|
frameRate FrameRate
|
||||||
batchLength int32
|
batchLength int32
|
||||||
|
batchData pixel.Image[T] // "image" with (width, height) of (batchLength, 1)
|
||||||
isBGR bool
|
isBGR bool
|
||||||
vSyncLines int16
|
vSyncLines int16
|
||||||
cmdBuf [1]byte
|
cmdBuf [1]byte
|
||||||
@@ -71,11 +84,17 @@ type Config struct {
|
|||||||
|
|
||||||
// New creates a new ST7789 connection. The SPI wire must already be configured.
|
// New creates a new ST7789 connection. The SPI wire must already be configured.
|
||||||
func New(bus drivers.SPI, resetPin, dcPin, csPin, blPin machine.Pin) Device {
|
func New(bus drivers.SPI, resetPin, dcPin, csPin, blPin machine.Pin) Device {
|
||||||
|
return NewOf[pixel.RGB565BE](bus, resetPin, dcPin, csPin, blPin)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewOf creates a new ST7789 connection with a particular pixel format. The SPI
|
||||||
|
// wire must already be configured.
|
||||||
|
func NewOf[T Color](bus drivers.SPI, resetPin, dcPin, csPin, blPin machine.Pin) DeviceOf[T] {
|
||||||
dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||||
resetPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
resetPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||||
csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||||
blPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
blPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||||
return Device{
|
return DeviceOf[T]{
|
||||||
bus: bus,
|
bus: bus,
|
||||||
dcPin: dcPin,
|
dcPin: dcPin,
|
||||||
resetPin: resetPin,
|
resetPin: resetPin,
|
||||||
@@ -85,7 +104,7 @@ func New(bus drivers.SPI, resetPin, dcPin, csPin, blPin machine.Pin) Device {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Configure initializes the display with default configuration
|
// Configure initializes the display with default configuration
|
||||||
func (d *Device) Configure(cfg Config) {
|
func (d *DeviceOf[T]) Configure(cfg Config) {
|
||||||
if cfg.Width != 0 {
|
if cfg.Width != 0 {
|
||||||
d.width = cfg.Width
|
d.width = cfg.Width
|
||||||
} else {
|
} else {
|
||||||
@@ -137,7 +156,14 @@ func (d *Device) Configure(cfg Config) {
|
|||||||
d.sendCommand(SLPOUT, nil) // Exit sleep mode
|
d.sendCommand(SLPOUT, nil) // Exit sleep mode
|
||||||
|
|
||||||
// Memory initialization
|
// Memory initialization
|
||||||
d.setColorFormat(ColorRGB565) // Set color mode to 16-bit color
|
var zeroColor T
|
||||||
|
switch any(zeroColor).(type) {
|
||||||
|
case pixel.RGB444BE:
|
||||||
|
d.setColorFormat(ColorRGB444) // 12 bits per pixel
|
||||||
|
default:
|
||||||
|
// Use default RGB565 color format.
|
||||||
|
d.setColorFormat(ColorRGB565) // 16 bits per pixel
|
||||||
|
}
|
||||||
time.Sleep(10 * time.Millisecond)
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
|
||||||
d.setRotation(d.rotation) // Memory orientation
|
d.setRotation(d.rotation) // Memory orientation
|
||||||
@@ -189,7 +215,7 @@ func (d *Device) Configure(cfg Config) {
|
|||||||
// Send a command with data to the display. It does not change the chip select
|
// Send a command with data to the display. It does not change the chip select
|
||||||
// pin (it must be low when calling). The DC pin is left high after return,
|
// pin (it must be low when calling). The DC pin is left high after return,
|
||||||
// meaning that data can be sent right away.
|
// meaning that data can be sent right away.
|
||||||
func (d *Device) sendCommand(command uint8, data []byte) error {
|
func (d *DeviceOf[T]) sendCommand(command uint8, data []byte) error {
|
||||||
d.cmdBuf[0] = command
|
d.cmdBuf[0] = command
|
||||||
d.dcPin.Low()
|
d.dcPin.Low()
|
||||||
err := d.bus.Tx(d.cmdBuf[:1], nil)
|
err := d.bus.Tx(d.cmdBuf[:1], nil)
|
||||||
@@ -202,7 +228,7 @@ func (d *Device) sendCommand(command uint8, data []byte) error {
|
|||||||
|
|
||||||
// startWrite must be called at the beginning of all exported methods to set the
|
// startWrite must be called at the beginning of all exported methods to set the
|
||||||
// chip select pin low.
|
// chip select pin low.
|
||||||
func (d *Device) startWrite() {
|
func (d *DeviceOf[T]) startWrite() {
|
||||||
if d.csPin != machine.NoPin {
|
if d.csPin != machine.NoPin {
|
||||||
d.csPin.Low()
|
d.csPin.Low()
|
||||||
}
|
}
|
||||||
@@ -210,14 +236,23 @@ func (d *Device) startWrite() {
|
|||||||
|
|
||||||
// endWrite must be called at the end of all exported methods to set the chip
|
// endWrite must be called at the end of all exported methods to set the chip
|
||||||
// select pin high.
|
// select pin high.
|
||||||
func (d *Device) endWrite() {
|
func (d *DeviceOf[T]) endWrite() {
|
||||||
if d.csPin != machine.NoPin {
|
if d.csPin != machine.NoPin {
|
||||||
d.csPin.High()
|
d.csPin.High()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getBuffer returns the image buffer, that's always d.batchLength wide and 1
|
||||||
|
// pixel high. It can be used as a temporary buffer to transmit image data.
|
||||||
|
func (d *DeviceOf[T]) getBuffer() pixel.Image[T] {
|
||||||
|
if d.batchData.Len() == 0 {
|
||||||
|
d.batchData = pixel.NewImage[T](int(d.batchLength), 1)
|
||||||
|
}
|
||||||
|
return d.batchData
|
||||||
|
}
|
||||||
|
|
||||||
// Sync waits for the display to hit the next VSYNC pause
|
// Sync waits for the display to hit the next VSYNC pause
|
||||||
func (d *Device) Sync() {
|
func (d *DeviceOf[T]) Sync() {
|
||||||
d.SyncToScanLine(0)
|
d.SyncToScanLine(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,7 +267,7 @@ func (d *Device) Sync() {
|
|||||||
// NOTE: Use GetHighestScanLine and GetLowestScanLine to obtain the highest
|
// NOTE: Use GetHighestScanLine and GetLowestScanLine to obtain the highest
|
||||||
// and lowest useful values. Values are affected by front and back porch
|
// and lowest useful values. Values are affected by front and back porch
|
||||||
// vsync settings (derived from VSyncLines configuration option).
|
// vsync settings (derived from VSyncLines configuration option).
|
||||||
func (d *Device) SyncToScanLine(scanline uint16) {
|
func (d *DeviceOf[T]) SyncToScanLine(scanline uint16) {
|
||||||
scan := d.GetScanLine()
|
scan := d.GetScanLine()
|
||||||
|
|
||||||
// Sometimes GetScanLine returns erroneous 0 on first call after draw, so double check
|
// Sometimes GetScanLine returns erroneous 0 on first call after draw, so double check
|
||||||
@@ -262,7 +297,7 @@ func (d *Device) SyncToScanLine(scanline uint16) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetScanLine reads the current scanline value from the display
|
// GetScanLine reads the current scanline value from the display
|
||||||
func (d *Device) GetScanLine() uint16 {
|
func (d *DeviceOf[T]) GetScanLine() uint16 {
|
||||||
d.startWrite()
|
d.startWrite()
|
||||||
data := []uint8{0x00, 0x00}
|
data := []uint8{0x00, 0x00}
|
||||||
d.dcPin.Low()
|
d.dcPin.Low()
|
||||||
@@ -277,24 +312,24 @@ func (d *Device) GetScanLine() uint16 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetHighestScanLine calculates the last scanline id in the frame before VSYNC pause
|
// GetHighestScanLine calculates the last scanline id in the frame before VSYNC pause
|
||||||
func (d *Device) GetHighestScanLine() uint16 {
|
func (d *DeviceOf[T]) GetHighestScanLine() uint16 {
|
||||||
// Last scanline id appears to be backporch/2 + 320/2
|
// Last scanline id appears to be backporch/2 + 320/2
|
||||||
return uint16(math.Ceil(float64(d.vSyncLines)/2)/2) + 160
|
return uint16(math.Ceil(float64(d.vSyncLines)/2)/2) + 160
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLowestScanLine calculate the first scanline id to appear after VSYNC pause
|
// GetLowestScanLine calculate the first scanline id to appear after VSYNC pause
|
||||||
func (d *Device) GetLowestScanLine() uint16 {
|
func (d *DeviceOf[T]) GetLowestScanLine() uint16 {
|
||||||
// First scanline id appears to be backporch/2 + 1
|
// First scanline id appears to be backporch/2 + 1
|
||||||
return uint16(math.Ceil(float64(d.vSyncLines)/2)/2) + 1
|
return uint16(math.Ceil(float64(d.vSyncLines)/2)/2) + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Display does nothing, there's no buffer as it might be too big for some boards
|
// Display does nothing, there's no buffer as it might be too big for some boards
|
||||||
func (d *Device) Display() error {
|
func (d *DeviceOf[T]) Display() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetPixel sets a pixel in the screen
|
// SetPixel sets a pixel in the screen
|
||||||
func (d *Device) SetPixel(x int16, y int16, c color.RGBA) {
|
func (d *DeviceOf[T]) SetPixel(x int16, y int16, c color.RGBA) {
|
||||||
if x < 0 || y < 0 ||
|
if x < 0 || y < 0 ||
|
||||||
(((d.rotation == drivers.Rotation0 || d.rotation == drivers.Rotation180) && (x >= d.width || y >= d.height)) ||
|
(((d.rotation == drivers.Rotation0 || d.rotation == drivers.Rotation180) && (x >= d.width || y >= d.height)) ||
|
||||||
((d.rotation == drivers.Rotation90 || d.rotation == drivers.Rotation270) && (x >= d.height || y >= d.width))) {
|
((d.rotation == drivers.Rotation90 || d.rotation == drivers.Rotation270) && (x >= d.height || y >= d.width))) {
|
||||||
@@ -304,7 +339,7 @@ func (d *Device) SetPixel(x int16, y int16, c color.RGBA) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// setWindow prepares the screen to be modified at a given rectangle
|
// setWindow prepares the screen to be modified at a given rectangle
|
||||||
func (d *Device) setWindow(x, y, w, h int16) {
|
func (d *DeviceOf[T]) setWindow(x, y, w, h int16) {
|
||||||
x += d.columnOffset
|
x += d.columnOffset
|
||||||
y += d.rowOffset
|
y += d.rowOffset
|
||||||
copy(d.buf[:4], []uint8{uint8(x >> 8), uint8(x), uint8((x + w - 1) >> 8), uint8(x + w - 1)})
|
copy(d.buf[:4], []uint8{uint8(x >> 8), uint8(x), uint8((x + w - 1) >> 8), uint8(x + w - 1)})
|
||||||
@@ -315,45 +350,41 @@ func (d *Device) setWindow(x, y, w, h int16) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FillRectangle fills a rectangle at a given coordinates with a color
|
// FillRectangle fills a rectangle at a given coordinates with a color
|
||||||
func (d *Device) FillRectangle(x, y, width, height int16, c color.RGBA) error {
|
func (d *DeviceOf[T]) FillRectangle(x, y, width, height int16, c color.RGBA) error {
|
||||||
d.startWrite()
|
d.startWrite()
|
||||||
err := d.fillRectangle(x, y, width, height, c)
|
err := d.fillRectangle(x, y, width, height, c)
|
||||||
d.endWrite()
|
d.endWrite()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Device) fillRectangle(x, y, width, height int16, c color.RGBA) error {
|
func (d *DeviceOf[T]) fillRectangle(x, y, width, height int16, c color.RGBA) error {
|
||||||
k, i := d.Size()
|
k, i := d.Size()
|
||||||
if x < 0 || y < 0 || width <= 0 || height <= 0 ||
|
if x < 0 || y < 0 || width <= 0 || height <= 0 ||
|
||||||
x >= k || (x+width) > k || y >= i || (y+height) > i {
|
x >= k || (x+width) > k || y >= i || (y+height) > i {
|
||||||
return errors.New("rectangle coordinates outside display area")
|
return errors.New("rectangle coordinates outside display area")
|
||||||
}
|
}
|
||||||
d.setWindow(x, y, width, height)
|
d.setWindow(x, y, width, height)
|
||||||
c565 := RGBATo565(c)
|
|
||||||
c1 := uint8(c565 >> 8)
|
|
||||||
c2 := uint8(c565)
|
|
||||||
|
|
||||||
data := make([]uint8, d.batchLength*2)
|
image := d.getBuffer()
|
||||||
for i := int32(0); i < d.batchLength; i++ {
|
image.FillSolidColor(pixel.NewColor[T](c.R, c.G, c.B))
|
||||||
data[i*2] = c1
|
j := int(width) * int(height)
|
||||||
data[i*2+1] = c2
|
|
||||||
}
|
|
||||||
j := int32(width) * int32(height)
|
|
||||||
for j > 0 {
|
for j > 0 {
|
||||||
// The DC pin is already set to data in the setWindow call, so we can
|
// The DC pin is already set to data in the setWindow call, so we can
|
||||||
// just write bytes on the SPI bus.
|
// just write bytes on the SPI bus.
|
||||||
if j >= d.batchLength {
|
if j >= image.Len() {
|
||||||
d.bus.Tx(data, nil)
|
d.bus.Tx(image.RawBuffer(), nil)
|
||||||
} else {
|
} else {
|
||||||
d.bus.Tx(data[:j*2], nil)
|
d.bus.Tx(image.Rescale(j, 1).RawBuffer(), nil)
|
||||||
}
|
}
|
||||||
j -= d.batchLength
|
j -= image.Len()
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DrawRGBBitmap8 copies an RGB bitmap to the internal buffer at given coordinates
|
// DrawRGBBitmap8 copies an RGB bitmap to the internal buffer at given coordinates
|
||||||
func (d *Device) DrawRGBBitmap8(x, y int16, data []uint8, w, h int16) error {
|
//
|
||||||
|
// Deprecated: use DrawBitmap instead.
|
||||||
|
func (d *DeviceOf[T]) DrawRGBBitmap8(x, y int16, data []uint8, w, h int16) error {
|
||||||
k, i := d.Size()
|
k, i := d.Size()
|
||||||
if x < 0 || y < 0 || w <= 0 || h <= 0 ||
|
if x < 0 || y < 0 || w <= 0 || h <= 0 ||
|
||||||
x >= k || (x+w) > k || y >= i || (y+h) > i {
|
x >= k || (x+w) > k || y >= i || (y+h) > i {
|
||||||
@@ -366,8 +397,15 @@ func (d *Device) DrawRGBBitmap8(x, y int16, data []uint8, w, h int16) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DrawBitmap copies the bitmap to the internal buffer on the screen at the
|
||||||
|
// given coordinates. It returns once the image data has been sent completely.
|
||||||
|
func (d *DeviceOf[T]) DrawBitmap(x, y int16, bitmap pixel.Image[T]) error {
|
||||||
|
width, height := bitmap.Size()
|
||||||
|
return d.DrawRGBBitmap8(x, y, bitmap.RawBuffer(), int16(width), int16(height))
|
||||||
|
}
|
||||||
|
|
||||||
// FillRectangleWithBuffer fills buffer with a rectangle at a given coordinates.
|
// FillRectangleWithBuffer fills buffer with a rectangle at a given coordinates.
|
||||||
func (d *Device) FillRectangleWithBuffer(x, y, width, height int16, buffer []color.RGBA) error {
|
func (d *DeviceOf[T]) FillRectangleWithBuffer(x, y, width, height int16, buffer []color.RGBA) error {
|
||||||
i, j := d.Size()
|
i, j := d.Size()
|
||||||
if x < 0 || y < 0 || width <= 0 || height <= 0 ||
|
if x < 0 || y < 0 || width <= 0 || height <= 0 ||
|
||||||
x >= i || (x+width) > i || y >= j || (y+height) > j {
|
x >= i || (x+width) > i || y >= j || (y+height) > j {
|
||||||
@@ -379,35 +417,32 @@ func (d *Device) FillRectangleWithBuffer(x, y, width, height int16, buffer []col
|
|||||||
d.startWrite()
|
d.startWrite()
|
||||||
d.setWindow(x, y, width, height)
|
d.setWindow(x, y, width, height)
|
||||||
|
|
||||||
k := int32(width) * int32(height)
|
k := int(width) * int(height)
|
||||||
data := make([]uint8, d.batchLength*2)
|
image := d.getBuffer()
|
||||||
offset := int32(0)
|
offset := 0
|
||||||
for k > 0 {
|
for k > 0 {
|
||||||
for i := int32(0); i < d.batchLength; i++ {
|
for i := 0; i < image.Len(); i++ {
|
||||||
if offset+i < int32(len(buffer)) {
|
if offset+i < len(buffer) {
|
||||||
c565 := RGBATo565(buffer[offset+i])
|
c := buffer[offset+i]
|
||||||
c1 := uint8(c565 >> 8)
|
image.Set(i, 0, pixel.NewColor[T](c.R, c.G, c.B))
|
||||||
c2 := uint8(c565)
|
|
||||||
data[i*2] = c1
|
|
||||||
data[i*2+1] = c2
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// The DC pin is already set to data in the setWindow call, so we don't
|
// The DC pin is already set to data in the setWindow call, so we don't
|
||||||
// have to set it here.
|
// have to set it here.
|
||||||
if k >= d.batchLength {
|
if k >= image.Len() {
|
||||||
d.bus.Tx(data, nil)
|
d.bus.Tx(image.RawBuffer(), nil)
|
||||||
} else {
|
} else {
|
||||||
d.bus.Tx(data[:k*2], nil)
|
d.bus.Tx(image.Rescale(k, 1).RawBuffer(), nil)
|
||||||
}
|
}
|
||||||
k -= d.batchLength
|
k -= image.Len()
|
||||||
offset += d.batchLength
|
offset += image.Len()
|
||||||
}
|
}
|
||||||
d.endWrite()
|
d.endWrite()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DrawFastVLine draws a vertical line faster than using SetPixel
|
// DrawFastVLine draws a vertical line faster than using SetPixel
|
||||||
func (d *Device) DrawFastVLine(x, y0, y1 int16, c color.RGBA) {
|
func (d *DeviceOf[T]) DrawFastVLine(x, y0, y1 int16, c color.RGBA) {
|
||||||
if y0 > y1 {
|
if y0 > y1 {
|
||||||
y0, y1 = y1, y0
|
y0, y1 = y1, y0
|
||||||
}
|
}
|
||||||
@@ -415,7 +450,7 @@ func (d *Device) DrawFastVLine(x, y0, y1 int16, c color.RGBA) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DrawFastHLine draws a horizontal line faster than using SetPixel
|
// DrawFastHLine draws a horizontal line faster than using SetPixel
|
||||||
func (d *Device) DrawFastHLine(x0, x1, y int16, c color.RGBA) {
|
func (d *DeviceOf[T]) DrawFastHLine(x0, x1, y int16, c color.RGBA) {
|
||||||
if x0 > x1 {
|
if x0 > x1 {
|
||||||
x0, x1 = x1, x0
|
x0, x1 = x1, x0
|
||||||
}
|
}
|
||||||
@@ -423,13 +458,13 @@ func (d *Device) DrawFastHLine(x0, x1, y int16, c color.RGBA) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FillScreen fills the screen with a given color
|
// FillScreen fills the screen with a given color
|
||||||
func (d *Device) FillScreen(c color.RGBA) {
|
func (d *DeviceOf[T]) FillScreen(c color.RGBA) {
|
||||||
d.startWrite()
|
d.startWrite()
|
||||||
d.fillScreen(c)
|
d.fillScreen(c)
|
||||||
d.endWrite()
|
d.endWrite()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Device) fillScreen(c color.RGBA) {
|
func (d *DeviceOf[T]) fillScreen(c color.RGBA) {
|
||||||
if d.rotation == NO_ROTATION || d.rotation == ROTATION_180 {
|
if d.rotation == NO_ROTATION || d.rotation == ROTATION_180 {
|
||||||
d.fillRectangle(0, 0, d.width, d.height, c)
|
d.fillRectangle(0, 0, d.width, d.height, c)
|
||||||
} else {
|
} else {
|
||||||
@@ -441,13 +476,13 @@ func (d *Device) fillScreen(c color.RGBA) {
|
|||||||
// The default is RGB565, setting it to any other value will break functions
|
// The default is RGB565, setting it to any other value will break functions
|
||||||
// like SetPixel, FillRectangle, etc. Instead, you can write color data in the
|
// like SetPixel, FillRectangle, etc. Instead, you can write color data in the
|
||||||
// specified color format using DrawRGBBitmap8.
|
// specified color format using DrawRGBBitmap8.
|
||||||
func (d *Device) SetColorFormat(format ColorFormat) {
|
func (d *DeviceOf[T]) SetColorFormat(format ColorFormat) {
|
||||||
d.startWrite()
|
d.startWrite()
|
||||||
d.setColorFormat(format)
|
d.setColorFormat(format)
|
||||||
d.endWrite()
|
d.endWrite()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Device) setColorFormat(format ColorFormat) {
|
func (d *DeviceOf[T]) setColorFormat(format ColorFormat) {
|
||||||
// Lower 4 bits set the color format used in SPI.
|
// Lower 4 bits set the color format used in SPI.
|
||||||
// Upper 4 bits set the color format used in the direct RGB interface.
|
// Upper 4 bits set the color format used in the direct RGB interface.
|
||||||
// The RGB interface is not currently supported, so it is left at a
|
// The RGB interface is not currently supported, so it is left at a
|
||||||
@@ -457,12 +492,12 @@ func (d *Device) setColorFormat(format ColorFormat) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Rotation returns the current rotation of the device.
|
// Rotation returns the current rotation of the device.
|
||||||
func (d *Device) Rotation() drivers.Rotation {
|
func (d *DeviceOf[T]) Rotation() drivers.Rotation {
|
||||||
return d.rotation
|
return d.rotation
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetRotation changes the rotation of the device (clock-wise)
|
// SetRotation changes the rotation of the device (clock-wise)
|
||||||
func (d *Device) SetRotation(rotation Rotation) error {
|
func (d *DeviceOf[T]) SetRotation(rotation Rotation) error {
|
||||||
d.rotation = rotation
|
d.rotation = rotation
|
||||||
d.startWrite()
|
d.startWrite()
|
||||||
err := d.setRotation(rotation)
|
err := d.setRotation(rotation)
|
||||||
@@ -470,7 +505,7 @@ func (d *Device) SetRotation(rotation Rotation) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Device) setRotation(rotation Rotation) error {
|
func (d *DeviceOf[T]) setRotation(rotation Rotation) error {
|
||||||
madctl := uint8(0)
|
madctl := uint8(0)
|
||||||
switch rotation % 4 {
|
switch rotation % 4 {
|
||||||
case drivers.Rotation0:
|
case drivers.Rotation0:
|
||||||
@@ -496,7 +531,7 @@ func (d *Device) setRotation(rotation Rotation) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Size returns the current size of the display.
|
// Size returns the current size of the display.
|
||||||
func (d *Device) Size() (w, h int16) {
|
func (d *DeviceOf[T]) Size() (w, h int16) {
|
||||||
if d.rotation == drivers.Rotation0 || d.rotation == drivers.Rotation180 {
|
if d.rotation == drivers.Rotation0 || d.rotation == drivers.Rotation180 {
|
||||||
return d.width, d.height
|
return d.width, d.height
|
||||||
}
|
}
|
||||||
@@ -504,7 +539,7 @@ func (d *Device) Size() (w, h int16) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// EnableBacklight enables or disables the backlight
|
// EnableBacklight enables or disables the backlight
|
||||||
func (d *Device) EnableBacklight(enable bool) {
|
func (d *DeviceOf[T]) EnableBacklight(enable bool) {
|
||||||
if enable {
|
if enable {
|
||||||
d.blPin.High()
|
d.blPin.High()
|
||||||
} else {
|
} else {
|
||||||
@@ -515,7 +550,7 @@ func (d *Device) EnableBacklight(enable bool) {
|
|||||||
// Set the sleep mode for this LCD panel. When sleeping, the panel uses a lot
|
// Set the sleep mode for this LCD panel. When sleeping, the panel uses a lot
|
||||||
// less power. The LCD won't display an image anymore, but the memory contents
|
// less power. The LCD won't display an image anymore, but the memory contents
|
||||||
// will be kept.
|
// will be kept.
|
||||||
func (d *Device) Sleep(sleepEnabled bool) error {
|
func (d *DeviceOf[T]) Sleep(sleepEnabled bool) error {
|
||||||
if sleepEnabled {
|
if sleepEnabled {
|
||||||
d.startWrite()
|
d.startWrite()
|
||||||
d.sendCommand(SLPIN, nil)
|
d.sendCommand(SLPIN, nil)
|
||||||
@@ -537,7 +572,7 @@ func (d *Device) Sleep(sleepEnabled bool) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// InvertColors inverts the colors of the screen
|
// InvertColors inverts the colors of the screen
|
||||||
func (d *Device) InvertColors(invert bool) {
|
func (d *DeviceOf[T]) InvertColors(invert bool) {
|
||||||
d.startWrite()
|
d.startWrite()
|
||||||
if invert {
|
if invert {
|
||||||
d.sendCommand(INVON, nil)
|
d.sendCommand(INVON, nil)
|
||||||
@@ -548,12 +583,12 @@ func (d *Device) InvertColors(invert bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// IsBGR changes the color mode (RGB/BGR)
|
// IsBGR changes the color mode (RGB/BGR)
|
||||||
func (d *Device) IsBGR(bgr bool) {
|
func (d *DeviceOf[T]) IsBGR(bgr bool) {
|
||||||
d.isBGR = bgr
|
d.isBGR = bgr
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetScrollArea sets an area to scroll with fixed top and bottom parts of the display.
|
// SetScrollArea sets an area to scroll with fixed top and bottom parts of the display.
|
||||||
func (d *Device) SetScrollArea(topFixedArea, bottomFixedArea int16) {
|
func (d *DeviceOf[T]) SetScrollArea(topFixedArea, bottomFixedArea int16) {
|
||||||
if d.height < 320 {
|
if d.height < 320 {
|
||||||
// The screen doesn't use the full 320 pixel height.
|
// The screen doesn't use the full 320 pixel height.
|
||||||
// Enlarge the bottom fixed area to fill the 320 pixel height, so that
|
// Enlarge the bottom fixed area to fill the 320 pixel height, so that
|
||||||
@@ -577,7 +612,7 @@ func (d *Device) SetScrollArea(topFixedArea, bottomFixedArea int16) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetScroll sets the vertical scroll address of the display.
|
// SetScroll sets the vertical scroll address of the display.
|
||||||
func (d *Device) SetScroll(line int16) {
|
func (d *DeviceOf[T]) SetScroll(line int16) {
|
||||||
if d.rotation == drivers.Rotation180 {
|
if d.rotation == drivers.Rotation180 {
|
||||||
// The screen is rotated by 180°, so we have to invert the scroll line
|
// The screen is rotated by 180°, so we have to invert the scroll line
|
||||||
// (taking care of the RowOffset).
|
// (taking care of the RowOffset).
|
||||||
@@ -591,16 +626,8 @@ func (d *Device) SetScroll(line int16) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// StopScroll returns the display to its normal state.
|
// StopScroll returns the display to its normal state.
|
||||||
func (d *Device) StopScroll() {
|
func (d *DeviceOf[T]) StopScroll() {
|
||||||
d.startWrite()
|
d.startWrite()
|
||||||
d.sendCommand(NORON, nil)
|
d.sendCommand(NORON, nil)
|
||||||
d.endWrite()
|
d.endWrite()
|
||||||
}
|
}
|
||||||
|
|
||||||
// RGBATo565 converts a color.RGBA to uint16 used in the display
|
|
||||||
func RGBATo565(c color.RGBA) uint16 {
|
|
||||||
r, g, b, _ := c.RGBA()
|
|
||||||
return uint16((r & 0xF800) +
|
|
||||||
((g & 0xFC00) >> 5) +
|
|
||||||
((b & 0xF800) >> 11))
|
|
||||||
}
|
|
||||||
|
|||||||
+1
-1
@@ -2,4 +2,4 @@ package drivers
|
|||||||
|
|
||||||
// Version returns a user-readable string showing the version of the drivers package for support purposes.
|
// 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.
|
// Update this value before release of new version of software.
|
||||||
const Version = "0.25.0"
|
const Version = "0.26.0"
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ import (
|
|||||||
type Config struct {
|
type Config struct {
|
||||||
Width int16 // Width is the display resolution
|
Width int16 // Width is the display resolution
|
||||||
Height int16
|
Height int16
|
||||||
LogicalWidth int16 // LogicalWidth must be a multiple of 8 and same size or bigger than Width
|
LogicalWidth int16 // LogicalWidth must be a multiple of 8 and same size or bigger than Width
|
||||||
Rotation Rotation // Rotation is clock-wise
|
Rotation drivers.Rotation
|
||||||
}
|
}
|
||||||
|
|
||||||
type Device struct {
|
type Device struct {
|
||||||
@@ -30,10 +30,11 @@ type Device struct {
|
|||||||
height int16
|
height int16
|
||||||
buffer []uint8
|
buffer []uint8
|
||||||
bufferLength uint32
|
bufferLength uint32
|
||||||
rotation Rotation
|
rotation drivers.Rotation
|
||||||
}
|
}
|
||||||
|
|
||||||
type Rotation uint8
|
// Deprecated: use drivers.Rotation instead.
|
||||||
|
type Rotation = drivers.Rotation
|
||||||
|
|
||||||
// Look up table for full updates
|
// Look up table for full updates
|
||||||
var lutFullUpdate = [30]uint8{
|
var lutFullUpdate = [30]uint8{
|
||||||
@@ -130,6 +131,17 @@ func (d *Device) DeepSleep() {
|
|||||||
d.WaitUntilIdle()
|
d.WaitUntilIdle()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set the sleep mode of the panel. The display will still show its contents,
|
||||||
|
// but will go into a lower-power state.
|
||||||
|
func (d *Device) Sleep(sleepEnabled bool) error {
|
||||||
|
if sleepEnabled {
|
||||||
|
d.DeepSleep()
|
||||||
|
} else {
|
||||||
|
d.Reset()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// SendCommand sends a command to the display
|
// SendCommand sends a command to the display
|
||||||
func (d *Device) SendCommand(command uint8) {
|
func (d *Device) SendCommand(command uint8) {
|
||||||
d.sendDataCommand(true, command)
|
d.sendDataCommand(true, command)
|
||||||
@@ -167,18 +179,22 @@ func (d *Device) SetLUT(fullUpdate bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetPixel modifies the internal buffer in a single pixel.
|
// SetPixel modifies the internal buffer in a single pixel.
|
||||||
// The display have 2 colors: black and white
|
// The display have 2 colors: black and white. We use a very simple cutoff to
|
||||||
// We use RGBA(0,0,0, 255) as white (transparent)
|
// determine whether a pixel is black or white (darker colors are black, lighter
|
||||||
// Anything else as black
|
// colors are white).
|
||||||
func (d *Device) SetPixel(x int16, y int16, c color.RGBA) {
|
func (d *Device) SetPixel(x int16, y int16, c color.RGBA) {
|
||||||
x, y = d.xy(x, y)
|
x, y = d.xy(x, y)
|
||||||
if x < 0 || x >= d.logicalWidth || y < 0 || y >= d.height {
|
if x < 0 || x >= d.logicalWidth || y < 0 || y >= d.height {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
byteIndex := (x + y*d.logicalWidth) / 8
|
byteIndex := (x + y*d.logicalWidth) / 8
|
||||||
if c.R == 0 && c.G == 0 && c.B == 0 { // TRANSPARENT / WHITE
|
// Very simle black/white split.
|
||||||
|
// This isn't very accurate (especially for sRGB colors) but is close enough
|
||||||
|
// to the truth that it probably doesn't matter much - especially on an
|
||||||
|
// e-paper display.
|
||||||
|
if int(c.R)+int(c.G)+int(c.B) > 128*3 { // light, convert to white
|
||||||
d.buffer[byteIndex] |= 0x80 >> uint8(x%8)
|
d.buffer[byteIndex] |= 0x80 >> uint8(x%8)
|
||||||
} else { // WHITE / EMPTY
|
} else { // dark, convert to black
|
||||||
d.buffer[byteIndex] &^= 0x80 >> uint8(x%8)
|
d.buffer[byteIndex] &^= 0x80 >> uint8(x%8)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -209,13 +225,13 @@ func (d *Device) DisplayRect(x int16, y int16, width int16, height int16) error
|
|||||||
if x < 0 || y < 0 || x >= d.logicalWidth || y >= d.height || width < 0 || height < 0 {
|
if x < 0 || y < 0 || x >= d.logicalWidth || y >= d.height || width < 0 || height < 0 {
|
||||||
return errors.New("wrong rectangle")
|
return errors.New("wrong rectangle")
|
||||||
}
|
}
|
||||||
if d.rotation == ROTATION_90 {
|
if d.rotation == drivers.Rotation90 {
|
||||||
width, height = height, width
|
width, height = height, width
|
||||||
x -= width
|
x -= width
|
||||||
} else if d.rotation == ROTATION_180 {
|
} else if d.rotation == drivers.Rotation180 {
|
||||||
x -= width - 1
|
x -= width - 1
|
||||||
y -= height - 1
|
y -= height - 1
|
||||||
} else if d.rotation == ROTATION_270 {
|
} else if d.rotation == drivers.Rotation270 {
|
||||||
width, height = height, width
|
width, height = height, width
|
||||||
y -= height
|
y -= height
|
||||||
}
|
}
|
||||||
@@ -301,27 +317,33 @@ func (d *Device) ClearBuffer() {
|
|||||||
|
|
||||||
// Size returns the current size of the display.
|
// Size returns the current size of the display.
|
||||||
func (d *Device) Size() (w, h int16) {
|
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.logicalWidth
|
return d.height, d.logicalWidth
|
||||||
}
|
}
|
||||||
return d.logicalWidth, d.height
|
return d.logicalWidth, d.height
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetRotation changes the rotation (clock-wise) of the device
|
// Rotation returns the current rotation of the device.
|
||||||
func (d *Device) SetRotation(rotation Rotation) {
|
func (d *Device) Rotation() drivers.Rotation {
|
||||||
|
return d.rotation
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRotation changes the rotation of the device.
|
||||||
|
func (d *Device) SetRotation(rotation drivers.Rotation) error {
|
||||||
d.rotation = rotation
|
d.rotation = rotation
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// xy chages the coordinates according to the rotation
|
// xy chages the coordinates according to the rotation
|
||||||
func (d *Device) xy(x, y int16) (int16, int16) {
|
func (d *Device) xy(x, y int16) (int16, int16) {
|
||||||
switch d.rotation {
|
switch d.rotation {
|
||||||
case NO_ROTATION:
|
case drivers.Rotation0:
|
||||||
return x, y
|
return x, y
|
||||||
case ROTATION_90:
|
case drivers.Rotation90:
|
||||||
return d.width - y - 1, x
|
return d.width - y - 1, x
|
||||||
case ROTATION_180:
|
case drivers.Rotation180:
|
||||||
return d.width - x - 1, d.height - y - 1
|
return d.width - x - 1, d.height - y - 1
|
||||||
case ROTATION_270:
|
case drivers.Rotation270:
|
||||||
return y, d.height - x - 1
|
return y, d.height - x - 1
|
||||||
}
|
}
|
||||||
return x, y
|
return x, y
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package epd2in13
|
package epd2in13
|
||||||
|
|
||||||
|
import "tinygo.org/x/drivers"
|
||||||
|
|
||||||
// Registers
|
// Registers
|
||||||
const (
|
const (
|
||||||
DRIVER_OUTPUT_CONTROL = 0x01
|
DRIVER_OUTPUT_CONTROL = 0x01
|
||||||
@@ -24,8 +26,8 @@ const (
|
|||||||
SET_RAM_Y_ADDRESS_COUNTER = 0x4F
|
SET_RAM_Y_ADDRESS_COUNTER = 0x4F
|
||||||
TERMINATE_FRAME_READ_WRITE = 0xFF
|
TERMINATE_FRAME_READ_WRITE = 0xFF
|
||||||
|
|
||||||
NO_ROTATION Rotation = 0
|
NO_ROTATION = drivers.Rotation0
|
||||||
ROTATION_90 Rotation = 1 // 90 degrees clock-wise rotation
|
ROTATION_90 = drivers.Rotation90 // 90 degrees clock-wise rotation
|
||||||
ROTATION_180 Rotation = 2
|
ROTATION_180 = drivers.Rotation180
|
||||||
ROTATION_270 Rotation = 3
|
ROTATION_270 = drivers.Rotation270
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -251,7 +251,7 @@ func writeGoWrapper(f *os.File, arch string, megahertz int) error {
|
|||||||
fmt.Fprintf(buf, " portClear, maskClear := d.Pin.PortMaskClear()\n")
|
fmt.Fprintf(buf, " portClear, maskClear := d.Pin.PortMaskClear()\n")
|
||||||
fmt.Fprintf(buf, "\n")
|
fmt.Fprintf(buf, "\n")
|
||||||
fmt.Fprintf(buf, " mask := interrupt.Disable()\n")
|
fmt.Fprintf(buf, " mask := interrupt.Disable()\n")
|
||||||
fmt.Fprintf(buf, " C.ws2812_writeByte%d(C.char(c), (*uint32)(unsafe.Pointer(portSet)), (*uint32)(unsafe.Pointer(portClear)), maskSet, maskClear)\n", megahertz)
|
fmt.Fprintf(buf, " C.ws2812_writeByte%d(C.char(c), (*C.uint32_t)(unsafe.Pointer(portSet)), (*C.uint32_t)(unsafe.Pointer(portClear)), C.uint32_t(maskSet), C.uint32_t(maskClear))\n", megahertz)
|
||||||
buf.WriteString(`
|
buf.WriteString(`
|
||||||
interrupt.Restore(mask)
|
interrupt.Restore(mask)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1328,7 +1328,7 @@ func (d Device) writeByte16(c byte) {
|
|||||||
portClear, maskClear := d.Pin.PortMaskClear()
|
portClear, maskClear := d.Pin.PortMaskClear()
|
||||||
|
|
||||||
mask := interrupt.Disable()
|
mask := interrupt.Disable()
|
||||||
C.ws2812_writeByte16(C.char(c), (*uint32)(unsafe.Pointer(portSet)), (*uint32)(unsafe.Pointer(portClear)), maskSet, maskClear)
|
C.ws2812_writeByte16(C.char(c), (*C.uint32_t)(unsafe.Pointer(portSet)), (*C.uint32_t)(unsafe.Pointer(portClear)), C.uint32_t(maskSet), C.uint32_t(maskClear))
|
||||||
|
|
||||||
interrupt.Restore(mask)
|
interrupt.Restore(mask)
|
||||||
}
|
}
|
||||||
@@ -1338,7 +1338,7 @@ func (d Device) writeByte48(c byte) {
|
|||||||
portClear, maskClear := d.Pin.PortMaskClear()
|
portClear, maskClear := d.Pin.PortMaskClear()
|
||||||
|
|
||||||
mask := interrupt.Disable()
|
mask := interrupt.Disable()
|
||||||
C.ws2812_writeByte48(C.char(c), (*uint32)(unsafe.Pointer(portSet)), (*uint32)(unsafe.Pointer(portClear)), maskSet, maskClear)
|
C.ws2812_writeByte48(C.char(c), (*C.uint32_t)(unsafe.Pointer(portSet)), (*C.uint32_t)(unsafe.Pointer(portClear)), C.uint32_t(maskSet), C.uint32_t(maskClear))
|
||||||
|
|
||||||
interrupt.Restore(mask)
|
interrupt.Restore(mask)
|
||||||
}
|
}
|
||||||
@@ -1348,7 +1348,7 @@ func (d Device) writeByte64(c byte) {
|
|||||||
portClear, maskClear := d.Pin.PortMaskClear()
|
portClear, maskClear := d.Pin.PortMaskClear()
|
||||||
|
|
||||||
mask := interrupt.Disable()
|
mask := interrupt.Disable()
|
||||||
C.ws2812_writeByte64(C.char(c), (*uint32)(unsafe.Pointer(portSet)), (*uint32)(unsafe.Pointer(portClear)), maskSet, maskClear)
|
C.ws2812_writeByte64(C.char(c), (*C.uint32_t)(unsafe.Pointer(portSet)), (*C.uint32_t)(unsafe.Pointer(portClear)), C.uint32_t(maskSet), C.uint32_t(maskClear))
|
||||||
|
|
||||||
interrupt.Restore(mask)
|
interrupt.Restore(mask)
|
||||||
}
|
}
|
||||||
@@ -1358,7 +1358,7 @@ func (d Device) writeByte120(c byte) {
|
|||||||
portClear, maskClear := d.Pin.PortMaskClear()
|
portClear, maskClear := d.Pin.PortMaskClear()
|
||||||
|
|
||||||
mask := interrupt.Disable()
|
mask := interrupt.Disable()
|
||||||
C.ws2812_writeByte120(C.char(c), (*uint32)(unsafe.Pointer(portSet)), (*uint32)(unsafe.Pointer(portClear)), maskSet, maskClear)
|
C.ws2812_writeByte120(C.char(c), (*C.uint32_t)(unsafe.Pointer(portSet)), (*C.uint32_t)(unsafe.Pointer(portClear)), C.uint32_t(maskSet), C.uint32_t(maskClear))
|
||||||
|
|
||||||
interrupt.Restore(mask)
|
interrupt.Restore(mask)
|
||||||
}
|
}
|
||||||
@@ -1368,7 +1368,7 @@ func (d Device) writeByte125(c byte) {
|
|||||||
portClear, maskClear := d.Pin.PortMaskClear()
|
portClear, maskClear := d.Pin.PortMaskClear()
|
||||||
|
|
||||||
mask := interrupt.Disable()
|
mask := interrupt.Disable()
|
||||||
C.ws2812_writeByte125(C.char(c), (*uint32)(unsafe.Pointer(portSet)), (*uint32)(unsafe.Pointer(portClear)), maskSet, maskClear)
|
C.ws2812_writeByte125(C.char(c), (*C.uint32_t)(unsafe.Pointer(portSet)), (*C.uint32_t)(unsafe.Pointer(portClear)), C.uint32_t(maskSet), C.uint32_t(maskClear))
|
||||||
|
|
||||||
interrupt.Restore(mask)
|
interrupt.Restore(mask)
|
||||||
}
|
}
|
||||||
@@ -1378,7 +1378,7 @@ func (d Device) writeByte168(c byte) {
|
|||||||
portClear, maskClear := d.Pin.PortMaskClear()
|
portClear, maskClear := d.Pin.PortMaskClear()
|
||||||
|
|
||||||
mask := interrupt.Disable()
|
mask := interrupt.Disable()
|
||||||
C.ws2812_writeByte168(C.char(c), (*uint32)(unsafe.Pointer(portSet)), (*uint32)(unsafe.Pointer(portClear)), maskSet, maskClear)
|
C.ws2812_writeByte168(C.char(c), (*C.uint32_t)(unsafe.Pointer(portSet)), (*C.uint32_t)(unsafe.Pointer(portClear)), C.uint32_t(maskSet), C.uint32_t(maskClear))
|
||||||
|
|
||||||
interrupt.Restore(mask)
|
interrupt.Restore(mask)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1114,7 +1114,7 @@ func (d Device) writeByte160(c byte) {
|
|||||||
portClear, maskClear := d.Pin.PortMaskClear()
|
portClear, maskClear := d.Pin.PortMaskClear()
|
||||||
|
|
||||||
mask := interrupt.Disable()
|
mask := interrupt.Disable()
|
||||||
C.ws2812_writeByte160(C.char(c), (*uint32)(unsafe.Pointer(portSet)), (*uint32)(unsafe.Pointer(portClear)), maskSet, maskClear)
|
C.ws2812_writeByte160(C.char(c), (*C.uint32_t)(unsafe.Pointer(portSet)), (*C.uint32_t)(unsafe.Pointer(portClear)), C.uint32_t(maskSet), C.uint32_t(maskClear))
|
||||||
|
|
||||||
interrupt.Restore(mask)
|
interrupt.Restore(mask)
|
||||||
}
|
}
|
||||||
@@ -1124,7 +1124,7 @@ func (d Device) writeByte320(c byte) {
|
|||||||
portClear, maskClear := d.Pin.PortMaskClear()
|
portClear, maskClear := d.Pin.PortMaskClear()
|
||||||
|
|
||||||
mask := interrupt.Disable()
|
mask := interrupt.Disable()
|
||||||
C.ws2812_writeByte320(C.char(c), (*uint32)(unsafe.Pointer(portSet)), (*uint32)(unsafe.Pointer(portClear)), maskSet, maskClear)
|
C.ws2812_writeByte320(C.char(c), (*C.uint32_t)(unsafe.Pointer(portSet)), (*C.uint32_t)(unsafe.Pointer(portClear)), C.uint32_t(maskSet), C.uint32_t(maskClear))
|
||||||
|
|
||||||
interrupt.Restore(mask)
|
interrupt.Restore(mask)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ func (d Device) WriteByte(c byte) error {
|
|||||||
|
|
||||||
switch machine.CPUFrequency() {
|
switch machine.CPUFrequency() {
|
||||||
case 16e6: // 16MHz
|
case 16e6: // 16MHz
|
||||||
C.ws2812_writeByte16(C.char(c), (*uint8)(unsafe.Pointer(port)), maskSet, maskClear)
|
C.ws2812_writeByte16(C.char(c), (*C.uint8_t)(unsafe.Pointer(port)), C.uint8_t(maskSet), C.uint8_t(maskClear))
|
||||||
interrupt.Restore(mask)
|
interrupt.Restore(mask)
|
||||||
return nil
|
return nil
|
||||||
default:
|
default:
|
||||||
|
|||||||
Reference in New Issue
Block a user