Программирование AVR u8glib: что такое Picture Loop Thu, February 23 2017  

Поделиться

нашли опечатку?

Пожалуйста, сообщите об этом - просто выделите ошибочное слово или фразу и нажмите Shift Enter.

u8glib: что такое Picture Loop Печать
Добавил(а) microsin   

В статье рассмотрен общий принцип работы популярной графической библиотеки u8glib, цикл формирования изображения на экране графического индикатора (перевод [1]). Обсуждаются вопросы:

• Как работает "picture loop" (цикл формирования изображения)? Зачем он вообще нужен?
• Как использовать "picture loop".
• Чего следует избегать...

[Немного теории]

Библиотеке u8glib для решения задачи вывода графики приходится иметь дело со следующими проблемами:

• Некоторые (многие) дисплеи не позволяют читать данные из своей графической памяти.
• Некоторые дисплеи не дают возможности управлять отдельными пикселами в своей графической памяти. Часто 4 или 8 точек должны быть назначены совместно.
• Ограниченный объем оперативной памяти RAM, который имеют на борту микроконтроллер, выводящий графику на индикатор.

Эти проблемы успешно решаются (конечно, с некоторыми ограничениями) библиотекой U8glib. Рассмотрим некоторые понятия библиотеки, которые помогут разобраться с принципами её работы.

Список графических примитивов. Предположим, у нас есть список графических команд, сгруппированных в одну процедуру:

void draw(void)
{
   u8g_SetColorIndex(u8g, 1);
   u8g_DrawBox(u8g, 10, 12, 20, 38);
   u8g_SetFont(u8g, u8g_font_osb35);
   u8g_DrawStr(u8g, 40, 50, "A");
   u8g_SetColorIndex(u8g, 0);
   u8g_DrawPixel(u8g, 28, 14);
}

При выполнении эта процедура нарисует на экране индикатора следующую картинку:

u8glib pl full

Примечание: точка с координатами (28, 14) сначала затемняется функцией DrawBox, и позже очищается функцией DrawPixel. Поэтому для формирования изображения важно, в каком порядке выполняются функции.

Локальный буфер фрейма. Из-за наличия внешних ограничений, в которых приходится работать библиотеке, нельзя очистить один пиксел памяти дисплея: у нас нет возможности ни установить один пиксел, ни прочитать значение соседних с ним пикселей. Как же быть, как можно строить на экране произвольную картинку?

Простое решение состоит в том, чтобы отобразить графическую память экрана на RAM микроконтроллера, т. е. в оперативной памяти микроконтроллера должна храниться копия экрана дисплея. Предположим, у нас монохромный дисплей 128x64 точек, тогда алгоритм будет примерно такой:

1. Выделение блока памяти для картинки (локальный буфер фрейма) в RAM микроконтроллера, чтобы в нем поместилась вся картинка 128x64 дисплея.
2. Очистка этого локального буфера фрейма (128x64 пикселов).
3. Выполнение процедуры draw(), чтобы синтезировать картинку в локальном буфере фрейма.
4. Передача локального буфера фрейма в графическую память индикатора.

Повторные прорисовки. Если процедура draw() состоит из списка команд, то можно вызвать эту процедуру draw дважды. Это означает, что стороннего наблюдателя не будет никакой визуальной разницы, если вызвать draw дважды:

1. Выделение локального буфера фрейма в RAM микроконтроллера.
2. Очистка этого локального буфера фрейма.
3. Выполнение draw().
4. Выполнение draw().
5. Передача локального буфера фрейма в графическую память индикатора.

Конечно, для передачи картинки целиком потребуется больше времени, чем для передачи только отдельной её части. И в действительности дополнительный вызов процедуры draw может не учитывать текущий момент (будет некий временной лаг между тем, что происходит в программе, и что появляется на экране).

Половинчатый буфер фрейма. Все еще остается еще одно ограничение: у встраиваемых систем всегда есть нехватка оперативной памяти RAM. Так что в памяти микроконтроллера может просто не оказаться в наличии достаточно места, чтобы удержать всю картинку целиком. Однако есть трюк, который позволит наполовину уменьшить затраты RAM на буфер экрана: картинка делится на 2 части, верхнюю и нижнюю. Верхняя часть картинки рисуется первым вызовом draw(), а вторая часть вторым вызовом draw():

