Продолжим знакомиться с дисплеями с точки зрения "железячника". Рассмотрим, как делается вывод текста на экраны TFT и обычные, монохромные. Начнём с того, что из себя представляет дисплей в целом... Для простоты возьмём цветной TFT, его особенность в том, что каждая точка на нём, в отличии от чёрно-белых, имеет свой уникальный адрес. Схематичная структура дисплея выглядит так:
Контроллер интерфейса - обеспечивает общение с МК. Самые распространённые интерфейсы - SPI, I2C, параллельный 4-х, 8-ми или 16-разрядный. Контроллер интерфейса определяет, команда или данные ему пришли. Если команда, то она выполняется, если данные - они записываются в текущую ячейку памяти. Какая ячейка считается текущей, определяется специальными регистрами, в которые можно писать координаты напрямую с помощью специальных команд, таким образом выбирая, какая ячейка памяти будет записана.
Следует отметить, что контроллер интерфейса и контроллер матрицы могут находится на одном чипе, а также раздельно. И сделано это более сложно, чем я сейчас описываю.
Контроллер матрицы постоянно сканирует память и засвечивает соответствующие пиксели тем значением, которое прописано в соответствующей ячейке памяти. Чёрному цвету соответствует значение 0х0000, белому цвету - 0хFFFF. После того, как в память будет записано значение (в цветных мониторах чаще 16-битное), при следующем сканировании эта точка выведется на матрицу.
Во всех дисплеях координаты имеют направление X от левого верхнего угла вправо, координата Y от верхнего левого угла вниз, пусть таблица будет нашим экраном:
Возьмём файл знакогенератора:
static const byte ASCII[][5] = { {0x00, 0x00, 0x00, 0x00, 0x00} // 20 " ", {0x00, 0x00, 0x5f, 0x00, 0x00} // 21 "!", {0x00, 0x07, 0x00, 0x07, 0x00} // 22 """, {0x14, 0x7f, 0x14, 0x7f, 0x14} // 23 "#" , {0x24, 0x2a, 0x7f, 0x2a, 0x12} // 24 "$" , {0x23, 0x13, 0x08, 0x64, 0x62} // 25 "%" , {0x36, 0x49, 0x55, 0x22, 0x50} // 26 "&" , {0x00, 0x05, 0x03, 0x00, 0x00} // 27 "'" , {0x00, 0x1c, 0x22, 0x41, 0x00} // 28 "(" , {0x00, 0x41, 0x22, 0x1c, 0x00} // 29 ")" , {0x14, 0x08, 0x3e, 0x08, 0x14} // 2a "*" , {0x08, 0x08, 0x3e, 0x08, 0x08} // 2b "+" , {0x00, 0x50, 0x30, 0x00, 0x00} // 2c "," , {0x08, 0x08, 0x08, 0x08, 0x08} // 2d "-" , {0x00, 0x60, 0x60, 0x00, 0x00} // 2e "." , {0x20, 0x10, 0x08, 0x04, 0x02} // 2f "/" , {0x3e, 0x51, 0x49, 0x45, 0x3e} // 30 "0" , {0x00, 0x42, 0x7f, 0x40, 0x00} // 31 "1" , {0x42, 0x61, 0x51, 0x49, 0x46} // 32 "2" , {0x21, 0x41, 0x45, 0x4b, 0x31} // 33 "3" , {0x18, 0x14, 0x12, 0x7f, 0x10} // 34 "4" , {0x27, 0x45, 0x45, 0x45, 0x39} // 35 "5" , (и так далее)
Таким образом, каждый символ при размере шрифта 5х7 пикселей кодируется пятью байтами. Бит равный "0" означает, что точка не засвечивается, бит равный "1" означает, что точка засвечена.
Вот теперь мы полученные бинарные данные занесём в память нашего экрана. Нам нужно занести каждый символ - ноль одним цветом, единицу другим. Но, чтобы не засорять таблицу огромными цифрами, мы так и занесём их в виде нуля и единицы, начиная с младшего бита. Причём заносить мы их будем по вертикали. В этом знакогенераторе символу "W" соответствует код {0x3F, 0x40, 0x38, 0x40, 0x3F}, распишем их в виде нулей и единиц:
0x3F = 0011 1111 0x40 = 0100 0000 0x38 = 0011 1000 0x40 = 0100 0000 0x3F = 0011 1111
И занесём в таблицу:
Вроде ничего интересного, но если раскрасить, допустим, единицы - красным, нули - белым, то получаем:
Мы видим, что получилась буква "W". Таким образом драйвер дисплея при выводе текста читает из памяти символ, из файла знакогенератора берёт данные, соответствующие данному символу и последовательно передаёт их контроллеру дисплея. Причём знакогенераторы могут быть разными. Засвечиваемые точки в байте могут идти с младшего бита, а могут и со старшего. Зависит это или от прихоти разработчика или от стандарта, если разработчик пытается ему следовать.
Итак... В итоге мы имеем цветной дисплей, в котором адресация точек такая, что каждая точка имеет свой уникальный адрес. Масштабирование такого шрифта выполняется довольно просто. Так как мы всё равно выводим на TFT-дисплей по одной точке, мы задаём координаты левого верхнего угла и правого нижнего. Если символ имеет стандартный размер, то эти координаты совпадают. Затем идёт заполнение этого пикселя цветом с помощью одной команды. Если символ увеличен в два раза, то координата правого нижнего угла увеличивается в два раза. Получаем квадрат 2х2 пикселя. Четыре получившихся пикселя заполняются уже четырьмя командами. При дальнейшем увеличении символа все шаги повторяются столько раз, сколько нужно.
Такой принцип вывода символа используется библиотекой AdafruitGFX, он довольно медленный, но универсальный.
Монохромный дисплей.
В монохромных дисплеях типа графических LCD и OLED адресация идёт немного по другому:
Координата верхнего левого угла будет X0:Y0. И, как всегда, есть нюанс - если по этому адресу записать байт, у нас загорятся те точки, которые соответствуют биту, который равен "1" в нашем байте. Получается мы можем записать только байт, побитно мы не можем писать, какие точки нам нужно зажечь. Таким образом, адресация этого дисплея: X - координата столбца, а Y - координата строки.
Такой метод вывода текста гораздо быстрее, чем у TFT-дисплеев. Но он хорош до тех пор, пока нам не нужно будет выводить текстовый символ размером не 5х7 точек, а допустим 8x12. Размер шрифта не кратен 8 по вертикали, поэтому алгоритм усложняется, а графику выводить вообще напряжно.
Программисты нашли из этого выход: заполняется буфер в ОЗУ, после заполнения сбрасывается в память экрана, но при большом разрешении экрана буфер будет довольно большим. Есть ещё метод - данные пишутся в экран, затем необходимые ячейки считываются из памяти экрана, модифицируются и пишутся обратно. Здесь ОЗУ нужно меньше, но контроллер дисплея должен позволять чтение из памяти дисплея. Есть и ещё методы, но рассматривать их тут не будем, так как статья не об этом, и, собственно, на этом на сегодня и завершаем.