Подключение 4-разрядного led индикатора всего к 4 портам микроконтроллера

Подключение 4-разрядного LED индикатора всего к 4 выводам микроконтроллера

Эта статья продолжает цикл моих публикация про организацию динамической индикации на микроконтроллерах PIC и LED индикаторах. Вот ссылки на предыдущие публикации:

Здесь будет рассмотрено экономичное подключение 4-разрядного индикатора к 4 выводам микроконтроллера (МК). Обычно, для подключения LED индикатора к микроконтроллеру через сдвиговый регистр, требуется не менее 7 выводов: три для управления регистром и четыре для управления собственно разрядами индикатора в динамическом режиме. Я же в этой статье предлагаю обойтись для этого всего четырьмя выводами МК, совместив управление регистром и разрядами. При этом, специальный алгоритм обеспечивает отсутствие влияния индикатора на работу с регистром и полное отсутствие паразитного свечения неактивных сегментов индикатора.

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

В каждом из прерываний с интервалом 2 мс (в данном случае от таймера TMR0) подготавливается один этап динамической индикации (ДИ) согласно алгоритму, который состоит из пяти фаз управления регистром и индикатором.

1-я фаза: выставлением логического 0 на входе сброса регистра, последний сбрасывается в 0. Эта фаза – единственная, при которой протекает паразитный ток через неактивные сегменты, но ее длительность сведена всего к одному такту ( для этого в программу на C специально введена ассемблерная инструкция) длительностью 1мкс (для тактовой частоты 4 МГц) при длительности одного этапа ДИ в 2000 мкс, так что свечение неактивных сегментов не заметно даже в темноте.

2-я фаза: положительный перепад на выводе 12 регистра (ST_CP) записывает нулевое состояние регистра в выходную защелку. Здесь и далее, до начала индикации, индикатор погашен нулевым потенциалом на сегментах.

3-я фаза: посредством управления выводами регистра 14 (DS – данные) и 11 (SH_CP – тактовый) в него записывается код для управления сегментами.

4-я фаза: положительным перепадом на выводе 12 регистра данные из регистра записываются в выходную защелку, причем, из-за положительных уровней на разрядах индикатор остается погашенным.

5-я фаза: здесь на выводы разрядов индикатора подается требуемый код, и далее происходит собственно индикация.

Если в схеме задействован один 4-х разрядный индикатор, то для правильной работы он должен быть с ОК. Если требуется управлять 8-ю разрядами, то используются 8 портов МК, при этом, остальные 4 порта просто управляют разрядами (в фазе 4 на них должен быть высокий уровень). Стоит отметить, что в этом случае возможно применение индикаторов как с ОК, так и с ОА, подключая к регистру соответственно сегменты или разряды (по причинам, изложенным ниже, ДИ в первом случае предпочтительно организовать посегментную, а во втором – поразрядную).

По этой методике можно подключить два четырехразрядных индикатора к МК PIC16F676, используя один сдвиговый регистр, при этом, останутся для использования целых четыре свободных вывода. Здесь, например, для такого подключения люди использовали совмещение в некоторых пинах МК функций ДИ и аналоговых входов (на мой взгляд, крайне сомнительное решение), что привело к значительному усложнению схемы и к некоторым ограничениям, о чем авторы и предупреждают. Используя мою схему подключения, все решилось бы просто и красиво – входы отдельно, индикация отдельно, плюс еще два вывода (включая MCLR) для кнопок.

Для тестирования данного способа управления предлагается следующая простая схема на МК PIC12F629 и индикаторе FYQ3641A, которое выдает на индикаторе попеременно слово «test» и число 1234.

Здесь решено применить посегментную ДИ (в каждый момент включен один сегмент, а на разрядных выводах присутствует код, где в каждом разряде: 0 – если в данном разряде должен гореть данный сегмент и 1 – в противном случае), при котором пиковые токи перекладываются на регистр. Почему? Этому есть две причины: первая – максимальная нагрузочная способность выходов 74HC595 35 мА против 25 мА у контроллеров PIC; вторая и главная – близкий к предельному ток через выходной порт МК теоретически может поднять выходной потенциал оного до уровня переключения входов регистра, что привело бы к ошибкам в работе. А так, в порты МК втекают токи 6-7 мА и на выходах потенциалы заведомо не превышают TTL-уровни.

Как упоминалось выше, интервал прерываний — 2 мс, что соответствует частоте обновления индикатора в 64 Гц и его свечение достаточно комфортно воспринимается глазом.

Данный способ ДИ, кроме всего прочего, позволил вдвое уменьшить количество токоограничительных резисторов (R2-R5).

Устройство собрано на так называемой «беспаечной» макетной плате.

Индикатор можно заменить на любой из серии 3641А.

Схема питается от стабилизированного источника, напряжением 5 В. Я использовал специальную плату-стабилизатор, предназначенный для использования совместно с упомянутой выше макетной платой.

Программа управления МК написана на языке Си и оттранслирована в среде MikroC.

Код в MikroC, проект Proteus, HEX-файл в приложении.

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

Схема

Запись команд/данных

Операция записи для 4-ех разрядной шины выглядит так:

