Работа с устройствами отображения в ESP32-S3 с помощью ESP-IDF (урок 3)


При работе над проектами встроенных систем нам часто требуется способ видеть, что происходит внутри нашего микроконтроллера. Именно здесь на помощь приходят устройства отображения. Устройства отображения — это важные компоненты вывода, которые позволяют микроконтроллеру напрямую взаимодействовать с внешним миром. Они помогают нам получать информацию, отображать данные с датчиков, показывать оповещения и создавать пользовательские интерфейсы. Вместо того чтобы просто включать один светодиод для индикации состояния, дисплеи позволяют нам отображать числа, текст и даже сложную графику.

Светодиодная матрица

Простейшее устройство отображения — это светодиодная матрица, представляющая собой двумерный массив светодиодов, расположенных в рядах и столбцах. Наиболее распространенный размер — матрица 8x8, содержащая 64 отдельных светодиода, упакованных в один модуль. Ранее на нашем сайте мы рассматривали подключение подобной светодиодной матрицы к микроконтроллеру AVR, к плате Arduino Uno и к микрокомпьютеру Raspberry Pi.

Если бы мы попытались управлять 64 светодиодами по отдельности, нам потребовалось бы 64 цифровых вывода, которых нет у большинства микроконтроллеров. Для решения этой проблемы светодиоды соединяются с помощью метода, называемого мультиплексированием. Аноды (положительные стороны) светодиодов в каждом ряду соединены вместе, а катоды (отрицательные стороны) в каждом столбце также соединены вместе.

Функциональная схема светодиодной матрицы 8х8

Из схемы видно, что для управления простой светодиодной матрицей 8 × 8 обычно требуется 16 контактов (8 строк и 8 столбцов). Например, подача высокого сигнала на столбец 1 активирует этот столбец.

Активация 1-го столбца в светодиодной матрице 8×8

Затем, управляя строками, мы можем решить, какие светодиоды в этом столбце будут включаться или выключаться. Предположим, мы хотим включить только первые два светодиода в столбце 1. В этом случае мы подадим высокое напряжение на столбец 1, а для выбора сигналов строк подадим высокий сигнал на все строки, кроме первой и второй, так что будут активны только эти два светодиода, а остальные останутся выключенными.

Активация первых двух светодиодов 1-го столбца в светодиодной матрице 8×8

Однако при попытке нарисовать более сложные фигуры может возникнуть проблема. Могут появиться конфликты, когда включение одних светодиодов приводит к загоранию других нежелательных светодиодов. В результате отображаемое изображение может выглядеть неполным или некорректным. Для решения этой проблемы можно разделить рисунок на несколько кадров. Например, чтобы нарисовать улыбающееся лицо, в первом кадре можно отобразить глаза, а во втором — рот. Быстро переключаясь между этими кадрами, мы создаём иллюзию одновременного отображения обеих частей. Благодаря инерции зрения наблюдатель воспринимает улыбающееся лицо целиком, а не отдельные изображения.

Принцип формирования сложного изображения на светодиодной матрице 8×8

Следуя этому методу, мы можем нарисовать большую часть нужной нам фигуры, но есть небольшая проблема: использование всех 16 контактов займет большую часть доступных контактов нашего микроконтроллера. Чтобы упростить работу со светодиодной матрицей, мы используем микросхемы драйверов, такие как MAX7219. Эта микросхема обрабатывает все мультиплексирование внутри, поэтому нам не нужно управлять каждым светодиодом по отдельности. В результате нам понадобятся только три цифровых контакта: данные, тактовый сигнал и нагрузка (CS).

Управление светодиодной матрицей с помощью драйвера MAX7219