1. Выделение локального буфера фрейма (его еще называют страницей, page) в RAM микроконтроллера (128x32 пиксела).
2. Очистка локального буфера фрейма 128x32, установка области прорисовки на строки от 0 до 31.
3. Выполнение draw().
4. Передача локального буфера фрейма в графическую память индикатора.
5. Очистка локального буфера фрейма 128x32, установка области прорисовки на строки от 32 до 63.
6. Выполнение draw().
7. Передача локального буфера фрейма в графическую память индикатора.

За шаги 1..4 мы получим верхнюю половину картинки на экране:

u8glib pl lower

Шаги 5..7 создадут нижнюю часть:

u8glib pl upper

Обе половинки, соединенные вместе на экране, дадут полную картинку:

u8glib pl full

[Реализация и проблемы]

Принятие решения о количестве частей экрана и вызовов процедуры draw() оставлено на низком уровне драйвера. Программист просто должен реализовать цикл, вместо того, чтобы делать явные вызовы:

// picture loop
u8g.firstPage();
do
{
   draw();
} while( u8g.nextPage() );

Этот цикл как раз и называется Picture Loop, цикл отрисовки картинки библиотекой u8glib.

Проблемы визуализации. Очень сложные сцены могут отрисовываться медленно, и наблюдатель может заметить, что верхние части появляются на экране раньше, чем нижние (в зависимости от примененного поворота экрана).

Хорошая новость состоит в том, что мерцание все же не наблюдается, потому что графическая память не очищается полностью.

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

Нарисовать на экране 'A'
Подождать
Нарисовать на экране 'B'

Запись только 'B' на экране удалит 'A'. Так что требуется нарисовать 'A' и 'B' вместе, на третьем шаге. В целом значение точки не должно зависеть от других значений точек: доступна информация о пикселе только на текущей странице. Как следствие, нельзя копировать области в памяти дисплея, или выполнять прокрутку экрана.

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

Внутренние переменные не сбрасываются автоматически в начале тела picture loop. Хорошим стилем программирования будет предварительная установка всех требуемых переменных наподобие цвета шрифта в самом начале тела picture loop.

Вместо заключения. Библиотека U8glib позволяет использовать read-only экраны с микроконтроллерами, у которых малый объем оперативной памяти RAM. Хотя экран перерисовывается несколько раз одним и тем же содержимым, скорость обновления остается вполне допустимой. К примеру частота кадров больше 20 в секунду вполне возможны на системах с микроконтроллерами AVR ATmega.

[Вопросы пользователей и комментарии разработчиков u8glib]

Q001. Какие есть трюки, позволяющие увеличить частоту смены кадров?

Мой picture loop рисует строку из 7 символов и маленький прямоугольник с частотой около 5.5 фреймов в секунду. Система работает на ATmega32U4 с частотой 16 МГц, 8 МГц SPI, экран типа ssd1351_128x128_332_hw_spi, и используется очень легкий по ресурсам обработчик прерывания таймера. Полученная частота фреймов довольно далека от 20 fps, как описано в Вашей документации, мне бы достичь хотя бы 10 fps.

Можно ограничить перерисовку только малой областью внутри экрана?

A001. Скорость смены кадров зависят от многих факторов. Вариант "4x" будет использовать больше памяти, и работает быстрее, но у ATmega32U4 не достаточно памяти для такого режима.

Вы можете остановить picture loop в любой нужный момент, что будет означать перерисовку только одной части экрана. Любой другой способ частичного обновления не возможен.

5 fps довольно хороший показатель доя такого микроконтроллера и такого дисплея, как у Вас. Я не помню точно, почему в документации была упомянута частота фреймов именно 20 fps, но Arduino Due (это ARM) с монохромным дисплеем может достичь скорости обновления до 100 fps.

Q002. С кем можно обсудить подключение нового микроконтроллера или индикатора к библиотеке? Есть какой-нибудь форум или список рассылки?

