Зачастую контроллеру приходится сообщать пользователю какие-либо данные, и далеко не всегда можно обойтись одними лишь цифрами. Для отображения текстовой информации как правило применяют LCD индикаторы на базе контроллера HD44780. Не смотря на то, что подобный дисплеев очень много все они управляются одинаково так как контроллер в них стоит один и соответственно система команд у них одна, так же они имеют одинаковый набор ног. В этой статье мы попробуем подключить такой дисплей к контроллеру установленному на STM32vl Discovery. Сам дисплей выглядит следующим образом:
Прежде чем писать программу которая будет рулить дисплеем нам потребуется разобраться какие у него есть выводы. Их не так много:
DB0…DB7 — Выводы через которые контроллер получает/передаёт данные и командыE — Строб. Когда на всех линиях данных установлены нужные логические уровни, необходимо кратковременно подать на него логическую единицу, а потом снова ноль. Только после такого дёрганья ногой дисплей считает данные (или команду) с ножек
RW — Состояние этой ноги сообщает дисплею, что мы хотим с ним делать: 1 — читать из дисплея, 0 писать в дисплей. Вообще чтение из дисплея — вещь совсем не нужная на мой взгляд, именно поэтому я никогда не подключаю этот вывод к контроллеру а просто сажаю его на землю. Таким образом режим записи в дисплей включен постоянно.
RS — Управляя состоянием этой ноги — мы сообщаем дисплею что хотим ему передать: Если ноль то команда, если единица то данные.V0 — Вывод управления контрастностью дисплея. обычно сюда подключают переменный резистор (как делитель напряжения) и вращая его настраивают необходимую контрастность. Если его не подключить, то изображение (в 99%) не появится вообще. Хотя у меня был один дисплей который не требовал подключения такого резистора. VSS — ну тут думаю всё понятно, земля. VCC — напряжение питания, обычно 5 вольт.A — анод светодиодной подсветки.K — катод светодиодной подсветки.Иногда подсветка уже имеет токоограничивающий резистор, и можно подключать эти два вывода напрямую к питанию, а иногда резистора нет и его нужно добавить. В противном случае подсветка может испустить тот самый волшебный дымок. Выводов многовато и почти все из них должны управляться нашим контроллером. Таким образом мы должны задействовать аж 10 ног! (DB0…DB7, RS и E). В принципе у STM32 нет такой большой проблемы со свободными ногами, как например у AVR или PIC, но всё равно жалко их тратить, да и куча проводов от дисплея это не гуд. Я веду к тому, что есть возможность так же полноценно управлять дисплеем используя всего четыре линии данных (DB4…DB7) вместо восьми. Поэтому, в данной статье будет рассмотрен именно этот режим работы. Но сначала много теории без которой мы далеко не уйдем. Я конечно понимаю, что на эту тему было написано достаточно кода и мануалов, но свой изобретенный велосипед помог мне лучше понять и запомнить как работать с дисплеем на контроллере HD44780. Начать стоит с того, что у дисплея есть память аж два вида памяти в которые мы можем что-то записать:
1. DDRAM. Когда мы записываем в эту память данные то всё что мы туда записали появляется на экране. Всего у контроллера 80 байт такой памяти, а сам дисплей может отображать 32 символа (16 символов * 2 строки) в один момент времени. Возникает вопрос: а какие 32 символа из 80 будут отображаться на экране? По каким адресам DDRAM мы должны писать что-то чтоб увидеть это на экране? Ответ в картинке ниже:
Из рисунка видно, что в желтенькое «окошко» размером 16х2 попали ячейки памяти с адресами c 0x00 по 0x0F и с 0x40 по 0x4F. Весь прикол в том что это самое «окошко» можно программно сдвигать. А это значит, что для того чтоб быстро менять содержимое дисплея, нужно сначала заполнить «видеопамять» нужными данными а потом дать команду на сдвиг окошка после чего на экране отобразится то, что было скрыто за его пределами. Вот такая вот немудрёная вещь, честно говоря мне эта фича еще ни разу не пригодилась и я думаю что и не пригодится.
2. CGRAM. Эта память используется для хранения пользовательских символов. Таблица символов у HD44780 конечно достаточно большая, каких только закорючек в ней нет, но бывает так, что требуется изобразить на экране нечто экзотическое и вот тут-то CGRAM нас и выручает. В эту таблицу можно записать 8 пользовательских символов, причем все они расположены в этой памяти непрерывно, один за другим. Каждый символ отображаемый на дисплее, имеет разрешение 5х8 пикселей. Это означает, что для того чтоб изобразить 1 символ нам потребуется 8 байт, причем 3 старших байта не играют роли (ширина то всего 5 пикселей). Для того чтоб вывести на экран символ предопределённый пользователем нужно отправить в дисплей символ с кодом от 0 до 7.
Если мы сразу после включения питания начнем что-то писать в DDRAM, то скорее всего на экране мы ничегошеньки не увидим, так как нужно предварительно произвести инициализацию, для этого нам нужно знать команды дисплея. Как правило все дисплеи которые считаются HD44780-совместимыми могут выполнять вот такие команды:
DB7 | DB6 | DB5 | DB4 | DB3 | DB2 | DB1 | DB0 | Описание команды |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | Очистить дисплей, курсор домой |
0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | Курсор домой |
0 | 0 | 0 | 0 | 0 | 1 | I/D | SH | Настройка сдвига экрана/курсора |
0 | 0 | 0 | 0 | 1 | D | CUR | BLN | Вкл/выкл экран и курсор |
0 | 0 | 0 | 1 | S/C | R/L | X | X | Разрешает сдвиг дисплея или курсора |
0 | 0 | 1 | DL | N | F | X | X | Установка разрядности, числа строк и размера символа |
0 | 1 | ASG | ASG | ASG | ASG | ASG | ASG | Установка адреса SGRAM |
1 | ADD | ADD | ADD | ADD | ADD | ADD | ADD | Установка адреса DDRAM |
Как видно из таблицы, некоторые биты в командах могут изменяться пользователем, осталось только разобраться какой бит за что отвечает: I/D — инкремент/декремент адреса DDRAM. 1 — инкремент (увеличение), 0 — декремент (уменьшение). Говоря по-русски: Если бит установлен, то счётчик адреса будет увеличиваться на единицу всякий раз когда мы пихаем символ в дисплей. За счёт этого для вывода какого-либо слова нам не нужно самостоятельно задавать позицию для вывода каждого символа, каждый новый символ будет выводится в следующую по порядку позицию. Ну а если бит сброшен, то счётчик адреса будет не увеличиваться на 1 а наоборот уменьшаться. К чему такое извращение не ясно, пользы ни какой. SH — Если бит установлен, то сдвиг дисплея разрешен. Всякий раз записывая символ в память, то самое «окошко» будет сдвигаться на один символ. D — Если бит установлен то содержимое DDRAM отображается экраном, если сброшен то очищается экран (но не память). Таким образом, если бит не установлен, на экране мы вообще ничего не увидим.CUR и BLN — Управляют отображением курсора на экране. Есть 4 комбинации этих бит:CUR=0 BLN=0 — курсор не отображаетсяCUR=0 BLN=1 — курсор не отображается, но мигает всё знакоместо (черным квадратом)CUR=1 BLN=0 — курсор есть и он не мигаетCUR=1 BLN=1 — курсор есть + мигает всё знакоместоS/C — Определяет что будет сдвигаться курсор (0) или дисплей (1)R/L — Определяет направление сдвига: Вправо 1, влево 0.DL — Бит определяет разрядность шины данных: 1 — 8 бит, 0 — 4 битаN — Задает число используемых строк. Бит сброшен — одна строка, установлен — две строкиF — Задает размер символа. Бит сброшен — размер 5 на 8 точек, бит установлен 5 на 10 точек. Как правило бит всегда сброшен, индикаторы поддерживающие сразу два размера символов встречаются крайне редко.
Теперь когда мы имеем представление о выводе команд, можно попробовать произвести начальную инициализацию дисплея. Первым делом нужно установить разрядность и тут есть нюанс. Контроллер перейдет в 4-х битный режим только после того как мы установим на ногах DB7..DB4 значение 0010 и дрыгнем ногой E. При этом на остальных ногах дисплея (DB3..DB0) может быть вообще что угодно, они ведь болтаются в воздухе. Таким образом будет выполнена команда 0010ХХХХ где Х это может быть 1 или 0 (непредсказуемо). Естественно после дрыга ногой
Следующий обязательный шаг инициализации — заставить дисплей отображать содержимое DDRAM. За это как вы помните отвечает бит D. По желанию можно заодно и курсор включить (соответствующими битами). Затем нужно выполнить команду позволяющую установить бит I/D в единицу. Чтоб каждый новый символ у нас писался вслед за другим. После этого нам необходим очистить экран и установить курсор в начало верхней строки. После этой минимальной инициализации мой дисплей стал принимать символы и отображать их на экране. Передача символа осуществляется абсолютно точно также как и команды, за исключением того, что перед началом передачи RS нужно установить в единицу. Без этого дисплей не поймет. что мы шлём данные а не команду. И собственно всё, передали 16 символов и все они появились в верхней строке. Начали передачу 17-го и он записался в следующий по порядку адрес который находится за пределами видимости (вспомним про «окошко»). А чтоб 17-й символ у нас появился в первой позиции второй строки на нужно установить адрес DDRAM равный 0x40 (см картинку выше). Для этого у нас есть простая команда которая так и называется — Установка адреса DDRAM. Старший бит единица, а остальные биты задают адрес. В итоге мы должны отправить команду 0xC0 чтоб курсор переместился куда мы хотим. Теперь поговорим о пользовательских символах. Как я уже отметил выше, для вывода любого из восьми пользовательских символов, мы должны отправить (в режим передачи данных, а не команд разумеется) число от нуля до семи которое соответствует желаемому символу. Нарисовать символ в памяти SGRAM это дело не хитрое. Первым делом мы должны сказать дисплею что нам нужно писать данные в память SGRAM по нужному адресу. Для этого выполняем команду которая так и называется — Установка адреса SGRAM. Лично у меня возник вопрос, а в какие адреса вообще что писать? Методом проб и ошибок был рожден алгоритм описанный ниже. Для начала определимся символ с каким номером (от 0 до 7) мы хотим создать. Пусть это будет номер 5. Вычисляем адрес для этого 8 умножаем на 5. Получается 101000. Устанавливаем в единицу шестой бит. В результате получаем 1101000. Передаем это в качестве команды. Теперь нужно передать в режиме данных 8 байт (которые и будут нашим новым символом). Не забываем, что символ у нас 5 на 8. Поэтому старшие три бита каждого байта будут отсечены! Для примера я изобразил полупустую батарейку:
00001110
00010001
00010001
00010001
00011111
00011111
00011111
00011111
После того как все 8 байт записались необходимо вернуться в режим работы с DDRAM (используя последнюю команду из таблички). Если вы всё-таки дочитали до этого момента, значит вам действительно интересно узнать как работать с этим дисплеем. Практическая часть будет в следующей статье, а пока переваривайте это.
Поскольку телевизор был уже разобран, и пользы от него не было не было ни какой, я решил попробовать подключить к нему контроллер.
Как подключить модуль LCD с большим разрешением и без видеопамяти к контроллеру — под катом.
Электроника телевизора состоит из 2 плат — на одной расположены видео-АЦП, видеопроцессор, тюнер, DC-DC и прочая обвязка. Эта плата нам не нужна. Вторая плата с маркировкой CPWBX0043TPZZ в данном случае важнее — на ней расположен контроллер LCD и инвертор для питания ламп подсветки. В гугле встречал упоминания, что точно такую же плату можно найти в портативном DVD плеере. Контроллером дисплея является микросхема LZ9JG17 (в даташите эта микросхема названа Timing Control IC). ВАЖНО: интерфейс у LCD модуля на данной микросхеме 18-bit RGB, и у данного LCD модуля нет встроенной видеопамяти.
Опишу поподробней работу данного интерфейса. Данные передаются в модуль по параллельной 18-битной шине(по 6 бит на цвет), по каждому тактовому сигналу на экране отображается новый пиксель, цвет которого соответствует коду на шине данных. Для того, чтобы пиксели отображались на экране в нужных местах, используются сигналы горизонтальной(строчной) и вертикальной (кадровой) синхронизации.
Перед началом передачи новой строки необходимо установить нужный уровень на линии горизонтальной синхронизации, послать определенное число тактовых импульсов, изменить уровень, и отправить еще определенное число тактовых импульсов, только после чего можно отправлять данные.
Вид уровня и количество тактов берутся из даташита на контроллер дисплея.
Ниже приведена временная диаграмма сигналов для LZ9JG17: clk — тактовый, HSY — горизонтальной синхронизации, DATA — данных.
По какой-то причине в реальности 216 тактовых импульсов отсчитывались от 2 фронта импульса HSY. Для правильной работы индикатора нужно выводить данные о всех 800 видимых пикселях — иначе нормального изображения не добиться.
С импульсом вертикальной синхронизации все похоже — для начала формирования нового кадра нужно установить на линии вертикальной синхронизации нужный уровень, отправить на индикатор определенное число линий, изменить уровень, отправить еще определенное число линий, и только после этого можно передавать данные, которые будут реально отображаться на экране. Частота импульсов VSY — это частота обновления изображения.
Ниже приведена временная диаграмма сигналов для LZ9JG17: VSY — вертикальной синхронизации, HSY — горизонтальной синхронизации, DATA — данных.
Поскольку видеопамяти у модуля LCD нет, то данные на индикатор нужно передавать постоянно. Как оказалось, для нормальной работы индикатора нужно обновлять изображение с частотой не менее 15 гц.
Микроконтроллер способен справиться с этой задачей, однако для достижения необходимой скорости передачи данных и формирования тактового сигнала нужно использовать SPI, тактовый сигнал при этом берется с вывода SCK, а данные — c MOSI. Поскольку вывод MOSI только один, то изображение возможно формировать только черно-белое, все входы данных модуля нужно запараллелить и соединить с MOSI. В данном случае, при передаче одного байта через SPI на экране будут обновляться 8 пикселей. Частота передачи данных через SPI может доходить до половины тактовой частоты контроллера. Таким образом, для управления модулем LCD требуется всего 4 линии — clk, DATA, VSY, HSY.
У контроллера дисплея есть и другие входы, но, к счастью, их использовать не надо — они отвечают за экзотические настройки контроллера, вроде зеркального отображения изображения, и подтянуты в контроллере к нужным уровням так, что модуль работает в наиболее часто используемом режиме.
Для хранения одного кадка потребуется (800*480)/8 = 48000 байт. К сожалению, у контроллера STM32-DISCOVERY всего 8 килобайт ОЗУ. Поэтому на DISCOVERY возможно формировать только текст. При размере знакоместа 8×16 на экране можно расположить 100×30 знакомест — то потребует 3000 байт ОЗУ.
По поводу подключения в модулю. Модуль LCD соединялся с основной платой телевизора через 40-пиновй шлейф, к выводам которого не подпаятся. Однако рядом с разъемом шлейфа на плате модуля имеются круглые тестовые площадки достаточно большого размера, чтобы к ним можно было припаять провод. Поскольку даташит на контроллер LCD у меня был, то прозвонив мультиметром выводы LZ9JG17 и тестовые площадки, я определил назначение тестовых площадок модуля. С цепями питания еще проще — при подключенной плате телевизора измерил напряжения на наиболее толстых дорожках рядом с разъемом, и определил, что где. Модуль требует 5В 0.1А для питания основных цепей и 12В 0.8А для инвертора. Напряжение сигналов, подаваемых на модуль — 3.3В, так как LZ9JG17 питается именно от 3.3 В. Источник 3.3 вольт есть на самом модуле. Сигнал управления на инвертор подается с еще одного вывода разъема, для того включить подсветку, нужно подать на него 3.3В.
Фотография участка модуля LCD с распиновкой.
Подписи соответствуют подписям выводов LZ9JG17 в даташите. Площадки HENAB, HRVC, HRVC остаются не подключенными.
Что интересно, диод на плате установили китайцы — когда я в первый раз разбирал телевизор, отверстия под винты были закрыты заводской наклейкой.
Вывод VSY модуля у меня соединяется с выводом PC5 платы DISCOVERY, HSY — с PC4, clk — с PA5(SPI_SCK), запараллеленные входы данных — с PA7(SPI_MOSI).
Опыт показал, что вывод clk нужно соединить с землей через конденсатор 18пФ, иначе возникают артефакты изображения.
Фотография конструкции в сборе:
Теперь о программе. Она написана в IAR. Информация у меня передается в SPI через DMA. Используется двойная буферизация — в то время, пока данные из одного буфера передаются на индикатор, другой буфер заполняется данными программой.
При вызове функции start_line() на линии HSY устанавливается высокий уровень, в SPI два раза отправляется байт, что дает 16 тактовых импульсов, после чего на HSY устанавливается низкий уровень. Горизонтальный импульс синхронизации сформирован. Остается отправить на индикатор 216 тактовых импульсов, что соответствует 27 байтам, после чего отправить 100 байт данных. Для этого DMA конфигурируется на передачу 127 байт; адрес, откуда DMA будет брать данные, определяется как адрес нулевого элемента нужного буфера минус 27. После конфигурирования DMA запускается, при этом устанавливается флаг разрешения заполнения нового буфера. Буфер должен быть заполнен раньше, чем DMA закончит передавать данные, поэтому оптимизация в компиляторе должна быть включена.
По завершению передачи DMA создает прерывание. Обработчик прерывания вновь вызывает функцию start_line(), после чего начинается передача новой линии. Если все линии переданы, то start_line() не вызывается, и устанавливается флаг окончания передачи кадра.
Формирование вертикального импульса синхронизации аналогично формированию горизонтального. При вызове функции lcd_new_frame() линии VSY устанавливается высокий уровень, вызывается функция start_line(), после чего программа ожидает завершения передачи линии через DMA. Эта оперция повторяется 6 раз, после чего на VSY устанавливается низкий уровень. После вызова start_line() линии начинают передаваться автоматически и функция lcd_new_frame() завершается. Вызывается lcd_new_frame() программно, после того как программа обнаруживает, что все линии переданы.
Фотография готового устройства:
Работает LCD модуль стабильно, мерцание экрана не заметно. В принципе, подобным образом к DISCOVERY можно подключить любой экран с RGB интерфейсом, лишь бы был даташит на контроллер LCD и индикатор мог работать на соответствующей частоте развертки(у меня 17 Гц).
Позже хочу подключить модуль к более мощному контроллеру с 64 Кбайт памяти, что позволит организовать настоящую видеопамять и выводить на экран любую графику, и сделать что-то вроде настенного календаря-будильника с wifi, что позволит показывать прогноз погоды из интернета, синхронизировать данные будильника с компьютером, показывать заметки, сделанные на компьютере. Эдакий шаг к умному дому.
Небольшой рассказ о том, как впихнуть невпихуемое и отобразить в реальном времени трехмерную графику при помощи контроллера, у которого недостаточно ни скорости, ни памяти для этого.
В далеком 2017 году (судя по дате модификации файла) решил перейти я с контроллеров AVR на более мощные STM32. Естественно, первым контроллером был выбран широко распиаренный F103. Не менее естественно, что использование готовых отладочных плат было отвергнуто в пользу изготовления своей с нуля согласно своим требованиям. Как ни странно, обошлось почти без косяков (разве что UART1 стоило бы вывести на нормальный разъем, а не костылить на проводках).
По сравнению с AVR характеристики камня довольно приличные: 72 МГц тактовой (на практике можно разогнать до 100 МГц, а то и больше, но только на свой страх и риск!), 20 кБ оперативки и 64 кБ флеша. Плюс тонна периферии, при использовании которой главная проблема — не испугаться этого изобилия и осознать, что для запуска не нужно лопатить все десять регистров, достаточно выставить три бита в нужных. По крайней мере до тех пор, пока не захотите странного.
Когда прошла первая эйфория от обладания такой мощью, возникло желание прощупать ее пределы. В качестве эффектного примера я выбрал расчет трехмерной графики со всеми этими матрицами, освещением, полигональными моделями и Z-буфером с выводом на дисплей 320х240 на контроллере ili9341. Две самые очевидные проблемы, которые предстоит решить — скорость и объем. Размер экрана 320х240 при 16 битах на цвет дает 150 кБ на один кадр. А всего оперативки у нас всего 20 кБ… И эти 150 кБ надо передавать на дисплей хотя бы 10 раз в секунду, то есть скорость обмена должна составлять хотя бы 1.5 МБ/с или 12 Мб/с, что уже выглядит существенной нагрузкой на ядро. К счастью, в данном контроллере присутствует модуль ПДП (прямой доступ к памяти, он же Direct Memory Access, DMA), который позволяет не нагружать ядро операциями переливания из пустого в порожнее. То есть можно подготовить буфер, сказать модулю «вот тебе буфер данных, работай!», а в это время готовить данные для следующей передачи. А если учесть способность дисплея принимать данные потоком, вырисовывается следующий алгоритм: выделяется передний буфер, из которого DMA передает данные на дисплей, задний буфер, в который происходит рендеринг, и Z-буфер, используемый для отсечения по глубине. Буферы представляют собой одну строку (или столбец, неважно) дисплея. И вот вместо 150 кБ нам требуется всего 1920 байт (320 пикселей на строку * 3 буфера * 2 байта на точку), что отлично помещается в память. Второй хак основан на том, что расчет матриц преобразования и координат вершин нельзя проводить для каждой строки, иначе изображение будет искажаться самыми причудливыми способами, да и по скорости это невыгодно. Вместо этого «внешние» расчеты, то есть перемножение матриц трансформации и их применение к вершинам пересчитываются на каждом кадре, после чего преобразуются в промежуточное представление, которое оптимизировано для рендеринга в картинку 320х1.
Из хулиганских соображений снаружи библиотека будет напоминать OpenGL. Как и в оригинальной OpenGL отрисовка начинается с формирования матрицы преобразования — очистка glLoadIdentity() делает текущую матрицу единичной, потом набор преобразований glRotateXY(…), glTranslate(…), каждое из которых умножается на текущую матрицу. Поскольку эти расчеты будут проводиться только один раз за кадр, особых требований к скорости нет, можно обойтись простыми float, без извращений с числами с фиксированной точкой. Сама матрица представляет собой массив float[4][4], отображенный на одномерный массив float[16] — вообще-то этот способ обычно применяется для динамических массивов, но и из статических можно извлечь немного выгоды. Еще один стандартный хак: вместо постоянного вычисления синусов и косинусов, которых в матрицах поворота немало, посчитаем их заранее и запишем в табличку. Для этого поделим полный круг на 256 частей, вычислим значение синуса для каждой и свалим его в массив sin_table[]. Ну а получить из синуса косинус сможет любой, кто учился в школе. Стоит отметить, что функции поворота принимают угол не в радианах, а в долях от полного оборота, после приведения к диапазону [0… 255]. Впрочем, реализованы и «честные» функции, выполняющие преобразование из угла в доли под капотом.
Когда матрица готова, можно приступить к отрисовке примитивов. Вообще, в трехмерной графике есть три типа примитивов — точка, линия и треугольник. Но если нас интересуют полигональные модели, внимание следует уделить только треугольнику. Его «отрисовка» происходит в функции glDrawTriangle() или glDrawTriangleV(). Слово «отрисовка» поставлено в кавычки потому что никакой отрисовки на данном этапе не происходит. Мы всего лишь умножаем все точки примитива на матрицу трансформации, после чего выковыриваем из них аналитические формулы ребер y = ky*x + by, которые позволяют найти пересечения всех трех ребер треугольника с текущей выводимой строкой. Одну из них отбросим, поскольку она лежит не на отрезке между вершин, а на его продолжении. То есть для отрисовки кадра нужно всего лишь пройти по всем строкам и для каждой закрасить область между точками пересечения. Но если применить этот алгоритм «в лоб», каждый примитив будет перекрывать те, что были нарисованы раньше. Нам же нужно учитывать Z-координату (глубину), чтобы треугольники пересекались красиво. Вместо простого вывода точки за точкой будем считать ее Z-координату и по сравнению с Z-координатой, хранимой в буфере глубины, либо выводить (обновляя Z-буфер), либо игнорировать. А для расчета Z-координаты каждой точки интересующей нас строки воспользуемся той же формулой прямой линии z = kz*y + bz, вычисленной по тем самым двум точкам пересечения с ребрами. В результате объект «полуфабрикатного» треугольника struct glTriangle состоит из трех X-координат вершин (хранить Y и Z-координаты смысла нет, они будут вычисляться) и k, b коэффициентов прямых, ну и цвет до кучи. Вот здесь, в отличие от расчета матриц преобразования, скорость критична, поэтому уже используем числа с фиксированной точкой. Причем если для слагаемого b достаточно той же точности, что для координат (2 байта), то точность множителя k чем больше, тем лучше, поэтому берем 4 байта. Но не float, поскольку работа с целыми числами все равно быстрее, даже при одинаковом размере.
Итак, вызвав кучу glDrawTriangle() мы подготовили массив полуфабрикатных треугольников. В моей реализации треугольники выводятся по одному явными вызовами функции. На самом деле было бы логично завести массив треугольников с адресами вершин, но здесь я решил не усложнять. Все равно функция отрисовки пишется роботами, а им без разницы, заполнять ли константный массив или писать триста одинаковых вызовов. Пришло время перевести полуфабрикаты треугольников в красивую картинку на экране. Для этого вызывается функция glSwapBuffers(). Как и было описано выше, она проходит по строкам дисплея, ищет для каждой точки пересечения со всеми треугольниками и рисует отрезки в соответствии с фильтрацией по глубине. После рендеринга каждой строки нужно эту строку отправить на дисплей. Для этого запускается DMA, которому указывается адрес строки и ее размер. А пока DMA работает, можно переключиться на другой буфер и рендерить уже следующую строку. Главное не забыть дождаться окончания передачи если вдруг закончили отрисовку раньше. Для визуализации соотношения скоростей я добавил включение красного светодиода после окончания рендеринга и выключение после завершения ожидания DMA. Получается что-то вроде ШИМ, который регулирует яркость в зависимости от времени ожидания. Теоретически вместо «тупого» ожидания можно было бы использовать прерывания DMA, но тогда я не умел ими пользоваться, да и алгоритм существенно бы усложнился. Для демонстрационной программы это излишне.
Результатом описанных выше процедур явилась вращающаяся картинка трех пересекающихся плоскостей разных цветов, причем с довольно приличной скоростью: яркость красного светодиода довольно велика, что говорит о большом запасе по производительности ядра.
Что ж, если ядро простаивает, надо его нагрузить. А нагружать его будем более качественными моделями. Правда, не стоит забывать, что память все-таки сильно ограничена, так что слишком много полигонов контроллер не потянет физически. Простейший расчет показал, что после вычитания памяти на буфер строки и тому подобное, осталось место на 378 треугольников. Как показала практика, под этот размер отлично подходят модели из старой, но интересной игры Gothic. Собственно, оттуда были выдернуты модельки шныга и кровавой мухи (а уже при написании этой статьи и мракориса, красующегося на КДПВ), после чего у контроллера кончилась флеш-память. Но игровые модельки не предназначены для использования микроконтроллером.
Скажем, они содержат анимацию, текстуры и тому подобное, что нам не пригодится, да и не поместится в память. К счастью, blender позволяет не только пересохранить их в *.obj, более поддающийся парсингу, но и уменьшить количество полигонов если нужно. Дальше при помощи простенькой самописной программы obj2arr *.obj файлы разбираются на координаты, из которых впоследствии формируется *.h файл для непосредственного включения в прошивку.
Но пока что модельки выглядят просто как одноцветные фигурные кляксы. На тестовой модели это нас не волновало, поскольку все грани были раскрашены в свои цвета, но не прописывать же цвета каждому полигону модельки. Нет, можно, конечно и муху раскрасить в рандомные цвета, но смотреться это будет довольно вырвиглазно, я проверял. Особенно когда цвета еще и меняются на каждом кадре… Вместо этого применим еще капельку векторной магии и добавим освещение.
Расчет освещения в его примитивном варианте заключается в расчете скалярного произведения нормали и направления на источник света с последующим умножением на «родной» цвет грани.
Моделек у нас теперь три — две из игры и одна тестовая, с которой начинали. Для их переключения воспользуемся одной из двух кнопок, распаянных на плате. Заодно можно добавить контроль загруженности процессора. Один контроль у нас уже есть — красный светодиод, связанный с временем ожидания DMA. А вторым, зеленым, светодиодом, будем мигать при каждом обновлении кадра — так мы сможем оценить частоту кадров. Для невооруженного глаза она составила что-то около 15 fps.
Если вдруг кому-то будет интересно, исходный код доступен тут.
Пока он шел я изучал документацию на драйвер, который им управляет, а на форумах все чаще встречал вопрос типа «Если STM32 имеет встроенный контроллер дисплея, то для чего нужен драйвер?». Все дело в том, для управления жидкими кристаллами, а точнее затворами полевиков, которые ими управляют, необходимо два напряжения VGH и VGL, 19V и -5.5V соответственно. Сам МК такие напряжения выдать не может, он лишь посылает сигналы драйверу согласно интерфейсу RGB, а драйвер дублирует их с необходимым напряжением.
Дисплей пришел, при подключении вопросов не возникло. Запустить дисплей тоже не составило труда, но нормально работать он не хотел. Суть проблемы заключалась в том, что при использовании двух слоев, изображение на дисплее периодически дергалось. Еще раз перечитав AN4861 понял, что проблема в пиксельной частоте.
Дело в том, что дисплей, купленный в Китае имеет разрешение 1024×600, и для того, чтобы изображение на дисплее обновлялось 60 раз в секунду пиксельная частота должна быть в диапазоне от 45 до 63 MHz.
А вывод из сложившейся ситуации можно сделать следующий, перед покупкой дисплея надо разобраться хватит ли пропускной способности оперативки для работы с ним.
Кто должен посещать этот курс?
Льготы заберешь
Краткое содержание курса
Концепция онлайн-курса
Предварительные требования
На данный момент существует множество графических библиотек для STM32.
Самые популярные перечислены ниже.
Учебник и дополнительные doc — это здесь .
STM-TouchGfx — TouchGFX — это современная бесплатная графическая программная среда, оптимизированная для микроконтроллеров STM32.
Используя преимущества графических функций и архитектуры STM32, TouchGFX ускоряет революцию в области HMI-вещей за счет создания потрясающих графических пользовательских интерфейсов, подобных смартфонам.
TouchGFX упрощает разработку графического интерфейса пользователя, сочетая симулятор WYSIWYG и автоматическую генерацию кода.
—
Основные характеристики
–
STemWin
Благодаря партнерству с SEGGER Microcontroller GmbH, STM может предоставить решение STemWin на основе SEGGER emWin one.
Эта библиотека представляет собой профессиональную библиотеку графического стека, позволяющую создавать графические пользовательские интерфейсы (GUI) с любым STM32, любым ЖК-дисплеем / TFT-дисплеем и любым контроллером LCD / TFT, используя преимущества аппаратного ускорения STM32 , когда это возможно.
STemWin предоставляется в бинарной форме любому пользователю STM32 бесплатно.
БиблиотекаSTemWin — это комплексное решение с широким набором функций, таких как декодирование JPG , GIF и PNG , множество виджетов (флажки, кнопки…) и сервер VNC , позволяющий удаленно отображать локальный дисплей, но также профессиональные инструменты разработки, такие как GUIBuilder, для создания GUIS простым перетаскиванием.
Основные характеристики
ПРИМЕЧАНИЕ:
Теперь библиотека и документация STemWin находятся внутри HAL Libraries .
Например, в библиотеке STM32F7 есть каталог, в котором находится обширная документация по графическим библиотекам SEGGER (STemWIN).
Загрузите библиотеку HAL, распакуйте ее и найдите в каталоге ниже.
C: \… .. \ STM32Cube_FW_F7_V1.2.0 \ Middlewares \ ST \ STemWin
В каталоге ниже находится пример STM32F7-DISCOVERY, в котором используются библиотеки Segger.
Все примеры STM относятся к примерам SEGGER, которые здесь .
Прочтите файл: readme.txt , который находится в:
C: \… .. \ STM32Cube_FW_F7_V1.2.0 \ Projects \ STM32746G-Discovery \ Applications \ STemWin \ STemWin_HelloWorld
Главный каталог:
C: \… .. \ STM32Cube_FW_F7_V1.2.0 \ Projects \ STM32746G-Discovery \ Applications \ STemWin
См. Также эту демонстрацию: STemWIN — графическая демонстрация для STM32F746G-DISCO и AC6
Продолжайте TOP
–
Учебные пособия и дополнительные документы
ВНИМАНИЕ:
На данный момент нижеприведенные документы выдаются только клиентам AVNET-SILICA .
Если вы являетесь клиентом SILICA, отправьте мне по электронной почте и спросите меня:
документ / ПО, который вам нужен, и не забудьте указать Ref.Cod.
, пожалуйста, укажите также ваше Имя , Фамилия , Город , Страна и ваш контакт в SILICA .
Если вы являетесь клиентом STM , обратитесь непосредственно в местный офис STM .
Продолжайте TOP
,