/* einfache WQVGA-TFT-Ansteuerung mit STM32F4 480x270 mit 64 Farben

Der Bildspeicher liegt im internen RAM (128k), der mit höchster Geschwindigkeit
beschrieben werden kann. Die Ausgabe des Bildes erfolgt per DMA über das FSMC-Interface,
wobei nur D0-D7, A16, A17 und /WR + /CS benötigt werden.
Pro Bildpunkt werden 2 x Rot, 2 x Blau, 2 x Grün wie folgt angeschlossen:

FSMC-Leitung    TFT-Anschluß
    D0      ->  R1,R3,R5
    D1      ->  R0,R2,R4
    D2      ->  G1,G3,G5
    D3      ->  G0,G2,G4
    D4      ->  B1,B3,B5
    D5      ->  B0,B2,B4
    D6      ->  tbd (invers)
    D7      ->  tbd (Blinken)
    /WR     ->  DCLK
    /CS     ->  (alternativ zu /WR)
    A16     ->  Hsync
    A17     ->  Vsync
    PA5     ->  LED Hintergrund

Eine nähere Beschreibung mit Schaltbild befindet sich hier:
http://www.mino-elektronik.de/TFT-direct-drive/TFT-direct-drive.htm#tft-b

2012-12-12 Michael Nowak
www.mino-elektronik.de
Alle Angaben ohne Gewähr.
*/


#include "stm32f4xx_gpio.h"
#include "stm32f4xx_rcc.h"
#include "stm32f4xx_fsmc.h"
#include "stm32f4xx_dma.h"

#include "cp850-8x16.dat"                               // Datei mit Zeichensatz

#define TFT_ADR 0x60000000                              // CS-Bereich

#define SCHWARZ                 0x00
#define ROT                     0x03
#define GRUEN                   0x0c
#define BLAU                    0x30
#define WEISS                   (ROT+BLAU+GRUEN)
#define GELB                    (ROT+GRUEN)
#define TUERKIS                 (0x2d)
#define VIOLETT                 (BLAU+GRUEN)
#define HELLGRAU                0x2a
#define DUNKELGRAU              0x15

// für WQVGA
#define WQVGA

#ifdef WQVGA
#define SIZE_X                  480
#define SIZE_Y                  270
#define X_START_POS             43                      // x-werte ab 43
#define Y_START_POS             14                      // Zeilen ab 14
#define THP                     2                       // Hsync länge
#define TVP                     2                       // Vsync länge in H-Zeilen
#else
#define SIZE_X			320			// für QVGA
#define SIZE_Y			240
#define X_START_POS		68			// x-werte ab 68
#define Y_START_POS		19			// Zeilen ab 19
#define THP			30			// Hsync länge
#define	TVP			3			// Vsync länge in H-Zeilen
#endif

#define MAX_X                   (SIZE_X-1)
#define MAX_Y                   (SIZE_Y-1)

#define PUFFER_X                0                       // bei Bedarf
#define BILDBREITE              (SIZE_X + PUFFER_X)

#define H_LINE_LEN              (BILDBREITE+X_START_POS)// gesamte Zeile
#define V_LEN                   (SIZE_Y+Y_START_POS)
#define HSYNC_ADR_MASKE         0x20000                 // A16 aktiv, wenn 0
#define VSYNC_ADR_MASKE         0x40000                 // A17 aktiv, wenn 0

#define VG  1                                           // Vordergrund wird gezeichnet
#define HG  2                                           // Hintergrund wird gezeichnet

uint8_t zeichen_modus = VG+HG;                          // Vorgabe: Vordergrund+Hintergrund
uint16_t line_cnt;                                      // für DMA
uint32_t akt_bild_adresse;                              // für DMA
int8_t schwarzes_pixel=0;                               // für DMA

uint8_t *aktuelle_schrift = (uint8_t *)schrift_8x16;    // aktiver Zeichensatz
#define ZEICHEN_LAENGE          aktuelle_schrift[0]     // Zeichenlänge
#define ZEICHEN_HOEHE           aktuelle_schrift[1]     // Zeichenhöhe

uint8_t vordergrund = SCHWARZ, hintergrund = WEISS;     // default
int8_t zoom_x = 1, zoom_y = 1;
int16_t akt_x=0, akt_y=0;                               // für Schriftausgabe