Драйвер MAX7219 помогает нам управлять светодиодной матрицей, сокращая количество управляющих выводов до трех. Для управления всеми светодиодами он содержит внутреннюю память 8×8 (аналогичную SRAM), в которой хранится информация о том, какие светодиоды должны быть включены. Затем его внутренняя схема преобразует эти данные и подает их на светодиодную матрицу. Мы взаимодействуем с этим драйвером по протоколу SPI, где поток данных управляется с помощью трех выводов:

  • Контакт данных (DIN): сюда поступает фактическая информация. Мы передаем по одному биту за раз. Первые 8 бит представляют адрес регистра, а следующие 8 бит — значение или шаблон.
  • Тактовый вывод (CLK): обеспечивает синхронизацию. На каждом восходящем фронте тактового сигнала MAX7219 производит выборку и сдвигает один бит с линии DIN в свой внутренний сдвиговый регистр.
  • Вывод CS/LOAD: этот вывод выполняет функцию защелки или сигнала выбора микросхемы (активный низкий уровень). Пока вывод CS находится в низком состоянии, микросхема прослушивает сигнал и сдвигает биты с каждым импульсом CLK. После ровно 16 тактовых импульсов (16 отправленных битов) мы переводим вывод CS в высокое состояние. Этот восходящий фронт сообщает MAX7219: «ОК, полная команда готова, теперь декодируйте ее и подайте на дисплей или регистры».

Вкратце, для связи с MAX7219 мы сначала устанавливаем низкий уровень сигнала CS (Chip Select), чтобы указать на начало передачи данных. Затем в течение 16 тактовых циклов мы устанавливаем нужный бит на выводе DIN (Data) и подаем импульсы высокого, а затем низкого уровня на вывод CLK (Clock). Каждый тактовый импульс отправляет один бит на микросхему. После 16-го тактового импульса мы устанавливаем высокий уровень сигнала CS. Затем MAX7219 фиксирует 16 бит, интерпретирует их как адрес и данные и обновляет память дисплея или параметры конфигурации, такие как яркость или ограничение сканирования. После этого MAX7219 автоматически обрабатывает непрерывное построчное сканирование светодиодной матрицы. На диаграмме показана эта работа.

Принцип управления светодиодной матрицей с помощью драйвера MAX7219

Давайте создадим простой проект для отображения сердца на светодиодной матрице. Сначала нам нужно собрать схему. Начнём с подключения выводов 5 В и GND микроконтроллера ESP32-S3 к выводам 5 В и GND модуля MAX7219. Затем подключим выводы управления следующим образом.

  • CLK к GPIO 19 Esp32s3
  • CS к GPIO 20 Esp32s3
  • DIN к Esp32s3 GPIO 21

Схема подключения светодиодной матрицы к ESP32-S3

Теперь давайте создадим нашу программу. Начнём с подключения необходимой нам библиотеки.

После этого мы создаём константы, представляющие наши контакты.

После этого мы создаём функцию, которая обрабатывает логику отправки данных. Поскольку аппаратный интерфейс отправляет данные по одному биту за раз, байт необходимо разбить на отдельные биты перед передачей. Мы решаем эту задачу, используя метод маскирования с побитовыми операциями. Внутри функции мы определяем массив масок:

Каждая маска соответствует одной битовой позиции в байте, начиная со старшего бита (MSB) и заканчивая младшим битом (LSB). Например, 0x80 (двоичная 10000000) маска выделяет первый бит, а 0x01 (двоичная 00000001) маска выделяет последний бит.

В каждой итерации цикла мы используем побитовую операцию И (data & bits[i]) для извлечения определенного бита из байта. Идея проста: операция И сохраняет только тот бит, который соответствует маске, и обнуляет все остальные. Если результат не равен нулю, это означает, что данный бит равен нулю. 

После извлечения битового значения мы отправляем его через вывод данных ( DIN). Если бит равен 1, мы устанавливаем вывод в высокое состояние; если он равен 0, мы устанавливаем его в низкое состояние. Одновременно мы управляем тактовым сигналом ( CLK) для синхронизации передачи. Сначала тактовый сигнал устанавливается в низкое состояние, затем бит данных помещается на линию данных, и, наконец, тактовый сигнал устанавливается в высокое состояние.

Повторяя этот процесс для всех 8 бит, функция успешно передает весь байт, по одному биту за раз, используя точное управление линиями данных и тактового сигнала.

После завершения работы над функцией, отвечающей за отправку одного байта, следующим шагом является создание функции более высокого уровня, которая обрабатывает взаимодействие с драйвером MAX7219.

