Skip to content

Commit e66a289

Browse files
martinbuddenGadgetoid
authored andcommitted
Added a pico display 2.0 Mandelbrot set example.
Both pico cores are used: core0 handles the key input and updates the display at low resolution. Core1 updates the display at high resolution. For speed, fixed point arithmetic is used. The main loop also checks for cyles to speed things up. Six color palettes are provided, including 3 HSV, greyscale and black/white.
1 parent f17c04b commit e66a289

File tree

3 files changed

+348
-0
lines changed

3 files changed

+348
-0
lines changed

examples/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ add_subdirectory(breakout_oled_128x128)
2727

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

0 commit comments

Comments
 (0)