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

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


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

  1. Огляд використаних програмних і апаратних рішень.
  2. Початок роботи з графічним контролером FT800. Використання готових mbed-бібліотек для периферійних пристроїв.
  3. Підключення датчика HYT-271. Створення і публікація в mbed власної бібліотеки для периферійних пристроїв.
  4. Розробка додатки: Структура програми, робота з сенсорним екраном.
  5. Розробка програми: Виведення зображення на дисплей, проблеми русифікації.
  6. Друк деталей корпусу. Аналіз помилок проектування і інші висновки.

За підсумками трьох попередніх статей ми отримали mbed-проект — програму, яка може бути скомпільована в робочу прошивку для будь налагоджувальної плати з інтерфейсами SPI і I2C і підтримкою ARM mbed. В якості піддослідних виступали налагоджувальні плати SLSTK3400A від Silicon Labs, ATSAMD21-XPRO від Atmel і WIZwiki-W7500P від Wiznet.



Реалізована програма поки виконує два завдання — опитування датчика температури і відносної вологості HYT та виведення результатів вимірювань на TFT-дисплей від Riverdi. Проект доступний за адресою.

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

1. Структура TFT-модуля

Отже, використовується TFT-модуль Riverdi складається з власне дисплея, графічного контролера з серії FT8xx від FTDI і додаткових компонентів — сенсорного контролера, аудиоконтроллера і т. д. На сайті виробника можна знайти повний список доступних TFT-модулів, а на сайті ЕФО — доступні зі складу позиції та діючі ціни.

Дисплеї різняться діагоналлю, роздільною здатністю, яскравістю, типом підсвічування, наявністю кріпильної або декоративної рамки, а також типом сенсорного екрану — випускаються ємнісні і резистивні TFT, а також дисплеї без підтримки сенсорного введення. Вбудований графічний контролер відповідає можливостям дисплея: контролер FT800 призначений для резистивних дисплеїв, FT801 — для ємкісних, а більш старші моделі FT81x відрізняються підтримкою щодо великого дозволу і іншими додатковими функціями.

Я використовую симпатичнейший модуль RVT43ULFNWC00 серії uxTouch діагоналлю 4.3", виконаний у чорній декоративній рамці. Модуль має ємнісний сенсорний екран і, відповідно, вбудований графічний контролер FT801.

Ми вже говорили про порядок управління TFT-модулем Riverdi — керуючий контролер підключається до мікросхемі FT801 і обмінюється з графічним контролером простими командами, тобто здійснює читання і запис певних регістрів FT801. Відповідно командами, отриманими від керуючого контролера, графічний контролер взаємодіє з дисплеєм — здійснює обробку і виведення зображень, реалізує сенсорний ввід і роботу аудіоканалу.



В ємнісних TFT-модулях, на відміну від моделей з резистивним екраном, встановлений окремий контролер сенсорної панелі. Втім, наявність цього додаткового апаратного блоку ніяк не впливає на процес управління TFT з точки зору програміста.

2. Отримання даних про торканні

Графічний контролер FT801 підтримує стандартний і розширений режими сенсорного введення. У першому випадку можуть детектувати тільки поодинокі торкання, а в розширеному режимі підтримується мультитач. Використовуваний режим роботи визначається значенням в регістрі REG_CTOUCH_EXTENDED:

  • 0: extended mode — розширений режим,
  • 1: FT800 compatibility mode — стандартний single-touch режим, це режим за замовчуванням.
Я буду використовувати тільки режим single-touch. По-перше, для мого додатки його можливостей цілком достатньо, по-друге, так програма буде сумісна з модулями на базі FT800.

При торканні TFT-дисплея контролер сенсорної панелі передає на FT801 «сирі» дані про області дотику. Ці дані записуються в регістр REG_CTOUCH_RAW_XY графічного контролера, після чого FT801 обчислює координати дотику (x, y) і поміщає результат в регістр REG_TOUCH_TAG_XY. Обчислення координат являє собою матричні перетворення, в яких беруть участь дані з REG_CTOUCH_RAW_XY і матриця координат, яка зберігається в регістрах з REG_CTOUCH_TRANSFORM_A за REG_CTOUCH_TRANSFORM_F. До регістрів REG_CTOUCH_TRANSFORM нам ще доведеться повернутися.