Микросхема MAX7219 ожидает данные в 16-битном формате, разделенном на два байта. Первый байт представляет собой адрес регистра, указывающий, к какому внутреннему регистру мы хотим получить доступ (например, к цифровому или управляющему регистру). Второй байт содержит фактические данные, которые будут записаны в этот регистр.

Для обработки этого функция принимает два аргумента: адрес регистра reg и значение для отправки data. Связь начинается с установки линии Chip Select ( ) в низкий уровень. Это сигнализирует MAX7219 о начале передачи и о необходимости обратить внимание на входящие данные.

После установления связи мы сначала отправляем адрес регистра, используя ранее определенную функцию send_byte. Это гарантирует, что драйвер знает, где должны храниться входящие данные. Сразу после этого мы отправляем сам байт данных, снова используя ту же функцию отправки байтов.

Наконец, после передачи обоих байтов, мы устанавливаем линию CS в высокий уровень. Этот восходящий фронт сообщает MAX7219 о необходимости зафиксировать полученные 16-битные данные в своих внутренних регистрах, тем самым фактически завершая операцию.

После настройки функции связи следующим шагом является инициализация MAX7219 для работы в нужном режиме. Это делается путем отправки последовательности команд конфигурации во внутренние регистры с помощью созданной ранее функции max7219_send.

Каждый вызов функции max7219_send(reg, data) записывает значение в определенный регистр внутри драйвера. Функция инициализации начинается с настройки регистра тестирования дисплея ( 0x0F) с помощью параметра 0x00, что отключает тестовый режим. Это важно, поскольку тестовый режим принудительно включает все светодиоды.

Далее регистр выключения ( 0x0C) устанавливается в значение 0x01. Несмотря на свое название, это фактически выводит устройство из режима выключения и включает дисплей. Без этого шага MAX7219 оставался бы неактивным.

Затем регистр ограничения сканирования ( 0x0B) устанавливается в значение 0x07. Это указывает драйверу использовать все 8 цифр (от 0 до 7). Если бы использовалось меньшее значение, была бы активна только часть дисплея.

После этого регистр интенсивности ( 0x0A) устанавливается в значение 0x08, которое регулирует яркость светодиодов. Значение обычно может варьироваться от 0x00 (самая тусклая) до 0x0F (самая яркая), поэтому устанавливается средний уровень яркости.

Затем регистр режима декодирования ( 0x09) устанавливается в значение 0x00, что отключает декодирование BCD. Это означает, что мы работаем в необработанном режиме, где каждый бит напрямую управляет сегментом светодиода.

Наконец, цикл проходит по всем 8 строкам (с 1 по 8) и очищает их, отправляя соответствующий сигнал 0x00. Это гарантирует, что дисплей запустится в чистом состоянии со всеми выключенными светодиодами.

Теперь, когда все необходимые компоненты готовы, мы можем реализовать основную логику программы внутри app_main.

В начале работы функции мы настраиваем выводы GPIO, подключенные к MAX7219 - DINCLK, CS как выходы. Затем мы устанавливаем линию CS (выбор микросхемы) в высокое состояние, что означает, что устройство находится в режиме ожидания и в данный момент не принимает данные.

Далее мы вызываем функцию max7219_init() для инициализации драйвера с конфигурацией, которую мы определили ранее.

После инициализации мы определяем массив heart содержащий 8 байтов. Каждый байт представляет собой одну строку светодиодной матрицы, и каждый бит внутри байта соответствует одному светодиоду в этой строке. Значение 1 означает, что светодиод включен, а значение 0 означает, что он выключен. Последовательность двоичных значений в этом массиве образует форму сердца при отображении на матрице.

Внутри бесконечного цикла мы сначала перебираем массив heart. Для каждой строки мы вызываем метод max7219_send(i + 1, heart[i])i + 1 обозначает строку, а heart[i] — шаблон для этой строки.

После отображения сердца мы вводим задержку в 500 миллисекунд с помощью vTaskDelay. Это позволяет сердцу оставаться видимым в течение короткого времени.

Затем мы запускаем еще один цикл, который очищает дисплей, отправляя данные 0 во все строки. Это фактически выключает все светодиоды. После этого следует еще одна задержка в 500 мс, в течение которой дисплей остается пустым.

