Напомним, что в формате BMP для разрешения 160х120 с 24bpp размер файла будет 57654 байта, при 8bpp — 20278 байт и для 1bpp — 2462 байта.
Добавим в тест фрагмент художественного фильма, это будет кусочек в 999 кадров из четвертой серии «17 мгновений весны». Сначала вместо фрагмента фильма был черно-белый вариант мультика, но так как по быстродействию это равнозначно 8-битному цвету, то для разнообразия сюжета вставлен фильм.
Программа для каждого «виртуального экрана» прогоняет заданное число раз указываемое число кадров, можно указать начальный кадр и количество кадров. Эти параметры индивидуальные для каждого «виртуального экрана». После окончания работы каждого экрана выводится информация в виде среднего числа FPS*100, то есть число 1000 будет говорить о среднем FPS, равном 10.
Для статьи использовались настройки - по три прогона подряд для мультика из 40 кадров, для всех вариантов цветности, а фильм, ввиду длинного фрагмента, прогонялся один раз, и все это в бесконечном цикле.
Сначала будем использовать построчное, под размер строки, чтение из файла (нужно учитывать, что формат BMP хранит информацию, начиная с конца картинки, при этом используются «паддинговые» байты для выравнивания строк по 32-битной границе, а для 1bpp, плюс к тому, дополнительные «паддинговые» биты для последнего байта для выравнивания по границе байта).
Поскольку фильм был подключен на последнем этапе и про него несколько слов будет позже, а черно-белый вариант идентичен 8-битному индексированному цвету, то полученные данные следующие:
На самом деле без труда производительность можно поднять более, чем вдвое для 24bpp и 8bpp, и процентов на 60 для 1bpp.
Вот фотография экрана, на котором,
24bpp – 8.26 fps
8bpp - 16.12 fps
1bpp – 20.00 fps
На ролике (ссылка ниже) результаты еще лучше.
Давайте посмотрим, куда утекает и на что тратится драгоценное время.
Разумеется, буфер на одну строку это мало. Для 24bpp это 960 байт, но для МК STM32F103С8, имеющего 20 килобайт встроенной SRAM спокойно можно даже половину памяти выделить по буфер. Можно попробовать использовать и внешнюю последовательную память на 32 килобайта, что с DMA будет наверняка оправдано. На более продвинутых версиях STM32 (со встроенным контроллером статической памяти FSMC и установленной внешней памятью) размер буфера можно будет существенно увеличить и даже создать цепочку экранных буферов. STM, в апноте для подсоединения LCD по RGB интерфейсу, такой подход не рекомендует, но я таким прямолинейным советам не очень доверяю. Скоро будет возможность проверить это самому — в разработке плата ZP-STM32M, на базе STM32F103 в 144-ногом корпусе и достаточным объемом памяти.
Ставим размер буфера для чтения с SD карты 10 килобайт (512х20), то есть 24bpp читается в шесть проходов, 8bpp — в два, 1bpp – целиком, и можно даже сразу четыре полных кадра записать в буфер. Оптимально производить чтение размером, кратным сектору в использованной файловой системе.
Но это даст прирост от 30% на самом тяжелом режиме до 10-15% при 1bpp.
Теперь самое время сказать о том, что для операций чтения - записи используются стандартные функции периферийной библиотеки STM. Разумеется, эти библиотеки (как и большинство подобного материала от всех программных и «железячных» производителей) даны только для иллюстрации и в большинстве своем служат только средством обучения.
Это приводит к тому, что люди, изучающие микроконтроллеры только «через язык программирования С», на этом остановятся и будут считать, что достигли максисмума. Это неправильный подход, но в этой жизни вообще мало что делается правильно.
Типовые функция чтения и записи в порт SPI из стандартной библиотеки периферии STM выглядят так:
void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data)
{
/* Check the parameters */
assert_param(IS_SPI_ALL_PERIPH(SPIx));
/* Write in the DR register the data to be sent */
SPIx->DR = Data;
}
uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx)
{
/* Check the parameters */
assert_param(IS_SPI_ALL_PERIPH(SPIx));
/* Return the data in the DR register */
return SPIx->DR;
}
Для записи в LCD порт конфигурируется на передачу 16 бит, а для чтения SD – карты на 8 бит. Получается, что для каждого пиксела или байта данных вызывается отдельная функция. Для потокового ввода/вывода это непроизводительные затраты по работе со стеком, превосходящие по времени операции записи/чтения пиксела или байта. Очевидно, что функцию нужно заменить макроподстановкой, просто используя, там, где надо для записи
SPIx->DR = Data;
а для чтения в буфер
*buffer++ = SPIx->DR;
При этом цикл записи в LCD желательно развернуть на 8-16-32 операции, это даст дополнительных 3-5% прироста.
Эти приемы используются достаточно часто, но также часто забывают и о втором компоненте, ожидающем установки аппаратного флага готовности к следующей операции. При этом, вместо прямого обращения к регистру SR МК, по-прежнему в потоке вызывается функция.
То есть, вместо пары строк для каждого пиксела
SPI_I2S_SendData(LCD_SPI, pixel);
while (SPI_I2S_GetFlagStatus(LCD_SPI, SPI_I2S_FLAG_TXE) == RESET);
Правильно будет использовать:
LCD_SPI->DR = pixel;
while ((LCD_SPI->SR & SPI_I2S_FLAG_TXE) == RESET);
Аналогично и для чтения с флагом RXNE.
И именно эта последняя строчка, уменьшающая латентность доступа к порту, и вносит наиболее весомый вклад в увеличение быстродействия.
Можно поработать и с дополнительным выравниванием структур данных.
Сказанное выше относится не только к микроконтроллерам от STM, это верно для любых типов МК.
И не забывайте о таймингах устройств. Заменив во всем своем коде вышеозначенные функции на макросы, можно легко получить неработоспособную программу. Например, если сигнал CS дисплея — макрос прямой работы с портом, при подаче низкого уровня активируется дисплей и если сразу следует макрос записи в системный регистр дисплея, то такая комбинация может и не заработать. По техническим данным для LCD MI0283QT2 (вернее, для его контроллера HX8347D), после CS должна быть пауза не менее 15 наносекунд. При такте 13.88нс (72MHz) это означает необходимость пропуска двух тактов процессора до операций с дисплеем. Так как функция до выполнения работает (внутренне) со стеком гораздо дольше, такая пауза обеспечивается. При использовании макроса необходимо после выборки по CS добавить
asm volatile("nop");
а, правильнее, но не по-оверски — двойной «nop».
Как эти анимашки выглядит вживую, можно посмотреть
на ролике (4.23МБ, mp4). Качество неважное (смартфон), в реальности все смотрится гораздо красивее. Скорость камеры такая, что она ловит два кадра на экране LCD в свой один, в реальности на LCD у мышонка нет четырех ног, да и для мульта резерв есть - мышонок двигается каждый второй кадр. То есть, двигая его каждый кадр, fps увеличим вдвое
.
Впрочем, оценить скорость и плавность вывода можно. Фильм вполне смотрибелен. Нерезкость, размытость и прочие погрешности - это влияние низкокачественной камеры.
На бенчмарке фильма 22.72 fps не совсем верно отражает ситуацию. Во фрагменте 999 кадров, но тогда битрейт 11 кадров с хвостиком, при битрейте оригинала 25. Поэтому показывается только каждый второй кадр, а суммарное время делится на 999 кадров. Но плавность это для экрана 160х120 не нарушает. Пока не очень понял, почему с ростом количества кадров падает fps, словно как у SD карты стоит входной буфер. На 300 кадров - 13.3, на 999 - 11.2. Учитывая, что это обычный 8bpp индексный формат. На большой новой системе попозже разберемся.
Как оказалось, для 1bpp бенчмарк делался на одном из старых вариантов, где флаг ловится функцией, а не макросом. Замена на макрос дает прирост 11%, после некоторых оптимизаций суммарно повышена производительность более, чем на 30% (по отношению к 20 fps) и сейчас результат 26.31 fps.
Но, ввиду того, что программа создается только для самообучения и постоянно меняется, обрастая разными экспериментальными приемами, полноценная оптимизация может дать дополнительный ощутимый прирост на всех режимах.
Выводы: не так все безнадежно На днях получу 32-битный 200Msps сниффер, по внешнему запуску надо будет произвести замеры в системе, а то начинает действовать принцип неопределенности при снижении периода тика системного таймера, поэтому я и не приводил времена по этапам, так как это требует проверки, а времена ниже 0.1мс лучше измерять извне.
Но, в целом, учитывая что мы экспериментировали с перегруженным (избыточным) потоком и на слабом интерфейсе, очевидно, что, при желании, можно наверняка обеспечить плавный вывод на экран 16-битного цвета (параллельный интерфейс обеспечивает десятикратный рост производительности). Другое дело, хватит ли STM32 для DSP-операций по раскодированию-преобразованию, вот тут можно законно засомневаться. Но то, что этот МК проприетарный полноэкранный 320х240 формат для плавного видео вытянет, у меня сомнений нет.
На плате ZP-STM32M будет установлен сопроцессор VLSI1053, который понимает практически любой аудиоформат, мы освободим центральный МК от этой работы и бросим всю его мощь на графическую составляющую.
На сегодня все.
Если есть что сказать — то идем
сюда. Всем удачи,
Zauropod.
25 ноября 2010 года.