Skip to content

Commit dad73b3

Browse files
committed
Add basic Game of Life Sense HAT LED Matrix Example
Can change colours, update time and choose between three starting seeds: Mold, Mazing and Octagon2. Each of these patterns are periodic oscillators (they repeat) and fit inside the 8x8 matrix.
1 parent 8f93656 commit dad73b3

File tree

3 files changed

+332
-0
lines changed

3 files changed

+332
-0
lines changed

examples/all/sense_hat_demo.cc

Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
1+
// Copyright lowRISC Contributors.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
/*
5+
* Simple demo using the LED Matrix. Can switch between a small 8x8 Conway's
6+
* Game of Life example from an initial state, and displaying some text
7+
* sweeping across the LED matrix.
8+
*
9+
* Hold down the joystick to switch between the two demos.
10+
*
11+
* Refer to the comment on the `sense_hat.hh` library: be careful about using
12+
* this demo when switching software / bitstream / resetting the FPGA. If used
13+
* in this context, you might find that the I2C controller on the Sense HAT
14+
* gets stuck, and so you need to either unplug/replug the Sense HAT or power
15+
* cycle the FPGA.
16+
*/
17+
18+
#include "../../libraries/sense_hat.hh"
19+
#include "../../third_party/display_drivers/core/m3x6_16pt.h"
20+
#include <compartment.h>
21+
#include <debug.hh>
22+
#include <platform-gpio.hh>
23+
#include <thread.h>
24+
25+
/// Expose debugging features unconditionally for this compartment.
26+
using Debug = ConditionalDebug<true, "Sense HAT">;
27+
using Colour = SenseHat::Colour;
28+
29+
const Colour OnColour = {.red = Colour::MaxRedValue, .green = 0, .blue = 0};
30+
const Colour OffColour = {.red = 25, .green = 25, .blue = 25};
31+
constexpr uint64_t GolFrameWaitMsec = 400;
32+
constexpr char DemoText[] = "CHERIoT <3 Sonata! ";
33+
constexpr uint64_t TextFrameWaitMsec = 150;
34+
35+
/// Use a custom bitmap for "g" to make presentation slightly cleaner
36+
constexpr uint8_t GBitmap[8] = {
37+
0x00, //
38+
0x06, // ##
39+
0x05, // # #
40+
0x07, // ###
41+
0x04, // #
42+
0x03, // ##
43+
0x00, //
44+
0x00, //
45+
};
46+
47+
/// Game of Life starting patterns
48+
constexpr bool Mold[8][8] = {
49+
{0, 0, 0, 0, 0, 0, 0, 0},
50+
{0, 0, 0, 0, 1, 1, 0, 0},
51+
{0, 0, 0, 1, 0, 0, 1, 0},
52+
{0, 0, 1, 0, 1, 0, 1, 0},
53+
{0, 0, 1, 0, 0, 1, 0, 0},
54+
{0, 1, 0, 0, 0, 0, 0, 0},
55+
{0, 0, 1, 0, 1, 0, 0, 0},
56+
{0, 0, 0, 0, 0, 0, 0, 0},
57+
};
58+
59+
constexpr bool Octagon2[8][8] = {
60+
{0, 0, 0, 1, 1, 0, 0, 0},
61+
{0, 0, 1, 0, 0, 1, 0, 0},
62+
{0, 1, 0, 0, 0, 0, 1, 0},
63+
{1, 0, 0, 0, 0, 0, 0, 1},
64+
{1, 0, 0, 0, 0, 0, 0, 1},
65+
{0, 1, 0, 0, 0, 0, 1, 0},
66+
{0, 0, 1, 0, 0, 1, 0, 0},
67+
{0, 0, 0, 1, 1, 0, 0, 0},
68+
};
69+
70+
constexpr bool Mazing[8][8] = {
71+
{0, 0, 0, 0, 0, 0, 0, 0},
72+
{0, 0, 0, 1, 1, 0, 0, 0},
73+
{0, 1, 0, 1, 0, 0, 0, 0},
74+
{1, 0, 0, 0, 0, 0, 1, 0},
75+
{0, 1, 0, 0, 0, 1, 1, 0},
76+
{0, 0, 0, 0, 0, 0, 0, 0},
77+
{0, 0, 0, 1, 0, 1, 0, 0},
78+
{0, 0, 0, 0, 1, 0, 0, 0},
79+
};
80+
81+
enum class Demo : uint8_t
82+
{
83+
GameOfLife = 0,
84+
ScrollingText = 1,
85+
};
86+
87+
void update_image(const bool state[8][8], Colour fb[64])
88+
{
89+
// Update the pixel framebuffer based on the current GOL game state
90+
for (uint8_t i = 0; i < 64u; i++)
91+
{
92+
fb[i] = state[i / 8u][i % 8u] ? OnColour : OffColour;
93+
}
94+
}
95+
96+
uint8_t get_neighbours(bool state[8][8], uint8_t y, uint8_t x)
97+
{
98+
// Count neighbours
99+
uint8_t neighbours = 0u;
100+
for (int8_t ny = static_cast<int8_t>(y - 1); ny <= y + 1; ny++)
101+
{
102+
for (int8_t nx = static_cast<int8_t>(x - 1); nx <= x + 1; nx++)
103+
{
104+
if ((ny == y && nx == x) || ny < 0 || ny >= 8 || nx < 0 || nx >= 8)
105+
continue;
106+
neighbours += state[ny][nx];
107+
}
108+
}
109+
return neighbours;
110+
}
111+
112+
void update_gol_state(bool state[8][8])
113+
{
114+
bool result[8][8] = {0u};
115+
// Iterate through cells
116+
for (uint8_t y = 0u; y < 8u; y++)
117+
{
118+
for (uint8_t x = 0u; x < 8u; x++)
119+
{
120+
uint8_t neighbours = get_neighbours(
121+
state, static_cast<int16_t>(y), static_cast<int16_t>(x));
122+
// Calculate the new state and store into a temporary result array
123+
if (state[y][x] && neighbours != 2u && neighbours != 3u)
124+
{
125+
result[y][x] = 0u;
126+
}
127+
else if (!state[y][x] && neighbours == 3u)
128+
{
129+
result[y][x] = 1u;
130+
}
131+
else
132+
{
133+
result[y][x] = state[y][x];
134+
}
135+
}
136+
}
137+
// Copy the newly calculated state back into the original 2D array
138+
memcpy(state, result, sizeof(bool[8][8]));
139+
}
140+
141+
void update_text_state(bool state[8][8], uint32_t *index, uint32_t *column)
142+
{
143+
uint8_t currentChar = static_cast<uint8_t>(DemoText[*index]);
144+
145+
/// If at the end of the string, reset to the beginning (wrap around)
146+
if (currentChar == '\0')
147+
{
148+
*index = 0;
149+
*column = 0;
150+
currentChar = static_cast<uint8_t>(DemoText[0]);
151+
}
152+
153+
/// Copy all columns left by 1.
154+
for (uint8_t x = 0u; x < 7u; x++)
155+
{
156+
for (uint8_t y = 0u; y < 8u; y++)
157+
{
158+
state[y][x] = state[y][x + 1];
159+
}
160+
}
161+
162+
/// Render the bitmap on the last column
163+
bool noBitsInColumn = true;
164+
for (uint8_t y = 1u; y < 8u; y++)
165+
{
166+
const uint32_t BitmapAddr = (currentChar - 32u) * 8u + y - 1u;
167+
const uint8_t Bit = 1u << *column;
168+
169+
/// Retrieve bitmap
170+
if (currentChar == 'g')
171+
{
172+
state[y][7] = (GBitmap[y - 1u] & Bit) >> *column;
173+
}
174+
else
175+
{
176+
state[y][7] = (m3x6_16ptBitmaps[BitmapAddr] & Bit) >> *column;
177+
}
178+
179+
/* (Slightly hacky): detect gaps (columns with no bits) to determine
180+
when the character ends, so that we can render this monospaced font
181+
without monospacing. This won't work for every character, but is good
182+
enough for our purposes. */
183+
if (state[y][7])
184+
{
185+
noBitsInColumn = false;
186+
}
187+
}
188+
189+
/* If a gap is found, or the entire character is rendered, progress to the
190+
next character to render. */
191+
if ((noBitsInColumn && (currentChar != ' ')) || (*column == 6u))
192+
{
193+
if (++*index > sizeof(DemoText))
194+
{
195+
*index = 0;
196+
}
197+
*column = 0;
198+
}
199+
else
200+
{
201+
*column += 1;
202+
}
203+
}
204+
205+
void initialize_demo(Demo currentDemo,
206+
bool ledState[8][8],
207+
uint32_t *index,
208+
uint32_t *column)
209+
{
210+
switch (currentDemo)
211+
{
212+
case Demo::GameOfLife:
213+
memcpy(ledState, Octagon2, sizeof(bool[8][8]));
214+
break;
215+
case Demo::ScrollingText:
216+
memset(ledState, 0u, sizeof(bool[8][8]));
217+
*column = 0u;
218+
*index = 0u;
219+
break;
220+
default:
221+
Debug::log("Unknown demo type to intialize: {}", currentDemo);
222+
break;
223+
}
224+
}
225+
226+
/// Thread entry point.
227+
void __cheri_compartment("sense_hat_demo") test()
228+
{
229+
Debug::log("Starting Sense HAT test");
230+
Demo currentDemo = Demo::GameOfLife;
231+
232+
// Initialise GPIO capability for joystick inputs
233+
auto gpio = MMIO_CAPABILITY(SonataGpioBoard, gpio_board);
234+
235+
// Initialise the Sense HAT
236+
auto senseHat = SenseHat();
237+
238+
// Initialise a blank LED Matrix.
239+
Colour fb[64] = {OffColour};
240+
senseHat.set_pixels(fb);
241+
242+
// Initialise LED Matrix starting states
243+
bool ledState[8][8];
244+
uint32_t index = 0;
245+
uint32_t column = 0;
246+
bool joystickPrevPressed = false;
247+
initialize_demo(currentDemo, ledState, &index, &column);
248+
249+
while (true)
250+
{
251+
/* If a joystick press is detected during an update, switch the demo
252+
type. This is not polled in a while waiting between updates, so a press
253+
can be missed between updates. */
254+
bool joystickIsPressed = static_cast<uint16_t>(gpio->read_joystick()) &
255+
static_cast<uint16_t>(SonataJoystick::Pressed);
256+
if (!joystickPrevPressed && joystickIsPressed)
257+
{
258+
currentDemo =
259+
static_cast<Demo>((static_cast<uint8_t>(currentDemo) + 1) % 2);
260+
thread_millisecond_wait(500);
261+
initialize_demo(currentDemo, ledState, &index, &column);
262+
joystickPrevPressed = true;
263+
}
264+
if (joystickPrevPressed && !joystickIsPressed)
265+
{
266+
joystickPrevPressed = false;
267+
}
268+
269+
switch (currentDemo)
270+
{
271+
case Demo::GameOfLife:
272+
/// Every frame, update the game state and LED matrix.
273+
thread_millisecond_wait(GolFrameWaitMsec);
274+
update_image(ledState, fb);
275+
senseHat.set_pixels(fb);
276+
update_gol_state(ledState);
277+
break;
278+
case Demo::ScrollingText:
279+
/// Every frame, update the scrolling text and LED matrix
280+
thread_millisecond_wait(TextFrameWaitMsec);
281+
update_image(ledState, fb);
282+
senseHat.set_pixels(fb);
283+
update_text_state(ledState, &index, &column);
284+
break;
285+
default:
286+
thread_millisecond_wait(100UL);
287+
Debug::log("Unknown demo type: {}", currentDemo);
288+
}
289+
}
290+
}