Постоянно повторяя эти два шага — отображение и последующее исчезновение изображения сердца — мы создаём эффект мигающего сердца на светодиодной матрице.

Семисегментный дисплей

Семисегментные дисплеи — это цифровые дисплеи, состоящие из семи светодиодных сегментов, расположенных таким образом, чтобы образовывать цифры от 0 до 9 и некоторые буквы. Эти сегменты обозначены буквами от A до G. Включая определенные комбинации этих сегментов, мы можем сформировать любое число. Например, чтобы отобразить число «1», мы включаем сегменты B и C.

Структура семисегментного дисплея

Существует два основных типа семисегментных дисплеев:

  • Общий катод: все отрицательные выводы (GND) светодиодов соединены вместе. Мы подаем высокий сигнал на вывод сегмента, чтобы включить его.
  • Общий анод: все положительные выводы (VCC) светодиодов соединены вместе. Для включения светодиода на его вывод подается низкий сигнал.

Если мы попытаемся управлять каждым сегментом по отдельности, нам потребуется 7 контактов GPIO. Это хорошо работает для одного дисплея, но при использовании нескольких семисегментных индикаторов мы быстро исчерпаем доступные контакты GPIO.

Для решения этой проблемы многие 7-сегментные дисплеи работают в паре с декодером BCD-кодирования в 7-сегментный. Такой подход сокращает количество необходимых контактов GPIO с 7 до всего лишь 4 управляющих контактов. Вместо прямого управления каждым сегментом мы передаем значение цифры, используя кодирование BCD (двоично-кодированное десятичное число), где каждая десятичная цифра представлена ​​своим двоичным эквивалентом.

Затем декодер преобразует этот двоичный входной сигнал в соответствующие сигналы для подсветки нужных сегментов. Соответствующее соответствие показано в таблице ниже:

Десятичная цифра BCD (a,b,c,d)
0 0000
1 0001
2 0010
3 0011
4 0100
5 0101
6 0110
7 0111
8 1000
9 1001

Например, с помощью семисегментного дисплея с общим катодом мы можем отобразить цифру «3», отправив BCD-сигнал (низкий, низкий, высокий, высокий), как показано ниже:

Принцип отображения на семисегментном дисплее цифры «3»

Четырехзначный 7-сегментный индикатор

Во многих приложениях нам необходимо отображать числа, состоящие более чем из одной цифры, например, двузначные, четырехзначные или более крупные значения. Четырехзначный 7-сегментный дисплей формируется путем объединения четырех отдельных 7-сегментных разрядов в один модуль. Это достигается путем соединения всех соответствующих выводов сегментов вместе, при этом для каждого разряда добавляется отдельный управляющий (выборочный) вывод, определяющий, какой из них активен в данный момент времени.

Структура четырехзначного 7-сегментного индикатора

Если бы мы попытались управлять всеми четырьмя цифрами вручную, нам потребовалось бы 12 контактов: 8 контактов для сегментов и 4 контакта для выбора цифры. Чтобы уменьшить количество контактов, мы можем использовать BCD-кодировщик, который позволяет управлять всеми четырьмя цифрами, используя всего 9 контактов (4 для BCD-входа + 5 для сегментов, включая десятичную точку); если убрать десятичную точку, можно использовать 8 контактов.

Принцип использования BCD-кодировщика при работе с четырехзначным 7-сегментным индикатором

Работа с TM1637

TM1637 — это специализированная схема управления светодиодами, обычно используемая с четырехразрядными 7-сегментными дисплеями. Как и MAX7219, её основная цель — значительно сократить количество выводов GPIO, необходимых для управления дисплеем. В то время как стандартному 4-разрядному дисплею может потребоваться до 12 выводов, TM1637 сокращает их количество до двух выводов связи, а также выводов питания и заземления.

Мы взаимодействуем с TM1637, используя двухпроводной последовательный протокол, очень похожий на I2C, хотя и без адресации устройства. Управление потоком данных осуществляется с помощью этих двух выводов:

  • Контакт данных (DIO): этот двунаправленный контакт используется для передачи данных в микросхему и из нее. По этой линии мы отправляем команды и отображаем данные, по одному биту за раз.
  • Тактовый вывод (CLK): он обеспечивает синхронизацию передачи данных. Данные на выводе DIO должны быть стабильными, пока тактовый сигнал находится в высоком состоянии, и могут изменять свое состояние только тогда, когда тактовый сигнал находится в низком состоянии.

