CraftDuino v2.0
  • - это CraftDuino - наш вариант полностью Arduino-совместимой платы.
  • CraftDuino - настоящий конструктор, для очень быстрого прототипирования и реализации идей.
  • Любая возможность автоматизировать что-то с лёгкостью реализуется с CraftDuino!
Просто добавьте CraftDuino!

SPI и Arduino: плодим выходы

SPI и Arduino:
  1. Теория
  2. Вывод
  3. Ввод

Рассмотрим классический сдвиговый регистр 74HC595, модель M74HC595B1 от STMicroelectronics. По сути, это преобразователь последовательного интерфейса в параллельный: получает данные по SPI, а потом разом выставляет уровни на 8 ножках согласно полученным битам. Биты, выставляемые ведущим на выводе SI, проталкиваются по цепочке D-триггеров с каждым тактовым импульсом (от ведущего) на ноге SCK. Одновременный вывод на ножки параллельного интерфейса обеспечивается так называемой защёлкой (latch) RCK, которая «не пускает» переданные биты на выводы раньше времени. Вывод G управляет состоянием выводов — включает их либо переводит в состояние Hi-Z:



А вот и сам регистр в DIP-корпусе:



Выводы микросхемы имеют следующее назначение:
  1. Vcc — питание, от 2 до 6 В
  2. GND — земля
  3. QA-QH — эти выводы соответствуют битам, записанными по SPI
  4. SI — вход ведомого, MOSI (SPI)
  5. G — Output Enabe; когда на этом выводе низкий уровень, выводы включены (подключены к «защёлкам»), когда высокий — выводы переходят в состояние Hi-Z
  6. RCK — защёлка, SS (SPI); при установке низкого уровня выводы регистра защёлкиваются
  7. SCK — тактовый вход, SCLK (SPI)
  8. SCLR — Shift Register Clear Input; если на этом выводе низкий уровень, очищает все триггеры по фронту тактового сигнала на SCLK. С нашей точки зрения это банальный RESET: прижал к земле — сбросил все биты регистра
  9. 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:Картинки из статьи лежат в альбоме на Яндекс.Фотках.

Использовалось железо:Следующая тема — SPI и Arduino: ввод
  • +1
  • 12 мая 2011, 14:19
  • burjui

Комментарии (35)

RSS свернуть / развернуть
+
+2
вопрос в следующем: хочу повесить на атмегу 328 управление 24ю сервами, но у самой микросхемы нет столько выходов.

надо либо делать все на 1280/2560 либо использовать сдвиговые регистры.

собственно, пробовал ли кто-нибудь второй вариант?
avatar

xtile

  • 20 мая 2011, 15:06
+
+3
О втором варианте я как раз статью пишу, и библиотеку Servo под это дело допиливаю. Пару дней потерпишь? (:
avatar

burjui

  • 20 мая 2011, 17:41
+
+1
Ха, отличная новость!

буду ждать :)
avatar

xtile

  • 20 мая 2011, 18:15
+
0
Спасибо за статью, расширила мой кругозор.
Пока делал по ней столкнулся с неожиданностью: на картинке с фоткой сверху микросхемы выходы обозначены как надо, но на схеме ниже уже нет :) Видимо потому что разные микросхемы. Меня это сбило с толку на некоторое время.
Например где на верхней QA, на нижней GND.
avatar

Sicness

  • 21 мая 2011, 07:04
+
+1
На схеме расположение выводов не нормированно — как рисовать удобней — обозначения и номерера совпадают же=)
avatar

Zoltberg

  • 21 мая 2011, 09:31