Для детектування дотику, в принципі, можна використовувати дані з REG_TOUCH_TAG_XY, але для програміста передбачені більш зручні інструменти.

Найпростіший з цих інструментів — теги (TAG). Мітка — це номер від 1 до 255, який може бути присвоєно графічного об'єкту, тобто прямокутника, точки, лінії, кнопки, тексту і так далі. Під час дотику в області зазначеного графічного об'єкта, в регістрі REG_TOUCH_TAG встановиться значення відповідної мітки. Таким чином, щоб детектувати дотик об'єкта, хост-контролер повинен періодично опитувати регістр REG_TOUCH_TAG і порівнювати отримане значення з міткою, присвоєної цікавить об'єкту.

Контролери FT801 також підтримують трекінг натискання — автоматичне обчислення кута або лінійного відстані між точкою дотику і точкою із заданими координатами. У цьому випадку, крім встановлення мітки знадобиться задати параметри її відстеження (команда Track()), а для детектування натискання перевіряти регістр REG_TRACKER замість REG_TOUCH_TAG.

Втім, давно пора перейти до прикладів.

2.1. Робота з готовими віджетами
Спочатку розглянемо віджети — графічні об'єкти, які апаратно реалізовані на графічний контролер. Три найбільш простих віджета для сенсорного інтерфейсу — кнопка, ряд кнопок і слайдер.

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

TFT.FgColor(0xC1004D);
TFT.Keys(27, 127, 271, 41, 29, 0, "123");
TFT.Button(26, 33, 120, 36, 27, OPT_FLAT, "Button");
TFT.Slider(244, 45, 161, 17, 0, 17, 100);


Щоб елементи не тільки відображення, але і реагували на дотик, потрібно додати створеним об'єктам мітки і опитувати зберігає мітку регістр.

Елементів об'єкта Ряд кнопок (Keys) мітки присвоюються автоматично, тому для детектування натискання кнопок «1», «2» і «3» досить просто опитувати регістр REG_TOUCH_TAG:

TFT.Keys(27, 127, 271, 41, 29, 0, "123");
char pressedButton = TFT.Rd8(REG_TOUCH_TAG);

Елементів типу Button мітку потрібно призначити вручну:

TFT.DL(TAG(1));
TFT.Button(26, 33, 120, 36, 27, OPT_FLAT, "Button");

char pressedButton = TFT.Rd8(REG_TOUCH_TAG);

При роботі зі слайдером використовується трекінг — крім встановлення мітки, потрібно виконати команду Track, встановлює параметри трекінгу, а опитувати слід 32-розрядний регістр REG_TRACKER, а не 8-розрядний REG_TOUCH_TAG:

char sliderVal = 0;
TFT.Track(244, 45, 161, 17, 2);
...
while(1)
{
TFT.DL(TAG(2));
TFT.Slider(244, 45, 161, 17, 0, sliderVal, 255);

int pressedSlider = TFT.Rd32(REG_TRACKER);
sliderVal = (pressedSlider >> 16) * 255 / 65536;
}

Наведений код дозволить реєструвати положення слайдера (в даному випадку воно змінюється від 0 до 255) і перемальовувати слайдер у відповідності з поточним положенням бігунка.

2.2. Калібрування екрану
Насправді для роботи сенсорних елементів недостатньо створити дисплей-лист з описом графічних елементів і опитувати відповідний регістр.

Справа в тому, що після включення і ініціалізації TFT-модуля всі регістри графічного контролера скинуті в значення за замовчуванням. Трохи вище ми говорили, що для обчислення координат натискання використовується матриця, що зберігається в регістрах REG_CTOUCH_TRANSFORM. Так ось, ці регістри після ініціалізації теж «порожні», а щоб записати туди коректні значення, потрібно виконати калібрування сенсорного екрану.

Для калібрування служить інструкція графічного співпроцесора CMD_CALIBRATE. Грубо кажучи, по приходу цієї інструкції графічний контролер FT8xx виводить на дисплей один за одним три точки, на які потрібно натиснути. У відповідності з координатами трьох торкань в регістри REG_CTOUCH_TRANSFORM заносяться потрібні значення.

