Okay, ages ago I promised improved version of the original code for using STM32F429/STM32F439 LCD controller. Like previously mentioned same code works with newer STM32F469/STM32F479 too and most likely on almost all other chips on the family.
So here it goes. Sorry this isn't a drop-in project, taking all code out from my libraries (with all supporting code included) would take a bit too much of time, so you'll have to settle for generalized version, you'll need to adapt it to your specific display; mostly with defines listed at the top. Note that I use the pin mappings for the 429 discovery board, but display size does not match it so this won't work there directly. I left also out the SPI initialization as most display modules don't either have it or use very different settings.
Main change here (compared to previous one) are the defines; now they are defined in same way they typically are in datasheets so adaptation for different displays should be easier.
#include "stm32f4_discovery.h" #include "stm32f4xx_conf.h" // Display module timing configuration. These are different for // each display module; see module's datasheet for details.
#define DISP_WIDTH (320) /* active area width */ #define DISP_HEIGHT (240) /* active area height */ #define DISP_HSYNC_W (3) /* HSYNC width, in clocks */ #define DISP_VSYNC_H (3) /* VSYNC height, in rows */ #define DISP_HORIZ_BACKPORCH (65) /* Horizontal backporch, excluding HSYNC. Data starts after backporch. */ #define DISP_VERT_BACKPORCH (15) /* Vertical backporch, excluding VSYNC */ #define DISP_HORIZ_FRONTPORCH (22) /* Horizontal frontporch (after data on scanline) */ #define DISP_VERT_FRONTPORCH (42) /* Vertical frontporch */
unsigned char dispFrameBuff[DISP_WIDTH * DISP_HEIGHT];
#define LTDC_CFG_PINCOUNT 22 const int ltdc_lcd_pins[] = // port AF pin. For some reason LCD has pins in both AF9 (alternate function 9) // and AF14 slots. { PORTA | 3 | (14<<8), PORTA | 4 | (14<<8), PORTA | 6 | (14<<8), PORTA | 11 | (14<<8), PORTA | 12 | (14<<8), PORTB | 0 | (9<<8), PORTB | 1 | (9<<8), PORTB | 8 | (14<<8), PORTB | 9 | (14<<8), PORTB | 10 | (14<<8), PORTB | 11 | (14<<8),PORTC | 6 | (14<<8), PORTC | 7 | (14<<8), PORTC | 10 | (14<<8),PORTD | 3 | (14<<8), PORTD | 6 | (14<<8), PORTF | 10 | (14<<8), PORTG | 6 | (14<<8), PORTG | 7 | (14<<8), PORTG | 10 | (9<<8), PORTG | 11 | (14<<8), PORTG | 12 | (9<<8) }; // some pins for easier probing; // PC2 = CS // PD13 = CMD/DATA // PF9 = SDA // PF7 = CLK // PA4 = vsync (active low) // PC6 = hsync (active low) // PF10 = DE (active high) // PG7 = dotclk
void dispInit() { // Enable clocks for GPIOs (pins) and devices. Last is DMA2D (Chrom-Art) unsigned int i; RCC->AHB1ENR |= (RCC_AHB1ENR_GPIOAEN | RCC_AHB1ENR_GPIOBEN | RCC_AHB1ENR_GPIOCEN | RCC_AHB1ENR_GPIODEN | RCC_AHB1ENR_GPIOFEN | RCC_AHB1ENR_GPIOGEN | (1<<23) ); // Initialize LTDC pins in AF mode. for (i = 0; i < LTDC_CFG_PINCOUNT; ++i) { unsigned int af = (ltdc_lcd_pins[i] >> 8) & 15;
// Set pin (3) in (PORTA) to (AF14) through GPIO controller
ioPinSetAF(ltdc_lcd_pins[i] & (~0xff00), 0, af);
} dispLTDCInit(); }
/* ----------------------------------------------------------------- * Init internal LTDC controller. */ void dispLTDCInit() { RCC->APB2ENR |= (1 << 26); // Enable LTDC clock. /* Clock setup: LCD pixel clock = HSE / M * N / R / DIVR (see your * display module specs to see what it should be) Most discovery board * templates has HSE 8MHz and M at 8. This gives PLL input clock of 1 MHz. * * Then we use values: * N = 192, R = 4, DIVR = 8 so output freq is 6 MHz * * PLL has also Q divider, for SAI (audio), but we do not use it so it * it just set to something here. */ #define PLLSAI_N 192 /* Multiplier for input clock (which is HSE/M) */ #define PLLSAI_R 4 /* Divider 1; /4 */ #define PLLSAI_DIVR 2 /* Divider 2; 0=/2, 1=/4, 2=/8, 3=/16 */ RCC->PLLSAICFGR = (PLLSAI_R << 28) | (4 << 24) | (PLLSAI_N << 6); RCC->DCKCFGR = (RCC->DCKCFGR & (~0x30000)) | (PLLSAI_DIVR << 16); RCC->CR |= (1<<28); // enable PLL SAI and wait until it is ready unsigned int n = 0; while (!(RCC->CR & (1<<29))) { if (++n > 2000000) { serialWrite((unsigned char*)"pllsai fail\r"); // PLL clock did not start; failure somewhere. return; } } LTDC->GCR = 0; // disable LCD-TFT controller for re-initialisation // Set up LCD timing variables. At first these values may seem like black magic // but trust me, if you spend some time with display datasheet they become clear eventually. // Or just trust me on these :) LTDC->SSCR = ((DISP_HSYNC_W-1) << 16) | (DISP_VSYNC_H-1); LTDC->BPCR = ((DISP_HSYNC_W+DISP_HORIZ_BACKPORCH-1) << 16) | ((DISP_VSYNC_H+DISP_VERT_BACKPORCH-1)); LTDC->AWCR = ((DISP_HSYNC_W+DISP_HORIZ_BACKPORCH+DISP_WIDTH) << 16) | (DISP_VSYNC_H+DISP_VERT_BACKPORCH+DISP_HEIGHT-1); LTDC->TWCR = ((DISP_HSYNC_W+DISP_HORIZ_BACKPORCH+DISP_WIDTH+DISP_HORIZ_FRONTPORCH-1) << 16) | (DISP_VSYNC_H+DISP_VERT_BACKPORCH+DISP_HEIGHT+DISP_VERT_FRONTPORCH-1); // background color (23:0 : R8G8B8). Purple-ish for testing. LTDC->BCCR = 0xff00ff; // Enable layer 1. We have 320x240 (76800) bytes in internal memory in L8 (with color LUT) mode // Discovery board has also external memory that could be used for bigger // display sizes, color depths or multiple display pages. // From the display controller's point of view the image is 240x320 pixels, portrait mode. // Entire area configured for layer 1. LTDC->L1CR = 0; // disable for reprogramming. LTDC->L1WHPCR = ((DISP_WIDTH+DISP_HSYNC_W+DISP_HORIZ_BACKPORCH+2) << 16) | (DISP_HSYNC_W+DISP_HORIZ_BACKPORCH); LTDC->L1WVPCR = ((DISP_HEIGHT+DISP_VSYNC_H+DISP_VERT_BACKPORCH) << 16) | (DISP_VSYNC_H+DISP_VERT_BACKPORCH); LTDC->L1PFCR = 5; // Pixel format: 5=L8. 2= RGB565 LTDC->L1CFBAR = (unsigned int)dispFrameBuff;
LTDC->L1CFBLR = ((DISP_WIDTH) << 16) | (DISP_WIDTH+6); // hi:line pitch in bytes; lo:(width_in_bytes+3). Note that L8 has just 1 byte per pixels!
// docs say that CFBLR loword should be W+3 but that doesn't seem to work, at least in L8 mode. So +6 instead.
LTDC->L1CFBLNR = DISP_HEIGHT; LTDC->L1CKCR = 0x000; // Black data is transparent, allowing background to be shown. // Set color look-up tables. Here we fill only first 128 entries with black-to-full color values. unsigned int i; for (i = 0; i < 16; ++i) LTDC->L1CLUTWR = ((i+0) << 24) + (i*0x101010); // 0-15: 16 greyscales for (i = 0; i < 16; ++i) LTDC->L1CLUTWR = ((i+16) << 24) + (i*0x100000); // 16-31: 16 reds for (i = 0; i < 16; ++i) LTDC->L1CLUTWR = ((i+32) << 24) + (i*0x001000); // 32-47: 16 greens for (i = 0; i < 16; ++i) LTDC->L1CLUTWR = ((i+48) << 24) + (i*0x000010); // 48-63: 16 blues for (i = 0; i < 16; ++i) LTDC->L1CLUTWR = ((i+64) << 24) + (i*0x101000); // 64-79: 16 yellows for (i = 0; i < 16; ++i) LTDC->L1CLUTWR = ((i+80) << 24) + (i*0x100010); // 80-95: 16 purples for (i = 0; i < 16; ++i) LTDC->L1CLUTWR = ((i+96) << 24) + (i*0x001010); // 96-112: 16 cyans LTDC->L1CR = 0x13; // Enable layer1 (0x01), CLUT (0x10) and color key (0x02) // Fill display buffer with some data. for (i = 0; i < sizeof(dispFrameBuff); ++i) { dispFrameBuff[i] = 7; } // White borders for (i = 0; i < DISP_WIDTH; ++i) { dispFrameBuff[i] = 15; dispFrameBuff[i+DISP_WIDTH] = 15; dispFrameBuff[i+(DISP_HEIGHT-1)*DISP_WIDTH] = 15; dispFrameBuff[i+(DISP_HEIGHT-2)*DISP_WIDTH] = 15; } for (i = 0; i < DISP_HEIGHT; ++i) { dispFrameBuff[i*DISP_WIDTH] = 15; dispFrameBuff[i*DISP_WIDTH+1] = 15; dispFrameBuff[i*DISP_WIDTH+DISP_WIDTH-1] = 15; dispFrameBuff[i*DISP_WIDTH+DISP_WIDTH-2] = 15; } // Color square at upper left corner for (i = 0; i < 16; ++i) { unsigned int j; for (j = 0; j < 16; ++j) { dispFrameBuff[DISP_WIDTH*2+2+ i*DISP_WIDTH*2+j*2] = i+j*16; dispFrameBuff[DISP_WIDTH*2+2+ i*DISP_WIDTH*2+1+j*2] = i+j*16; dispFrameBuff[DISP_WIDTH*3+2+ i*DISP_WIDTH*2+j*2] = i+j*16; dispFrameBuff[DISP_WIDTH*3+2+ i*DISP_WIDTH*2+1+j*2] = i+j*16; } } // Reload shadow registers to active in SRCR LTDC->SRCR = 1; // 1=reload immeiately; 2=reload on vertical blank // Enable controller. LTDC->GCR = 1; }
So there.