Инициализация TFT дисплея на примере ILI9341 для AVR.
Около года тому назад на сайте появилась статья о том как инициализировать TFT дисплей, под управлением SSD1289, а где-то около месяца назад мне написал один из посетителей сайта. Суть письма была в том, что он заказал дисплей по указанной в статье ссылке, но запустить его не получалось и он предложил этот дисплей и ещё несколько других выслать мне, а я, в свою очередь, должен буду выложить код если получится их запустить.
Как оказалось, дисплей, который ему прислали с али управляется драйвером ILI9341, об этом помогла догадаться надпись на нём.
Верхний дисплей с драйвером SSD1289, нижний с ILI9341.
Поэтому как только ко мне пришёл этот дисплей, сразу начал изучать даташит на ILI9341.
В отличие от SSD1289 у ILI9341 нет регистров, для общения с ним используются команды. Сначала посылаешь команду, а затем набор параметров, то есть после того, как мы послали команду дисплей уже ждёт, что ему передадут параметры. Также надо сказать, что общение с дисплеем осуществляется по одному из двух протоколов: интеловский i8080 и мотороловский M6800, чем они отличаются описывал тут, не стал изменять традициям и выбрал i8080. У этого протокола существует две реализации, для получения более подробной информации можно почитать даташит.
Первым делом необходимо реализовать низкоуровневые функции и тут важно понять, что мы правильно понимаем даташит. Самый простой способ проверить, правильность понимания даташита, это считать какую-то информацию с дисплея, например, его ID.
На картинке видно, что если контроллеру отправить команду 0xD3h и 4 раза считать данные, то в последние два считывания он должен вернуть ID(9341) драйвера. Но для начала надо разобраться как отправлять команды и читать данные, для этого нам понадобятся три картинки из даташита.
Первая, определяет управляющие сигналы для выбранного интерфейса.
Вторая и третья определяют их последовательность, для записи
для чтения
Тогда функция, для отправки команды, будет выглядеть так.
А функция которая читает данные так.
Из скриншотов понятно, что описанные выше функции заработали, осталось реализовать отправку данных по 8 и 16 бит. Дело в том, что в основном при отправке данных используются только младшие 8 бит шины, но, например, при записи данных в память дисплея используются все 16 бит.
Всё, мы реализовали все необходимые низкоуровневые функции, теперь можно переходить непосредственно к инициализации. Перед тем как перейти к инициализации, надо сказать, что напряжение питания ЖК-ячейки постоянно изменяет свою полярность, сделано это для того, чтобы избежать явлений гидролиза и диссоциации сложных органических соединений, из которых состоит жидкокристаллический материал.
В функции инициализации есть функция TFT_SetOrientation(2), которая определяет порядок отрисовки, MAX_X и MAX_Y объявлены глобально и инициализированы нулями.
А вот как выглядит дисплей после инициализации.
Чтобы понять, что он точно инициализировался, закрасил его красным цветом.
Как это сделать буду описывать в следующей статье.
Теперь что касается схемы подключения так, как распиновка 40 выводных дисплеев SSD1289 и ILI9341 одинаковая, схему подключения оставил без изменения и выглядит она так.
Таким образом, можно сделать одну отладочную плату для обоих дисплеев и ещё важно чтобы напряжение питания МК и дисплея было 3.3 вольта, при питании от 5 вольт дисплей не запустился. Подсветку дисплея тоже запитал от 3.3 вольта через резистор 47Ом.
А на этом всё, в следующей статье мы рассмотрим как выводить символы на дисплей и там же будет проект для Atmega16 в AtmelStudio6.2.
Для желающих быстро проверить работает ли их дисплей, оставляю тут прошивку для Atmega16, которая заливает дисплей разными цветами .
Недавно приобрёл пару дисплеев тут.
Как оказалось, дисплей, который ему прислали с али управляется драйвером ILI9341, об этом помогла догадаться надпись на нём.
Верхний дисплей с драйвером SSD1289, нижний с ILI9341.
Поэтому как только ко мне пришёл этот дисплей, сразу начал изучать даташит на ILI9341.
В отличие от SSD1289 у ILI9341 нет регистров, для общения с ним используются команды. Сначала посылаешь команду, а затем набор параметров, то есть после того, как мы послали команду дисплей уже ждёт, что ему передадут параметры. Также надо сказать, что общение с дисплеем осуществляется по одному из двух протоколов: интеловский i8080 и мотороловский M6800, чем они отличаются описывал тут, не стал изменять традициям и выбрал i8080. У этого протокола существует две реализации, для получения более подробной информации можно почитать даташит.
Первым делом необходимо реализовать низкоуровневые функции и тут важно понять, что мы правильно понимаем даташит. Самый простой способ проверить, правильность понимания даташита, это считать какую-то информацию с дисплея, например, его ID.
На картинке видно, что если контроллеру отправить команду 0xD3h и 4 раза считать данные, то в последние два считывания он должен вернуть ID(9341) драйвера. Но для начала надо разобраться как отправлять команды и читать данные, для этого нам понадобятся три картинки из даташита.
Первая, определяет управляющие сигналы для выбранного интерфейса.
Вторая и третья определяют их последовательность, для записи
для чтения
Тогда функция, для отправки команды, будет выглядеть так.
void TFT_Send_Cmd(uint8_t cmd)
{
COMMAND_PORT &= ~(1<<lcd_dc); //будем слать команду
COMMAND_PORT |= (1<<lcd_rd); //выставляем на ножке, отвечающей за чтение 1
COMMAND_PORT &= ~(1<<lcd_cs); //активируем чип
COMMAND_PORT1 &= ~(1<<lcd_wr); //стробируем битом записи
DATA_PORT_0 = cmd;
_delay_us(5);
COMMAND_PORT1 |= (1<<lcd_wr);
COMMAND_PORT |= (1<<lcd_cs); //деактивируем чип
}
А функция которая читает данные так.
uint8_t TFT_Read_Data(void)
{
uint8_t data = 0;
DATA_DDR_0 = 0X00; //порт на вход с подтяжкой к земле
DATA_PORT_0 = 0x00;
COMMAND_PORT |= (1<<lcd_dc); //будем читать ДАННЫЕ
COMMAND_PORT1 |= (1<<lcd_wr); //выставляем на ножке, отвечающей за запись 1
COMMAND_PORT &= ~(1<<lcd_cs); //активируем чип
COMMAND_PORT &= ~(1<<lcd_rd); //стробируем битом чтения
_delay_us(5);
data = PINA;
COMMAND_PORT |= (1<<lcd_rd);
COMMAND_PORT |= (1<<lcd_cs); //деактивируем чип
DATA_DDR_0 = 0XFF; //порт на выход
return data;
}
Из скриншотов понятно, что описанные выше функции заработали, осталось реализовать отправку данных по 8 и 16 бит. Дело в том, что в основном при отправке данных используются только младшие 8 бит шины, но, например, при записи данных в память дисплея используются все 16 бит.
void TFT_Write_Data(uint8_t data)
{
COMMAND_PORT |= (1<<lcd_dc); //будем слать ДАННЫЕ
COMMAND_PORT |= (1<<lcd_rd); //выставляем на ножке, отвечающей за чтение 1
COMMAND_PORT &= ~(1<<lcd_cs); //активируем чип
COMMAND_PORT1 &= ~(1<<lcd_wr); //стробируем битом записи
DATA_PORT_0 = data;
_delay_us(5);
COMMAND_PORT1 |= (1<<lcd_wr);
COMMAND_PORT |= (1<<lcd_cs); //деактивируем чип
}
void TFT_Write_Data16(uint16_t data)
{
COMMAND_PORT |= (1<<lcd_dc); //будем слать ДАННЫЕ
COMMAND_PORT |= (1<<lcd_rd); //выставляем на ножке, отвечающей за чтение 1
COMMAND_PORT &= ~(1<<lcd_cs); //активируем чип
COMMAND_PORT1 &= ~(1<<lcd_wr); //стробируем битом записи
DATA_PORT_0 = (data & 0x00ff);
DATA_PORT_8 = (data >> 8);
_delay_us(5);
COMMAND_PORT1 |= (1<<lcd_wr);
COMMAND_PORT |= (1<<lcd_cs); //деактивируем чип
}
Всё, мы реализовали все необходимые низкоуровневые функции, теперь можно переходить непосредственно к инициализации. Перед тем как перейти к инициализации, надо сказать, что напряжение питания ЖК-ячейки постоянно изменяет свою полярность, сделано это для того, чтобы избежать явлений гидролиза и диссоциации сложных органических соединений, из которых состоит жидкокристаллический материал.
void Init_ILI9341(void)
{
//конфигурируем все используемые выводы как выходы
DATA_DDR_0 = 0XFF;
DATA_DDR_8 = 0XFF;
COMMAND_DDR = 0XFF;
COMMAND_DDR1 = 0X01;
COMMAND_PORT |= (1<<lcd_res);
TFT_Send_Cmd(0x01); //Software Reset
_delay_ms(1000);
//Power Control 1
TFT_Send_Cmd(0xC0); //задаём градацию серого цвета
TFT_Write_Data(0x25);
//Power Control 2
TFT_Send_Cmd(0xC1); //настройка step up преобразователя
TFT_Write_Data(0x11);
//VCOM Control 1
TFT_Send_Cmd(0xC5); //контрастность определяется разностью VCOMH - VCOML = 5.2V
TFT_Write_Data(0x2B); //VCOMH = 3.825
TFT_Write_Data(0x2B); //VCOML = -1.375
//VCOM Control 2
TFT_Send_Cmd(0xC7); //на Vcom по сути ШИМ, а тут мы задаем offset для него
TFT_Write_Data(0x86); //VML=58 VMH=58
//Memory Access Control
TFT_SetOrientation(orient); //выбираем ориентацию дисплея
//COLMOD: Pixel Format Set
TFT_Send_Cmd(0x3A); //один пиксель будет кодироваться 16 битами
TFT_Write_Data(0x05);
//Frame Rate Control
TFT_Send_Cmd(0xB1);
TFT_Write_Data(0x00);
TFT_Write_Data(0x18); //Frame Rate 79Hz
//Display Function Control
TFT_Send_Cmd(0xB6);
TFT_Write_Data(0x0A);
TFT_Write_Data(0x82);//восьмой бит определяет нормальный цвет кристала белый - 1, черный - 0,
TFT_Write_Data(0x27);
// Sleep Out
TFT_Send_Cmd(0x11);
_delay_ms(120);
//Display On
TFT_Send_Cmd(0x29);
}
В функции инициализации есть функция TFT_SetOrientation(2), которая определяет порядок отрисовки, MAX_X и MAX_Y объявлены глобально и инициализированы нулями.
void TFT_SetOrientation(uint8_t orient)
{
TFT_Send_Cmd(0x36);
switch (orient)
{
case 0: TFT_Write_Data(0x48);
break;
case 1: TFT_Write_Data(0x28);
break;
case 2: TFT_Write_Data(0x88);
break;
case 3: TFT_Write_Data(0xE8);
break;
}
if (orient == 0 || orient == 2)
{
MAX_X = 239;
MAX_Y = 319;
}
else
{
MAX_X = 319;
MAX_Y = 239;
}
}
А вот как выглядит дисплей после инициализации.
Чтобы понять, что он точно инициализировался, закрасил его красным цветом.
Как это сделать буду описывать в следующей статье.
Теперь что касается схемы подключения так, как распиновка 40 выводных дисплеев SSD1289 и ILI9341 одинаковая, схему подключения оставил без изменения и выглядит она так.
Таким образом, можно сделать одну отладочную плату для обоих дисплеев и ещё важно чтобы напряжение питания МК и дисплея было 3.3 вольта, при питании от 5 вольт дисплей не запустился. Подсветку дисплея тоже запитал от 3.3 вольта через резистор 47Ом.
А на этом всё, в следующей статье мы рассмотрим как выводить символы на дисплей и там же будет проект для Atmega16 в AtmelStudio6.2.
Для желающих быстро проверить работает ли их дисплей, оставляю тут прошивку для Atmega16, которая заливает дисплей разными цветами .
Недавно приобрёл пару дисплеев тут.
Похожие статьи