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

Should have mentioned in the above: Encoder code is specific to the STM32F4, tested on Nucleo F401, should work on the Nucleo F411..

/media/uploads/gregeric/img_20150121_093103_744-1-.jpg /media/uploads/gregeric/img_20150121_093055_682-1-.jpg

Revision:
2:b1169b84a563
Parent:
1:a9641f372bea
Child:
3:267e7130007d
--- 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