Содержание
- Назначение и принцип работы
- Основные характеристики сдвиговых регистров 74HC595 и 74HC165
- Выходной сдвиговый регистр 74HC595, подключение к Ардуино, примеры использования
- Входной сдвиговый регистр 74РС165, подключение к Ардуино, примеры использования
- Выводы
- FAQ
Назначение и принцип работы
У нас есть светодиод или реле, подключенное к Arduino. Мы хотим, чтобы он или оно включалось и выключалось, когда надо, по команде от нашей программы. Для этого мы подключаем его на любой свободный пин, указываем номер пина в программе, инициализируем его и пользуемся. Теперь представим, что светодиода два, нет, лучше десять, и еще десять реле, а еще десять кнопок и штук пять энкодеров, и все это не считая остального - датчиков, дисплеев и так далее. Ардуино Уно, с ее 20 пинами, уже давно не хватает и заканчиваются ножки на Меге. Но тут появилась задача добавить в проект десяток семисегментных индикатора, пять шаговиков, каждому из которых необходимо еще по два пина, и пару джойстиков, чтобы всем мог управлять оператор. Количество требуемых ножек перевалило за сотню, а в планах еще много чего. Да, если пойти по пути развития и усложнения своих проектов, не довольствуясь одним только blink, рано или поздно такое случится, пинов под все нужды может не хватить даже у самого большого контроллера-многоножки.
Для простого и надежного решения проблемы с дефицитом портов ввода-вывода и придуманы так называемые сдвиговые регистры - микросхемы, превращающие всего три пина Ардуино в десятки и сотни, преобразовывая последовательный интерфейс (список того, что включить, а что выключить) в параллельный (включено-выключено на каждой ножке) или наоборот.
В природе существуют много технических исполнений сдвиговых регистров, мы здесь рассмотрим два самых распространенных: 74HC595 - выходной, и 74HC165 - входной. Как нетрудно догадаться по названию, они специализированы либо на вход, либо на выход. Каждый из них представляет из себя микросхему с 16 ножками:
Оба восьмиразрядны, то есть у 74HC595 восемь выходов, у 74HC165, соответственно, восемь входов. Где же обещанные десятки и сотни, спросите вы? И я отвечу - у регистров есть замечательная способность соединяться как вагоны в поезде, передавая данные друг другу по цепочке. На языке схемотехники это называется “каскад”. Максимальное количество регистров в каскаде теоретически не ограничено и при правильном соединении их можно стыковать очень и очень много - скорее всего, раньше закончится память у Ардуино или фантазия и терпение у изготовителя.
Почему популярны именно эти сдвиговые регистры? Они просты, удобны, быстры, надежны, дешевы, их легко найти в продаже. В случае чего их легко заменить и не жалко выбросить.
Основные характеристики сдвиговых регистров 74HC595 и 74HC165
- Разрядность параллельного входа (выхода) - 8 бит
- Фактор и количество пинов - DIP-16 и SO-16
- Количество сигнальных линий для передачи данных - 3
- Время установки - 20 нс
- Максимальная частота - 100 МГц
- Напряжение питания - 5 В
- Ток потребления - 40 мА
Выходной сдвиговый регистр 74HC595, подключение к Ардуино, примеры использования
Схема расположения ножек у регистра 74HC595 выглядит так:
Где:
- Q0-Q7 - выходы разрядов
- VCC - плюс питания
- GND - общий (земля)
- DS - вход последовательных данных (DATA)восемь бит: единица - включить, ноль - выключить
- ST-CP - тактовый вход регистра сдвига (CLOCK), тактирование занимает отдельную линию, однако позволяет не привязываться к строгим таймингам, что значительно повышает надежность и помехоустойчивость передачи данных, а также позволяет работать с любой скоростью, которую обеспечивает программа и контроллер
- ST-CP - тактовый вход регистра хранения, так называемая “защелка” (LATCH), позволяет (после сессии передачи данных) устанавливать уровни на всех восьми выходных ножках одновременно
- Q7 - выход переноса, к нему подключается входной DS следующего регистра каскада, тактовые сигналы подключаются ко всем регистрам параллельно.
Схема подключения к Ардуино:
Пины Ардуино 10, 11 и 12 выбраны для дальнейших примеров, вообще они назначаются программно и могут быть любыми. На схеме показано каскадное соединение двух микросхем, дальнейшее соединение производится аналогично.
Очень кратко принцип работы регистра можно описать так: у нас имеется байт для каждого регистра в каскаде (в примере будет всего один, для остальных надо лишь повторить процедуру нужное количество раз, начиная с самого дальнего), открываем “защелку” LATCH (один раз на весь каскад), передаем данные по линии DATA синхронно с тактовыми импульсами CLOCK, закрываем “защелку” (заканчивая передачу всей цепочки данных, если регистров несколько). После “защелкивания” состояние каждого бита переданного байта будет моментально установлено на соответствующих выходящих ножках регистра - Q0-Q7. Например, байт “0b01101001” установит ножку Q0 в единицу (то есть сделает на ней +5В), на ножках Q1 и Q2 - ноль, Q3 в единицу и так далее. Напомню, что нулевой бит находится у байта справа, седьмой слева.
Щелкать защелкой, тактировать импульсы и передавать данные можно несколькими способами, пару из которых мы здесь рассмотрим.
Самый простой, он же самый медленный. При помощи встроенной в среду Ардуино функции shiftOut():
#define DATA_PIN 10 // пин данных #define LATCH_PIN 11 // пин защелки #define CLOCK_PIN 12 // пин тактов синхронизации byte b[6] = { // байты, который будут последовательно циклически выводиться в регистре 0b00000000, 0b11111111, 0b11110000, 0b00001111, 0b10101010, 0b01010101, }; void setup() { pinMode(DATA_PIN, OUTPUT); // инициализация пинов pinMode(CLOCK_PIN, OUTPUT); pinMode(LATCH_PIN, OUTPUT); digitalWrite(LATCH_PIN, HIGH); } void loop() { static byte i = 0; out_595_shift(b[i]); // передача байта на регистр i = i == 5 ? 0 : i + 1; // подготовка следующего байта delay(1000); // задержка между установками 1 сек } void out_595_shift(byte x) { digitalWrite(LATCH_PIN, LOW); // "открываем защелку" shiftOut(DATA_PIN, CLOCK_PIN, LSBFIRST, x); // отправляем данные digitalWrite(LATCH_PIN, HIGH); // "закрываем защелку", выходные ножки регистра установлены }
В качестве результата наблюдаем вот такую картину:
Давайте посмотрим как выглядит сигнал в реальности.
Верхняя линия - защелка LATCH, средняя - DATA, нижняя - синхроимпульс CLOCK. Хорошо видно, как открылась защелка, падением из единицы в ноль, как восемь раз тикнули синхроимпульсы с разным значением данных при них. Наблюдательный читатель уже догадался, что на данной картинке изображена передача байта “0b01010101”.
Полное время отправки данных, как видно по стрелкам сверху, чуть более 101 мкс, то есть примерно одна десятитысячная секунды. Казалось бы, очень быстро! Для большинства случаев так и есть, нетрудно подсчитать, что для установки каскада из 100 регистров (а это 800 независимых выводов) потребуется всего лишь одна сотая секунды! Но у нас случился приступ перфекционизма и мы хотим попробовать улучшить результат. Для этого заменим стандартные digitalWrite() на прямое обращение к порту контроллера. Ножки 10, 11 и 12, это 2, 3 и 4 бит порта B контроллера Atmega328, который установлен в Arduino UNO, пример будет для него, для других пинов и/или Arduino MEGA надо будет сделать поправки. Итак:
#define DATA_PIN 2 // номера битов порта, не пины! #define LATCH_PIN 3 #define CLOCK_PIN 4 byte b[6] = { 0b00000000, 0b11111111, 0b11110000, 0b00001111, 0b10101010, 0b01010101, }; void setup() { DDRB = 0b00011100; // инициализация пинов PORTB = 0b00001000; } void loop() { static byte i = 0; out_595_port(b[i]); // поочередно выводим байты i = i == 5 ? 0 : i + 1; delay(1000); } void out_595_port(byte x) { // функция вывода данных в регистр for (int i = 0; i <= 7; i++) { if (bitRead(byteW, i)) { PBdigWH(DATA_PIN); } else { PBdigWL(DATA_PIN); } PBdigWH(CLOCK_PIN); PBdigWL(CLOCK_PIN); } } inline void PBdigWH(byte NB) { PORTB |= 1 << NB; } inline void PBdigWL(byte NB) { PORTB &= ~(1 << NB); }
Ого! Время передачи уменьшилось более чем в пять раз! Стало намного лучше. Но стоп, почему периоды между синхроимпульсами не одинаковы и увеличиваются с каждым тактом? Дело в единственной оставшейся в программе от Ардуино функции bitRead(). Она должна возвращать конкретный бит из байта, но, судя по результатам, делает это тем дольше, чем дальше бит от нулевой позиции. В принципе, все работает хорошо и быстро, но так как волна перфекционизма еще не откатилась окончательно, попытаемся убрать из программы эту странную функцию, окончательно заменив чистой двоичной логикой:
#define DATA_PIN 2 #define LATCH_PIN 3 #define CLOCK_PIN 4 byte b[6] = { 0b00000000, 0b11111111, 0b11110000, 0b00001111, 0b10101010, 0b01010101, }; void setup() { DDRB = 0b00011100; PORTB = 0b00001000; } void loop() { static byte i = 0; out_595_port(b[i]); i = i == 5 ? 0 : i + 1; delay(1000); } void out_595_port(byte x) { PBdigWL(LATCH_PIN); byte ii = 0b00000001; for (int i = 0; i <= 7; i++) { if (ii & x) { PBdigWH(DATA_PIN); } else { PBdigWL(DATA_PIN); } ii <<= 1; PBdigWH(CLOCK_PIN); PBdigWL(CLOCK_PIN); } PBdigWH(LATCH_PIN); } inline void PBdigWH(byte NB) { PORTB |= 1 << NB; } inline void PBdigWL(byte NB) { PORTB &= ~(1 << NB); }
Светодиоды мигают, на глаз все так же, смотрим на сигнал:
Идеально! Тактирование ровное, и время уменьшилось еще в два с лишним раза, теперь регистр получает команду всего лишь за 8 мкс, что быстрее стандартного в 12 раз! Для каскада из 100 регистров на передачу данных потребуется менее миллисекунды, иными словами 800 светодиодов могут мелькать с частотой порядка 1 КГц. Впечатляет, не правда ли?
Для закрепления и разнообразия, выполним этим же способом классический бегущий огонек:
#define DATA_PIN 2 #define LATCH_PIN 3 #define CLOCK_PIN 4 void setup() { DDRB = 0b00011100; PORTB = 0b00001000; } void loop() { static byte i = 0b00000001; out_595_port(i); if (i == 0b10000000) { i = 0b00000001; } else { i <<= 1; } delay(100); } void out_595_port(byte x) { PBdigWL(LATCH_PIN); byte ii = 0b00000001; for (int i = 0; i <= 7; i++) { if (ii & x) { PBdigWH(DATA_PIN); } else { PBdigWL(DATA_PIN); } ii <<= 1; PBdigWH(CLOCK_PIN); PBdigWL(CLOCK_PIN); } PBdigWH(LATCH_PIN); } inline void PBdigWH(byte NB) { PORTB |= 1 << NB; } inline void PBdigWL(byte NB) { PORTB &= ~(1 << NB); }
Смотрим:
Отлично!
Если все еще не рассеялся вопрос, а зачем нам мелькать сотнями светодиодов, приведу простой пример другого применения. Так называемые “часы из часов”, по сути, это от нескольких десятков до нескольких сотен шаговых двигателей, соединенных в одну сложную систему.
Причем каждому шаговику нужно как минимум два управляющих сигнала: направление и тактирование. Применение выходных сдвиговых регистров - самый очевидный и реальный путь для решения подобной задачи.
Входной сдвиговый регистр 74РС165, подключение к Ардуино. Примеры использования
Несмотря на то, что задача у регистра “165-ого” прямо противоположная “595-ому”, принцип работы и использование у них очень схожи. Вместо выходов у него входы, а значит к ним подключаются не исполнительные устройства, а бинарные датчики, вроде кнопок, герконов, энкодеров, оптронов и тому подобных. Входные “165-ые” тоже могут соединяться каскадами и собирать данные сотнями штук в мгновение ока. Порядок работы отличается лишь тем, что “защелка” единожды открывается и тут же закрывается, что знаменует начало приема данных, далее каждым синхроимпульсом поднимаем в канале DATA состояние очередного бита из памяти регистра или регистров, если их несколько. Таким образом, тактируя канал CLOCK и считывая биты, мы собираем картину состояния ножек всех регистров в каскаде.
Обратите внимание, что схема подключения сильно отличается, от выходящего регистра на своих местах остались лишь ножки питания, все остальное расположено иначе:
На схеме нарисованы два регистра, соединенных в каскад, остальные подключаются аналогично.
В примере рассмотрим считывание состояния восьми кнопок, подключенных к одному регистру. Как и в предыдущем случае, сделать это можно несколькими путями, рассмотрим два. Первый стандартный, через функцию shiftIn(), аналогичной shiftOut(), но действующей в противоположную сторону:
#define DATA_PIN 10 // пин данных #define LATCH_PIN 11 // пин защелки #define CLOCK_PIN 12 // пин тактов синхронизации void setup() { pinMode(DATA_PIN, INPUT); // инициализация пинов pinMode(CLOCK_PIN, OUTPUT); pinMode(LATCH_PIN, OUTPUT); digitalWrite(LATCH_PIN, HIGH); Serial.begin(9600); } void loop() { byte b = in_165_shift(); // считываем Serial.println(b, BIN); // выводим результат в COM-порт delay(1000); // пауза 1 сек } byte in_165_shift() { digitalWrite(LATCH_PIN, LOW); // щелкнули защелкой digitalWrite(LATCH_PIN, HIGH); return shiftIn(DATA_PIN, CLOCK_PIN, MSBFIRST); // считали данные }
Традиционно смотрим как это выглядит в реальности и сколько занимает времени:Все как задумано, прекрасно работает и длится 120 мкс.
Для наглядности, чтобы был виден порядок работы, можно заменить функцию shiftIn(), разбив ее на обычные digitalWrite() и digitalRead(), тогда наша функция считывания байта будет выглядеть так:
byte readByte() { byte byteR = 0; digitalWrite(LATCH_PIN, LOW); // защелка digitalWrite(LATCH_PIN, HIGH); for (int i = 7; i >= 0; i--) { // считываем побитно 8 раз if (digitalRead(DATA_PIN)) bitSet(byteR, i);// читываем бит, вставляем в байт digitalWrite(CLOCK_PIN, HIGH); // синхроимпульс digitalWrite(CLOCK_PIN, LOW); } return byteR; }
Что примечательно, время считывания в таком случае даже немного, но уменьшится.
Но, несмотря на то, процесс происходит довольно быстро и для большинства применений этого будет более чем достаточно, мы вновь чувствуем прилив перфекционизма к рукам, пишем то же самое напрямую через порты, причем сразу собирая байт бинарными элементалями:
#define DATA_PIN 2 #define LATCH_PIN 3 #define CLOCK_PIN 4 void setup() { DDRB = 0b00011000; PORTB = 0b00001000; Serial.begin(9600); } void loop() { byte b = in_165_port(); Serial.println(b, BIN); delay(1000); } byte in_165_port() { byte b = 0; PBdigWL(LATCH_PIN); PBdigWH(LATCH_PIN); byte ii = 0b00000001; for (int i = 0; i <= 7; i++) { PBdigWH(CLOCK_PIN); PBdigWL(CLOCK_PIN); if (PINB & (1 << DATA_PIN)) b |= ii; //b |= (1 << i); ii <<= 1; } return b; } inline void PBdigWH(byte NB) { PORTB |= 1 << NB; } inline void PBdigWL(byte NB) { PORTB &= ~(1 << NB); }
Предвкушая, снимаем показания и смотрим результат:
Предчувствия не обманули, разница впечатляющая, стало быстрее аж в 22 раза - 5,4 мкс против 120! Благодаря такой скорости можно успевать отслеживать огромные массивы событий с очень большим разрешением. Например, сто регистров (800 датчиков) вполне реально опрашивать с частотой около 2 тысяч раз в секунду. Даже трудно представить, для чего потребуется еще быстрее.
Несколько слов о совместном использовании входного и выходного регистра. Для динамического опроса двух стандартных матричных клавиатур 4х4 потребуется всего по одному из каждых регистров.
При некоторой ловкости, на оба можно задействовать всего 4 пина Ардуино, LATCH у каждого регистра свой, а DATA и CLOCK общие. Поочередно оперируя “защелками”, устанавливаем комбинацию на “595-ом” и считываем на “165-ом”, не забывая менять настройку DATA с OUTPUT на INPUT, разумеется. На одну установку четыре считывания, итого 20 операций на обе клавиатуры для полного сканирования. Весь цикл опроса 32 кнопок укладывается в 120 мкс.
Выводы
Сдвиговые регистры - это доступный инструмент для легкого и почти безграничного увеличения количества цифровых входов и выходов контроллера. Уметь пользоваться им должен каждый уважающий себя DIY-мастер, рано или поздно это обязательно пригодится.
Должен напомнить, мы рассмотрели самые популярные и, вероятно, простые микросхемы из этого семейства. Существуют более сложные решения, в которых назначения пинов универсальны и изменяются программно прямо на ходу, могут самостоятельно генерировать ШИМ, обрабатывать аналоговые сигналы и многое другое. Они используют разные интерфейсы и протоколы связи, что сложнее в понимании и использовании, но изучить их, имея опыт работы со старыми добрыми 74HC595 и 75РС165, будет уже намного проще.
FAQ
1. Не ухудшает ли повышение скорости качество передачи данных?Нет, сигнал синхронизируется тактированием, если в линии не будет очень сильных помех, вызванных плохими контактами, неправильной разводкой и тому подобными ошибками, вероятность искажения минимальна. 2. Можно ли использовать два и более каскада регистров на одном контроллере? Конечно. Иногда это это удобно, если каскады выполняют разные задачи и с разной частотой. Для экономии пинов, линии DATA и CLOCK можно использовать общие, обращаясь к нужному каскаду лишь при помощи LATCH. Более того, можно одновременно использовать разноименные регистры, о чем написано выше. 3. Можно ли использовать выходящие регистры для динамической индикации? И даже нужно, если элементов индикатора более четырех. У стандартного четырехразрядного индикатора от 11 до 13 ножек: 7 сегментов, 1-3 разделительные точки и 4 общих катода (или анода) для каждого разряда. Двух регистров хватит с запасом. Примечание: почти все драйверы модулей индикаторов являются сдвиговыми регистрами. У светодиодных матриц ножек еще больше, особенно многоцветных, без регистров вообще не обойтись. 4. Получится или нет генерировать ШИМ на ножках регистра? Нет препятствий, но нужно помнить, что максимальная частота ограничена скоростью обновления (для одного регистра это порядка 100 КГц) и что контроллер будет занят этим процессом постоянно. Для этой цели лучше использовать специализированные ШИМ-генераторы. 5. Управляется ли сдвиговый регистр при помощи аппаратного SPI?
Да, то что мы делали, является программной имитацией SPI-интерфейса. Но аппаратный SPI на Ардуино всего один и привязан к конкретным пинам, лучше его держать свободным для других целей, описанные в этой статье решения более универсальны и наглядны.