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 } }