Skip to content

Commit c257a25

Browse files
committed
unoqmatrix: LED matrix on the Arduino Uno Q
Signed-off-by: deadprogram <ron@hybridgroup.com>
1 parent 4071028 commit c257a25

File tree

4 files changed

+705
-0
lines changed

4 files changed

+705
-0
lines changed

examples/unoqmatrix/main.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package main
2+
3+
import (
4+
"machine"
5+
6+
"image/color"
7+
"math/rand"
8+
9+
"tinygo.org/x/drivers/unoqmatrix"
10+
)
11+
12+
var on = color.RGBA{255, 255, 255, 255}
13+
14+
func main() {
15+
display := unoqmatrix.NewFromBasePin(machine.PF0)
16+
display.ClearDisplay()
17+
18+
w, h := display.Size()
19+
x := int16(0)
20+
y := int16(0)
21+
deltaX := int16(1)
22+
deltaY := int16(1)
23+
24+
for {
25+
pixel := display.GetPixel(x, y)
26+
if pixel.R != 0 || pixel.G != 0 || pixel.B != 0 {
27+
display.ClearDisplay()
28+
x = 1 + int16(rand.Int31n(3))
29+
y = 1 + int16(rand.Int31n(3))
30+
deltaX = 1
31+
deltaY = 1
32+
if rand.Int31n(2) == 0 {
33+
deltaX = -1
34+
}
35+
if rand.Int31n(2) == 0 {
36+
deltaY = -1
37+
}
38+
}
39+
display.SetPixel(x, y, on)
40+
41+
x += deltaX
42+
y += deltaY
43+
44+
if x == 0 || x == w-1 {
45+
deltaX = -deltaX
46+
}
47+
48+
if y == 0 || y == h-1 {
49+
deltaY = -deltaY
50+
}
51+
52+
display.Display()
53+
}
54+
}

