Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions examples/unor4matrix/checkerboard/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//go:build arduino_uno_r4_wifi

package main

import (
"image/color"

"tinygo.org/x/drivers/unor4matrix"
)

func main() {
display := unor4matrix.NewOnboard()
if err := display.Configure(); err != nil {
println(err.Error())
return
}
width, height := display.Size()
for y := int16(0); y < height; y++ {
for x := int16(0); x < width; x++ {
if (x+y)%2 == 0 {
display.SetPixel(x, y, color.RGBA{R: 255})
}
}
}
for {
if err := display.Display(); err != nil {
println(err.Error())
return
}
}
}
40 changes: 40 additions & 0 deletions examples/unor4matrix/life/life.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//go:build arduino_uno_r4_wifi

package main

const (
width = 12
height = 8
)

type board [height][width]bool

func nextGeneration(current board) (next board) {
for y := 0; y < height; y++ {
for x := 0; x < width; x++ {
neighbors := 0
for dy := -1; dy <= 1; dy++ {
for dx := -1; dx <= 1; dx++ {
if dx == 0 && dy == 0 {
continue
}
if current[(y+dy+height)%height][(x+dx+width)%width] {
neighbors++
}
}
}
next[y][x] = neighbors == 3 || current[y][x] && neighbors == 2
}
}
return next
}

func seed() board {
var cells board
cells[0][1] = true
cells[1][2] = true
cells[2][0] = true
cells[2][1] = true
cells[2][2] = true
return cells
}
44 changes: 44 additions & 0 deletions examples/unor4matrix/life/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//go:build arduino_uno_r4_wifi

package main

import (
"image/color"
"time"

"tinygo.org/x/drivers/unor4matrix"
)

const generationInterval = 300 * time.Millisecond

func main() {
display := unor4matrix.NewOnboard()
if err := display.Configure(); err != nil {
println(err.Error())
return
}
cells := seed()
draw := func() {
display.ClearDisplay()
for y, row := range cells {
for x, alive := range row {
if alive {
display.SetPixel(int16(x), int16(y), color.RGBA{G: 255})
}
}
}
}
draw()
nextUpdate := time.Now().Add(generationInterval)
for {
if err := display.Display(); err != nil {
println(err.Error())
return
}
if now := time.Now(); !now.Before(nextUpdate) {
cells = nextGeneration(cells)
draw()
nextUpdate = now.Add(generationInterval)
}
}
}
4 changes: 4 additions & 0 deletions smoketest.sh
Original file line number Diff line number Diff line change
Expand Up @@ -194,3 +194,7 @@ tinygo build -size short -o ./build/test.hex -target=wioterminal -stack-size 8kb
# network examples (comboat)
tinygo build -size short -o ./build/test.hex -target=elecrow-rp2040 -stack-size 8kb ./examples/net/tlsclient/
tinygo build -size short -o ./build/test.hex -target=elecrow-rp2350 -stack-size 8kb ./examples/net/ntpclient/

# Arduino UNO R4 WiFi LED matrix
tinygo build -size short -o ./build/test.hex -target=arduino-uno-r4-wifi ./examples/unor4matrix/checkerboard/
tinygo build -size short -o ./build/test.hex -target=arduino-uno-r4-wifi ./examples/unor4matrix/life/
67 changes: 67 additions & 0 deletions unor4matrix/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Arduino UNO R4 WiFi LED matrix

Driver for the built-in 12x8 monochrome matrix. Import
`tinygo.org/x/drivers/unor4matrix`. The driver implements `drivers.Displayer`.

## Usage

```go
display := unor4matrix.NewOnboard()
if err := display.Configure(); err != nil {
return
}
display.SetPixel(0, 0, color.RGBA{R: 255})
for {
if err := display.Display(); err != nil {
return
}
}
```

`NewOnboard` is available with `arduino_uno_r4_wifi`. `New` accepts eleven
`CharlieplexPin` values in this order:

