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.