В отличие от MAX7219, который использует вывод выбора микросхемы (CS) для индикации начала и конца передачи, TM1637 использует определенные условия сигнализации на линиях данных и тактовой частоты:

  • Условие запуска: передача начинается, когда линия DIO переведена в низкий уровень, а линия CLK остается в высоком уровне.
  • Условие остановки: передача завершается, когда линия DIO переходит в высокое состояние, в то время как линия CLK находится в высоком состоянии.
  • Передача данных: между условиями запуска и остановки отправляется 8 бит данных, начиная с младшего значащего бита (LSB). После отправки 8 бит генерируется 9-й тактовый импульс, позволяющий микросхеме TM1637 отправить сигнал подтверждения (ACK), подняв линию DIO в низкий уровень. После этого можно установить условие остановки для прекращения передачи данных или отправить следующий пакет данных.

Временная диаграмма работы TM1637

Давайте создадим простой проект для отображения числа "1234" на нашем 4-разрядном 7-сегментном дисплее. Начнём с подключения контактов 5 В и GND микроконтроллера ESP32-S3 к контактам VCC и GND модуля TM1637. Затем подключим контакты связи следующим образом:

  • CLK к GPIO 19 ESP32-S3
  • DIO к GPIO ESP32-S3 21

Схема подключения 4-разрядного 7-сегментного дисплея к ESP32-S3

Теперь давайте напишем нашу программу. Начнём с подключения необходимых библиотек.

После этого мы создаём константы, которые представляют наши контакты.

Поскольку протокол TM1637 основан на условиях запуска и остановки, а не на выводе CS, нам необходимо создать две небольшие вспомогательные функции для обработки этих специфических последовательностей. В условии запуска DIO переходит в низкое состояние, в то время как CLK находится в высоком состоянии. В условии остановки DIO переходит в высокое состояние, в то время как CLK находится в высоком состоянии.

Далее мы реализуем функцию, отвечающую за отправку одного байта. В отличие от MAX7219, который ожидает передачи данных сначала старшим битом (MSB), TM1637 требует, чтобы сначала был отправлен младший бит (LSB). Для этого мы меняем логику извлечения битов, использованную ранее. Вместо итерации по массиву масок от первой к последней, мы проходим по ним в обратном направлении.

После передачи всех 8 бит TM1637 требуется фаза подтверждения (ACK). В течение этой фазы устройство устанавливает низкий уровень на линии DIO, чтобы указать на успешное получение байта. Для обработки этого мы генерируем дополнительный (9-й) тактовый импульс и устанавливаем низкий уровень на линии данных, чтобы завершить цикл, не проверяя явно сигнал ACK.

Для отображения чисел на 7-сегментном дисплее нам необходимо знать, какие сегменты нужно включить для каждой цифры (0-9). Для этого мы можем определить массив, где индекс соответствует цифре, а значение — шестнадцатеричное представление сегментов, которые необходимо включить.

Теперь мы можем собрать все воедино и реализовать основную логику приложения для дисплея TM1637.

В начале app_main мы настраиваем выводы CLK и DIO как выходы. Эти две линии образуют интерфейс связи с TM1637. После этого мы устанавливаем обе линии в высокое состояние, что соответствует состоянию простоя шины. Это важно, поскольку протокол TM1637 ожидает, что линии тактового сигнала и данных будут в высоком состоянии, когда передача данных не происходит.

Обмен данными разделён на три основные команды, каждая из которых заключена между условиями начала и окончания с помощью функций tm1637_start() и tm1637_stop(). Эти функции определяют начало и конец каждой передачи.

Первая команда отправляет значение 0x40, которое настраивает TM1637 на режим автоматического увеличения адреса. Это означает, что после записи одного байта данных внутренний указатель адреса автоматически перемещается на следующую позицию, что упрощает последовательную отправку нескольких цифр.

