Как перестать бояться и полюбить mbed [Часть 4]

в 7:56, , рубрики: atmel, FT800, HYT, IST, mbed, SiLabs, Wiznet, Блог компании ЭФО, программирование микроконтроллеров, Промышленное программирование, Разработка для интернета вещей, Разработка робототехники

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

Как перестать бояться и полюбить mbed [Часть 4] - 1

Содержание цикла публикаций:

  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.

Как перестать бояться и полюбить mbed [Часть 4] - 2

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

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

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


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

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

Я использую симпатичнейший модуль RVT43ULFNWC00 серии uxTouch диагональю 4.3'', выполненный в черной декоративной рамке. Модуль имеет ёмкостный сенсорный экран и, соответственно, встроенный графический контроллер FT801.

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

Как перестать бояться и полюбить mbed [Часть 4] - 3

В емкостных 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 нам ещё предстоит вернуться.

Как перестать бояться и полюбить mbed [Часть 4] - 4

Для детектирования касания, в принципе, можно можно использовать данные из 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);

Как перестать бояться и полюбить mbed [Часть 4] - 5

Чтобы элементы не только отображались, но и реагировали на касание, нужно добавить созданным объектам метки и опрашивать хранящий метку регистр.

Элементам объекта Ряд кнопок (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. Использование сенсорного интерфейса в собственном проекте


Моё конечное приложение — это главное меню, на котором отображаются текущие данные о температуре и относительной влажности, а также несколько подразделов:

Как перестать бояться и полюбить mbed [Часть 4] - 6

Поскольку вопросов вывода изображений и русификации мы коснемся только в следующей статье, сегодня будем разбирать проект-полуфабрикат:

Как перестать бояться и полюбить mbed [Часть 4] - 7

Для навигации между разделами служат два перечисления. Первое — это все метки, присвоенные графическим объектам:

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 menu screen
        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, указанный в моем профиле.

Автор: ЭФО

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js