Работаем с цветным TFT дисплеем ILI9341

Наконец добрались руки до дисплея. Купил его еще летом, а вот нормально заняться им вышло только сейчас.
Дисплеев на сегодняшний день огромное количество, мой выбор пал на решение "лоу кост". Такой дисплей у китайских друзей стоит в 5-6$.

В общем то ничего необычного. Разрешение всего 320х240, но зато он цветной и с последовательным интерфейсом, что нещадно бъет по скорости обновления, но сильно экономит ноги контролера (и нервы при соединении на макетке).
У данного дисплея есть контролер для обработки команд и обмена с микроконтроллером - ILI9341.

Из него можно узнать набор команд и последовательность данных.
У дисплея есть ноги для обмена по SPI, нога аппаратного сброса и нога, которая помогает ему понять когда принятые данные - команда, а когда - цвет пикселя.
Для всего этого "дергонога" я решил заготовить макросы:

#define TFT_RESET_PIN GPIO_Pin_2
#define TFT_DC_PIN    GPIO_Pin_3
#define TFT_CS_PIN    GPIO_Pin_4

#define TFT_DC_SET    GPIO_SetBits(GPIOA, TFT_DC_PIN);
#define TFT_DC_RESET  GPIO_ResetBits(GPIOA, TFT_DC_PIN);

#define TFT_RST_SET   GPIO_SetBits(GPIOA, TFT_RESET_PIN);
#define TFT_RST_RESET GPIO_ResetBits(GPIOA, TFT_RESET_PIN);

#define TFT_CS_SET    GPIO_SetBits(GPIOA, TFT_CS_PIN);
#define TFT_CS_RESET   GPIO_ResetBits(GPIOA, TFT_CS_PIN);

Этап настройки SPI я пожалуй пропущу. Тут все стандартно, берем настройку из любого примера для вашего камня в режиме 1 (CPOL = 0, CPHA = 0).
Когда это готово, делаем функции посылки команды и данных:

// шлем команду
void LCD_SendCommand(u8 com) {
  TFT_DC_RESET;
  TFT_CS_RESET;
  spi1_send(com);
  TFT_CS_SET;
}

// шлем данные
void LCD_SendData(u8 data) {
  TFT_DC_SET;
  TFT_CS_RESET;
  spi1_send(data);
  TFT_CS_SET;
}

Тут тоже все просто. Для посылки команды нужно чтобы линия DC была в 0, а для данных - в 1. С линией CS поступаем как всегда. Можно в принципе возложить эту ответственность на железо, если ваш контроллер такое умеет.

Инициализация дисплея

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

