Привет, читатель, в прошлой статье я писал о том, как взаимодействовать с аппаратным TWI интерфейсом посредством ассемблера. Теперь перейдем непосредственно к практике.
Все любители электронных самоделок когда-нибудь приходят к желанию отобразить работу своей поделки на экране в виде текста или графики. Самый бюджетный способ сделать это — обратиться к алфавитно-цифровому дисплею типа LCD1602 или LCD2004, общение с которыми происходит либо по параллельному интерфейсу, либо через переходник-конвертор в I2C. Второй способ — использовать графический дисплей, их множество, например SSD1306 с размером матрицы 128x64 пикселя. Он есть в двух вариантах, с интерфейсом SPI или I2C. Мы будем взаимодействовать с таким дисплеем через I2C интерфейс (он же TWI).
Документацию на этот экран можно найти здесь. Экран можно приобрести на AliExpress за 150-200 рублей.
Как я уже писал в предыдущем посту, формат пакета данных для дисплея следующий:
- Сначала подаем команду старт
- Передаем адрес дисплея SLA+W
- Дальше идет m слов содержащих всевозможные настройки дисплея
- Передается признак того, что дальше пойдут графические данные (1 байт) и собственно данные n байт.
Передача каждого байта устанавливает статус ACK.
Инициализация модуля
Вот последовательность действий для инициализации графического дисплея
- Отправляем дисплей в SLEEP MODE (команда 0xAE SSD1306_DISPLAYOFF)
- Установка делителя частоты (команда 0xD5 SSD1306_SETDISPLAYCLOCKDIV) параметр 0x80
- Устанавливает отношение мультиплексирования (команда A8 SSD1306_SETMULTIPLEX) параметр 0x3F
- Устанавливаем смещение дисплея (команда 0xD3 SSD1306_SETDISPLAYOFFSET) параметр 0x00
- устанавливаем начальную линию (команда 0x40 SSD1306_SETSTARTLINE|0x00)
- разрешаем charge pump (команда 0x8d SSD1306_CHARGEPUMP) параметр 0x14
- устанавливаем горизонтальный режим памяти (команда 0x20 SSD1306_MEMORYMODE) параметр 0x00.
- устанавливаем remapping столбцов seg0←→seg127 (команда 0xA0 SSD1306_SEGREMAP | 0x00)
- устанавливаем направление сканирования строк (команда 0xС8 SSD1306_COMSCANDEC)
- Устанавливаем конфигурацию выводов COM-сигналов в соответствии с компоновкой оборудования OLED - панели (команда 0xDA SSD1306_SETCOMPINS) параметр 0x12
- Устанавливаем контраст (команда 0x81 SSD1306_SETCONTRAST) параметр 0xCF
- Установка предзаряда (команда 0xD9 SSD1306_SETPRECHARGE) параметр 0xF1
- Установка Vcomp deselect level (команда 0xDB SSD1306_SETVCOMDETECT) параметр 0x40
- Возобновление отображения содержимого оперативной памяти (команда 0xA4 SSD1306_DISPLAYALLON_RESUME)
- Отображение в нормальном виде команда 0xA6 SSD1306_NORMALDISPLAY
- Включение дисплея в нормальном режиме команда 0xAF SSD1306_DISPLAYON
Ниже на рисунке 3 приведена ассемблерная подпрограмма инициализации дисплея
LCD_Command — это макрос следующего вида:
Этот макрос сделан для удобства использования подпрограммы _LCD_Command. Дело в том, что параметры передаются в нее через регистры tmp1 и tmp2 и запись LCD_Command par1, par2 более естественна, чем три строчки из макроса. Стоит заметить, что и макрос и вызов подпрограммы с передачей параметров занимают одинаковое число тактов процессора и объем ROM.
Что же представляет собой подпрограмма _LCD_Command мы посмотрим на рисунке 5.
Как видно, это подпрограмма посылает управляющее слово (см рисунок 2) по I2C. Подпрограмма использует 3 регистра tmp0, tmp1 и tmp2. В двух последних передаются параметры для подпрограммы ControlByte и DataByte.
Подпрограммы i2c_start, i2c_stop, i2c_send рассмотрены в этой статье.
В нашей реализации подпрограмма инициализации дисплея занимает 152 байта. Этот размер можно уменьшить, если обратить внимание на то, что ControlByte у всех команд, отправляемых в экран равен COMMAND. Как минимум, можно переписать макросы таким образом, чтобы лишний раз не обновлять регистр tmp1.
Очистка экрана (заливка определенным паттерном)
Что такое очистка экрана — это всего лишь заполнение видеопамяти символами определенного качества, в нашем случае это заполнение каждого сегмента страницы видеопамяти графического модуля нулями. Нижеприведенный код подпрограммы будет актуален только при использовании MEMORY_MODE = 0x00 или 0x01.
Мы передаем по I2C DATA байты в количестве 128x64 = 8192. Каждый переданный байт определяет тип очистки. Если передается 0 — экран очищается, если 0xFF— заливается цветом. Подпрограмма состоит из двух вложенных циклов по страницам памяти (8) и по количеству сегментов (128).
Для удобства использования подпрограмма также была завернута в макрос (рисунок 7)
Позиционирование
Рассмотрим теперь позиционирование на экране. Несмотря на то, что у нас дисплей 128x64 пикселя, адресовать номер страницы памяти и номер столбца. Каждая страница памяти это 8 строк. Поэтому, если мы хотим зажечь, допустим, 15 пиксель в 10 строке, мы должны выбрать 1 страницу памяти, в ней выбрать 15 сегмент и по этому адресу записать 4 (бинарное 0000 0100).
Выбор номера страницы памяти осуществляется командами B0-B7, или B0 | y.
Чтобы установить номер столбца, стачала нужно записать в шину последнюю (младшую) шестнадцатиричную цифру числа, затем первую (старшую).
Таким образом, позиционирование выльется в посылке 3 посылки по шине TWI (рисунок 8)
В этом макросе мы устанавливаем маркер текущего положения курсора в позицию (x,y), а также присваиваем соответствующие значения переменным LCD_X, LCD_Y.
Еще один неопределенный макрос SETMEM как раз и занимается сохранением данных в памяти.