- PVSM.RU - https://www.pvsm.ru -
Прошел год и многие вечера коротались написанием очередного, куда более крупного и на этот раз полезного проекта.
В прошлый раз везде приходилось ужиматься, как только возможно. Ресурсов того многострадального камня мне стало не хватать и в какой-то момент пришло интересное решение. Отдать часть задач другому контроллеру.
( Как и в прошлый раз, под катом много воды и изображений)
Несмотря на наличие таких проектов как Nextion HMI и Gameduino, я решил сделать свое решение по ряду причин.
Gameduino хоть и казалась интересным решением (VGA выход и FPGA на борту), но невозможность достать вторую версию этой платы, вынудила приобрести ее первую ревизию. Это были боль и страдания:
Несмотря на взрывную популярность Nextion сейчас, проект начал появляться до столь широкого распространения и вот другие причины отказа:
И самое главное, в обоих случаях нет возможности дописать что-то свое.
Часть идей была позаимствована от всевозможных библиотек, личное по мелочам и старых консолей (после прочтения кучи мануалов по устройству, но далеко не все, что хотелось бы).
Описывать все возможности в одной статье явно не выйдет, более того все доступные команды можно посмотреть в исходниках для хоста (Arduino библиотеки) или в файле «commandDiscriptions.txt».
Можно было бы остановиться на этом и писать уже под stm32, но ведь так трудно устоять перед очередным троллейбусом!
Не просто так написал ядро, достаточно сменить тип контроллера в проекте и изменить пару инклюдов и получаем sGPU с большим объемом памяти и контроллером FSMC
(дешевизна и распространённость mini версии, не дают покоя не использовать ее).
Выбор пал на UART. С выбором скорости было уже не так очевидно, так как далеко не все устройства могут поддерживать скорость обмена в 1МБод. Недолго думая просто сделал четыре возможных скорости: 9600, 57600, 115200 и 1М. Так же сделана возможность выбора скорости аппаратно, при помощи 3-х GPIO (соответствие GPIO и скоростей можно найти в STM32_GPU_GPIO_Pinout.txt), что дает 8 возможных значений.
Из них только 4 использовано, остальное в резерве (можно сделать хоть 1200 Бод).
Скорость выбирается по маске, посредством
switch(GPIOA->IDR & 0x07) // считываем состояние GPIO
{
case 0x01: {
init_UART1(USART_BAUD_9600);
print(T_BAUD_9600);
} break;
case 0x02: {
init_UART1(USART_BAUD_57600);
print(T_BAUD_57K);
} break;
case 0x03: {
init_UART1(USART_BAUD_115200);
print(T_BAUD_115K);
} break;
case 0x04: {
init_UART1(USART_BAUD_1M);
print(T_BAUD_1M);
} break;
/*
* params 0x05-0x07 and 0x00: reserved
*
*/
default: {
init_UART1(USART_BAUD_57600);
print(T_DAUD_DEFAULT);
} break;
}
Каждый тайл, это всего-навсего массив байт с индексами цветов в текущей палитре цветов.
0E 0E 0E 0E 0E 0E 0E 0E 0E 38 38 0E 0E 38 38 0E 0E 38 28 0E 0E 28 38 0E 0E 0E 0E 0E 0E 0E 0E 0E 0E 0E 0E 0E 0E 0E 0E 0E 0E 38 0E 0E 0E 0E 38 0E 0E 0E 38 38 38 38 0E 0E 0E 0E 0E 0E 0E 0E 0E 0E
Подробнее описывать, что такое тайлы я точно не буду, понадеюсь на вашу способность найти информацию в сети, уж больно велик объем информации.
Так как тайлов могут быть сотни и тысячи, то они будут занимать безумные объемы ПЗУ.
Совершенно нелогично передавать их со стороны хоста (так сделано в Gameduino первой ревизии).
Выход был цеплять карту памяти по SPI, SDIO пока нет.
Самым тяжелым, оказалось, поднять FatFS. Так как все пишется при помощи SPL библиотеки, а не HAL и CubeMX и возможно потому, что плохо проверил подключение питания для SD карты (к питанию щепетильны жутко они), в какой-то момент все просто заработало.
В конечном счете, со стороны хоста достаточно отправить название файла и сколько тайлов загрузить в RAM sGPU.
gpu.loadTile8x8("pcs8x8", TILE_SET_W, RAM_POS, 1);
// pcs8x8 – название файла с тайлами (указывать без расширения);
// TILE_SET_W – ширина набора тайлов в тайлах;
// это нужно для правильного расчета оффсетов внутри файла;
// RAM_POS – номер тайла в RAM, куда следует поместить загруженный тайл;
// 1 – номер загружаемого тайла в файле.
Согласитесь, что команда размером в 5 байт (это минимальный размер для загрузки одного тайла на момент написания статьи) куда меньше чем громоздкая библиотека FatFS съедающая значительную часть как оперативной, так и постоянной памяти, что совершенно не заметно на контролере уровня stm32.
Поддерживаются следующие размеры тайлов:
Последний тип тайлов доступен только для pro версии, так как всего 10 таких съедают половину памяти mini версии, что весьма очевидно — неприемлемо.
Так же упомяну, что воспользовался хитростью при выводе их на экран. Помимо использования DMA(есть буфер для одного преобразованного тайла в RGB565), я проверяю индекс каждого тайла, и если он совпадает с предыдущим, то преобразование пропускается, и старый тайл выводится по новым координатам. Не смотря на такую трату памяти, это имеет неоспоримое преимущество, когда подряд на экран выводятся одинаковые тайлы. Время на преобразование отсутствует, поэтому тайл выводится почти сразу.
Ложка дегтя – на видео сверху код для хоста использует С версию библиотеки (идентичного можно достигнуть только если использовать STM32 под Arduino).
По ней можно заметить множество пустот во время выставления адресного окна. Эти пустоты по большей части переключение линии DC (выбор данных или команды, второй канал красная линия) и ожидание освобождения буфера SPI (ожидание передачи всех данных). С этим из-за особенностей дисплея почти ничего не сделаешь (только 9 бит режим или FSMC).
Создать тайл не так сложно, достаточно сделать следующее:
Не совсем, так как нужно создать само изображение тайла или набора тайлов (tileset). Среди множества инструментов для pixel art мною был выбран PixelEdit
(снова понадеюсь на вашу способность найти информацию в сети).
Сделав там изображение его достаточно экспортировать как *.png и скормить GIMP (как описано выше).
Количество спрайтов для mini — 56, тогда как для pro версии немногим больше — 63.
Их количество для версий mini и pro вычислялось по-разному. Так для mini это сумма половины максимального количества тайлов каждого типа, тогда как для pro это сумма четвертей 63 спрайта хватит всем.
Спрайты могут состоять из любого, но одинакового типа тайлов, т.е. нельзя сделать спрайт из тайлов 8x8 и 16x16 одновременно, но, можно два спрайта из тайлов 8x8 и 16x16. Каждый спрайт состоит из четырех тайлов (максимум) спрайта 64x64 хватит всем, для всего остального есть вывод *.bmp.
Возможны следующие комбинации размеров:
где первая цифра это высота в тайлах, а вторая ширина в тайлах.
Помимо этого каждый спрайт содержит координаты в пикселях, где он будет рисоваться (может помочь для расчета столкновений двух спрайтов, да это тут тоже есть).
typedef struct {
uint16_t posX; // __ координаты где выводить на экран
uint16_t posY; // /
uint8_t type; // размер и тип спрайта ( 1x1, 1x2… 8x8, 16x16…)
uint8_t visible;// должен выводится на экран или нет
uint8_t tle[4]; // индексы используемых тайлов
} sprite_t;
В памяти содержится массив из 1200 байт (40x30 тайлов) с индексами тайлов только размером 8x8 (в будущем может быть будет возможность выбора).
Тайловая карта хранится в RAM и грузится с SD карты (расширение *.map).
На текущий момент инструменты, которые позволили бы создавать файл карты, отсутствуют.
Пример тайлового фона можно увидеть во время подачи питания.
Теперь самое неприятное. Спрайты и тайловый фон используют одни и те же тайлы 8x8. Это означает, что для фона и спрайтов необходимо использовать разные тайлы (да, которых не так много на mini версии).
Поэтому учтя кучу возможных сценариев, было решено распределить так:
Не стоит забывать про более крупный контроллер, в котором 64 кб RAM, для него распределение уже будет следующим:
До сих пор сомневаюсь в правильности распределения (динамика совсем не выход).
Остальное можно найти в файле «RAMmath.txt».
Как самая простая палитра была использована палитра NES, но была обнаружена ее недостаточность.
Вооружившись GIMP и встроенным колориметром в системе, расширил цветовой набор до 76 (4 черных цвета для резерва).
Не являясь человеком с идеальным цветовосприятием, я, конечно же, не смог сделать адекватную палитру из
Буду премного благодарен, если кто сможет найти или сделать более адекватную палитру на 256 цветов (с одним цветом для будущего альфа канала).
Если нужно будет использовать другую палитру, ее можно загрузить с SD карты.
В RAM sGPU есть целых 512 байт под это (256 цветов по два байта на цвет, прямо как в GameBoy Advance, но только в RGB565).
Экспортировать палитру из GIMP очень просто, достаточно экспортировать любое индексированное изображение (как raw data), и GIMP рядом с файлом изображения создаст еще один с расширением *.pal. Его и нужно поместить на SD карту (не забыв про размер имени до 8 символов).
Любая команда начинается с байта ее кода. Конечный размер всей команды зависит от ее кода. Так для заполнения всего экрана одиночным цветом нужно только 3 байта, но для того чтобы нарисовать треугольник уже 15 байт.
Коды команд аккуратно разбиты по диапазонам (секциям), есть большое количество не использованных кодов. Всего в текущей реализации протокола доступно 255 команд из них использована даже не половина (есть свободное поле для творчества).
Не смотря на большой входной буфер для команд sGPU, все равно есть риск его переполнения. Поэтому есть два варианта защиты (по факту 1):
Есть две версии библиотек под Arduino.
Версия библиотеки, полностью заточенная под Arduino (на C++), конечно хоть и обладает безумным количеством преимуществ, но у нее есть один огромный и побочный недостаток — она ну очень толстая и медленная (см. раздел тайлов). Иными словами пустой скетч,
#include <STMsGPU.h>
STMsGPU gpu;
void setup() {}
void loop() {}
съедает 1634 байт ROM и 217 байт RAM, и это без метода синхронизации с sGPU! Более того, если использовать STM32, то эти цифры будут уже в 10 (как минимум) раз выше – 16732 байта ROM и 3960 байт RAM!
Поэтому есть версия на С. К сожалению, пока заточенная под atmega328p (и ей подобным) и лишенная почти всех достоинств Arduino. Эти недостатки компенсируются как увеличенной скоростью работы, так и значительно меньшим размером: 922 байт ROM и 46 байт RAM (можно сделать и меньше конечно) только уже с синхронизацией.
Самое главное если вы использовали библиотеку для экранов ili9341 от Adafruit, то не придется переписывать почти ничего! Имеется почти полная совместимость со всеми методами.
Если есть вопросы, задавайте, буду рад ответить.
Так же могу написать еще, осветив какие либо моменты куда подробнее (только дайте знать надо это или нет). В одну статью, как уже писал выше, все не впихнуть.
Автор: Bismuth208
Источник [2]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/programmirovanie/240965
Ссылки в тексте:
[1] Вот ссылка на репозиторий проекта: https://github.com/Bismuth208/STMGPU
[2] Источник: https://geektimes.ru/post/285570/
Нажмите здесь для печати.