Урок 34
Часть 7
Дисплей TFT 240×320 8bit
В прошлой части нашего урока мы написали ещё несколько функций для работы с дисплеем и вывели на его экран другие примитивы (линии, прямоугольники, окружности).
В данной заключительной части урока мы поработаем уже с текстом. Это будет нелёгкая работа, но зато успокаивет то, что эта часть будет последняя в данном уроке.
В одной из предыдущих частей мы создали массив для шрифта размером в 16 пикселей, правда там не все символы, а только некоторые, так как размещение во флеш-памяти всех символов невозможно из-за её нехватки. Добавим также массив шрифта в 8 пикселей
//—————————————————————
//font 8
const unsigned char chars8[][8] PROGMEM ={
//SPACE
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
// !
{0x04,0x04,0x04,0x04,0x00,0x04,0x00,0x00},
// «
{0x0A,0x0A,0x0A,0x00,0x00,0x00,0x00,0x00},
// #
{0x0A,0x0A,0x1F,0x0A,0x1F,0x0A,0x0A,0x00},
// $
{0x04,0x0F,0x14,0x0E,0x05,0x1E,0x04,0x00},
// %
{0x18,0x19,0x02,0x04,0x08,0x13,0x03,0x00},
// &
{0x0C,0x12,0x14,0x08,0x14,0x12,0x0D,0x00},
// '
{0x0C,0x04,0x08,0x00,0x00,0x00,0x00,0x00},
// (
{0x02,0x04,0x08,0x08,0x08,0x04,0x02,0x00},
// )
{0x08,0x04,0x02,0x02,0x02,0x04,0x08,0x00},
// *
{0x00,0x04,0x15,0x0E,0x15,0x04,0x00,0x00},
// +
{0x00,0x04,0x04,0x1F,0x04,0x04,0x00,0x00},
// ,
{0x00,0x00,0x00,0x00,0x0C,0x04,0x08,0x00},
// —
{0x00,0x00,0x00,0x1F,0x00,0x00,0x00,0x00},
// .
{0x00,0x00,0x00,0x00,0x00,0x0C,0x0C,0x00},
// /
{0x00,0x01,0x02,0x04,0x08,0x10,0x00,0x00},
// 0
{0x0E,0x11,0x13,0x15,0x19,0x11,0x0E,0x00},
// 1
{0x04,0x0C,0x04,0x04,0x04,0x04,0x0E,0x00},
// 2
{0x0E,0x11,0x01,0x02,0x04,0x08,0x1F,0x00},
// 3
{0x1F,0x02,0x04,0x02,0x01,0x11,0x0E,0x00},
// 4
{0x02,0x06,0x0A,0x12,0x1F,0x02,0x02,0x00},
// 5
{0x1F,0x10,0x1E,0x01,0x01,0x11,0x0E,0x00},
// 6
{0x06,0x08,0x10,0x1E,0x11,0x11,0x0E,0x00},
// 7
{0x1F,0x01,0x02,0x04,0x08,0x08,0x08,0x00},
// 8
{0x0E,0x11,0x11,0x0E,0x11,0x11,0x0E,0x00},
// 9
{0x0E,0x11,0x11,0x0F,0x01,0x02,0x0C,0x00},
// :
{0x00,0x01,0x02,0x04,0x08,0x10,0x00,0x00},
// ;
{0x00,0x0C,0x0C,0x00,0x0C,0x04,0x08,0x00},
// <
{0x02,0x04,0x08,0x10,0x08,0x04,0x02,0x00},
// =
{0x00,0x00,0x1F,0x00,0x1F,0x00,0x00,0x00},
// >
{0x08,0x04,0x02,0x01,0x02,0x04,0x08,0x00},
// ?
{0x0E,0x11,0x01,0x02,0x04,0x00,0x04,0x00},
// @
{0x0E,0x11,0x01,0x0D,0x15,0x15,0x0E,0x00},
// A
{0x0E,0x11,0x11,0x11,0x1F,0x11,0x11,0x00},
// B
{0x1E,0x11,0x11,0x1E,0x11,0x11,0x1E,0x00},
// C
{0x0E,0x11,0x10,0x10,0x10,0x11,0x0E,0x00},
// D
{0x1C,0x12,0x11,0x11,0x11,0x12,0x1C,0x00},
// E
{0x1F,0x10,0x10,0x1E,0x10,0x10,0x1F,0x00},
// F
{0x1F,0x10,0x10,0x1E,0x10,0x10,0x10,0x00},
// G
{0x0E,0x11,0x10,0x17,0x11,0x11,0x0E,0x00},
// H
{0x11,0x11,0x11,0x1F,0x11,0x11,0x11,0x00},
// I
{0x0E,0x04,0x04,0x04,0x04,0x04,0x0E,0x00},
// J
{0x07,0x02,0x02,0x02,0x02,0x12,0x0C,0x00},
// K
{0x11,0x12,0x14,0x18,0x14,0x12,0x11,0x00},
// L
{0x10,0x10,0x10,0x10,0x10,0x10,0x1F,0x00},
// M
{0x11,0x1B,0x15,0x15,0x11,0x11,0x11,0x00},
// N
{0x11,0x11,0x19,0x15,0x13,0x11,0x11,0x00},
// O
{0x0E,0x11,0x11,0x11,0x11,0x11,0x0E,0x00},
// P
{0x1E,0x11,0x11,0x1E,0x10,0x10,0x10,0x00},
// Q
{0x0E,0x11,0x11,0x11,0x15,0x12,0x0D,0x00},
// R
{0x1E,0x11,0x11,0x1E,0x14,0x12,0x11,0x00},
// S
{0x0F,0x10,0x10,0x0E,0x01,0x01,0x1E,0x00},
// T
{0x1F,0x04,0x04,0x04,0x04,0x04,0x04,0x00},
// U
{0x11,0x11,0x11,0x11,0x11,0x11,0x0E,0x00},
// V
{0x11,0x11,0x11,0x11,0x11,0x0A,0x04,0x00},
// W
{0x11,0x11,0x11,0x11,0x15,0x15,0x0E,0x00},
// X
{0x11,0x11,0x0A,0x04,0x0A,0x11,0x11,0x00},
// Y
{0x11,0x11,0x11,0x0A,0x04,0x04,0x04,0x00},
// Z
{0x1F,0x01,0x02,0x04,0x08,0x10,0x1F,0x00},
// [
{0x0E,0x08,0x08,0x08,0x08,0x08,0x0E,0x00},
//
{0x11,0x0A,0x1F,0x04,0x1F,0x04,0x04,0x00},
// ]
{0x0E,0x02,0x02,0x02,0x02,0x02,0x0E,0x00},
// ^
{0x04,0x0A,0x11,0x00,0x00,0x00,0x00,0x00},
// _
{0x00,0x00,0x00,0x00,0x00,0x00,0x1F,0x00},
// '
{0x08,0x04,0x00,0x00,0x00,0x00,0x00,0x00},
// a
{0x00,0x00,0x0E,0x01,0x0F,0x11,0x0F,0x00},
// b
{0x10,0x10,0x1E,0x11,0x11,0x11,0x1E,0x00},
// c
{0x00,0x00,0x0E,0x10,0x10,0x11,0x0E,0x00},
// d
{0x01,0x01,0x0D,0x13,0x11,0x11,0x0F,0x00},
// e
{0x00,0x00,0x0E,0x11,0x1F,0x10,0x0E,0x00},
// f
{0x06,0x09,0x08,0x1C,0x08,0x08,0x08,0x00},
// g
{0x00,0x0F,0x11,0x11,0x0F,0x01,0x0E,0x00},
// h
{0x10,0x10,0x16,0x19,0x11,0x11,0x11,0x00},
// i
{0x04,0x00,0x0C,0x04,0x04,0x04,0x0E,0x00},
// j
{0x02,0x00,0x06,0x02,0x02,0x12,0x0C,0x00},
// k
{0x10,0x10,0x12,0x14,0x18,0x14,0x12,0x00},
// l
{0x18,0x08,0x08,0x08,0x08,0x08,0x1C,0x00},
// m
{0x00,0x00,0x1A,0x15,0x15,0x11,0x11,0x00},
// n
{0x00,0x00,0x16,0x19,0x11,0x11,0x11,0x00},
// o
{0x00,0x00,0x0E,0x11,0x11,0x11,0x0E,0x00},
// p
{0x00,0x00,0x1E,0x11,0x1E,0x10,0x10,0x00},
// q
{0x00,0x00,0x0F,0x11,0x0F,0x01,0x01,0x00},
// r
{0x00,0x00,0x16,0x19,0x10,0x10,0x10,0x00},
// s
{0x00,0x00,0x0E,0x10,0x0E,0x01,0x1E,0x00},
// t
{0x08,0x08,0x1C,0x08,0x08,0x09,0x06,0x00},
// u
{0x00,0x00,0x11,0x11,0x11,0x13,0x0D,0x00},
// v
{0x00,0x00,0x11,0x11,0x11,0x0A,0x04,0x00},
// w
{0x00,0x00,0x11,0x11,0x11,0x15,0x0A,0x00},
// x
{0x00,0x00,0x11,0x0A,0x04,0x0A,0x11,0x00},
// y
{0x00,0x00,0x11,0x11,0x0F,0x01,0x0E,0x00},
// z
{0x00,0x00,0x1F,0x02,0x04,0x08,0x1F,0x00}
};
//—————————————————————
Теперь добавим функцию по выводу символа определённой высоты в определённое место экрана
//—————————————————————
void TFT9341_Draw_Char(int x, int y, unsigned int color, unsigned int phone, unsigned char charcode, unsigned char size)
{
}
//—————————————————————
Во входных параметрах будут координаты вывода символа, цвет символа, цвет фона, код символа и размер символа.
Добавим возможность отслеживания размера с помощью операции ветвления
void TFT9341_Draw_Char(int x, int y, unsigned int color, unsigned int phone, unsigned char charcode, unsigned char size)
{
switch(size)
{
int i,h;
case 1:
break;
case 2:
break;
case 3:
break;
case 4:
break;
}
}
вариант 1 будет у нас для шрифта размером 8 пикселей, а вариант 2 для 16 пиксельного, остальные два варианта пока будут в запасе.
Начнем заполнять первый кейс, написав туда двухмерный цикл
case 1:
for(h=0;h<8;h++)
{
for(i=0;i<8;i++)
{
}
}
break;
В принципе, несложно догадаться, что мы из элемента массива собираем пиксели по горизонтали (i) и по вертикали (h).
Теперь внутри внутреннего цикла будет условие, которое в зависимости от значения проверяемого бита в элементе массива будет отрисовывать точку определённого цвета. При единице будет цвет символа, а при нуле — цвет фона
for(i=0;i<8;i++)
{
if ((pgm_read_byte(&chars8[charcode-0x20][h])>>(7-i))&0x01)
{
TFT9341_DrawPixel(x+i,y+h,color);
}
else
{
TFT9341_DrawPixel(x+i,y+h,phone);
}
}
В случае второго варианта (размер шрифта 16 пикселей) будет также двухмерный цикл, только h уже будет доходить до 16, а не до восьми
case 2:
for(h=0;h<16;h++)
{
for(i=0;i<8;i++)
{
}
}
break;
Далее в теле внутреннего цикла у нас будет уже два условия, одно — для первого байта горизонтали, а второе — для второго
for(i=0;i<8;i++)
{
if ((pgm_read_byte(&chars16[charcode-0x20][h*2])>>(7-i))&0x01)
{
TFT9341_DrawPixel(x+i,y+h,color);
}
else
{
TFT9341_DrawPixel(x+i,y+h,phone);
}
if ((pgm_read_byte(&chars16[charcode-0x20][h*2+1])>>(7-i))&0x01)
{
TFT9341_DrawPixel(x+i+8,y+h,color);
}
else
{
TFT9341_DrawPixel(x+i+8,y+h,phone);
}
}
Создадим прототип нашей функции и попробуем в функции main() что-то написать на экране
TFT9341_FillScreen(BLACK);
TFT9341_Draw_Char(10,10,RED,GREEN,0x21,2);
TFT9341_Draw_Char(26,10,RED,GREEN,0x22,2);
TFT9341_Draw_Char(42,10,RED,GREEN,0x23,2);
TFT9341_Draw_Char(58,10,RED,GREEN,0x24,2);
TFT9341_Draw_Char(74,10,RED,GREEN,0x25,2);
TFT9341_Draw_Char(10,26,RED,GREEN,0x21,1);
TFT9341_Draw_Char(18,26,RED,GREEN,0x22,1);
TFT9341_Draw_Char(26,26,RED,GREEN,0x23,1);
TFT9341_Draw_Char(34,26,RED,GREEN,0x24,1);
TFT9341_Draw_Char(42,26,RED,GREEN,0x25,1);
_delay_ms(1000);
Соберём код, прошьём контроллер и посмотрим результат
Теперь попробуем написать функцию отрисовки целой строки на экране
//—————————————————————
void TFT9341_String(unsigned int x, unsigned int y, unsigned int color,unsigned int phone,char *str, unsigned char size)
{
while(*str)
{
if((x + (size*8)) > X_SIZE)
{
x = 1;
y = y + (size*8);
}
TFT9341_Draw_Char(x, y, color, phone,*str, size);
x += size*8;
*str++;
}
}
//—————————————————————
При кажущейся сложности отрисовки строки, функция оказалась не такой и тяжёлой. Постепенно сдвигая указатель на следующий символ в строке и отслеживая конец строки, мы потихоньку выводим символы слева направо. Но также не забываем отслеживать правый прай дисплея,чтобы не уйти за него.
Также напишем прототип данной функции и в main() напишем тест для проверки данной функции
_delay_ms(1000);
TFT9341_FillScreen(BLACK);
TFT9341_String(1, 1, RED,GREEN,«12345ABCDE», 1);
TFT9341_String(1, 9, RED,GREEN,«EDCBA54321», 1);
TFT9341_String(10, 17, RED,GREEN,«ABCDEabcde», 1);
_delay_ms(1000);
TFT9341_FillScreen(BLACK);
while (1)
Соберём код, прошьём контроллер и посмотрим наши строки на практике
А также в бесконечный цикл мы напишем тест вывода случайных нулей и единиц размером 8 пикселей в случайные места экрана
TFT9341_FillScreen(BLACK);
while (1)
{
TFT9341_Draw_Char((rand()%15)*16,(rand()%20)*16,GREEN,BLACK,0x21+(rand()%2),2);
}
}
Скомпилируем наш код, прошьём контроллер и посмотрим результат
Я думаю, просмотрев полностью данное занятие, вы стали гораздо грамотнее в программировании микроконтроллеров, а также немного начали разбираться в написании кода для контроллеров дисплеев с параллельным подключением.
Предыдущая часть Программирование МК AVR Следующий урок
Техническая документация на контроллер дисплея ILI9341
Программатор, символьный дисплей LCD 20×4 и переходник для него можно приобрести здесь:
Программатор USBASP USBISP с адаптером USBASP USBISP 3.3 с адаптером
Смотреть ВИДЕОУРОК в RuTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)