20 lipca 2018

Pierwsze próby z wyświetlaczem

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:

#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).

Brak komentarzy:

Prześlij komentarz