Подключаем неизвестный мультиплексированный ЖКИ
Приветствую всех!
На просторах можно найти немало материалов и рекомендаций касательно того, как подключить к микроконтроллеру дисплей от телефона, видеорегистратора или навигатора. А для более продвинутых электронщиков, имеющих мощные МК и не боящихся встретиться с интерфейсом LVDS, — от ноута или планшета.
А вот касательно «голых» ЖКИ информации традиционно маловато. Итак, в ходе данной статьи рассмотрим, как подключить такой экран, какие микросхемы контроллеров для них бывают, где такое применить. Традиционно будет много всякого интересного.
❯ Вообще
В наши дни так любимой всеми китайской промышленностью выпускается широчайшее множество дисплеев, способных подойти даже для самого навороченного проекта. Про HD44780 вообще говорить даже не стоит: с мигания светодиодиком и вывода на такой дисплей «Hello, Habrahabr World!» для многих вообще открылся весь мир микроконтроллеров. Ну, а кто-то, подключая сей артефакт к LPT, знакомился с моддингом или даже связью железа с компьютером. Ну, а ныне выпускающиеся OLED-экранчики на базе контроллера SSD1306 уже приближаются по популярности к этому алфавитно-цифровому экрану.
Казалось бы, сегментные ЖКИ должны тихо уйти в историю. Если уж не насовсем, то в любительских проектах точно. Но нет. Может быть, вам хочется оживить метеостанцию с большим экраном, добавив туда беспроводной датчик и синхронизацию по GPS? Собрать девайс, который должен будет прожить пару-тройку лет при работе от пары литиевых батареек? Нужен электронный термометр, а дисплей на HD44780 в ближайшем «Чип и Дипе» стоит слишком конских денег? А, может, у вас есть МК со встроенным контроллером ЖКИ, который в вашей схеме грех не задействовать? Во всех этих случаях сегментные экраны будут отличным выбором.
❯ Где взять дисплей
Разумеется, такой индикатор можно пойти и купить. Стоят они недорого, особенно, если сравнивать с текстовыми. На случай вашей встречи с неадекватными предложениями напомню: редко когда адекватная цена на такой дисплей превышает пятьдесят-семьдесят рублей. В таком случае частенько удаётся найти и даташит на него, что сильно облегчает подключение. А ещё они часто идут с жёсткими выводами, что освобождает вас от необходимости мучиться с разводкой переходной платы, использованием анизотропной резины для контакта, обеспечением крепления и тому подобным.
Также предмет нашего обсуждения можно изъять из помершего мультиметра, калькулятора, тетриса и тому подобного отслужившего своё электронного хлама. Тут может всплыть проблема непосредственно с организацией контакта между дисплеем и вашей схемой. Увы, дисплеи разные, вариантов подключения тоже много (через резинки, через шлейфик…), так что в данном случае помочь не смогу, придётся полагаться на то, что есть под рукой, что можно использовать для организации контакта. Впрочем, вам может повезти и вы обнаружите дисплей с выводами.
На всякий случай уточню: в данной статье речь пойдёт именно о «голых» дисплеях без контроллера (в том числе в виде chip-on-glass).
❯ Немного теории
Прежде чем мы перейдём к определению выводов дисплея, поговорим немного о том, как они вообще работают.
Сегментный ЖКИ состоит из двух стеклянных пластин, между которыми находится жидкость, обладающая жидкокристаллическими свойствами. Когда к ней приложено напряжение, плоскость поляризации проходящего через неё света меняется. Именно этот принцип и лежит в основе ЖКИ. Стеклянные пластины покрыты токопроводящим слоем, на котором вытравлены сегменты и подводящие дорожки. Такое устройство позволяет делать сегментные ЖКИ практически любой конфигурации (впрочем, производство индикаторов на заказ — не совсем дешёвое удовольствие). Спереди и сзади расположены два поляризатора, обычно представляющие собой приклеенные кусочки поляризационной плёнки.
Благодаря тому, что ЖКИ не перестаёт пропускать свет, а лишь меняет плоскость поляризации, можно изготавливать как обычные, так и инверсные экраны, всё зависит исключительно от ориентации этих плёнок. В своё время практическое применение данного эффекта демонстрировалось товарищем spiritus_sancti в давнишнем посте про проекционные часы (и даже не в одном!). Там же, кстати, описан и вариант подключения дисплея.
По способу подключения ЖКИ делятся на простые и мультиплексированные. И если с первым всё понятно и так (просто один общий вывод и куча отводов по одному от каждого сегмента), то про второй вариант поговорим поподробнее, тем более, что встречается он куда чаще (в силу экономии выводов и, соответственно, дешевизны драйвера). В какой-то степени он похож на метод подключения светодиодных дисплеев с динамической индикацией — имеются общие линии (COM) и линии сегментов (SEG). Обычно в ЖКИ малого размера используют от двух до четырёх общих линий. Число этих линий называют значением мультиплексности ЖКИ. Принцип тут несколько сложнее: надо подавать напряжение смещение и на выключенные сегменты, чтобы избежать их паразитной засветки. Дело в том, что для зажигания сегментов достаточно чрезвычайно малых уровней, так что даже наводок будет достаточно для того, чтобы погашенный сегмент засветился. Кроме этого, дисплей — по сути ещё и конденсатор, отчего отсутствие поданного напряжения смещения приведёт к тому, что погашенные сегменты на деле продолжат быть активными. Соответственно, подаваемое на дисплей напряжение должно иметь несколько уровней. Их количество в документации обычно называется Bias. Для малых сегментных дисплеев Bias обычно равен ⅓ или ¼. Если подать напряжение промежуточного значения (между напряжением гашения и напряжением индикации), можно добиться иной контрастности сегментов.
Также имеет значение продолжительность работы каждого ряда сегментов, он же Duty cycle, зависящий от степени мультиплексности ЖКИ. Он напрямую влияет на контрастность дисплея, так как именно от него зависит промежуток времени, сколько сегменты будут гореть, а сколько — не будут.
Питание дисплея осуществляется исключительно переменным напряжением. Это связано с тем, что при подаче постоянного напряжения дисплей чрезвычайно быстро деградирует (под действиям электролиза разлагаются жидкие кристаллы, разрушаются сегментные электроды). Обычно для этого на выводы COM и SEG подаётся меандр: синфазный для гашения и противофазный для зажигания.
❯ Обзор оборудования
Так получилось, что мне досталась целая куча остатков отслуживших своё теплосчётчиков Apator Powogaz ELF. Блоки с крыльчатками отправились на металлолом, а вот навершия с электроникой внутри достались мне.
В отличие от водосчётчика, теплосчётчик устроен намного сложнее. Количество затраченного тепла рассчитывается исходя из количества теплоносителя, прошедшего через него, а также разности температур на входе и выходе системы. Соответственно, нужен датчик скорости потока, два датчика температуры, а также электронная схема для управления.
Помимо катушек, платиновых термосопротивлений (PT-500), ионистора (который поддерживал питания платы во время замены элемента питания), МК MSP430 и кучки мелких деталей был, разумеется, и экранчик. Разумеется, можно было воспользоваться и местным MSP430, но в данном случае решил подключить отдельно дисплей (тем более, что таких плат у меня было больше десятка, простор для экспериментов есть).
Кстати, ионистор с «подопытной» платы по заветам товарища dlinyj послужил для оживления КПК Palm M105.
Итак, берём бокорезы с твердосплавными губками и выкусываем дисплей с платы. Пробуем подключать.
❯ Определяем распиновку
Итак, будем считать, что дисплей вы не купили, а откуда-то выдрали. Ну, или купили, но даташит на него так и не нашли. Так что самое время разобраться, какой вывод для чего служит. И сделать это куда проще, чем может показаться.
Разумеется, не стоит даже пытаться проверять дисплей по образу и подобию светодиодного индикатора — подачей постоянного напряжения. Даже небольшое его воздействие может оказаться губительным.
Есть более рациональный вариант — написать программу, которая бы генерировала на цифровых выводах контроллера противофазный меандр. После этого, прикасаясь проводами к выводам дисплея, можно определить назначение его сегментов. Но способ этот долгий и неудобный, так как при отсутствии напряжения смещения у вас непременно будет гореть целая куча сегментов.
Но есть вариант проще. Определим только линии COM и SEG, а непосредственно назначение сегментов выясним уже в программе.
Итак, берём какой-нибудь плохой импульсный блок питания. Чем сильнее он шумит в нагрузку — тем лучше. Берём дисплей двумя пальцами за края и проводим по его контактам проводом от этого БП. Второй провод никуда подключать не надо. Как можно будет заметить, наводок будет более чем достаточно, чтобы сегменты начали светиться. Теперь по очереди касаемся каждого вывода ЖКИ и смотрим, что горит. Если загорелся длинный горизонтальный (ну, или вертикальный. Мало ли какой у вас будет индикатор) ряд из сегментов, это, скорее всего, COM. Если же горит это небольшая кучка сегментов, то это, вероятно, SEG. На случай возникновения трудностей подскажу: очень часто выводы COM находятся где-то с краю дисплея. Возможно, это облегчит вам задачу.
В моём случае получилось так: первые три вывода — COM, последующие — SEG.
Для примера извлечём из щедрых закромов Родины ещё один дисплей (не помню, от чего он). Примерно так от наводок загорается ряд сегментов (COM).
А вот так вот — группа (SEG).
❯ Контроллер
В качестве контроллера ЖКИ будем использовать HT1621B. Почему именно его? Он дешёвый (в районе пятидесяти рублей), распространённый, дубовый, имеет готовые библиотеки почти для всех МК. А если готовой и нет, портировать функции для работы с ним — дело пятнадцати минут, пять из которых вы будете искать их в другой библиотеке и копировать в свою прогу. Совсем начинающие могут просто скачать библиотеку для любого семисегментного дисплея на базе такого контроллера и поменять в ней константы на свои. Для подключения HT1621B к МК требуется всего три цифровых выхода, что тоже не может не радовать.
В данном примере я подключил его к STM8, но ничего не мешает сделать то же самое с Arduino или STM32.
Кстати, некоторые МК STM8 и STM32 (в основном, L-серии) уже имеют встроенный контроллер ЖКИ. Вот и ещё один аргумент в пользу сегментных дисплеев.
Вот для примера плата STM8L-Discovery с установленной демо-прошивкой, выводящей на экран ток потребления контроллера в разных режимах. К слову говоря, купил я её в своё время именно ради экспериментов с контроллером LCD.
❯ Определяем сегменты
Как я уже говорил, нет необходимости вручную искать соответствие выводов сегментам, если в итоге можно найти это при помощи программы. Итак, собираем схему. Общие выводы припаиваем к общим, сегментные — последовательно к сегментным. От драйвера идут пять проводков: питание, земля, CS, WR, DATA.
Время писать программу. Собственно, функции для работы с контроллером выглядят примерно так:
void lcdCommand(uint8_t command) {
GPIO_WriteLow(LCD_GPIO, LCD_CS);
lcdData(0x80, 4);
lcdData(command, 8);
GPIO_WriteHigh(LCD_GPIO, LCD_CS);
}
void lcdData(uint8_t data, uint8_t cnt) {
unsigned char i;
for (i = 0; i < cnt; i++) {
GPIO_WriteLow(LCD_GPIO, LCD_WR);
delay(10);
if (data & 0x80) {
GPIO_WriteHigh(LCD_GPIO, LCD_DATA);
}
else
{
GPIO_WriteLow(LCD_GPIO, LCD_DATA);
}
GPIO_WriteHigh(LCD_GPIO, LCD_WR);
delay(10);
data <<= 1;
}
}
void lcdAddress(uint8_t addr, uint8_t data1)
{
addr <<= 2;
GPIO_WriteLow(LCD_GPIO, LCD_CS);
lcdData(0xa0, 3);
lcdData(addr, 6);
lcdData(data1, 8);
GPIO_WriteHigh(LCD_GPIO, LCD_CS);
}
Первая из них позволяет отправить команду контроллеру, вторая — непосредственно записать данные, а третья — зажечь сегмент по определённому адресу. Перед началом индикации дисплей нужно инициализировать:
void lcdStart(void) {
uint8_t i;
GPIO_DeInit(LCD_GPIO);
GPIO_Init(LCD_GPIO, LCD_CS, GPIO_MODE_OUT_PP_LOW_FAST);
GPIO_Init(LCD_GPIO, LCD_WR, GPIO_MODE_OUT_PP_LOW_FAST);
GPIO_Init(LCD_GPIO, LCD_DATA, GPIO_MODE_OUT_PP_LOW_FAST);
for(i = 0; i < 21; i++) lcdBuffer[i] = 0x00;
lcdCommand(BIAS);
lcdCommand(RC256);
lcdCommand(SYSDIS);
lcdCommand(WDTDIS1);
lcdCommand(SYSEN);
lcdCommand(LCDON);
}
»… Вы можете понять функцию каждой кнопки, понажимав их и посмотрев, что изменяется на дисплее» — говорилось в какой-то инструкции. Так и тут. Вы можете понять назначение каждого адреса, записав что-то туда и посмотрев, что изменяется на дисплее. Всё очень просто — шлём очередной байт, записываем куда-нибудь его соответствие сегменту.
Адрес Бит Значение
0 0 Символ "imp"
0 1 Сегмент "Е" первого разряда
0 2 Сегмент "G" первого разряда
0 3 Сегмент "F" первого разряда
0 4 Символ "|/"
0 5 Символ "Кран"
0 6 Цифра 1
0 7 Цифра 2
1 0 Сегмент "D" первого разряда
1 1 Сегмент "C" первого разряда
1 2 Сегмент "B" первого разряда
1 3 Сегмент "A" первого разряда
1 4 Символ "imp"
1 5 Сегмент "Е" первого разряда
1 6 Сегмент "G" первого разряда
1 7 Сегмент "F" первого разряда
2 0 Символ "/|"
2 1 Цифра 3
2 2 Цифра 4
2 3 Символ "Термометр"
2 4 Сегмент "D" первого разряда
2 5 Сегмент "C" первого разряда
2 6 Сегмент "B" первого разряда
2 7 Сегмент "A" первого разряда
3 0 Символ "b/s"
3 1 Сегмент "E" второго разряда
3 2 Сегмент "G" второго разряда
3 3 Сегмент "F" второго разряда
3 4 Символ "/|"
3 5 Цифра 3
3 6 Цифра 4
3 7 Символ "Закрашенный термометр"
4 0 Сегмент "D" второго разряда
4 1 Сегмент "C" второго разряда
4 2 Сегмент "B" второго разряда
4 3 Сегмент "A" второго разряда
4 4 Символ "b/s"
4 5 Сегмент "E" второго разряда
4 6 Сегмент "G" второго разряда
4 7 Сегмент "F" второго разряда
5 0 Символ "Градус"
5 1 Символ "Минус"
5 2 Символ "Пустой термометр"
5 3 Символ "Горизонтальная черта"
5 4 Сегмент "D" второго разряда
5 5 Сегмент "C" второго разряда
5 6 Сегмент "B" второго разряда
5 7 Сегмент "A" второго разряда
6 0 Символ "Кубометр"
6 1 Сегмент "E" третьего разряда
6 2 Сегмент "G" третьего разряда
6 3 Сегмент "F" третьего разряда
6 4 Символ "Градус"
6 5 Символ "Минус"
6 6 Символ "Пустой термометр"
6 7 Символ "Горизонтальная черта"
7 0 Сегмент "D" третьего разряда
7 1 Сегмент "C" третьего разряда
7 2 Сегмент "B" третьего разряда
7 3 Сегмент "A" третьего разряда
7 4 Символ "Кубометр"
7 5 Сегмент "E" третьего разряда
7 6 Сегмент "G" третьего разряда
7 7 Сегмент "F" третьего разряда
8 0 Точка третьего разряда
8 1 Символ "Горизонтальная черта"
8 2 Символ "График"
8 3 Символ "Пунктир"
8 4 Сегмент "D" третьего разряда
8 5 Сегмент "C" третьего разряда
8 6 Сегмент "B" третьего разряда
8 7 Сегмент "A" третьего разряда
9 0 Символ "/h"
9 1 Сегмент "E" четвёртого разряда
9 2 Сегмент "G" четвёртого разряда
9 3 Сегмент "F" четвёртого разряда
9 4 Точка третьего разряда
9 5 Символ "Горизонтальная черта"
9 6 Символ "График"
9 7 Символ "Пунктир"
10 0 Сегмент "D" четвёртого разряда
10 1 Сегмент "C" четвёртого разряда
10 2 Сегмент "B" четвёртого разряда
10 3 Сегмент "A" четвёртого разряда
10 4 Символ "/h"
10 5 Сегмент "E" четвёртого разряда
10 6 Сегмент "G" четвёртого разряда
10 7 Сегмент "F" четвёртого разряда
11 0 Символ "GJ"
11 1 Точка четвёртого разряда
11 2 Символ "Внимание"
11 3 Символ "Часы"
11 4 Сегмент "D" четвёртого разряда
11 5 Сегмент "C" четвёртого разряда
11 6 Сегмент "B" четвёртого разряда
11 7 Сегмент "A" четвёртого разряда
12 0 Символ "Две горизонтальные линии"
12 1 Сегмент "E" пятого разряда
12 2 Сегмент "G" пятого разряда
12 3 Сегмент "F" пятого разряда
12 4 Символ "GJ"
12 5 Точка четвёртого разряда
12 6 Символ "Внимание"
12 7 Символ "Часы"
13 0 Сегмент "D" пятого разряда
13 1 Сегмент "C" пятого разряда
13 2 Сегмент "B" пятого разряда
13 3 Сегмент "A" пятого разряда
13 4 Символ "Две горизонтальные линии"
13 5 Сегмент "E" пятого разряда
13 6 Сегмент "G" пятого разряда
13 7 Сегмент "F" пятого разряда
14 0 Символ "kW"
14 1 Точка пятого разряда
14 2 Символ "Гаечный ключ"
14 3 Символ "Стрелка влево"
14 4 Сегмент "D" пятого разряда
14 5 Сегмент "C" пятого разряда
14 6 Сегмент "B" пятого разряда
14 7 Сегмент "A" пятого разряда
15 0 Символ "Две горизонтальные линии"
15 1 Сегмент "E" шестого разряда
15 2 Сегмент "G" шестого разряда
15 3 Сегмент "F" шестого разряда
15 4 Символ "kW"
15 5 Точка пятого разряда
15 6 Символ "Гаечный ключ"
15 7 Символ "Стрелка влево"
16 0 Сегмент "D" шестого разряда
16 1 Сегмент "С" шестого разряда
16 2 Сегмент "B" шестого разряда
16 3 Сегмент "A" шестого разряда
16 4 Символ "Две горизонтальные линии"
16 5 Сегмент "E" шестого разряда
16 6 Сегмент "G" шестого разряда
16 7 Сегмент "F" шестого разряда
17 0 Символ "h"
17 1 Символ "h"
17 2 Символ "W"
17 3 Символ "M"
17 4 Сегмент "D" шестого разряда
17 5 Сегмент "С" шестого разряда
17 6 Сегмент "B" шестого разряда
17 7 Сегмент "A" шестого разряда
18 0 Точка шестого разряда
18 1 Сегмент "E" седьмого разряда
18 2 Сегмент "G" седьмого разряда
18 3 Сегмент "F" седьмого разряда
18 4 Символ "h"
18 5 Символ "h"
18 6 Символ "W"
18 7 Символ "M"
19 0 Сегмент "D" седьмого разряда
19 1 Сегмент "C" седьмого разряда
19 2 Сегмент "B" седьмого разряда
19 3 Сегмент "A" седьмого разряда
19 4 Точка шестого разряда
19 5 Сегмент "E" седьмого разряда
19 6 Сегмент "G" седьмого разряда
19 7 Сегмент "F" седьмого разряда
20 0 Символ "Стрелка вправо"
20 1 Символ "Рамка"
20 2 Символ "D"
20 3 Символ "Y"
20 4 Сегмент "D" седьмого разряда
20 5 Сегмент "C" седьмого разряда
20 6 Сегмент "B" седьмого разряда
20 7 Сегмент "A" седьмого разряда
Привожу эту таблицу чисто как пример того, что должно получиться у вас. Хотя, возможно, кто-то разломает такой же или похожий теплосчётчик, отчего и ему мои изыскания тоже пригодятся.
Теперь дело за малым — написать массив, в котором будут забиты значения бит для готовых семисегментных цифр. Хотя, конечно, возможно, что ваш дисплей будет показывать что-то иное, так что всё зависит только от вас.
У меня значения вышли вот такие:
uint8_t numbers[10] = {0xAF, 0x06, 0x6D, 0x4F, 0xC6, 0xCB, 0xEB, 0x8E, 0xEF, 0xCF};
❯ Пишем итоговую программу
После попытки вывести что-то обнаружилось, что нельзя просто так выбрать адрес и записать туда байт сегмента. Во-первых, есть ещё и спецсимволы, во-вторых, на соседних адресах сидят одни и те же сегменты. Поэтому в контроллер загонялось значение, модифицированное при помощи битовых операций.
Итоговая программа вышла вот такая. Её без труда можно запустить на Arduino или любом другом МК, достаточно лишь переписать вызовы функций задержки и работы с портами.
/* MAIN.C file
*
* Copyright (c) 2002-2005 STMicroelectronics
*/
#include "stm8s.h"
#define LCD_GPIO GPIOC
#define LCD_CS GPIO_PIN_3
#define LCD_WR GPIO_PIN_4
#define LCD_DATA GPIO_PIN_5
#define BIAS 0x52
#define SYSDIS 0X00
#define SYSEN 0X02
#define LCDON 0X06
#define RC256 0X30
#define WDTDIS1 0X0A
#define BUFFERSIZE 12
static void delay(uint32_t t);
static void lcdData(uint8_t data, uint8_t cnt);
static void lcdCommand(uint8_t command);
static void lcdAddress(uint8_t addr, uint8_t data1);
static void lcdStart(void);
static void lcdClear(void);
static void lcdShowNumber(uint32_t number);
static void lcdDisplay(void);
static void lcdSetSymbol(uint8_t addr, uint8_t symb);
static void lcdUnsetSymbol(uint8_t addr, uint8_t symb);
uint8_t lcdBuffer[21];
uint8_t numbers[10] = {0xAF, 0x06, 0x6D, 0x4F, 0xC6, 0xCB, 0xEB, 0x8E, 0xEF, 0xCF};
void lcdCommand(uint8_t command) { //100
GPIO_WriteLow(LCD_GPIO, LCD_CS);
lcdData(0x80, 4);
lcdData(command, 8);
GPIO_WriteHigh(LCD_GPIO, LCD_CS);
}
void lcdData(uint8_t data, uint8_t cnt) {
unsigned char i;
for (i = 0; i < cnt; i++) {
GPIO_WriteLow(LCD_GPIO, LCD_WR);
delay(10);
if (data & 0x80) {
GPIO_WriteHigh(LCD_GPIO, LCD_DATA);
}
else
{
GPIO_WriteLow(LCD_GPIO, LCD_DATA);
}
GPIO_WriteHigh(LCD_GPIO, LCD_WR);
delay(10);
data <<= 1;
}
}
void lcdAddress(uint8_t addr, uint8_t data1)
{
addr <<= 2;
GPIO_WriteLow(LCD_GPIO, LCD_CS);
lcdData(0xa0, 3);
lcdData(addr, 6);
lcdData(data1, 8);
GPIO_WriteHigh(LCD_GPIO, LCD_CS);
}
void delay(uint32_t t) {
while(--t);
}
void lcdStart(void) {
uint8_t i;
GPIO_DeInit(LCD_GPIO);
GPIO_Init(LCD_GPIO, LCD_CS, GPIO_MODE_OUT_PP_LOW_FAST);
GPIO_Init(LCD_GPIO, LCD_WR, GPIO_MODE_OUT_PP_LOW_FAST);
GPIO_Init(LCD_GPIO, LCD_DATA, GPIO_MODE_OUT_PP_LOW_FAST);
for(i = 0; i < 21; i++) lcdBuffer[i] = 0x00;
lcdCommand(BIAS);
lcdCommand(RC256);
lcdCommand(SYSDIS);
lcdCommand(WDTDIS1);
lcdCommand(SYSEN);
lcdCommand(LCDON);
}
void lcdClear(void) {
uint8_t i;
for(i = 0; i < 21; i++) {
lcdAddress(i,0);
lcdBuffer[i] = 0x00;
}
}
void lcdShowNumber(uint32_t number) {
uint8_t i = 0;
for(i = 1; i < 20; i += 3) {
if(number) lcdBuffer[i] = numbers[number % 10];
lcdBuffer[i-1] = lcdBuffer[i] << 4;
lcdBuffer[i+1] = lcdBuffer[i] >> 4;
number /= 10;
}
}
void lcdDisplay(void) {
uint8_t i = 0;
for(i = 0; i < 21; i++) lcdAddress(20-i,lcdBuffer[i]);
}
void lcdSetSymbol(uint8_t addr, uint8_t symb) {
lcdBuffer[20-addr] |= (1 << symb);
}
void lcdUnsetSymbol(uint8_t addr, uint8_t symb) {
lcdBuffer[20-addr] &= ~(1 << symb);
}
main()
{
lcdStart();
lcdClear();
lcdShowNumber(208);
lcdDisplay();
while(1);;
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t* file, uint32_t line)
{
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* Infinite loop */
while (1)
{
}
}
#endif
#define LCD_CS 5
#define LCD_WR 6
#define LCD_DATA 7
#define BIAS 0x52
#define SYSDIS 0X00
#define SYSEN 0X02
#define LCDON 0X06
#define RC256 0X30
#define WDTDIS1 0X0A
#define BUFFERSIZE 12
static void lcdData(uint8_t data, uint8_t cnt);
static void lcdCommand(uint8_t command);
static void lcdAddress(uint8_t addr, uint8_t data1);
static void lcdStart(void);
static void lcdClear(void);
static void lcdShowNumber(uint32_t number);
static void lcdDisplay(void);
static void lcdSetSymbol(uint8_t addr, uint8_t symb);
static void lcdUnsetSymbol(uint8_t addr, uint8_t symb);
uint8_t lcdBuffer[21];
uint8_t numbers[10] = {0xAF, 0x06, 0x6D, 0x4F, 0xC6, 0xCB, 0xEB, 0x8E, 0xEF, 0xCF};
void lcdCommand(uint8_t command) { //100
digitalWrite(LCD_CS,LOW);
lcdData(0x80, 4);
lcdData(command, 8);
digitalWrite(LCD_CS,HIGH);
}
void lcdData(uint8_t data, uint8_t cnt) {
unsigned char i;
for (i = 0; i < cnt; i++) {
digitalWrite(LCD_WR,LOW);
delayMicroseconds(10);
if (data & 0x80) {
digitalWrite(LCD_DATA,HIGH);
}
else
{
digitalWrite(LCD_DATA,LOW);
}
digitalWrite(LCD_WR,HIGH);
delayMicroseconds(10);
data <<= 1;
}
}
void lcdAddress(uint8_t addr, uint8_t data1)
{
addr <<= 2;
digitalWrite(LCD_CS,LOW);
lcdData(0xa0, 3);
lcdData(addr, 6);
lcdData(data1, 8);
digitalWrite(LCD_CS,HIGH);
}
void lcdStart(void) {
uint8_t i;
pinMode(LCD_CS,OUTPUT);
pinMode(LCD_WR,OUTPUT);
pinMode(LCD_DATA,OUTPUT);
digitalWrite(LCD_CS,LOW);
digitalWrite(LCD_WR,LOW);
digitalWrite(LCD_DATA,LOW);
for(i = 0; i < 21; i++) lcdBuffer[i] = 0x00;
lcdCommand(BIAS);
lcdCommand(RC256);
lcdCommand(SYSDIS);
lcdCommand(WDTDIS1);
lcdCommand(SYSEN);
lcdCommand(LCDON);
}
void lcdClear(void) {
uint8_t i;
for(i = 0; i < 21; i++) {
lcdAddress(i,0);
lcdBuffer[i] = 0x00;
}
}
void lcdShowNumber(uint32_t number) {
uint8_t i = 0;
for(i = 1; i < 20; i += 3) {
if(number) lcdBuffer[i] = numbers[number % 10];
lcdBuffer[i-1] = lcdBuffer[i] << 4;
lcdBuffer[i+1] = lcdBuffer[i] >> 4;
number /= 10;
}
}
void lcdDisplay(void) {
uint8_t i = 0;
for(i = 0; i < 21; i++) lcdAddress(20-i,lcdBuffer[i]);
}
void lcdSetSymbol(uint8_t addr, uint8_t symb) {
lcdBuffer[20-addr] |= (1 << symb);
}
void lcdUnsetSymbol(uint8_t addr, uint8_t symb) {
lcdBuffer[20-addr] &= ~(1 << symb);
}
void setup() {
lcdStart();
lcdClear();
lcdShowNumber(208);
lcdDisplay();
}
void loop() {
}
Ну что, загружаем и запускаем? Работает, однако!
❯ Вот как-то так
Итак, как мы выяснили, подключить сегментный дисплей намного проще, чем вам может показаться. А при наличии у вас МК со встроенным драйвером ЖКИ вам не понадобится даже HT1621B.
Такие дела.