Modified Bob Stone's code for ILI9341 QVGA TFT's without touch capability. Navigation is now done with rotary encoders - two for position, & one setting the maxiterations.
Dependencies: SPI_TFT_ILI9341 TFT_fonts mbed
Fork of Mandelbrot by
Should have mentioned in the above: Encoder code is specific to the STM32F4, tested on Nucleo F401, should work on the Nucleo F411..
main.cpp
- Committer:
- gregeric
- Date:
- 2015-01-20
- Revision:
- 2:b1169b84a563
- Parent:
- 1:a9641f372bea
- Child:
- 3:267e7130007d
File content as of revision 2:b1169b84a563:
/* Mandelbrot for mbed - April 2013
* Uses Peter Drescher's library for Mikroelektronika's TFT-Proto screen, an inexpensive SPI-driven QVGA touch panel
* Library: http://mbed.org/cookbook/SPI-driven-QVGA-TFT
* Manufacturer's page for the screen: http://www.mikroe.com/add-on-boards/display/tft-proto/
* UK distributor: http://www.mcustore.com/tft-proto-board.html
* Partly adapted from Kamil Ondrousek's code for another screen: http://mbed.org/users/JLS/code/Mandelbrot-2/
*/
/* Adaptation for displays without touch screen from Bob Stone's https://developer.mbed.org/users/RorschachUK/code/Mandelbrot/
* Instead, using cheap rotary encoders with integral button.
* Two encoders navigate the complex plane Z, a third changes the maxiterations value.
* Status (Z, zoom, maxiters) is shown on bottom of screen.
*
* Max-iters can be changed on the fly so that its effect on detail & colour rendering can be evaluated quickly.
* Drawing can be interrupted with any of the buttons to allow re-centering or re-zooming without having to wait for a full redraw.
*
* After the rendering is completed, or interrupted, these controls are available:
* Turning either of the position encoders will move a small cursor around the complex plane.
* Turning the third encoder will adjust maxiters.
* The buttons atop the Zr&Zi encoders set new zoom out/in levels & initiate drawing at that new zoom.
* The button atop the maxiters encoder initiates a re-draw at the current zoom.
* State rendering/idle is indicated by a '+'/' ' in the bottom right corner.
*
* David Lowe Jan 2015
*/
#include "mbed.h"
#include "SPI_TFT_ILI9341.h"
#include "Encoder.h"
#include "Arial10x10.h"
#define WIDTH 320
#define HEIGHT 240
#define ORIENTATION 3
SPI_TFT_ILI9341 tt(SPI_MOSI, SPI_MISO, SPI_SCK, D9, D6, D7,"TFT"); // mosi, miso, sclk, cs, reset, dc
// Wire all encoder commons to 3V3,
// encoder A&B outputs to A0 A1, D4 D5, D10 PB7
TIM_Encoder_InitTypeDef encoder2, encoder3, encoder4;
TIM_HandleTypeDef timer2, timer3, timer4;
//Wire one side of button to 3V3 (same as encoder common connections)
InterruptIn zoominbutton(D3); //@ encoder3: real (x) axis, button -> redraw zoom out
InterruptIn zoomoutbutton(D2); //@ encoder2: imaginary (y) axis, button -> redraw zoom in
InterruptIn zoomsamebutton(USER_BUTTON); //@ encoder4: iterations, button -> redraw zoom unchanged
//Setup base Mandelbrot
double centrex = -0.5;
double centrey = -0.0;
double zoom=0.5;
int32_t log2zoom=-1;
#define MAXITERS 8192
#define MINITERS 64
#define maxiters (MINITERS+TIM4->CNT)
uint16_t lastiters=MINITERS;
enum nextaction {
idle=0,
zoomin,
zoomout,
zoomsame,
interrupted
};
enum nextaction buttonpressed;
void GotZoomIn(void)
{
buttonpressed=zoomin;
}
void GotZoomOut(void)
{
buttonpressed=zoomout;
}
void GotZoomSame(void)
{
buttonpressed=zoomsame;
}
void UpdateStats(void)
{
//invert orientation & set cursor to top left (our bottom right at usual orientation)
tt.set_orientation(ORIENTATION^2);
tt.locate(0,0);
//indicate "rendering active" & erase a few chars at line end
tt.printf("+ ");
//write current infos to bottom of screen: real, imag, log2(zoom), maxiters
tt.set_orientation(ORIENTATION);
tt.locate(0,240-10);
tt.printf("%1.16f%+1.16fi %d %d", centrex, -centrey, log2zoom, maxiters);
}
void DrawMandelbrot(void)
{
double xmin = centrex - 1.0/zoom;
double xmax = centrex + 1.0/zoom;
double ymin = centrey - HEIGHT / (WIDTH * zoom);
double ymax = centrey + HEIGHT / (WIDTH *zoom);
double dx = (xmax - xmin) / WIDTH;
double dy = (ymax - ymin) / HEIGHT;
double y = ymin;
double c;
unsigned int cr, cg, cb;
for (int j = 0; j < HEIGHT; j++) {
double x = xmin;
for (int i = 0; i < WIDTH; i++) {
double a = x;
double b = y;
int n = 0;
if (maxiters!=lastiters) { //update stats if user changes maxiters on-the-fly
UpdateStats();
lastiters=maxiters;
}
while (n < maxiters) {
double aa = a * a;
double bb = b * b;
double twoab = 2.0 * a * b;
a = aa - bb + x;
b = twoab + y;
if(aa + bb > 16.0) {
break;
}
n++;
}
if (n == maxiters) {
//It's in the set - Black
tt.pixel(i,j,Black);
} else {
//Not in the set - pick a colour
c = 3.0 * (maxiters-n)/maxiters;
cr = ((c < 1.0) ? 255 * ( 1.0 - c) : (c > 2.0) ? 255 * (c - 2.0) : 0);
cg = ((c < 1.0) ? 255 * c : (c > 2.0) ? 0 : 255 * (2.0 - c));
cb = ((c < 1.0) ? 0 : (c > 2.0) ? 255 * (3.0 - c) : 255 * (c - 1.0));
tt.pixel(i,j, RGB(cr,cg,cb));
}
x += dx;
if (buttonpressed) return;
}
y += dy;
}
}
void PixelBrot(int i, int j)
{
double xmin = centrex - 1.0/zoom;
double xmax = centrex + 1.0/zoom;
double ymin = centrey - HEIGHT / (WIDTH * zoom);
double ymax = centrey + HEIGHT / (WIDTH *zoom);
double dx = (xmax - xmin) / WIDTH;
double dy = (ymax - ymin) / HEIGHT;
double x=xmin+dx*i;
double y=ymin+dy*j;
double a = x;
double b = y;
int n = 0;
double c;
unsigned int cr, cg, cb;
while (n < maxiters) {
double aa = a * a;
double bb = b * b;
double twoab = 2.0 * a * b;
a = aa - bb + x;
b = twoab + y;
if(aa + bb > 16.0) {
break;
}
n++;
}
if (n == maxiters) {
//It's in the set - Black
tt.pixel(i,j,Black);
} else {
//Not in the set - pick a colour
c = 3.0 * (maxiters-n)/maxiters;
cr = ((c < 1.0) ? 255 * ( 1.0 - c) : (c > 2.0) ? 255 * (c - 2.0) : 0);
cg = ((c < 1.0) ? 255 * c : (c > 2.0) ? 0 : 255 * (2.0 - c));
cb = ((c < 1.0) ? 0 : (c > 2.0) ? 255 * (3.0 - c) : 255 * (c - 1.0));
tt.pixel(i,j, RGB(cr,cg,cb));
}
}
void EraseCursor(int x, int y)
{
int x1,x2,y1,y2;
x1=x-3;
if (x1<0) x1=0;
if (x1>WIDTH-1) x1=WIDTH-1;
x2=x+3;
if (x2<0) x2=0;
if (x2>WIDTH-1) x2=WIDTH-1;
y1=y-3;
if (y1<0) y1=0;
if (y1>HEIGHT-1) y1=HEIGHT-1;
y2=y+3;
if (y2<0) y2=0;
if (y2>HEIGHT-1) y2=HEIGHT-1;
if (x<0) x=0;
if (x>WIDTH-1) x=WIDTH-1;
if (y<0) y=0;
if (y>HEIGHT-1) y=HEIGHT-1;
//Overwite crosshair
int i;
for(i=x1; i<=x2; i++) PixelBrot(i,y);
for(i=y1; i<=y2; i++) PixelBrot(x,i);
}
void DrawCursor(int x, int y)
{
int x1,x2,y1,y2;
x1=x-3;
if (x1<0) x1=0;
if (x1>WIDTH-1) x1=WIDTH-1;
x2=x+3;
if (x2<0) x2=0;
if (x2>WIDTH-1) x2=WIDTH-1;
y1=y-3;
if (y1<0) y1=0;
if (y1>HEIGHT-1) y1=HEIGHT-1;
y2=y+3;
if (y2<0) y2=0;
if (y2>HEIGHT-1) y2=HEIGHT-1;
if (x<0) x=0;
if (x>WIDTH-1) x=WIDTH-1;
if (y<0) y=0;
if (y>HEIGHT-1) y=HEIGHT-1;
//Draw crosshair
tt.line(x,y1,x,y2,White);
tt.line(x1,y,x2,y,White);
}
void InitEncoders(void)
{
//TIM1 is used by mbed for SPI clocking
//A0 A1, common to 3V3
//counting on both B-input only, 2 ticks per cycle, full 32-bit count
EncoderInit(encoder2, timer2, TIM2, 0xffffffff, TIM_ENCODERMODE_TI2);
//D4 D5, common to 3V3
//counting on B-input only, 2 ticks per cycle, full 16-bit count
EncoderInit(encoder3, timer3, TIM3, 0xffff, TIM_ENCODERMODE_TI2);
//D10 PB7, common to 3V3
//counting on both A&B edges, 4 ticks per cycle, max count used to limit maxiters to range (MINITERS..MAXITERS)
EncoderInit(encoder4, timer4, TIM4, MAXITERS-MINITERS+3, TIM_ENCODERMODE_TI12);
//TIM5 is used by mbed for us_ticker
}
int main()
{
//Setup screen
tt.background(Black); // set background to black
tt.foreground(White); // set chars to white
tt.cls(); // clear the screen
tt.set_font((unsigned char*)Arial10x10);
InitEncoders();
zoominbutton.rise(&GotZoomIn);
zoominbutton.mode(PullDown);
zoomoutbutton.rise(&GotZoomOut);
zoomoutbutton.mode(PullDown);
zoomsamebutton.rise(&GotZoomSame);
zoomsamebutton.mode(PullDown);
while(true) {
//log status @ PC serial port
printf("%1.18f%+1.18fi, %+d, %d\n\r", centrex, -centrey, log2zoom, maxiters);
wait(0.2); //debounce
buttonpressed=idle;
UpdateStats();
//Draw the thing, can be interrupted by a button press
tt.set_orientation(ORIENTATION);
DrawMandelbrot();
wait(0.2); //debounce
//flag that we should update status bar, but not commence new render
if (buttonpressed) buttonpressed=interrupted;
//throw away any user-twiddling of real & imaginary encoders made while rendering
TIM2->CNT=0;
TIM3->CNT=0;
int16_t shiftx, shifty;
int16_t lastshiftx=0, lastshifty=0;
while(true) { // loop until a redraw button is pressed, then go draw something
while(true) { //loop forever until real or imag or iters dials have moved, or any of the redraw buttons pressed
shiftx=(int16_t)TIM2->CNT/2;
shifty=(int16_t)TIM3->CNT/2;
if (buttonpressed) break;
if (shiftx!=lastshiftx || shifty!=lastshifty || maxiters!=lastiters) break;
}
EraseCursor(WIDTH/2+lastshiftx, HEIGHT/2+lastshifty);
DrawCursor(WIDTH/2+shiftx, HEIGHT/2+shifty);
lastshiftx=shiftx;
lastshifty=shifty;
lastiters=maxiters;
if(buttonpressed==interrupted) buttonpressed=idle; //draw was halted, update status & wait for new controls
if(buttonpressed) break; //go redraw whole screen with updated centre/zoom/maxiters
//erase a few chars at line end
tt.set_orientation(ORIENTATION^2);
tt.locate(0,0);
tt.printf(" ");
//write ammended infos to bottom of screen: real, imag, log2(zoom), maxiters
tt.set_orientation(ORIENTATION);
tt.locate(0,240-10);
tt.printf("%1.16f%+1.16fi %d %d",centrex+((double)(shiftx*2))/(WIDTH*zoom),
-(centrey+((double)(shifty*2))/(WIDTH*zoom)),
buttonpressed==1 ? log2zoom+2 : (buttonpressed==2 ? log2zoom-2 : log2zoom),
maxiters);
}
centrex += ((double)(shiftx*2))/(WIDTH*zoom);
centrey += ((double)(shifty*2))/(WIDTH*zoom);
if(buttonpressed==zoomin) {
zoom*=4.0;
log2zoom+=2;
}
if(buttonpressed==zoomout) {
zoom/=4.0;
log2zoom-=2;
}
//buttonpressed==zoomsame, redraw with zoom unchanged
}
}
