Compare commits

..

23 Commits

Author SHA1 Message Date
Ayke van Laethem 5d1b0c39cc gbadisplay: add simple driver for the GameBoy Advance display
This only implements the 15 bits per pixel mode, not any of the other
possible display modes. This matches conventional SPI displays most
closely. Future additions might add more display modes.

Normally I'd argue interfacing with chip/board specific hardware should
be done in the machine package using a generic interface, but the GBA is
kind of special and I don't want the machine package to depend on the
tinygo.org/x/drivers/pixel package.
2023-11-23 22:39:33 +01:00
Ayke van Laethem 6cf07aff4c pyportal_boing: update to use DrawBitmap
Replace DrawRGBBitmap8 with DrawBitmap, following the change in the
previous commit.

This improves performance from 86fps to 100fps! I didn't investigate
why, but I suspect it's because it now needs only a single store instead
of two to update a pixel.
2023-11-23 17:27:00 +01:00
Ayke van Laethem af33129f41 ili9341: st7735: st7789: add DrawBitmap method
This adds a new DrawBitmap method, which is meant to replace
DrawRGBBitmap8.
2023-11-23 17:27:00 +01:00
Ayke van Laethem 100aadf585 st7789: make the display generic over RGB565 and RGB444
Same as for st7735 in the previous commit.

In addition, this avoids allocating a big chunk of memory on _every_
draw operation (even SetPixel) and instead reuses it across draw
operations. This makes the driver a whole lot more efficient.
2023-11-23 17:27:00 +01:00
Ayke van Laethem 4a9667ffef st7735: make the display generic over RGB565 and RGB444
Using RGB444 instead of RGB565 can speed up graphics operations by up to
25%, especially on slow screens. But for full support, all parts of the
driver need to be aware of the color format.

It's possible to do this using a regular configuration variable, but
it's unlikely to be very efficient. Hence the usage of generics.
2023-11-23 17:27:00 +01:00
Ayke van Laethem f96a70915e pixel: add package for efficiently working with raw pixel buffers
This has been optimized for working with SPI displays like the ST7789.
By working directly in the native color format of the display, graphics
operations can be much, _much_ faster.

Also, this makes it easier to use a different color format like RGB444
simply by changing the generic type.
2023-11-23 17:27:00 +01:00
Ayke van Laethem 3e64e754a2 epd2in13: use better black/white approximation
The previoius behavior was that entirely black pixels were treated as
white, and anything else as black. That's at least counter-intuitive.
This patch changes the behavior to actually look at the color values and
use a cutoff around medium gray: darker colors are treated as black, and
lighter colors are treated as white.

This is a backwards incompatible change, but I think this behavior makes
a lot more sense.
2023-11-09 10:07:19 +01:00
Ayke van Laethem 2c2da5c7bb epd2in13: add Sleep method like other displays
For details, see: https://github.com/tinygo-org/drivers/pull/548
2023-11-07 10:39:14 +01:00
Ayke van Laethem 14994a3f31 epd2in13: unify rotation configuration with other displays
This commit updates rotation behavior to match other displays.
For more information, see: https://github.com/tinygo-org/drivers/pull/550

This changes the signature of `SetRotation` but I don't think any
existing code will be affected by this change.
2023-11-07 10:39:14 +01:00
deadprogram 47dfeb9e94 examples/lora/lorawan: modify atcmd and basic demo to support choosing
any one of the supported regions at compile time by using ldflags.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-10-26 08:32:04 +02:00
deadprogram f20d1759d3 examples/lora/lorawan: add missing functions for simulated interface
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-10-26 08:32:04 +02:00
deadprogram 6b79a1b386 lora/lorawan: refactor shared functionality for ChannelUS, etc into embedded type
named channel, do the same for RegionSettings, and then change RegionSettings to
just Settings to avoid the redundant naming.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-10-26 08:32:04 +02:00
Emilio Garcia 7dd1067cc1 LoRa WAN add setter functions 2023-10-26 08:32:04 +02:00
Emilio Garcia 4edb771c5b fix lora us915 logic to step into 500 kHz range 2023-10-26 08:32:04 +02:00
Mirac Kara e95cfe66c1 LoRa WAN US915 Support
Adds a driver for us 915 protocol to the lora WAN drivers.
2023-10-26 08:32:04 +02:00
Thomas Richner 4edb58c0c5 fix-tests: fix broken testrunner 2023-10-25 09:57:40 +02:00
Thomas Richner f384e2db48 fix-tests: migrated legacy I2C 2023-10-25 09:57:40 +02:00
sago35 7c96387845 sh1106: fix I2C interface and add smoketest 2023-10-14 11:43:06 +02:00
Christian Ege 182d4c6ebb sh1106: fixed the description of the device struct
It looks like there was an copy paste left over. This is corrected to be
concise.

Signed-off-by: Christian Ege <ch@ege.io>
2023-10-09 08:59:44 +02:00
Thomas Richner dc2de4f9ed adafruit4650: support for Adafruite 4650 feather OLED 2023-10-06 09:03:32 +02:00
Christian Ege 36a12dd7a2 at24cx: fixed the description of the device struct
It looks like there was an copy paste left over. This is corrected to be
concise.

Signed-off-by: Christian Ege <ch@ege.io>
2023-10-06 08:44:12 +02:00
Christian Ege 00992756eb ds3231: fix the description in the example
It looks like there was a copy paste leftover. This got corrected to be
concise.

Signed-off-by: Christian Ege <ch@ege.io>
2023-10-06 08:44:12 +02:00
Ayke van Laethem 715c33d4b0 all: prepare for CGo changes in TinyGo
For details, see: https://github.com/tinygo-org/tinygo/pull/3927
This change just means we need to be more careful to use the right type,
now that types like C.uint32_t don't match to the Go equivalent (like
uint32).
2023-10-05 10:01:25 +02:00
39 changed files with 1713 additions and 541 deletions
+196
View File
@@ -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
}
}
+176
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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)
+42
View File
@@ -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})
}
+13 -22
View File
@@ -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 -1
View File
@@ -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 (
+23 -22
View File
@@ -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)
} }
} }
+4 -4
View File
@@ -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
+12 -1
View File
@@ -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 {
+4 -4
View File
@@ -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
``` ```
+14 -3
View File
@@ -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()
+12 -7
View File
@@ -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()
+85
View File
@@ -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
}
+15
View File
@@ -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()
+3
View File
@@ -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
View File
@@ -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)
+21 -23
View File
@@ -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
} }
+42
View File
@@ -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 }
+19 -25
View File
@@ -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
} }
-16
View File
@@ -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
}
+25
View File
@@ -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
}
+82
View File
@@ -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
View File
@@ -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)
}
}
+64
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
} }
} }
+2
View File
@@ -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
View File
@@ -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
View File
@@ -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))
}
+41 -19
View File
@@ -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
+6 -4
View File
@@ -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
) )
+1 -1
View File
@@ -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)
} }
+6 -6
View File
@@ -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)
} }
+2 -2
View File
@@ -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)
} }
+1 -1
View File
@@ -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: