Skip to content

Commit 1317f2e

Browse files
authored
Merge pull request #680 from pimoroni/pico-display-mandelbrot
Added a pico display 2.0 Mandelbrot set example.
2 parents f17c04b + 8047f29 commit 1317f2e

File tree

3 files changed

+361
-0
lines changed

3 files changed

+361
-0
lines changed

examples/pico_display_2/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
add_subdirectory(mandelbrot)
2+
13
set(OUTPUT_NAME pico_display2_demo)
24

35
add_executable(
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
set(OUTPUT_NAME display_2_mandelbrot)
2+
3+
add_executable(
4+
${OUTPUT_NAME}
5+
demo.cpp
6+
)
7+
8+
# Pull in pico libraries that we need
9+
target_link_libraries(${OUTPUT_NAME} pico_stdlib pico_multicore hardware_spi hardware_pwm hardware_dma rgbled button pico_display_2 st7789 pico_graphics)
10+
11+
# create map/bin/hex file etc.
12+
pico_add_extra_outputs(${OUTPUT_NAME})
Lines changed: 347 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,347 @@
1+
#define MULTICORE
2+
3+
#include <math.h>
4+
#include "pico/stdlib.h"
5+
#if defined(MULTICORE)
6+
#include "pico/multicore.h"
7+
#endif
8+
9+
#include "libraries/pico_display_2/pico_display_2.hpp"
10+
#include "drivers/st7789/st7789.hpp"
11+
#include "libraries/pico_graphics/pico_graphics.hpp"
12+
#include "rgbled.hpp"
13+
#include "button.hpp"
14+
15+
16+
using namespace pimoroni;
17+
18+
// PicoDisplay2 is 320 by 240
19+
#define DISPLAY_WIDTH PicoDisplay2::WIDTH
20+
#define DISPLAY_HEIGHT PicoDisplay2::HEIGHT
21+
22+
ST7789 st7789(DISPLAY_WIDTH, DISPLAY_HEIGHT, ROTATE_0, false, get_spi_pins(BG_SPI_FRONT));
23+
PicoGraphics_PenRGB565 graphics(st7789.width, st7789.height, nullptr);
24+
25+
RGBLED led(PicoDisplay2::LED_R, PicoDisplay2::LED_G, PicoDisplay2::LED_B);
26+
27+
Button button_a(PicoDisplay2::A);
28+
Button button_b(PicoDisplay2::B);
29+
Button button_x(PicoDisplay2::X);
30+
Button button_y(PicoDisplay2::Y);
31+
32+
typedef int32_t fixed_t;
33+
34+
class complex_fixed_t {
35+
public:
36+
complex_fixed_t() {}
37+
complex_fixed_t(fixed_t _r, fixed_t _i) : r(_r), i(_i) {}
38+
public:
39+
fixed_t r;
40+
fixed_t i;
41+
};
42+
inline bool operator==(const complex_fixed_t& lhs, const complex_fixed_t& rhs){ return lhs.r == rhs.r && lhs.i == rhs.i; }
43+
44+
#define FXD_FRACTIONAL_BITS 25
45+
46+
#define FXD_FROM_INT(x) ((x) << FXD_FRACTIONAL_BITS)
47+
static inline fixed_t float_to_fixed(float x) { return static_cast<fixed_t>(x * static_cast<float>(1u << FXD_FRACTIONAL_BITS)); }
48+
static inline float fixed_to_float(fixed_t x) { return static_cast<float>(x) / static_cast<float>(1u << FXD_FRACTIONAL_BITS); }
49+
static inline fixed_t fixed_multiply(fixed_t a, fixed_t b) {
50+
const int64_t r = static_cast<int64_t>(a) * static_cast<int64_t>(b);
51+
return static_cast<int32_t>(r >> FXD_FRACTIONAL_BITS);
52+
}
53+
#define FXD_MUL(x, y) fixed_multiply(x, y)
54+
55+
56+
class MandelbrotView {
57+
public:
58+
void init(int aSizeX, int aScreenSizeY, uint16_t *aBuffer, int aPalettSize, int aBlockSizeX, int aInterationLimit, int aCore);
59+
void init(const MandelbrotView& aView, int aBlockSizeX, int aInterationLimit, int aCore);
60+
void setRange(fixed_t aFxdRangeR, const complex_fixed_t& aFxdCenter);
61+
void setRange(const MandelbrotView& aView);
62+
void render(void);
63+
void createPalettes(int aPaletteSize);
64+
void nextPalette(void);
65+
inline void start(void) { running = true; }
66+
inline void stop(void) { running = false; }
67+
inline bool isRunning(void) const { return running; }
68+
private:
69+
bool running;
70+
int core;
71+
int screenSizeX;
72+
int screenSizeY;
73+
fixed_t fxdRangeR;
74+
complex_fixed_t fxdCenter;
75+
complex_fixed_t fxdMin;
76+
complex_fixed_t fxdMax;
77+
complex_fixed_t fxdPixel;
78+
int iterationLimit;
79+
int blockSizeX;
80+
uint16_t *pFrameBuffer;
81+
static const uint8_t PALETTE_COUNT = 6;
82+
int paletteIndex;
83+
uint16_t *pPalette;
84+
uint16_t *pPalettes[PALETTE_COUNT];
85+
};
86+
87+
void MandelbrotView::init(int aScreenSizeX, int aScreenSizeY, uint16_t *aBuffer, int aPaletteSize, int aBlockSizeX, int aInterationLimit, int aCore) {
88+
screenSizeX = aScreenSizeX;
89+
screenSizeY = aScreenSizeY;
90+
createPalettes(aPaletteSize);
91+
blockSizeX = aBlockSizeX;
92+
pFrameBuffer = aBuffer;
93+
iterationLimit = aInterationLimit;
94+
core = aCore;
95+
running = true;
96+
}
97+
98+
void MandelbrotView::init(const MandelbrotView& aView, int aBlockSizeX, int aInterationLimit, int aCore) {
99+
*this = aView;
100+
for (int ii = 0; ii < PALETTE_COUNT; ++ii) {
101+
pPalettes[ii] = aView.pPalettes[ii];
102+
}
103+
blockSizeX = aBlockSizeX;
104+
iterationLimit = aInterationLimit;
105+
core = aCore;
106+
running = false;
107+
}
108+
109+
void MandelbrotView::setRange(fixed_t aFxdRangeR, const complex_fixed_t& aFxdCenter) {
110+
fxdRangeR = aFxdRangeR;
111+
fxdCenter = aFxdCenter;
112+
fxdMin.r = aFxdCenter.r - fxdRangeR / 2;
113+
fxdMax.r = aFxdCenter.r + fxdRangeR / 2;
114+
const float screenRatio2 = static_cast<float>(screenSizeY) / static_cast<float>(screenSizeX * 2);
115+
const fixed_t fxdRangeI2 = float_to_fixed(fixed_to_float(fxdRangeR) * screenRatio2);
116+
fxdMin.i = fxdCenter.i - fxdRangeI2;
117+
fxdMax.i = fxdCenter.i + fxdRangeI2;
118+
fxdPixel.r = float_to_fixed(fixed_to_float(fxdRangeR) / screenSizeX);
119+
fxdPixel.i = float_to_fixed(2*fixed_to_float(fxdRangeI2) / screenSizeY);
120+
}
121+
122+
void MandelbrotView::setRange(const MandelbrotView& aView) {
123+
fxdRangeR = aView.fxdRangeR;
124+
fxdCenter= aView.fxdCenter;
125+
fxdMin = aView.fxdMin;
126+
fxdMax = aView.fxdMax;
127+
fxdPixel = aView.fxdPixel;
128+
}
129+
130+
void MandelbrotView::render(void) {
131+
const fixed_t fxdMaxZ2 = FXD_FROM_INT(4);
132+
uint16_t *pFrame = pFrameBuffer;
133+
134+
Pen pen = 0;
135+
136+
complex_fixed_t fxdC = fxdMin;
137+
for (int screenY = 0; screenY < screenSizeY; ++screenY) {
138+
fxdC.r = fxdMin.r;
139+
for (int screenX = 0; screenX < screenSizeX; screenX += blockSizeX) {
140+
if (!running) {
141+
return;
142+
}
143+
pen = 0;
144+
complex_fixed_t fxdZ = fxdC;
145+
complex_fixed_t fxdZ0 = fxdZ;
146+
int period0 = 0;
147+
148+
for (int ii = 0; ii < iterationLimit; ++ii) {
149+
const fixed_t fxdZR2 = FXD_MUL(fxdZ.r, fxdZ.r);
150+
const fixed_t fxdZI2 = FXD_MUL(fxdZ.i, fxdZ.i);
151+
if (fxdZR2 + fxdZI2 >= fxdMaxZ2) {
152+
// we are outside the set so set color and break
153+
pen = pPalette[ii];
154+
break;
155+
}
156+
fxdZ.i = 2 * FXD_MUL(fxdZ.r, fxdZ.i) + fxdC.i;
157+
fxdZ.r = fxdZR2 - fxdZI2 + fxdC.r;
158+
if (fxdZ == fxdZ0) {
159+
// we have a repeating cycle, so we are inside the set
160+
break;
161+
}
162+
if (++period0 > 20) {
163+
period0 = 0;
164+
fxdZ0 = fxdZ;
165+
}
166+
}
167+
168+
for (int ii = 0; ii < blockSizeX; ++ii) {
169+
*(pFrame + screenX + ii) = pen;
170+
fxdC.r += fxdPixel.r;
171+
}
172+
}
173+
fxdC.i += fxdPixel.i;
174+
pFrame += screenSizeX;
175+
if (core == 1 && (screenY & 0xF) == 0) {
176+
// update core1 view every 16 lines
177+
st7789.update(&graphics);
178+
}
179+
}
180+
st7789.update(&graphics);
181+
}
182+
183+
// HSV Conversion expects float inputs in the range of 0.0-360.0 for the h channel and 0.0-1.0 for the s and v channels
184+
uint16_t penFromHSV(float h, float s, float v) {
185+
h = fmod(h, 360.0) / 360.0;
186+
const float i = floor(h * 6.0f);
187+
const float f = h * 6.0f - i;
188+
v *= 255.0f;
189+
const uint8_t p = v * (1.0f - s);
190+
const uint8_t q = v * (1.0f - f * s);
191+
const uint8_t t = v * (1.0f - (1.0f - f) * s);
192+
193+
uint8_t r = 0, g = 0, b = 0;
194+
switch (int(i) % 6) {
195+
case 0: r = v; g = t; b = p; break;
196+
case 1: r = q; g = v; b = p; break;
197+
case 2: r = p; g = v; b = t; break;
198+
case 3: r = p; g = q; b = v; break;
199+
case 4: r = t; g = p; b = v; break;
200+
case 5: r = v; g = p; b = q; break;
201+
}
202+
return graphics.create_pen(r, g, b);
203+
}
204+
205+
void MandelbrotView::createPalettes(int aPaletteSize) {
206+
paletteIndex = 2;
207+
for (int ii = 0; ii < PALETTE_COUNT; ++ii) {
208+
pPalettes[ii] = static_cast<uint16_t*>(malloc(sizeof(uint16_t) * aPaletteSize));
209+
}
210+
for (int ii = 0; ii < aPaletteSize; ++ii) {
211+
pPalettes[0][ii] = graphics.create_pen(255 - 255 * ii / aPaletteSize, 255 - 255 * ii / aPaletteSize, 255 - 255 * ii / aPaletteSize);
212+
pPalettes[1][ii] = graphics.create_pen(255, 255, 255);
213+
pPalettes[2][ii] = penFromHSV(160.0 + 360.0 * static_cast<float>(ii) / aPaletteSize, 0.9, 1.0);
214+
pPalettes[3][ii] = penFromHSV(60.0 + 360.0 * static_cast<float>(ii) / aPaletteSize, 0.9, 1.0);
215+
pPalettes[4][ii] = penFromHSV(360.0 * static_cast<float>(ii) / aPaletteSize, 0.9, 1.0);
216+
pPalettes[5][ii] = graphics.create_pen(ii % 4 * 64, ii % 8 * 32, ii % 16 * 16);
217+
}
218+
pPalette = pPalettes[paletteIndex];
219+
}
220+
221+
void MandelbrotView::nextPalette(void) {
222+
++paletteIndex;
223+
if (paletteIndex >= PALETTE_COUNT) {
224+
paletteIndex = 0;
225+
}
226+
pPalette = pPalettes[paletteIndex];
227+
}
228+
229+
MandelbrotView g_view0;
230+
231+
#if defined(MULTICORE)
232+
MandelbrotView g_view1;
233+
234+
// Note no mutual exclusion required for start and stop
235+
static inline void core1_start(void) {
236+
g_view1.start();
237+
}
238+
239+
static inline void core1_stop(void) {
240+
g_view1.stop();
241+
}
242+
243+
void core1_main(void) {
244+
while (true) {
245+
//uint32_t command = multicore_fifo_pop_blocking();
246+
while (!g_view1.isRunning()) {
247+
//tight_loop_contents();
248+
sleep_ms(1);
249+
}
250+
g_view1.render();
251+
}
252+
}
253+
#endif // MULTICORE
254+
255+
int main() {
256+
st7789.set_backlight(100);
257+
graphics.set_pen(0, 0, 0);
258+
graphics.clear();
259+
st7789.update(&graphics);
260+
261+
const int iterationLimitV0 = 40;
262+
const int iterationLimitV1 = 128;
263+
g_view0.init(DISPLAY_WIDTH, DISPLAY_HEIGHT, (uint16_t *)graphics.frame_buffer, iterationLimitV1, 4, iterationLimitV0, 0);
264+
265+
#if defined(MULTICORE)
266+
g_view1.init(g_view0, 1, iterationLimitV1, 1);
267+
// Launch core1, it won't start rendering until core1_start() is called
268+
multicore_launch_core1(core1_main);
269+
#endif
270+
271+
const fixed_t fxdInitialRangeR = float_to_fixed(3.2);
272+
const complex_fixed_t fxdInitialCenter(float_to_fixed(-0.75), float_to_fixed(0.0));
273+
fixed_t fxdRangeR = fxdInitialRangeR;
274+
complex_fixed_t fxdCenter = fxdInitialCenter;
275+
276+
while (true) {
277+
g_view0.setRange(fxdRangeR, fxdCenter);
278+
#if defined(MULTICORE)
279+
g_view1.setRange(fxdRangeR, fxdCenter);
280+
#endif
281+
led.set_rgb(64, 0, 0);
282+
283+
g_view0.render();
284+
285+
led.set_rgb(0, 0, 64);
286+
#if defined(MULTICORE)
287+
core1_start();
288+
#endif
289+
290+
led.set_rgb(0, 0, 0);
291+
292+
// Loop, waiting for key presses
293+
bool keyPressed = false;
294+
while (keyPressed == false) {
295+
sleep_ms(1);
296+
297+
if (button_a.raw()) {
298+
// Hold A to move left/right
299+
if (button_x.read() && fxdCenter.r > FXD_FROM_INT(-3)) {
300+
fxdCenter.r -= fxdRangeR / 8;
301+
keyPressed = true;
302+
} else if (button_y.read() && fxdCenter.r < FXD_FROM_INT(3)) {
303+
fxdCenter.r += fxdRangeR / 8;
304+
keyPressed = true;
305+
} else if (button_b.read()) {
306+
// Press A and B together to switch palette
307+
g_view0.nextPalette();
308+
#if defined(MULTICORE)
309+
g_view1.nextPalette();
310+
#endif
311+
keyPressed = true;
312+
}
313+
} else if (button_b.raw()) {
314+
// Hold B to move up/down
315+
if (button_x.read() && fxdCenter.i > FXD_FROM_INT(-2)) {
316+
fxdCenter.i -= fxdRangeR / 8;
317+
keyPressed = true;
318+
} else if (button_y.read() && fxdCenter.i < FXD_FROM_INT(2)) {
319+
fxdCenter.i += fxdRangeR / 8;
320+
keyPressed = true;
321+
}
322+
} else {
323+
// Otherwise zoom in/out
324+
if (button_x.read()) {
325+
if (button_y.read()) {
326+
// Press X and Y together to reset to initial position
327+
fxdRangeR = fxdInitialRangeR;
328+
fxdCenter = fxdInitialCenter;
329+
} else {
330+
fxdRangeR /= 4;
331+
fxdRangeR *= 3;
332+
}
333+
keyPressed = true;
334+
} else if (button_y.read() && fxdRangeR < FXD_FROM_INT(3)) {
335+
fxdRangeR *= 4;
336+
fxdRangeR /= 3;
337+
keyPressed = true;
338+
}
339+
}
340+
}
341+
#if defined(MULTICORE)
342+
// key pressed, so stop core1
343+
core1_stop();
344+
#endif
345+
}
346+
return 0;
347+
}

0 commit comments

Comments
 (0)