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 Bob Stone

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers main.cpp Source File

main.cpp

00001 /* Mandelbrot for mbed - April 2013
00002  * Uses Peter Drescher's library for Mikroelektronika's TFT-Proto screen, an inexpensive SPI-driven QVGA touch panel
00003  * Library: http://mbed.org/cookbook/SPI-driven-QVGA-TFT
00004  * Manufacturer's page for the screen: http://www.mikroe.com/add-on-boards/display/tft-proto/
00005  * UK distributor: http://www.mcustore.com/tft-proto-board.html
00006  * Partly adapted from Kamil Ondrousek's code for another screen: http://mbed.org/users/JLS/code/Mandelbrot-2/
00007  */
00008 
00009 
00010 /* Adaptation for displays without touch screen from Bob Stone's https://developer.mbed.org/users/RorschachUK/code/Mandelbrot/
00011  * Instead, using cheap rotary encoders with integral button.
00012  * Two encoders navigate the complex plane Z, a third changes the maxiterations value.
00013  * Status (Z, zoom, maxiters) is shown on bottom of screen.
00014  *
00015  * Max-iters can be changed on the fly so that its effect on detail & colour rendering can be evaluated quickly.
00016  * Drawing can be interrupted with any of the buttons to allow re-centering or re-zooming without having to wait for a full redraw.
00017  *
00018  * After the rendering is completed, or interrupted, these controls are available:
00019  * Turning either of the position encoders will move a small cursor around the complex plane.
00020  * Turning the third encoder will adjust maxiters.
00021  * The buttons atop the Zr&Zi encoders set new zoom out/in levels & initiate drawing at that new zoom.
00022  * The button atop the maxiters encoder initiates a re-draw at the current zoom.
00023  * State rendering/idle is indicated by a '+'/' ' in the bottom right corner.
00024  *
00025  * David Lowe Jan 2015
00026  */
00027 
00028 #include "mbed.h"
00029 #include "SPI_TFT_ILI9341.h"
00030 #include "Encoder.h"
00031 #include "Arial10x10.h"
00032 
00033 #define WIDTH 320
00034 #define HEIGHT 240
00035 #define ORIENTATION 3
00036 SPI_TFT_ILI9341 tt(SPI_MOSI, SPI_MISO, SPI_SCK, D9, D6, D7,"TFT"); // mosi, miso, sclk, cs, reset, dc
00037 
00038 // Wire all encoder commons to 3V3,
00039 // encoder A&B outputs to  A0 A1,    D4 D5,  D10 PB7
00040 TIM_Encoder_InitTypeDef encoder2, encoder3, encoder4;
00041 TIM_HandleTypeDef timer2, timer3, timer4;
00042 
00043 //Wire one side of button to 3V3 (same as encoder common connections)
00044 InterruptIn zoominbutton(D3); //@ encoder3: real (x) axis, button -> redraw zoom out
00045 InterruptIn zoomoutbutton(D2); //@ encoder2: imaginary (y) axis, button -> redraw zoom in
00046 InterruptIn zoomsamebutton(USER_BUTTON); //@ encoder4: iterations, button -> redraw zoom unchanged
00047 
00048 //Setup base Mandelbrot
00049 double centrex = -0.5;
00050 double centrey = -0.0;
00051 double zoom=0.5;
00052 int32_t log2zoom=-1;
00053 
00054 #define MAXITERS 8192
00055 #define MINITERS 64
00056 #define maxiters (MINITERS+TIM4->CNT)
00057 uint16_t lastiters=MINITERS;
00058 
00059 enum nextaction_t {
00060     idle=0,
00061     zoomin,
00062     zoomout,
00063     zoomsame,
00064     interrupted
00065 };
00066 
00067 nextaction_t buttonpressed;
00068 
00069 void GotZoomIn(void)
00070 {
00071     buttonpressed=zoomin;
00072 }
00073 
00074 void GotZoomOut(void)
00075 {
00076     buttonpressed=zoomout;
00077 }
00078 
00079 void GotZoomSame(void)
00080 {
00081     buttonpressed=zoomsame;
00082 }
00083 
00084 void UpdateStats(void)
00085 {
00086     //invert orientation & set cursor to top left (our bottom right at usual orientation)
00087     tt.set_orientation(ORIENTATION^2);
00088     tt.locate(0,0);
00089     //indicate "rendering active" & erase a few chars at line end
00090     tt.printf("+     ");
00091 
00092     //write current infos to bottom of screen: real, imag, log2(zoom), maxiters
00093     tt.set_orientation(ORIENTATION);
00094     tt.locate(0,240-10);
00095     tt.printf("%1.16f%+1.16fi %d %d", centrex, -centrey, log2zoom, maxiters);
00096 }
00097 
00098 void DrawMandelbrot(void)
00099 {
00100     double xmin = centrex - 1.0/zoom;
00101     double xmax = centrex + 1.0/zoom;
00102     double ymin = centrey - HEIGHT / (WIDTH * zoom);
00103     double ymax = centrey + HEIGHT / (WIDTH *zoom);
00104 
00105     double dx = (xmax - xmin) / WIDTH;
00106     double dy = (ymax - ymin) / HEIGHT;
00107 
00108     double y = ymin;
00109 
00110     double c;
00111     unsigned int cr, cg, cb;
00112 
00113     for (int j = 0; j < HEIGHT; j++) {
00114         double x = xmin;
00115 
00116         for (int i = 0;  i < WIDTH; i++) {
00117             double a = x;
00118             double b = y;
00119             int n = 0;
00120 
00121             if (maxiters!=lastiters) { //update stats if user changes maxiters on-the-fly
00122                 UpdateStats();
00123                 lastiters=maxiters;
00124             }
00125 
00126             while (n < maxiters) {
00127                 double aa = a * a;
00128                 double bb = b * b;
00129                 double twoab = 2.0 * a * b;
00130 
00131                 a = aa - bb + x;
00132                 b = twoab + y;
00133 
00134                 if(aa + bb > 16.0) {
00135                     break;
00136                 }
00137                 n++;
00138             }
00139 
00140             if (n == maxiters) {
00141                 //It's in the set - Black
00142                 tt.pixel(i,j,Black);
00143             } else {
00144                 //Not in the set - pick a colour
00145                 c = 3.0 * (maxiters-n)/maxiters;
00146                 cr = ((c < 1.0) ? 255 * ( 1.0 - c) : (c > 2.0) ? 255 * (c - 2.0) : 0);
00147                 cg = ((c < 1.0) ? 255 * c : (c > 2.0) ? 0 : 255 * (2.0 - c));
00148                 cb = ((c < 1.0) ? 0 : (c > 2.0) ? 255 * (3.0 - c) : 255 * (c - 1.0));
00149                 tt.pixel(i,j, RGB(cr,cg,cb));
00150             }
00151             x += dx;
00152             if (buttonpressed) return;
00153         }
00154         y += dy;
00155     }
00156 }
00157 
00158 void PixelBrot(int i, int j)
00159 {
00160     double xmin = centrex - 1.0/zoom;
00161     double xmax = centrex + 1.0/zoom;
00162     double ymin = centrey - HEIGHT / (WIDTH * zoom);
00163     double ymax = centrey + HEIGHT / (WIDTH *zoom);
00164 
00165     double dx = (xmax - xmin) / WIDTH;
00166     double dy = (ymax - ymin) / HEIGHT;
00167 
00168     double x=xmin+dx*i;
00169     double y=ymin+dy*j;
00170 
00171     double a = x;
00172     double b = y;
00173     int n = 0;
00174     double c;
00175     unsigned int cr, cg, cb;
00176 
00177     while (n < maxiters) {
00178         double aa = a * a;
00179         double bb = b * b;
00180         double twoab = 2.0 * a * b;
00181 
00182         a = aa - bb + x;
00183         b = twoab + y;
00184 
00185         if(aa + bb > 16.0) {
00186             break;
00187         }
00188         n++;
00189     }
00190 
00191     if (n == maxiters) {
00192         //It's in the set - Black
00193         tt.pixel(i,j,Black);
00194     } else {
00195         //Not in the set - pick a colour
00196         c = 3.0 * (maxiters-n)/maxiters;
00197         cr = ((c < 1.0) ? 255 * ( 1.0 - c) : (c > 2.0) ? 255 * (c - 2.0) : 0);
00198         cg = ((c < 1.0) ? 255 * c : (c > 2.0) ? 0 : 255 * (2.0 - c));
00199         cb = ((c < 1.0) ? 0 : (c > 2.0) ? 255 * (3.0 - c) : 255 * (c - 1.0));
00200         tt.pixel(i,j, RGB(cr,cg,cb));
00201     }
00202 
00203 }
00204 
00205 void EraseCursor(int x, int y)
00206 {
00207     int x1,x2,y1,y2;
00208 
00209     x1=x-3;
00210     if (x1<0) x1=0;
00211     if (x1>WIDTH-1) x1=WIDTH-1;
00212     x2=x+3;
00213     if (x2<0) x2=0;
00214     if (x2>WIDTH-1) x2=WIDTH-1;
00215 
00216     y1=y-3;
00217     if (y1<0) y1=0;
00218     if (y1>HEIGHT-1) y1=HEIGHT-1;
00219     y2=y+3;
00220     if (y2<0) y2=0;
00221     if (y2>HEIGHT-1) y2=HEIGHT-1;
00222 
00223     if (x<0) x=0;
00224     if (x>WIDTH-1) x=WIDTH-1;
00225 
00226     if (y<0) y=0;
00227     if (y>HEIGHT-1) y=HEIGHT-1;
00228 
00229     //Overwite crosshair
00230     int i;
00231     for(i=x1; i<=x2; i++) PixelBrot(i,y);
00232     for(i=y1; i<=y2; i++) PixelBrot(x,i);
00233 }
00234 
00235 void DrawCursor(int x, int y)
00236 {
00237     int x1,x2,y1,y2;
00238 
00239     x1=x-3;
00240     if (x1<0) x1=0;
00241     if (x1>WIDTH-1) x1=WIDTH-1;
00242     x2=x+3;
00243     if (x2<0) x2=0;
00244     if (x2>WIDTH-1) x2=WIDTH-1;
00245 
00246     y1=y-3;
00247     if (y1<0) y1=0;
00248     if (y1>HEIGHT-1) y1=HEIGHT-1;
00249     y2=y+3;
00250     if (y2<0) y2=0;
00251     if (y2>HEIGHT-1) y2=HEIGHT-1;
00252 
00253     if (x<0) x=0;
00254     if (x>WIDTH-1) x=WIDTH-1;
00255 
00256     if (y<0) y=0;
00257     if (y>HEIGHT-1) y=HEIGHT-1;
00258 
00259     //Draw crosshair
00260     tt.line(x,y1,x,y2,White);
00261     tt.line(x1,y,x2,y,White);
00262 }
00263 
00264 void InitEncoders(void)
00265 {
00266     //TIM1 is used by mbed for SPI clocking
00267 
00268     //A0 A1, common to 3V3
00269     //counting on both B-input only, 2 ticks per cycle, full 32-bit count
00270     EncoderInit(encoder2, timer2, TIM2, 0xffffffff, TIM_ENCODERMODE_TI2);
00271 
00272     //D4 D5, common to 3V3
00273     //counting on B-input only, 2 ticks per cycle, full 16-bit count
00274     EncoderInit(encoder3, timer3, TIM3, 0xffff, TIM_ENCODERMODE_TI2);
00275 
00276     //D10 PB7, common to 3V3
00277     //counting on both A&B edges, 4 ticks per cycle, max count used to limit maxiters to range (MINITERS..MAXITERS)
00278     EncoderInit(encoder4, timer4, TIM4, MAXITERS-MINITERS+3, TIM_ENCODERMODE_TI12);
00279 
00280     //TIM5 is used by mbed for us_ticker
00281 }
00282 
00283 int main()
00284 {
00285     //Setup screen
00286     tt.background(Black);    // set background to black
00287     tt.foreground(White);    // set chars to white
00288     tt.cls();                // clear the screen
00289     tt.set_font((unsigned char*)Arial10x10);
00290 
00291     InitEncoders();
00292 
00293     zoominbutton.rise(&GotZoomIn);
00294     zoominbutton.mode(PullDown);
00295     zoomoutbutton.rise(&GotZoomOut);
00296     zoomoutbutton.mode(PullDown);
00297     zoomsamebutton.rise(&GotZoomSame);
00298     zoomsamebutton.mode(PullDown);
00299 
00300     while(true) {
00301 
00302         //log status @ PC serial port
00303         printf("%1.18f%+1.18fi, %+d, %d\n\r", centrex, -centrey, log2zoom, maxiters);
00304 
00305         wait(0.3); //debounce
00306         buttonpressed=idle;
00307 
00308         UpdateStats();
00309 
00310         //Draw the thing, can be interrupted by a button press
00311         tt.set_orientation(ORIENTATION);
00312         DrawMandelbrot();
00313 
00314         wait(0.3); //debounce
00315         //flag that we should update status bar, but not commence new render
00316         if (buttonpressed) buttonpressed=interrupted;
00317 
00318         //throw away any user-twiddling of real & imaginary encoders made while rendering
00319         TIM2->CNT=0;
00320         TIM3->CNT=0;
00321 
00322         int16_t shiftx, shifty;
00323         int16_t lastshiftx=0, lastshifty=0;
00324 
00325         while(true) { // loop until a redraw button is pressed, then go draw something
00326 
00327             while(true) { //loop forever until real or imag or iters dials have moved, or any of the redraw buttons pressed
00328                 shiftx=(int16_t)TIM2->CNT/2;
00329                 shifty=(int16_t)TIM3->CNT/2;
00330                 if (buttonpressed) break;
00331                 if (shiftx!=lastshiftx || shifty!=lastshifty || maxiters!=lastiters) break;
00332             }
00333 
00334             if(maxiters==lastiters) { //we're here cos cursor moved (speeds up redraw when twiddling maxiter knob
00335                 EraseCursor(WIDTH/2+lastshiftx, HEIGHT/2+lastshifty);
00336                 DrawCursor(WIDTH/2+shiftx, HEIGHT/2+shifty);
00337             }
00338 
00339             lastshiftx=shiftx;
00340             lastshifty=shifty;
00341             lastiters=maxiters;
00342 
00343             if(buttonpressed==interrupted) buttonpressed=idle; //draw was halted, update status & wait for new controls
00344             if(buttonpressed) break; //go redraw whole screen with updated centre/zoom/maxiters
00345 
00346             //erase a few chars at line end
00347             tt.set_orientation(ORIENTATION^2);
00348             tt.locate(0,0);
00349             tt.printf("        ");
00350 
00351             //write ammended infos to bottom of screen: real, imag, log2(zoom), maxiters
00352             tt.set_orientation(ORIENTATION);
00353             tt.locate(0,240-10);
00354             tt.printf("%1.16f%+1.16fi %d %d",centrex+((double)(shiftx*2))/(WIDTH*zoom),
00355                       -(centrey+((double)(shifty*2))/(WIDTH*zoom)),
00356                       buttonpressed==1 ? log2zoom+2 : (buttonpressed==2 ? log2zoom-2 : log2zoom),
00357                       maxiters);
00358         }
00359 
00360         centrex += ((double)(shiftx*2))/(WIDTH*zoom);
00361         centrey += ((double)(shifty*2))/(WIDTH*zoom);
00362 
00363         if(buttonpressed==zoomin) {
00364             zoom*=4.0;
00365             log2zoom+=2;
00366         }
00367         if(buttonpressed==zoomout) {
00368             zoom/=4.0;
00369             log2zoom-=2;
00370         }
00371         //buttonpressed==zoomsame, redraw with zoom unchanged
00372     }
00373 }