uint8_t bildspeicher[X_START_POS + SIZE_X*SIZE_Y];      // mit H-sync Vorlauf
uint8_t *bild = &bildspeicher[X_START_POS];             // eff. Bildanfang

void set_xy(int16_t x, int16_t y)
{
    if(x > MAX_X) x = 0;
    if(y > MAX_Y) y = 0;
    akt_x = x;
    akt_y = y;
}

void schreibe_zeichen_dyn(uint8_t c, uint8_t farbe)
{       
uint8_t zeile, spalte;
uint8_t *adr, *temp_adr;        
uint8_t muster, i, j;
uint8_t *zeichen = &aktuelle_schrift[(c+1)*ZEICHEN_HOEHE]; // basisadresse des Zeichens
uint16_t eff_x, eff_y;
    eff_y = akt_y;
    zeile = ZEICHEN_HOEHE;
    while(zeile--) {                                    // gesamte höhe
        eff_x = akt_x;
        muster = *zeichen++;                            // daten der Zeile
        adr = temp_adr; 
        spalte = ZEICHEN_LAENGE;
        while(spalte--) {
            temp_adr = adr = (uint8_t *) (bild + (eff_x+eff_y*BILDBREITE));
            i= 0;
            while(i < zoom_y) {                         // zoom y
                j = 0;
                while(j < zoom_x) {                     // zoom x
                    if((eff_x+j) <= MAX_X && (eff_y+i) <= MAX_Y) {
                        if(muster & 0x80) {             // Vordergrund zeichnen
                            if(zeichen_modus & VG) *adr = farbe;
                        } else {                        // Hintergrund zeichnen
                            if(zeichen_modus & HG) *adr = hintergrund;
                        }       
                        adr++;
                    }
                    j++;
                }               // while(j)
                temp_adr += BILDBREITE; 
                adr = temp_adr;
                i++;
            }                   // while(i)
            muster <<= 1;                               // nächstes pixel
            eff_x += zoom_x;
        }                       // while(spalte)
        eff_y += zoom_y;                                // nächste Zeile
    }                           // while(zeile)
}

void string_ausgabe(int8_t *s, uint8_t farbe)
{
uint8_t c;
    while(c = *s++) {
        schreibe_zeichen_dyn(c,farbe);
        akt_x += ZEICHEN_LAENGE * zoom_x;
    }
}               

void schreibe_string(int8_t *s, uint8_t farbe)
{
    zoom_x = zoom_y = 1;
    string_ausgabe(s,farbe);
}

void schreibe_string2(int8_t *s, uint8_t farbe)
{
    zoom_x = zoom_y = 2;
    string_ausgabe(s,farbe);
}

void schreibe_string3(int8_t *s, uint8_t farbe)
{
    zoom_x = zoom_y = 3;
    string_ausgabe(s,farbe);
}

void schreibe_string_mittig(int8_t *s, uint8_t farbe)
{
uint8_t c;
    c=0; while(s[c]) c++;
    akt_x = (MAX_X - c * ZEICHEN_LAENGE*zoom_x)/2;      
    while(c = *s++) {
        schreibe_zeichen_dyn(c,farbe);
        akt_x += ZEICHEN_LAENGE*zoom_x;
    }           
}

void tft_str_einmitten(int8_t *s, uint16_t y_pos, int8_t farbe, int8_t zoom)
{
uint16_t temp_y, temp_x_zoom, temp_y_zoom;
    temp_y = akt_y;
    temp_x_zoom = zoom_x;
    temp_y_zoom = zoom_y;
    zoom_x = zoom_y = zoom;
    akt_y = y_pos;
    schreibe_string_mittig(s, farbe);
    akt_y = temp_y;
    zoom_x = temp_x_zoom;
    zoom_y = temp_y_zoom;
}



void set_pixel(uint16_t x, uint16_t y, uint8_t farbe)
{
    if(x <= MAX_X && y <= MAX_Y)                
        bild[(x + y*BILDBREITE)] = farbe;
}

