Jak wspominałem wcześniej moim celem było opanowanie wyświetlacza na płytce stm32f429-discovery. Wbrew pozorom to dość skomplikowane wyzwanie, ale teraz wszystkie niezbędne elementy: PLL, SPI oraz SDRAM są uruchomione, można zacząć przygodę z wyświetlaczem.
Na początek drobna poprawka modułu spi. Poprzednio testowałem go z żyroskopem, ale teraz chciałbym mieć możliwość używania zarówno do obsługi tego czujnika, jak i wyświetlacza. Zmieniam więc niec plik nagłówkowy spi.h:
Oraz sam kod w spi.c:
Zmiany są kosmetyczne - teraz można wybrać układ z którym chcemy się komunikować.
Czas uruchomić wyświetlacz. Pamięć SDRAM posłuży za bufor obrazu - w kolejnym wpisie, na początek niech wyświetli się samo tło. Interfejs SPI służy do konfiguracji kontrolera obrazu, same dane są przesyłane przez kontroler LCD.
Taka transmisja wymaga dość sporo pinów, używam więc funkcji config_pin do ich ustawienia:
To które piny są używane i jakie funkcje należy im przypisać opisane zostało w dokumentacji. Wystarczy poszukać i przepisać.
Jak zwykle moduł wyświetlacza jest domyślnie wyłączony, trzeba więc go uruchomić:
RCC->APB2ENR |= RCC_APB2ENR_LTDCEN;
Wyświetlacz używa własnego sygnału zegarowego z dedykowanego PLL. Konfiguracja wygląda następująco:
RCC->PLLSAICFGR = (192 << 6) | (7 << 24) | (4 << 28);
RCC->DCKCFGR |= 0x00020000;
RCC->CR |= RCC_CR_PLLSAION;
while ((RCC->CR & RCC_CR_PLLSAIRDY) == 0);
Następnie należy ustawić timingi wyświetlacza. Jest z tym trochę liczenia, ale końcowy kod jest prosty:
LTDC->SSCR = (9 << 16) | 1;
LTDC->BPCR = (29 << 16) | 3;
LTDC->AWCR |= (269 << 16) | 323;
LTDC->TWCR |= (279 << 16) | 327;
W pierwszym przykładzie nie wyświetlam danych, jedynie tło. Jego kolor ustawiany jest w rejestrze BCCR:
LTDC->BCCR |= 0xf0ff00;
Ostatni krok to uruchomienie wyświetlacza:
LTDC->GCR |= LTDC_GCR_LTDCEN;
Okazuje się, że uruchomienie wyświetlacza nie jest aż takie trudne jak się wydaje. Co prawda jest jeszcze jeden ważny element - konfiguracja kontrolera w samym wyświetlaczu. Jak wspomniałem używane jest do tego SPI, a sam kod czyli funkcje LCD_PowerOn przeniosłem z przykładów - wymaga konfiguracji ogromnej liczby rejestrów, ale na szczęscie wykonuje się go tylko raz. Wypadałoby wszystko sprawdzić w dokumentacji, ale tym razem byłem trochę leniwy.
Poniżej efekt działania programu:
Program główny w main.c:
Oraz to co najważniejsze, czyli moduł lcd.c:
W kolejnych wpisach dodam drobne modyfikacje do modułu lcd. Wbrew pozorom teraz będzie już tylko łatwiej (i ładniej).
Na początek drobna poprawka modułu spi. Poprzednio testowałem go z żyroskopem, ale teraz chciałbym mieć możliwość używania zarówno do obsługi tego czujnika, jak i wyświetlacza. Zmieniam więc niec plik nagłówkowy spi.h:
#ifndef __SPI__ #define __SPI__ #include <stdint.h> #define SPI_L3GD20 0 #define SPI_LCD 1 void spi_init(void); void spi_start(int spi); void spi_stop(int spi); uint8_t spi_sendrecv(uint8_t tx); #endif // __SPI__
Oraz sam kod w spi.c:
#include "spi.h" #include "stm32f429xx.h" static void config_pin(GPIO_TypeDef* port, uint8_t pin, uint32_t func) { if (pin < 8) { port->AFR[0] |= func << (pin * 4); port->AFR[0] |= func << (pin * 4); } else { port->AFR[1] |= func << ((pin - 8) * 4); port->AFR[1] |= func << ((pin - 8) * 4); } port->MODER |= 2 << (pin * 2); port->OSPEEDR |= 2 << (pin * 2); } void spi_init(void) { RCC->APB2ENR |= RCC_APB2ENR_SPI5EN; config_pin(GPIOF, 7, 5); config_pin(GPIOF, 8, 5); config_pin(GPIOF, 9, 5); GPIOC->MODER |= GPIO_MODER_MODE1_0 | GPIO_MODER_MODE2_0; GPIOC->BSRR = GPIO_BSRR_BS1 | GPIO_BSRR_BS2; SPI5->CR1 = SPI_CR1_BR_2 | SPI_CR1_BR_0 | SPI_CR1_MSTR | SPI_CR1_SSM | SPI_CR1_SSI; SPI5->CR1 |= SPI_CR1_SPE; } uint8_t spi_sendrecv(uint8_t tx) { while ((SPI5->SR & SPI_SR_TXE) == 0); SPI5->DR = tx; while ((SPI5->SR & SPI_SR_RXNE) == 0); return SPI5->DR; } void spi_start(int spi) { if (spi == SPI_L3GD20) GPIOC->BSRR = GPIO_BSRR_BR1; else GPIOC->BSRR = GPIO_BSRR_BR2; } void spi_stop(int spi) { while ((SPI5->SR & SPI_SR_TXE) == 0); while (SPI5->SR & SPI_SR_BSY); if (spi == SPI_L3GD20) GPIOC->BSRR = GPIO_BSRR_BS1; else GPIOC->BSRR = GPIO_BSRR_BS2; }
Zmiany są kosmetyczne - teraz można wybrać układ z którym chcemy się komunikować.
Czas uruchomić wyświetlacz. Pamięć SDRAM posłuży za bufor obrazu - w kolejnym wpisie, na początek niech wyświetli się samo tło. Interfejs SPI służy do konfiguracji kontrolera obrazu, same dane są przesyłane przez kontroler LCD.
Taka transmisja wymaga dość sporo pinów, używam więc funkcji config_pin do ich ustawienia:
config_pin(GPIOA, 3, 14); config_pin(GPIOA, 4, 14); config_pin(GPIOA, 6, 14); config_pin(GPIOA, 11, 14); config_pin(GPIOA, 12, 14); config_pin(GPIOB, 0, 9); config_pin(GPIOB, 1, 9); config_pin(GPIOB, 8, 14); config_pin(GPIOB, 9, 14); config_pin(GPIOB, 10, 14); config_pin(GPIOB, 11, 14); config_pin(GPIOC, 6, 14); config_pin(GPIOC, 7, 14); config_pin(GPIOC, 10, 14); config_pin(GPIOD, 3, 14); config_pin(GPIOD, 6, 14); config_pin(GPIOF, 10, 14); config_pin(GPIOG, 6, 14); config_pin(GPIOG, 7, 14); config_pin(GPIOG, 10, 9); config_pin(GPIOG, 11, 14); config_pin(GPIOG, 12, 9);
To które piny są używane i jakie funkcje należy im przypisać opisane zostało w dokumentacji. Wystarczy poszukać i przepisać.
Jak zwykle moduł wyświetlacza jest domyślnie wyłączony, trzeba więc go uruchomić:
RCC->APB2ENR |= RCC_APB2ENR_LTDCEN;
Wyświetlacz używa własnego sygnału zegarowego z dedykowanego PLL. Konfiguracja wygląda następująco:
RCC->PLLSAICFGR = (192 << 6) | (7 << 24) | (4 << 28);
RCC->DCKCFGR |= 0x00020000;
RCC->CR |= RCC_CR_PLLSAION;
while ((RCC->CR & RCC_CR_PLLSAIRDY) == 0);
Następnie należy ustawić timingi wyświetlacza. Jest z tym trochę liczenia, ale końcowy kod jest prosty:
LTDC->SSCR = (9 << 16) | 1;
LTDC->BPCR = (29 << 16) | 3;
LTDC->AWCR |= (269 << 16) | 323;
LTDC->TWCR |= (279 << 16) | 327;
W pierwszym przykładzie nie wyświetlam danych, jedynie tło. Jego kolor ustawiany jest w rejestrze BCCR:
LTDC->BCCR |= 0xf0ff00;
Ostatni krok to uruchomienie wyświetlacza:
LTDC->GCR |= LTDC_GCR_LTDCEN;
Okazuje się, że uruchomienie wyświetlacza nie jest aż takie trudne jak się wydaje. Co prawda jest jeszcze jeden ważny element - konfiguracja kontrolera w samym wyświetlaczu. Jak wspomniałem używane jest do tego SPI, a sam kod czyli funkcje LCD_PowerOn przeniosłem z przykładów - wymaga konfiguracji ogromnej liczby rejestrów, ale na szczęscie wykonuje się go tylko raz. Wypadałoby wszystko sprawdzić w dokumentacji, ale tym razem byłem trochę leniwy.
Poniżej efekt działania programu:
Program główny w main.c:
#include <stdio.h> #include <stdint.h> #include <stdbool.h> #include "stm32f429xx.h" #include "pll.h" #include "sdram.h" #include "uart.h" #include "spi.h" #include "lcd.h" int _write(int fd, char *str, int len) { int i; for (i = 0; i < len; i++) usart_putc(str[i]); return len; } int main(int argc, char *argv[]) { RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN | RCC_AHB1ENR_GPIOBEN | RCC_AHB1ENR_GPIOCEN | RCC_AHB1ENR_GPIODEN | RCC_AHB1ENR_GPIOEEN | RCC_AHB1ENR_GPIOFEN | RCC_AHB1ENR_GPIOGEN; pll_init(); uart_init(); sdram_init(); spi_init(); lcd_init(); printf("Hello world!\r\n"); while (1) { printf("Starting SPI communication...\r\n"); spi_start(SPI_L3GD20); uint8_t r = spi_sendrecv(0x8f); printf("Received: %02x\r\n", r); r = spi_sendrecv(0x00); printf("Received: %02x\r\n", r); spi_stop(SPI_L3GD20); printf("done.\r\n"); for (volatile uint32_t dly = 0; dly < 10000000; dly++); } return 0; }
Oraz to co najważniejsze, czyli moduł lcd.c:
#include "lcd.h" #include "spi.h" #include "stm32f429xx.h" #define LCD_SLEEP_OUT 0x11 /* Sleep out register */ #define LCD_GAMMA 0x26 /* Gamma register */ #define LCD_DISPLAY_OFF 0x28 /* Display off register */ #define LCD_DISPLAY_ON 0x29 /* Display on register */ #define LCD_COLUMN_ADDR 0x2A /* Colomn address register */ #define LCD_PAGE_ADDR 0x2B /* Page address register */ #define LCD_GRAM 0x2C /* GRAM register */ #define LCD_MAC 0x36 /* Memory Access Control register*/ #define LCD_PIXEL_FORMAT 0x3A /* Pixel Format register */ #define LCD_WDB 0x51 /* Write Brightness Display register */ #define LCD_WCD 0x53 /* Write Control Display register*/ #define LCD_RGB_INTERFACE 0xB0 /* RGB Interface Signal Control */ #define LCD_FRC 0xB1 /* Frame Rate Control register */ #define LCD_BPC 0xB5 /* Blanking Porch Control register*/ #define LCD_DFC 0xB6 /* Display Function Control register*/ #define LCD_POWER1 0xC0 /* Power Control 1 register */ #define LCD_POWER2 0xC1 /* Power Control 2 register */ #define LCD_VCOM1 0xC5 /* VCOM Control 1 register */ #define LCD_VCOM2 0xC7 /* VCOM Control 2 register */ #define LCD_POWERA 0xCB /* Power control A register */ #define LCD_POWERB 0xCF /* Power control B register */ #define LCD_PGAMMA 0xE0 /* Positive Gamma Correction register*/ #define LCD_NGAMMA 0xE1 /* Negative Gamma Correction register*/ #define LCD_DTCA 0xE8 /* Driver timing control A */ #define LCD_DTCB 0xEA /* Driver timing control B */ #define LCD_POWER_SEQ 0xED /* Power on sequence register */ #define LCD_3GAMMA_EN 0xF2 /* 3 Gamma enable register */ #define LCD_INTERFACE 0xF6 /* Interface control register */ #define LCD_PRC 0xF7 /* Pump ratio control register */ static void LCD_WriteCommand(uint8_t LCD_Reg) { GPIOD->BSRR = GPIO_BSRR_BR13; spi_start(SPI_LCD); spi_sendrecv(LCD_Reg); spi_stop(SPI_LCD); } static void LCD_WriteData(uint8_t value) { GPIOD->BSRR = GPIO_BSRR_BS13; spi_start(SPI_LCD); spi_sendrecv(value); spi_stop(SPI_LCD); } static void LCD_PowerOn(void) { LCD_WriteCommand(0xCA); LCD_WriteData(0xC3); LCD_WriteData(0x08); LCD_WriteData(0x50); LCD_WriteCommand(LCD_POWERB); LCD_WriteData(0x00); LCD_WriteData(0xC1); LCD_WriteData(0x30); LCD_WriteCommand(LCD_POWER_SEQ); LCD_WriteData(0x64); LCD_WriteData(0x03); LCD_WriteData(0x12); LCD_WriteData(0x81); LCD_WriteCommand(LCD_DTCA); LCD_WriteData(0x85); LCD_WriteData(0x00); LCD_WriteData(0x78); LCD_WriteCommand(LCD_POWERA); LCD_WriteData(0x39); LCD_WriteData(0x2C); LCD_WriteData(0x00); LCD_WriteData(0x34); LCD_WriteData(0x02); LCD_WriteCommand(LCD_PRC); LCD_WriteData(0x20); LCD_WriteCommand(LCD_DTCB); LCD_WriteData(0x00); LCD_WriteData(0x00); LCD_WriteCommand(LCD_FRC); LCD_WriteData(0x00); LCD_WriteData(0x1B); LCD_WriteCommand(LCD_DFC); LCD_WriteData(0x0A); LCD_WriteData(0xA2); LCD_WriteCommand(LCD_POWER1); LCD_WriteData(0x10); LCD_WriteCommand(LCD_POWER2); LCD_WriteData(0x10); LCD_WriteCommand(LCD_VCOM1); LCD_WriteData(0x45); LCD_WriteData(0x15); LCD_WriteCommand(LCD_VCOM2); LCD_WriteData(0x90); LCD_WriteCommand(LCD_MAC); LCD_WriteData(0xC8); LCD_WriteCommand(LCD_3GAMMA_EN); LCD_WriteData(0x00); LCD_WriteCommand(LCD_RGB_INTERFACE); LCD_WriteData(0xC2); LCD_WriteCommand(LCD_DFC); LCD_WriteData(0x0A); LCD_WriteData(0xA7); LCD_WriteData(0x27); LCD_WriteData(0x04); /* colomn address set */ LCD_WriteCommand(LCD_COLUMN_ADDR); LCD_WriteData(0x00); LCD_WriteData(0x00); LCD_WriteData(0x00); LCD_WriteData(0xEF); /* Page Address Set */ LCD_WriteCommand(LCD_PAGE_ADDR); LCD_WriteData(0x00); LCD_WriteData(0x00); LCD_WriteData(0x01); LCD_WriteData(0x3F); LCD_WriteCommand(LCD_INTERFACE); LCD_WriteData(0x01); LCD_WriteData(0x00); LCD_WriteData(0x06); LCD_WriteCommand(LCD_GRAM); LCD_WriteCommand(LCD_GAMMA); LCD_WriteData(0x01); LCD_WriteCommand(LCD_PGAMMA); LCD_WriteData(0x0F); LCD_WriteData(0x29); LCD_WriteData(0x24); LCD_WriteData(0x0C); LCD_WriteData(0x0E); LCD_WriteData(0x09); LCD_WriteData(0x4E); LCD_WriteData(0x78); LCD_WriteData(0x3C); LCD_WriteData(0x09); LCD_WriteData(0x13); LCD_WriteData(0x05); LCD_WriteData(0x17); LCD_WriteData(0x11); LCD_WriteData(0x00); LCD_WriteCommand(LCD_NGAMMA); LCD_WriteData(0x00); LCD_WriteData(0x16); LCD_WriteData(0x1B); LCD_WriteData(0x04); LCD_WriteData(0x11); LCD_WriteData(0x07); LCD_WriteData(0x31); LCD_WriteData(0x33); LCD_WriteData(0x42); LCD_WriteData(0x05); LCD_WriteData(0x0C); LCD_WriteData(0x0A); LCD_WriteData(0x28); LCD_WriteData(0x2F); LCD_WriteData(0x0F); LCD_WriteCommand(LCD_SLEEP_OUT); LCD_WriteCommand(LCD_DISPLAY_ON); /* GRAM start writing */ LCD_WriteCommand(LCD_GRAM); } void lcd_init(void) { GPIOD->MODER |= GPIO_MODER_MODE13_0; LCD_PowerOn(); RCC->APB2ENR |= RCC_APB2ENR_LTDCEN; config_pin(GPIOA, 3, 14); config_pin(GPIOA, 4, 14); config_pin(GPIOA, 6, 14); config_pin(GPIOA, 11, 14); config_pin(GPIOA, 12, 14); config_pin(GPIOB, 0, 9); config_pin(GPIOB, 1, 9); config_pin(GPIOB, 8, 14); config_pin(GPIOB, 9, 14); config_pin(GPIOB, 10, 14); config_pin(GPIOB, 11, 14); config_pin(GPIOC, 6, 14); config_pin(GPIOC, 7, 14); config_pin(GPIOC, 10, 14); config_pin(GPIOD, 3, 14); config_pin(GPIOD, 6, 14); config_pin(GPIOF, 10, 14); config_pin(GPIOG, 6, 14); config_pin(GPIOG, 7, 14); config_pin(GPIOG, 10, 9); config_pin(GPIOG, 11, 14); config_pin(GPIOG, 12, 9); RCC->PLLSAICFGR = (192 << 6) | (7 << 24) | (4 << 28); RCC->DCKCFGR |= 0x00020000; RCC->CR |= RCC_CR_PLLSAION; while ((RCC->CR & RCC_CR_PLLSAIRDY) == 0); LTDC->SSCR = (9 << 16) | 1; LTDC->BPCR = (29 << 16) | 3; LTDC->AWCR |= (269 << 16) | 323; LTDC->TWCR |= (279 << 16) | 327; LTDC->BCCR |= 0xf0ff00; LTDC->GCR |= LTDC_GCR_LTDCEN; } static void config_pin(GPIO_TypeDef* port, uint8_t pin, uint8_t func) { if (pin < 8) { port->AFR[0] |= func << (pin * 4); } else { port->AFR[1] |= func << ((pin - 8) * 4); } port->MODER |= 2 << (pin * 2); port->OSPEEDR |= 2 << (pin * 2); }
W kolejnych wpisach dodam drobne modyfikacje do modułu lcd. Wbrew pozorom teraz będzie już tylko łatwiej (i ładniej).