У бібліотеці FT800_2 передбачена стандартна функція для проведення калібрування
DLstart(); 
DL(CLEAR_COLOR_RGB(64,64,64)); 
DL(CLEAR(1,1,1)); 
DL(COLOR_RGB(0xff,0xff,0xff)); 
Text((DispWidth/2), (DispHeight/2), 27, OPT_CENTER, "Please Tap on the dot"); 
Calibrate(0); 
Flush_Co_Buffer(); 
WaitCmdfifo_empty(); 


Функцію калібрування слід викликати по закінченні ініціалізації TFT-дисплея. Виглядає це наступним чином:


З проектом, запущеним на відео можна ознайомитися за посиланням.

Звичайно, дивно було б змушувати користувача калібрувати екран після кожного включення пристрою. Тому має сенс один раз провести калібрування, вважати вміст регістрів REG_CTOUCH_TRANSFORM_A… REG_CTOUCH_TRANSFORM_F, зберегти дані, а потім програмно заносити їх у регістри REG_CTOUCH_TRANSFORM після ініціалізації TFT-модуля.

Калібрувальні дані можна зберігати в який-небудь EEPROM, а якщо совість дозволяє діяти зовсім незграбно, то можна проводити «калібрування» приблизно ось так:

void Display::Calibration()
{ 
char calibration[25] = {98, 99, 0, 0, 182, 254, 255, 255, 245, 142, 248, 255, 117, 254, 255, 255, 34, 98, 0, 0, 123, 154, 248, 255};
for (int i = 0; i < 24; i++) {
(*_TFT).Wr8(REG_TOUCH_TRANSFORM_A + i, calibration[i]);
}
}

3. Використання сенсорного інтерфейсу у власному проекті

Моє кінцеве додаток — це головне меню, на якому відображаються поточні дані про температури і відносної вологості, а також кілька підрозділів:



Оскільки питань виведення зображень і русифікації ми торкнемося тільки у наступній статті, сьогодні будемо розбирати проект-напівфабрикат:



Для навігації між розділами служать два перерахування. Перше — це всі мітки, присвоєні графічних об'єктів:

typedef enum {
NONE_PRESS,
CURR_TEMP_PRESS,
CURR_HUM_PRESS,
MENU_PRESS,
} pressValues;

Друге — список екранів, між якими ми перемикаємося.

typedef enum {
MENU_SCREEN,
CURR_HUM_SCREEN,
CURR_TEMP_SCREEN,
} screenValues; 

Логіка роботи програми описується наступним чином:

disp.activeScreen = MENU_SCREEN;
disp.pressedButton = NONE_PRESS;

// change active screen depending on pressed area
while(1) {
dataUpdate();
disp.pressedButton = disp.GetTouch();
// Main screen menu
if (disp.activeScreen == MENU_SCREEN) {
disp.MainMenu(SENSOR.humidity, SENSOR.temperature);
if (disp.pressedButton) {
wait_ms(150);
if (disp.pressedButton == CURR_TEMP_PRESS) {
disp.activeScreen = CURR_TEMP_SCREEN;
} else if (disp.pressedButton == CURR_HUM_PRESS) {
disp.activeScreen = CURR_HUM_SCREEN;
} 
disp.pressedButton = NONE_PRESS;
}
// Any other screen
} else {
// You can back to main menu from any screen
if (disp.pressedButton == MENU_PRESS) {
disp.pressedButton = NONE_PRESS;
disp.activeScreen = MENU_SCREEN;
} else {
// Screen with current temperature / humidity
if (disp.activeScreen == CURR_TEMP_SCREEN) {
disp.CurrentTemperature(SENSOR.temperature);
} else if (disp.activeScreen == CURR_HUM_SCREEN) {
disp.CurrentHumidity(SENSOR.humidity);
} 
}
}
}

Для відтворення всіх трьох екранів використовуються тільки прості графічні примітиви (LINES, POINTS, EDGE_STRIP_B і RECTS) і віджети для виведення тексту і чисел. Вважаю що після прочитання статті Як перестати боятися і любити mbed [Частина 2] і перегляду вихідного коду не повинно залишитися питань щодо візуалізації елементів меню і графіків, тому зупинюся тільки на реалізації елементів сенсорного введення.