examples/all/xmake.lua

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,7 @@ compartment("rgbled_lerp")
2222
compartment("proximity_sensor_example")
2323
add_deps("debug")
2424
add_files("proximity_sensor_example.cc")
25+
26+
compartment("sense_hat_demo")
27+
add_deps("debug", "sense_hat")
28+
add_files("sense_hat_demo.cc", "../../third_party/display_drivers/core/m3x6_16pt.c")

examples/xmake.lua

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,44 @@ firmware("proximity_test")
139139
after_link(convert_to_uf2)
140140

141141

142+
-- Demo that uses a Sense HAT's LED Matrix
143+
firmware("sense_hat_leds")
144+
add_deps("freestanding", "led_walk_raw", "lcd_test", "rgbled_lerp", "sense_hat_demo")
145+
on_load(function(target)
146+
target:values_set("board", "$(board)")
147+
target:values_set("threads", {
148+
{
149+
compartment = "led_walk_raw",
150+
priority = 2,
151+
entry_point = "start_walking",
152+
stack_size = 0x200,
153+
trusted_stack_frames = 1
154+
},
155+
{
156+
compartment = "lcd_test",
157+
priority = 2,
158+
entry_point = "lcd_test",
159+
stack_size = 0x1000,
160+
trusted_stack_frames = 1
161+
},
162+
{
163+
compartment = "rgbled_lerp",
164+
priority = 2,
165+
entry_point = "lerp_rgbleds",
166+
stack_size = 0x200,
167+
trusted_stack_frames = 1
168+
},
169+
{
170+
compartment = "sense_hat_demo",
171+
priority = 2,
172+
entry_point = "test",
173+
stack_size = 0x1000,
174+
trusted_stack_frames = 1
175+
}
176+
}, {expand = false})
177+
end)
178+
after_link(convert_to_uf2)
179+
142180
-- Demo that does proximity test as well as LCD screen, etc for demos.
143181
firmware("leds_and_lcd")
144182
add_deps("freestanding", "led_walk_raw", "lcd_test", "rgbled_lerp")

0 commit comments

Comments
 (0)