```text
P0_03 P0_04 P0_11 P0_12 P0_13 P0_15 P2_04 P2_05 P2_06 P2_12 P2_13
```

Set must drive the requested level. Float must initialize a high-impedance input
without pull-ups. Configure returns `ErrMissingPinFunc` if either function is
missing, otherwise it floats every line. Configure may be repeated and keeps
the image. Give the device exclusive access to its pins.

## Refresh model

The image occupies 12 bytes. SetPixel edits it, ClearDisplay clears it, and
LoadFrame replaces it with Arduino-format words. Words are row-major, MSB first:
word 0 bit 31 is (0,0), word 2 bit 0 is (11,7). Any nonzero RGB is on, alpha is
ignored, and writes outside the drawing area are ignored.

Display scans all 96 slots directly from the image and floats each active pair
after its slot. Call it repeatedly to keep an image visible. Every slot sleeps
for 100 us, including dark pixels. A frame takes at least 9.6 ms plus GPIO and
scheduler overhead. Before successful Configure, Display returns ErrNotConfigured.

An active LED stays driven during time.Sleep. Other goroutines can prolong its on-time.

## Examples and limitations

```sh
tinygo flash -target=arduino-uno-r4-wifi ./examples/unor4matrix/checkerboard
tinygo flash -target=arduino-uno-r4-wifi ./examples/unor4matrix/life
```

Life runs a glider on a wrapping 12x8 grid with 300 ms generations. Display leaves
the lines floating before Life computes the next generation.

Use the default `tasks` scheduler. With TinyGo 0.44 and `-scheduler=none`, short
sleeps can wait for a 1 ms SysTick, reducing refresh to about 10 Hz. Display
blocks and foreground work pauses refresh. Call the driver from one application
goroutine, never from an interrupt handler. Rotation and brightness control are
unsupported. UNO R4 Minima has no built-in matrix. UNO Q uses `unoqmatrix`.

## Sources