Вторая команда управляет состоянием дисплея и яркостью. Значение 0x8F объединяет две настройки: включение дисплея ( 0x88) и установку максимальной яркости ( 0x07). Это обеспечивает видимость и четкую подсветку цифр.

Внутри бесконечного цикла третья команда начинается с отправки 0xC0, которая устанавливает начальный адрес для отображаемых данных (позиция первой цифры). После этого мы отправляем четыре байта, каждый из которых представляет собой цифру для отображения. Эти значения берутся из массива segment_map.

После отправки всех команд мы добавляем задержку в 1 секунду, используя vTaskDelay. Это позволяет поддерживать стабильное состояние отображаемых чисел до повторения цикла и обновления экрана.

Чтобы сделать проект интереснее, мы изменили основную логику таким образом, чтобы на дисплее отображался обратный отсчет от 59 до 0 вместо фиксированных цифр. Общая структура программы осталась прежней: инициализация и настройка связи остались точно такими же, как и раньше, обновлена ​​только логика отображения.

Внутри бесконечного цикла мы вводим цикл for, который отсчитывает от 59 до 0. Этот цикл управляет значением, которое мы хотим отобразить на 4-разрядном экране. На каждой итерации мы отправляем новое значение на дисплей.

Обновление дисплея начинается с обычных условий запуска и команды установки 0xC0 начального адреса. Затем мы отправляем четыре байта, каждый из которых представляет одну цифру:

  • Первые две цифры установлены на 0, поэтому они отображаются как 00. Это позволяет удерживать фокус на последних двух цифрах.
  • Третья цифра устанавливается с помощью функции i / 10.. Это извлекает разряд десятков числа. Например, если i = 47, то 47 / 10 = 4.
  • Четвертая цифра устанавливается с помощью i % 10. Это извлекает разряд единиц. Например, 47 % 10 = 7.

Объединив эти две операции, мы разбиваем число на десятичные разряды и правильно отображаем их на семисегментном дисплее.

После отправки цифр мы останавливаем передачу и ждем 1 секунду, используя команду vTaskDelay. Это создает видимый эффект обратного отсчета, уменьшая число на одну секунду.

ЖК-дисплей

Светодиодные матрицы и семисегментные дисплеи очень полезны, когда необходимо отображать числа или простые геометрические фигуры. Однако у них есть важное ограничение. Семисегментные дисплеи в основном предназначены для отображения чисел, а светодиодные матрицы требуют большого количества светодиодов и сложного управления для отображения читаемого текста. Для решения этой проблемы используются ЖК-дисплеи (жидкокристаллические дисплеи). Эти дисплеи позволяют отображать текстовые символы, а иногда и небольшие знаки, что делает их идеальными для создания пользовательских интерфейсов.

В жидкокристаллических дисплеях (ЖКД) используется особый материал, называемый жидкими кристаллами. Эти материалы обладают свойствами, промежуточными между жидкостью и твердым кристаллом.

Внутри ЖК-модуля находится несколько слоев:

  1. Два поляризационных фильтра.
  2. Слой жидких кристаллов.
  3. Стеклянные пластины с прозрачными электродами.
  4. Подсветка.

Принцип работы ЖК дисплея

В отличие от светодиодных дисплеев, пиксели ЖК-дисплея сами по себе не излучают свет. Вместо этого они действуют как крошечные затворы, которые контролируют прохождение света через экран. Когда внутренний контроллер подает напряжение на определенные области пикселя, жидкие кристаллы физически скручиваются. Это скручивание изменяет поляризацию света. В то время как свет проходит через кристаллы естественным образом, когда электричество не подается, скрученные кристаллы заставляют второй (передний) поляризационный фильтр блокировать свет. В результате пиксель выглядит темным, в то время как окружающие области остаются яркими. Этот заблокированный свет создает темные «пиксели», из которых состоят наши буквы, цифры и символы.

Существует несколько типов модулей ЖК-дисплеев, но наиболее распространенными на рынке являются:

  • 16 × 2 ЖК дисплей отображает 16 символов в строке и 2 строки.
  • 20 × 4 ЖК дисплей отображает 20 символов в строке и 4 строки.
  • 40 × 2 ЖК дисплей отображает 40 символов в строке и 2 строки.
  • 40 × 4 ЖК дисплей отображает 40 символов в строке и 4 строки.

