Подключаем кучу устройств к Arduino по 5 проводам


Стандартная Arduino/CraftDuino имеет 20 цифровых пинов (6 из них — ещё и аналоговые входы), что бывает недостаточно для решения некоторых задач: тот же LCD-дисплей требует минимум 6 пинов. Для подключения нескольких устройств, не требующих двунаправленной передачи данных, вполне подойдут сдвиговые регистры.

В предыдущих статьях про вывод и ввод через сдвиговые регистры мы уже рассмотрели самое простое их применение, а именно управление светодиодами путём посылки байта и считывание состояний кнопок. Для того, чтобы заставить работать тот же LCD-дисплей через регистр, придётся применить парочку архитектурных программерских трюков. При наличии некоторого опыта в C++ это не покажется сложным (:

Начнём с того, что авторы библиотек Arduino старались сильно не заморачиваться насчёт расширяемости функционала, и не получится просто сказать объекту LCD «подключись через сдвиговый регистр». Так что придётся немножко поработать руками и мозгами за них: доработать библиотеки напильником. Заодно упростим себе работу со SPI. Ну что ж, приступим.

С первого взгляда не очень-то понятно, а как, чёрт возьми, заставить работать тот же LCD через сдвиговый регистр? Что записывать в регистр, какие его выводы должны менять состояние и как? Очевидно, что для начала нужно как-то предоставить возможность управлять отдельными выводами регистра так же, как и обычными пинами Arduino.

Вспомним, как подключается LCD, а затем заглянем в исходный код библиотеки LiquidCrystal и увидим, что там чуть ли не в каждой функции дёргаются пины, к которым подключен LCD, вызовами функции digitalWrite(), а режимы этих пинов устанавливаются через pinMode(). Логично будет написать такие же функции и для сдвиговых регистров.

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

В целях унификации определим интерфейс «контроллера линии»:

class LineDriver
{
public:
  virtual void lineConfig(uint8_t pin, uint8_t mode) = 0;
  virtual void lineWrite(uint8_t pin, uint8_t value) = 0;
  virtual uint8_t lineRead(uint8_t pin) = 0;
};

Этот интерфейс содержит объявления функций lineConfig(), lineWrite() и lineRead() — это аналоги pinMode(), digitalWrite() и digitalRead(). Всю работу с нужными пинами библиотека LCD должна будет делать через интерфейс LineDriver. Для начала создадим драйвер по умолчанию, который будет просто вызывать стандартные функции:

class DefaultLineDriver: public LineDriver
{
public:
  virtual void lineConfig(uint8_t pin, uint8_t mode)
  {
    pinMode(pin, mode);
  }

  virtual void lineWrite(uint8_t pin, uint8_t value)
  {
    digitalWrite(pin, value);
  }

  virtual uint8_t lineRead(uint8_t pin)
  {
    return digitalRead(pin);
  }

  static DefaultLineDriver* getInstance()
  {
    return &g_instance;
  }

private:
  static DefaultLineDriver g_instance; // один глобальный экземпляр драйвера
};

Теперь добавим в класс LiquidCrystal указатель на экземпляр LineDriver и изменим конструкторы класса и функцию init() так, чтобы они принимали соответствующий аргумент:

// LiquidCrystalExt.h
class LiquidCrystal: public Print
{
…
public:
  LiquidCrystal(uint8_t rs, uint8_t enable,
    uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3,
    LineDriver *line_driver = 0, uint8_t backlight = 0xFF);
…
protected:
  void init(uint8_t fourbitmode, uint8_t rs, uint8_t rw, uint8_t enable,
    uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3,
    uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7,
    LineDriver *line_driver = 0, uint8_t backlight = 0xFF);
…
  LineDriver *_pins;
…
};

// LiquidCrystalExt.cpp
LiquidCrystal::LiquidCrystal(uint8_t rs, uint8_t enable,
  uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3,
  LineDriver *line_driver, uint8_t backlight)
{
  init(1, rs, 0xFF, enable, d0, d1, d2, d3, 0, 0, 0, 0, backlight, line_driver);
}

void LiquidCrystal::init(uint8_t fourbitmode, uint8_t rs, uint8_t rw, uint8_t enable,
  uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3,
  uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7,
  uint8_t backlight, LineDriver *line_driver)
{
  _pins = line_driver;

  if (!_pins)
    _pins = DefaultLineDriver::getInstance();
…
}

По умолчанию аргумент line_driver будет равным нулю и его можно не указывать при объявлении объекта LCD-дисплея — тогда будет использоваться драйвер по умолчанию (DefaultLineDriver) и дисплей будет работать, как обычно, через пины Arduino.

Теперь добавим поддержку интерфейса LineDriver в класс для работы с SPI:

class SPI_Bus: public LineDriver
{
public:
  …
  virtual void lineConfig(uint8_t pin, uint8_t mode)
  {
    /* Оставляем метод пустым, т.к. возможность управления состоянием выводов
     * зависит от конкретного устройства.
     */
  }

  virtual void lineWrite(uint8_t line_num, uint8_t value)
  {
    /* Каждому байту соответствует 8 "линий данных" (виртуальных пинов).
     * Проверяем, не превышает ли номер линии максимальный.
     */
    if (line_num < m_bandwidth * 8)
    {
      /* Рассчитываем номер байта в буфере и номер бита в байте */
      const uint8_t byte_index = line_num / 8,
                    bit_index = line_num % 8;

      /* Сначала стираем нужный бит в буфере */
      m_buffer[byte_index] &= ~(1 << bit_index);
      /* Потом устанавливаем нужное значение бита ("уровень на линии") */
      m_buffer[byte_index] |= (value == LOW ? 0 : 1) << bit_index);

      /* Шлём изменённое содержимое буфера. Если работаем со сдвиговым
       * регистром, то нужная ножка регистра поменяет свой уровень на
       * тот, который задан в аргументе value.
       */
      communicate(&SPI_Bus::operationSendBuffer);
    }
  }

  virtual uint8_t lineRead(uint8_t line_num)
  {
    /* Если такой линии нет, возвращаем "низкий уровень на линии" */
    if (line_num >= m_bandwidth * 8)
      return LOW;

    /* Линия есть, считываем состояние линий в буфер */
    communicate(&SPI_Bus::operationReceiveEntireBuffer);

    const uint8_t byte_index = line_num / 8,
                  bit_index = line_num % 8;

    /* Читаем нужный бит из буфера и возвращаем соответствующий ему
     * уровень на линии.
     */
    return (m_buffer[byte_index] >> bit_index) & 1 ? HIGH : LOW;
  }
  …
};