- [Arduino mapping and 10 kHz timer](https://github.com/arduino/ArduinoCore-renesas/blob/424e86eff92d37f72123c2b641dd8bbf06a38b47/libraries/Arduino_LED_Matrix/src/Arduino_LED_Matrix.h).
- [UNO R4 WiFi line order](https://github.com/arduino/ArduinoCore-renesas/blob/424e86eff92d37f72123c2b641dd8bbf06a38b47/variants/UNOWIFIR4/variant.cpp).
- [RA4M1 hardware manual](https://www.renesas.com/en/document/mah/renesas-ra4m1-group-users-manual-hardware?language=en), R01UH0887EJ0110 Rev.1.10, sections 19.2.1, 19.2.3 and 19.2.5, pp. 357, 359 and 361-362. PODR holds the output level, POSR/PORR update it, and PDR enables output.
27 changes: 27 additions & 0 deletions unor4matrix/framebuffer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package unor4matrix

const (
// Width is the number of columns in the matrix.
Width = 12
// Height is the number of rows in the matrix.
Height = 8

framebufferSize = (Width*Height + 7) / 8
)

type framebuffer struct{ pixels [framebufferSize]byte }

func (f *framebuffer) get(index int) bool {
return f.pixels[index/8]&(byte(1)<<(index%8)) != 0
}

func (f *framebuffer) set(index int, on bool) {
mask := byte(1) << (index % 8)
if on {
f.pixels[index/8] |= mask
} else {
f.pixels[index/8] &^= mask
}
}

func (f *framebuffer) clear() { f.pixels = [framebufferSize]byte{} }
116 changes: 116 additions & 0 deletions unor4matrix/mapping.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package unor4matrix

// pinPair holds matrix-line indices in the line order defined by NewOnboard in matrix_unor4.go.
type pinPair struct {
anode uint8
cathode uint8
}

// ledMapping is row-major wiring from ArduinoCore-renesas.
// https://github.com/arduino/ArduinoCore-renesas/blob/424e86eff92d37f72123c2b641dd8bbf06a38b47/libraries/Arduino_LED_Matrix/src/Arduino_LED_Matrix.h
var ledMapping = [...]pinPair{
// Row y = 0.
{anode: 7, cathode: 3},
{anode: 3, cathode: 7},
{anode: 7, cathode: 4},
{anode: 4, cathode: 7},
{anode: 3, cathode: 4},
{anode: 4, cathode: 3},
{anode: 7, cathode: 8},
{anode: 8, cathode: 7},
{anode: 3, cathode: 8},
{anode: 8, cathode: 3},
{anode: 4, cathode: 8},
{anode: 8, cathode: 4},
// Row y = 1.
{anode: 7, cathode: 0},
{anode: 0, cathode: 7},
{anode: 3, cathode: 0},
{anode: 0, cathode: 3},
{anode: 4, cathode: 0},
{anode: 0, cathode: 4},
{anode: 8, cathode: 0},
{anode: 0, cathode: 8},
{anode: 7, cathode: 6},
{anode: 6, cathode: 7},
{anode: 3, cathode: 6},
{anode: 6, cathode: 3},
// Row y = 2.
{anode: 4, cathode: 6},
{anode: 6, cathode: 4},
{anode: 8, cathode: 6},
{anode: 6, cathode: 8},
{anode: 0, cathode: 6},
{anode: 6, cathode: 0},
{anode: 7, cathode: 5},
{anode: 5, cathode: 7},
{anode: 3, cathode: 5},
{anode: 5, cathode: 3},
{anode: 4, cathode: 5},
{anode: 5, cathode: 4},
// Row y = 3.
{anode: 8, cathode: 5},
{anode: 5, cathode: 8},
{anode: 0, cathode: 5},
{anode: 5, cathode: 0},
{anode: 6, cathode: 5},
{anode: 5, cathode: 6},
{anode: 7, cathode: 1},
{anode: 1, cathode: 7},
{anode: 3, cathode: 1},
{anode: 1, cathode: 3},
{anode: 4, cathode: 1},
{anode: 1, cathode: 4},
// Row y = 4.
{anode: 8, cathode: 1},
{anode: 1, cathode: 8},
{anode: 0, cathode: 1},
{anode: 1, cathode: 0},
{anode: 6, cathode: 1},
{anode: 1, cathode: 6},
{anode: 5, cathode: 1},
{anode: 1, cathode: 5},
{anode: 7, cathode: 2},
{anode: 2, cathode: 7},
{anode: 3, cathode: 2},
{anode: 2, cathode: 3},
// Row y = 5.
{anode: 4, cathode: 2},
{anode: 2, cathode: 4},
{anode: 8, cathode: 2},
{anode: 2, cathode: 8},
{anode: 0, cathode: 2},
{anode: 2, cathode: 0},
{anode: 6, cathode: 2},
{anode: 2, cathode: 6},
{anode: 5, cathode: 2},
{anode: 2, cathode: 5},
{anode: 1, cathode: 2},
{anode: 2, cathode: 1},
// Row y = 6.
{anode: 7, cathode: 10},
{anode: 10, cathode: 7},
{anode: 3, cathode: 10},
{anode: 10, cathode: 3},
{anode: 4, cathode: 10},
{anode: 10, cathode: 4},
{anode: 8, cathode: 10},
{anode: 10, cathode: 8},
{anode: 0, cathode: 10},
{anode: 10, cathode: 0},
{anode: 6, cathode: 10},
{anode: 10, cathode: 6},
// Row y = 7.
{anode: 5, cathode: 10},
{anode: 10, cathode: 5},
{anode: 1, cathode: 10},
{anode: 10, cathode: 1},
{anode: 2, cathode: 10},
{anode: 10, cathode: 2},
{anode: 7, cathode: 9},
{anode: 9, cathode: 7},
{anode: 3, cathode: 9},
{anode: 9, cathode: 3},
{anode: 4, cathode: 9},
{anode: 9, cathode: 4},
}
Loading