Стандартный ЖК-модуль 16 × 2 обычно имеет 16 контактов. Эти контакты используются для питания, управляющих сигналов, передачи данных и управления подсветкой.

Распиновка ЖК дисплея 16×2

Для прямой связи с ЖК-дисплеем возможны два режима:

  • 8-битный режим
  • 4-битный режим

В 8-битном режиме мы используем все восемь выводов данных (D0–D7) ЖК-дисплея для отправки одного полного байта данных за раз. Этот метод быстрее, поскольку весь символ передается за одну операцию. Однако этот режим требует большого количества выводов GPIO:

  • 8 контактов для передачи данных
  • 2 управляющих контакта (RS, Enable)

В 4-битном режиме ЖК-дисплей принимает данные в два этапа вместо одного. Сначала отправляются старшие 4 бита, затем младшие 4 бита того же байта. Внутри ЖК-дисплей объединяет эти две части для восстановления полного 8-битного значения.

Такой подход уменьшает количество необходимых контактов:

  • 4 вывода данных (D4–D7)
  • 2 управляющих контакта (RS и Enable)

Давайте создадим простой пример «Hello World», используя 16×2 ЖК-дисплей в 4-битном режиме. Сначала подключим ЖК-дисплей к ESP32-S3: RS к GPIO 20, EN к GPIO 19, D4 к GPIO 1, D5 к GPIO 2, D6 к GPIO 42 и D7 к GPIO 41. Для питания подключим VSS, RW и K к GND, а VDD и A к 5 В.

Схема подключения 16×2 ЖК-дисплея к ESP32-S3

Теперь давайте создадим компонент LCD. Для начала добавим каталог components в корневую директорию проекта. Внутри него создадим папку с именем lcd в которой будут храниться все файлы, относящиеся к драйверу LCD.

Внутри этой папки lcd добавим файл CMakeLists.txt для определения правил сборки, файл lcd.c для реализации и директорию include для заголовочного файла lcd.h.

Внутри components/lcd/CMakeLists.txt мы используем функцию idf_component_register, чтобы сообщить системе сборки, какие исходные файлы нужно скомпилировать и где найти общедоступные заголовочные файлы:

Наконец, мы обновляем файл CMakeLists.txt внутри папки main. Добавив ключевое слово REQUIRES, мы гарантируем, что система сборки свяжет компонент lcd с нашим основным приложением:

После завершения реструктуризации проекта мы можем приступить к реализации логики ЖК-компонента. Сначала мы изучим техническое описание, чтобы понять, как программировать и управлять устройством.
Быстрая проверка показывает, что контакты RS и RW управляют режимом работы ЖК-дисплея:

  • При RS=0 и RW=0 ЖК-дисплей принимает команды конфигурации.
  • RS=1 и RW=0 — ЖК-дисплей получает команды для отображения

Также из технической документации видно, что команды отправляются через контакты данных D0...D7, и ЖК-дисплей принимает следующие команды.

Команды для управления ЖК-дисплеем 16х2

Для каждой отправленной команды необходимо подать импульс на контакты E (разрешение) для проверки данных. На следующей диаграмме показано, как следует подавать сигнал.

Временные диаграммы управления ЖК дисплеем 16х2

Наконец, прежде чем отправлять какие-либо команды или пытаться отобразить какой-либо символ на ЖК-дисплее, необходимо сначала подождать 15 мс и инициализировать его, установив D5 и D4 в 1 и отправив три импульса разрешения. После этого первой командой, которую необходимо отправить, является режим отображения; мы устанавливаем его на 4 бита, после чего можем начать настройку ЖК-дисплея, отправив остальные команды. Следующая диаграмма отображает рабочий процесс.

Алгоритм отображения символа на ЖК-дисплее 16х2

Теперь, когда мы знаем, как работает ЖК-дисплей, давайте начнём создавать нашу программу. Для начала подключим библиотеку, которую будем использовать.

Теперь давайте создадим наши функции, начнём с lcd_pulse_enable - она обрабатывает импульс разрешения на выводе E.

