Урок 77. Визуализация параметров и измеренных значений в виде стрелочных приборов на круглом LCD-дисплее RP2040-LCD-1.28. Проект квазистрелочного вольтметра.

RP2040-LCD-1.28

В уроке расскажу, как создавать программы для отображения стрелочных измерителей на круглом дисплее RP2040-LCD-1.28 в среде Arduino IDE. В качестве примера разработаем стрелочный вольтметр. Немного расскажу о работе с графикой на LCD-дисплее RP2040-LCD-1.28 и, в частности, о повороте графических объектов на плоскости.

Предыдущий урок     Список уроков     Следующий урок

Мне заказали разработку одного устройство. Задача примитивная.

 

Есть компрессор с аналоговым датчиком давления. Надо реализовать релейный регулятор для стабилизации давления. Измеренное значение ниже нормы – компрессор должен работать, давление выше нормы – компрессор надо выключить.

Естественно, необходимо задавать значение стабильного давления и отображать текущее. Казалось бы проще простого! LCD-дисплеи, LED-индикаторы, выбирай, какие нравятся!

Но заказчик – перфекционист в особо тяжелой форме. Захотел в качестве средства отображения использовать круглый LCD-дисплей RP2040-LCD-1.28. И даже нарисовал конкретную картинку.

Эскиз манометра

Надо только стрелки заставить поворачиваться.

Таким образом, простая задача реализации релейного регулятора для компрессора превратилась в проблему отображения и поворота графических объектов. Хорошо, что в дисплее используется мощный 32х разрядный микроконтроллер.

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

LCD-дисплей RP2040-LCD-1.28.

Дисплей интересный. Картинка красивая, яркая. Я вывел первую попавшуюся иконку.

вывод иконки на экран

Даже на фотографии выглядит хорошо. В реальности еще эффектнее.

Цена для такого дисплея достаточно невысокая. На Али Экспресс это 1500 руб.

Я не буду подробно рассказывать об этом устройстве. Его параметры, установку в Arduino IDE, примеры программирования можно посмотреть по ссылке RP2040-LCD-1.28 - Waveshare Wiki.

Дам минимум информации, чтобы вы могли представить, о чем идет речь.

Итак, RP2040-LCD-1.28 это круглый цветной LCD-дисплей диаметром 1,28 дюйма (32,5 мм) и разрешающей способностью 240x240 пикселей.

Внешний вид RP2040-LCD-1.28

  • В устройстве используется микроконтроллер RP2040, аналог платы Raspberry Pi Pico.
  • Это 32х разрядный, 2х ядерный микроконтроллер с тактовой частотой 133 мГц.
  • Память: 264 кБайт SRAM и 2 мБайт FLASH.
  • Множество периферии:  2 x SPI, 2 x I2C, 2 x UART, 4 x 12-bit ADC, 16 x PWM, датчик температуры, часы реального времени, акселерометр, гироскоп.
  • Может программироваться в среде Arduino IDE.

Для подключения внешних устройств используются 2 разъема.

Распиновка выводов RP2040-LCD-1.28

Работа с графическими объектами в RP2040-LCD-1.28.

Я выполнял действия в следующей последовательности.

По ссылке Demo code загрузил файл RP2040-LCD-1.28.zip

Меню

Из него разархивировал проект RP2040-LCD-1.28 -> Arduino -> RP2040-LCD-1.28.

В нем используются акселерометр и гироскоп, но нас интересуют стандартные библиотеки для работы с дисплеем. Работу с акселерометром и гироскопом я выбросил из проекта.

Теперь создаем собственно стрелочный прибор.

Первым делом необходимо вывести на дисплей шкалу прибора. Формализованная задача -  вывести картинку разрешением 240x240.

Нарисовал шкалу прибора. Есть специальные программы для разработки шкал, в том числе и онлайн. Я не очень хороший дизайнер. Получилась такая картинка.

Моя шкала

Ее надо разместить в файле ImageData.cpp в числовом виде.

const unsigned char scale[] = {
  0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff
  . . . . . . . . . . . . . . . . . . . . . . .
};

Цвет каждой точки кодируется 2 байтами в формате 65K RGB565. На красный цвет отводится 5 бит, на зеленый 6 бит и 5 бит для синего цвета.

В массиве пиксели передаются в последовательности слева направо, сверху вниз.

