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