Поскольку мы планируем использовать 4-битный режим, давайте создадим функцию, отвечающую за отправку 4 бит на ЖК-дисплей. Функция будет принимать в качестве аргумента данные типа байт, и поскольку мы можем отправлять на наши выводы только 1 бит за раз, мы будем использовать побитовую обработку с маской для извлечения наших выводов.

Теперь реализуем функцию, отвечающую за отправку команды. В 4-битном режиме ЖК-дисплей ожидает данные в два этапа: сначала старшие 4 бита (старший полубайт), затем младшие 4 бита (младший полубайт). Чтобы извлечь первые 4 старших бита, мы сдвигаем их вправо на 4.

Мы создаём функцию для инициализации наших контактов GPIO.

Теперь, когда мы создали основу для связи с ЖК-дисплеем, мы можем реализовать функцию инициализации. Эта функция подготавливает ЖК-дисплей к корректной работе в 4-битном режиме.

При включении ЖК-дисплей по умолчанию переходит в 8-битный режим. Для переключения в 4-битный режим необходимо выполнить определенную последовательность инициализации, описанную в техническом описании контроллера. Сначала нужно подождать около 50 мс, чтобы убедиться, что ЖК-дисплей полностью включен.

Затем трижды отправить значение 0x03 с помощью (D4 и D5), каждый раз сопровождаемое импульсом разрешения. Этот шаг принудительно переводит ЖК-дисплей в известное 8-битное состояние, независимо от его предыдущего состояния.

После этого нужно отправить команду 0x02 для переключения ЖК-дисплея в 4-битный режим.
Как только ЖК-дисплей перейдет в 4-битный режим, можно отправлять полные команды для настройки дисплея. Начнем с команды «Установка функций» для выбора 4-битного интерфейса и 2-строчного режима отображения. Затем включим дисплей, отключив курсор и заставив его мигать. Наконец, очистим дисплей и дождемся завершения операции.

Прежде чем реализовывать функции, используемые для отображения текста и символов, мы сначала определим две вспомогательные функции.

Первая функция очищает дисплей и возвращает курсор в исходное положение. Это делается путем отправки команды очистки дисплея ( 0x01), за которой следует небольшая задержка, позволяющая ЖК-дисплею завершить операцию.

Вторая вспомогательная функция перемещает курсор в определенную позицию, заданную строкой и столбцом. Это достигается путем установки старшего бита (D7) в 1 и объединения его с целевым адресом DDRAM, как показано в таблице ниже.

Для большинства ЖК-дисплеев с соотношением сторон 16×2:

  • Строка 1 начинается с адреса 0x00→ базовый адрес команды0x80
  • Строка 2 начинается с адреса 0x40→ базовый адрес команды0xC0

Схема адресации ЖК-дисплея с соотношением сторон 16×2

Теперь мы можем реализовать функцию, отвечающую за отображение одного символа. Эта функция принимает символ в качестве аргумента, устанавливает вывод RS (Register Select) в высокое состояние, указывая на то, что мы отправляем данные (а не команду), отправляет символ на ЖК-дисплей, а затем возвращает вывод RS в низкое состояние.

Мы также можем создать функцию для отображения полного сообщения (строки). Эта функция принимает массив символов и использует цикл для перебора каждого символа до тех пор, пока не будет достигнут нулевой символ-терминатор ( '\0'). Затем каждый символ отправляется на ЖК-дисплей с помощью функции write_character.

Последний шаг — создание заголовочного файла lcd.h. Этот файл определяет публичный интерфейс нашего драйвера ЖК-дисплея, который позволяет другим частям программы взаимодействовать с ЖК-дисплеем.

Сначала мы используем проверки включения, чтобы предотвратить многократное включение заголовочного файла, что может привести к ошибкам. Это делается с помощью  #ifndef #define include вверху и  #endif  include внизу.

Между проверками мы пишем функции, которые планируем использовать совместно, и определяем константы, представляющие наши контакты.

Теперь в файл main.c можно добавить компонент ЖК-дисплея и отобразить простое сообщение.

После инициализации ЖК-дисплея мы отправляем строку для отображения на экране.

(Проголосуй первым!)
Загрузка...
32 просмотров

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *