Подключаем HD44780 дисплей к STM32

Зачастую контроллеру приходится сообщать пользователю какие-либо данные, и далеко не всегда можно обойтись одними лишь цифрами. Для отображения текстовой информации как правило применяют LCD индикаторы на базе контроллера HD44780. Не смотря на то, что подобный дисплеев очень много все они управляются одинаково так как контроллер в них стоит один и соответственно система команд у них одна, так же они имеют одинаковый набор ног. В этой статье мы попробуем подключить такой дисплей к контроллеру установленному на STM32vl Discovery. Сам дисплей выглядит следующим образом:
 
hd44780
 
Прежде чем писать программу которая будет рулить дисплеем нам потребуется разобраться какие у него есть выводы. Их не так много:
DB0...DB7 - Выводы через которые контроллер получает/передаёт данные и команды
E - Строб. Когда на всех линиях данных установлены нужные логические уровни, необходимо кратковременно подать на него логическую единицу, а потом снова ноль. Только после такого дёрганья ногой дисплей считает данные (или команду) с ножек DB0...DB7
RW - Состояние этой ноги сообщает дисплею, что мы хотим с ним делать: 1 - читать из дисплея, 0 писать в дисплей. Вообще чтение из дисплея - вещь совсем не нужная на мой взгляд, именно поэтому я никогда не подключаю этот вывод к контроллеру а просто сажаю его на землю. Таким образом режим записи в дисплей включен постоянно.
RS - Управляя состоянием этой ноги - мы сообщаем дисплею что хотим ему передать: Если ноль то команда, если единица то данные.
V0 - Вывод управления контрастностью дисплея. обычно сюда подключают переменный резистор (как делитель напряжения) и вращая его настраивают необходимую контрастность. Если его не подключить, то изображение (в 99%) не появится вообще. Хотя у меня был один дисплей который не требовал подключения такого резистора. 
VSS - ну тут думаю всё понятно, земля. 
VCC - напряжение питания, обычно 5 вольт.
A - анод светодиодной подсветки.
K - катод светодиодной подсветки.
Иногда подсветка уже имеет токоограничивающий резистор, и можно подключать эти два вывода напрямую к питанию, а иногда резистора нет и его нужно добавить. В противном случае подсветка может испустить тот самый волшебный дымок. 
Выводов многовато и почти все из них должны управляться нашим контроллером. Таким образом мы должны задействовать аж 10 ног! (DB0...DB7RS и E). В принципе у STM32 нет такой большой проблемы со свободными ногами, как например у AVR или PIC, но всё равно жалко их тратить, да и куча проводов от дисплея это не гуд. Я веду к тому, что есть возможность так же полноценно управлять дисплеем используя всего четыре линии данных (DB4...DB7) вместо восьми. Поэтому, в данной статье будет рассмотрен именно этот режим работы. Но сначала много теории без которой мы далеко не уйдем. Я конечно понимаю, что на эту тему было  написано достаточно кода и мануалов, но свой изобретенный велосипед помог мне лучше понять и запомнить как работать с дисплеем на контроллере HD44780. Начать стоит с того, что у дисплея есть память аж два вида памяти в которые мы можем что-то записать:

1. DDRAM. Когда мы записываем в эту память данные то всё что мы туда записали появляется на экране. Всего у контроллера 80 байт такой памяти, а сам дисплей может отображать 32 символа (16 символов  * 2 строки) в один момент времени. Возникает вопрос: а какие 32 символа из 80 будут отображаться на экране? По каким адресам DDRAM мы должны писать что-то чтоб увидеть это на экране? Ответ в картинке ниже:

hd44780 memory

Из рисунка видно, что в желтенькое "окошко" размером 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 точек. Как правило бит всегда сброшен, индикаторы поддерживающие сразу два размера символов встречаются крайне редко. 
ASG - биты задающие адрес по которому будем писать в SGRAM память.
ADD - биты задающие адрес по которому будем писать в DDRAM память.
 
Выглядит конечно немного запутанно, но постепенно все прояснится. Особенно если проверять эти команды на практике и побольше экспериментировать. Таблица показывает, что каждая команда у нас восьмибитная, а дисплей подключен к контроллеру по четырем проводам (напомню, что задействованны линии DB4...DB7). Может возникнуть вполне уместный вопрос: Как передать 8-ми битную команду по 4-м проводам? Для этого мы должны сначала передать старший полубайт команды а затем младший. Ну и конечно не забываем своевременно дергать выводы E и RS. Полный алгоритм передачи команды выглядит так: 
  1. Вывод RS=0
  2. Вывод E=0
  3. Выводим старший полубайт на линии DB4...DB7
  4. Вывод E=1
  5. Небольшая задержка
  6. Вывод E=0
  7. Небольшая задержка
  8. Выводим младший полубайт на линии DB4...DB7
  9. Вывод E=1
  10. Небольшая задержка
  11. Вывод E=0
  12. Небольшая задержка
Теперь когда мы имеем представление о выводе команд, можно попробовать произвести начальную инициализацию дисплея. Первым делом нужно установить разрядность и тут есть нюанс. Контроллер перейдет в 4-х битный режим только после того как мы установим на ногах DB7..DB4 значение 0010 и дрыгнем ногой E. При этом на остальных ногах дисплея (DB3..DB0) может быть вообще что угодно, они ведь болтаются в воздухе. Таким образом будет выполнена команда 0010ХХХХ где Х это может быть 1 или 0 (непредсказуемо). Естественно после дрыга ногой E, дисплей перейдет в 4-х битный режим но вот что там записалось в биты N и F неизвестно. Поэтому нужно задать их еще раз, в 4-х битном режиме это стало возможно. Таким образом полная процедура перевода дисплея в 4-х битный режим, включения двух строк и использования символов размером 5 на 8 точек будет выглядеть так: 
  1. Вывод RS=0
  2. Вывод E=0
  3. Выводим старший полубайт 0010      // Бит DL 
  4. Вывод E=1
  5. Небольшая задержка
  6. Вывод E=0
  7. Небольшая задержка
  8. Выводим старший полубайт 0010      // Бит DL
  9. Вывод E=1
  10. Небольшая задержка
  11. Вывод E=0
  12. Небольшая задержка
  13. Выводим младший полубайт 1000    // Бит N, Бит F
  14. Вывод E=1
  15. Небольшая задержка
  16. Вывод E=0
  17. Небольшая задержка
Следующий обязательный шаг инициализации - заставить дисплей отображать содержимое 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 (используя последнюю команду из таблички).  Если вы всё-таки дочитали до этого момента, значит вам действительно интересно узнать как работать с этим дисплеем. Практическая часть будет в следующей статье, а пока переваривайте это.