Я не використовую готові віджети від FTDI, оскільки на деяких кнопках головного меню будуть розташовані зображення і рядки різного розміру, а ще тому що мене бісять закруглені кути кнопок. Тому мої кнопки представляють із себе не віджети button, а прямокутники, поверх яких виводяться текстові рядки та інші елементи.

Теги (TAG) для «звичайних» графічних об'єктів, наприклад прямокутників, встановлюються так само, як при використанні віджетів. Всім графічним об'єктам, описаним після виклику команди TAG(CURR_HUM_PRESS), присвоюється мітка CURR_HUM_PRESS. Закінчити список зазначених об'єктів можна або викликом TAG() з новим аргументом, або командою TAG_MASK(0), яка забороняє призначення міток. При використанні TAG_MASK(0), потрібно не забути дозволити присвоювання міток (TAG_MASK(1)) перед наступним викликом TAG().

Також варто передбачити якусь візуалізацію натискання. У моєму випадку для цього робиться ось що:

а) Якщо після відображення на TFT-дисплеї попереднього кадру зафіксовано дотик кнопки, на новому кадрі колір кнопки змінюватись з темно-синього на блакитний (див. код нижче),
б) Після виведення кадру з блакитний кнопкою виконується затримка 150 мілісекунд (див. код вище).

void Display::MainMenu(float humidity, float temperature)
{
...
(*_TFT).DL(TAG_MASK(1));
(*_TFT).DL(TAG(CURR_HUM_PRESS));
(*_TFT).DL(COLOR_RGB(9, 0, 63));
// якщо кнопка вже була натиснута, вибираємо для її більш світлий відтінок
if (pressedButton == CURR_HUM_PRESS) {
(*_TFT).DL(COLOR_RGB(75, 70, 108));
}
(*_TFT).DL(BEGIN(RECTS));
(*_TFT).DL(VERTEX2II(12, 62, 0, 0));
(*_TFT).DL(VERTEX2II(12 + 400, 62 + 93, 0, 0));
(*_TFT).DL(COLOR_RGB(255, 255, 255));
(*_TFT).Text(12 + 10, 62 + 5, 30, 0, "Current humidity (rH)");
// перетворення значення вологості в рядок (32 -> "32%")
CreateStringTempHum(humidityStr, humidity, 0);
(*_TFT).Text(12 + 10, 62 + 45, 31, 0, humidityStr);
(*_TFT).DL(TAG_MASK(0));
...
}

Всі кнопки головного меню формуються аналогічно. По натисненню на кнопку Current temperature ми переходимо на екран з графіком зміни температури, по натисненню на Current humidity — на екран з графіком зміни відносної вологості.

З екранів з графіками можна повернутися в головне меню після натискання на посилання Back to main menu. Для створення такої посилання використовуються ті ж інструменти — мітка TAG(MENU_PRESS), після якої описуються графічні об'єкти — текстовий рядок і лінія (підкреслення).

(*_TFT).DL(TAG_MASK(1));
(*_TFT).DL(TAG(MENU_PRESS));
(*_TFT).DL(COLOR_RGB(0, 0, 0));
(*_TFT).Text(14, 240, 22, 0, "Back to main menu");
(*_TFT).DL(BEGIN(LINES));
(*_TFT).DL(LINE_WIDTH(8));
(*_TFT).DL(VERTEX2F(15 * 16, 260 * 16));
(*_TFT).DL(VERTEX2F(155 * 16, 260 * 16));
(*_TFT).DL(TAG_MASK(0));

Виглядає це наступним чином:


Вихідний код проекту доступний на developer.mbed.org.

І цей проект, і демо-проект з рожевими віджетами, і розглянуті у попередніх статтях програми благополучно запускаються на платах SLSTK3400A від Silicon Labs, ATSAMD21-XPRO від Atmel і WIZwiki-W7500P від Wiznet. Потрібно тільки змінити назви використовуються GPIO і замінити цільову плату перед компіляцією. Як тут не полюбити mbed?

Висновок

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

0 коментарів

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