mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-08-11 10:23:44 +00:00
Compare commits
23 Commits
| 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 |
@@ -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 |
+145
-224
@@ -8,16 +8,15 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"tinygo.org/x/drivers"
|
"tinygo.org/x/drivers"
|
||||||
|
"tinygo.org/x/drivers/internal/legacy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Device wraps an I2C connection to a APDS-9960 device.
|
// Device wraps an I2C connection to a APDS-9960 device.
|
||||||
type Device struct {
|
type Device struct {
|
||||||
bus drivers.I2C
|
bus drivers.I2C
|
||||||
_txerr error
|
|
||||||
gesture gestureData
|
|
||||||
buf [8]byte
|
|
||||||
Address uint8
|
Address uint8
|
||||||
mode uint8
|
mode uint8
|
||||||
|
gesture gestureData
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configuration for APDS-9960 device.
|
// Configuration for APDS-9960 device.
|
||||||
@@ -47,24 +46,15 @@ type gestureData struct {
|
|||||||
received bool
|
received bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// for enabling various device functions.
|
// for enabling various device function
|
||||||
type encfg uint8
|
type enableConfig struct {
|
||||||
|
GEN bool
|
||||||
// data := []byte{gen<<6 | pien<<5 | aien<<4 | wen<<3 | pen<<2 | aen<<1 | pon}
|
PIEN bool
|
||||||
const (
|
AIEN bool
|
||||||
enPON encfg = 1 << iota
|
WEN bool
|
||||||
enAEN
|
PEN bool
|
||||||
enPEN
|
AEN bool
|
||||||
enWEN
|
PON bool
|
||||||
enAIEN
|
|
||||||
enPIEN
|
|
||||||
enGEN
|
|
||||||
)
|
|
||||||
|
|
||||||
func (e encfg) write7bits(b []byte) {
|
|
||||||
for i := uint8(0); i < 7; i++ {
|
|
||||||
b[i] = byte(e>>(6-i)) & 1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new APDS-9960 connection. The I2C bus must already be
|
// New creates a new APDS-9960 connection. The I2C bus must already be
|
||||||
@@ -78,8 +68,9 @@ func New(bus drivers.I2C) Device {
|
|||||||
// Connected returns whether APDS-9960 has been found.
|
// Connected returns whether APDS-9960 has been found.
|
||||||
// It does a "who am I" request and checks the response.
|
// It does a "who am I" request and checks the response.
|
||||||
func (d *Device) Connected() bool {
|
func (d *Device) Connected() bool {
|
||||||
d.txNew()
|
data := []byte{0}
|
||||||
return d.txRead8(APDS9960_ID_REG) == 0xAB && d.txErr() == nil
|
legacy.ReadRegister(d.bus, d.Address, APDS9960_ID_REG, data)
|
||||||
|
return data[0] == 0xAB
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMode returns current engine mode
|
// GetMode returns current engine mode
|
||||||
@@ -88,82 +79,65 @@ func (d *Device) GetMode() uint8 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DisableAll turns off the device and all functions
|
// DisableAll turns off the device and all functions
|
||||||
func (d *Device) DisableAll() error {
|
func (d *Device) DisableAll() {
|
||||||
err := d.enable(0)
|
d.enable(enableConfig{})
|
||||||
if err != nil {
|
legacy.WriteRegister(d.bus, d.Address, APDS9960_GCONF4_REG, []byte{0x00})
|
||||||
return err
|
d.mode = MODE_NONE
|
||||||
}
|
d.gesture.detected = GESTURE_NONE
|
||||||
d.txWrite8(APDS9960_GCONF4_REG, 0)
|
|
||||||
err = d.txErr()
|
|
||||||
if err == nil {
|
|
||||||
d.mode = MODE_NONE
|
|
||||||
d.gesture.detected = GESTURE_NONE
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetProximityPulse sets proximity pulse length (4, 8, 16, 32) and count (1..64)
|
// SetProximityPulse sets proximity pulse length (4, 8, 16, 32) and count (1~64)
|
||||||
// default: 16, 64
|
// default: 16, 64
|
||||||
func (d *Device) SetProximityPulse(length, count uint8) error {
|
func (d *Device) SetProximityPulse(length, count uint8) {
|
||||||
d.txNew()
|
legacy.WriteRegister(d.bus, d.Address, APDS9960_PPULSE_REG, []byte{getPulseLength(length)<<6 | getPulseCount(count)})
|
||||||
d.txWrite8(APDS9960_PPULSE_REG, getPulseLength(length)<<6|getPulseCount(count))
|
|
||||||
return d.txErr()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetGesturePulse sets gesture pulse length (4, 8, 16, 32) and count (1..64)
|
// SetGesturePulse sets gesture pulse length (4, 8, 16, 32) and count (1~64)
|
||||||
// default: 16, 64
|
// default: 16, 64
|
||||||
func (d *Device) SetGesturePulse(length, count uint8) error {
|
func (d *Device) SetGesturePulse(length, count uint8) {
|
||||||
d.txNew()
|
legacy.WriteRegister(d.bus, d.Address, APDS9960_GPULSE_REG, []byte{getPulseLength(length)<<6 | getPulseCount(count)})
|
||||||
d.txWrite8(APDS9960_GPULSE_REG, getPulseLength(length)<<6|getPulseCount(count))
|
|
||||||
return d.txErr()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetADCIntegrationCycles sets ALS/color ADC internal integration cycles (1..256, 1 cycle = 2.78 ms)
|
// SetADCIntegrationCycles sets ALS/color ADC internal integration cycles (1~256, 1 cycle = 2.78 ms)
|
||||||
// default: 4 (approx. 10 ms)
|
// default: 4 (~10 ms)
|
||||||
func (d *Device) SetADCIntegrationCycles(cycles uint16) error {
|
func (d *Device) SetADCIntegrationCycles(cycles uint16) {
|
||||||
if cycles > 256 {
|
if cycles > 256 {
|
||||||
cycles = 256
|
cycles = 256
|
||||||
}
|
}
|
||||||
d.txNew()
|
legacy.WriteRegister(d.bus, d.Address, APDS9960_ATIME_REG, []byte{uint8(256 - cycles)})
|
||||||
d.txWrite8(APDS9960_ATIME_REG, uint8(256-cycles))
|
|
||||||
return d.txErr()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetGains sets proximity/gesture gain (1, 2, 4, 8x) and ALS/color gain (1, 4, 16, 64x)
|
// SetGains sets proximity/gesture gain (1, 2, 4, 8x) and ALS/color gain (1, 4, 16, 64x)
|
||||||
// default: 1, 1, 4
|
// default: 1, 1, 4
|
||||||
func (d *Device) SetGains(proximityGain, gestureGain, colorGain uint8) error {
|
func (d *Device) SetGains(proximityGain, gestureGain, colorGain uint8) {
|
||||||
d.txNew()
|
legacy.WriteRegister(d.bus, d.Address, APDS9960_CONTROL_REG, []byte{getProximityGain(proximityGain)<<2 | getALSGain(colorGain)})
|
||||||
d.txWrite8(APDS9960_CONTROL_REG, getProximityGain(proximityGain)<<2|getALSGain(colorGain))
|
legacy.WriteRegister(d.bus, d.Address, APDS9960_GCONF2_REG, []byte{getProximityGain(gestureGain) << 5})
|
||||||
d.txWrite8(APDS9960_GCONF2_REG, getProximityGain(gestureGain)<<5)
|
|
||||||
return d.txErr()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LEDBoost sets proximity and gesture LED current level (100, 150, 200, 300 (%))
|
// LEDBoost sets proximity and gesture LED current level (100, 150, 200, 300 (%))
|
||||||
// default: 100
|
// default: 100
|
||||||
func (d *Device) LEDBoost(percent uint16) error {
|
func (d *Device) LEDBoost(percent uint16) {
|
||||||
var v uint8
|
var v uint8
|
||||||
switch {
|
switch percent {
|
||||||
case percent < 125:
|
case 100:
|
||||||
v = 0
|
v = 0
|
||||||
case percent < 175:
|
case 150:
|
||||||
v = 1
|
v = 1
|
||||||
case percent < 250:
|
case 200:
|
||||||
v = 2
|
v = 2
|
||||||
default:
|
case 300:
|
||||||
v = 3 // Maximum case.
|
v = 3
|
||||||
}
|
}
|
||||||
d.txNew()
|
legacy.WriteRegister(d.bus, d.Address, APDS9960_CONFIG2_REG, []byte{0x01 | v<<4})
|
||||||
d.txWrite8(APDS9960_CONFIG2_REG, 0x01|(v<<4))
|
|
||||||
return d.txErr()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setthreshold sets threshold (0..255) for detecting gestures
|
// Setthreshold sets threshold (0~255) for detecting gestures
|
||||||
// default: 30
|
// default: 30
|
||||||
func (d *Device) Setthreshold(t uint8) {
|
func (d *Device) Setthreshold(t uint8) {
|
||||||
d.gesture.threshold = t
|
d.gesture.threshold = t
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setsensitivity sets sensivity (0..100) for detecting gestures
|
// Setsensitivity sets sensivity (0~100) for detecting gestures
|
||||||
// default: 20
|
// default: 20
|
||||||
func (d *Device) Setsensitivity(s uint8) {
|
func (d *Device) Setsensitivity(s uint8) {
|
||||||
if s > 100 {
|
if s > 100 {
|
||||||
@@ -173,69 +147,47 @@ func (d *Device) Setsensitivity(s uint8) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// EnableProximity starts the proximity engine
|
// EnableProximity starts the proximity engine
|
||||||
func (d *Device) EnableProximity() error {
|
func (d *Device) EnableProximity() {
|
||||||
if d.mode != MODE_NONE {
|
if d.mode != MODE_NONE {
|
||||||
err := d.DisableAll()
|
d.DisableAll()
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
err := d.enable(enPON | enPEN | enWEN)
|
d.enable(enableConfig{PON: true, PEN: true, WEN: true})
|
||||||
if err == nil {
|
d.mode = MODE_PROXIMITY
|
||||||
d.mode = MODE_PROXIMITY
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Err returns the current error state of the device if encountered during I2C communication.
|
|
||||||
// After a call to Err the error is cleared.
|
|
||||||
func (d *Device) Err() error {
|
|
||||||
err := d.txErr()
|
|
||||||
d.txNew()
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProximityAvailable reports if proximity data is available
|
// ProximityAvailable reports if proximity data is available
|
||||||
func (d *Device) ProximityAvailable() bool {
|
func (d *Device) ProximityAvailable() bool {
|
||||||
if d.mode != MODE_PROXIMITY {
|
if d.mode == MODE_PROXIMITY && d.readStatus("PVALID") {
|
||||||
return false
|
return true
|
||||||
}
|
}
|
||||||
status, err := d.ReadStatus()
|
return false
|
||||||
return err == nil && status.PVALID()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadProximity reads proximity data (0..255)
|
// ReadProximity reads proximity data (0~255)
|
||||||
func (d *Device) ReadProximity() (proximity int32) {
|
func (d *Device) ReadProximity() (proximity int32) {
|
||||||
if d.mode != MODE_PROXIMITY {
|
if d.mode != MODE_PROXIMITY {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
d.txNew()
|
data := []byte{0}
|
||||||
val := d.txRead8(APDS9960_PDATA_REG)
|
legacy.ReadRegister(d.bus, d.Address, APDS9960_PDATA_REG, data)
|
||||||
return 255 - int32(val)
|
return 255 - int32(data[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
// EnableColor starts the color engine
|
// EnableColor starts the color engine
|
||||||
func (d *Device) EnableColor() (err error) {
|
func (d *Device) EnableColor() {
|
||||||
if d.mode != MODE_NONE {
|
if d.mode != MODE_NONE {
|
||||||
err = d.DisableAll()
|
d.DisableAll()
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
err = d.enable(enPON | enAEN | enWEN)
|
d.enable(enableConfig{PON: true, AEN: true, WEN: true})
|
||||||
if err == nil {
|
d.mode = MODE_COLOR
|
||||||
d.mode = MODE_COLOR
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ColorAvailable reports if color data is available
|
// ColorAvailable reports if color data is available
|
||||||
func (d *Device) ColorAvailable() bool {
|
func (d *Device) ColorAvailable() bool {
|
||||||
if d.mode != MODE_COLOR {
|
if d.mode == MODE_COLOR && d.readStatus("AVALID") {
|
||||||
return false
|
return true
|
||||||
}
|
}
|
||||||
status, err := d.ReadStatus()
|
return false
|
||||||
return err == nil && status.AVALID()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadColor reads color data (red, green, blue, clear color/brightness)
|
// ReadColor reads color data (red, green, blue, clear color/brightness)
|
||||||
@@ -243,36 +195,28 @@ func (d *Device) ReadColor() (r int32, g int32, b int32, clear int32) {
|
|||||||
if d.mode != MODE_COLOR {
|
if d.mode != MODE_COLOR {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
d.txNew()
|
data := []byte{0, 0, 0, 0, 0, 0, 0, 0}
|
||||||
data := d.buf[:8]
|
legacy.ReadRegister(d.bus, d.Address, APDS9960_CDATAL_REG, data[:1])
|
||||||
const numLowRegs = APDS9960_GDATAH_REG - APDS9960_CDATAL_REG + 1
|
legacy.ReadRegister(d.bus, d.Address, APDS9960_CDATAH_REG, data[1:2])
|
||||||
for i := uint8(0); i < numLowRegs; i++ {
|
legacy.ReadRegister(d.bus, d.Address, APDS9960_RDATAL_REG, data[2:3])
|
||||||
data[i] = d.txRead8(i + APDS9960_CDATAL_REG)
|
legacy.ReadRegister(d.bus, d.Address, APDS9960_RDATAH_REG, data[3:4])
|
||||||
}
|
legacy.ReadRegister(d.bus, d.Address, APDS9960_GDATAL_REG, data[4:5])
|
||||||
data[numLowRegs] = d.txRead8(APDS9960_BDATAL_REG)
|
legacy.ReadRegister(d.bus, d.Address, APDS9960_GDATAH_REG, data[5:6])
|
||||||
data[numLowRegs+1] = d.txRead8(APDS9960_BDATAH_REG)
|
legacy.ReadRegister(d.bus, d.Address, APDS9960_BDATAL_REG, data[6:7])
|
||||||
if d.txErr() != nil {
|
legacy.ReadRegister(d.bus, d.Address, APDS9960_BDATAH_REG, data[7:])
|
||||||
return
|
|
||||||
}
|
|
||||||
clear = int32(uint16(data[1])<<8 | uint16(data[0]))
|
clear = int32(uint16(data[1])<<8 | uint16(data[0]))
|
||||||
r = int32(uint16(data[3])<<8 | uint16(data[2]))
|
r = int32(uint16(data[3])<<8 | uint16(data[2]))
|
||||||
g = int32(uint16(data[5])<<8 | uint16(data[4]))
|
g = int32(uint16(data[5])<<8 | uint16(data[4]))
|
||||||
b = int32(uint16(data[7])<<8 | uint16(data[6]))
|
b = int32(uint16(data[7])<<8 | uint16(data[6]))
|
||||||
return r, g, b, clear
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// EnableGesture starts the gesture engine
|
// EnableGesture starts the gesture engine
|
||||||
func (d *Device) EnableGesture() error {
|
func (d *Device) EnableGesture() {
|
||||||
if d.mode != MODE_NONE {
|
if d.mode != MODE_NONE {
|
||||||
err := d.DisableAll()
|
d.DisableAll()
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
err := d.enable(enPON | enPEN | enGEN | enWEN)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
d.enable(enableConfig{PON: true, PEN: true, GEN: true, WEN: true})
|
||||||
d.mode = MODE_GESTURE
|
d.mode = MODE_GESTURE
|
||||||
d.gesture.detected = GESTURE_NONE
|
d.gesture.detected = GESTURE_NONE
|
||||||
d.gesture.gXDelta = 0
|
d.gesture.gXDelta = 0
|
||||||
@@ -280,7 +224,6 @@ func (d *Device) EnableGesture() error {
|
|||||||
d.gesture.gXPrevDelta = 0
|
d.gesture.gXPrevDelta = 0
|
||||||
d.gesture.gYPrevDelta = 0
|
d.gesture.gYPrevDelta = 0
|
||||||
d.gesture.received = false
|
d.gesture.received = false
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GestureAvailable reports if gesture data is available
|
// GestureAvailable reports if gesture data is available
|
||||||
@@ -288,26 +231,29 @@ func (d *Device) GestureAvailable() bool {
|
|||||||
if d.mode != MODE_GESTURE {
|
if d.mode != MODE_GESTURE {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
d.txNew()
|
|
||||||
gstatus := d.txRead8(APDS9960_GSTATUS_REG)
|
data := []byte{0, 0, 0, 0}
|
||||||
if gstatus&1 == 0 {
|
|
||||||
|
// check GVALID
|
||||||
|
legacy.ReadRegister(d.bus, d.Address, APDS9960_GSTATUS_REG, data[:1])
|
||||||
|
if data[0]&0x01 == 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
availableDataSets := d.txRead8(APDS9960_GFLVL_REG)
|
|
||||||
|
// get number of data sets available in FIFO
|
||||||
|
legacy.ReadRegister(d.bus, d.Address, APDS9960_GFLVL_REG, data[:1])
|
||||||
|
availableDataSets := data[0]
|
||||||
if availableDataSets == 0 {
|
if availableDataSets == 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
data := d.buf[:]
|
|
||||||
// read up, down, left and right proximity data from FIFO
|
// read up, down, left and right proximity data from FIFO
|
||||||
var dataSets [32][4]uint8
|
var dataSets [32][4]uint8
|
||||||
const numAddrs = APDS9960_GFIFO_R_REG - APDS9960_GFIFO_U_REG + 1
|
|
||||||
for i := uint8(0); i < availableDataSets; i++ {
|
for i := uint8(0); i < availableDataSets; i++ {
|
||||||
for j := uint8(0); j < numAddrs; j++ {
|
legacy.ReadRegister(d.bus, d.Address, APDS9960_GFIFO_U_REG, data[:1])
|
||||||
data[j] = d.txRead8(j + APDS9960_GFIFO_U_REG)
|
legacy.ReadRegister(d.bus, d.Address, APDS9960_GFIFO_D_REG, data[1:2])
|
||||||
}
|
legacy.ReadRegister(d.bus, d.Address, APDS9960_GFIFO_L_REG, data[2:3])
|
||||||
if d.txErr() != nil {
|
legacy.ReadRegister(d.bus, d.Address, APDS9960_GFIFO_R_REG, data[3:4])
|
||||||
return false
|
|
||||||
}
|
|
||||||
for j := uint8(0); j < 4; j++ {
|
for j := uint8(0); j < 4; j++ {
|
||||||
dataSets[i][j] = data[j]
|
dataSets[i][j] = data[j]
|
||||||
}
|
}
|
||||||
@@ -369,11 +315,9 @@ func (d *Device) ReadGesture() (gesture int32) {
|
|||||||
|
|
||||||
// private functions
|
// private functions
|
||||||
|
|
||||||
func (d *Device) configureDevice(cfg Configuration) error {
|
func (d *Device) configureDevice(cfg Configuration) {
|
||||||
err := d.DisableAll() // turn off everything
|
d.DisableAll() // turn off everything
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// "default" settings
|
// "default" settings
|
||||||
if cfg.ProximityPulseLength == 0 {
|
if cfg.ProximityPulseLength == 0 {
|
||||||
cfg.ProximityPulseLength = 16
|
cfg.ProximityPulseLength = 16
|
||||||
@@ -406,92 +350,69 @@ func (d *Device) configureDevice(cfg Configuration) error {
|
|||||||
d.gesture.sensitivity = 20
|
d.gesture.sensitivity = 20
|
||||||
}
|
}
|
||||||
|
|
||||||
err = d.SetProximityPulse(cfg.ProximityPulseLength, cfg.ProximityPulseCount)
|
d.SetProximityPulse(cfg.ProximityPulseLength, cfg.ProximityPulseCount)
|
||||||
if err != nil {
|
d.SetGesturePulse(cfg.GesturePulseLength, cfg.GesturePulseCount)
|
||||||
return err
|
d.SetGains(cfg.ProximityGain, cfg.GestureGain, cfg.ColorGain)
|
||||||
|
d.SetADCIntegrationCycles(cfg.ADCIntegrationCycles)
|
||||||
|
|
||||||
|
if cfg.LEDBoost > 0 {
|
||||||
|
d.LEDBoost(cfg.LEDBoost)
|
||||||
}
|
}
|
||||||
err = d.SetGesturePulse(cfg.GesturePulseLength, cfg.GesturePulseCount)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = d.SetGains(cfg.ProximityGain, cfg.GestureGain, cfg.ColorGain)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = d.SetADCIntegrationCycles(cfg.ADCIntegrationCycles)
|
|
||||||
if err == nil && cfg.LEDBoost > 0 {
|
|
||||||
err = d.LEDBoost(cfg.LEDBoost)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Device) enable(cfg encfg) error {
|
func (d *Device) enable(cfg enableConfig) {
|
||||||
d.txNew()
|
var gen, pien, aien, wen, pen, aen, pon uint8
|
||||||
cfg.write7bits(d.buf[:7])
|
|
||||||
d.txWrite(APDS9960_ENABLE_REG, d.buf[:7])
|
if cfg.GEN {
|
||||||
err := d.txErr()
|
gen = 1
|
||||||
if err == nil && cfg&enPON != 0 {
|
}
|
||||||
|
if cfg.PIEN {
|
||||||
|
pien = 1
|
||||||
|
}
|
||||||
|
if cfg.AIEN {
|
||||||
|
aien = 1
|
||||||
|
}
|
||||||
|
if cfg.WEN {
|
||||||
|
wen = 1
|
||||||
|
}
|
||||||
|
if cfg.PEN {
|
||||||
|
pen = 1
|
||||||
|
}
|
||||||
|
if cfg.AEN {
|
||||||
|
aen = 1
|
||||||
|
}
|
||||||
|
if cfg.PON {
|
||||||
|
pon = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
data := []byte{gen<<6 | pien<<5 | aien<<4 | wen<<3 | pen<<2 | aen<<1 | pon}
|
||||||
|
legacy.WriteRegister(d.bus, d.Address, APDS9960_ENABLE_REG, data)
|
||||||
|
|
||||||
|
if cfg.PON {
|
||||||
time.Sleep(time.Millisecond * 10)
|
time.Sleep(time.Millisecond * 10)
|
||||||
}
|
}
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Device) txErr() error { return d._txerr }
|
func (d *Device) readStatus(param string) bool {
|
||||||
|
data := []byte{0}
|
||||||
|
legacy.ReadRegister(d.bus, d.Address, APDS9960_STATUS_REG, data)
|
||||||
|
|
||||||
func (d *Device) txNew() { d._txerr = nil }
|
switch param {
|
||||||
|
case "CPSAT":
|
||||||
func (d *Device) txRead8(addr uint8) uint8 {
|
return data[0]>>7&0x01 == 1
|
||||||
if d._txerr != nil {
|
case "PGSAT":
|
||||||
return 0
|
return data[0]>>6&0x01 == 1
|
||||||
|
case "PINT":
|
||||||
|
return data[0]>>5&0x01 == 1
|
||||||
|
case "AINT":
|
||||||
|
return data[0]>>4&0x01 == 1
|
||||||
|
case "PVALID":
|
||||||
|
return data[0]>>1&0x01 == 1
|
||||||
|
case "AVALID":
|
||||||
|
return data[0]&0x01 == 1
|
||||||
|
default:
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
d.buf[0] = addr
|
|
||||||
d._txerr = d.bus.Tx(uint16(d.Address), d.buf[:1], d.buf[1:2])
|
|
||||||
return d.buf[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Device) txWrite8(addr uint8, val uint8) {
|
|
||||||
if d._txerr != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
d.buf[0] = addr
|
|
||||||
d.buf[1] = val
|
|
||||||
d._txerr = d.bus.Tx(uint16(d.Address), d.buf[:2], nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Device) txWrite(addr uint8, data []byte) {
|
|
||||||
if d._txerr != nil {
|
|
||||||
return
|
|
||||||
} else if len(data) > len(d.buf)-1 {
|
|
||||||
panic("txWrite: data too long")
|
|
||||||
}
|
|
||||||
d.buf[0] = addr
|
|
||||||
copy(d.buf[1:], data)
|
|
||||||
d._txerr = d.bus.Tx(uint16(d.Address), d.buf[:len(data)+1], nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
type status uint8
|
|
||||||
|
|
||||||
const (
|
|
||||||
statusAVALID status = 1 << iota
|
|
||||||
statusPVALID
|
|
||||||
_
|
|
||||||
_
|
|
||||||
statusAINT
|
|
||||||
statusPINT
|
|
||||||
statusPGSAT
|
|
||||||
statusCPSAT
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s status) CPSAT() bool { return s&statusCPSAT != 0 }
|
|
||||||
func (s status) PGSAT() bool { return s&statusPGSAT != 0 }
|
|
||||||
func (s status) PINT() bool { return s&statusPINT != 0 }
|
|
||||||
func (s status) AINT() bool { return s&statusAINT != 0 }
|
|
||||||
func (s status) PVALID() bool { return s&statusPVALID != 0 }
|
|
||||||
func (s status) AVALID() bool { return s&statusAVALID != 0 }
|
|
||||||
|
|
||||||
func (d *Device) ReadStatus() (status, error) {
|
|
||||||
d.txNew()
|
|
||||||
return status(d.txRead8(APDS9960_STATUS_REG)), d.txErr()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getPulseLength(l uint8) uint8 {
|
func getPulseLength(l uint8) uint8 {
|
||||||
|
|||||||
+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})
|
||||||
|
}
|
||||||
@@ -8,42 +8,33 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// Sleep to catch any errors through the serial monitor.
|
|
||||||
time.Sleep(1000 * time.Millisecond)
|
|
||||||
bus := machine.I2C0
|
|
||||||
// use Nano 33 BLE Sense's internal I2C bus
|
|
||||||
err := bus.Configure(machine.I2CConfig{
|
|
||||||
SCL: machine.GP1,
|
|
||||||
SDA: machine.GP0,
|
|
||||||
Frequency: 400 * machine.KHz,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
panic(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
sensor := apds9960.New(bus)
|
// use Nano 33 BLE Sense's internal I2C bus
|
||||||
|
machine.I2C1.Configure(machine.I2CConfig{
|
||||||
|
SCL: machine.SCL1_PIN,
|
||||||
|
SDA: machine.SDA1_PIN,
|
||||||
|
Frequency: machine.TWI_FREQ_400KHZ,
|
||||||
|
})
|
||||||
|
|
||||||
|
sensor := apds9960.New(machine.I2C1)
|
||||||
|
|
||||||
// use default settings
|
// use default settings
|
||||||
sensor.Configure(apds9960.Configuration{})
|
sensor.Configure(apds9960.Configuration{})
|
||||||
|
|
||||||
if !sensor.Connected() {
|
if !sensor.Connected() {
|
||||||
println("APDS-9960 not connected!")
|
println("APDS-9960 not connected!")
|
||||||
println("err:", sensor.Err())
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
println("APDS connected!")
|
|
||||||
err = sensor.EnableProximity() // enable proximity engine
|
sensor.EnableProximity() // enable proximity engine
|
||||||
if err != nil {
|
|
||||||
panic(err.Error())
|
|
||||||
}
|
|
||||||
for {
|
for {
|
||||||
|
|
||||||
if sensor.ProximityAvailable() {
|
if sensor.ProximityAvailable() {
|
||||||
p := sensor.ReadProximity()
|
p := sensor.ReadProximity()
|
||||||
println("Proximity:", p)
|
println("Proximity:", p)
|
||||||
}
|
}
|
||||||
if err := sensor.Err(); err != nil {
|
|
||||||
println(err.Error())
|
|
||||||
}
|
|
||||||
time.Sleep(time.Millisecond * 100)
|
time.Sleep(time.Millisecond * 100)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,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
|
||||||
|
}
|
||||||
@@ -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}},
|
||||||
|
}}
|
||||||
|
}
|
||||||
+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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+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
|
||||||
@@ -135,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))
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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