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

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

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

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

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