Существуют конвертеры графических файлов в код языка C/C++. Я использовал этот.

Конвертер графических файлов в C/C++

В результате конверсии появляется блок данных Image data, который надо копировать в массив. Массив для изображения шкалы  я назвал scale. Всего должно быть 240*240*2 байтов.

В файле ImageData.h необходимо объявить этот массив для доступа к нему из основного файла проекта.

extern const unsigned char scale[];

Теперь о выводе на дисплей. Необходимо выполнить следующую последовательность действий.

Объявил указатель на объект (область памяти) , с которым будут производиться действия над графическими объектами.

uint16_t *BlackImage;

Выделил под него память 240*240*2 байт.

BlackImage = (uint16_t *)malloc(Imagesize);

Создал объект Image.

Paint_NewImage((uint8_t *)BlackImage, LCD_1IN28.WIDTH, LCD_1IN28.HEIGHT, 0, WHITE);

Теперь с этим объектом можно работать через функции стандартной библиотеки файла GUI_Paint.h .

Для загрузки изображения из массива scale можно использовать стандартную функцию

void Paint_DrawImage(const unsigned char *image, UWORD xStart, UWORD yStart, UWORD W_Image, UWORD H_Image);

В программе будет выглядеть так

Paint_DrawImage(scale, 0, 0, 240, 240);

Но я применил более быстрый вариант. Просто копировал массив scale в область памяти BlackImage.

for( int i=0; i < Imagesize; i++ ) *((unsigned char *)BlackImage + i) = scale[i]; // фоновая картинка

Собственно вывод изображения на дисплей производится функцией

LCD_1IN28_Display(BlackImage);

Приборная шкала появилась на экране.

Приборная шкала на экране

Теперь надо формировать стрелку.

 

Поворот графических объектов на плоскости.

Стрелка – это картинка, которую нужно поворачивать относительно центра дисплея.

В двумерном пространстве поворот можно задать одним углом α.

Матрица поворота вычисляется по формуле:

Матрица поворота
При умножении любого вектора на матрицу длина вектора не изменяется. Для поворота изображения необходимо координаты каждой точки умножить на матрицу поворота.

Новые координаты точки вычисляются так;

Вычисление координат для поворота графического объекта
Кроме поворота картинка стрелки должна быть с прозрачным фоном, иначе будет исчезать шкала и другие объекты на дисплее.

Я разработал функцию, которая выводит картинку на экран с поворотом и прозрачным фоном. Она расположена в конце основного файла.

void drawTurnImage(UWORD *Image, const unsigned char *image, float turn, uint16_t backGround );

  • Image - указатель на область памяти дисплея. В нашем случае это BlackImage.
  • image – указатель на массив (имя массива) с рисунком, который выводится на дисплей.
  • turn – нормализованный коэффициент поворота. Значение 0 соответствует повороту на 0°, значение 1 – поворот на 360°.
  • background – цвет, который принимается за фоновый и на дисплей не выводится.

Перед пересчетом координат точек разрешение картинки удваивается, после вычислений приводится к исходному разрешению. Это делается для того, чтобы не было пустых точек в повернутой картинке.

Создал картинку для стрелки.

Картинка стрелки

И сформировал массив для нее в файле файле ImageData.cpp

const unsigned char arrow_0[] = {
  0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xf
  . . . . . . . . . . . .
};

Я нарисовал стрелку в исходном положении налево. Потом сообразил, что при значении аргумента turn=0 она должна “смотреть” вниз. Стрелку перерисовывать не стал, учел это смещение в функции drawTurnImage.

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

drawTurnImage(BlackImage, arrow_0, MIN_TURN +  (voltage / 6.) * (MAX_TURN - MIN_TURN), 65535); // стрелка

Начало и конец шкалы заданы константами:

#define MIN_TURN 0.125  // начало шкалы
#define MAX_TURN 0.8725  // конец шкалы

Решил продублировать измеренное значение в цифровом виде. Оказалось, что шрифт максимального размера в стандартной библиотеке 24 пикселя. Цифры получаются маленького размера, с трудом читаются, плохо выглядят.

Пришлось разработать функцию, которая выводит символ стандартного шрифта удвоенного размера.

void Paint_DrawChar2(UWORD Xpoint, UWORD Ypoint, const char Acsii_Char, sFONT* Font, UWORD Color_Foreground);