A002. Обычно мы читаем обсуждения на форуме Arduino site:forum.arduino.cc (секция Display, посвященная выводу на дисплеи). Если Вас интересуют индикаторы ILSoft OLED, то см. форум http://forum.ilsoft.co.uk/. Вы также можете поместить сообщение о проблеме в проект.

Q003. Я измерил длительность прокрутки picture loop с AT90USB1286 в вариантах индикаторов ssd1351_128x128_332_hw_spi и ssd1351_128x128_4x_332_hw_spi. Оказалось, что они отличаются примерно на 10% (220 против 200 мс). Почему так медленно?

A003. По этой ссылке представлены некоторые измерения: http://code.google.com/p/u8glib/source/browse/sys/arduino/FPS/FPS.pde. Скорость обновления информации зависит от количества выводимой информации.

Q004. Мне надо подключить экран 96x16 OLED (контроллер SSD1306, разрешение 128x32) к микроконтроллеру ATmega8. Можно ли жестко закодировать количество записываемых страниц в случае использования микроконтроллера с малым количеством RAM? Например, для заполнения экрана 96x16 было бы достаточно 2 раза переслать по 128 байт из FLASH (вместо передачи 4 полных страниц):

u8g.firstPage(); draw(); draw();

Можно ли так сделать? Увеличит ли это скорость обновления на маленьких экранах?

A004. Для того, что Вы хотите, требуется правка кода. Откройте модуль u8g_dev_ssd1306_128x32.c и измените определения #defines в его начале. Если код у Вас заработает, сообщите разработчикам (лучше всего для этого выложить сообщение о проблеме в проект). Разработчики сделают устройство u8g_dev_ssd1306_96x16.c специально для Вашего дисплея. Также укажите полный тип дисплея (дайте ссылку на описание или даташит). Будет также полезной дополнительная информация об наблюдаемых артефактах (например, сдвинутые или отраженные пиксели).

Q005. Мне надо подключить монохромный OLED 128x64 (дешевый, без ACK по шине I2C) к микроконтроллеру ATmega328P. Вы писали, что "Принятие решения о количестве частей экрана и вызовов процедуры draw() оставлено на низком уровне драйвера".

Означает ли это, что я все еще могу выделить целый килобайт моей драгоценной (но имеющейся в наличии) RAM для буфера фрейма, чтобы появилась только одна страница экрана (при этом вызов NextPage() всегда будет возвращать 0)? Мне нравится идея деления буфера фрейма на части, но зачем мне это делать, если у меня есть в наличии память RAM для целого экрана.

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

Каковы критерии для вычислений драйвера низкого уровня, когда делается выбор о количестве используемых страниц? Могу ли я задать через макрос что-то типа "выделить максимум RAM для u8glib"? Я бегло просмотрел код библиотеки, и нашел макрос "PAGE_HEIGHT". Правда ли, что то что я хочу, просто задается с помощью следующей строки?

#define PAGE_HEIGHT HEIGHT

A005. Часто имеется выбор между вариантами конструктора 2X или 4X, которые используют двойное или четырехкратное деление памяти. В таких случаях можно просто выбрать компромисс между затратами RAM и быстродействием. Однако количество страниц фиксировано для каждого контроллера. Также процедуры оптимизированы под специфический размер страницы, так что простое изменение PAGE_HEIGHT может оказаться недостаточным.

Q006. Замечание по поводу ATmega32U4. Прочитать все вопросы и комментарии здесь было очень полезным и интересным занятием. Очевидно, что ATMega32u4 не настолько быстр, чтобы он мог дать качественную анимацию, и это печально. Я также использую Arduino Pro Micro 32u4, и когда удаляю все задержки, и оставляю только picture loop для прорисовки (с увеличением x_pos), то получаю запинающееся изображение. Я надеялся, что от 32u4 можно получить нечто большее, хотя бы 30fps. Библиотека u8g замечательная вещь, и было бы стыдно использовать микроконтроллер от наладонника (наподобие ARM) для отрисовки картинки разрешением 128x32 точки на маленьком дисплее...

A006. Разработчик: у меня получалось выдать 30 FPS с платой chipkit.

[Ссылки]

1. u8glib Picture Loop site:code.google.com.
2. Запуск индикатора OLED WEX025664 на библиотеке u8glib.

 

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


Защитный код
Обновить

Top of Page