![](/media/cache/img/default_profile.jpg.50x50_q85.jpg)
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..
Diff: main.cpp
- Revision:
- 2:b1169b84a563
- Parent:
- 1:a9641f372bea
- Child:
- 3:267e7130007d
diff -r a9641f372bea -r b1169b84a563 main.cpp --- a/main.cpp Wed Apr 17 18:14:12 2013 +0000 +++ b/main.cpp Tue Jan 20 15:10:25 2015 +0000 @@ -5,42 +5,128 @@ * 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.h" -#include "Arial12x12.h" -#include "touch_tft.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 -touch_tft tt(p18,p19,p16,p17,p11, p12, p13, p14, p15,"TFT"); // x+,x-,y+,y-,mosi, miso, sclk, cs, reset +// 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; -void drawMandelbrot(float centrex, float centrey, float zoom, int maxIter) { - float xmin = centrex - 1.0/zoom; - float xmax = centrex + 1.0/zoom; - float ymin = centrey - HEIGHT / (WIDTH * zoom); - float ymax = centrey + HEIGHT / (WIDTH *zoom); +#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; +} - float dx = (xmax - xmin) / WIDTH; - float dy = (ymax - ymin) / HEIGHT; +void GotZoomOut(void) +{ + buttonpressed=zoomout; +} - float y = ymin; +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("+ "); - float c; + //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++) { - float x = xmin; + double x = xmin; for (int i = 0; i < WIDTH; i++) { - float a = x; - float b = y; + double a = x; + double b = y; int n = 0; - while (n < maxIter) { - float aa = a * a; - float bb = b * b; - float twoab = 2.0 * a * b; + 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; @@ -51,57 +137,235 @@ n++; } - if (n == maxIter) { + 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 * (maxIter-n)/maxIter; + 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.claim(stdout); // send stdout to the TFT display tt.background(Black); // set background to black tt.foreground(White); // set chars to white tt.cls(); // clear the screen - tt.set_font((unsigned char*) Arial12x12); // select the font - tt.set_orientation(1); - tt.calibrate(); // calibrate the touch - point p; + tt.set_font((unsigned char*)Arial10x10); + + InitEncoders(); - tt.locate(0,0); - - //Setup base Mandelbrot - float centrex = -0.5; - float centrey = 0.0; - float zoom=0.5; - int maxiterations = 150; + zoominbutton.rise(&GotZoomIn); + zoominbutton.mode(PullDown); + zoomoutbutton.rise(&GotZoomOut); + zoomoutbutton.mode(PullDown); + zoomsamebutton.rise(&GotZoomSame); + zoomsamebutton.mode(PullDown); while(true) { - //Draw it - drawMandelbrot(centrex, centrey, zoom, maxiterations); - - //Wait for a touch - p=tt.get_touch(); - while(!tt.is_touched(p)) { - p=tt.get_touch(); + + //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); } - //Set new centre and zoom - p = tt.to_pixel(p); - centrex += (2.0 * p.x - WIDTH) / (zoom * WIDTH); - centrey += (2.0 * p.y - HEIGHT) / (zoom * WIDTH); - zoom *= 4.0; - tt.rect(p.x - WIDTH / 8.0, p.y - HEIGHT / 8.0, p.x + WIDTH / 8.0, p.y + HEIGHT / 8.0, Yellow); + + 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 } } \ No newline at end of file