unoqmatrix/matrix.go

Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
1+
// Package unoqmatrix provides a driver for the UnoQMatrix LED matrix display.
2+
//
3+
// The UnoQMatrix is an 8x13 LED matrix display that can be controlled using a single pin.
4+
// It uses a multiplexing technique to control the LEDs, which allows for a large number of LEDs to be controlled with fewer pins.
5+
//
6+
// This driver provides basic functionality to set individual pixels, clear the display, and refresh the display.
7+
//
8+
// Note: The UnoQMatrix does not support brightness control or color depth. Each pixel can only be turned on or off.
9+
// Could it suppport brightness control by using PWM on the pin? To be investigated.
10+
package unoqmatrix
11+
12+
import (
13+
"image/color"
14+
"time"
15+
16+
pin "tinygo.org/x/drivers/internal/pin"
17+
)
18+
19+
type Config struct {
20+
// Rotation of the LED matrix.
21+
Rotation uint8
22+
}
23+
24+
// Valid values:
25+
//
26+
// 0: regular orientation, (0 degree rotation)
27+
// 1: 90 degree rotation clockwise
28+
// 2: 180 degree rotation clockwise
29+
// 3: 270 degree rotation clockwise
30+
const (
31+
RotationNormal = 0
32+
Rotation90 = 1
33+
Rotation180 = 2
34+
Rotation270 = 3
35+
)
36+
37+
const (
38+
ledRows = 8
39+
ledCols = 13
40+
41+
pixelRefreshDelay = 10 * time.Microsecond
42+
)
43+
44+
// CharlieplexPin represents a pin used for charlieplexing.
45+
// It must be able to drive high/low (output mode) and float (high-impedance/input mode).
46+
//
47+
// Example construction from a machine.Pin using the pin HAL pattern:
48+
//
49+
// var isOutput bool
50+
// cp := unoqmatrix.CharlieplexPin{
51+
// Set: pin.OutputFunc(func(level bool) {
52+
// if !isOutput {
53+
// p.Configure(machine.PinConfig{Mode: machine.PinOutput})
54+
// isOutput = true
55+
// }
56+
// p.Set(level)
57+
// }),
58+
// Float: func() {
59+
// if isOutput {
60+
// p.Configure(machine.PinConfig{Mode: machine.PinInput})
61+
// isOutput = false
62+
// }
63+
// },
64+
// }
65+
type CharlieplexPin struct {
66+
Set pin.OutputFunc // Drive pin high (true) or low (false); auto-configures to output mode.
67+
Float func() // Put pin into high-impedance (input) mode.
68+
}
69+
70+
const numPins = 11
71+
72+
// Device represents the UnoQMatrix LED matrix display.
73+
type Device struct {
74+
pins [numPins]CharlieplexPin
75+
buffer [ledRows][ledCols]color.RGBA
76+
rotation uint8
77+
}
78+
79+
// New returns a new unoqmatrix driver.
80+
// The provided pins are the 11 charlieplex pins used to control the LED matrix.
81+
func New(pins [numPins]CharlieplexPin) Device {
82+
return Device{pins: pins}
83+
}
84+
85+
// Configure sets up the device.
86+
func (d *Device) Configure(cfg Config) {
87+
d.SetRotation(cfg.Rotation)
88+
}
89+
90+
// SetRotation changes the rotation of the LED matrix.
91+
//
92+
// Valid values for rotation:
93+
//
94+
// 0: regular orientation, (0 degree rotation)
95+
// 1: 90 degree rotation clockwise
96+
// 2: 180 degree rotation clockwise
97+
// 3: 270 degree rotation clockwise
98+
func (d *Device) SetRotation(rotation uint8) {
99+
d.rotation = rotation % 4
100+
}
101+
102+
// SetPixel sets the color of a specific pixel.
103+
func (d *Device) SetPixel(x int16, y int16, c color.RGBA) {
104+
d.buffer[y][x] = c
105+
}
106+
107+
// GetPixel returns the color of a specific pixel.
108+
func (d *Device) GetPixel(x int16, y int16) color.RGBA {
109+
return d.buffer[y][x]
110+
}
111+
112+
// Display sends the buffer (if any) to the screen.
113+
// Only lights active (non-black) pixels, and resets only the 2 previously
114+
// driven pins between LEDs instead of all 11, making each refresh cycle
115+
// proportional to the number of lit LEDs.
116+
func (d *Device) Display() error {
117+
d.clearDisplay()
118+
119+
var lastIdx0, lastIdx1 uint8
120+
hasLast := false
121+
122+
for row := 0; row < ledRows; row++ {
123+
for col := 0; col < ledCols; col++ {
124+
c := d.buffer[row][col]
125+
if c.R == 0 && c.G == 0 && c.B == 0 {
126+
continue
127+
}
128+
129+
idx := row*ledCols + col
130+
if idx < 0 || idx >= len(pinMapping) {
131+
continue
132+
}
133+
134+
// Float only the two pins that were driving the previous LED.
135+
if hasLast {
136+
d.pins[lastIdx0].Float()
137+
d.pins[lastIdx1].Float()
138+
}
139+
hasLast = true
140+
141+
idx0 := pinMapping[idx][0]
142+
idx1 := pinMapping[idx][1]
143+
d.pins[idx0].Set.High()
144+
d.pins[idx1].Set.Low()
145+
lastIdx0 = idx0
146+
lastIdx1 = idx1
147+
148+
time.Sleep(pixelRefreshDelay)
149+
}
150+
}
151+
152+
// Float the last driven LED.
153+
if hasLast {
154+
d.pins[lastIdx0].Float()
155+
d.pins[lastIdx1].Float()
156+
}
157+
158+
return nil
159+
}
160+
161+
// ClearDisplay turns off all the LEDs on the display.
162+
func (d *Device) ClearDisplay() {
163+
for row := 0; row < ledRows; row++ {
164+
for col := 0; col < ledCols; col++ {
165+
d.buffer[row][col] = color.RGBA{0, 0, 0, 255}
166+
}
167+
}
168+
}
169+
170+
// Size returns the current size of the display.
171+
func (d *Device) Size() (w, h int16) {
172+
return ledCols, ledRows
173+
}
174+
175+
// pinMapping defines the mapping of LED indices to pin pairs. Each entry corresponds
176+
// to an LED index (0-104) and contains the two pin numbers that need to be set to turn on that LED.
177+
// based on https://github.com/arduino/ArduinoCore-zephyr/blob/main/loader/matrix.inc#L13
178+
var pinMapping = [][2]uint8{
179+
{0, 1}, // 0
180+
{1, 0},
181+
{0, 2},
182+
{2, 0},
183+
{1, 2},
184+
{2, 1},
185+
{0, 3},
186+
{3, 0},
187+
{1, 3},
188+
{3, 1},
189+
{2, 3}, // 10
190+
{3, 2},
191+
{0, 4},
192+
{4, 0},
193+
{1, 4},
194+
{4, 1},
195+
{2, 4},
196+
{4, 2},
197+
{3, 4},
198+
{4, 3},
199+
{0, 5}, // 20
200+
{5, 0},
201+
{1, 5},
202+
{5, 1},
203+
{2, 5},
204+
{5, 2},
205+
{3, 5},
206+
{5, 3},
207+
{4, 5},
208+
{5, 4},
209+
{0, 6}, // 30
210+
{6, 0},
211+
{1, 6},
212+
{6, 1},
213+
{2, 6},
214+
{6, 2},
215+
{3, 6},
216+
{6, 3},
217+
{4, 6},
218+
{6, 4},
219+
{5, 6}, // 40
220+
{6, 5},
221+
{0, 7},
222+
{7, 0},
223+
{1, 7},
224+
{7, 1},
225+
{2, 7},
226+
{7, 2},
227+
{3, 7},
228+
{7, 3},
229+
{4, 7}, // 50
230+
{7, 4},
231+
{5, 7},
232+
{7, 5},
233+
{6, 7},
234+
{7, 6},
235+
{0, 8},
236+
{8, 0},
237+
{1, 8},
238+
{8, 1},
239+
{2, 8}, // 60
240+
{8, 2},
241+
{3, 8},
242+
{8, 3},
243+
{4, 8},
244+
{8, 4},
245+
{5, 8},
246+
{8, 5},
247+
{6, 8},
248+
{8, 6},
249+
{7, 8}, // 70
250+
{8, 7},
251+
{0, 9},
252+
{9, 0},
253+
{1, 9},
254+
{9, 1},
255+
{2, 9},
256+
{9, 2},
257+
{3, 9},
258+
{9, 3},
259+
{4, 9}, // 80
260+
{9, 4},
261+
{5, 9},
262+
{9, 5},
263+
{6, 9},
264+
{9, 6},
265+
{7, 9},
266+
{9, 7},
267+
{8, 9},
268+
{9, 8},
269+
{0, 10}, // 90
270+
{10, 0},
271+
{1, 10},
272+
{10, 1},
273+
{2, 10},
274+
{10, 2},
275+
{3, 10},
276+
{10, 3},
277+
{4, 10},
278+
{10, 4},
279+
{5, 10}, // 100
280+
{10, 5},
281+
{6, 10},
282+
{10, 6},
283+
}
284+
285+
// clearDisplay turns off all the LEDs on the display by floating all pins.
286+
func (d *Device) clearDisplay() {
287+
for i := range d.pins {
288+
d.pins[i].Float()
289+
}
290+
}

0 commit comments

Comments
 (0)