Як перестати боятися і любити mbed [Частина 5]

Продовжуємо серію публікацій, присвячених використанню середовища ARM mbed для створення прототипу вимірювального пристрою.

Сьогодні я нарешті закінчую опис програмної частини — залишилися питання, пов'язані з виведенням на TFT-дисплей зображень і кирилиці. Зробимо все красиво.


Зміст циклу публікацій:


Нагадаю, що мова йде про розробку прототипу пристрою з сенсорним екраном, яке служить для високошвидкісного вимірювання відносної вологості і (заодно) температури. Для написання програми для мікроконтролера використовується онлайн IDE mbed, що дозволяє створювати железонезависимый код, який однаково працює на налагоджувальних платах від SiLabs, Atmel, Wiznet, STM32, NXP і інших виробників.

1. Виведення зображень на TFT

Загальна схема управління TFT-дисплеєм з допомогою графічного контролера від FTDI вже була описана в попередніх статті. Як і інші пов'язані з відображенням процедури, виведення зображень на TFT-дисплей апаратно реалізований на графічний контролер FT801. Від керуючого хост-контролера потрібно тільки передавати на FT801 прості керуючі команди і необхідні дані.

Графічні контролери серії FT8xx дозволяють працювати із зображеннями формату .jpeg або .png. Зображення можна не тільки виводити на екран, але і трансформувати — змінювати розмір, обертати або переміщати по екрану, а також використовувати як заставку. Всі ці операції виконуються на графічний контролер по приходу відповідних команд від управляючого МК.

Говорячи про завантаження зображень на графічний контролер, варто відразу розділити два підходи до роботи з зображеннями.


У першому випадку файл .jpeg береться з пам'яті керуючого МК або з зовнішнього носія і відразу записується в пам'ять графічного контролера FT8xx. В цьому випадку для завантаження використовується команда CMD_LOADIMAGE. Після завантаження зображення в пам'ять FT8xx стають доступні всі функції для роботи з зображенням — трансформації картинки і її виведення на дисплей. Такий підхід є оптимальним, якщо вам є де зберігати зображення, тобто використовується USB Flash-пам'ять або SD-карта.

У другому випадку зображення попередньо стискається за алгоритмом Deflate. Отриманий в результаті кодування bitmap займає значно менше місця, тому може зберігатися не тільки на зовнішньому носії, але у вбудованій пам'яті керуючого МК. Зображення завантажується на графічний контролер FT8xx в стислому вигляді, а розпакування даних виконується вже графічним контролером. Для завантаження рисунка слугує команда CMD_INFLATE. Після того як зображення распаковано, з ним можна працювати точно так само, як і в першому випадку.

Оскільки в моєму проекті не передбачається використання зовнішньої пам'яті, будемо розглядати тільки перший випадок — роботу із стисненими зображеннями, що зберігаються в пам'яті керуючого мікроконтролера. В результаті я хочу виводити на дисплей три зображення: іконки для температури та відносної вологості на екрані з головним меню і фотографію датчика HYT-271 на екрані з описом датчика.





1.1. Форматування зображення, компресія

Алгоритм Deflate спочатку створювався для zip-архівів, але завдяки відсутності патентів успішно використовується і для інших цілей, в тому числі для стиснення зображень. Ось, до речі, відмінна стаття про Deflate і png.

Для перетворення зображення FTDI пропонує кілька консольних утиліт, наприклад img_cvt. Також доступні графічні оболонки зразок EVE Screen Editor, які не тільки дозволяють конвертувати зображення, але і взагалі сильно спрощують життя при створенні програм для графічного контролера. По суті, EVE Screen Editor — це емулятор TFT-модулів Riverdi.



В інтерфейсі програми немає рішуче нічого незвичайного — в центральному вікні формується майбутній екран TFT — поле, на яке можна перетягувати потрібні вам зображення та інші графічні елементи.

Використовуваний мною контролер FT801 дозволяє виводити на TFT-дисплей зображення дев'яти форматів: чорно-білі L1, L4 і L8, а також RGB332, ARGB2, ARGB4, RGB565, PALETTED і ARGB1555, про них можна детальніше почитати здесь. Різні формати дозволяють отримати різне співвідношення якості зображення та розміру кодує зображення бінарного файлу.


