Подключение сенсорного дисплея на ILI9341 (R61520, SPFD5408) к STM32

Подключение сенсорного дисплея на ILI9341 (R61520, SPFD5408) к STM32

Подключение дисплея по 8-ми битной шине к stm32f103c8t6 без дополнительных библиотек.

Завалялся у меня TFT LCD дисплей 2.4 дюйма с сенсорным экраном. Никакой информации о нем я не знал. На модуле есть слот для sd карты, стабилизатор напряжения и пара LVC245A.

Судя по контактам управление по 8-битной шине. На помощь пришел поиск по картинкам. Нашел похожие дисплеи за исключением мелких деталей на плате, потом нашел и свой. Так толком и не понял что это за дисплей, но он запустился и работает по командам из Datasheet от ILI9341. Пишут, что это аналоги SPFD5408 и R61520. Я не сравнивал.

На дисплее есть резистивный сенсор. Управление по 4 проводам. Выводы подключены к выводам передачи данных. X+ на LCD_D1, X- на LCD_CS, Y+ на LCD_RS и Y- на LCD_D0. Я так понимаю этот модуль рассчитан на Arduino Uno. Это совершено не удобно, нужно использовать ногу где есть АЦП. А еще это большая потеря скорости отдачи данных на дисплей.

Я отпаял шлейф сенсора и подключил отдельно, но об этом в отдельной статье.

На МК STM32F103C8T6 нет FSMC. Я советую использовать подряд идущие пины одного порта, например A0-A7  для передачи данных на экран. Можно и не с нулевого пина, просто при записи делать побитовый сдвиг.

В моем случае я сделал плату для соединения экрана и модуля с МК STM32F103C8T6 по этому подключение будет таким.

#define DISP_WIDTH 320 // ширина дисплея в пикселях
#define DISP_HEIGHT 240 // высота дисплея в пикселях
#define TFT_DATA_PORT GPIOB
#define TFT_DATA_PIN_START GPIO_PinSource8
#define TFT_DATA_PINS (GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11 | GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15)
#define TFT_PORT GPIOA
#define TFT_RST GPIO_Pin_9
#define TFT_CS GPIO_Pin_10
#define TFT_RS GPIO_Pin_11
#define TFT_WR GPIO_Pin_12
 
#define TFT_RST_LOW() (TFT_PORT->BRR |= TFT_RST)
#define TFT_RST_HI()  (TFT_PORT->BSRR |= TFT_RST)
 
#define TFT_RS_LOW() (TFT_PORT->BRR |= TFT_RS)
#define TFT_RS_HI()  (TFT_PORT->BSRR |= TFT_RS)
 
#define TFT_WR_LOW() (TFT_PORT->BRR |= TFT_WR)
#define TFT_WR_HI()  (TFT_PORT->BSRR |= TFT_WR)
 
#define TFT_CS_LOW() (TFT_PORT->BRR |= TFT_CS)
#define TFT_CS_HI()  (TFT_PORT->BSRR |= TFT_CS)
 
#define TFT_STROBE()  TFT_WR_LOW();TFT_WR_HI()

То есть B8 - B15 я подключил к портам LCD_D0 - LCD_D7. Выводы RST, CS, RS, WR я подключил к A9-A12. Вывод RD я не использую, так как читать с экрана данные мне не нужно, по этому я подключил его к питанию 3.3В.

Команды, которые нам пригодятся для теста дисплея

#define ILI9341_SWRESET    0x01 // программный сброс
#define ILI9341_DISPOFF    0x28 // выключение дисплея
#define ILI9341_DISPON     0x29 // включение дисплея
#define ILI9341_CASET      0x2A // выбор первого и последнего пикселя по оси X
#define ILI9341_PASET      0x2B // выбор первого и последнего пикселя по оси Y
#define ILI9341_RAMWR      0x2C // запись пикселя в дисплей
#define ILI9341_PWCTR1     0xC0 // настройка питания
#define ILI9341_SLPIN      0x10 // перевод дисплея в режим сна
#define ILI9341_SLPOUT     0x11 // пробуждение
#define ILI9341_PIXFMT     0x3A // установка формата пикселя
#define ILI9341_MADCTL     0x36 // настройки порядка вывода пикселей на экран