Как можно заметить из приведённого кода, в классе SPI_Bus данные буферизуются: если из регистра ничего не считывать, а только управлять отдельными его выводами через функционал LineDriver, то регистр будет поддерживать уровни напряжений на выводах до тех пор, пока вы не установите новые. Это как раз то, что нужно для управления устройствами — менять состояния отдельных выводов, не трогая при этом другие.

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

/* Допустим, у нас есть один сдвиговый регистр с 8 выходами,
 * защёлка которого (SS) подключена к 10му пину Arduino.
 */
SPI_Bus shift_register(_8bit, 10);
/* Аналог digitalWrite(3), только вместо 3-го пина Arduino
 * мы управляем 3-м выходом сдвигового регистра.
 */
shift_register.lineWrite(3, LOW);

Так как сделано это через реализацию интерфейса LineDriver, мы можем передавать указатель на объект сдвигового регистра в конструктор объекта LCD:

SPI_Bus shift_register(_8bit, 10);

/* А LCD-дисплей подключен к сдвиговому регистру:
 * RS ⇨ выход QA (0)
 * E ⇨ QB (1)
 * DB4-DB7 ⇨ QC-QF (2-5)
 */
LiquidCrystal lcd(0, 1, 2, 3, 4, 5, &shift_register);

Теперь при работе с LCD будут меняться уровни не на пинах Arduino, а на выходах сдвигового регистра. Одна строка кода для подключения регистра, и один дополнительный аргумент для объекта LiquidCrystal — и можно управлять дисплеем по 3м проводам SPI. При этом ещё два выхода регистра остались свободными, и мы можем их использовать, как обычные пины.

Но мы не ограничимся одним лишь LCD — есть же ещё сервоприводы. Я проделал аналогичные манипуляции с библиотекой Servo, и теперь точно так же можно управлять сервоприводами через сдвиговый регистр:

SPI_Bus shift_register(_8bit, 10);
Servo servo(&shift_register);

/* А дальше всё делается, как обычно */
void setup()
{
  servo.attach(6); // вывод QG регистра
}

А что насчёт ввода? Ранее я писал о чтении состояний кнопок через сдвиговый регистр. Теперь, с библиотекой SPI_Bus, это можно сделать так:

SPI_Bus shift_register(_8bit, 10);

void setup()
{
  /* Говорим регистру, чтобы дёргал защёлку до чтения по SPI */
  shift_register.setSelectionPolicy(SPI_Bus::SELECT_BEFORE);
}

void loop()
{
  uint8_t states = shift_register.read8bits(); // считываем состояния
}

А теперь пора использовать наши сдвиговые регистры на полную катушку — подключим к Arduino сразу несколько устройств всего по 5 проводам: LCD-дисплей, 4 сервопривода, RGB-светодиод, несколько кнопок и dip-переключатель на 3 позиции.

Схема подключения:

Вся логика запитана от +5 В Craftduino, а сервы — напрямую от +5 В блока питания. Сама Craftduino запитана от +12 В провода БП. Тут нужно быть поосторожнее с нагрузкой: если она будет слишком велика, то стабилизатор на Craftduino будет перегреваться из-за слишком высокого входного напряжения. В этой схеме подавать +12 В вполне допустимо, т.к. компоненты, запитанные от крафтины, не особо жадные.

Общий вид:

Здесь я сделал пару хитростей для уменьшения количества проводов. Во-первых, сделал breakout-платы для 74HC595 (в корпусах SSOP), которые легко соединяются каскадно, используя общие линии SPI и питание:

Во-вторых, сделал для сигнальных линий сервоприводов 4-жильный шлейф:

Просто взял 4 жилы от старого шлейфа для привода гибких дисков (флопарей), припаял к ним 4 пина гребёнки PLS и залепил всё это поликапролактоном (ПКЛ) — последний для этих целей рулит не по-детски (:

Ну, и в-третьих, припаял к LCD-дисплею легендарный транзистор КТ315 с килоомным резистором на базе — для управления подсветкой, подстроечный резистор на 10 кОм — для настройки контрастности, а также 9-жильный шлейф (4 линии данных DB7-DB4, Enable, RS, подсветка, +5V, GND):

Можете рассмотреть некоторые части системы отдельно. Вот сдвиговые регистры 74HC595, к которым подключены LCD, сервы и RGB-светодиод:

Регистр 74HC165 c подключенными кнопками и dip-переключателем:

Заметьте, кнопки и dip-переключатель подключены к «земле» резисторами на 1 кОм — я это сделал лишь потому, что провода ещё больше загромоздили бы плату. Так как к питанию они подключены через резисторы на 10 кОм, получается, что входы 74HC165 подключены к резистивным делителям напряжения с коэффициентом 10:1, но так как при нажатии кнопки делитель выдаёт напряжение около 0.5 В, это не вызывает никаких проблем — такое напряжение является логическим нулём, так как меньше необходимого порога.

Можете убедиться, к Craftduino идёт всего 5 сигнальных проводов:

Ну и повторюсь — питается это всё от обычного компьютерного блока питания — оттуда берётся +12 В на вход Craftduino, +5 В для питания серв и «земля»:

Суть эксперимента такова: пусть при щёлкании dip-переключателем включается/выключается подсветка LCD и загораются/гаснут два значка на нём, при нажатии и удержании кнопок вращаются сервоприводы, и параллельно ещё меняются цвета RGB-светодиода. Для этого примера потребуются библиотеки LineDriver, SPI_Bus, ServoExt и LiquidCrystalExt, ссылки на которые приведены в конце статьи.

/* Включаем все необходимые библиотеки */
#include <LineDriver.h>
#include <SPI.h>
#include <SPI_Bus.h>
#include <ServoExt.h>
#include <LiquidCrystalExt.h>

enum
{
  SERVOS_AMOUNT = 4, // количество серв
  SERVOS_FIRST_PIN = 9, // вывод сдвигового регистра для первой сервы
  SERVO_ROTATE_STEP = 2, // насколько поворачивать серву, когда нажата кнопка

  SYMBOL_HEIGHT = 8, // высота символа LCD
  SYM_DANGER = 0, // код символа "череп и кости"
  SYM_LIGHTNING = 1, // код символа "молния"

  RED_LINE = 2, // номер вывода регистра для красного канала светодиода
  GREEN_LINE = 0, // и зелёного
  BLUE_LINE = 1, // и синего

  SWITCH1 = 1 << 4, // бит первого dip-переключателя в байте состояний входов
  SWITCH2 = 1 << 5, // второго
  SWITCH3 = 1 << 6, // и третьего

  FIRST_BUTTON = 1 << 0, // бит первой кнопки управления сервами
};

SPI_Bus shreg_in(_8bit, 9); // сдвиговый регистр параллельной загрузки 74HC165
SPI_Bus shreg(_16bit, 10); // каскад из двух сдвиговых регистров 74HC595
/* LCD подсоендинён к 74HC595, в качестве последнего аргумента указан вывод
 * для управления подсветкой.
 */
LiquidCrystal lcd(5, 6, 7, 13, 14, 15, &shreg, 4);

/* Сервы, подсоёдинённые к каскаду из 74HC595 */
Servo servos[SERVOS_AMOUNT] = { &shreg, &shreg, &shreg, &shreg };

/* Направления вращения серв. Про это дело производители договорится
 * не сподобились, так что сервы SG-90 и Robbe 10g при одинаковом коде
 * вращаются в разные стороны, и два последних направления пришлось
 * инвертировать для гармонии.
 */
int servo_directions[SERVOS_AMOUNT] = { 1, 1, -1, -1 };

/* Символ "череп и кости" */
uint8_t danger[SYMBOL_HEIGHT] =
{
  B01110,
  B10101,
  B11011,
  B01110,
  B00000,
  B10001,
  B01110,
  B10001,
};

/* Символ "молния" */
uint8_t lightning[SYMBOL_HEIGHT] =
{
  B00010,
  B00100,
  B01000,
  B11111,
  B00010,
  B00100,
  B01000,
  B00000,
};


void setup()
{
  /* Защёлкой 74HC165 нужно дёргать перед считыванием состояний входов */
  shreg_in.setSelectionPolicy(SPI_Bus::SELECT_BEFORE);

  /* Поворачиваем все сервы в среднее положение */
  for (int i = 0; i < SERVOS_AMOUNT; ++i)
  {
    servos[i].attach(SERVOS_FIRST_PIN + i);
    servos[i].write(90);
  }

  /* Сервы управляются  в фоновом режиме прерываниями таймеров через те же
  * сдвиговые регистры, что и экран, так что на время работы с экраном
  * прерывания лучше запретить во избежание хаоса на шине SPI и порчи
  * содержимого внутреннего буфера в shreg.
  * Буфер там нужен для запоминания последнего состояния выходов, если что.
  */
  noInterrupts();
  lcd.createChar(SYM_DANGER, danger);
  lcd.createChar(SYM_LIGHTNING, lightning);
  lcd.begin(8, 2);
  lcd.print("Yo dawg!");
  interrupts();
}


/* Эта функция показывает символ symbol в столбце pos второй строки LCD,
* если enabled == true, иначе в эту позицию выводится пробел.
*/
void indicate(char symbol, uint8_t pos, bool enabled)
{
  lcd.setCursor(pos, 1);
  lcd.write(enabled ? symbol : ' ');
}


void loop()
{
  static unsigned long last_micros = 0;
  static uint8_t last_inputs = 0;

  /* Проверяем состояния входов регистра 74HC165 каждые 10 мс */
  if (micros() - last_micros >= 10000)
  {
   last_micros = micros();

   noInterrupts();
   /* Читаем состояния входов */
   const uint8_t inputs = shreg_in.read8bits();
   /* Первый dip-переключатель занимается подсветкой. Включение/выключение
    * подсветки - быстрая операция, можно и 100 раз в секунду выполнять.
    */
   lcd.backlight((inputs & SWITCH1) != 0);
   interrupts();

   /* А вот сам LCD-дисплей имеет очень низкую скорость обновления, и мы
    * не сможем обновлять его 100 раз в секунду, так что будем это делать
    * только когда состояния соответствующих переключателей изменились.
    */
   if (inputs != last_inputs)
   {
     noInterrupts();
     indicate(SYM_DANGER, 3, (inputs & SWITCH2) != 0);
     indicate(SYM_LIGHTNING, 5, (inputs & SWITCH3) != 0);
     interrupts();

     last_inputs = inputs;
   }

   /* Теперь порулим сервами */
   for (int i = 0; i < SERVOS_AMOUNT; ++i)
   {
     /* Соответствующая серве кнопка нажата? */
     if ((inputs & (FIRST_BUTTON << i)) != 0)
     {
       /* Рассчитываем следующее положение сервы */
       int new_angle = servos[i].read() + servo_directions[i] * SERVO_ROTATE_STEP;

       /* Если положение выходит за допустимые пределы, меняем
        * направление вращения и корректируем положение.
        */
       if (new_angle < 0 || new_angle > 180)
       {
         new_angle = constrain(new_angle, 0, 180);
         servo_directions[i] *= -1;
       }

       /* Обновляем позицию сервы */
       servos[i].write(new_angle);
     }
   }

   /* В довесок будем менять цвет светодиода 2 раза в секунду */
   static uint8_t ticks = 0, led_color = 0;

   if (ticks >= 50)
   {
     noInterrupts();
     /* Загоняем на линии R, G, B первый, второй и третий биты
      * переменной led_color.
      */
     shreg.lineWrite(RED_LINE, led_color & (1 << 0));
     shreg.lineWrite(GREEN_LINE, led_color & (1 << 1));
     shreg.lineWrite(BLUE_LINE, led_color & (1 << 2));
     interrupts();

     ticks = 0;
     ++led_color; // меняем цвет
   }

   ++ticks;
  }
}

А теперь видео-демка:

Этот исходник в виде файла вы можете взять тут. Библиотеки лежат в репозитории RoboCraft на GitHub, можно скачать архивы самых свежих версий: LineDriver, SPI_Bus, ServoExt и LiquidCrystalExt.
Есть принципиальная схема в формате программы DipTrace.
Рисунки печатной платы - верх и низ - имеют разрешение 600 DPI (точек на дюйм), распечататься они должны в размере около одного квадратного дюйма. Так же есть проект платы в формате DipTrace. Резисторы на плате должны быть номиналом около 100 кОм, но у меня были только 4.7 кОм - их и пришлось поставить. На работу платы не влияет (:
Фотки из статьи доступны в альбоме на Яндекс.Фотках.

Использованное железо:

  • Три макетки Breadbord
  • Craftduino
  • LCD-дисплей на базе контроллера HD44780, 8x2 символов
  • Сервоприводы SG-90 (2 шт.) и Robbe 10g (2 шт.)
  • RGB-светодиод
  • Сдвиговые регистры 74HC595 и 74HC165. У меня 74HC595 в корпусах SSOP, посаженные на платы, но ничто не мешает вам использовать вариант в DIP-корпусе - просто придётся соединять всё это кучей проводов
  • 4 кнопки и один dip-переключатель
  • Резисторы 10 кОм, 1.5 кОм, 1 кОм и 500 Ом, один подстроечный резистор на 10 кОм (можно поставить и здоровенный потенциометр такого же номинала)
  • Транзистор КТ315 (подойдёт любой другой)

Disclaimer
Будьте аккуратны с подключением питания и ничего не напутайте - блок питания запросто может выдать ток в несколько ампер, и если эти амперы достанутся Arduino, а не сервам, то Arduino от этого жужжать и вращаться не станет. Если у вас после прочтения этой статьи сгорит дом, собака и подшивка Playboy - я не виноват (:


52 комментария на «“Подключаем кучу устройств к Arduino по 5 проводам”»

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

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

  1. Интерсная статья, вот только arduino содержит 20 цифровых пинов. ДВАДЦАТЬ.
    Юзание аппаратного инф. обмена — зачот.
    Однако минусов такого использования два — никаких тебе внешних прерываний и быстрое снижение скорости работы периферии.

    Дисклеймер порадовал 🙂 Хоть к 100 Амперному источнику исправную ардуинку подруби — будет нормально работать и кушать свои честные 50мА.

    • Ну да, тут особо не поразгоняешься, когда аппаратно только SPI.transfer() может работать, а вся остальная работа делается библиотекой SPI_Bus. Хотя для конкретных задач можно написать нечто более ограниченное в возможностях, но менее тормозное. Кстати, о скоростях: у gcc с оптимизацией кода для AVR явные проблемы robocraft.ru/forum/viewtopic.php?f=27&t=26.
      Что до прерываний, то они есть в более продвинутых девайсах, как-то I2C-расширителях выводов. О них я в своё время тоже напишу. Но сдвиговые регистры банально дешевле в разы, к тому же.
      А дисклеймер скорее про нечаянное подключение питания с БП на линии логики. Такое иногда случается, но понимаешь это обычно по вспышке и характерному запаху.

    • не-не-не. Никаких расширителей при прерываниях! Я имею в виду конкретно прерывания МК -INT и PCINT. (ну и входной захват по таймеру и аналоговый компаратор до кучи) Без них о RTOS например и речи быть не может. Когда какой-то обслуживающий контроллер типа как описано — последовательный опрос/обновление применимы. но когда речь идет о следящей системе, без прерываний никуда.
      А скорость работы вполне себе высокая — из SPI в МК при определенной сноровке можно и Мбиты выжимать. Речь идет о том, что девайсов много и в конечном итоге обновление может стать очень медленным.

    • Дык я о том, что I2C-расширители имеют выход INT, который можно подрубить к МК — тогда можно будет не заниматься постоянным опросом в цикле. Кстати, скорости I2C при этом (400 кГц) вполне хватит.

  2. Мде, мегу купить проще. Хоть 1280 за 30$ сразу чем брать за 25$ UNO, а потом выдумывать, кодить и прочее — скупой платит дважды народная мудрость…

    • Смотря, какие цели и финансы. Если уж на то пошло, дешевле взять китайский клон Arduino Nano за ещё меньшие деньги и пару грошовых сдвиговых регистров. Тем более, эта статья может служить трамплином в тему расширения выводов голого МК с очень малым количеством выводов — ATtiny13A, например, или какого-нибудь PIC. Мир не сошёлся клином на одном Arduino (:
      К тому же, я писал этот код скорее для демонстрации одного из методов программирования, который позволяет абстрагироваться от аппаратной части и повысить поддерживаемость и расширяемость кода. На этих принципах строятся такие сложные системы, как ядро Linux — его модель драйверов многоуровневая, и позволяет писать кроссплатформенный код: к примеру, драйвер какой-нибудь USB веб-камеры или DVD-ROM в большинстве случаев будет работать одинаково успешно и на x86, и на ARM без переписывания кода.

    • А теперь посчитайте то время которое потратили на код программы?! Мне в 10раз быстрее заработать 30у.е. на ардуино мегу 1280 чем потратить неделю-две на код + его отладку еще месяц для оттачивания. В итоге выигрыш нулевой. Этот метод лишь тем кто УЖЕ имеет что-то и ему не хватает портов. А тем кто планирует проект где будет море всего действительно проще сразу купить мегу. Ведь всё потом упрется в обьём ОЗУ и ФЛЕША в мелких МК

    • да и в принципе никто не мешает к готовой ардуине сделать «распределитель» расширитель, ну как угодно назовите — добавить просто одну еще атмегу в самом простом обвязе(кварц да 2 кондера)(я брал атмега8а дип + кварц ровно 2 бакса на Украине) и на ней делать 2ю программу которая будет строго взаимодействовать с первой ардуиной общение через сериал порты, тут и распределенность и цена смешная)
      Теперь по поводу цен, нано будет всеравно не меньше 20у.е., 2 — 595 будут еще 1 доллар +нужен текстолит, нужно рисовать плату, травить, сверлить еще 1-2вечера нав всё, итого выйдет 22 и выше у.е. А мега1280 с 54портами всего 30 долларов, и где смысл? Так еще бонус больше прерываний, больше озу, флеша, количество АЦП больше в 2раза

    • Вы невнимательно прочитали мой комментарий. Я говорил о любом китайском клоне, который будет стоить дешевле 20$ — это раз. Сдвиговые регистры доступны в DIP-корпусах (в т.ч. прямо здесь в магазине), что удобно для макетирования — это два.
      Ну и использовать вторую мегу со своей прошивкой для расширения выводов — это пальба термоядерной боеголовкой по кишлаку, чтобы уничтожить одного террориста.
      Не будьте столь категоричны, а то по-вашему выходит, что всегда нужно просто покупать железо пожирнее, и это будет лучшее решение всех проблем.

    • Кричать-то зачем? Код я написал один раз, а использовать его могут хоть 100500 человек по 10 раз. К чему спорить, если вы сами сказали, что кому-то это может пригодиться? Вам не пригодилось — ну и ладно, забудьте, что я писал эту статью (:

    • Кричать не буду, а покритиковать немного захотелось. Сам столкнулся с такой ситуацией, когда тупо не хватает портов. Тоже изучал расширители портов. А потом просто купил мегу и радости нет предела. Сейчас ваяю большой модуль где мозгом будет мега2560. Из постоянного подключения DS1307, ULN2803, DS18B20, матрица 32х16 двухцветная,SD карта шилд, кнопки и ИК приёмник. Из того что будет к ней подключаться как модули в случе гулянок -MSGEQ7, L293DNE (2моторчика с зеркалами для лазерного спирографа), и общение с RGB светильником на 27вт светодиодов работающих на атмеге8й(ардуино). И всё пойдет, нет даже полетит))) и портов хватит. Еще в планах сделать на 2х сервах поворотный механизм для светильника в двух осях. Из микросхем и оборудования всё есть, сейчас дорисовываю платки, код на 30%

  3. Статья классная, Мне очень помогла в освоении домашней автоматики. Уважаемый, Burjui, есть ли у вас есть время, можете допилить LiquidCrystalRus под SPI. К сожалению, мои знания в программинге слабоваты.

    • Давно уж через SPI работает, просто свежие версии я выкладываю в репозиторий на GitHub. Последнюю версию всегда можете качнуть целиком, одним архивом.

    • Огромное спасибо! Ждем новых статей. Особенно про расширение выходов ШИМ. 🙂

    • Скачал последнюю версию LiquidCrystalRus и обнаружи, что она не работает с дисплеем 20х4.
      Видно это из того, что 3ю строку начинает писать с 17 символа 1й строки. Код рабочий, работал с LiquidCrystalExt. Поправьте, если есть возможность.
      Спасибо!

    • Да щас «не нужны»! (:
      SCLR вообще, когда в LOW, очищает содержимое сдвигового регистра, CLK_INH в LOW запрещает тактирование, а G в HIGH вообще запрещает вывод на ноги регистра. Если их оставить висящими в воздухе, регистр будет выдавать на ноги всякую хрень, ибо будет ловить наводки от сети 220 В.

    • Не adminЯ не про кинуть в воздух, а на питание и землю без резаков. Собственно, как и видел ранее. Просто тут впервые увидел про резаки, вот и интересен их смысл

    • А, это. Ну да, если входы высокоимпендансные, то можно и без резюков. Но я их таки влепил — для пущей надёжности.

    • Сделано. Обновил ссылки на архивы в посте, чтобы указывали на архивы свежих версий на GitHub.

    • Если найдёте подходящую библиотеку или сами модифицируете существующую. Я с графическими не работал.

    • А аналоговые выходы можно похожим образом расширить?

    • Для этого есть специальные микросхемы — мультиплексоры. Но я в этом не разбираюсь, спросите лучше Zoltberg’а.

    • Так и есть — можно брать любую. Просто это базовая стандартная логика, и её выпускают все, кому не лень, хотя характеристики и идентичны.

  4. Добрый день. Подскажите, пожалуйста, почему нельзя использовать общую линию SS при подключении 74*595 и 74*165 регистров? Или все-таки можно?

    Спасибо.

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

      Потому что 74HC165 всю малину портит тем, что использует SS не совсем по-SPIшному. В SPI принято выбирать устройство прижатием соответствующей ему линии SS к земле на время работы с ним, тогда как для 74HC165 нужно сначала дёрнуть SS вниз-вверх, а потом уже считывать данные.

      (в общем-то, учитывая исключительную простоту устройства шины SPI, можно сказать, что 74HC165 не поддерживает SPI, а просто имеет общие с ней принципы работы)

  5. SPI_Bus shreg(_16bit, 10); // каскад из двух сдвиговых регистров 74HC595
    /* LCD подсоендинён к 74HC595, в качестве последнего аргумента указан вывод
    * для управления подсветкой.
    */
    LiquidCrystal lcd(5, 6, 7, 13, 14, 15, &shreg, 4);

    Строчный и рядом блочный комментарии прочитал как одно целое). Смутила фраза «в качестве последнего аргумента указан вывод для управления подсветкой» — сначала подумал, что это относится к строке кода выше, где создается SPI_Bus. Минут пять думал, где подсветка у регистра пока не дошло, что это про 4-й пин, который передали в LiquidCrystal)

  6. Доброго времени суток!
    Скачал библиотек по ссылкам, водрузи в ардуину.
    сделал инклюд… три раза, LineDriver, SPI, SPI_Bus
    Жму компил… получаю

    In file included from LCD5110_Scrolling_Text.pde:1:
    C:\ARDUINO\libraries\LiquidCrystalRus/LineDriver.h:9: error: stray ‘\302’ in program
    C:\ARDUINO\libraries\LiquidCrystalRus/LineDriver.h:9: error: stray ‘\267’ in program
    C:\ARDUINO\libraries\LiquidCrystalRus/LineDriver.h:9: error: stray ‘\302’ in program
    C:\ARDUINO\libraries\LiquidCrystalRus/LineDriver.h:9: error: stray ‘\267’ in program
    In file included from LCD5110_Scrolling_Text.pde:1:
    C:\ARDUINO\libraries\LiquidCrystalRus/LineDriver.h:315:46: error: invalid suffix «c91d764c9a82611726495e8820e9e» on integer constant
    C:\ARDUINO\libraries\LiquidCrystalRus/LineDriver.h:316:62: error: invalid suffix «c91d764c9a82611726495e8820e9e» on integer constant
    C:\ARDUINO\libraries\LiquidCrystalRus/LineDriver.h:367:257: error: invalid suffix «x» on floating constant
    C:\ARDUINO\libraries\LiquidCrystalRus/LineDriver.h:401:110: error: invalid digit «9» in octal constant
    C:\ARDUINO\libraries\LiquidCrystalRus/LineDriver.h:501: error: stray ‘#’ in program
    C:\ARDUINO\libraries\LiquidCrystalRus/LineDriver.h:501: error: stray ‘#’ in program
    C:\ARDUINO\libraries\LiquidCrystalRus/LineDriver.h:501: error: stray ‘#’ in program
    C:\ARDUINO\libraries\LiquidCrystalRus/LineDriver.h:501: error: stray ‘#’ in program
    In file included from C:\Program Files\Arduino\libraries\SPI_Bus/SPI_Bus.h:27,
    from LCD5110_Scrolling_Text.pde:3:
    C:\ARDUINO\libraries\LiquidCrystalRus/LineDriver.h:9: error: stray ‘\302’ in program
    C:\ARDUINO\libraries\LiquidCrystalRus/LineDriver.h:9: error: stray ‘\267’ in program
    C:\ARDUINO\libraries\LiquidCrystalRus/LineDriver.h:9: error: stray ‘\302’ in program
    C:\ARDUINO\libraries\LiquidCrystalRus/LineDriver.h:9: error: stray ‘\267’ in program
    In file included from C:\Program Files\Arduino\libraries\SPI_Bus/SPI_Bus.h:27,
    from LCD5110_Scrolling_Text.pde:3:
    C:\ARDUINO\libraries\LiquidCrystalRus/LineDriver.h:315:46: error: invalid suffix «c91d764c9a82611726495e8820e9e» on integer constant
    C:\ARDUINO\libraries\LiquidCrystalRus/LineDriver.h:316:62: error: invalid suffix «c91d764c9a82611726495e8820e9e» on integer constant
    C:\ARDUINO\libraries\LiquidCrystalRus/LineDriver.h:367:257: error: invalid suffix «x» on floating constant
    C:\ARDUINO\libraries\LiquidCrystalRus/LineDriver.h:401:110: error: invalid digit «9» in octal constant
    C:\ARDUINO\libraries\LiquidCrystalRus/LineDriver.h:501: error: stray ‘#’ in program
    C:\ARDUINO\libraries\LiquidCrystalRus/LineDriver.h:501: error: stray ‘#’ in program
    C:\ARDUINO\libraries\LiquidCrystalRus/LineDriver.h:501: error: stray ‘#’ in program
    C:\ARDUINO\libraries\LiquidCrystalRus/LineDriver.h:501: error: stray ‘#’ in program
    In file included from LCD5110_Scrolling_Text.pde:1:
    C:\ARDUINO\libraries\LiquidCrystalRus/LineDriver.h:4: error: expected unqualified-id before ‘<‘ token
    C:\ARDUINO\libraries\LiquidCrystalRus/LineDriver.h:501: error: expected constructor, destructor, or type conversion before ‘.’ token
    C:\ARDUINO\libraries\LiquidCrystalRus/LineDriver.h:501: error: expected unqualified-id before ‘<‘ token
    C:\ARDUINO\libraries\LiquidCrystalRus/LineDriver.h:501: error: expected unqualified-id before ‘<‘ token
    C:\ARDUINO\libraries\LiquidCrystalRus/LineDriver.h:501: error: expected unqualified-id before ‘<‘ token
    C:\ARDUINO\libraries\LiquidCrystalRus/LineDriver.h:545: error: expected unqualified-id before numeric constant
    In file included from c:/program files/arduino/hardware/tools/avr/lib/gcc/../../avr/include/inttypes.h:37,
    from C:\Program Files\Arduino\libraries\SPI_Bus/SPI_Bus.h:25,
    from LCD5110_Scrolling_Text.pde:3:
    c:/program files/arduino/hardware/tools/avr/lib/gcc/../../avr/include/stdint.h:159: error: ‘int8_t’ does not name a type
    c:/program files/arduino/hardware/tools/avr/lib/gcc/../../avr/include/stdint.h:213: error: ‘int8_t’ does not name a type
    In file included from C:\Program Files\Arduino\hardware\arduino\cores\arduino/Arduino.h:8,
    from C:\Program Files\Arduino\libraries\SPI/SPI.h:15,
    from C:\Program Files\Arduino\libraries\SPI_Bus/SPI_Bus.h:26,
    from LCD5110_Scrolling_Text.pde:3:
    c:/program files/arduino/hardware/tools/avr/lib/gcc/../../avr/include/avr/pgmspace.h:215: error: ‘int8_t’ does not name a type
    In file included from C:\Program Files\Arduino\libraries\SPI_Bus/SPI_Bus.h:27,
    from LCD5110_Scrolling_Text.pde:3:
    C:\ARDUINO\libraries\LiquidCrystalRus/LineDriver.h:4: error: expected unqualified-id before ‘<‘ token
    C:\ARDUINO\libraries\LiquidCrystalRus/LineDriver.h:501: error: expected constructor, destructor, or type conversion before ‘.’ token
    C:\ARDUINO\libraries\LiquidCrystalRus/LineDriver.h:501: error: expected unqualified-id before ‘<‘ token
    C:\ARDUINO\libraries\LiquidCrystalRus/LineDriver.h:501: error: expected unqualified-id before ‘<‘ token
    C:\ARDUINO\libraries\LiquidCrystalRus/LineDriver.h:501: error: expected unqualified-id before ‘<‘ token
    C:\ARDUINO\libraries\LiquidCrystalRus/LineDriver.h:545: error: expected unqualified-id before numeric constant
    In file included from LCD5110_Scrolling_Text.pde:3:
    C:\Program Files\Arduino\libraries\SPI_Bus/SPI_Bus.h:46: error: expected class-name before ‘{‘ token
    C:\Program Files\Arduino\libraries\SPI_Bus/SPI_Bus.h:52: error: ‘LineDriver’ has not been declared
    C:\Program Files\Arduino\libraries\SPI_Bus/SPI_Bus.h:56: error: ‘LineDriver’ has not been declared
    C:\Program Files\Arduino\libraries\SPI_Bus/SPI_Bus.h:95: error: ISO C++ forbids declaration of ‘LineDriver’ with no type
    C:\Program Files\Arduino\libraries\SPI_Bus/SPI_Bus.h:95: error: expected ‘;’ before ‘*’ token
    C:\Program Files\Arduino\libraries\SPI_Bus/SPI_Bus.h:107: error: ‘LineDriver’ has not been declared

    как побороть?

    • после некоторых манипуляций, остались вот эти ошибки

      In file included from shreg_demo_2x.ino:3:
      C:\Program Files\Arduino\libraries\SPI_Bus/SPI_Bus.h:46: error: expected class-name before ‘{‘ token
      C:\Program Files\Arduino\libraries\SPI_Bus/SPI_Bus.h:52: error: ‘LineDriver’ has not been declared
      C:\Program Files\Arduino\libraries\SPI_Bus/SPI_Bus.h:56: error: ‘LineDriver’ has not been declared
      C:\Program Files\Arduino\libraries\SPI_Bus/SPI_Bus.h:95: error: ISO C++ forbids declaration of ‘LineDriver’ with no type
      C:\Program Files\Arduino\libraries\SPI_Bus/SPI_Bus.h:95: error: expected ‘;’ before ‘*’ token
      C:\Program Files\Arduino\libraries\SPI_Bus/SPI_Bus.h:107: error: ‘LineDriver’ has not been declared

      Чего делать?

  7. Помогите пожалуйста переделать библиотеку Stepper для работы шаговиками через сдвиговый регистр… Надо подключить 4 вот таких шаговика через 2 последовательных 74HC595.
    У меня опыта работы с библиотеками совсем нет. Пытался сам разобраться, но не осилил.
    Очень нужна помощь!

  8. В целом эта статья почти единственная на эту тему я уже несколько недель шарю по интернету и нигде нечего не могу найти
    в частности меня интересует подключение дисплея 1602 через сдвиговый регистр
    решил попробовать почерпнуть чтонить от сюда но проблемы начались уже с момента старта компилятора он отругал за библиотеки заявив что они не могут так называться
    дальше он до шел до строчки
    SPI_Bus shreg_in(_8bit, 9); // сдвиговый регистр параллельной загрузки 74HC165
    и все
    подскажите пожалуйста в каком направлении решать проблему

  9. Кто нибудь пробовал подключать через регистр дисплей Nokia 5110 LCD?
    Помогите пожалуйста. Пробовал сам переписать библиотеку по примерам, не заработало((

  10. Пытаюсь подключить много акселерометров к ардуино нано по spi. Сейчас подключено 4 датчика, данные собираются и отправляются в комп по serial. Начал подключать больше датчиков, началась дичь. Данные от уже подключенных датчиков становятся неправильными, появляется дребезг на данных. Это происходит именно при подключении самого провода sdo от пятого датчика. С шестью еще хуже становится. Датчики подключены через экранированный провод длиной 1 метр. Подскажите как исправить.

  11. Добрый вам день.
    Подскажите плиз глупому, никак не могу разобраться.
    Мне надо к ардуинке подключить 1-n вводных сдвиговых регистров (на входах регистров будут кнопки) и 1-n выводных сдвиговых регистров. Регистры должны подключаться каскадом. Основная суть — при нажатии кнопки x должен загораться светодиод x. Помогите плиз чайнику схемкой соединения этого добра и желательно скетчом.

  12. Здравствуйте. Подскажите пожалуйста, почему у меня все работает не так как надо)
    Я подключил 2 микросхемы 74hc595, как у вас в статье и цепляю к ним LCD вот так:

    SPI_Bus shreg(_16bit,10);
    
    LiquidCrystal lcd(7, 6, 5, 4, 3, 2, &shreg, 1);

    Далее идет мой большой код) И в одном месте, я еще мигаю светодиодом, вот так:

    shreg.lineWrite(9, j);
      j=!j;

    И у меня вместе со светодиодом мигает подсветка экрана… Пробовал цеплять его на 10 ножку сдвигового региста, тогда кракозябры появляются на экране. Что может быть не так?

    • Убрал все 100КОм резисторы, и заработало нормально.

  13. Добрый день!

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

    Правильно ли я понял, что для увеличения только цифровых входов и выходов нужно подключить лишь следующие библиотеки:

    #include <LineDriver.h>
    #include <SPI.h>
    #include <SPI_Bus.h>
    

    Сейчас стоит задача увеличить количество digital I/O c 14ти до 37ми.
    25 из которых будут выходы и 12 входы.

    • Как надо изменить код обработки LOW на шести каналах при подключении одного сдвигового регистра и использовании библиотек из этой статьи?
      Как выглядит код инициации выводов регистра, чтобы с ними можно было работать как с обычными цифровыми выходами?

      Сейчас void loop выглядит следующим образом:

       // обработка первого канала
       if(digitalRead(Can1) == LOW){digitalWrite(out1, HIGH);}
       else{digitalWrite(out1, LOW);}
      
      
       // обработка второго канала
       if(digitalRead(Can2) == LOW){digitalWrite(out2, HIGH);}
       else{digitalWrite(out2, LOW);}
      
       //обработка третьего канала
       if(digitalRead(Can3) == LOW){digitalWrite(out3, HIGH);}
       else{digitalWrite(out3, LOW);}
      
       //обработка четвертого канала
       if(digitalRead(Can4) == LOW){digitalWrite(out4, HIGH);}
       else{digitalWrite(out4, LOW);}
      
       //обработка пятого канала
       if(digitalRead(Can5) == LOW){digitalWrite(out5, HIGH);}
       else{digitalWrite(out5, LOW);}
      
       //обработка шестого канала
       if(digitalRead(Can6) == LOW){digitalWrite(out6, HIGH);}
       else{digitalWrite(out6, LOW);}
      
      
    • Понял в чем была ошибка, у Меги вывод ss на 53 контакте по умолчанию

      Курим дальше…
      С выводами на один регистр понятно
      1. подключаем библиотеки

      #include <LineDriver.h>
      #include <SPI.h>
      #include <SPI_Bus.h>

      2. Инициализируем регистр

      SPI_Bus shift_register(_8bit, <b>номер_порта_SS_по_даташиту_платы</b>);

      3. Переписываем все digitalWrite(переменая_цифрового_выхода_на_дуине, HIGH_или_LOW) следующим образом:

      shift_register.lineWrite(номер_выхода_регистра, HIGH_или_LOW);

      Подскажите что надо прописать при подключении трех регистров каскадом, как указать в скетче выходы второго и третьего регистра?

Добавить комментарий

Arduino

Что такое Arduino?
Зачем мне Arduino?
Начало работы с Arduino
Для начинающих ардуинщиков
Радиодетали (точка входа для начинающих ардуинщиков)
Первые шаги с Arduino

Разделы

  1. Преимуществ нет, за исключением читабельности: тип bool обычно имеет размер 1 байт, как и uint8_t. Думаю, компилятор в обоих случаях…

  2. Добрый день! Я недавно начал изучать программирование под STM32 и ваши уроки просто бесценны! Хотел узнать зачем использовать переменную типа…

3D-печать AI Arduino Bluetooth CraftDuino DIY Google IDE iRobot Kinect LEGO OpenCV Open Source Python Raspberry Pi RoboCraft ROS swarm ИК автоматизация андроид балансировать бионика версия видео военный датчик дрон интерфейс камера кибервесна манипулятор машинное обучение наше нейронная сеть подводный пылесос работа распознавание робот робототехника светодиод сервомашинка собака управление ходить шаг за шагом шаговый двигатель шилд юмор

OpenCV
Робототехника
Будущее за бионическими роботами?
Нейронная сеть - введение