torstai 28. huhtikuuta 2016

STM32F4xx display code, refined version


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.

Ei kommentteja:

Lähetä kommentti