- PVSM.RU - https://www.pvsm.ru -
Продолжаем серию публикаций, посвященных использованию среды ARM mbed для создания прототипа измерительного устройства.
Сегодня я наконец-то заканчиваю описание программной части — остались вопросы связанные с выводом на TFT-дисплей изображений и кириллицы. Сделаем всё красиво.
Содержание цикла публикаций:
Напомню, что речь идет о разработке прототипа устройства с сенсорным экраном, которое служит для высокоскоростного измерения относительной влажности и (заодно) температуры. Для написания программы для микроконтроллера используется онлайн IDE mbed, позволяющая создавать железонезависимый код, который одинаково работает на отладочных платах от SiLabs, Atmel, Wiznet, STM32, NXP и других производителей.
Графические контроллеры серии FT8xx позволяют работать с изображениями формата .jpeg или .png. Изображение можно не только выводить на экран, но и трансформировать — изменять размер, вращать или перемещать по экрану, а также использовать как заставку. Все эти операции выполняются на графическом контроллере по приходу соответствующих команд от управляющего МК.
Говоря о загрузке изображений на графический контроллер, следует сразу разделить два подхода к работе с изображениями.
В первом случае файл .jpeg берется из памяти управляющего МК или с внешнего носителя и сразу записывается в память графического контроллера FT8xx. В этом случае для загрузки используется команда CMD_LOADIMAGE. После загрузки изображения в память FT8xx становятся доступны все функции для работы с изображением — трансформации картинки и её вывод на дисплей. Такой подход является оптимальным, если вам есть где хранить изображения, то есть используется USB Flash-память или SD-карта.
Во втором случае изображение предварительно сжимается по алгоритму Deflate. Полученный в результате кодирования bitmap занимает гораздо меньше места, поэтому может храниться не только на внешнем носителе, но во встроенной памяти управляющего МК. Изображение загружается на графический контроллер FT8xx в сжатом виде, а распаковка данных выполняется уже графическим контроллером. Для загрузки сжатого изображения служит команда CMD_INFLATE. После того как изображение распаковано, с ним можно работать точно так же, как и в первом случае.
Поскольку в моём проекте не предполагается использование внешней памяти, будем рассматривать только первый случай — работу со сжатыми изображениями, хранящимися в памяти управляющего микроконтроллера. В результате я хочу выводить на дисплей три изображения: иконки для температуры и относительной влажности на экране с главным меню и фотографию датчика HYT-271 на экране с описанием датчика.
Для преобразования изображения FTDI предлагает несколько консольных утилит [6], например img_cvt. Также доступны графические оболочки вроде EVE Screen Editor [7], которые не только позволяют конвертировать изображения, но и вообще сильно упрощают жизнь при создании программ для графического контроллера. По сути, EVE Screen Editor — это эмулятор TFT-модулей Riverdi.
В интерфейсе программы нет решительно ничего необычного — в центральном окне формируется будущий экран TFT — поле, на которое можно перетаскивать нужные изображения и другие графические элементы.
Используемый мной контроллер FT801 позволяет выводить на TFT-дисплей изображения девяти форматов: черно-белые L1, L4 и L8, а также RGB332, ARGB2, ARGB4, RGB565, PALETTED и ARGB1555, о них можно подробнее почитать здесь [8]. Разные форматы позволяют получить разное соотношение качества изображений и размера кодирующего изображение бинарного файла.
(на рисунке слева направо L1, L4, L8, RGB332, ARGB2, ARGB4, RGB565, PALETTED, ARGB1555)
По идее, наилучшее качество можно получить при использовании формата ARGB1555, однако на практике лучше попробовать разные варианты и подобрать наиболее подходящий. Фотография датчика действительно лучше всего выглядит после преобразования в ARGB1555 или RGB565, однако в какой-то момент мне не хватило встроенной памяти МК и пришлось отказаться от этих форматов в пользу RGB332. Смотрится более-менее прилично, а занимает 1865 байт вместо 7067 и 7816 у ARGB1555 и RGB565 соответственно.
С иконками получилось интереснее. Исходные файлы — .png с прозрачным фоном размером 37x77 и 53x67 точек. Прозрачность иконок можно сохранить только с кодированием типа ARGB, т.е. выбирать приходится из форматов ARGB2, ARGB4, и ARGB1555. Из них наиболее симпатичное сглаженное изображение дает не ARGB2, и, к моему удивлению, не ARGB1555, а ARGB4.
(на рисунке слева направо ARGB2, ARGB4, ARGB1555)
После того как выбран подходящий формат, нужно сохранить проект в Screen Editor. В папке, куда Screen Editor был установлен, появится директория images, где для каждого из использованных изображений создан файл .binh. Он то нам и нужен.
const unsigned char hum_icon[]={
120,156,165,86,203,109,195,48,12,205,177,40,250,25,193,35,120,4,141,160,17,60,130,71,200,181,55,143,224,123,46,26,193,11,20,208,4,133,54,168,71,96,31,165,248,43,74,145,82,18,8,20,138,79,228,35,41,193,244,73,117,218,86,250,175,250,251,65,243,115,200,203,133,88,236,83,232,153,130,92,171,145,29,109,82,135,108,214,168,44,117,172,29,209,1,59,22,35,21,197,82,91,165,125,100,45,121,254,188,147,33,131,94,110,255,37,17,177,52,248,189,149,209,247,155,136,85,2,82,195,238,124,109,186,213,102,132,156,83,189,80,152,91,222,111,130,213,207,226,94,92,60,93,152,186,137,150,185,185,98,53,193,178,229,51,223,53,194,121,237,217,255,235,133,215,248,229,115,250,195,126,11,109,68,228,33,79,159,63,75,251,184,135,224,228,162,202,247,167,188,83,58,238,251,178,106,156,119,162,51,219,60,36,121,164,59,35,237,113,189,77,6,107,40,121,163,49,85,46,121,110,200,215,194,39,199,199,74,59,152,116,247,176,19,83,34,242,85,172,111,28,57,226,140,76,185,74,185,58,6,117,130,151,46,136,186,100,215,9,54,93,128,85,240,27,78,182,49,83,255,189,54,2,227,255,96,37,30,146,106,33,103,85,88,171,49,142,113,123,45,234,145,191,17,194,228,249,59,138,233,74,34,113,187,172,204,236,254,182,76,194,253,91,170,100,211,188,128,154,252,219,167,197,26,49,39,147,190,41,216,9,239,185,5,131,150,253,192,65,161,7,206,91,135,240,250,101,84,249,232,103,49,197,95,24,13,226,26,68,183,56,103,196,58,91,255,63,65,221,118,198,
};
Чтобы загрузить это изображение в память графического контроллера FT801, понадобится отправить с управляющего МК три команды:
#define IMAGE_ADDR_HUMIDITY 29696
...
(*_TFT).WrCmd32(CMD_INFLATE);
(*_TFT).WrCmd32(IMAGE_ADDR_HUMIDITY);
(*_TFT).WrCmdBufFromFlash(hum_icon, sizeof(hum_icon));
CMD_INFLATE — команда, сообщающая FT801, что в его память будет записано сжатое по алгоритму Deflate изображение.
IMAGE_ADDR_HUMIDITY — начальный адрес в памяти графического контроллера RAM_G, по которому будет доступно изображение.
hum_icon — массив, хранящий изображение.
После такой операции иконка «Влажность» будет доступна для вывода на экран пока память графического контроллера не будет очищена программно или в результате сброса.
StartDL();
...
(*_TFT).DL(BITMAP_HANDLE(0));
(*_TFT).DL(BITMAP_SOURCE(IMAGE_ADDR_HUMIDITY));
(*_TFT).DL(BITMAP_LAYOUT(ARGB4, 60, 38));
(*_TFT).DL(BITMAP_SIZE(NEAREST, BORDER, BORDER, 30, 38));
...
FinishDL();
Командой BITMAP_HANDLE мы присваиваем каждому изображению (объекту Bitmap) указатель — номер от 0 до 31, по которому в дальнейшем можно будет обратиться к изображению.
Команда BITMAP_SOURCE указывает на адрес в памяти RAM_G графического контроллера FT8xx (см. п. Загрузка изображения).
Команда BITMAP_LAYOUT сообщает графическому контроллеру формат изображения и его размеры, а команда BITMAP_SIZE определяет размер выводимого изображения. Изменяя её аргументы можно, например, обрезать картинку справа или слева.
Захват изображения, также как и его загрузку в память графического контроллера, достаточно выполнить один раз после инициализации FT8xx. Однако важно понимать, что команды захвата изображения, в отличии от команд загрузки, являются так называемыми командами дисплей-листа, то есть их можно использовать только после команды начала дисплей-листа и до команды окончания дисплей-листа.
* О том, что такое дисплей-лист и с чем его едят, можно почитать во второй статье данного цикла [9].
StartDL();
...
// отрисовка прямоугольника (кнопки)
...
(*_TFT).DL(BEGIN(BITMAPS));
(*_TFT).DL(VERTEX2II(12 + 255, 62 + 10, 0, 0));
(*_TFT).DL(END());
...
FinishDL();
В первых двух аргументах команды VERTEX2II указываются координаты для вывода на экран, а третий аргумент является указателем на иконку «Влажность», который был задан в BITMAP_SOURCE и BITMAP_HANDLE — «0».
StartDL();
...
(*_TFT).DL(BEGIN(BITMAPS));
(*_TFT).DL(VERTEX2II(12 + 260, 62 + 93 + 12 + 10, 1, 0));
(*_TFT).DL(END());
...
FinishDL();
StartDL();
...
(*_TFT).DL(BEGIN(BITMAPS));
(*_TFT).DL(VERTEX2II(360, 140, 2, 0));
(*_TFT).DL(END());
...
FinishDL();
Ссылка на полный исходный код проекта приводится ниже.
(*_TFT).Text(22, 67, 27, 0, "Current humidity (rH)");
Аргументы функции — координаты первого символа строки (22, 67), номер используемого шрифта (27), дополнительные опции (0) и, собственно, текстовая строка. С координатами всё понятно, дополнительные опции также относятся только к положению строки на экране, поэтому поговорим о шрифтах.
Номер используемого шрифта — это число от 0 до 31, причем номера с 0 до 15 зарезервированы под пользовательские шрифты, а номера с 16 по 31 соответствуют шестнадцати встроенным шрифтам. Если в вашем приложении достаточно выводить только первые 128 ASCII символов и вам достаточно стандартного начертания этих символов, то можно остановиться на этом месте и не читать статью дальше — просто используйте шрифты с номерами 16-31.
Если же вам нужны нестандартные начертания цифр и латиницы или требуется вывод символов, выходящих за пределы стандартного набора ASCII (например, кириллицы), то придется разбираться с загрузкой собственных шрифтов.
Для графических контроллеров FT8xx пользовательские шрифты — это почти те же bitmap, что и изображения, поэтому создание нового шрифта во многом повторяет процесс вывода изображений.
В Screen Editor шрифт импортируется также, как файлы изображений — в окно Content добавляется файл шрифта, а в окне Properties устанавливаются параметры его компрессии: формат, размер и charset.
Формат выбирается из трех опций — L1, L4 и L8. Разница, как и при конвертации изображений, в соотношении качества отрисовки и размера бинарного файла. Размер шрифта просто определяет ширину символов в пикселях, а наибольшего внимания заслуживает поле charset.
Для графических контроллеров FTDI шрифты по умолчанию состоят из 128 ASCII символов.
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~
// отсчет не с нулевого, а с 32-го символа
Если вы используете только эти символы и добавляете новый шрифт только чтобы изменить их начертание — отлично, конвертируйте шрифт не изменяя charset. А если нужно добавить кириллицу или какой-то другой не входящий в ASCII символ, то charset придется заменить. В моём приложении понадобится вся кириллица, цифры, некоторые знаки препинания и математические символы, знаки градуса и процента, а также несколько латинских букв. В итоге измененный charset выглядит следующим образом:
0123456789АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюя.,:-°±%<>rHYTICS
// отсчет не с нулевого, а с 32-го символа
Теперь, также как при работе с изображением, сохраняем проект Screen Editor, заходим в папку, где установлен EVE Screen Editor и в директории /fonts находим бинарный файл .binh.
#define FONT_ADDR_ROBOTO_REGULAR_30 16992
...
(*_TFT).WrCmd32(CMD_INFLATE);
(*_TFT).WrCmd32(FONT_SET_ROBOTO_REGULAR_30);
(*_TFT).WrCmdBufFromFlash(font_RobotoRegular30, sizeof(font_RobotoRegular30));
(*_TFT).DL(BITMAP_HANDLE(3));
(*_TFT).DL(BITMAP_SOURCE(FONT_ADDR_ROBOTO_REGULAR_30));
(*_TFT).DL(BITMAP_LAYOUT(L4, 16, 33));
(*_TFT).DL(BITMAP_SIZE(NEAREST, BORDER, BORDER, 32, 33));
(*_TFT).SetFont(3, FONT_SET_ROBOTO_REGULAR_30);
(*_TFT).Text(22, 67, 27, 0, "Current humidity (rH)");
на
(*_TFT).Text(22, 67, 3, 0, "Current humidity (rH)"); // выведет на экран ерунду
Однако мы собираемся выводить нестандартные для контроллера FT8xx символы, поэтому вместо явного указания строки («Current humidity (rH)») придется каждый раз лепить эту строку из отельных символов.
Рассмотрим строку «Относительная влажность». Задача состоит в том, чтобы каждому символу этой строки поставить в соответствие его номер в charset.
Если компилятор поддерживает кириллицу, то каждому символу начиная с прописных букв АБВ и заканчивая строчными эюя (с пропуском букв ё и Ё) будет соответствовать значение от 0xC0 до 0xFF. Значит чтобы сопоставить символы строки с номерами этих символов в charset нужно вычесть из кода каждого символа фиксированное значение. Например, если буква А в charlist занимает 32-ую позицию (0x20), а следующие за А буквы идут в том же порядке что и в таблице CP1251, то из кода каждого символа строки «Относительная влажность» (кроме пробела) нужно будет вычесть значение 0xA0.
Однако компилятор может и не поддерживать кириллицу, mbed-овский как раз не поддерживает. Это значит, что компилятор не воспринимает кириллицу как коды с 0xC0 до 0xFF, поэтому мне не остается ничего кроме как использовать юникод, точнее UTF-8.
Каждый символ, не входящий в основную ASCII таблицу — кириллица, знаки ° и ± — представляется как двухбайтный код UTF-8. Я беру код каждого символа и ставлю ему в соответствие номер в своём charset.
Для латинских букв, которые тоже есть в charlist, нужно также заменить юникод на номер в charset, разница лишь в том что код латинских букв и других знаков ASCII типа точки, запятой и процента состоит из одного, а не двух байт.
Символ конвертируемой строки | Код UTF-8 | Порядковый номер в моём charset |
АБВ… ноп | 0xD090… 0xD0BF | 43… 90 |
рст… эюя | 0xD180… 0xD18F | 91… 96 |
° | 0xC2B0 | 112 |
± | 0xC2B1 | 113 |
пробел | 0x20 | 32 |
0… 9 | 0x30… 0x39 | 33… 43 |
. | 0x2E | 108 |
, | 0x2C | 109 |
: | 0x3A | 110 |
и так далее |
Для выполнения такого преобразования строки создана соответствующая функция.
void Display::CreateStringRussian(const string rustext)
{
// CHANGED ASCII:
// 0123456789АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюя.,:-°±%<>rHYTICS
int len = rustext.length();
int j = 0;
for (int i = 0; i < len; i ++) {
uint16_t res = uint8_t(rustext[i]);
if (res > 0x7F) {
res = res << 8 | uint8_t(rustext[i + 1]);
// АБВ ... ноп
if ((res >= 0xD090) && (res <= 0xD0BF)) {
char offset = (char)(res - 0xD090);
russianStr[j] = 32 + 11 + offset;
// рст ... эюя
} else if ((res >= 0xD180) && (res <= 0xD18F)) {
char offset = (char)(res - 0xD180);
russianStr[j] = 32 + 59 + offset;
}
// Degree sign
else if (res == 0xC2B0) {
russianStr[j] = 32 + 79;
}
// Plus-minus sign
else if (res == 0xC2B1) {
russianStr[j] = 32 + 80;
}
i++;
} else {
// Space
if (res == 0x20) {
russianStr[j] = 32;
}
// Numbers
else if (res >= 0x30 && res <= 0x39) {
russianStr[j] = 32 + 1 + (res - 0x30);
}
// .
else if (res == 0x2E) {
russianStr[j] = 32 + 75;
}
// ,
else if (res == 0x2C) {
russianStr[j] = 32 + 76;
}
// :
else if (res == 0x3A) {
russianStr[j] = 32 + 77;
}
// -
else if (res == 0x2D) {
russianStr[j] = 32 + 78;
}
// %
else if (res == 0x25) {
russianStr[j] = 32 + 81;
}
// <
else if (res == 0x3C) {
russianStr[j] = 32 + 82;
}
// >
else if (res == 0x3C) {
russianStr[j] = 32 + 83;
}
// "r"
else if (res == 0x72) {
russianStr[j] = 32 + 84;
}
// "H"
else if (res == 0x48) {
russianStr[j] = 32 + 85;
}
// "Y"
else if (res == 0x59) {
russianStr[j] = 32 + 86;
}
// "T"
else if (res == 0x54) {
russianStr[j] = 32 + 87;
}
// "I"
else if (res == 0x49) {
russianStr[j] = 32 + 88;
}
// "C"
else if (res == 0x43) {
russianStr[j] = 32 + 89;
}
// "S"
else if (res == 0x53) {
russianStr[j] = 32 + 90;
}
}
j++;
}
russianStr[j] = 0;
}
Таким образом, чтобы вывести на TFT-дисплей строку «Относительная влажность» (или любую другую строку на русском языке) нужно сначала выполнить её преобразование, а затем использовать стандартный вывод строки, не забыв указать номер шрифта в качестве третьего аргумента.
CreateStringRussian("Относительная влажность");
(*_TFT).Text(15, 15, 3, 0, russianStr);
Таким образом я наконец-то заканчиваю описание создания проекта в онлайн IDE ARM mbed. Мы рассмотрели всё начиная с написания mbed-овского Hello Word до довольно объемной программы, использующей две библиотеки периферийных устройств — HYT для одноименного датчика и FT800_2 для TFT-модуля от Riverdi.
Волшебство в том, что полученная программа может быть скомпилирована в рабочую прошивку для любой из поддерживаемых в mbed отладочных плат [13].
В последней статье данного цикла поделюсь историей создания корпуса для этого девайса.
Автор: ЭФО
Источник [14]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/programmirovanie-mikrokontrollerov/199164
Ссылки в тексте:
[1] [Часть 1] Обзор использованных программных и аппаратных решений.: https://habrahabr.ru/company/efo/blog/308440/
[2] [Часть 2] Начало работы с графическим контроллером FT800. Использование готовых mbed-библиотек для периферийных устройств.: https://habrahabr.ru/company/efo/blog/309918/
[3] [Часть 3] Подключение датчика HYT-271. Создание и публикация в mbed собственной библиотеки для периферийных устройств.: https://habrahabr.ru/company/efo/blog/310058/
[4] [Часть 4] Разработка приложения: Структура программы, работа с сенсорным экраном.: https://habrahabr.ru/company/efo/blog/310720/
[5] отличная статья: https://habrahabr.ru/post/274825/
[6] несколько консольных утилит: http://www.ftdichip.com/Support/Utilities.htm#EVEImageConverters
[7] EVE Screen Editor: http://www.ftdichip.com/Support/Utilities.htm#EVEScreenEditor
[8] здесь: http://www.ftdichip.com/Support/Documents/AppNotes/AN_303%20FT800%20Image%20File%20Conversion.pdf
[9] второй статье данного цикла: https://habrahabr.ru/company/efo/blog/30991 8/#display-list
[10] статье: https://habrahabr.ru/company/efo/blog/309918
[11] консольную утилиту: http://www.ftdichip.com/Support/Utilities.htm#EVEFontConverter
[12] developer.mbed.org: https://developer.mbed.org/users/Ksenia/code/Temp__RH_at_TFT_with_touchscreen-RUS/
[13] поддерживаемых в mbed отладочных плат: https://developer.mbed.org/platforms/
[14] Источник: https://habrahabr.ru/post/311816/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best
Нажмите здесь для печати.