Вспомогательные функции для задержки

#define    DWT_CYCCNT    *(volatile unsigned long *)0xE0001004
#define    DWT_CONTROL   *(volatile unsigned long *)0xE0001000
#define    SCB_DEMCR     *(volatile unsigned long *)0xE000EDFC
#define    DWT_CTRL_CYCCNTENA_Msk  (0x1UL << 0)
 
void delay_us(uint32_t us)
{
    int32_t us_count_tick =  us * (SystemCoreClock/1000000);
    SCB_DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
    DWT_CYCCNT  = 0;
    DWT_CONTROL |= DWT_CTRL_CYCCNTENA_Msk;
    while(DWT_CYCCNT < us_count_tick);
    DWT_CONTROL &= ~DWT_CTRL_CYCCNTENA_Msk;
}
 
void delay_ms(uint32_t ms)
{
    int32_t ms_count_tick =  ms * (SystemCoreClock/1000);
    SCB_DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
    DWT_CYCCNT  = 0;
    DWT_CONTROL|= DWT_CTRL_CYCCNTENA_Msk;
    while(DWT_CYCCNT < ms_count_tick);
    DWT_CONTROL &= ~DWT_CTRL_CYCCNTENA_Msk;
}

Обмен данными с дисплеем на базе ILI9341 и инициализация

// отправка нашего байта
void TFTSend(uint8_t b) {
    // сбрасываем все пины в 0
    TFT_DATA_PORT->BRR = TFT_DATA_PINS;
    // устанавливаем нужные пины в 1
    // если у вас пины подключены начиная с 0-го, смещение можно убрать
    TFT_DATA_PORT->BSRR = b << TFT_DATA_PIN_START;
    // дергаем LCD_WR сообщая экрану, что можно читать байт
    TFT_STROBE();
}

// отправка команды
void TFTCmd(uint8_t b) {
    // начало обмена данными
    TFT_CS_LOW();
    // прижимаем LCD_RS к нулю т.к. это команда
    TFT_RS_LOW();
    // отправляем наш байт
    TFTSend(b);
    // обмен данными завершен
    TFT_CS_HI();
}

// отправка данных
void TFTData(uint8_t b) {
    // начало обмена данными
    TFT_CS_LOW();
    // LCD_RS подтягиваем к питанию т.к. это данные
    TFT_RS_HI();
    // отдаем байт
    TFTSend(b);
    // завершаем обмен
    TFT_CS_LOW();
}

// сброс экрана
void TFTReset() {
    TFT_RST_HI();
    TFT_CS_HI();
    TFT_RS_HI();
    TFT_WR_HI();
    delay_us(5);
    TFT_RST_LOW();
    delay_us(15);
    TFT_RST_HI();
    delay_us(15);
}