+
+1
Zoltberg дело говорит (:
Если бы я расположил выводы на схеме так же, как и на реальной микросхеме, схема получилась бы достаточно запутанной. Собственно, я сначала так и делал, пока не стал путаться сам.
avatar

burjui

  • 21 мая 2011, 10:21
+
+1
я дополню, что для микросхем обычно есть некое стандартное схемотехническое обозначение, иногда даже без номеров выводов, просто с названиями
avatar

xtile

  • 21 мая 2011, 13:43
+
+1
А чем можно расширить число выходов с PWM?
Ну что бы например подключить 3-4 RGB-светодиода?
avatar

nubas

  • 24 мая 2011, 19:06
+
0
Хм, это тоже можно сделать через сдвиговый регистр при желании. Я тут пишу статью по подключению разной периферии через регистры — попробую что-нибудь придумать для PWM (:
А так-то, для этих целей используют специальные микросхемы вроде SG1525A.
avatar

burjui

  • 24 мая 2011, 20:02
+
0
Иностранцы пишут, что для LED годится TLC5940, которая через PWM управляет 16 светодиодами. То есть одной микросхемы хватит на 5 RGB-светодиодов.
avatar

burjui

  • 24 мая 2011, 20:05
+
+1
В последнем примере объявлена процедура
void writeShiftRegister16(int ss_pin, uint16_t value)
А после она вызывается с недостающим параметром, что вызывает ошибку
writeShiftRegister16(nomad);
avatar

pakhontas

  • 4 сентября 2011, 21:05
+
0
Спасибо, исправил. Похоже, ранее я поправил эту ошибку у себя локально и забыл обновить пост.
avatar

burjui

  • 4 сентября 2011, 21:59
+
0
Какая разница между 74HC595B1 и 74HC595D? Второй стоит в 2,5 раза дешевле!
avatar

nes

  • 24 октября 2011, 08:52
+
0
У 74HC595B1 корпус — DIP-16 (можно ставить в макетку), а у 74HC595D — SO-16 (для поверхностного монтажа, SMD, нужно разводить плату).
avatar

burjui

  • 24 октября 2011, 10:29
+
0
Запоздалое спасибо за ответ! Купил осенью себе пару штук от NXP по 50+ рублей, а на eBay такие микрухи от ST в DIP-корпусе обошлись мне по 7,5 р :)
avatar

nes

  • 16 марта 2012, 12:13
+
0
Ребята, а подскажите, а на ноги MOSI и SCK обязательно вешать? Или все же можно на любые свободные?
avatar

Singrana

  • 13 февраля 2012, 22:31
+
0
Желательно. Если очень хочется, можно использовать пару других ног, но тогда вам придётся их настроить самостоятельно, а также использовать функцию shiftOut() для записи в регистр.
avatar

burjui

  • 14 февраля 2012, 07:46
+
0
То есть придется отказаться от класса SPI? Требуется сразу задействовать ввод и вывод. Хотя, как я понимаю, они не будут друг другу мешать. Для вывода, например, к MOSI и SCK используется еще и 8 пин, а для ввода MISO, SCK и пин 9. По идее все должно нормально работать?
avatar

Singrana

  • 14 февраля 2012, 09:04
+
0
Всё верно: на каждое SPI-устройство выделяешь по отдельной линии SS — и они друг другу не мешают.
avatar

burjui

  • 14 февраля 2012, 09:20
+
0
Благодарю :) Теперь можно окончательно доделывать схему и печатную плату устройства :)
avatar

Singrana

  • 14 февраля 2012, 09:22
+
0
Верно ли я понимаю, что драйвер светодиодов MBI5028 — такой же 16-битный сдвиговый регистр и подключается он так же — за исключением линии яркости и резистора на ту же тему? Или у него как-то по-другому реализовано?
avatar

Shihad

  • 13 июня 2012, 20:14
+
0
А можно ли передать ШИМ по spi?
avatar

ishutinvalera48

  • 8 ноября 2012, 19:06