void LCD_Init(){
  // настраиваем ноги
  LCD_InitGPIO();
  
  TFT_CS_SET;
  spi1_init(); 
  
  // сброс дисплея
  TFT_RST_SET;
  LCD_SendCommand(ILI9341_RESET);
  Delay(100);
  
  /// настраиваем дисплей
  LCD_SendCommand(ILI9341_POWERA);
  LCD_SendData(0x39);
  LCD_SendData(0x2C);
  LCD_SendData(0x00);
  LCD_SendData(0x34);
  LCD_SendData(0x02);
  LCD_SendCommand(ILI9341_POWERB);
  LCD_SendData(0x00);
  LCD_SendData(0xC1);
  LCD_SendData(0x30);
  LCD_SendCommand(ILI9341_DTCA);
  LCD_SendData(0x85);
  LCD_SendData(0x00);
  LCD_SendData(0x78);
  LCD_SendCommand(ILI9341_DTCB);
  LCD_SendData(0x00);
  LCD_SendData(0x00);
  LCD_SendCommand(ILI9341_POWER_SEQ);
  LCD_SendData(0x64);
  LCD_SendData(0x03);
  LCD_SendData(0x12);
  LCD_SendData(0x81);
  LCD_SendCommand(ILI9341_PRC);
  LCD_SendData(0x20);
  LCD_SendCommand(ILI9341_POWER1);
  LCD_SendData(0x23);
  LCD_SendCommand(ILI9341_POWER2);
  LCD_SendData(0x10);
  LCD_SendCommand(ILI9341_VCOM1);
  LCD_SendData(0x3E);
  LCD_SendData(0x28);
  LCD_SendCommand(ILI9341_VCOM2);
  LCD_SendData(0x86);
  LCD_SendCommand(ILI9341_MAC);
  LCD_SendData(0x48);
  LCD_SendCommand(ILI9341_PIXEL_FORMAT);
  LCD_SendData(0x55);
  LCD_SendCommand(ILI9341_FRC);
  LCD_SendData(0x00);
  LCD_SendData(0x18);
  LCD_SendCommand(ILI9341_DFC);
  LCD_SendData(0x08);
  LCD_SendData(0x82);
  LCD_SendData(0x27);
  LCD_SendCommand(ILI9341_3GAMMA_EN);
  LCD_SendData(0x00);
  LCD_SendCommand(ILI9341_COLUMN_ADDR);
  LCD_SendData(0x00);
  LCD_SendData(0x00);
  LCD_SendData(0x00);
  LCD_SendData(0xEF);
  LCD_SendCommand(ILI9341_PAGE_ADDR);
  LCD_SendData(0x00);
  LCD_SendData(0x00);
  LCD_SendData(0x01);
  LCD_SendData(0x3F);
  LCD_SendCommand(ILI9341_GAMMA);
  LCD_SendData(0x01);
  LCD_SendCommand(ILI9341_PGAMMA);
  LCD_SendData(0x0F);
  LCD_SendData(0x31);
  LCD_SendData(0x2B);
  LCD_SendData(0x0C);
  LCD_SendData(0x0E);
  LCD_SendData(0x08);
  LCD_SendData(0x4E);
  LCD_SendData(0xF1);
  LCD_SendData(0x37);
  LCD_SendData(0x07);
  LCD_SendData(0x10);
  LCD_SendData(0x03);
  LCD_SendData(0x0E);
  LCD_SendData(0x09);
  LCD_SendData(0x00);
  LCD_SendCommand(ILI9341_NGAMMA);
  LCD_SendData(0x00);
  LCD_SendData(0x0E);
  LCD_SendData(0x14);
  LCD_SendData(0x03);
  LCD_SendData(0x11);
  LCD_SendData(0x07);
  LCD_SendData(0x31);
  LCD_SendData(0xC1);
  LCD_SendData(0x48);
  LCD_SendData(0x08);
  LCD_SendData(0x0F);
  LCD_SendData(0x0C);
  LCD_SendData(0x31);
  LCD_SendData(0x36);
  LCD_SendData(0x0F);
  LCD_SendCommand(ILI9341_SLEEP_OUT);
  
  Delay(100);
  LCD_SendCommand(ILI9341_DISPLAY_ON);
  LCD_SendCommand(ILI9341_GRAM);
}

Рисуем

Чтобы что-то нарисовать нужно поставить курсор в нужное место:

void LCD_SetCursorPosition(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) {
	LCD_SendCommand(ILI9341_COLUMN_ADDR);
	LCD_SendData(x1 >> 8);
	LCD_SendData(x1 & 0xFF);
	LCD_SendData(x2 >> 8);
	LCD_SendData(x2 & 0xFF);

	LCD_SendCommand(ILI9341_PAGE_ADDR);
	LCD_SendData(y1 >> 8);
	LCD_SendData(y1 & 0xFF);
	LCD_SendData(y2 >> 8);
	LCD_SendData(y2 & 0xFF);
}

Курсор поставили, можно рисовать:

void LCD_DrawPixel(uint16_t x, uint16_t y, uint16_t color) {
    LCD_SetCursorPosition(x, y, x, y);
    LCD_SendCommand(ILI9341_GRAM);
    LCD_SendData(color >> 8);
    LCD_SendData(color & 0xFF);
}

Ради интереса можно сохранить картинку в GIMP в формат C файла. И подтянуть его в проект. Там вы увидите массив. Его нужно просто вывести в цикле на экран.

void LCD_Image(unsigned char const *img) {
        uint32_t n;
        
	LCD_SetCursorPosition(0, 0, LCD_WIDTH - 1, LCD_HEIGHT - 1);

	LCD_SendCommand(ILI9341_GRAM);

	for (n = 0; n < LCD_PIXEL_COUNT; n++) {
                u8 color = *img;
                img++;
		LCD_SendData(*img);
		LCD_SendData(color);
                img++;
	}
}

Результат:

Шрифты

Вывести текст на экрна тоже очень просто. Нужно раздобыть массив со шрифтом. Сделать это можно при помощи утилиты BitFontCreator (она крутая, но денег немереных стоит) или же народной приблуды FontsGenerator.exe, которую я нашел на easyelectronics.
В массиве аккуратно уложены битовые маски шрифта. Например имеем 16 пикселей высотой и 8 шириной, тогда нам нужно последовательно выводить по пикселю и при этом следить каким цветом нужно сделать текущий пиксель.

