Рассмотрим классический сдвиговый регистр 74HC595, модель M74HC595B1 от STMicroelectronics. По сути, это преобразователь последовательного интерфейса в параллельный: получает данные по SPI, а потом разом выставляет уровни на 8 ножках согласно полученным битам. Биты, выставляемые ведущим на выводе SI, проталкиваются по цепочке D-триггеров с каждым тактовым импульсом (от ведущего) на ноге SCK. Одновременный вывод на ножки параллельного интерфейса обеспечивается так называемой защёлкой (latch) RCK, которая «не пускает» переданные биты на выводы раньше времени. Вывод G управляет состоянием выводов — включает их либо переводит в состояние Hi-Z:
А вот и сам регистр в DIP-корпусе:
Выводы микросхемы имеют следующее назначение:
Vcc — питание, от 2 до 6 В
GND — земля
QA-QH — эти выводы соответствуют битам, записанными по SPI
SI — вход ведомого, MOSI (SPI)
G — Output Enabe; когда на этом выводе низкий уровень, выводы включены (подключены к «защёлкам»), когда высокий — выводы переходят в состояние Hi-Z
RCK — защёлка, SS (SPI); при установке низкого уровня выводы регистра защёлкиваются
SCK — тактовый вход, SCLK (SPI)
SCLR — Shift Register Clear Input; если на этом выводе низкий уровень, очищает все триггеры по фронту тактового сигнала на SCLK. С нашей точки зрения это банальный RESET: прижал к земле — сбросил все биты регистра
QH' — на этом выводе будет появляться старший переданный бит
Для начала попробуем помигать светодиодами через сдвиговый регистр. Для этого подключим выводы по следующей схеме:
Vcc ⇨ +5 В
GND ⇨ GND
QA-QH ⇨ светодиоды через резисторы на 510 Ом
SI ⇨ пин 11 Arduino (MOSI)
G ⇨ GND (выводы включены)
RCK ⇨ пин 8 (SS)
SCK ⇨ пин 13 (SCLK)
SCLR ⇨ +5 В (сброс неактивен)
QH' оставим неподключенным
У меня получилось так:
Мигать будем не как в классическом скетче Blink, а пробегая огоньком по линейке светодиодов, начиная с первого. Для этого напишем следующий код:
#include <SPI.h>
enum { REG_SELECT = 8 }; // пин, управляющий защёлкой (SS в терминах SPI)
void setup()
{
/* Инициализируем шину SPI. Если используется программная реализация,
* то вы должны сами настроить пины, по которым будет работать SPI.
*/
SPI.begin();
pinMode(REG_SELECT, OUTPUT);
digitalWrite(REG_SELECT, LOW); // выбор ведомого - нашего регистра
SPI.transfer(0); // очищаем содержимое регистра
/* Завершаем передачу данных. После этого регистр установит
* на выводах QA-QH уровни, соответствующие записанным битам.
*/
digitalWrite(REG_SELECT, HIGH);
}
/* Эта функция сдвигает биты влево на одну позицию, перемещая старший бит
* на место младшего. Другими словами, она "вращает" биты по кругу.
* Например, 11110000 превращается в 11100001.
*/
void rotateLeft(uint8_t &bits)
{
uint8_t high_bit = bits & (1 << 7) ? 1 : 0;
bits = (bits << 1) | high_bit;
}
void loop()
{
static uint8_t nomad = 1; // это наш бегающий бит
/* Записываем значение в сдвиговый регистр */
digitalWrite(REG_SELECT, LOW);
SPI.transfer(nomad);
digitalWrite(REG_SELECT, HIGH);
/* И вращаем биты влево - в следующий раз загорится другой светодиод */
rotateLeft(nomad);
delay(1000 / 8); // пробегаем все 8 светодиодов за 1 секунду
}
Здорово. А если нам нужно больше выводов? Можно подсоединить ещё один сдвиговый регистр к той же шине SPI:
Как-то так, например:
Здесь у обоих регистров линии SCLK и MOSI общие, но у каждого своя линия SS. В результате мы получаем независимое управление двумя регистрами по одной шине SPI. Код аналогичен первому примеру:
#include <SPI.h>
enum { REG1 = 8, REG2 = 7 };
/* Копипаста не для нас, писать в регистр теперь будем так */
void writeShiftRegister(int ss_pin, uint8_t value)
{
digitalWrite(ss_pin, LOW);
SPI.transfer(value);
digitalWrite(ss_pin, HIGH);
}
void setup()
{
SPI.begin();
/* Всё то же, что и в первом примере, только для двух регистров */
pinMode(REG1, OUTPUT);
pinMode(REG2, OUTPUT);
writeShiftRegister(REG1, 0);
writeShiftRegister(REG2, 0);
}
void rotateLeft(uint8_t &bits)
{
uint8_t high_bit = bits & (1 << 7) ? 1 : 0;
bits = (bits << 1) | high_bit;
}
void rotateRight(uint8_t &bits)
{
uint8_t low_bit = bits & 1 ? (1 << 7) : 0;
bits = (bits >> 1) | low_bit;
}
void loop()
{
static uint8_t nomad1 = 1, nomad2 = 0x80;
writeShiftRegister(REG1, nomad1);
rotateLeft(nomad1);
writeShiftRegister(REG2, nomad2);
/* Для разнообразия погоняем биты во втором регистре в обратную сторону */
rotateRight(nomad2);
delay(1000 / 8);
}
Но и это ещё не всё, как любят говорить в «магазинах на диване» — есть ещё каскадное подключение сдвиговых регистров. При таком подключении биты из первого регистра будут проталкиваться в следующий в каскаде регистр, а из него — в следующий, и так далее. Таким образом, каскад из двух 8-битных регистров будет работать, как один 16-битный, а каскад из 10 регистров — как один 80-битный (схему можно печатать на рулонах — получится оригинальный подарок электронщику).
Мы не мажоры, нам хватит и 16-битного. Нужно подсоединить вывод QH' первого регистра к пину SI (MOSI) второго, и оба регистра повесить на общую линию SS, чтобы активировать их оба за раз:
#include <SPI.h>
enum { REG = 8 };
/* Теперь шлём по 16 бит. Важный момент: так как по умолчанию
* данные передаются, начиная со старшего бита, сначала нужно
* послать старший байт, затем - младший - тогда всё 16 бит
* передадутся в правильном порядке.
*/
void writeShiftRegister16(int ss_pin, uint16_t value)
{
digitalWrite(ss_pin, LOW);
/* Фокус вот в чём: сначала шлём старший байт */
SPI.transfer(highByte(value));
/* А потом младший */
SPI.transfer(lowByte(value));
digitalWrite(ss_pin, HIGH);
}
void setup()
{
SPI.begin();
pinMode(REG, OUTPUT);
writeShiftRegister16(REG, 0);
}
/* Слегка изменим функцию для работы с 16-битными значениями */
void rotateLeft(uint16_t &bits)
{
uint16_t high_bit = bits & (1 << 15) ? 1 : 0;
bits = (bits << 1) | high_bit;
}
void loop()
{
static uint16_t nomad = 1;
writeShiftRegister16(REG, nomad);
rotateLeft(nomad);
delay(1000 / 8);
}
Заметьте, в функции writeShiftRegister16() сначала пишется старший байт — это потому что мы используем порядок бит MSBFIRST. Если смените порядок бит на LSBFIRST, придётся функцию поменять — слать сначала младший байт.
Исходники примеров доступны для скачивания напрямую и на GitHub в RoboCraft:
Спасибо за статью, расширила мой кругозор.
Пока делал по ней столкнулся с неожиданностью: на картинке с фоткой сверху микросхемы выходы обозначены как надо, но на схеме ниже уже нет :) Видимо потому что разные микросхемы. Меня это сбило с толку на некоторое время.
Например где на верхней QA, на нижней GND.
Zoltberg дело говорит (:
Если бы я расположил выводы на схеме так же, как и на реальной микросхеме, схема получилась бы достаточно запутанной. Собственно, я сначала так и делал, пока не стал путаться сам.
Хм, это тоже можно сделать через сдвиговый регистр при желании. Я тут пишу статью по подключению разной периферии через регистры — попробую что-нибудь придумать для PWM (:
А так-то, для этих целей используют специальные микросхемы вроде SG1525A.
В последнем примере объявлена процедура
void writeShiftRegister16(int ss_pin, uint16_t value)
А после она вызывается с недостающим параметром, что вызывает ошибку
writeShiftRegister16(nomad);
Желательно. Если очень хочется, можно использовать пару других ног, но тогда вам придётся их настроить самостоятельно, а также использовать функцию для записи в регистр.
То есть придется отказаться от класса SPI? Требуется сразу задействовать ввод и вывод. Хотя, как я понимаю, они не будут друг другу мешать. Для вывода, например, к MOSI и SCK используется еще и 8 пин, а для ввода MISO, SCK и пин 9. По идее все должно нормально работать?
Комментарии (20)
RSS свернуть / развернутьнадо либо делать все на 1280/2560 либо использовать сдвиговые регистры.
собственно, пробовал ли кто-нибудь второй вариант?
xtile
burjui
буду ждать :)
xtile
Пока делал по ней столкнулся с неожиданностью: на картинке с фоткой сверху микросхемы выходы обозначены как надо, но на схеме ниже уже нет :) Видимо потому что разные микросхемы. Меня это сбило с толку на некоторое время.
Например где на верхней QA, на нижней GND.
Sicness
Zoltberg
Если бы я расположил выводы на схеме так же, как и на реальной микросхеме, схема получилась бы достаточно запутанной. Собственно, я сначала так и делал, пока не стал путаться сам.
burjui
xtile
Ну что бы например подключить 3-4 RGB-светодиода?
nubas
А так-то, для этих целей используют специальные микросхемы вроде SG1525A.
burjui
burjui
void writeShiftRegister16(int ss_pin, uint16_t value)
А после она вызывается с недостающим параметром, что вызывает ошибку
writeShiftRegister16(nomad);
pakhontas
burjui
nes
burjui
nes
Singrana
burjui
Singrana
burjui
Singrana
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.