+
0
Только если организуете ШИМ вручную — дёргая выводы сдвигового регистра в нужные моменты (по прерываниям от тайпера, например). Всё-таки, сдвиговые регистры — не полноценные входы/выходы (:
avatar

burjui

  • 8 ноября 2012, 19:10
+
0
Помогите, есть платка с жк, а на обратной стороне 8 штук hef4015bt. Вот и нужно запустить это все а на эти регистры нету никаких примеров, и как включать их я тоже не знаю
avatar

ishutinvalera48

  • 9 ноября 2012, 20:15
+
0
спасибо! самое понятное с примерами разъяснение SPI и работа с 71HC595

у меня по ходу вопрос: а почему, при включении 71HC595, светодиоды некоторые зажигаются? почему сам регистр не очищается при включении?
может есть какой-то аппаратный метод включения для самоочистки?
avatar

umkin777

  • 30 января 2013, 16:03
+
0
74HC595 *

видимо, нужно допаять сброс с конденсатором и резистором на SCLR?
avatar

umkin777

  • 30 января 2013, 16:09
+
0
Хорошая идея.
avatar

burjui

  • 1 февраля 2013, 02:42
+
0
Товарищи, хочу сделать что то типо этого www.youtube.com/watch?v=JFKPlNoBpfk, но не могу с кодом разобраться, читал читал так и не получается. не могу заставить бегать бит от изменений на А0.
avatar

itan21

  • 11 февраля 2013, 09:16
+
0
Огромное спасибо за статью!
Решил поэкспериментировать с выходным сдвиговым регистром. Возникла проблемка, так как я новичок в этом, как записать значения в регистр из массива?
Заранее спасибо!!!
avatar

Maksim_Z

  • 22 июня 2013, 17:40
+
0
Я не понимаю сути вопроса. У вас проблемы со сдвиговым регистром, с массивами или с битовыми операциями? Допустим, у вас есть массив 8 значений — 0 или 1, и вы хотите, чтобы каждый элемент массива «управлял» соответствующим выводом регистра, тогда код будет таким:

uint8_t array[8] = ...;
uint8_t reg_value = 0;

for (int i = 0; i < 8; ++i)
    reg_value |= array[i] << i;

digitalWrite(REG_SELECT, LOW);
SPI.transfer(reg_value);
digitalWrite(REG_SELECT, HIGH);
avatar

burjui

  • 23 июня 2013, 13:34
+
0
Спасибо! Примерно этого я и хотел добиться! Если что, извиняйте, если не понятно суть вопроса изложил. Проблемы в целом, с программированием...:) Только учусь.

«reg |= lamp_state_array[i] << i» эта строка возвращает не те значения, которые нужно.
С каждым нажатием кнопки в переменной reg увеличивается число. И соответственно контроллер этого уже не понимает. Я хочу управлять нагрузкой через сдвиговый регистр. И мне нужно в определенном элементе массива менять с 0 на 1 либо наоборот. С массивом то я разобрался...:) только вот как теперь значения в регистр загнать ни как не могу допетрить..) Тот, вариант который вы предложили рабочий, но до второго нажатия кнопки. Потом не работает.
avatar

Maksim_Z

  • 23 июня 2013, 16:55
+
0
Вот поэтому я и говорю, что пользователям Arduino всё равно нужно знать C++, чтобы сделать что-либо, кроме стандартных примеров (:

В случае обработки нажатия кнопки вам нужно делать так:
reg_value = 0;
reg |= lamp_state_array[i] << i;
Дело в том, что запись
reg |= x;
эквивалентна записи
reg = reg | x;
Естественно, что вам нужно обнулять значение reg перед циклом на каждое нажатие кнопки.
avatar

burjui

  • 23 июня 2013, 20:02
+
0
Попытайтесь более понятно объяснить, чего хотите добиться.


 reg_val = 0;
...
 reg_val |= 1 << i;
...


Это кусок выставляет в единичку i-тый бит. Следите за значениями в array. Они должны быть строго 1 или 0.
Более надёжно:

...
   if(arry[i])
     reg_value |= 1 << i;
avatar

Asmodey

  • 24 июня 2013, 10:17
+
0
Есть ещё интересная схемотехника Daisy Chaining совмещения (зацикливания) выходных (74HC595) и входных (74HC165) решистров:
robots.freehostia.com/Software/ShiftRegister/ShiftRegisterBody.html
avatar

4RESTER

  • 11 августа 2013, 23:26
+
0
Для определения на какой ноге находится SS, MOSI, MISO и SCK, есть глобальные переменные

Serial.print('SS: ');
Serial.println(SS);
Serial.print('MOSI: ');
Serial.println(MOSI);
Serial.print('MISO: ');
Serial.println(MISO);
Serial.print('SCK: ');
Serial.println(SCK);
avatar

Nook

  • 26 апреля 2015, 11:14

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