1. установить RS (1- данные , 0 — команды)
2. вывести старшую тетраду байта данных на линии шины DB4 – DB7
3. установить E = 1
4. программная задержка 1
5. установить E = 0
6. вывести младшую тетраду байта данных на линии шины DB4 – DB7
7. установить E = 1
8. программная задержка 1
9. установить E = 0
10. программная задержка 2

Ничего сложного и можно “в лоб” написать, функцию записи команд.

void LCD_WriteCom( unsigned char data)
<
ClearBit(PORT_SIG, RS); //RS = 0

PORT_DATA = data; //выводим старшую тетраду
SetBit(PORT_SIG, EN);
_delay_us(2);
ClearBit(PORT_SIG, EN);

data = __swap_nibbles (data); //меняем тетрады местами
PORT_DATA = data; //выводим младшую тетраду
SetBit(PORT_SIG, EN);
_delay_us(2);
ClearBit(PORT_SIG, EN);
_delay_us(40);
>

void LCD_WriteCom( unsigned char data)
<
ClearBit(PORT_SIG, RS); //RS = 0

unsigned char tmp;
tmp = PORT_DATA & 0x0f;
tmp |= (data & 0xf0);

PORT_DATA = tmp; //выводим старшую тетраду
SetBit(PORT_SIG, EN);
_delay_us(2);
ClearBit(PORT_SIG, EN);

data = __swap_nibbles (data); //меняем тетрады местами
tmp = PORT_DATA & 0x0f;
tmp |= (data & 0xf0);

PORT_DATA = data; //выводим младшую тетраду
SetBit(PORT_SIG, EN);
_delay_us(2);
ClearBit(PORT_SIG, EN);
_delay_us(40);
>

В хедер добавляем

#pragma inline = forced
void LCD_CommonFunc( unsigned char data)
<
#ifdef BUS_4BIT
//код для 4-ех разрядной шины
unsigned char tmp;
tmp = PORT_DATA & 0x0f;
tmp |= (data & 0xf0);

PORT_DATA = tmp;
SetBit(PORT_SIG, EN);
_delay_us(2);
ClearBit(PORT_SIG, EN);

data = __swap_nibbles (data);
tmp = PORT_DATA & 0x0f;
tmp |= (data & 0xf0);

PORT_DATA = data;
SetBit(PORT_SIG, EN);
_delay_us(2);
ClearBit(PORT_SIG, EN);
#else
//код для 8-ми разрядной шины
PORT_DATA = data;
SetBit(PORT_SIG, EN);
_delay_us(2);
ClearBit(PORT_SIG, EN);
#endif
>

Инициализация lcd