void horizontale_linie(uint16_t x1, uint16_t x2, uint16_t y, uint8_t farbe)
{
uint16_t temp;
uint8_t *adr;   
    if(x1 > MAX_X) x1 = MAX_X;
    if(x2 > MAX_X) x2 = MAX_X;
    if(y > MAX_Y) y = MAX_Y;

    if(x1 > x2) {
        temp = x1;
        x1 = x2;
        x2 = temp;
    }   
    adr = (bild + (x1+y*BILDBREITE));
    while(x1 <= x2) {
        *adr = farbe;
        adr++;
        x1++;
    }
}       

int16_t sgn(int16_t x){
    return (x > 0) ? 1 : (x < 0) ? -1 : 0;
}

void zeichne_linie(int16_t xstart, int16_t ystart, int16_t xend, int16_t yend, int8_t farbe)
{
int16_t x, y, t, dx, dy, incx, incy, pdx, pdy, ddx, ddy, es, el, err;

    dx = xend - xstart;
    dy = yend - ystart;
    incx = sgn(dx);
    incy = sgn(dy);
    if(dx<0) dx = -dx;
    if(dy<0) dy = -dy;

    if (dx>dy){
        pdx=incx; pdy=0;
        ddx=incx; ddy=incy;
        es =dy;   el =dx;
    } else {
        pdx=0;    pdy=incy;
        ddx=incx; ddy=incy;
        es =dx;   el =dy;
    }

    x = xstart;
    y = ystart;
    err = el/2;
    set_pixel(x,y, farbe);

    for(t=0; t<el; ++t) {
        err -= es;
        if(err<0) {
            err += el;
            x += ddx;
            y += ddy;
        } else {
            x += pdx;
            y += pdy;
        }
        set_pixel(x,y, farbe);
    }
}

void zeichne_rechteck(int16_t x1, int16_t y1, int16_t x2, int16_t y2, int8_t farbe)
{
    zeichne_linie(x1, y1, x1, y2, farbe);       // links senkrecht
    zeichne_linie(x2, y1, x2, y2, farbe);       // rechts senkrecht
    zeichne_linie(x1, y1, x2, y1, farbe);       // oben
    zeichne_linie(x1, y2, x2, y2, farbe);       // unten
}

void fuelle_rechteck(int16_t x1, int16_t y1, int16_t x2, int16_t y2, int8_t farbe)
{
int16_t temp;   
    if(y1 > y2) {
        temp = y1;
        y1 = y2;
        y2 = temp;
    }
    while(y1 <= y2) {
        horizontale_linie(x1, x2, y1, farbe);
        y1++;
    }
}       

void loesche_tft(uint8_t farbe)
{
    fuelle_rechteck(0,0,MAX_X,MAX_Y,farbe);
}

