mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-08-18 21:53:56 +00:00
Add sharpmem (#724)
sharpmem: add implementation of sharpmem display driver * Implement Configure, Clear and ClearBuffer, add some tests, add documentation/comments * Reverse white * Inverted bits, fix and improve ClearBuffer, cleanup * Refine doc comment * Driver refactor, optimizations toggle, additional tests & support for all SKUs * Fix address overflow padding, add wire-level tests for assumed address encoding * Minor rename * Cleanup and doc fixes * Add device configs * Bounds check * Add example and smoketest entry * Use uf2 output file format * Refine example
This commit is contained in:
committed by
GitHub
parent
486949686c
commit
f57b5ecee9
@@ -0,0 +1,90 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image/color"
|
||||||
|
"machine"
|
||||||
|
"math/rand/v2"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"tinygo.org/x/drivers/sharpmem"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// example wiring using a nice!view and nice!nano:
|
||||||
|
// (view) (nano)
|
||||||
|
// MOSI --> P0.24
|
||||||
|
// SCK ---> P0.22
|
||||||
|
// GND ---> GND
|
||||||
|
// VCC ---> 3.3V
|
||||||
|
// CS ----> P0.06
|
||||||
|
|
||||||
|
spi = machine.SPI0
|
||||||
|
|
||||||
|
sckPin = machine.SPI0_SCK_PIN // SCK
|
||||||
|
sdoPin = machine.SPI0_SDO_PIN // MOSI
|
||||||
|
sdiPin = machine.SPI0_SDI_PIN // (any pin)
|
||||||
|
|
||||||
|
csPin = machine.P0_06 // CS
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
|
err := spi.Configure(machine.SPIConfig{
|
||||||
|
Frequency: 2000000,
|
||||||
|
SCK: sckPin,
|
||||||
|
SDO: sdoPin,
|
||||||
|
SDI: sdiPin,
|
||||||
|
Mode: 0,
|
||||||
|
LSBFirst: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
println("spi.Configure() failed, error:", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||||
|
|
||||||
|
display := sharpmem.New(spi, csPin)
|
||||||
|
|
||||||
|
cfg := sharpmem.ConfigLS011B7DH03
|
||||||
|
display.Configure(cfg)
|
||||||
|
|
||||||
|
// clear the display before first use
|
||||||
|
err = display.Clear()
|
||||||
|
if err != nil {
|
||||||
|
println("display.Clear() failed, error:", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// random boxes pop into and out of existence
|
||||||
|
for {
|
||||||
|
x0 := int16(rand.IntN(int(cfg.Width - 7)))
|
||||||
|
y0 := int16(rand.IntN(int(cfg.Height - 7)))
|
||||||
|
|
||||||
|
for x2 := int16(0); x2 < 16; x2++ {
|
||||||
|
x2 := x2
|
||||||
|
c := color.RGBA{R: 255, G: 255, B: 255, A: 255}
|
||||||
|
|
||||||
|
if x2 >= 8 {
|
||||||
|
// effectively erases the box after it showed up
|
||||||
|
x2 = x2 - 8
|
||||||
|
c = color.RGBA{R: 0, G: 0, B: 0, A: 255}
|
||||||
|
}
|
||||||
|
|
||||||
|
for x := int16(0); x < x2; x++ {
|
||||||
|
for y := int16(0); y < 8; y++ {
|
||||||
|
display.SetPixel(x0+x, y0+y, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = display.Display()
|
||||||
|
if err != nil {
|
||||||
|
println("display.Display() failed, error:", err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(33 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,374 @@
|
|||||||
|
package sharpmem
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"image/color"
|
||||||
|
|
||||||
|
"tinygo.org/x/drivers"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
bitWriteCmd uint8 = 0b00000001
|
||||||
|
bitVcom uint8 = 0b00000010
|
||||||
|
bitClear uint8 = 0b00000100
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ConfigLS010B7DH04 = Config{Width: 128, Height: 128}
|
||||||
|
ConfigLS011B7DH03 = Config{Width: 160, Height: 68}
|
||||||
|
ConfigLS012B7DD01 = Config{Width: 184, Height: 38}
|
||||||
|
ConfigLS013B7DH03 = ConfigLS010B7DH04
|
||||||
|
ConfigLS013B7DH05 = Config{Width: 144, Height: 168}
|
||||||
|
ConfigLS018B7DH02 = Config{Width: 230, Height: 303}
|
||||||
|
ConfigLS027B7DH01 = Config{Width: 400, Height: 240}
|
||||||
|
ConfigLS027B7DH01A = ConfigLS027B7DH01
|
||||||
|
ConfigLS032B7DD02 = Config{Width: 336, Height: 536}
|
||||||
|
ConfigLS044Q7DH01 = Config{Width: 320, Height: 240}
|
||||||
|
)
|
||||||
|
|
||||||
|
type Pin interface {
|
||||||
|
High()
|
||||||
|
Low()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Device represents a Sharp Memory Display device. This driver implementation
|
||||||
|
// concerns the 1-bit color versions only (black and white memory displays).
|
||||||
|
//
|
||||||
|
// Supported SKUs include:
|
||||||
|
// LS010B7DH04, LS011B7DH03, LS012B7DD01, LS013B7DH03, LS013B7DH05,
|
||||||
|
// LS018B7DH02, LS027B7DH01, LS027B7DH01A, LS032B7DD02, LS044Q7DH01
|
||||||
|
//
|
||||||
|
// Note: Only SKU LS011B7DH03 (160x68) has been tested as of writing.
|
||||||
|
//
|
||||||
|
// The driver includes optimizations (frame and per-line invalidation) that
|
||||||
|
// only transmit the changed lines to the display. These optimizations are on
|
||||||
|
// by default, and they can be disabled with the respective config option.
|
||||||
|
type Device struct {
|
||||||
|
bus drivers.SPI
|
||||||
|
csPin Pin
|
||||||
|
buffer []byte
|
||||||
|
txBuf []byte
|
||||||
|
lineDiff []byte
|
||||||
|
width int16
|
||||||
|
height int16
|
||||||
|
bufferSize int16
|
||||||
|
bytesPerLine int16
|
||||||
|
vcom uint8
|
||||||
|
diffing bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Width int16
|
||||||
|
Height int16
|
||||||
|
|
||||||
|
// DisableOptimizations disables frame and line invalidation optimizations.
|
||||||
|
// Useful if constant frame times are desired.
|
||||||
|
DisableOptimizations bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new device connection.
|
||||||
|
// The SPI bus must have already been configured.
|
||||||
|
func New(bus drivers.SPI, csPin Pin) Device {
|
||||||
|
d := Device{
|
||||||
|
bus: bus,
|
||||||
|
csPin: csPin,
|
||||||
|
}
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure initializes the display with specified configuration. It can be
|
||||||
|
// called multiple times on the same display, resetting its internal state.
|
||||||
|
func (d *Device) Configure(cfg Config) {
|
||||||
|
if cfg.Width == 0 {
|
||||||
|
cfg.Width = 160
|
||||||
|
}
|
||||||
|
if cfg.Height == 0 {
|
||||||
|
cfg.Height = 68
|
||||||
|
}
|
||||||
|
|
||||||
|
d.width = cfg.Width
|
||||||
|
d.height = cfg.Height
|
||||||
|
d.diffing = !cfg.DisableOptimizations
|
||||||
|
|
||||||
|
d.initialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialize properly initializes the display and the in-memory image buffers.
|
||||||
|
func (d *Device) initialize() {
|
||||||
|
d.csPin.Low()
|
||||||
|
|
||||||
|
// initialize VCOM as high
|
||||||
|
d.vcom = bitVcom
|
||||||
|
|
||||||
|
// bytesPerLine has to be 16-bit aligned, as some resolutions require
|
||||||
|
// padding to the nearest 2nd byte.
|
||||||
|
d.bytesPerLine = ceilDiv(d.width, 16) * 2
|
||||||
|
|
||||||
|
// preallocate a contiguous byte buffer for all lines, including
|
||||||
|
// protocol-required padding for each line apriori (easier to transfer).
|
||||||
|
d.bufferSize = d.bytesPerLine * d.height
|
||||||
|
d.buffer = make([]byte, d.bufferSize)
|
||||||
|
// A bit being 1 is white (reflective), 0 is black (less reflective).
|
||||||
|
for i := range d.buffer {
|
||||||
|
d.buffer[i] = 0xff
|
||||||
|
}
|
||||||
|
|
||||||
|
// auxiliary buffer for SPI transfers to avoid dynamic allocations
|
||||||
|
d.txBuf = make([]byte, 2)
|
||||||
|
|
||||||
|
if d.diffing {
|
||||||
|
// buffer to store the changed lines. First bit is whether any line has
|
||||||
|
// changed at all (i.e. the frame is invalid), followed by N bits,
|
||||||
|
// one for each line.
|
||||||
|
d.lineDiff = make([]byte, bitfieldBufLen(1+d.height))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPixel enables or disables a pixel in the buffer.
|
||||||
|
// color.RGBA{0, 0, 0, 255} is considered transparent (reflective, white),
|
||||||
|
// anything else will enable a pixel on the screen (make it appear less
|
||||||
|
// reflective, black).
|
||||||
|
func (d *Device) SetPixel(x, y int16, c color.RGBA) {
|
||||||
|
if d.width == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// bounds check
|
||||||
|
if x < 0 || x >= d.width || y < 0 || y >= d.height {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
offset := y * d.bytesPerLine
|
||||||
|
|
||||||
|
div := offset + x/8
|
||||||
|
mod := uint8(x % 8)
|
||||||
|
|
||||||
|
prev := hasBit(d.buffer[div], mod)
|
||||||
|
curr := c.R == 0 && c.G == 0 && c.B == 0 && c.A == 255
|
||||||
|
|
||||||
|
if prev == curr {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if curr {
|
||||||
|
d.buffer[div] = setBit(d.buffer[div], mod)
|
||||||
|
} else {
|
||||||
|
d.buffer[div] = unsetBit(d.buffer[div], mod)
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.diffing {
|
||||||
|
d.invalidateLine(y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size returns the current size of the display.
|
||||||
|
func (d *Device) Size() (x, y int16) {
|
||||||
|
return d.width, d.height
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display renders the buffer to the screen. It only transmits changed lines if
|
||||||
|
// optimizations are enabled. It should be called at >=1hz, even if the
|
||||||
|
// buffer hasn't been modified.
|
||||||
|
func (d *Device) Display() error {
|
||||||
|
if d.width == 0 {
|
||||||
|
return errors.New("display not configured")
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.diffing {
|
||||||
|
if !hasBit(d.lineDiff[0], 0) {
|
||||||
|
// no pixels have been modified, simply toggle VCOM
|
||||||
|
return d.holdDisplay()
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
for i := 0; i < len(d.lineDiff); i++ {
|
||||||
|
d.lineDiff[i] = 0x00
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := bitWriteCmd | d.vcom
|
||||||
|
|
||||||
|
d.toggleVcom()
|
||||||
|
|
||||||
|
// Padding to use for high bits of line numbers that overflow 8 bits.
|
||||||
|
var hiPad = uint8(0)
|
||||||
|
if d.height >= 512 {
|
||||||
|
hiPad = 3 + 3 // 3 mode bits + 3 low bits
|
||||||
|
} else if d.height >= 256 {
|
||||||
|
hiPad = 3 + 4 // 3 mode bits + 4 low bits
|
||||||
|
}
|
||||||
|
|
||||||
|
// start transfer
|
||||||
|
d.csPin.High()
|
||||||
|
|
||||||
|
for i := int16(0); i < d.height; i++ {
|
||||||
|
if d.diffing {
|
||||||
|
// Skip rendering lines that haven't changed.
|
||||||
|
linediv := (i + 1) / 8
|
||||||
|
linemod := uint8((i + 1) % 8)
|
||||||
|
if !hasBit(d.lineDiff[linediv], linemod) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The first 5 bits are either dummy or part of the current line
|
||||||
|
// (1-indexed) if it overflows 8-bits.
|
||||||
|
// The last 3 bits are the command for the first line and dummy bits
|
||||||
|
// for subsequent lines (set as command for simplicity)
|
||||||
|
hi := uint8((i + 1) >> 8)
|
||||||
|
hi = hi << hiPad
|
||||||
|
d.txBuf[0] = cmd | hi
|
||||||
|
|
||||||
|
// The second byte is the low bits of the current line (1-indexed).
|
||||||
|
// for <8 bits cases, the high bits are dummy, so we leave them as 0.
|
||||||
|
d.txBuf[1] = uint8(i + 1)
|
||||||
|
|
||||||
|
// send the first two bytes
|
||||||
|
err := d.bus.Tx(d.txBuf, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// send the line data
|
||||||
|
err = d.bus.Tx(d.buffer[i*d.bytesPerLine:(i+1)*d.bytesPerLine], nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trailer 16 bits (low)
|
||||||
|
d.txBuf[0] = 0x00
|
||||||
|
d.txBuf[1] = 0x00
|
||||||
|
err := d.bus.Tx(d.txBuf, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// end transfer
|
||||||
|
d.csPin.Low()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// holdDisplay simply toggles VCOM without updating any lines.
|
||||||
|
func (d *Device) holdDisplay() error {
|
||||||
|
d.txBuf[0] = d.vcom
|
||||||
|
d.txBuf[1] = 0x00
|
||||||
|
|
||||||
|
d.toggleVcom()
|
||||||
|
|
||||||
|
// begin transaction
|
||||||
|
d.csPin.High()
|
||||||
|
|
||||||
|
err := d.bus.Tx(d.txBuf, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// end transaction
|
||||||
|
d.csPin.Low()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear clears both the in-memory buffer and the display.
|
||||||
|
func (d *Device) Clear() error {
|
||||||
|
if d.width == 0 {
|
||||||
|
return errors.New("display not configured")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.ClearBuffer()
|
||||||
|
return d.ClearDisplay()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearBuffer clears the in-memory buffer. The display is not updated.
|
||||||
|
func (d *Device) ClearBuffer() {
|
||||||
|
if d.width == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.diffing {
|
||||||
|
// detect what rows need to be reset on the next render
|
||||||
|
d.invalidateModifiedLines()
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset the in-memory buffer
|
||||||
|
for i := 0; i < len(d.buffer); i++ {
|
||||||
|
d.buffer[i] = 0xff
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// invalidateModifiedLines marks any line that has at least a single black pixel
|
||||||
|
// as invalidated. Padding bits, if any, are always 1.
|
||||||
|
func (d *Device) invalidateModifiedLines() {
|
||||||
|
for y := int16(0); y < d.height; y++ {
|
||||||
|
offset := y * d.bytesPerLine
|
||||||
|
|
||||||
|
updateLine := false
|
||||||
|
for x := int16(0); x < d.width; x++ {
|
||||||
|
div := offset + x/8
|
||||||
|
mod := uint8(x % 8)
|
||||||
|
|
||||||
|
if !hasBit(d.buffer[div], mod) {
|
||||||
|
updateLine = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if updateLine {
|
||||||
|
d.invalidateLine(y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearDisplay clears the display. The in-memory buffer is not updated. A
|
||||||
|
// subsequent call to Display() will re-render the content as it was before
|
||||||
|
// clearing.
|
||||||
|
func (d *Device) ClearDisplay() error {
|
||||||
|
if d.width == 0 {
|
||||||
|
return errors.New("display not configured")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.txBuf[0] = d.vcom | bitClear
|
||||||
|
d.txBuf[1] = 0x00
|
||||||
|
|
||||||
|
d.toggleVcom()
|
||||||
|
|
||||||
|
// begin transaction
|
||||||
|
d.csPin.High()
|
||||||
|
|
||||||
|
err := d.bus.Tx(d.txBuf, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// end transaction
|
||||||
|
d.csPin.Low()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// invalidateLine marks a line and the frame itself as invalidated.
|
||||||
|
func (d *Device) invalidateLine(line int16) {
|
||||||
|
// mark the frame as invalidated
|
||||||
|
d.lineDiff[0] = setBit(d.lineDiff[0], 0)
|
||||||
|
|
||||||
|
// mark the line as invalidated
|
||||||
|
linediv := (line + 1) / 8
|
||||||
|
linemod := uint8((line + 1) % 8)
|
||||||
|
d.lineDiff[linediv] = setBit(d.lineDiff[linediv], linemod)
|
||||||
|
}
|
||||||
|
|
||||||
|
// toggleVcom toggles the VCOM, as is instructed by the datasheet.
|
||||||
|
// Toggling VCOM can help maintain the display's longevity. It should ideally
|
||||||
|
// be called at least once per second, preferably at 4-100 Hz.
|
||||||
|
// Toggling VCOM causes a tiny bit of flicker, but without it the pixels can
|
||||||
|
// be permanently damaged by the DC bias accumulating over time.
|
||||||
|
func (d *Device) toggleVcom() {
|
||||||
|
if d.vcom != 0 {
|
||||||
|
d.vcom = 0x00
|
||||||
|
} else {
|
||||||
|
d.vcom = bitVcom
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,225 @@
|
|||||||
|
package sharpmem
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image/color"
|
||||||
|
"math/rand/v2"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
qt "github.com/frankban/quicktest"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_setBit(t *testing.T) {
|
||||||
|
c := qt.New(t)
|
||||||
|
|
||||||
|
for i := uint8(0); i < 8; i++ {
|
||||||
|
v := uint8(1) << i
|
||||||
|
|
||||||
|
c.Assert(setBit(0x00, i), qt.Equals, v)
|
||||||
|
c.Assert(setBit(0x00, (i+1)%8), qt.Not(qt.Equals), v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_unsetBit(t *testing.T) {
|
||||||
|
c := qt.New(t)
|
||||||
|
|
||||||
|
for i := uint8(0); i < 8; i++ {
|
||||||
|
v := uint8(1) << i
|
||||||
|
|
||||||
|
c.Assert(unsetBit(v, i), qt.Equals, uint8(0x00))
|
||||||
|
c.Assert(unsetBit(v, (i+1)%8), qt.Not(qt.Equals), uint8(0x00))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_hasBit(t *testing.T) {
|
||||||
|
c := qt.New(t)
|
||||||
|
|
||||||
|
for i := uint8(0); i < 8; i++ {
|
||||||
|
v := uint8(1) << i
|
||||||
|
|
||||||
|
c.Assert(hasBit(v, i), qt.Equals, true)
|
||||||
|
c.Assert(hasBit(v, (i+1)%8), qt.Equals, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_bitfieldBufLen(t *testing.T) {
|
||||||
|
c := qt.New(t)
|
||||||
|
|
||||||
|
for i := int16(1); i < 536; i++ {
|
||||||
|
requiredBufferSize := i / 8
|
||||||
|
wouldOverflow := i % 8
|
||||||
|
|
||||||
|
if wouldOverflow > 0 {
|
||||||
|
requiredBufferSize += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Assert(bitfieldBufLen(i), qt.Equals, requiredBufferSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockBus struct {
|
||||||
|
b []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockBus) Tx(w, _ []byte) error {
|
||||||
|
m.b = append(m.b, w...)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockBus) Transfer(b byte) (byte, error) {
|
||||||
|
m.b = append(m.b, b)
|
||||||
|
return 0x00, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockPin struct{}
|
||||||
|
|
||||||
|
func (m mockPin) High() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m mockPin) Low() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_Device(t *testing.T) {
|
||||||
|
c := qt.New(t)
|
||||||
|
|
||||||
|
cfgs := []Config{
|
||||||
|
ConfigLS010B7DH04,
|
||||||
|
ConfigLS011B7DH03,
|
||||||
|
ConfigLS012B7DD01,
|
||||||
|
ConfigLS013B7DH03,
|
||||||
|
ConfigLS013B7DH05,
|
||||||
|
ConfigLS018B7DH02,
|
||||||
|
ConfigLS027B7DH01,
|
||||||
|
ConfigLS027B7DH01A,
|
||||||
|
ConfigLS032B7DD02,
|
||||||
|
ConfigLS044Q7DH01,
|
||||||
|
}
|
||||||
|
|
||||||
|
cfgLen := len(cfgs)
|
||||||
|
for i := 0; i < cfgLen; i++ {
|
||||||
|
cfgs = append(cfgs, Config{
|
||||||
|
Width: cfgs[i].Width,
|
||||||
|
Height: cfgs[i].Height,
|
||||||
|
DisableOptimizations: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
spi := &mockBus{}
|
||||||
|
pin := mockPin{}
|
||||||
|
display := New(spi, pin)
|
||||||
|
|
||||||
|
for _, cfg := range cfgs {
|
||||||
|
display.Configure(cfg)
|
||||||
|
|
||||||
|
x, y := display.Size()
|
||||||
|
c.Assert(x, qt.Equals, cfg.Width)
|
||||||
|
c.Assert(y, qt.Equals, cfg.Height)
|
||||||
|
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
x := int16(rand.IntN(int(cfg.Width)))
|
||||||
|
y := int16(rand.IntN(int(cfg.Height)))
|
||||||
|
display.SetPixel(x, y, color.RGBA{R: 255, G: 255, B: 255, A: 255})
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
x := int16(rand.IntN(int(cfg.Width)))
|
||||||
|
y := int16(rand.IntN(int(cfg.Height)))
|
||||||
|
display.SetPixel(x, y, color.RGBA{R: 0, G: 0, B: 0, A: 255})
|
||||||
|
}
|
||||||
|
|
||||||
|
err := display.Display()
|
||||||
|
c.Assert(err, qt.Equals, nil)
|
||||||
|
|
||||||
|
err = display.ClearDisplay()
|
||||||
|
c.Assert(err, qt.Equals, nil)
|
||||||
|
|
||||||
|
display.ClearBuffer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_HiPad(t *testing.T) {
|
||||||
|
c := qt.New(t)
|
||||||
|
|
||||||
|
spi := &mockBus{}
|
||||||
|
pin := mockPin{}
|
||||||
|
display := New(spi, pin)
|
||||||
|
|
||||||
|
t.Run("LS011B7DH03, 8-bit address", func(t *testing.T) {
|
||||||
|
t.Cleanup(func() {
|
||||||
|
spi.b = nil
|
||||||
|
})
|
||||||
|
|
||||||
|
display.Configure(ConfigLS011B7DH03)
|
||||||
|
|
||||||
|
display.SetPixel(0, display.height-1, color.RGBA{R: 255, G: 255, B: 255, A: 255})
|
||||||
|
err := display.Display()
|
||||||
|
c.Assert(err, qt.Equals, nil)
|
||||||
|
|
||||||
|
// 160 perfectly divisible by 16, so 20 bytes of pixel data
|
||||||
|
c.Assert(spi.b, qt.HasLen, 2+20+2)
|
||||||
|
|
||||||
|
// line is 1-indexed on the wire (67+1)
|
||||||
|
// 68 in binary
|
||||||
|
// 0b01000100
|
||||||
|
|
||||||
|
// DDDDDMMM
|
||||||
|
c.Assert(spi.b[0], qt.Equals, uint8(0b00000011)) // mode 1, vcom is high on first run
|
||||||
|
|
||||||
|
c.Assert(spi.b[1], qt.Equals, uint8(0b01000100)) // the actual address
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("LS018B7DH02, 9-bit address", func(t *testing.T) {
|
||||||
|
t.Cleanup(func() {
|
||||||
|
spi.b = nil
|
||||||
|
})
|
||||||
|
|
||||||
|
display.Configure(ConfigLS018B7DH02)
|
||||||
|
|
||||||
|
display.SetPixel(0, display.height-1, color.RGBA{R: 255, G: 255, B: 255, A: 255})
|
||||||
|
err := display.Display()
|
||||||
|
c.Assert(err, qt.Equals, nil)
|
||||||
|
|
||||||
|
// 2 first bytes command+address
|
||||||
|
// 230 bits are not divisible by 16, 240 is (15*16), so 30 bytes for line data
|
||||||
|
// 2 trailing bytes
|
||||||
|
c.Assert(spi.b, qt.HasLen, 2+30+2)
|
||||||
|
|
||||||
|
// line is 1-indexed on the wire (302+1)
|
||||||
|
// 303 in binary (split in 2 bytes)
|
||||||
|
// R
|
||||||
|
// 0b00000001 0b00101111
|
||||||
|
// ^
|
||||||
|
|
||||||
|
// RDDDDMMM
|
||||||
|
c.Assert(spi.b[0], qt.Equals, uint8(0b10000011)) // mode 1, vcom is high on first run
|
||||||
|
// ^
|
||||||
|
|
||||||
|
c.Assert(spi.b[1], qt.Equals, uint8(0b00101111)) // rest of the address (low 8 bits)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("LS032B7DD02, 10-bit address", func(t *testing.T) {
|
||||||
|
t.Cleanup(func() {
|
||||||
|
spi.b = nil
|
||||||
|
})
|
||||||
|
|
||||||
|
display.Configure(ConfigLS032B7DD02)
|
||||||
|
|
||||||
|
display.SetPixel(0, display.height-1, color.RGBA{R: 255, G: 255, B: 255, A: 255})
|
||||||
|
err := display.Display()
|
||||||
|
c.Assert(err, qt.Equals, nil)
|
||||||
|
|
||||||
|
c.Assert(spi.b, qt.HasLen, 2+336/8+2) // 2 command+address, width / 2, 2 trailing bytes
|
||||||
|
|
||||||
|
// line is 1-indexed on the wire (535+1)
|
||||||
|
// 536 in binary (split in 2 bytes)
|
||||||
|
// RR
|
||||||
|
// 0b00000010 0b00011000
|
||||||
|
// ^^
|
||||||
|
|
||||||
|
// RRDDDMMM
|
||||||
|
c.Assert(spi.b[0], qt.Equals, uint8(0b10000011)) // mode 1, vcom is high on first run
|
||||||
|
// ^^
|
||||||
|
|
||||||
|
c.Assert(spi.b[1], qt.Equals, uint8(0b00011000)) // rest of the address (low 8 bits)
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package sharpmem
|
||||||
|
|
||||||
|
// setBit sets the bit at pos in n to 1 and returns the updated number.
|
||||||
|
func setBit(n uint8, pos uint8) uint8 {
|
||||||
|
n |= 1 << pos
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
// unsetBit sets the bit at pos in n to 0 and returns the updated number.
|
||||||
|
func unsetBit(n uint8, pos uint8) uint8 {
|
||||||
|
n &^= 1 << pos
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
// hasBit returns whether the bit at pos in n is 1.
|
||||||
|
func hasBit(n uint8, pos uint8) bool {
|
||||||
|
n = n & (1 << pos)
|
||||||
|
return n > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// bitfieldBufLen returns the required buffer size for keeping track of
|
||||||
|
// changed lines.
|
||||||
|
func bitfieldBufLen(bits int16) int16 {
|
||||||
|
return 1 + (bits-1)/8
|
||||||
|
}
|
||||||
|
|
||||||
|
// ceilDiv divides a with b, but it uses the ceiling if modulo is not 0.
|
||||||
|
func ceilDiv(a, b int16) int16 {
|
||||||
|
return 1 + (a-1)/b
|
||||||
|
}
|
||||||
@@ -137,6 +137,7 @@ tinygo build -size short -o ./build/test.hex -target=macropad-rp2040 ./examples/
|
|||||||
tinygo build -size short -o ./build/test.hex -target=macropad-rp2040 ./examples/encoders/quadrature-interrupt
|
tinygo build -size short -o ./build/test.hex -target=macropad-rp2040 ./examples/encoders/quadrature-interrupt
|
||||||
tinygo build -size short -o ./build/test.uf2 -target=pico ./examples/mcp9808/main.go
|
tinygo build -size short -o ./build/test.uf2 -target=pico ./examples/mcp9808/main.go
|
||||||
tinygo build -size short -o ./build/test.hex -target=pico ./examples/tmc5160/main.go
|
tinygo build -size short -o ./build/test.hex -target=pico ./examples/tmc5160/main.go
|
||||||
|
tinygo build -size short -o ./build/test.uf2 -target=nicenano ./examples/sharpmem/main.go
|
||||||
# network examples (espat)
|
# network examples (espat)
|
||||||
tinygo build -size short -o ./build/test.hex -target=challenger-rp2040 ./examples/net/ntpclient/
|
tinygo build -size short -o ./build/test.hex -target=challenger-rp2040 ./examples/net/ntpclient/
|
||||||
# network examples (wifinina)
|
# network examples (wifinina)
|
||||||
|
|||||||
Reference in New Issue
Block a user