DDRX_DATA |= 0xf0;
PORT_DATA |= 0xf0;
DDRX_SIG |= (1 //4-ех разрядная шина, 2 строки
LCD_WriteCom(0xf); //0b00001111 — дисплей, курсор, мерцание включены
LCD_WriteCom(0x1); //0b00000001 — очистка дисплея
_delay_ms(2);
LCD_WriteCom(0x6); //0b00000110 — курсор движется вправо, сдвига нет

А так будет выглядеть исправленная функции инициализации нашей библиотеки.

void LCD_Init( void )
<
#ifdef BUS_4BIT
DDRX_DATA |= 0xf0;
PORT_DATA |= 0xf0;
DDRX_SIG |= (1 //4-ех разрядная шина, 2 строки
#else
DDRX_DATA |= 0xff;
PORT_DATA |= 0xff;
DDRX_SIG |= (1 //0b00111000 — 8 разрядная шина, 2 строки
#endif
LCD_WriteCom(0xf); //0b00001111 — дисплей, курсор, мерцание включены
LCD_WriteCom(0x1); //0b00000001 — очистка дисплея
_delay_ms(2);
LCD_WriteCom(0x6); //0b00000110 — курсор движется вправо, сдвига нет
>

Чтение флага занятости lcd

Для 4-ех разрядной шины цикл чтения выглядит следующим образом

1. Сделать порт входом
2. Установить RW = 1
3. Установить RS = 0
4. Установить EN = 1
5. Програмная задержка 1
6. Считать старшую тетраду байта данных
7. Установить EN = 0
8. Установить EN = 1
9. Програмная задержка 1
10. Считать младшую тетраду байта данных
11. Установить EN = 0

Код на Си будет такой

unsigned char data;
DDRX_DATA &= 0x0f; //конфигурируем порт на вход
PORT_DATA |= 0xf0; //включаем pull-up резисторы
SetBit(PORT_SIG, RW); //RW в 1 чтение из lcd
ClearBit(PORT_SIG, RS); //RS в 0 команды

SetBit(PORT_SIG, EN);
_delay_us(2);
data = PIN_DATA & 0xf0; //чтение данных с порта
ClearBit(PORT_SIG, EN);
data = __swap_nibbles (data);
SetBit(PORT_SIG, EN);
_delay_us(2);
data |= PIN_DATA & 0xf0; //чтение данных с порта
ClearBit(PORT_SIG, EN);
data = __swap_nibbles (data);

7 бит считанного байта – будет флагом занятости BF.

Исправим функцию ожидания флага BF
Без «начинки» она будет выглядеть так:

void LCD_Wait( void )
<
#ifdef CHECK_FLAG_BF
#ifdef BUS_4BIT

//код для 4-ех разрядной шины

//код для 8-ми разрядной шины

Окончательный вариант функции LCD_Wait() будет таким:

#pragma inline = forced
void LCD_Wait( void )
<
#ifdef CHECK_FLAG_BF
#ifdef BUS_4BIT

unsigned char data;
DDRX_DATA &= 0x0f; //конфигурируем порт на вход
PORT_DATA |= 0xf0; //включаем pull-up резисторы
SetBit(PORT_SIG, RW); //RW в 1 чтение из lcd
ClearBit(PORT_SIG, RS); //RS в 0 команды
do <
SetBit(PORT_SIG, EN);
_delay_us(2);
data = PIN_DATA & 0xf0; //чтение данных с порта
ClearBit(PORT_SIG, EN);
data = __swap_nibbles (data);
SetBit(PORT_SIG, EN);
_delay_us(2);
data |= PIN_DATA & 0xf0; //чтение данных с порта
ClearBit(PORT_SIG, EN);
data = __swap_nibbles (data);
> while ((data & (1 #else
unsigned char data;
DDRX_DATA = 0; //конфигурируем порт на вход
PORT_DATA = 0xff; //включаем pull-up резисторы
SetBit(PORT_SIG, RW); //RW в 1 чтение из lcd
ClearBit(PORT_SIG, RS); //RS в 0 команды
do <
SetBit(PORT_SIG, EN);
_delay_us(2);
data = PIN_DATA; //чтение данных с порта
ClearBit(PORT_SIG, EN);
> while ((data & (1 #endif
#else
_delay_us(40);
#endif
>

Читайте также  Малогабаритная антенна св диапазона

Все. Библиотека исправлена, вот что получилось — драйвер символьного lcd.

Подключение 4-разрядного led индикатора всего к 4 портам микроконтроллера

Объяснение в любви. Подключаем LCD к микроконтроллеру по 4-х битной шине.

Автор:
Опубликовано 02.11.2005

AT90S2313 — не лучший мк, к которому нужно подключать такие дисплеи т.к. он имеет мало EEPROM и RAM, но для небольших проектов вполне сойдет. Например, прибор для измерения количества оборотов модельного воздушного винта. Да и портов ввода/вывода явно маловато, поэтому подключать его будем к порту D по параллельному 4-х битному интерфейсу. Зато у мк ATMEL сохраняется совместимость с более старшими моделями, поэтому код для подключения к старшим моделям мк практически не изменится, а при подключении по 8-ми битной шине даже упростится.

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

Схема собрана на микроконтроллере AT90S2313.

Напряжение питания — 5 вольт от стабилизированного источника. Питание подсветки расчитано на 4 вольта, но при подключении по основной, так сказать, шине на пути своём имеет токоограничивающий резистор. При превышении напряжения сгорит резистор, а не подсветка. Своеобразная защита.

Контроллер, установленный в индикаторе аналогичен HD44780 фирмы Hitachi, что позволяет заменять импортные индикаторы на отечественный аналог. Правда, по этой причине есть одно ограничение, связанное с набором русских символов. Поэтому, если удобное использование русских символов жизненно необходимо, то изучайте дисплеи с контроллерами Epson. В остальном, кодовая таблица контроллера Hitachi соответствует ASCII (Латиница и специальные символы). Есть возможность запрограммировать свои собственные 8 символов, что продемонтрированно в коде прошивки.

При отладке очень пригодились светодиоды на порту B, оставшиеся от светодиодной мигалки.

Приблизительный список команд инициализации дисплея:

  1. включить питание (ждать 30 мкс)
  2. установить разрядность интерфейса (восьмибитный режим)(0b00000011)(ждать 40 мкс)
  3. установить разрядность интерфейса (восьмибитный режим)(0b00000011)(ждать 40 мкс)
  4. установить разрядность интерфейса (восьмибитный режим)(0b00000011)(ждать 40 мкс)
  5. установить разрядность интерфейса* (восьмибитный режим, здесь происходит переход в четырехбитный режим)(0b00000010)(ждать 40 мкс)
  6. установить разрядность интерфейса (четырехбитный режим)(0b00000010,0b00001000)(ждать 40 мкс)
  7. выключить дисплей (0b00000000,0b00001000)(ждать 40 мкс)
  8. установить направление сдвига курсора и разрешить сдвиг дисплея (0b00000000,0b00000110)(ждать 40 мкс)
  9. включить индикатор и разрешить курсор, установить его тип* (0b00000000,0b00001111)(ждать 40 мкс)
  10. очистить индикатор, курсор домой (0b00000000,0b00000001)(ждать 1500 мкс)

После команд установки разрядности в 4-х битном режиме флаг занятости дисплея проверять нельзя, после всех остальных можно 🙂

Небольшое пояснение: Первые три команды установки типа интерфейса нужны для ГАРАНТИРОВАННОГО перевода ЖК индикатора в 8-ми битный режим, независимо от его состояния (при включении питания индикатор может оказаться как в 8-ми, так и в 4-х битном режиме). Четвёртой командой индикатор или переводится в 4-х битный режим, или устанавливаются правильные параметры в 8-ми битном режиме. В 4-х битном режиме индикатор начинает правильно работать с 5-й команды, которой и устанавливаются параметры.
Формат посылки: 0b00000000=0bx,A0,R/W,E,DB7,DB6,DB5,DB4.

  • Передача байта начинается установкой битов db7-db4,A0,R/W. Минимальное время предустановки 40 нс. При тактовой частоте 10МГц это занимает один такт.
  • Далее появляется сигнал E. Минимальное время выдержки 230нс. Примерно 4 такта.
  • Длительность цикла м/у фронтами E — 500нс. Итого, ещё 270нс выдерживается до следующего импульса E.
  • Время удержания данных состовляет 10нс.
  • Время выполнения команды смотрите в документации. В среднем выполнение команды занимает 40мкс, кроме команды «очистка, курсор домой» (1500мкс).

А вот так это выглядит на картинке:

Описание команд модуля можно найти в датащите на странице 7, но для ленивых:

Биты C и B в команде «Display ON/OFF control»*:

  • C=0,B=0 — курсора нет, ничего не мигает;
  • C=0,B=1 — курсора нет, мигает весь символ в позиции курсора;
  • C=1,B=0 — курсор есть (подчёркивание), ничего не мигает;
  • C=1,B=1 — курсор есть (подчёркивание) и только он и мигает.

Рекомендации, если ничего не работает

В текстовых ЖКИ «МЭЛТ» есть фича: при любом изменении состояния сигналов A0 или R/W внутренний счётчик полубайтов сбрасывается в состояние приёма старшего полубайта. Поэтому можно дёрнуть любым из этих сигналов (на >100нс), подать команду 28h (если индикатор был в 8-ми разрядном режиме, то пройдут команды 2Fh и 8Fh, что тоже устраивает), (в этот момент индикатор гарантированно будет находиться в 4-х разрядном режиме), потом снова дёрнуть сигналом A0 или R/W (для гарантированного переключения внутреннего счётчика на приём старшего полубайта), после этого подать команду включения индикатора (0Ch) — на экране должно появиться хоть что-нибудь (мусор после включения питания). По крайней мере, должна пойти развёртка — это заметно по изменению контрастности видимого поля по отношению к остальному полю ЖК индикатора.

Если развертка не появилась то, возможно, Вам придется 3 ногу завести на землю. Это управление контрастностью. Хоть в датащите и написано, что переменный резистор управления контрастностью можно не устанавливать, но мой дисплей отказался показывать что-либо без этой манипуляции. Если м/у ногами питания поставить построечный резистор 10-50кОм, а среднюю ногу завести на 3-ю дисплея, то можно регулировать контрастность.

Достаточно функциональный дисплей, хорошо юзабельный, хорошего качества. Сайт производителя: https://melt.aha.ru Группа поддержки работает хорошо, поддерживают оперативно. Спасибо тов. Петухову (наверно из группы поддержки) за оперативную помощь в поиске багов. И конечно же спасибо Сереге за помощь в написании прошивки и (а)моральную поддержку 🙂

* в датащите на момент написания статьи есть маленькая неточность

AVR Урок 26. SPI. Подключаем LED индикатор

Урок 26

SPI. Подключаем LED индикатор

Продолжаем работать с шиной SPI. На прошлом занятии мы научились работать со сдвиговым регистром 74HC595.

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

Я думаю, это будет выглядеть интереснее, чем бегущие огни по светодиодной планке.

Общий анод индикатора мы подключим к шине питания, а сегменты – к регистру через токоограничивающие резисторы вот таким вот образом

Управляющие входы регистра остались без именения подключенными таким же образом, как и в прошлом занятии.

У моего индикатора, который я подключил, вот такая вот распиновка

На практике всё выглядит приблизительно вот так

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

Если мы сейчас в протеусе включим наш проект на выполнение, то у нас будет работать прошлый код, только те сегменты, которые светились в прошлом занятии, наоборот светиться не будут, так подключены у нас светодиоды в обратном порядке и не на общую шину, а на шину питания

До бесконечного цикла код в функции main() будет вот таким

DDRB |= ((1 PORTB2 )|(1 PORTB3 )|(1 PORTB5 )); //ножки SPI на выход

((1 PORTB2 )|(1 PORTB3 )|(1 PORTB5 )); //низкий уровень

SPCR = ((1 SPE )|(1 MSTR )); //¬ключим шину, объ¤вим ведущим

while (!( SPSR & (1 SPIF ))); //подождем пока данные передадутся

//сгенерируем отрицательный фронт дл¤ записи в STORAGE REGISTER

PORTB |= (1 PORTB2 ); //высокий уровень

(1 PORTB2 ); //низкий уровень

_delay_ms (500);

То есть мы здеь оставим отправку всех нулей в регистр, и задержку оставим 500. То есть у нас должны будут зажечься все задействованные сегменты индикатора, так как он с общим анодом и активное состояние у него низкое.

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

Чтобы нам не морочить головы с кодами, мы все их возьмём из проекта по сборке часов на светодиодном четырехразрядном индикаторе из функции segchar().

Начнём с единицы

SPDR = 0b11111001;

while (!( SPSR & (1 SPIF ))); //подождем пока данные передадутся

//сгенерируем отрицательный фронт дл¤ записи в STORAGE REGISTER

PORTB |= (1 PORTB2 ); //высокий уровень

(1 PORTB2 ); //низкий уровень

_delay_ms (500);

Затем мы можем код скопировать несколько раз и подправить в нём значения, заносимые в регистр SPDR.

Вот полный код бесконечного цикла (код под спойлером, нажмите «+«)

//1

SPDR = 0b11111001;

while (!( SPSR & (1 SPIF ))); //подождем пока данные передадутся

//сгенерируем отрицательный фронт дл¤ записи в STORAGE REGISTER

PORTB |= (1 PORTB2 ); //высокий уровень

(1 PORTB2 ); //низкий уровень

_delay_ms (500);

//2

SPDR = 0b10100100;

while (!( SPSR & (1 SPIF ))); //подождем пока данные передадутся

//сгенерируем отрицательный фронт дл¤ записи в STORAGE REGISTER

PORTB |= (1 PORTB2 ); //высокий уровень

(1 PORTB2 ); //низкий уровень

_delay_ms (500);

//3

SPDR = 0b10110000;

while (!( SPSR & (1 SPIF ))); //подождем пока данные передадутся

//сгенерируем отрицательный фронт дл¤ записи в STORAGE REGISTER

PORTB |= (1 PORTB2 ); //высокий уровень

(1 PORTB2 ); //низкий уровень

_delay_ms (500);

//4

SPDR = 0b10011001;

while (!( SPSR & (1 SPIF ))); //подождем пока данные передадутся

//сгенерируем отрицательный фронт дл¤ записи в STORAGE REGISTER

PORTB |= (1 PORTB2 ); //высокий уровень

(1 PORTB2 ); //низкий уровень

//5

SPDR = 0b10010010;

while (!( SPSR & (1 SPIF ))); //подождем пока данные передадутся

//сгенерируем отрицательный фронт дл¤ записи в STORAGE REGISTER

PORTB |= (1 PORTB2 ); //высокий уровень

(1 PORTB2 ); //низкий уровень

_delay_ms (500);

//6

SPDR = 0b10000010;

while (!( SPSR & (1 SPIF ))); //подождем пока данные передадутся

//сгенерируем отрицательный фронт дл¤ записи в STORAGE REGISTER

PORTB |= (1 PORTB2 ); //высокий уровень

(1 PORTB2 ); //низкий уровень

_delay_ms (500);

//7

SPDR = 0b11111000;

while (!( SPSR & (1 SPIF ))); //подождем пока данные передадутся

//сгенерируем отрицательный фронт дл¤ записи в STORAGE REGISTER

PORTB |= (1 PORTB2 ); //высокий уровень

(1 PORTB2 ); //низкий уровень

_delay_ms (500);

//8

SPDR = 0b10000000;

while (!( SPSR & (1 SPIF ))); //подождем пока данные передадутся

//сгенерируем отрицательный фронт дл¤ записи в STORAGE REGISTER

PORTB |= (1 PORTB2 ); //высокий уровень

(1 PORTB2 ); //низкий уровень

_delay_ms (500);

//9

SPDR = 0b10010000;

while (!( SPSR & (1 SPIF ))); //подождем пока данные передадутся

//сгенерируем отрицательный фронт дл¤ записи в STORAGE REGISTER

PORTB |= (1 PORTB2 ); //высокий уровень

(1 PORTB2 ); //низкий уровень

_delay_ms (500);

//0

SPDR = 0b11000000;

while (!( SPSR & (1 SPIF ))); //подождем пока данные передадутся

//сгенерируем отрицательный фронт дл¤ записи в STORAGE REGISTER

PORTB |= (1 PORTB2 ); //высокий уровень

(1 PORTB2 ); //низкий уровень

_delay_ms (500);

SPDR = 0b01111111;

while (!( SPSR & (1 SPIF ))); //подождем пока данные передадутся

//сгенерируем отрицательный фронт дл¤ записи в STORAGE REGISTER

PORTB |= (1 PORTB2 ); //высокий уровень

(1 PORTB2 ); //низкий уровень

_delay_ms (500);

SPDR = 0b10111111;

while (!( SPSR & (1 SPIF ))); //подождем пока данные передадутся

//сгенерируем отрицательный фронт дл¤ записи в STORAGE REGISTER

PORTB |= (1 PORTB2 ); //высокий уровень

(1 PORTB2 ); //низкий уровень

//С

SPDR = 0b11000110;

while (!( SPSR & (1 SPIF ))); //подождем пока данные передадутся

//сгенерируем отрицательный фронт дл¤ записи в STORAGE REGISTER

PORTB |= (1 PORTB2 ); //высокий уровень

(1 PORTB2 ); //низкий уровень

_delay_ms (500);

//пустое место

SPDR = 0b11111111;

while (!( SPSR & (1 SPIF ))); //подождем пока данные передадутся

//сгенерируем отрицательный фронт дл¤ записи в STORAGE REGISTER

PORTB |= (1 PORTB2 ); //высокий уровень

(1 PORTB2 ); //низкий уровень

_delay_ms (500);

Соберём код, посмотрим сначала в протеусе

Если всё нормально работает, то прошьём контроллер и посмотрим результат на настоящем индикаторе

Всё работает отлично!

Благодаря нашим знаниям программирования, мы превратили вроде бы никому не нужный копеешный сдвиговый регистр в драйвер светодиодного индикатора (причём не важно, что у него общее – анод или катод), работающий по шине SPI, то есть практически по трем ножкам портов.

В следующем занятии мы попытаеся подключить два таких регистра, а к ним – четырёхразрядный светодиодный индикатор и применить динамическую индикацию, также пользуясь интерфейсом SPI.

Посмотрим, что у нас получится.

Программатор ,сдвиговые регистры и индикатор можно приобрести здесь:

Смотреть ВИДЕОУРОК (нажмите на картинку)

Динамическая индикация. Подключение светодиодной матрицы к микроконтроллеру


Иногда требуется подключить к микроконтроллеру несколько семисегментных индикаторов или светодиодную матрицу, при этом для отображения информации используется динамическая индикация. Суть динамической индикации заключается в поочередном выводе информации на индикаторы. Ниже на схеме представлен пример соединения нескольких семисегментных индикаторов (для примера с общим катодом) для реализации динамической индикации, вообще с учетом точки получается 8 сегментов, но по старинке их называют именно так. Все выводы (аноды) одноименных сегментов соединяют вместе, итого 8 линий которые через резисторы подключают к микроконтроллеру. Общий катод каждого индикатора подключают к микроконтроллеру через транзистор.

Алгоритм индикации следующий: сначала устанавливаем на линиях требуемые логические уровни в зависимости от того какие сегменты надо включить на первом индикаторе (индикация слево направо), при этом высокий логический уровень для включения, низкий для выключения сегмента. Далее подаем высокий логический уровень на базу транзистора VT1, тем самым общий катод первого индикатора подключается к общему проводу, в этот момент загораются те сегменты, на анодах которых присутствует логическая единица. Через определенное время (пауза) индикатор отключаем, подав низкий логический уровень на базу транзистора, затем снова меняем логические уровни на линиях в соответствии с выводимой информацией, предназначенной для второго индикатора, и подаем сигнал включения на транзистор VT2. Таким образом, по порядку в круговом цикле коммутируем все индикаторы, вот и вся динамическая индикация.

Для получения цельного изображения без мерцаний, переключение необходимо выполнять с большой скоростью, для исключения мерцания светодиодов частоту обновления необходимо устанавливать от 70 Гц и более, я обычно устанавливаю 100 Гц. Для вышерассмотренной конструкции пауза рассчитывается следующим образом: для частоты в 100 Гц период равен 10 мс, всего 4 индикатора, соответственно время свечения каждого индикатора устанавливаем на уровне 10/4=2,5 мс. Существуют многоразрядные семисегментные индикаторы в одном корпусе, в которых одноименные сегменты соединены внутри самого корпуса, естественно для их использования необходимо применять динамическую индикацию.

Для реализации динамической индикации необходимо воспользоваться прерываниями по переполнению одного из таймеров. Ниже представлен код с использованием таймера TMR0:

В основной программе сначала настраиваем таймер с помощью регистра OPTION_REG, ранее я рассказывал про использование таймеров для временной задержки. Далее очищаем регистр shet, предназначенный для введения счета от 1 до 4, для каждого индикатора. Этот регистр инкрементируется в подпрограмме обработки прерываний и там же корректируется (он будет считать от 1 до 4), поэтому данная очистка выполняется однократно после включения питания. По этому регистру будем определять, какой индикатор включать и выдавать данные соответствующие ему. Следующим шагом будет очистка регистров хранения информации, четыре регистра dataind1,2,3,4 соответствующие четырем индикаторам. Очистка равнозначна выключению индикаторов, так как в подпрограмме обработки прерываний, содержимое этих регистров передается в регистр PORTB, к которому подключены аноды индикаторов. Это необходимо для того чтобы на индикаторах не высвечивался всякий мусор после разрешения прерываний, в принципе этого можно и не делать, если сразу записывать правильную информацию для вывода. Далее сбрасываем флаг прерывания по переполнению таймера, разрешаем прерывания по переполнению TMR0, и наконец, разрешаем глобальные прерывания.

В подпрограмме обработки прерываний, первым делом выключаем все индикаторы (подав низкие логические уровни на базы транзисторов), потому что неизвестно какой из них включен. Производим инкремент регистра shet, с проверкой на равенство числу 5, при наличии такого совпадения записываем в регистр число 1, так как необходимо вести счет от 1 до 4. Далее проверяем, какое именно число лежит в регистре shet, по которому загружаем в PORTB данные из регистров хранения информации (dataind) для соответствующего индикатора и включаем его. После чего сбрасываем флаг прерывания по переполнению TMR0, записываем число 100 в таймер (ниже приведен расчет этого значения), для временной задержки и выходим из обработчика прерываний. При первом прерывании включается первый индикатор, во втором прерывании второй и так по круговому циклу. В основной программе остается только загружать данные в регистры хранения информации для каждого индикатора. В подпрограмме прерываний не забываем сохранять и восстанавливать значения ключевых регистров, об этом я писал в статье про прерывания.

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

Частоту тактового генератора примем за 4 МГц, машинный цикл 1 мкс. Частота обновления каждого индикатора пускай составит 100 Гц (период T=10 мс), соответственно необходимая временная задержка равна 10/4 = 2,5 мс. Коэффициент предделителя для TMR0 устанавливаем равным 1:16, при этом максимально возможная задержка равна 256х16 = 4096 мкс, а нам требуется пауза в 2,5 мс. Рассчитаем число для записи в TMR0: 256-((256х2,5)/4,096) = 256-156,25 = 99,75. После округления получим число 100.

Ниже можно скачать модель для программы Proteus, прошивку и исходник с реализацией динамической индикации на 4-х разрядный индикатор с общим катодом с применением микроконтроллера PIC16F628A. Для примера на индикатор выводятся числа 0000; 0001; 0002; 13,52; 9764.
Прошивка МК и исходник + файл проекта Proteus_7.7

Теперь рассмотрим подключение матрицы с разрешением 8х8 точек (светодиодов). Структуру матрицы обычно рассматривают в виде строк и столбцов. На картинке ниже в каждом столбце соединены катоды всех светодиодов, а в каждой строке аноды. Строки (8 линий, аноды светодиодов) через резисторы подключают к микроконтроллеру. Каждый столбец (катоды светодиодов) подключают к микроконтроллеру через 8 транзисторов. Алгоритм индикации такой же, сначала устанавливаем необходимые логические уровни на строках, в соответствии с тем, какие светодиоды должны гореть в столбце, далее подключаем первый столбец (индикация слева направо). Через определенную паузу выключаем столбец, и изменяем логические уровни на строках для отображения второго столбца, далее подключаем второй столбец. И так поочередно коммутируем все столбцы. Ниже представлена схема подключения матрицы к микроконтроллеру.

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

Самым распространенным последовательным регистром является микросхема 74НС595, которая содержит в себе сдвиговый регистр для загрузки данных, и регистр хранения через который данные передаются на выходные линии. Загружать данные в него просто, устанавливаем логический 0 на входе тактирования SH_CP, далее устанавливаем требуемый логический уровень на входе данных DS, после чего переключаем вход тактирования в 1, при этом происходит сохранение значения уровня (на входе DS) внутри сдвигового регистра. Одновременно с этим происходит сдвиг данных на один разряд. Снова сбрасываем вывод SH_CP в 0, устанавливаем требуемый уровень на входе DS и поднимаем SH_CP в 1. После полной загрузки сдвигового регистра (8 бит), устанавливаем в 1 вывод ST_CP, в этот момент данные передаются в регистр хранения и поступают на выходные линии Q0…Q7, после чего сбрасываем вывод ST_CP. Во время последовательной загрузки, данные сдвигаются от Q0 до Q7. Вывод Q7’ подключен к последнему разряду сдвигового регистра, этот вывод можно подключить на вход второй микросхемы, таким образом можно загружать данные сразу в две и более микросхемы. Вывод OE переключает выходные линии в третье (высокоомное) состояние, при подаче на него логической 1. Вывод MR предназначен для сброса сдвигового регистра, то есть установка низких логических уровней на выходах триггеров регистра, что эквивалентно загрузке восьми нулей. Ниже представлена диаграмма загрузки данных в микросхему 74НС595, установка значения 11010001 на выходных линиях Q0…Q7, при условии, что изначально там были нули:

Рассмотрим подключение матрицы 8×8 к микроконтроллеру PIC16F628A с помощью двух сдвиговых регистров 74HC595, схема представлена ниже:

Данные загружаются в микросхему DD2 (управление логическими уровнями на строках, аноды светодиодов) затем через вывод Q7’ передаются в DD3 (управление столбцами), соответственно сначала загружаем байт для включения столбца, затем байт с логическими уровнями на строках. К выходным линиям DD3 подключены транзисторы коммутирующие столбцы матрицы (катоды светодиодов). Ниже приведен код программы для вывода изображения на матрицу:

Динамическая индикация | Программирование микроконтроллеров AVR

Динамическая индикация широко применяется для отображения различной информации, например температуры, напряжения, времени или просто количества срабатывания каких-либо устройств или датчиков. Динамическая индикация на базе семисегментных индикаторов отлично согласуется в совместной работе с микроконтроллерами. Однако в литературе по программированию микроконтроллеров AVR данный вопрос рассмотрен очень поверхностно и далеко не в каждой книге, посвященной соответствующей тематике. Поэтому более подробно рассмотрим, как подключить семисегментный индикатор с динамической индикацией к микроконтроллеру, в данном случае – к ATmega8, но аналогия сохраняется для МК AVR любой серии.

По количеству разрядов (цифр) динамические семисегментные индикаторы бывают одноразрядные, двухразрядные, трехразрядные, четырехразрядные и очень редко – шестиразрядные. Основное внимание мы уделим четырехразрядным семисегментным индикаторам, как наиболее применяемому типу динамической индикации. Изготовляются они с общим анодом и общим катодом. Схемы соединения светодиодов отдельных сегментов представлены на рисунках.

Как видно из рисунков, каждый разряд, называемый digit, имеет свой отдельный общий в пределах разряда вывод. Поскольку рассматривается 4-х разрядная динамическая индикация, то таких выводы четыре – digit1, digit2, digit3, digit4.

Распиновка выводов 4-х разрядного семисегментного индикатора приведена на рисунке ниже. В данном случае показан вид сверху, то есть индикатор не нужно переворачивать вверх ногами.

Как работает динамическая индикация

Теперь рассмотрим, как работает динамическая индикация с общим катодом. Например, нам необходимо отобразить число 1987. Для этого следует в первый момент времени подать высокий потенциал на аноды сегментов, образующих единицу – b и c, а на общий катод первого разряда подать низкий потенциал. Общие катоды оставшихся трех разрядов – digit2, digit3 и digit4 остаются не подключенными.

Во второй момент времени получают питания сегменты, образующие цифру 9, общий катод второго разряда подключается к минусу, а digit1 теряет питание; digit2, digit3, как и раннее – остаются не подключенными.

В третий момент времени засвечивается цифра 8 на третьем индикаторе, а остальные индикаторы гаснут.

В четвертый момент времени получает питание последний индикатор и отображается цифра 7.

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

Схема подключения динамической индикации к микроконтроллеру ATmega8

Сегменты динамической индикации будем подключать через токоограничивающие резисторы номиналом 330 Ом к выводам порта D микроконтроллера ATmega8. Выводы, отвечающие digit1, digit2, digit3, digit4 подсоединим через транзисторы n-p-n тип, например BC547 или 2n2222 к выводам порта B.

Если применять динамическую индикацию с общим анодом, тогда понадобятся биполярные транзисторы p-n-p типа, например BC557, эмиттеры которых нужно подсоединить к плюсу «+» источника питания, а коллекторы – к минусу «-» также через подтягивающий резистор 10 кОм. Принцип работы и подробные расчет транзисторного ключа описан ранее.

Алгоритм написания кода для подключения динамической индикации

Для большей конкретизации действий будем применять 4-х разрядный семисегментный индикатор с общим катодом. Первым делом следует создать массив цифр от 0 до 9. Этому мы уже научились ранее, вот здесь. Далее необходимо разбить 4-х значное число на четыре отдельных цифры. Например, число 1987 нужно разбить на 1, 9, 8 и 7. Затем единицу нужно отобразить в первом разряде индикатора, девятку – во втором, восьмерку – в третьем и семерку – в четвертом.

Среди многих алгоритмов разбивки многозначного числа на отдельные числа мы воспользуемся операциями деления и остатком от деления. Рассмотрим пример:

В языке С при использовании целочисленного типа данных int при выполнении деления все десятые, сотые и т. д., то есть все числа меньше единицы отбрасываются. Остаются только целые числа. Математическое округление здесь не работает, то есть 1,9 в данном случае будет 1, а не 2.

Команда “остаток от деления” обозначается знаком процента «%». Данная команда отбрасывает все целые числа и оставляет остальную часть числа. Например, 1987%1000 → 987; 1987%100 → 87; 1987%10 → 7.

Далее следует написать команду, которая сначала отобразит первый разряд и соответствующее ему число, потом, через некоторый промежуток времени, второй разряд и отвечающее ему число; и так далее. Ниже приведен код с комментариями.

#define F_CPU 1000000L

#include

#define CHISLO PORTD

#define RAZRIAD PORTB

unsigned int razr1 = 0, razr2 = 0, razr3 = 0, razr4 = 0;

unsigned int chisla [10] = <

// числа от 0 до 10

0x3f, 0x6, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x7, 0x7f, 0x6f

void vse_chislo (unsigned int rabivka_chisla)

razr1 = rabivka_chisla/1000; // тысячи

razr2 = rabivka_chisla%1000/100; // сотни

razr3 = rabivka_chisla%100/10; // десятки

razr4 = rabivka_chisla%10; // единицы

int main(void)

DDRB = 0b00001111;

DDRD = 0b11111111;

RAZRIAD = 0b00000001; // изначально 1-й разряд

CHISLO = 0x3f; // число 0

while (1)

vse_chislo(1987); // отображаемое число

RAZRIAD = 0b00000001; // включаем 1-й разряд, остальные выключаем

CHISLO = chisla [razr1]; // отображаем 1-ю цифру

_delay_ms(3);

RAZRIAD = 0b00000010; // включаем 2-й разряд, остальные выключаем

CHISLO = chisla [razr2]; // отображаем 2-ю цифру

_delay_ms(3);

RAZRIAD = 0b00000100; // включаем 3-й разряд, остальные выключаем

CHISLO = chisla [razr3]; // отображаем 3-ю цифру

_delay_ms(3);

RAZRIAD = 0b00001000; // включаем 4-й разряд, остальные выключаем

CHISLO = chisla [razr4]; // отображаем 4-ю цифру

_delay_ms(3);

Улучшаем программу для работы динамической индикации

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

Для того, чтобы при каждом прерывании числа отображались последовательно в каждом разряде индикатора, добавлена переменная bc547, которая увеличивается на единицу при последующем вызове прерывания ISR (TIMER0_OVF_vect). Затем выполняется проверка значения переменной bc547 и получает питания соответствующий разряд. Когда bc547 становится больше четырех, происходит сброс в единицу.