Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added a pico display 2.0 Mandelbrot set example. #680

Merged
merged 2 commits into from
Feb 22, 2023
Merged
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
2 changes: 2 additions & 0 deletions examples/pico_display_2/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
add_subdirectory(mandelbrot)

set(OUTPUT_NAME pico_display2_demo)

add_executable(
Expand Down
12 changes: 12 additions & 0 deletions examples/pico_display_2/mandelbrot/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
set(OUTPUT_NAME display_2_mandelbrot)

add_executable(
${OUTPUT_NAME}
demo.cpp
)

# Pull in pico libraries that we need
target_link_libraries(${OUTPUT_NAME} pico_stdlib pico_multicore hardware_spi hardware_pwm hardware_dma rgbled button pico_display_2 st7789 pico_graphics)

# create map/bin/hex file etc.
pico_add_extra_outputs(${OUTPUT_NAME})
347 changes: 347 additions & 0 deletions examples/pico_display_2/mandelbrot/demo.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,347 @@
#define MULTICORE

#include <math.h>
#include "pico/stdlib.h"
#if defined(MULTICORE)
#include "pico/multicore.h"
#endif

#include "libraries/pico_display_2/pico_display_2.hpp"
#include "drivers/st7789/st7789.hpp"
#include "libraries/pico_graphics/pico_graphics.hpp"
#include "rgbled.hpp"
#include "button.hpp"


using namespace pimoroni;

// PicoDisplay2 is 320 by 240
#define DISPLAY_WIDTH PicoDisplay2::WIDTH
#define DISPLAY_HEIGHT PicoDisplay2::HEIGHT

ST7789 st7789(DISPLAY_WIDTH, DISPLAY_HEIGHT, ROTATE_0, false, get_spi_pins(BG_SPI_FRONT));
PicoGraphics_PenRGB565 graphics(st7789.width, st7789.height, nullptr);

RGBLED led(PicoDisplay2::LED_R, PicoDisplay2::LED_G, PicoDisplay2::LED_B);

Button button_a(PicoDisplay2::A);
Button button_b(PicoDisplay2::B);
Button button_x(PicoDisplay2::X);
Button button_y(PicoDisplay2::Y);

typedef int32_t fixed_t;

class complex_fixed_t {
public:
complex_fixed_t() {}
complex_fixed_t(fixed_t _r, fixed_t _i) : r(_r), i(_i) {}
public:
fixed_t r;
fixed_t i;
};
inline bool operator==(const complex_fixed_t& lhs, const complex_fixed_t& rhs){ return lhs.r == rhs.r && lhs.i == rhs.i; }

#define FXD_FRACTIONAL_BITS 25

#define FXD_FROM_INT(x) ((x) << FXD_FRACTIONAL_BITS)
static inline fixed_t float_to_fixed(float x) { return static_cast<fixed_t>(x * static_cast<float>(1u << FXD_FRACTIONAL_BITS)); }
static inline float fixed_to_float(fixed_t x) { return static_cast<float>(x) / static_cast<float>(1u << FXD_FRACTIONAL_BITS); }
static inline fixed_t fixed_multiply(fixed_t a, fixed_t b) {
const int64_t r = static_cast<int64_t>(a) * static_cast<int64_t>(b);
return static_cast<int32_t>(r >> FXD_FRACTIONAL_BITS);
}
#define FXD_MUL(x, y) fixed_multiply(x, y)


class MandelbrotView {
public:
void init(int aSizeX, int aScreenSizeY, uint16_t *aBuffer, int aPalettSize, int aBlockSizeX, int aInterationLimit, int aCore);
void init(const MandelbrotView& aView, int aBlockSizeX, int aInterationLimit, int aCore);
void setRange(fixed_t aFxdRangeR, const complex_fixed_t& aFxdCenter);
void setRange(const MandelbrotView& aView);
void render(void);
void createPalettes(int aPaletteSize);
void nextPalette(void);
inline void start(void) { running = true; }
inline void stop(void) { running = false; }
inline bool isRunning(void) const { return running; }
private:
bool running;
int core;
int screenSizeX;
int screenSizeY;
fixed_t fxdRangeR;
complex_fixed_t fxdCenter;
complex_fixed_t fxdMin;
complex_fixed_t fxdMax;
complex_fixed_t fxdPixel;
int iterationLimit;
int blockSizeX;
uint16_t *pFrameBuffer;
static const uint8_t PALETTE_COUNT = 6;
int paletteIndex;
uint16_t *pPalette;
uint16_t *pPalettes[PALETTE_COUNT];
};

void MandelbrotView::init(int aScreenSizeX, int aScreenSizeY, uint16_t *aBuffer, int aPaletteSize, int aBlockSizeX, int aInterationLimit, int aCore) {
screenSizeX = aScreenSizeX;
screenSizeY = aScreenSizeY;
createPalettes(aPaletteSize);
blockSizeX = aBlockSizeX;
pFrameBuffer = aBuffer;
iterationLimit = aInterationLimit;
core = aCore;
running = true;
}

void MandelbrotView::init(const MandelbrotView& aView, int aBlockSizeX, int aInterationLimit, int aCore) {
*this = aView;
for (int ii = 0; ii < PALETTE_COUNT; ++ii) {
pPalettes[ii] = aView.pPalettes[ii];
}
blockSizeX = aBlockSizeX;
iterationLimit = aInterationLimit;
core = aCore;
running = false;
}

void MandelbrotView::setRange(fixed_t aFxdRangeR, const complex_fixed_t& aFxdCenter) {
fxdRangeR = aFxdRangeR;
fxdCenter = aFxdCenter;
fxdMin.r = aFxdCenter.r - fxdRangeR / 2;
fxdMax.r = aFxdCenter.r + fxdRangeR / 2;
const float screenRatio2 = static_cast<float>(screenSizeY) / static_cast<float>(screenSizeX * 2);
const fixed_t fxdRangeI2 = float_to_fixed(fixed_to_float(fxdRangeR) * screenRatio2);
fxdMin.i = fxdCenter.i - fxdRangeI2;
fxdMax.i = fxdCenter.i + fxdRangeI2;
fxdPixel.r = float_to_fixed(fixed_to_float(fxdRangeR) / screenSizeX);
fxdPixel.i = float_to_fixed(2*fixed_to_float(fxdRangeI2) / screenSizeY);
}

void MandelbrotView::setRange(const MandelbrotView& aView) {
fxdRangeR = aView.fxdRangeR;
fxdCenter= aView.fxdCenter;
fxdMin = aView.fxdMin;
fxdMax = aView.fxdMax;
fxdPixel = aView.fxdPixel;
}

void MandelbrotView::render(void) {
const fixed_t fxdMaxZ2 = FXD_FROM_INT(4);
uint16_t *pFrame = pFrameBuffer;

Pen pen = 0;

complex_fixed_t fxdC = fxdMin;
for (int screenY = 0; screenY < screenSizeY; ++screenY) {
fxdC.r = fxdMin.r;
for (int screenX = 0; screenX < screenSizeX; screenX += blockSizeX) {
if (!running) {
return;
}
pen = 0;
complex_fixed_t fxdZ = fxdC;
complex_fixed_t fxdZ0 = fxdZ;
int period0 = 0;

for (int ii = 0; ii < iterationLimit; ++ii) {
const fixed_t fxdZR2 = FXD_MUL(fxdZ.r, fxdZ.r);
const fixed_t fxdZI2 = FXD_MUL(fxdZ.i, fxdZ.i);
if (fxdZR2 + fxdZI2 >= fxdMaxZ2) {
// we are outside the set so set color and break
pen = pPalette[ii];
break;
}
fxdZ.i = 2 * FXD_MUL(fxdZ.r, fxdZ.i) + fxdC.i;
fxdZ.r = fxdZR2 - fxdZI2 + fxdC.r;
if (fxdZ == fxdZ0) {
// we have a repeating cycle, so we are inside the set
break;
}
if (++period0 > 20) {
period0 = 0;
fxdZ0 = fxdZ;
}
}

for (int ii = 0; ii < blockSizeX; ++ii) {
*(pFrame + screenX + ii) = pen;
fxdC.r += fxdPixel.r;
}
}
fxdC.i += fxdPixel.i;
pFrame += screenSizeX;
if (core == 1 && (screenY & 0xF) == 0) {
// update core1 view every 16 lines
st7789.update(&graphics);
}
}
st7789.update(&graphics);
}

// 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
uint16_t penFromHSV(float h, float s, float v) {
h = fmod(h, 360.0) / 360.0;
const float i = floor(h * 6.0f);
const float f = h * 6.0f - i;
v *= 255.0f;
const uint8_t p = v * (1.0f - s);
const uint8_t q = v * (1.0f - f * s);
const uint8_t t = v * (1.0f - (1.0f - f) * s);

uint8_t r = 0, g = 0, b = 0;
switch (int(i) % 6) {
case 0: r = v; g = t; b = p; break;
case 1: r = q; g = v; b = p; break;
case 2: r = p; g = v; b = t; break;
case 3: r = p; g = q; b = v; break;
case 4: r = t; g = p; b = v; break;
case 5: r = v; g = p; b = q; break;
}
return graphics.create_pen(r, g, b);
}

void MandelbrotView::createPalettes(int aPaletteSize) {
paletteIndex = 2;
for (int ii = 0; ii < PALETTE_COUNT; ++ii) {
pPalettes[ii] = static_cast<uint16_t*>(malloc(sizeof(uint16_t) * aPaletteSize));
}
for (int ii = 0; ii < aPaletteSize; ++ii) {
pPalettes[0][ii] = graphics.create_pen(255 - 255 * ii / aPaletteSize, 255 - 255 * ii / aPaletteSize, 255 - 255 * ii / aPaletteSize);
pPalettes[1][ii] = graphics.create_pen(255, 255, 255);
pPalettes[2][ii] = penFromHSV(160.0 + 360.0 * static_cast<float>(ii) / aPaletteSize, 0.9, 1.0);
pPalettes[3][ii] = penFromHSV(60.0 + 360.0 * static_cast<float>(ii) / aPaletteSize, 0.9, 1.0);
pPalettes[4][ii] = penFromHSV(360.0 * static_cast<float>(ii) / aPaletteSize, 0.9, 1.0);
pPalettes[5][ii] = graphics.create_pen(ii % 4 * 64, ii % 8 * 32, ii % 16 * 16);
}
pPalette = pPalettes[paletteIndex];
}

void MandelbrotView::nextPalette(void) {
++paletteIndex;
if (paletteIndex >= PALETTE_COUNT) {
paletteIndex = 0;
}
pPalette = pPalettes[paletteIndex];
}

MandelbrotView g_view0;

#if defined(MULTICORE)
MandelbrotView g_view1;

// Note no mutual exclusion required for start and stop
static inline void core1_start(void) {
g_view1.start();
}

static inline void core1_stop(void) {
g_view1.stop();
}

void core1_main(void) {
while (true) {
//uint32_t command = multicore_fifo_pop_blocking();
while (!g_view1.isRunning()) {
//tight_loop_contents();
sleep_ms(1);
}
g_view1.render();
}
}
#endif // MULTICORE

int main() {
st7789.set_backlight(100);
graphics.set_pen(0, 0, 0);
graphics.clear();
st7789.update(&graphics);

const int iterationLimitV0 = 40;
const int iterationLimitV1 = 128;
g_view0.init(DISPLAY_WIDTH, DISPLAY_HEIGHT, (uint16_t *)graphics.frame_buffer, iterationLimitV1, 4, iterationLimitV0, 0);

#if defined(MULTICORE)
g_view1.init(g_view0, 1, iterationLimitV1, 1);
// Launch core1, it won't start rendering until core1_start() is called
multicore_launch_core1(core1_main);
#endif

const fixed_t fxdInitialRangeR = float_to_fixed(3.2);
const complex_fixed_t fxdInitialCenter(float_to_fixed(-0.75), float_to_fixed(0.0));
fixed_t fxdRangeR = fxdInitialRangeR;
complex_fixed_t fxdCenter = fxdInitialCenter;

while (true) {
g_view0.setRange(fxdRangeR, fxdCenter);
#if defined(MULTICORE)
g_view1.setRange(fxdRangeR, fxdCenter);
#endif
led.set_rgb(64, 0, 0);

g_view0.render();

led.set_rgb(0, 0, 64);
#if defined(MULTICORE)
core1_start();
#endif

led.set_rgb(0, 0, 0);

// Loop, waiting for key presses
bool keyPressed = false;
while (keyPressed == false) {
sleep_ms(1);

if (button_a.raw()) {
// Hold A to move left/right
if (button_x.read() && fxdCenter.r > FXD_FROM_INT(-3)) {
fxdCenter.r -= fxdRangeR / 8;
keyPressed = true;
} else if (button_y.read() && fxdCenter.r < FXD_FROM_INT(3)) {
fxdCenter.r += fxdRangeR / 8;
keyPressed = true;
} else if (button_b.read()) {
// Press A and B together to switch palette
g_view0.nextPalette();
#if defined(MULTICORE)
g_view1.nextPalette();
#endif
keyPressed = true;
}
} else if (button_b.raw()) {
// Hold B to move up/down
if (button_x.read() && fxdCenter.i > FXD_FROM_INT(-2)) {
fxdCenter.i -= fxdRangeR / 8;
keyPressed = true;
} else if (button_y.read() && fxdCenter.i < FXD_FROM_INT(2)) {
fxdCenter.i += fxdRangeR / 8;
keyPressed = true;
}
} else {
// Otherwise zoom in/out
if (button_x.read()) {
if (button_y.read()) {
// Press X and Y together to reset to initial position
fxdRangeR = fxdInitialRangeR;
fxdCenter = fxdInitialCenter;
} else {
fxdRangeR /= 4;
fxdRangeR *= 3;
}
keyPressed = true;
} else if (button_y.read() && fxdRangeR < FXD_FROM_INT(3)) {
fxdRangeR *= 4;
fxdRangeR /= 3;
keyPressed = true;
}
}
}
#if defined(MULTICORE)
// key pressed, so stop core1
core1_stop();
#endif
}
return 0;
}