static void fsmc_init(void)
{
        GPIO_InitTypeDef GPIO_InitStructure;
        FSMC_NORSRAMInitTypeDef  FSMC_NORSRAMInitStructure;
        FSMC_NORSRAMTimingInitTypeDef FSMC_NORSRAMTimingInitStructure;
        
        /* Enable GPIOs clock */
        RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD | RCC_AHB1Periph_GPIOE , ENABLE);
        
        /* Enable FSMC clock */
        RCC_AHB3PeriphClockCmd(RCC_AHB3Periph_FSMC, ENABLE);

        /* GPIOD configuration */
        /* Set PD.00(D2), PD.01(D3),  PD.05(NWE), PD.07 (/CS),
           PD.11(A16), PD.12(A17), PD.14(D0), PD.15(D1) as alternate function push pull */
        GPIO_PinAFConfig(GPIOD, GPIO_PinSource0, GPIO_AF_FSMC);
        GPIO_PinAFConfig(GPIOD, GPIO_PinSource1, GPIO_AF_FSMC);
        GPIO_PinAFConfig(GPIOD, GPIO_PinSource5, GPIO_AF_FSMC);
        GPIO_PinAFConfig(GPIOD, GPIO_PinSource7, GPIO_AF_FSMC);
        GPIO_PinAFConfig(GPIOD, GPIO_PinSource11, GPIO_AF_FSMC);
        GPIO_PinAFConfig(GPIOD, GPIO_PinSource12, GPIO_AF_FSMC);
        GPIO_PinAFConfig(GPIOD, GPIO_PinSource14, GPIO_AF_FSMC);
        GPIO_PinAFConfig(GPIOD, GPIO_PinSource15, GPIO_AF_FSMC);
        
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 |  GPIO_Pin_5 | GPIO_Pin_7 |
                      GPIO_Pin_11 | GPIO_Pin_12 | GPIO_Pin_14 | GPIO_Pin_15;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
        GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
        GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_UP;
        
        GPIO_Init(GPIOD, &GPIO_InitStructure);
        
        /* Set PE.07(D4), PE.08(D5), PE.09(D6), PE.10(D7) as alternate function push pull */    
        /* GPIOE configuration */
        GPIO_PinAFConfig(GPIOE, GPIO_PinSource7 , GPIO_AF_FSMC);
        GPIO_PinAFConfig(GPIOE, GPIO_PinSource8 , GPIO_AF_FSMC);
        GPIO_PinAFConfig(GPIOE, GPIO_PinSource9 , GPIO_AF_FSMC);
        GPIO_PinAFConfig(GPIOE, GPIO_PinSource10 , GPIO_AF_FSMC);
        
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7 | GPIO_Pin_8  | GPIO_Pin_9  | GPIO_Pin_10;
        GPIO_Init(GPIOE, &GPIO_InitStructure);
                
        FSMC_NORSRAMInitStructure.FSMC_Bank = FSMC_Bank1_NORSRAM1;
        FSMC_NORSRAMInitStructure.FSMC_DataAddressMux = FSMC_DataAddressMux_Disable;
        FSMC_NORSRAMInitStructure.FSMC_MemoryType = FSMC_MemoryType_SRAM;
        FSMC_NORSRAMInitStructure.FSMC_MemoryDataWidth = FSMC_MemoryDataWidth_16b;
        FSMC_NORSRAMInitStructure.FSMC_BurstAccessMode = FSMC_BurstAccessMode_Disable;
        FSMC_NORSRAMInitStructure.FSMC_WaitSignalPolarity = FSMC_WaitSignalPolarity_Low;
        FSMC_NORSRAMInitStructure.FSMC_WrapMode = FSMC_WrapMode_Disable;
        FSMC_NORSRAMInitStructure.FSMC_WaitSignalActive = FSMC_WaitSignalActive_BeforeWaitState;
        FSMC_NORSRAMInitStructure.FSMC_WriteOperation = FSMC_WriteOperation_Enable;
        FSMC_NORSRAMInitStructure.FSMC_WaitSignal = FSMC_WaitSignal_Disable;
        FSMC_NORSRAMInitStructure.FSMC_AsynchronousWait = FSMC_AsynchronousWait_Disable;
        FSMC_NORSRAMInitStructure.FSMC_ExtendedMode = FSMC_ExtendedMode_Disable;
        FSMC_NORSRAMInitStructure.FSMC_WriteBurst = FSMC_WriteBurst_Disable;
        FSMC_NORSRAMInitStructure.FSMC_ReadWriteTimingStruct = &FSMC_NORSRAMTimingInitStructure;
        
        FSMC_NORSRAMTimingInitStructure.FSMC_AddressSetupTime = 0;
        FSMC_NORSRAMTimingInitStructure.FSMC_AddressHoldTime = 0;       
        FSMC_NORSRAMTimingInitStructure.FSMC_DataSetupTime = 8;
        FSMC_NORSRAMTimingInitStructure.FSMC_BusTurnAroundDuration = 0x04;
        FSMC_NORSRAMTimingInitStructure.FSMC_CLKDivision = 0x0f;
        FSMC_NORSRAMTimingInitStructure.FSMC_DataLatency = 0x0f;
        FSMC_NORSRAMTimingInitStructure.FSMC_AccessMode = FSMC_AccessMode_A;
        FSMC_NORSRAMInitStructure.FSMC_WriteTimingStruct = &FSMC_NORSRAMTimingInitStructure;    
        
        FSMC_NORSRAMInit(&FSMC_NORSRAMInitStructure);
        FSMC_NORSRAMCmd(FSMC_Bank1_NORSRAM1, ENABLE);


        RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);

        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
        GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
        GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
        GPIO_Init(GPIOA, &GPIO_InitStructure);
        GPIO_ResetBits(GPIOA , GPIO_Pin_5);                     // Hintergrundbeleuchtung einschalten

}