uint32_t LCD_Putchar(uint32_t x, uint32_t y, char c) {
        uint32_t i, j;
        unsigned short Data;
        
        uint32_t offset = (c-32)*TimesNewRoman.height;
        uint16_t width = TimesNewRoman.width;
        
    	for (i = 0; i < TimesNewRoman.height; i++) {

            Data = TimesNewRoman.data_table[offset+i];    
            
            
            for (j = 0; j < width; j++) {
                if ((Data << j) & 0x8000) {
                    LCD_DrawPixel(x + j, (y + i), 0xFFFF);  //white
                } else {
                    LCD_DrawPixel(x + j, (y + i), 0x0000);  //black
                }
            }
        }
        
        return x+width;
}

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

Строку можно выводить обычным методом при помощи указателей:

void LCD_DrawString(uint32_t x, uint32_t y, char *str)
{
    while(*str) {
        x = LCD_Putchar(x,y,*str++);
    }
}

Получается вот так:

Пример можно посмотреть тут: https://www.dropbox.com/s/3q0tvjz2f6j399p/tft_lcd_spi.rar

Просмотров:   32564

Комментарии

ср, 12/17/2014 - 04:39
sva

Какая частота полной заливки экрана в секунду у вас получилась?

ср, 12/17/2014 - 14:40

Не замерял. Но медленно. Порядка двух секунд идет полная перерисовка. Но с частотой не играл.

вс, 04/19/2015 - 18:52
Олег

Я думаю тормоза могут быть вызваны тем, что вы наверное делаете заливку с помощью функции LCD_DrawPixel.
Т.е. каждый пиксел в цикле рисуете с ее помощью. И тут получается Нереально много команд, 10 шт только на установку курсора для каждого пикселя. на самом деле если нужно выводить картинку (или большие цифры в виде картинок) или залитый прямоугольник, то его можно выводить построчно, например слева направо, команды
LCD_SetCursorPosition(x, y, x+(размер картинки по Х), y+1) и LCD_SendCommand(ILI9341_GRAM) даете в начале каждой строки, а потом в цикле рисуете строку двумя командами:
LCD_SendData(color >> 8);
LCD_SendData(color & 0xFF);
у дисплея автоинкремент.
переменную color меняете цветом пикселя из картинки. Скорость увеличивается в разы.
вот например переделанная заливка прямоугольника из этой библиотеки как у вас, процесса заливки не заметно теперь вообще:
void LCD_DrawFilledRectangle(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t color)
{ uint16_t i = 0;
uint16_t j = 0;
ILI9341_x = x0;
ILI9341_y = y0;
for(i=0;i<(y1-y0);i++){
ILI9341_SetCursorPosition(ILI9341_x, ILI9341_y + i, x1, ILI9341_y + i+1);
ILI9341_SendCommand(ILI9341_GRAM);
for(j=0;j<(x1-x0);j++)
{
ILI9341_SendData(color >> 8);
ILI9341_SendData(color & 0xFF);
}
}
}
Я ещё себе сделал шрифт 64х100, для электронного спидометра на этом экране, переписал все функции вывода картинок и шрифтов таким же образом, и все теперь летает, вывод цифр очень быстрый.) перерисовки не видно. Но у меня STM32F103, работает на 72 Мгц, SPI - 36 Мгц. Это в дебагере показывает так, сколько на самом деле - х.з. Разогнал его до 128 МГц, обновление цифр стало еще быстрее и плавное - хрен заметишь.)
Если интересно могу выслать исходники. zuidwest@bk.ru

вт, 04/21/2015 - 19:49

Цикл все равно медленный (на мой вкус).
Тут http://blablacode.ru/node/513 настроил DMA. Вот только картинку конвертировать нужно иначе.

чт, 12/18/2014 - 06:56
sva

Имеет смысл поиграть. Я когда-то брал готовую библиотеку, на частоте SPI 18 МГц экран выдавал 6-7 кадров в секунду. Это уже вполне приемлемо для интерфейса какого-нибудь девайса. В целом, экран вполне годный для изделий, если не гонять на нем видео)

пн, 12/22/2014 - 12:13

Согласен. Только вот пока задачу под него не подобрал.

