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

main.cpp

Committer:
gregeric
Date:
2015-01-22
Revision:
3:267e7130007d
Parent:
2:b1169b84a563

File content as of revision 3:267e7130007d:

/* 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_t {
    idle=0,
    zoomin,
    zoomout,
    zoomsame,
    interrupted
};

nextaction_t 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.3); //debounce
        buttonpressed=idle;

        UpdateStats();

        //Draw the thing, can be interrupted by a button press
        tt.set_orientation(ORIENTATION);
        DrawMandelbrot();

        wait(0.3); //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;
            }

            if(maxiters==lastiters) { //we're here cos cursor moved (speeds up redraw when twiddling maxiter knob
                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
    }
}