Урок 34
Часть 4
Дисплей TFT 240×320 8bit
В предыдущей части нашего занятия по дисплею мы написали функцию ориентации дисплея, а также завершили писать функцию инициализации дисплея.
Напишем теперь функцию заливки определённой области памяти данными пикселей одного цвета для последующего использования в функции заливки одним цветом прямоугольной области
//—————————————————————
void TFT9341_Flood(unsigned short color, unsigned long len)
{
}
//—————————————————————
Первый входной параметр — это цвет пикселей, а второй длина области памяти для заливки. Адресация будет в отдельной функции.
Добавим в функцию несколько переменных, причём две из них сразу проинициализируем определённым образом
void TFT9341_Flood(unsigned short color, unsigned long len)
{
unsigned short blocks;
unsigned char i, hi = color>>8, lo=color;
То есть 16 бит цвета мы здесь распределим по двум переменным.
Опустим ножку выбора и ножку команд/данных
unsigned char i, hi = color>>8, lo=color;
CS_ACTIVE;
CD_COMMAND;
Отправим цвет в соответствующий для этого регистр
CD_COMMAND;
TFT9341_Write8(0x2C);
CD_DATA;
TFT9341_Write8(hi);
TFT9341_Write8(lo);
То есть здесь мы уже не используем специальные функции для отправки команд и данных для экономии времени, здесь уже время очень важно, иначе пострадает скорость работы с дисплеем. Поэтому мы уже пользуемся функциями более низкоуровневыми без лишнего дрыгания ножками.
Продекрементируем длину области, так как отсчёт у нас происходит не с 1, а с 0
TFT9341_Write8(lo);
len—;
Разобьём нашу длину на блоки по 64 пикселя
len—;
blocks=(unsigned short)(len/64);//64 pixels/block
Дальше условие, если младшая часть 16-битного цвета равна старшей
blocks=(unsigned short)(len/64);//64 pixels/block
if (hi==lo)
{
}
else
{
}
В теле условия цикл, равный количеству блоков
if (hi==lo)
{
while(blocks—)
{
}
}
В теле цикла ещё один цикл, равный 16
while(blocks—)
{
i=16;
do
{
WR_STROBE;WR_STROBE;WR_STROBE;WR_STROBE;//2bytes/pixel
WR_STROBE;WR_STROBE;WR_STROBE;WR_STROBE;//x4 pixel
} while (—i);
}
В данном цикле мы просто стробируем, отправляя один и тот же байт в контроллер дисплея, уровни байта ведь у на ножках порта установлен. Получается, что мы один и тот же байт отправим 128 раз, то есть если разбить на пары, то 64 пары и, следовательно, 64 пикселя. То есть при условии равенства старшего и младшего байтов цвета мы отправили столько блоков, сколько у нас есть, а засчёт того, что мы каждый раз не устанавливаем уровень на ножках порта данных, мы значительно выигрываем во времени.
Выйдя из цикла отправки всех блоков, мы отправим оставшуюся часть байтов, если у нас длина не будет делиться на 64 без остатка
} while (—i);
}
//Fill any remaining pixels(1 to 64)
for (i=(unsigned char)len&63;i—;)
{
WR_STROBE;
WR_STROBE;
}
}
Вот так. Теперь заходим в «противную часть» нашего условия, когда у нас старший и младший байт цвета будут разными. Я думаю, это будет чаще. Там также будет цикл, равный количеству полных блоков
else
{
while(blocks—)
{
}
}
В цикле также цикл, равный 16, в котором мы передаём весь блок
while(blocks—)
{
i=16;
do
{
TFT9341_Write8(hi);TFT9341_Write8(lo);TFT9341_Write8(hi);TFT9341_Write8(lo);
TFT9341_Write8(hi);TFT9341_Write8(lo);TFT9341_Write8(hi);TFT9341_Write8(lo);
} while (—i);
}
Ну, тут понятно, раз байты не равны, передаём их по очереди.
Затем также передаём оставшуюся часть пикселей
} while (—i);
}
//Fill any remaining pixels(1 to 64)
for (i=(unsigned char)len&63;i—;)
{
TFT9341_Write8(hi);
TFT9341_Write8(lo);
}
}
Ну и в конце поднимем ножку выбора
TFT9341_Write8(lo);
}
}
CS_IDLE;
}
Теперь напишем функцию записи в регистр 32-битного числа
//—————————————————————
void TFT9341_WriteRegister32(unsigned char r, unsigned long d)
{
CS_ACTIVE;
CD_COMMAND;
TFT9341_Write8(r);
CD_DATA;
_delay_us(1);
TFT9341_Write8(d>>24);
_delay_us(1);
TFT9341_Write8(d>>16);
_delay_us(1);
TFT9341_Write8(d>>8);
_delay_us(1);
TFT9341_Write8(d);
CS_IDLE;
}
//—————————————————————
Я думаю, тут даже объяснять ничего не надо. Мы сначала передаём адрес регистра, а затем с некотроыми задержками передаём наши данные.
Но, прежде чем отправлять в память байты, нам нужно объявить область памяти, в которую будет вся наша цепочка одинаковых пикселей отправляться. Для этого мы напишем специальную функцию
//—————————————————————
void TFT9341_SetAddrWindow(unsigned int x1,unsigned int y1,unsigned int x2,unsigned int y2)
{
unsigned long t;
CS_ACTIVE;
t = x1;
t<<=16;
t |= x2;
TFT9341_WriteRegister32(0x2A,t);//Column Addres Set
t = y1;
t<<=16;
t |= y2;
TFT9341_WriteRegister32(0x2B,t);//Page Addres Set
CS_IDLE;
}
//—————————————————————
Ну здесь мы в специализированные регистры 2Ah и 2Bh передаём наши коортдинаты сначала начала и окончания вертикальной области, а затем горизонтальной, распределив соответственно эти координаты в четырехбайтовые величины. Отправка в регистры стандартная.
Также нам потребуется какой-то шрифт, если мы захотим выводить какой-то текст. Я с помощью определённой программы создал этот шрифт, причём написал не весь шрифт, а несколько букв, ибо памяти у нас не так много. Сделал я их высотой в 16 пикселей
//————————————————————— //font 16 const unsigned char chars16[][32] PROGMEM = { //SPACE {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, //0 {0x00, 0x00, 0x00, 0x00, 0x07, 0xc0, 0x0f, 0xe0, 0x0c, 0x60, 0x18, 0x30, 0x18, 0x30, 0x18, 0x30, 0x18, 0x30, 0x18, 0x30, 0x18, 0x30, 0x0c, 0x60, 0x0f, 0xe0, 0x07, 0xc0, 0x00, 0x00, 0x00, 0x00}, //1 {0x00, 0x00, 0x00, 0x00, 0x03, 0x80, 0x03, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x03, 0xc0, 0x03, 0xc0, 0x00, 0x00, 0x00, 0x00}, //A {0x00, 0x00, 0x00, 0x00, 0x07, 0xf0, 0x07, 0xf0, 0x01, 0x40, 0x03, 0x60, 0x03, 0x60, 0x06, 0x30, 0x07, 0xf0, 0x0f, 0xf8, 0x0c, 0x18, 0x0c, 0x18, 0x3e, 0x3e, 0x3e, 0x3e, 0x00, 0x00, 0x00, 0x00}, //B {0x00, 0x00, 0x00, 0x00, 0x0f, 0xe0, 0x0f, 0xf0, 0x06, 0x30, 0x06, 0x30, 0x07, 0xe0, 0x07, 0xf0, 0x06, 0x38, 0x06, 0x18, 0x06, 0x18, 0x06, 0x38, 0x0f, 0xf0, 0x0f, 0xe0, 0x00, 0x00, 0x00, 0x00}, //C {0x00, 0x00, 0x00, 0x00, 0x07, 0xd8, 0x0f, 0xf8, 0x1c, 0x38, 0x38, 0x18, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x08, 0x38, 0x0c, 0x1c, 0x38, 0x0f, 0xf0, 0x07, 0xe0, 0x00, 0x00, 0x00, 0x00} }; //—————————————————————
Ну и далее собственно заливка всей области экрана определённым цветом
//—————————————————————
void TFT9341_FillScreen(unsigned int color)
{
TFT9341_SetAddrWindow(0,0,X_SIZE-1,Y_SIZE-1);
TFT9341_Flood(color,(long)X_SIZE*(long)Y_SIZE);
}
//—————————————————————
Мы использовали наши вышенаписанные функции, чтобы сначала передать координаты, а затем уже отправить цвет и длину всей области, получив её способом перемножения горизонтального и вертикального размеров.
Напишем на данную функцию прототип и вызовем её в main() после инициализации дисплея
TFT9341_ini();
TFT9341_FillScreen(BLACK);
Соберём код, пошьём контроллер и посмотрим результат
Как мы видим, дисплей наш окрасился в чёрный цвет.
Поменяем цвет на красный и ещё раз проверим
TFT9341_ini();
TFT9341_FillScreen(RED);
В следующей части нашего урока мы напишем ещё несколько функций для работы с дисплеем и попробуем вывести некоторые примитивы на экран дисплея.
Предыдущая часть Программирование МК AVR Следующая часть
Техническая документация на контроллер дисплея ILI9341
Программатор, символьный дисплей LCD 20×4 и переходник для него можно приобрести здесь:
Программатор USBASP USBISP с адаптером USBASP USBISP 3.3 с адаптером
Смотреть ВИДЕОУРОК в RuTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)
Спасибо за видео, мне нравится. Только вот что-то у меня не пошло вроде все проверил, подсоединено все верно и программу проверил все также, но что — то не идет, не могу понять куда копать?
Спасибо за оценку ресурса! Возможно, другой дисплей немного, идентификатор хотя бы считывается?
Добрый день,
спасибо за столь подробное описание.
Я пишу на CodeVisionAVR. Решил переделать код под нее.
Но вот незадача, качаю исходник, беру готовых hex, прошиваю.
Дисплей инициализируется, но вместо теста выдает вот такую картинку
Открываю проект в AtmelStudio 7
Компилирую по новой и экран перестает инициализироваться.
Подскажите в чем может быть причина?
И не совсем понял, как можно изменить номера портов Data.
С командными все понятно, они явно заданы.
А у дата портов указывается только буква порта
Добрый день!
Номера ножек совпадают с номерами битов в 8-битной переменной cmd.
А по какой причине скопмилированный hex из исходника может не работать?
Просто в AtmelStudio не работал. Возможно есть какие-то нюансы при компиляции
И есть ли возможность указания номеров ножек не по порядку?
Добрый день. Скажите пожалуйста, если в Atmega8 залить прошивку из вашего файла исходника , должны ли быть дергания на шине управления без подключенного дисплея. Просто у меня на всех ножках МК тишина.
Код в железе не работает ! Автор вопросы игнорирует.
Автор все вопросы читает. Если у него есть ответ, то он его даёт. Если ответа нет, то такой комментарий скорее всего также пройдёт модерацию и имеет право быть опубликованным, если нет в нём ссылок и нарушений. Вашу ситуацию воспроизвести не удалось. У меня код работает прекрасно. Вы уверены, что в Вашем дисплее установлен именно такой же контроллер?
Здравствуйте. Да, уверен. Но дело даже не в этом. ATMEGA8 без подключения к дисплею совсем молчит — на всех ножках по нулям. МК точно живой, т.к. другие ваши проекты работают на нем. Но такого же не может быть !? Также в схеме на первом уроке не указан внешний кварц, а в проекте указана частота 16МГц. Мега8 может тактироваться от внутреннего источника на 16 МГц ? Даже если и от 8-ми, все равно на ножках МК должны же быть какие-то шевеления?
Надо читать техническую документацию. Источник тактирования задаётся фьюзами. Поэтому только внимательно изучать, иначе можно контроллер вывести из строя.
Я это понимаю. Вопрос в том , у Вас в данном проекте используется внешний кварц на 16 МГц?
И еще странность. В Протеусе , на ножке RESET присутствует 5 вольт. И дисплей там работает. Заливаю прошивку в Atmega8 на RESET 0 вольт постоянно . МК точно рабочий.
Здраствуйте! Понадобился Ili9341 по 8bit.
Собирал инфу и попал к вам. Проект старый, но может вы вспомните свои алгоритмы.
В вашем коде для отправки данных весь массив делится на блоки по 64 пикселя и каждый блок на 16 по 4 пикселя.
Зачем нужны блоки по 4 пикселя мне, думаю, понятны, а зачем нужны блоки по 64 — нет.
Отправляя по 4, а не по одному, уменьшается количество проверок While.
В одном цикле проверка условия While будет после отправки каждых 4 байта.
Во втором цикле после каждых 16х4 = 64 байт будет дополнительная проверка, забирающая такт или больше.
Не могу понять зачем нужен второй цикл. Что я упускаю?
Ошибочка: не байт, а пиксель. (пиксель = 2 байта).
Исправьте, пожалуйста.