сб, 12/27/2014 - 09:23
Гость

Судя из этого
https://www.youtube.com/watch?feature=player_detailpage&v=fPTaWHccljo
можно добиться и видео ;)

ср, 12/31/2014 - 14:12
Иван

Большая просьба подробнее описать как преобразовать формат jpg в Си (.c и .h). Я использовал программу GIMP 2. Но при выводе изображения на экране получается абракадабра

вс, 01/04/2015 - 10:47

Описывать там особо нечего.

Думаю вам просто нужно перевернуть картинку. Скорее всего вы пытаетесь вывести вертикальное изображение горизонтально.

чт, 04/30/2015 - 03:29
Анатолий

Спасибо за код импортировал на AVR заработало почти сразу. Почти потому что кто то написал что нужен режим 1 spi оказалось для AVR нужен режим 3 SPI. При заливке экрана прога зависает. Еще бы n 16 битная а LCD_PIXEL_COUNT=320*240=76800
void LCD_Fill(uint16_t color) {
unsigned int n, i, j;
i = color >> 8;
j = color & 0xFF;
LCD_SetCursorPosition(0, 0, LCD_WIDTH - 1, LCD_HEIGHT - 1);

LCD_SendCommand(ILI9341_GRAM);

for (n = 0; n < LCD_PIXEL_COUNT; n++) {
LCD_SendData(i);
LCD_SendData(j);
}
}

чт, 09/10/2015 - 22:41
radiomanoff

Здравствуйте.
Вопрос такого плана. Откуда взялись в инициализации - ILI9341_POWERA (0xCB) и ILI9341_POWERB (0xCF) ,в datasheet на ILI9341 я их в упор невижу.

вс, 11/01/2015 - 12:52

Power Control A
Страница 195. Даташит приложен в статье.

вс, 11/15/2015 - 08:22
Евгений

Есть возможность управления яркостью подстветки в таких модулях? У моего подстветка заведена на 5В, отдельного вывода нет. Спасибо за хорошую статью.

вт, 11/17/2015 - 16:03

кроме ШИМ ничего в голову не приходит

сб, 01/30/2016 - 09:10
Iptash

Что за команда GRAM?

сб, 01/30/2016 - 17:02

ILI9341_GRAM - это команда Memory Write

пн, 02/08/2016 - 13:58
Егор

А какой программой можно массив с шрифтом сделать? Подскажите, что бы как у вас uint16_t был?

пн, 02/08/2016 - 18:58

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

вс, 04/17/2016 - 07:07
Андрей

Скажите чем открывается ваш пример? Заранее спасибо.

вс, 04/17/2016 - 14:24

IAR embedded workbench for ARM

пн, 04/25/2016 - 19:58
cgw

Статья отстой ничего не понятно что куда написано для тех кто и так уже все знает ,только зачем для них писать неясно. А новички голову ломают недоумевая что куда, а статья говорит ну вы же и так все знаете.

вт, 05/31/2016 - 21:14
Дмитрий

Здравствуйте. Подскажите пожалуйста, как выводить переменную информацию, например данные с АЦП?

вт, 06/28/2016 - 19:24
Сергей123123

Статья ужас для новичков, нету схемы подключения даже, мало всего расписано! Но в основном из-за схемы, какие провода куда. толку от кода?!

сб, 07/09/2016 - 16:55
Кирилл

Медленный вывод получается потому, что сама функция вывода - неправильная. За один раз выводится только один байт данных (а это полпикселя), и в каждом байте дергается CS. А это - неправильно. С таким подходом даже на максимальных частотах интерфейса и ЦП у вас перерисовка дисплея будет со скоростью всего лишь пару кадров в секунду, а то и меньше!
Поэтому, учитесь работать эффективно! Даже если не используете DMA, только на одних лишь прерываниях модуля SPI - и то можно добиться существенной скорости. Лучше всего конечно DMA - аппаратная передача данных блоком некоторого размера.
Но если хотите действительно быстрых FPS - используйте параллельный интерфейс, 8-ми или лучше даже 16-битный.

сб, 07/30/2016 - 11:33
Oleg

Здравствуйте.
Подскажите пожалуйста, почему для прорисовки пикселя используется такая функция с четырьмя координатами:
LCD_SetCursorPosition(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2)

Тоесть, местоположение одного пикселя должно описываться только двумя координатами, а не четырьмя. Что реально делает эта функция

