На данном уроке мы попробуем к контроллеру ESP32 подключить дисплей TFT разрешением 240×320 по интерфейсу SPI.
С подобным дисплеем мы уже работали неоднократно с применением других контроллеров. Мы подключали его и по 8-разрядной, и по 16-разрядной параллельным шинам. Работали мы с ним и с использованием интерфейса SPI, подключая его к контроллеру STM32 вот в этом уроке.
Поэтому нет смысла заново рассказывать о характеристиках данного дисплея. Я лишь напомню, что подключен он с использованием контроллера дисплея ILI9341, который позволяет подключать подобные дисплеи по различным шинам.
Дисплей поставляется вместе с контроллером на удобной плате. Только нужно искать комплектацию именно с интерфейсом SPI, так как есть и другие виды поставок. Внизу страницы я дам ссылку на такой дисплей, на момент написания сценария урока она рабочая, сколько просуществует, не знаю.
Вот так выглядит данный дисплей
У меня дисплей размером диагонали 2,8 дюймов, могут быть другие размеры, главное, чтобы разрешение было 320×240 и контроллер чтобы был такой же.
Верхние 5 ножек относятся к работе с сенсорной панелью (TouchScreen), поэтому мы их использовать не будем, следующая 6 ножка — MISO тоже не будет нами использована, так как нам нет смысла ничего читать из дисплея, мы знаем, что у нас именно ILI9341, да и не совсем корректно контроллер работает с MISO.
Подключим к нашей отладочной плате только нижние следующим образом
Для мониторинга передачи данных по шине SPI я подключил ещё и логический анализатор к ножкам MOSI, SCK и CS.
Проект был сделан из проекта урока 8 с именем LED7219 и назван был ILI9341_SPI.
В каталоге main проекта откроем файл Kconfig.projbuild. В данном пункте нужно будет добавить пункты для остальных ножек. В пунктах для контактов SPI также изменятся номера ножек портов GPIO. Поэтому даю полное содержимое файла
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
menu "Example Configuration" config PIN_NUM_MOSI int "MOSI pin" range 0 48 default 23 help MOSI pin config PIN_NUM_MISO int "MISO pin" range 0 48 default 25 help MISO pin config PIN_NUM_CLK int "CLK pin" range 0 48 default 19 help CLK pin config PIN_NUM_CS int "CS pin" range 0 48 default 22 help CS pin config PIN_NUM_DC int "DC pin" range 0 48 default 21 help DC pin config PIN_NUM_BCKL int "BCKL pin" range 0 48 default 5 help BCKL pin config PIN_NUM_RST int "RST pin" range 0 48 default 18 help RST pin endmenu |
Также переименуем файлы max7219.h и max7219.c соответственно в spi_ili9341.h и spi_ili9341.c.
Сразу изменим содержимое данных файлов:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#ifndef MAIN_SPI_ILI9341_H_ #define MAIN_SPI_ILI9341_H_ //--------------------------------------------------------------------- #include <unistd.h> #include <string.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/queue.h" #include "driver/gpio.h" #include "driver/spi_master.h" #include "sdkconfig.h" //--------------------------------------------------------------------- //--------------------------------------------------------------------- #endif /* MAIN_SPI_ILI9341_H_ */ |
1 2 |
#include "spi_ili9341.h" //------------------------------------------------------------------- |
В файле CMakeLists.txt внесём также соответствующие изменения
set(COMPONENT_SRCS "main.c spi_ili9341.c")
В main.h также изменим имя подключаемого файла
#include "spi_ili9341.h"
Откроем наш проект в Espressif IDE, перейдём в файл main.c и в функции app_main удалим объявление переменной для счётчика
volatile long cnt;
В данном поле укажем конкретный максимальный размер буфера
.max_transfer_sz = 16*320*2+8,
Вот это можно удалить
.flags = 0
Здесь вместо единички будет макрос для автоматического определения модуля DMA
ret=spi_bus_initialize(HSPI_HOST, &cfg, SPI_DMA_CH_AUTO);
Выше функции app_main добавим вот такую хитрую функцию обратного вызова
1 2 3 4 5 6 7 |
//------------------------------------------------ void lcd_spi_pre_transfer_callback(spi_transaction_t *t) { int dc=(int)t->user; gpio_set_level(CONFIG_PIN_NUM_DC, dc); } //------------------------------------------------ |
Она нужна для того, чтобы при вызове функции выполнения транзакции с модулем SPI мы могли передавать значение уровня ножки DC.
Вернёмся в функцию app_main, объявим там и сразу проинициализируем переменную типа структуры устройства
1 2 3 4 5 6 7 8 |
ESP_LOGI(TAG, "spi bus initialize: %d", ret); spi_device_interface_config_t devcfg={ .clock_speed_hz=10*1000*1000, //Clock out at 10 MHz .mode=0, //SPI mode 0 .spics_io_num=CONFIG_PIN_NUM_CS, //CS pin .queue_size=7, //We want to be able to queue 7 transactions at a time .pre_cb=lcd_spi_pre_transfer_callback, //Specify pre-transfer callback to handle D/C line }; |
В начале функции объявим переменную типа указателя для устройства
1 2 |
esp_err_t ret; spi_device_handle_t spi; |
Присоединим устройство к модулю и посмотрим возвращённый результат кода ошибки
1 2 3 4 |
.pre_cb=lcd_spi_pre_transfer_callback, //Specify pre-transfer callback to handle D/C line }; ret=spi_bus_add_device(HSPI_HOST, &devcfg, &spi); ESP_LOGI(TAG, "spi bus add device: %d", ret); |
В файле spi_ili9341.c добавим функцию инициализации контроллера дисплея, в которой объявим небольшой массив, зададим направление работы дополнительных ножек, а также включим подсветку
1 2 3 4 5 6 7 8 9 10 11 12 |
#include "spi_ili9341.h" //------------------------------------------------------------------- void TFT9341_ini(spi_device_handle_t spi, uint16_t w_size, uint16_t h_size) { uint8_t data[15]; //Initialize non-SPI GPIOs gpio_set_direction(CONFIG_PIN_NUM_DC, GPIO_MODE_OUTPUT); gpio_set_direction(CONFIG_PIN_NUM_RST, GPIO_MODE_OUTPUT); gpio_set_direction(CONFIG_PIN_NUM_BCKL, GPIO_MODE_OUTPUT); gpio_set_level(CONFIG_PIN_NUM_BCKL, 1); } //------------------------------------------------------------------- |
Объявим прототип данной функции в заголовочном файле и вызовем её в функции app_main файла main.c
1 2 |
ESP_LOGI(TAG, "spi bus add device: %d", ret); TFT9341_ini(spi, 320, 240); |
Код, находящийся ниже данной строки до бесконечного цикла удалим.
Из бесконечного цикла также удалим всё ненужное, оставим только задержку, несколько увеличив её продолжительность
1 2 3 |
while (1) { vTaskDelay(500 / portTICK_PERIOD_MS); } |
В файле spi_ili9341.c выше функции TFT9341_ini добавим функцию перезагрузки контроллера дисплея, в которой сначала опустим, а затем поднимем соответствующую ножку
1 2 3 4 5 6 7 8 9 |
//------------------------------------------------------------------- void TFT9341_reset(void) { gpio_set_level(CONFIG_PIN_NUM_RST, 0); vTaskDelay(100 / portTICK_RATE_MS); gpio_set_level(CONFIG_PIN_NUM_RST, 1); vTaskDelay(100 / portTICK_RATE_MS); } //------------------------------------------------------------------- |
Вызовем данную функцию в функции TFT9341_ini
1 2 |
gpio_set_level(CONFIG_PIN_NUM_BCKL, 1); TFT9341_reset(); |
Выше функции TFT9341_reset добавим функцию отправки команды в шину
1 2 3 4 5 6 7 8 9 10 11 12 13 |
//------------------------------------------------------------------- void lcd_cmd(spi_device_handle_t spi, const uint8_t cmd) { esp_err_t ret; spi_transaction_t t; memset(&t, 0, sizeof(t)); //Zero out the transaction t.length=8; //Command is 8 bits t.tx_buffer=&cmd; //The data is the cmd itself t.user=(void*)0; //D/C needs to be set to 0 ret=spi_device_polling_transmit(spi, &t); //Transmit! assert(ret==ESP_OK); //Should have had no issues. } //------------------------------------------------------------------- |
Здесь мы как раз воспользовались функцией обратного вызова, передав в параметре уровень ножки DC.
Ниже добавим аналогичную функцию для отправки данных
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
//------------------------------------------------------------------- void lcd_data(spi_device_handle_t spi, const uint8_t *data, int len) { esp_err_t ret; spi_transaction_t t; if (len==0) return; //no need to send anything memset(&t, 0, sizeof(t)); //Zero out the transaction t.length=len*8; //Len is in bytes, transaction length is in bits. t.tx_buffer=data; //Data t.user=(void*)1; //D/C needs to be set to 1 ret=spi_device_polling_transmit(spi, &t); //Transmit! assert(ret==ESP_OK); //Should have had no issues. } //------------------------------------------------------------------- |
Здесь мы уже указываем высокий уровень для данной ножки, а также получаем размер данных, передаваемых в шину.
Вернёмся в функцию TFT9341_ini и теперь перезагрузим контроллер дисплея программно
1 2 3 4 |
TFT9341_reset(); //Software Reset lcd_cmd(spi, 0x01); vTaskDelay(1000 / portTICK_RATE_MS); |
Далее идёт инициализация различных параметров дисплея, которая у нас уже выработана многими уроками и в принципе сильно не менялась
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 |
vTaskDelay(1000 / portTICK_RATE_MS); //Power control A, Vcore=1.6V, DDVDH=5.6V data[0] = 0x39; data[1] = 0x2C; data[2] = 0x00; data[3] = 0x34; data[4] = 0x02; lcd_cmd(spi, 0xCB); lcd_data(spi, data, 5); //Power contorl B, power control = 0, DC_ENA = 1 data[0] = 0x00; data[1] = 0x83; data[2] = 0x30; lcd_cmd(spi, 0xCF); lcd_data(spi, data, 3); //Driver timing control A, //non-overlap=default +1 //EQ=default - 1, CR=default //pre-charge=default - 1 data[0] = 0x85; data[1] = 0x01; data[2] = 0x79; lcd_cmd(spi, 0xE8); lcd_data(spi, data, 3); //Driver timing control, all=0 unit data[0] = 0x00; data[1] = 0x00; lcd_cmd(spi, 0xEA); lcd_data(spi, data, 2); //Power on sequence control, //cp1 keeps 1 frame, 1st frame enable //vcl = 0, ddvdh=3, vgh=1, vgl=2 //DDVDH_ENH=1 data[0] = 0x64; data[1] = 0x03; data[2] = 0x12; data[3] = 0x81; lcd_cmd(spi, 0xED); lcd_data(spi, data, 4); //Pump ratio control, DDVDH=2xVCl data[0] = 0x20; lcd_cmd(spi, 0xF7); lcd_data(spi, data, 1); //Power control 1, GVDD=4.75V data[0] = 0x26; lcd_cmd(spi, 0xC0); lcd_data(spi, data, 1); //Power control 2, DDVDH=VCl*2, VGH=VCl*7, VGL=-VCl*3 data[0] = 0x11; lcd_cmd(spi, 0xC1); lcd_data(spi, data, 1); //VCOM control 1, VCOMH=4.025V, VCOML=-0.950V data[0] = 0x35; data[1] = 0x3E; lcd_cmd(spi, 0xC5); lcd_data(spi, data, 2); //VCOM control 2, VCOMH=VMH-2, VCOML=VML-2 data[0] = 0xBE; lcd_cmd(spi, 0xC7); lcd_data(spi, data, 1); //Memory access contorl, MX=MY=0, MV=1, ML=0, BGR=1, MH=0 data[0] = 0x28; lcd_cmd(spi, 0x36); lcd_data(spi, data, 1); //Pixel format, 16bits/pixel for RGB/MCU interface data[0] = 0x55; lcd_cmd(spi, 0x3A); lcd_data(spi, data, 1); //Frame rate control, f=fosc, 70Hz fps data[0] = 0x00; data[1] = 0x1B; lcd_cmd(spi, 0xB1); lcd_data(spi, data, 2); //Display function control data[0] = 0x08; data[1] = 0x82; data[2] = 0x27; lcd_cmd(spi, 0xB6); lcd_data(spi, data, 3); //Enable 3G, disabled data[0] = 0x08; lcd_cmd(spi, 0xF2); lcd_data(spi, data, 1); //Gamma set, curve 1 data[0] = 0x01; lcd_cmd(spi, 0x26); lcd_data(spi, data, 1); //Positive gamma correction data[0] = 0x0F; data[1] = 0x31; data[2] = 0x2B; data[3] = 0x0C; data[4] = 0x0E; data[5] = 0x08; data[6] = 0x4E; data[7] = 0xF1; data[8] = 0x37; data[9] = 0x07; data[10] = 0x10; data[11] = 0x03; data[12] = 0x0E; data[13] = 0x09; data[14] = 0x00; lcd_cmd(spi, 0xE0); lcd_data(spi, data, 15); //Negative gamma correction data[0] = 0x00; data[1] = 0x0E; data[2] = 0x14; data[3] = 0x03; data[4] = 0x11; data[5] = 0x07; data[6] = 0x31; data[7] = 0xC1; data[8] = 0x48; data[9] = 0x08; data[10] = 0x0F; data[11] = 0x0C; data[12] = 0x31; data[13] = 0x36; data[14] = 0x0F; lcd_cmd(spi, 0xE1); lcd_data(spi, data, 15); //Column address set, SC=0, EC=0xEF data[0] = 0x00; data[1] = 0x00; data[2] = 0x00; data[3] = 0xEF; lcd_cmd(spi, 0x2A); lcd_data(spi, data, 4); //Page address set, SP=0, EP=0x013F data[0] = 0x00; data[1] = 0x00; data[2] = 0x01; data[3] = 0x3F; lcd_cmd(spi, 0x2B); lcd_data(spi, data, 4); //Memory write lcd_cmd(spi, 0x2C); //Entry mode set, Low vol detect disabled, normal display data[0] = 0x07; lcd_cmd(spi, 0xB7); lcd_data(spi, data, 1); |
Выведем дисплей из спящего режима и включим его
1 2 3 4 5 |
lcd_data(spi, data, 1); //Sleep out lcd_cmd(spi, 0x11); //Display on lcd_cmd(spi, 0x29); |
В файле spi_ili9341.h объявим две глобальные переменные для хранения высоты и ширины дисплея
1 2 3 4 5 |
#include "sdkconfig.h" //--------------------------------------------------------------------- uint16_t TFT9341_WIDTH; uint16_t TFT9341_HEIGHT; //--------------------------------------------------------------------- |
Вернёмся в файл spi_ili9341.c в функцию TFT9341_ini и присвоим данным переменным значения из входящих параметров
1 2 3 |
lcd_cmd(spi, 0x29); TFT9341_WIDTH = w_size; TFT9341_HEIGHT = h_size; |
В следующей части урока мы начнём писать тесты для проверки работы дисплея, добавляя при этом дополнительные служебные функции.
Предыдущий урок Программирование МК ESP32 Следующая часть
Недорогие отладочные платы ESP32 можно купить здесь Недорогие отладочные платы ESP32
2,8 дюймов 240×320 SPI TFT LCD
Логический анализатор 16 каналов можно приобрести здесь
Многофункциональный переходник JTAG UART FIFO SPI I2C можно приобрести здесь CJMCU FT232H USB к JTAG UART FIFO SPI I2C
Смотреть ВИДЕОУРОК в RuTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в Дзен (нажмите на картинку)
Добавить комментарий