(на малюнку зліва направо 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. Він нам і потрібен.

1.2. Завантаження зображення в пам'ять графічного контролера

Отримавши бінарне представлення зображення, повертаємося з Screen Editor в mbed IDE, де в коді програми зберігаємо всі отримані bitmap як масиви. Іконка для вологості, наприклад, виглядає ось так.

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 — масив, який зберігає зображення.

Після такої операції іконка «Вологість» буде доступна для виводу на екран поки пам'ять графічного контролера не буде очищена програмно або в результаті скидання.

1.3. Захоплення зображення

Наступний крок після завантаження — «захоплення» зображення.

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. Однак важливо розуміти, що команди захоплення зображення, у відмінності від команд завантаження, є так званими командами дисплей-листа, тобто їх можна використовувати тільки після команди почала дисплей-листа і до команди закінчення дисплей-листа.
* Про те, що таке дисплей-лист і з чим його їдять, можна почитати у другої статті даного циклу.

1.4. Висновок зображення

Після того як виконані завантаження і захоплення зображення, його можна вивести на TFT-дисплей. Для цього використовуються команди групи BITMAP, наприклад для виведення іконки «Вологість» поверх однієї з кнопок головного меню я виконую три команди:

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();


Посилання на повний вихідний код проекту наводиться нижче.

Використання користувальницьких шрифтів

статті, присвяченій початку роботи з графічним контролером серії FT8xx, згадувалася підтримка стандартних віджетів — процедур для висновку щодо складних графічних об'єктів, які апаратно реалізовані на графічних контролерах від FTDI. Серед віджетів є текстовий рядок, у mbed-бібліотеці FT800_2 висновку рядка на дисплей відповідає функція Text().

(*_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, що й зображення, тому створення нового шрифту багато в чому повторює процес виведення зображень.

2.1. Форматування шрифту, компресія

На першому етапі потрібно завантажити або створити потрібний шрифт (формат .ttf цілком підійде) і отримати бінарний файл для завантаження в графічний контролер FT8xxx. Для цього можна використовувати або консольну утиліту, або той же EVE Screen Editor.


У 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.

2.2. Завантаження в пам'ять графічного контролера

Завантаження нового шрифту повторює процес завантаження зображення — зберігаємо бінарне представлення шрифту як масив і завантажуємо на FT8xx за визначеним заздалегідь початкового адресою в пам'ять графічного контролера за допомогою команди CMD_INFLATE.

#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));


2.3. Захоплення і встановлення шрифту

«Захоплення» інтерфейсу шрифту виконується тими ж командами, що і захоплення зображення, різниця полягає в тому що після команд BITMAP_HANDLE, BITMAP_SOURCE, BITMAP_LAYOUT і BITMAP_SIZE потрібно ще встановити новий шрифт через виклик SetFont().

(*_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);

2.4. Використання шрифту

Тепер серед користувальницьких шрифтів під номером 3 значиться завантажений нами шрифт Roboto Regular. Якби при конвертації не був змінений charset цього шрифту, то для зміни вбудованого шрифту номер 27 на Roboto Regular потрібно було б лише змінити

(*_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.

Кодування кирилиці CP1251image

Однак компілятор може і не підтримувати кирилицю, 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 і так далі
Для виконання такого перетворення рядка створена відповідна функція.

Функція перетворення рядка CreateStringRussian
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;
}
// "І"
else if (res == 0x49) {
russianStr[j] = 32 + 88;
}
// "С"
else if (res == 0x43) {
russianStr[j] = 32 + 89;
}
// "С"
else if (res == 0x53) {
russianStr[j] = 32 + 90;
}
}
j++;
}
russianStr[j] = 0;
}


Таким чином, щоб вивести на TFT-дисплей рядок «Відносна вологість» (або будь-яку іншу рядок російською мовою) потрібно спочатку виконати її перетворення, а потім використовувати стандартний вивід рядка, не забувши вказати номер шрифту в якості третього аргументу.

CreateStringRussian("Відносна вологість");
(*_TFT).Text(15, 15, 3, 0, russianStr);

3. Підсумковий результат

Вихідний код готового проекту доступний на developer.mbed.org. До теми сьогоднішньої статті відносяться наступні файли проекту:

  • /pictures.h — конвертовані зображення і шрифти
  • /TFT/display.ImagesAndFonts.cpp — завантаження і захоплення зображень і шрифтів, установка шрифтів
  • /TFT/display.StringsTransform.cpp — перетворення рядків
  • /TFT/display.Draw_MainMenu.cpp — формування екрані головного меню, де відображаються іконки Температура і Вологість
  • /TFT/display.Draw_AboutSensor.cpp — формування екрану з описом датчика, де виводиться його фото

Таким чином я нарешті закінчую опис створення проекту в онлайн IDE ARM mbed. Ми розглянули всі починаючи з написання mbed-івського Hello Word до досить об'ємної програми, що використовує дві бібліотеки периферійних пристроїв — HYT для однойменного датчика і FT800_2 для TFT-модуля від Riverdi.

Чари в тому, що отримана програма може бути скомпільована в робочу прошивку для будь-якої з підтримуваних mbed налагоджувальних плат.



В останній статті даного циклу поділюся історією створення корпусу для цього девайса.

Висновок

У висновку традиційно дякую за увагу читача і нагадую, що питання щодо застосування продукції, про яку ми пишемо на хабре, можна сміливо ставити на email, вказаний в моєму профілі.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.