пн, 08/01/2016 - 15:13

Указывается диапазон пикселей которые можно писать.
Команда описана на с.110 приложенного к статье даташита.

сб, 08/06/2016 - 19:23
Oleg

Спасибо !

пн, 10/10/2016 - 00:14
cgw

Кто не будь подключил его на Atmega8/16/32/64...
Вот 1 человек подключил.
http://www.vabolis.lt/2013/12/07/avr49-kitas-grafinis-lcd-ili9341/
https://www.youtube.com/watch?v=1-yzlwWFBjc

Я сделал так же на 12Mhz кварц. ту же распиновку, залил его *.hex и без результата, 5раз прочел даташит, пробовал на нескольких дисплеях ничего не получается. Все правильно а ничего не происходит на экране.

Люди поделитесь кодом на atmega плиз, сделайте кто не будь нормальную статью, у кого получилось.
больше в сети ни одного примера на Atmega нету.

пн, 10/17/2016 - 14:54
Саша

Применил для stm32f103c8t6!!!

пн, 10/31/2016 - 12:26
Дима

объясните как работает функция, пожалуйста
void LCD_Image(unsigned char const *img) {
uint32_t n;

LCD_SetCursorPosition(0, 0, LCD_WIDTH - 1, LCD_HEIGHT - 1);

LCD_SendCommand(ILI9341_GRAM);

for (n = 0; n < LCD_PIXEL_COUNT; n++) {
u8 color = *img;
img++;
LCD_SendData(*img);
LCD_SendData(color);
img++;
}
}

ср, 11/09/2016 - 10:35

Сначала посылается команда установки курсора.
Затем в цикле данные изображения из массива img выводятся в цикле.
LCD_PIXEL_COUNT=320*240=76800 - количество пикселей на экране. оно же количество пикселей изображения.
Изображение при помощи GIMP я конвертировал в C файл и добавил в программу.

пн, 10/31/2016 - 19:21
Андрей

Спасибо, искал инфу для старта с этим дисплеем эта статья - то что нужно!

вс, 11/20/2016 - 09:55
-=Женек=-

Может кому пригодится мой опыт.
Китайцы для дисплеев ili9341 с диагональю 2.8 дюйма делают какую-то особую плату, точнее ставят на нее какой-то особый преобразватель питания. У меня дисплей глючил, отказывался запускаться, если не подключен JTAG (да, не запускался именно дисплей), зависал когда отключаешь JTAG и не только - мог зависнуть в любое удобное время.
Возле преобразователя на плате разведены контактные площадки под 0603 резистор с нулевым сопротивлением, если их соединить пайкой, питание идет непосредственно от источника.
Я запаял нахрен и все проблемы исчезли.

вс, 01/15/2017 - 07:56
Nick

Потому что питать модуль надо от 5 вольт!
Там стоит преобразователь - он делает из 5 вольт 3.3 вольта.
Если соединить выводы - закорачиваем вход и выход преобразователя и напряжение идет напрямую. Сделано для питания модуля от 3 вольт.

Кстати, у меня получалось выжать 12 fps при 16 MHz SPI.

ср, 02/08/2017 - 16:23
Дмитрий

Есть у кого кряк или ключ к BitFontCreator, или кто знает еще какую нибудь программу для генерации моноширинных шрифтов, в том числе и русских, в формате 16ти битного массива?

пт, 02/10/2017 - 13:26

Добрый день, у кого есть ключ, кряк, взлом на BotFontCreator 2.3.2? Или уже взломанная версия, или может кто знает любой другой редактор, которых генерирует такой же массив со шрифтом, как и в BotFontCreator?

вт, 02/14/2017 - 15:13

Добрый день, может у кого есть ссылка на выше упомянутую программу FontsGenerator.exe?

пн, 05/15/2017 - 15:16
Андрей

Уже все стали забывать. Старые телевизоры, с ЭЛТ, выводили изображение на экран черезстрочно. При общей частоте кадровой развертки 50гц. реально было 25. И ведь никто не замечал смены кадра. Мозг так устроен, что додумывает недостающие пробелы.

Добавить комментарий

Plain text

  • HTML-теги не обрабатываются и показываются как обычный текст
  • Адреса страниц и электронной почты автоматически преобразуются в ссылки.
  • Строки и параграфы переносятся автоматически.
CAPTCHA
Введи эти символы. Ато роботы одолели!