void init_dma2_0(void)
{
        RCC->AHB1ENR   |= 0x00400000;                           // DMA2 aktivieren
        DMA2->LIFCR = 0xfd;                                     // alle int-flags loeschen
        DMA2_Stream0->CR = 0x490;                               // 8bit-modus  Adressmodus PINC=0 , MINC = 1
        DMA2_Stream0->NDTR = H_LINE_LEN;
        akt_bild_adresse = (uint32_t)&bildspeicher;
        DMA2_Stream0->PAR = (uint32_t)akt_bild_adresse;
        DMA2_Stream0->M0AR = (uint32_t)(TFT_ADR + HSYNC_ADR_MASKE - THP);
        NVIC_EnableIRQ(DMA2_Stream0_IRQn);
        NVIC_SetPriority(DMA2_Stream0_IRQn,0x4);
        DMA2_Stream0->CR |= 0x1;
        __enable_interrupt();
}

void DMA2_Stream0_IRQHandler(void)
{
        DMA2->LIFCR = 0xfd;                                     //alle int-flags loeschen
        if(line_cnt >= V_LEN) {
                line_cnt = 0;
                akt_bild_adresse = (uint32_t)&bildspeicher;
        }       
        if(line_cnt < TVP) {
                DMA2_Stream0->M0AR = (uint32_t)(TFT_ADR + HSYNC_ADR_MASKE - THP);
        } else {        
                DMA2_Stream0->M0AR = (uint32_t)(TFT_ADR + HSYNC_ADR_MASKE + VSYNC_ADR_MASKE - THP);
        }       

        if(line_cnt < Y_START_POS) {
                DMA2_Stream0->CR = 0x490;                       // 8bit-modus  Adressmodus PINC=0 , MINC = 1
                DMA2_Stream0->PAR = (uint32_t)&schwarzes_pixel;
        } else {
                DMA2_Stream0->CR = 0x690;                       // 8bit-modus  Adressmodus PINC =1 , MINC = 1
                DMA2_Stream0->PAR = (uint32_t)akt_bild_adresse;
                akt_bild_adresse += (BILDBREITE);
        }       
        DMA2_Stream0->NDTR = H_LINE_LEN;
        line_cnt++;
        DMA2_Stream0->CR |= 0x1;
}

int main (void)
{
int32_t i;      
uint8_t farbe;

    fsmc_init();
    init_dma2_0();

    loesche_tft(WEISS);
    fuelle_rechteck(0,0,MAX_X,40,ROT);
    fuelle_rechteck(0,40,MAX_X,80,GRUEN);
    fuelle_rechteck(0,80,MAX_X,120,BLAU);
    for(farbe = 0; farbe < 64; farbe++) {
        fuelle_rechteck(20 + farbe*7, MAX_Y-50, 20 + (farbe+1)*7, MAX_Y-10, farbe);
    }

    aktuelle_schrift = (uint8_t *)schrift_8x16;
    zeichen_modus = VG;                                         // nur Vordergrund zeichnen
    zoom_x = zoom_y = 3;
    set_xy(20,20);
    schreibe_string_mittig("STM32F4 > WQVGA-TFT",SCHWARZ);      
    set_xy(20,90);
    hintergrund = GELB;
    zoom_x = zoom_y = 2;
    zeichen_modus = VG+HG;
    schreibe_string_mittig(" Wiederholrate 80 Bilder/s ",ROT);  
    set_xy(20,120);
    schreibe_string_mittig(" 64 Farben; 3x2 Bit/Pixel ",BLAU);
        
    hintergrund = WEISS;
    do {
        set_xy(10,160);
        schreibe_string("Zeichenausgabe 8x16 50us/Zeichen  Optimierung: max. speed",SCHWARZ);   
        set_xy(10,180);
        schreibe_string2("Zeichenausgabe 16x32 115us/Z.",SCHWARZ);      
//      set_xy(10,215);
//      schreibe_string3("zoom_xy = 3: 24x48",SCHWARZ); 

        for(i=0;i<100000;i++);
    } while(1);
}