Вывод значения напряжения с двумя значащими разрядами после запятой производится следующим блоком.

sprintf(str, "%4f2", voltage + 0.005 );
Paint_DrawChar2(V_START_X, 186, str[0], &Font24, cl);
Paint_DrawChar2(V_START_X + 20, 186, str[1], &Font24, cl);
Paint_DrawChar2(V_START_X + 40, 186, str[2], &Font24, cl);
Paint_DrawChar2(V_START_X + 65, 186, str[3], &Font24, cl);

Цвет измеренного значения в цифровом виде изменяется в зависимости от самого значения, в соответствии с положением стрелки.

Прибор на экране

Для тестирования работы измерителя добавил в программу блок, который плавно наращивает значение переменной voltage. При достижении максимального значения переменная начинает уменьшаться.

Скетч такого варианта можно загрузить по ссылке.

 

Зарегистрируйтесь и оплатитеВсего 60 руб. в месяц за доступ ко всем ресурсам сайта!
 

При компиляции надо выбрать вариант оптимизации: Optimize: "Optimize (-O)". С предыдущим вариантом у меня не заработало.

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

Картинка рыбы вместо сьредки

Фоновый цвет – красный.

Для переключения на рыбу в качестве стрелки надо разрешить компиляции следующей строки.

//drawTurnImage(BlackImage, arrow_0, MIN_TURN +  (voltage / 6.) * (MAX_TURN - MIN_TURN), 65535); // стрелка
drawTurnImage(BlackImage, fish_0, MIN_TURN +  (voltage / 6.) * (MAX_TURN - MIN_TURN), 248); // рыба

Выглядит так.

Рыба вместо стрелки

Или в динамике.

Разработка вольтметра со стрелочной шкалой.

Большая часть проекта уже сделана. Осталось измерить напряжение и загрузить его в переменную voltage.

Для измерения напряжения будем использовать аналоговый вход A0 (GP26).

Подключил переменный резистор в качестве делителя 5 В.

Измерение напряжения производится стандартной для Ардуино функцией analogRead(). Производится 256 выборок, затем значение усредняется и приводится к диапазону 0…6 В.

// измерение напряжения
uint32_t avCode=0;
for( int i=0; i < 256; i++ ) avCode += analogRead(A0);
voltage= (float)avCode / (float)(4095. * 256) * MAX_VOLTAGE;
Serial.println(voltage);

Вот окончательный скетч проекта.

 

Зарегистрируйтесь и оплатитеВсего 60 руб. в месяц за доступ ко всем ресурсам сайта!
 

Это я изменяю напряжение на входе A0.

И вариант с рыбой в качестве стрелки.

Подведение итогов.

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

Тем не менее, все выше написанное может быть использовано в практических приложениях.

Что касается несделанного.

  • Вычисления для поворота графических объектов требует достаточно много времени. Стрелка поворачивается не очень быстро. В связи с этим неплохо было бы ускорить вычисления, например, за счет перехода на вычисления координат точек в формате с фиксированной запятой. Вычисления с плавающей запятой требуют много времени.
  • Можно использовать для вывода информации на дисплей второе ядро.
  • Конечно, надо бы оформить работу с графикой библиотекой и дополнить ее другими полезными функциями.

 

Возможно дополню урок, когда закончу проект управления компрессором.

Я серьезно доработал проект в следующем уроке.

Предыдущий урок     Список уроков     Следующий урок

0

Автор публикации

не в сети 4 часа

Эдуард

280
Комментарии: 1936Публикации: 197Регистрация: 13-12-2015

2 комментария на «Урок 77. Визуализация параметров и измеренных значений в виде стрелочных приборов на круглом LCD-дисплее RP2040-LCD-1.28. Проект квазистрелочного вольтметра.»

  1. Здравствуйте Эдуард! Очень занимательный урок! Прочитал с удовольствием! А можно кроме аналогово входа подключить ещё энкодер?

    0
    • Здравствуйте!
      В проекте, который мне заказали, параметры должны устанавливаться энкодером. В ближайшие дни буду подключать.

      0

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Нажимая кнопку "Отправить" Вы даёте свое согласие на обработку введенной персональной информации в соответствии с Федеральным Законом №152-ФЗ от 27.07.2006 "О персональных данных".