// инициализация экрана
void TFTInit() {
    // сбрасываем экран
    TFTReset();
    // шлем команду программного сброса
    // это сбросит настройки дисплея на значение по умолчанию
    TFTCmd(ILI9341_SWRESET);
    // задержка, в описании 5ms
    delay_ms(5);
    // программное выключение дисплея
    TFTCmd(ILI9341_DISPOFF);
    // обычный режим питания
    TFTCmd(ILI9341_PWCTR1);
    // выводим из спящего режима
    TFTCmd(ILI9341_SLPOUT);
    delay_us(1500);
    // включаем дисплей
    TFTCmd(ILI9341_DISPON);
    // задаем формат передачи пикселя 
    TFTCmd(ILI9341_PIXFMT);
    // я не буду копировать всю таблицу из даташита
    // подробности о различных режимах можно посмотреть в ДШ на ILI9341
    // указываем 16 битный формат пикселя
    TFTData(0x55);

    // Выключаем инверсию
    TFTCmd(ILI9341_INVOFF);

    // устанавливаем порядок выводов пикселя
    // таблица ниже
    TFTCmd(ILI9341_MADCTL);
    TFTData(0x80 | 0x40 | 0x08 | 0x20);

Таблица настроек вывода пикселя

Memory Access Control table

ILI9341 Memory Access Control

Функции вывода пикселей на экран

// задаем размер блока в который будем слать пиксели
void TFTRect(uint16_t x, uint16_t y, uint16_t w, uint16_t h) {
    w = w + x - 1;
    h = h + y - 1;

    // адрес первого пикселя
    TFTCmd(ILI9341_CASET);
    TFTData(x >> 8);
    TFTData(x);
    TFTData(w >> 8);
    TFTData(w);

    // адрес последнего пикселя
    TFTCmd(ILI9341_PASET);
    TFTData(y >> 8);
    TFTData(y);
    TFTData(h >> 8);
    TFTData(h);
}
 
uint32_t px, px_count;
// заливка области
void TFTFill(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t c) {
    // устанавливаем размер области
    TFTRect(x, y, w, h);
    TFTCmd(ILI9341_RAMWR);
    px_count = w * h;
    for (px = 0; px < px_count; ++ px) {
        // шлем цвет пикселя
        // в моем случае 16 битный режим
        TFTData(c >> 8);
        TFTData(c);
    }
}


Команда CASET (Column Address Set) - принимает два байта, номер первого пикселя в строке, по оси X и два байта номер последнего.

Команда PASET (Page Address Set) аналогично принимает принимает по Y.

Для удобства вместо номера конечного пикселя в качестве аргумента функции TFTRect я использую ширину и высоту, а уже в самой функции перевожу в адрес пикселя.

Команда RAMWR это запись наших пикселей. Если взять ширину 5 пикселей а высоту 2, то всего нужно будет передать 2 * 5 = 10 пикселей по 2 байта.

Некоторые цвета для теста

#define COLOR_BLACK       0x0000
#define COLOR_BLUE        0x001F
#define COLOR_GREEN       0x07E0
#define COLOR_RED         0xF800
#define COLOR_YELLOW      0xFFE0
#define COLOR_WHITE       0xFFFF

Или можно воспользоваться макросом для вычисления цвета из RGB

#define RGB565(r, g, b) (((r & 0b11111000) << 8) | ((g & 0b11111100) << 3) | (b >> 3))

Остается настроить тактовую частоту и порты на вывод с подтяжкой к питанию

void SetSysClockTo72(void)
{
    ErrorStatus HSEStartUpStatus;
 
    RCC_DeInit();
 
    RCC_HSEConfig(RCC_HSE_ON);
 
    HSEStartUpStatus = RCC_WaitForHSEStartUp();
 
    if (HSEStartUpStatus == SUCCESS)
    {
        RCC_HCLKConfig(RCC_SYSCLK_Div1);
 
        RCC_PCLK2Config(RCC_HCLK_Div1);
 
        RCC_PCLK1Config(RCC_HCLK_Div2);
 
        RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9);
 
        RCC_PLLCmd(ENABLE);
 
        while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
        {
        }
 
        RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
 
        while (RCC_GetSYSCLKSource() != 0x08)
        {
        }
    }
    else
    {
        while (1)
        {
        }
    }
}
 
void GPIOInit() {
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO | RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC, ENABLE);
 
    GPIO_InitTypeDef gpio;
    gpio.GPIO_Pin = TFT_RST | TFT_CS | TFT_RS | TFT_WR;
    gpio.GPIO_Mode = GPIO_Mode_Out_PP;
    gpio.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(TFT_PORT, &gpio);
 
    gpio.GPIO_Pin = TFT_DATA_PINS;
    gpio.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_Init(TFT_DATA_PORT, &gpio);
}

И можно что то выводить на дисплей

int main(void)
{
    // настройки тактирования
    SetSysClockTo72();

    // Настройки портов
    GPIOInit();

    // Инициализация дисплея
    TFTInit();

    // Закрасим все черным
    TFTFill(0, 0, DISP_WIDTH, DISP_HEIGHT, COLOR_BLACK);

    // Выведем вверху желтую панель
    TFTFill(0, 0, DISP_WIDTH, 20, COLOR_YELLOW);

    // синюю внизу
    TFTFill(0, DISP_HEIGHT - 20, DISP_WIDTH, 20, ILI9341_BLUE);

    while(1);
}

Ну вот и все. 

ILI9341 вывод с STM32

При копировании материалов ссылка на https://terraideas.ru/ обязательна

Комментарии к статье: Подключение сенсорного дисплея на ILI9341 (R61520, SPFD5408) к STM32

Нет ни одного комментария. Будьте первым!