Информация к новости
  • Просмотров: 2873
  • Автор: sulicompany
  • Дата: 16-02-2013, 04:57
 (голосов: 0)
16-02-2013, 04:57

SPI и Arduino

Категория: Электроника » Arduino

SPI (Serial Peripheral Interface), или последовательный периферийный интерфейс, был разработан компанией Motorola для организации быстрого и простого в реализации обмена данными между компонентами системы — микроконтроллерами и периферийными устройствами. На шине может быть одно ведущее устройство (master) и несколько ведомых (slave).

Интерфейс использует 4 линии для обмена данными:

  • SCLK — Serial Clock: тактовый сигнал (от ведущего)
    Другие обозначения: SCK, CLK
    Arduino: пин 13
  • MOSI — Master Output, Slave Input: данные от ведущего к ведомому
    Другие обозначения: SDI, DI, SI
    Arduino: пин 11
  • MISO — Master Input, Slave Output: данные от ведомого к ведущему
    Другие обозначения: SDO, DO, SO
    Arduino: пин 12
  • SS — Slave Select: выбор ведомого; устанавливается ведущим
    Другие обозначения: nCS, CS, CSB, CSN, nSS, STE
    Arduino: по умолчанию пин 10

Линия SS обычно для каждого ведомого своя, но некоторых ведомых возможно подключить к одной SS — такой способ используется для каскадного подключения устройств.

Стандартный алгоритм работы SPI таков:

  1. Ведущий устанавливает низкий уровень на той линии SS, к которой подключен нужный ведомый.
  2. Ведущий задаёт такт, «дрыгая» уровнем на SCLK, и одновременно с каждым дёрганьем SCLK выставляет нужный уровень на MOSI, передавая ведомому по биту за такт.
  3. Ведомый на каждый «дрыг» SCLK выставляет нужный уровень на MISO, передавая ведущему по биту за такт.
  4. Для завершения передачи ведущий устанавливает высокий уровень на SS.

SPI является полнодуплексной шиной — данные передаются одновременно в обе стороны. Типичная скорость работы шины лежит в пределах 1-50 МГц. Благодаря исключительной простоте алгоритма передачи SPI получил широчайшее распространение в самых различных электронных устройствах — например, в датчиках, чипах памяти, радиомодулях, и т.д.

Вообще, у SPI есть четыре режима передачи, которые основаны на комбинации «полярности» тактового сигнала (clock polarity, CPOL) и фазы синхронизации (clock phase, CPHA). Проще говоря, CPOL — это уровень на тактовой линии до начала и после окончания передачи: низкий (0) или высокий (1). А фаза определяет, на фронте или спаде тактового сигнала передавать биты:

  • Режим 0: CPOL=0, CPHA=0
    Чтение бита происходит на фронте тактового сигнала (переход 0 ⇨ 1), а запись — на спаде (1 ⇨ 0).
  • Режим 1: CPOL=0, CPHA=1
    Чтение — на спаде, запись — на фронте.
  • Режим 2: CPOL=1, CPHA=0
    Чтение — на спаде, запись — на фронте.
  • Режим 3: CPOL=1, CPHA=1
    Чтение — на фронте, запись — на спаде.




Данные по SPI можно передавать либо старшим битом вперёд (по умолчанию для Arduino), либо младшим. Обычно используется первый вариант, но перед началом работы с устройством следует уточнять этот момент в документации.

Кратко о библиотеке SPI

Эта библиотека использует аппаратные возможности AVR для работы по SPI на Arduino, причём только в режиме ведущего (SPI master). Функций в ней совсем немного:

  • begin()иend()
    Инициализация и завершение работы с SPI. При инициализации линии SCLK (13), MOSI (11) и SS (10) настраиваются на вывод, выставляя на SCK и MOSI низкий, а на SS — высокий уровень. Вызовend()линии не трогает, оставляя в том же состоянии, что и до вызова — просто выключает блок SPI микроконтроллера.
  • setBitOrder(order)
    Устанавливает порядок посылки битов данных (order):
    MSBFIRST— первым идёт старший бит посылки (по умолчанию)
    LSBFIRST— первым идёт младший бит
  • setClockDivider(divider)
    Устанавливает делитель тактов для SPI относительно основной частоты. Доступны делители 2, 4, 8, 16, 32, 64 и 128. Соответствующие константы имеют имена видаSPI_CLOCK_DIVn, гдеn— делитель, например,SPI_CLOCK_DIV32. По умолчанию делитель равен 4 — при обычной тактовой частоте МК на Arduino в 16 МГц SPI будет работать на частоте 4 МГц.
    На заметку: если устройство поддерживает частоту, скажем, 1.25 МГц, то нужно выставить делитель, соответствующий этой или меньшей частоте — 16, например.
  • setDataMode(mode)
    Задаёт режим работы SPI, используя константыSPI_MODE0(по умолчанию),SPI_MODE1,SPI_MODE2иSPI_MODE3. Это те самые режимы c параметрами CPOL и CPHA.
  • transfer(value)
    Осуществляет двустороннюю передачу: передаёт байтvalueи возвращает байт, принятый от ведомого.

Кроме того, доступны функции shiftIn(miso_pin, sclk_pin, bit_order) и shiftOut(mosi_pin, sclk_pin, order, value), они предоставляют программную полудуплексную передачу данных по SPI — этакие половинки метода transfer():shiftIn() только принимает, а shiftOut() только передаёт данные. Как видно по их аргументам, они позволяют использовать любые цифровые пины Arduino в качестве линий SPI, но вы сами должны настроить их как входы/выходы, функции shiftIn() и shiftOut() этого не делают.

Ссылки:

  • Статьи об SPI наи
  • к библиотеке SPI и заметка о
  • Рассмотрим классический сдвиговый регистр 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:
    Картинки из статьи лежат вна Яндекс.Фотках.

    Использовалось железо:
    • Сдвиговые регистры 74HC595, модель
    • Arduino-совместимая плата, удобныеи
    • Светодиоды, резисторы 510 Ом
    Теперь попробуем считать состояние нескольких кнопок через другой сдвиговый регистр, предназначенный для ввода — 74HC165, модель SN74HC165N от Texas Instruments. Этот регистр, в отличие от рассмотренного ранее 74HC595, работает наоборот — преобразует параллельный интерфейс в последовательный. Но порядок работы немного другой: сначала дёргается линия SS вниз-вверх — состояния входов «защёлкиваются» во внутренний регистр, а уже потом идёт тактирование по SCLK и передача данных по MISO.

    Чтобы понять, как работает регистр, взглянем на его схему:



    Вход SH/L͞D (сдвиг/загрузка) управляет занесением состояний входов в триггеры — так называемой параллельной загрузкой: стоит прижать его к «земле», как состояние входов регистра будет подано на S-входы триггеров. Чтобы отключить триггеры от входов регистра и иметь возможность читать из регистра, нужно установить SH/L͞D в 1. Тактирование происходит при переходе CLK из 0 в 1 при условии, что SH/L͞D = 1 (параллельная загрузка отключена), а CLK INH = 0 (тактирование включено). При каждом «тике» CLK каждый триггер проталкивает бит в следующий триггер, захватывая бит со своего 1D-входа. Так как к 1D-входу первого триггера подключен вход SER, то подаваемые на него биты проталкиваются в регистр, позволяя соединять регистры в цепочки. В конечном итоге проталкиваемые биты достигают выхода последнего регистра, где подаются сразу на два вывода — на QH и, через инвертор, на Q͞H.

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



    Назначение выводов:
    • Vcc— питание
    • GND— земля
    • SH/L͞D— защёлка, SS (SPI)
    • CLK— тактовый вход, SCLK (SPI)
    • A-H— входы, состояние которых считывается в регистр
    • QH— последовательный вывод, MISO (SPI)
    • Q͞H— инверсный вывод, на нём идут биты с QH, но инвертированные
    • SER— последовательный ввод; к нему можно подсоединить вывод QH второго регистра, получив каскадное подключение
    • CLK INH— Clock Inhibit, или инвертированный Clock Enable; когда на нём 1, тактирование выключено
    Попробуем считать состояние 8 кнопок, подключенных к выводам A-H регистра. Для этого подключим выводы следующим образом:
    • Vcc⇨ +5 В
    • GND⇨ GND
    • SH/L͞D⇨ пин 8
    • CLK⇨ пин 13
    • QH⇨ пин 12
    • Q͞Hоставим неподключенным
    • SER⇨ GND
    • CLK INH⇨ GND
    Кнопки сажаем на выводы A-H, подтягивая к питанию резисторами на 10 кОм, и на землю:



    Получилась такая каша из проводов, что мои mad skillz в делах рисовальных не помогли, так что постарайтесь как-нибудь догадаться, что куда идёт (:



    Лепить на макетку ещё и светодиоды было бы слишком, поэтому состояния кнопок будем слать по UART и смотреть через Serial Monitor. Поехали:

    #include <SPI.h>

    enum { REG_LATCH = 8 };


    void setup()
    {
     
    /* Включаем UART и SPI */
     
    Serial.begin(9600);
      SPI
    .begin();
     
    /* Включаем защёлку */
      pinMode
    (REG_LATCH, OUTPUT);
      digitalWrite
    (REG_LATCH, HIGH);
    }


    void loop()
    {
     
    static uint8_t last_input_states = 0;

     
    /* Выставим на защёлке сначала низкий, потом - высокий уровни.
       * Сдвиговый регистр запомнит уровни сигналов на входах и сможет
       * их нам потом отдать бит за битом.
       */

      digitalWrite
    (REG_LATCH, LOW);
      digitalWrite
    (REG_LATCH, HIGH);
     
    /* Читаем запомненные состояния входов. Ноль шлём просто потому,
       * что transfer() одновременно и шлёт, и принимает. Да и вообще,
       * MOSI не подключена (:
       */

      uint8_t states
    = SPI.transfer(0);

     
    /* Если состояние любого входа изменилось, расскажем по UART */
     
    if (states != last_input_states)
     
    {
       
    /* Старый добрый XOR даст нам битовую разницу между старым и новым состояниями. */
        uint8_t changed
    = states ^ last_input_states;
        last_input_states
    = states; // запоминаем текущие состояния

       
    for (int i = 0; i < 8; ++i)
       
    {
         
    if (changed & 1) // если состояние кнопки изменилось…
         
    {
           
    Serial.print("#");
           
    Serial.print(i); // шлём номер кнопки
           
    Serial.print(" -> ");
           
    Serial.println(states & 1); // … и новое состояние
         
    }

         
    /* Сдвигаем биты вправо. Теперь первый бит
           * будет указывать состояние следующей кнопки.
           */

          changed
    >>= 1;
          states
    >>= 1;
       
    }
     
    }
    }




    С каскадным подключением история такая же, как и с 74HC595, только здесь вывод QH второго регистра подключается к выводу SER первого и т.п. Или, как вариант, использовать выводы Q͞H вместо QH, если нужно считать много кнопок, подтянутых к питанию.

    Исходник примера доступен для скачиванияи на GitHub.

    Картинки из статьи лежат вна Яндекс.Фотках.

    Использовалось железо:
    • Сдвиговый регистр 74HC165, модель
    • Arduino-совместимая плата, удобныеи
    • Резисторы 510 Ом, кнопки
    • Подключаем кучу устройств к Arduino по 5 проводам

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

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

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

      С первого взгляда не очень-то понятно, а как, чёрт возьми, заставить работать тот же 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 — есть же ещё. Я проделал аналогичные манипуляции с, и теперь точно так же можно управлять сервоприводами через сдвиговый регистр:

      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;
       
      }
      }

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


      Этот исходник в виде файла вы можете взять. Библиотеки лежат вна GitHub, можно скачать архивы самых свежих версий:,,и.
      Естьв формате программы 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 — я не виноват (:
      Думаю, ни для кого не секрет, что первоначально программу в микроконтроллер заливают при помощи специального устройства — программатора. Конечно, ардуинщикам обычно не нужно об этом беспокоиться — у них есть bootloader (загрузчик), заранее прошитый в микроконтроллер, и прошивку он забирает по UART через COM-порт или через USB. Но чтобы прошить этот загрузчик или другую прошивку в «чистый» МК, нужен программатор.

      Но в этой статье мы не будем рассматривать сборку и пайкус нуля, а воспользуемся возможностями Arduino. Дело в том, что на большинстве плат Arduino до версии Uno есть микросхемаFT232RLкомпании FTDI.

      Эта микросхема представляет собой конвертор UART->USB, предоставляя операционной системе виртуальный COM-порт, работающий через USB. Но в данном случае нам нужна другая её возможность — управление отдельными выводам микросхемы, именуемое режимом bit-bang, которое позволяет «завернуть» произвольный протокол в USB. Задача состоит в том, завернуть в USB протокол прошивки МК.

      Микроконтроллеры AVR, используемые в Arduino, прошиваются по уженам протоколу SPI через разъём для внутрисхемного программирования —ISP(In-SystemProgramming). Он так называется потому, что позволяет прошивать МК прямо в конечном устройстве. Вот как выглядит этот разъём на плате CraftDuino:



      MISO, MOSI, SCK, RESET — это всё линии шины SPI, только вместо SS — RESET.

      Но нам ещё нужен доступ выводам FT232RL, через которые будет осуществляться прошивка, и разработчики Arduino позаботились об этом, сделав разъёмX3(икс-три):



      Если на вашей плате только контактные площадки для X3, нужно будет припаять кусок PLS-гребёнки самим.

      Пины этого разъёма имеют следующее назначение для ISP-программатора:
      • 1 (CTS) — MISO
      • 2 (DSR) — SCK
      • 3 (DCD) — MOSI
      • 4 (RI) — RESET
      А у CraftDuino вместо X3 есть стандартный разъём RS232, тоже соединённый с FT232RL, из которого нам нужны те же 4 вывода:



      • 1 (CD) — MOSI
      • 6 (DSR) — SCK
      • 8 (CTS) — MISO
      • 9 (RI) — RESET
      Пораскинув мозгами, сделаем кабель для нашего импровизированного ISP-программатора:



      С одного конца кабеля — разъёмы для Arduino X3/CraftDuino UART, и для питания:



      А с другого конца — стандартный разъём ISP:



      Обычно для прошивки AVR используется популярная утилитаavrdude, которая поддерживает множество различных программаторов и моделей МК, её использует даже среда Arduino IDE для заливки скетча. Для этой утилиты есть патч, позволяющий прошивать МК через микросхему FT232RL, используя режим bit-bang. Нашлись добрые люди, которые уже пропатчили Windows-версиюavrdudeдля работы с bit-bang-программатором на базе этой микросхемы, а я сделал то же и для Linux:
      • Патченая версия 5.3.1 для.
      • Версия 5.10, deb-пакет для Linuxи(x86_64).
      • с исходными кодами версии 5.10, библиотекой libftd2xx-1.0.4 с официального сайта FTDI и правленымиMakefile.inиavrdude.conf, чтобы всё это собиралось, корректно устанавливалось и работало. Проверял только на Ubuntu 11.04 (i386 и amd64).
      Собственно, deb-пакеты собраны из выложенного тут патченого дистрибутива, но для параноиков есть другой вариант — пропатчить и собрать avrdude самолично, про это читайте. Только вavrdude.confнужно добавить следующие строчки, а не то, что написано в статье по ссылке:

      programmer
        id
      = "ftbb";
        desc
      = "FT232R Synchronous BitBang";
        type
      = ft245r;
        miso
      = 3; # CTS X3(1)
        sck
      = 5; # DSR X3(2)
        mosi
      = 6; # DCD X3(3)
        reset
      = 7; # RI X3(4)
      ;

      Если вы работаете в Linux, придётся сделать ещё несколько действий (FTDI нас любит):
      • Убить драйверftdi_sio, который мешаетavrdudeоткрыть COM-порт FTDI:
        sudo rmmod ftdi_sio

        Если хотите, можете занести этот модуль ядра в чёрный список, внеся в/etc/modprobe.d/blacklist.confстрочку:
        blacklist ftdi_sio

        Только имейте ввиду, что для работы с виртуальным портом/dev/ttyUSB0и т.п. (нужно Arduino IDE) этот модуль должен быть запущен. Это можно делать командой
        sudo modprobe ftdi_sio
      • По умолчанию, система при попытке avrdude открыть виртуальный COM-порт FTDI покажет ему кукиш, а он вам — не слишком информативное сообщение об ошибке. Нужно дать себе права на полный доступ к виртуальным COM-портам, создав в/etc/udev/rules.d/файлик с именем80-ftdi.rulesследующего содержания:
        SYSFS{idVendor}=="0403", SYSFS{idProduct}=="6001", MODE="660", GROUP="ftdi-user"

        USB Vendor ID и Product ID можно для верности уточнить командойlsusb, если Arduino подключена к компу:

        ...
        Bus 005 Device 012: ID 0403:6001 Future Technology Devices International, Ltd FT232 USB-Serial (UART) IC
        ...

        Потом нужно создать группуftdi-userи добавить себя в неё:
        sudo addgroup ftdi-user
        sudo usermod
        -a -G ftdi-user <имя пользователя>
        после чего нужно перелогиниться.

        Ну и, чтобы службаudevузнала про изменения, нужно либо перезагрузить комп, либо вызвать
        sudo udevadm trigger

      В статье про программирование AVR на C товарищ noonv уже, как пользоватьсяavrdude, а я опишу лишь заливку загрузчика на Arduino Diecimila с ATmega168:
      1. Устанавливаем необходимые для быстрой прошивки загрузчика fuse-биты: кварц > 8 МГц, встроенный делитель на 8 выключен:
        avrdude -c ftbb -P ft0 -p m8 -B 9600 -U hfuse:w:0xdd:m -U lfuse:w:0xff:m
      2. Заливаем загрузчик:
        avrdude -c ftbb -P ft0 -p ATMega168 -B 19200 -U flash:w:ATmegaBOOT_168_diecimila.hex

      Но консоль не всегда лучше гуя, особенно для задания fuse-битов, и есть гораздо более удобный и надёжный способ — воспользоваться программой SinaProg, разработанной иранскими программистами. Их сайт давно сдох, зато программа живёт и здравствует и по сей день. О её настройке подробноу Di Halt’а, ну а мы не будем терять времени и возьмём готовую настроеннуюс пропатченным avrdude (в сборке Di Halt’а ошибка в одном из конфигов). Версии под Linux, увы, не существует.



      В секцииHex fileвыбирается hex-файл, который нужно залить или считать. Записывать и считывать можно как память программ (Flash), так и энергонезависимую (EEPROM). В секцииDeviceнужно указать конкретный МК, в секцииProgrammer— программатор (у нас это ftbb), порт (FTDI0) и скорость порта (9600).

      Ну и то, ради чего стоит пользоваться этой программой — секцияFuses. В раскрывающемся списке можно выбрать предопределённые конфигурации fuse-битов, которые задаются в Fuse.txt. Но самое главное открывается нашему взору по нажатию кнопкиAdvanced:



      Здесь можно набивать fuse-байты вручную, а можно нажимать на кнопки «C» рядом со значением байтов и выставлять фьюзы тыканием галочек с описаниями. Для заливки загрузчика сначала нажмём кнопкуRead, чтобы считать текущие значения fuse, а затем настроимLow fuse: частота кварца — более 8 МГц, время старта МК — 65 мс, делитель на 8 выключен:



      После настройки жмём кнопкуWriteи ждём появления надписи «Writing Fuses… OK».

      Теперь можно в главном окне в секцииHex fileвыбирать файл загрузчикаATmegaBOOT_168_diecimila.hexи в секцииFlashжать кнопкуProgram. Если в процессе будут возникать ошибки, то над индикатором прогресса есть кнопка ">", открывающая сбоку в окне лог работыavrdude.

      А давайте-ка прошьём какой-нибудь другой МК — например,ATtiny13.



      Поставим МК на макетку, подсоединим к ней все линии разъёма ISP от нашего bit-bang-программатора, прицепим светодиод через резистор на 500 Ом к 3-й ножке (DB4) и подтянемRESETк питанию резистором на 10 кОм:





      Пишем в любимом текстовом редакторе простой код простой мигалки светодиодом в файлblink.c:
      #include <avr/io.h>
      #include <util/delay.h>

      enum { LED_BIT = 1 << PORTB4 };

      int main()
      {
        DDRB
      |= LED_BIT;

       
      while (1)
       
      {
          PORTB
      |= LED_BIT;
          _delay_ms
      (500);
          PORTB
      &= ~LED_BIT;
          _delay_ms
      (500);
       
      }
      }

      Компилируем:
      avr-gcc -DF_CPU=1200000 -Os -mmcu=attiny13 blink.c -o blink.out

      Делаем hex-файл прошивки:
      avr-objcopy -O ihex blink.out blink.hex

      Заливаем на МК:
      avrdude -c ftbb -P ft0 -p attiny13 -B 9600 -U flash:w:blink.hex

      Загрузка пошла:
      [email protected]:~/tmp$ avrdude -c ftbb -P ft0 -p attiny13 -B 9600 -U flash:w:blink.hex
      avrdude: BitBang OK
      avrdude: pin assign miso 3 sck 5 mosi 6 reset 7
      avrdude: drain OK
      ft245r:  bitclk 4800 -> ft baud 2400
      avrdude: AVR device initialized and ready to accept instructions
      Reading | ################################################## | 100% 0.00s
      avrdude: Device signature = 0x1e9007
      avrdude: NOTE: FLASH memory has been specified, an erase cycle will be performed
              To disable this feature, specify the -D option.
      avrdude: erasing chip
      ft245r:  bitclk 4800 -> ft baud 2400
      avrdude: reading input file "blink.hex"
      avrdude: input file blink.hex auto detected as Intel Hex
      avrdude: writing flash (78 bytes):
      Writing | ################################################## | 100% 0.64s
      avrdude: 78 bytes of flash written
      avrdude: verifying flash memory against blink.hex:
      avrdude: load data flash data from input file blink.hex:
      avrdude: input file blink.hex auto detected as Intel Hex
      avrdude: input file blink.hex contains 78 bytes
      avrdude: reading on-chip flash data:
      Reading | ################################################## | 100% 0.50s
      avrdude: verifying ...
      avrdude: 78 bytes of flash verified
      avrdude: safemode: Fuses OK
      avrdude done.  Thank you.

      Перевтыкаем Arduino в USB и наблюдаем мигание светодиода при условии, что нет ошибок подключения (:

      Вот так, путём нехитрых манипуляций руками и мозгами, можно сделать себе USB ISP-программатор, так что не нужно париться с отсутствием LPT на современных компах и COM-порта почти на любом ноутбуке — уж USB-то есть везде.
      Попросили тут помощи по теме, решил оформить постом. 
      Итак, я как-тоо входных сдвиговых регистрах серии 74HC165 (далее — просто «регистры»), но как-то не учёл сложности включения их каскадом («гирляндой», daisy chain). Объясню, в чём суть. Допустим нам нужно обработать 24 кнопки, а у регистра всего 8 входов. Первое решение, которое приходит в голову — обработать данные с 3х регистров независимо. Но это решение плохо тем, что на каждый регистр нужно выделять свою линию SS для работы по SPI — по одной линии на каждый вывод SH/~LD, а использовать одну общую не получится, так как по шине SPI полезут биты сразу с трёх регистров, и программа прочитает мусор.

      Для решения этой проблемы придумали каскадное подключение. Идея такова: раз при каждом дёргании CLK биты, захваченные со входов регистра, проталкиваются к выходу (QH и ~QH), то почему бы не заставить регистр проталкивать после своих битов ещё и биты, захваченные с внешнего источника — ещё одного сдвигового регистра:



      При этом используется всего одна линия SS на все регистры, благодаря чему подключение к Arduino требует всего 3 провода — SS, SCK и MISO.
      Проиллюстрирую такое подключение на примере 3х регистров (таких же, как и в предыдущей статье по ним). Смотрим на расположение выводов:



      Соединяем регистры, как в предыдущей статье, добавив для каскада следущее:
      • Вывод SER третьего регистра соединяем с GND, чтобы с SER читались нули, а вывод QH подключем к SER второго.
      • Вывод QH второго регистра соединяем с SER первого.
      • Вывод QH первого регистра соединяем с MISO на Arduino (пин 12).
      SCK (пин 13) и SS (пин 10) — общие для всех регистров. Ну и для проверки на вшивость соединим через резисторы 10 кОм (у меня под рукой были только 510 Ом, если что) выводы 1C, 2B и 3A с землёй, а выводы 1E, 2F и 3G — с питанием (+5 В) и напишем скетч, в котором будем проверять состояние только этих выводов, ибо с остальных будет читаться мусор — они же не подключены. Для проверки на изменение состояний входов можно потыкать их проводком, соединённым с землёй или питанием через резистор.

      Как это выглядит у меня:



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

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

      SPI_Bus reg
      (_24bit, 10, MSBFIRST);


      void setup()
      {
       
      Serial.begin(9600);
        reg
      .setSelectionPolicy(SPI_Bus::SELECT_BEFORE);
      }


      void loop()
      {
       
      static uint32_t last_input_states = 0;
       
       
      /* Читаем наши 24 бита разом. В C++ нет типов данных для 24-битных чисел,
         * поэтому используем read32bits(), который считает столько бит,
         * сколько мы указали при создании объекта reg (24 бита), но вернёт их
         * одним 32-битным значением.
         */

        uint32_t states
      = reg.read32bits();

       
      if (states != last_input_states)
       
      {
          uint32_t changed
      = states ^ last_input_states;
          last_input_states
      = states;
         
         
      for (int i = 0; i < reg.bandwidth() * 8; ++i)
         
      {
           
      /* А вот тут проверяем только нужные нам входы */
           
      if ((i == 0   || // 3A
                 i
      == 6   || // 3G
                 i
      == 9   || // 2B
                 i
      == 13  || // 2F
                 i
      == 18  || // 1C
                 i
      == 20) && // 1E
               
      (changed & 1))
           
      {
             
      Serial.print("#");
             
      Serial.print(i);
             
      Serial.print(" -> ");
             
      Serial.println(states & 1);
           
      }
           
            changed
      >>= 1;
            states
      >>= 1;
         
      }
       
      }
      }

      That's all, folks! (:

      Исходнички:
      • Библиотекаи необходимая ей. Также, их можно клонировать изна GitHub.
      • Скетч:,.
      • уже давно и прочно вошёл в нашу жизнь в качестве удобного протокола связи различных устройств: мобильных телефонов, ноутбуков, КПК, гарнитур, мышей, клавиатур… Список можно ещё долго продолжать. Обычно эту технологию интегрируют в свои продукты крупные производители электроники в виде малюсенькой микросхемы в корпусе BGA или QFN. А как быть нам, простому электронному люду, не владеющими тёмными силами создания 4-слойных плат и кунг-фу микромонтажа? Для этого есть
        от наших китайских собратьев.

        Этот модуль представляет собой плату размером 2.7x1.4 см, с 34 выводами с шагом 1.5 мм, расположенных по периметру платы, и имеет на одном из торцов антенну:




        На плате расположен чип BC417 от компании Cambridge Silicon Radio, который обеспечивает аппаратную поддержку стека Bluetooth 2.0+EDR (Enhaced Data Rate), а также флэш-память ES29LV800DB-70WGI от Excel Semiconductor на 8 Мбит (1 МБ), хранящая прошивку и настройки.
        С оригинальной китайской прошивкой модуль умеет работать в двух режимах: простого «радиоудлинителя» и управления AT-командами. В первом случае всё предельно просто — включаешь модуль, подключаешься к нему с компа или иного устройства, умеющего делать COM-порт по Bluetooth, и шлёшь в этот порт данные. Во втором режиме можно управлять модулем посредством AT-команд вида «AT+КОМАНДА» — например, команда «AT+NAME?» позволяет узнать имя модуля, но об этом позже.

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


        • TX, RX, CTS, RTS — линии UART; CTS и RTS недоступны в данной прошивке
        • PCM_CLK, PCM_OUT, PCM_IN, PCM_SYNC — линии для приёма-передачи звука (недоступны)
        • AIO0, AIO1 — линии I/O общего назначения (недоступны)
        • RESET — линия сброса (активируется логическим нулём)
        • 3.3 V, GND — питание, земля
        • NC — не подсоединён (Not Connected)
        • USB_D+, USB_D- — линии данных USB (недоступны)
        • CSB, MOSI, MISO, CLK — линии SPI (CSB — это SS, Slave Select), используются для прошивки чипа
        • PIO0 — разрешение/запрет RX
        • PIO1 — разрешение/запрет TX
        • PIO2-PIO7, PIO10 — линии I/O общего назначения
        • PIO8 — для светодиода, показывающего состояние модуля: светодиод мигает с разной скоростью в зависимости от того, чем занят модуль — опросом Bluetooth-устройств, ожиданием или чем-то ещё
        • PIO9 — для светодиода, показывающего статус соединения: горит, если установлено соединение с другим Bluetooth-устройством
        • PIO11 — для управления режимом работы: по умолчанию режим простого удлинителя UART, а если подать на него логичскую 1 — режим AT-команд
        Кстати, модуль питается от 3.3 В, но его линии I/O могут работать и с 5-вольтовой логикой, что позволяет подключать его UART к Arduino без заморочек.

        Но это ещё не всё: шаг между выводами у модуля — 1.5 мм, что категорически не сочетается с шагом отверстий в макетных платах, и для прототипирования придётся припаивать проводки. Но нас такой вариант не устроил, и пришлось немного напрячься и сделать плату-breakout для модуля. Zotlberg взял на себя эту задачу и успешно её решил вот в таком виде:



        На плате установлен стабилизатор на 3.3 В, так что можно запитать модуль стандартным длянапряжением 5 В, подключив питание к выводу, обозначенному 5V. Принципиальная схема платы:



        А вот ещё исходники в формате DipTrace:и.

        Итак, плата готова, приступим к работе с модулем. Ставим breakout в макетную плату и подключаем следующим образом:
        • 3.3v — к 3.3 В от Arduino
        • GND — к GND Arduino
        • RX — к TX Arduino
        • TX — к RX Arduino


        Включаем Arduino с подключенным модулем. Если в вашем компьютере нет встроенного Bluetooth-контроллера, воспользуйтесь Bluetooth USB-донглом вроде этого:

        Теперь займёмся программной частью. Для начала зальём в Arduino такой скетч:


        enum { LED_PIN = 13 };
        enum LedState { LED_ON, LED_OFF, LED_BLINK };

        LedState led_state;

        void setup()
        {
          led_state
        = LED_OFF;
          pinMode
        (LED_PIN, OUTPUT);
         
         
        Serial.begin(38400);
        }

        void loop()
        {  
         
        if (Serial.available())
         
        {
           
        char command = Serial.read();
           
           
        switch (command)
           
        {
             
        case '1': led_state = LED_ON; break;
             
        case '0': led_state = LED_OFF; break;
             
        case '*': led_state = LED_BLINK; break;
             
             
        default:
             
        {
               
        for (int i = 0; i < 5; ++i)
               
        {
                  digitalWrite
        (LED_PIN, HIGH);
                  delay
        (50);
                  digitalWrite
        (LED_PIN, LOW);
                  delay
        (50);
               
        }
             
        }
           
        }
         
        }
         
         
        switch (led_state)
         
        {
           
        case LED_ON: digitalWrite(LED_PIN, HIGH); break;
           
        case LED_OFF: digitalWrite(LED_PIN, LOW); break;
           
           
        case LED_BLINK:
           
        {
             
        static unsigned long start_millis = 0;
             
             
        if (millis() - start_millis >= 300)
             
        {
                start_millis
        = millis();
                digitalWrite
        (LED_PIN, !digitalRead(LED_PIN));
             
        }
           
        }
         
        }
        }

        Windows 7

        Кликните правой кнопкой мыши на значке Bluetooth в трее:



        В появившемся списке устройств выбираем наш модуль и жмёмДалее:



        В следующем окне выберите вариант ввода PIN-кода вручную:



        Введите код «1234» и нажмитеДалее:



        Если авторизация пройдёт успешно, то вы увидите следующее окно:



        Откройте список Bluetooth-устройств и зайдите в свойства модуля:





        На вкладке «Службы» поставьте галочку напротив профиля последовательного порта и нажмитеОК:



        Windows установит нужный драйвер и покажет облачко с соответствующим сообщением и названием порта:



        Теперь вам понадобится программа-терминал с хорошей настраиваемостью. Неплохим вариантом будет лёгкая и бесплатная (даже для коммерческого использования) программа. Установите её, запустите и нажмитеSettings. В настройках поставьте:
        Port— COM6 (ну или какой порт Windows назначила модулю у вас)
        Baud rate— 38400
        Transmitted text— Append nothing



        ЖмитеOKи в главном окне программыDisconnected — click to connect:



        Всё — можно слать символы:



        Модуль должен зажигать светодиодLв ответ на символ '1', тушить на '0' и мигать им на '*' c частотой примерно два раза в секунду. При вводе любых других символов светодиод должен около секунды мигать с большей частотой.

        Ubuntu Linux 11.04

        Запустите bluetooth-wizard и нажмите «Вперёд»:



        В следующем окне дождитесь нахождения вашего модуля и нажмите кнопку «Параметры PIN»:



        Выберите PIN-код 1234 и закройте окно:



        В окне со списком устройств Жмите «Вперёд», и если будут ошибки, повторите операцию с PIN-кодом. В случае успеха вы увидите такое окно:



        Запустите сканирование доступных Bluetooth-устройств:

        $ hcitool scan 
        Scanning ...
                       
        00:11:04:29:02:55       H-C-2010-06-01

        H-C-2010-06-01 — это имя нашего устройства (может быть также «HC-05»). Создаём устройство для работы по протоколу RFCOMM:

        $ sudo rfcomm bind /dev/rfcomm0 00:11:04:29:02:55

        Всё, теперь можно обмениваться данными через виртуальный COM-порт/dev/rfcomm0. Можно прямо в консоли:

        $ stty -F /dev/rfcomm0 38400  # устанавливаем скорость порта
        $ echo
        -n '1' > /dev/rfcomm0  # пишем туда символ '1', без символов перевода строки


        Также можно воспользоваться замечательной программойcutecom: в поле Device введите/dev/rfcomm0, в спискеBaud rateпоставьте скорость38400, внизу окна в списке выберите вариантNo line end. Теперь жмитеOpen deviceи шлите через полеInputсимволы:



        Модуль должен зажигать светодиод L в ответ на символ '1', тушить на '0' и мигать им на '*'.

        AT-команды

        Чтобы задействовать такие функции модуля, как опрос «соседних» Bluetooth-устройств, установку другой скорости UART и прочие, необходимо использовать AT-команды, описанные в этом. Сразу предупреждаю: этот даташит очень китайский — многие вещи просто не описаны, для некоторых команд не описано даже их поведение, оформление кривое, куча ошибок из-за невнимательного copy&paste. Но другие даташиты на этот модуль ещё хуже }:[=]

        Все команды имеют видAT+КОМАНДА,AT+КОМАНДА?илиAT+КОМАНДА=ПАРАМЕТРЫи должны оканчиваться комбинацией CR+LF (символы с кодами 0x0D и 0x0A, '\r' и '\n'). Примеры команд:

        AT+NAME?— спросить у модуля его имя
        AT+ROLE=1— задать роль master
        AT+INQ— запустить опрос соседних Bluetooth-устройств

        Отвечает модуль так:
        • В случае успеха:
          +КОМАНДА: ОТВЕТ
          OK


          Строки, начинающейся с '+', может и не быть, если команда не должна ничего возвращать. Например, на команду AT+NAME? модуль ответит так:
          +NAME:H-C-2010-06-1
          OK


          а на команду AT (тест) просто:
          OK
        • В случае ошибки:
          FAIL
          или
          ERROR:(КОД ОШИБКИ)

          Самый первый код ошибки 0 значит, что формат команды неверен — на командуAT+ЖАХНИ, к примеру, модуль ответит:
          ERROR:(0)

          Кстати, зацените грустный смайл :( в этом ответе.

          Многие команды имеют как форму запроса, так и форму установки параметра. Опять же, командаAT+NAME?возвращает имя модуля, тогда какAT+NAME=ИМЯего устанавливает.

          Для того, чтобы модуль мог принимать AT-команды, нужно его перевести в соответствующий режим — для этого нужно установить выводPIO11в логическую 1 (подтянув к питанию, например). После этого соединяйтесь с модулем, используя Arduino в качестве переходника USB-UART, аккуратно вытащив из него микроконтроллер (не сломайте ножки МК!)
          Выставьте в терминалке конец строкиCR+LFи попробуйте скормить модулю пару команд. Выглядеть это должно примерно так:



          Всего команд 36, описаны они все в даташите. Заданные командами настройки модуль сохраняет в своей flash-памяти, так что после его можно использовать без повторной настройки.

          А вообще, работать напрямую с AT-командами, да ещё на микроконтроллере, довольно сложно и уныло, так что я основательно вкурил ядрёный китайский даташит и накатал библиотекуBluetooth_HC05, которую можно скачать вили клонировать сна GitHub. В библиотеке я реализовал все функции модуля, документация по библиотеке лежит в директории doc.

          И напоследок: возможность перепрошивки аналогичного модуля обсуждали, ну и в сообществе easyelectronics есть небольшая.
        • Да, именно такое сочетание никак не связанных между собой девайсов мы рассмотрим сегодня. Объектами для изучения будут:
          • Энкодер механический инкрементальный
          • Шкала линейная 10-разрядная
          Энкодер по-научному он называется «преобразователь угол-код», или сокращённо ПУК (:
          Название говорит само за себя: он позволяет перевести угол поворота в некий код. Абсолютные энкодеры выдают непосредственно угол, абсолютное положение, тогда как инкрементальный — определённое число щелчков на оборот и направление.

          Вообще, энкодеры бывают механические, оптические или магнитные.
          Внутри находятся два датчика (расположенные друг за другом над кодирующим диском), которые при вращении ручки последовательно замыкают крайние выводы на средний.
          И если средний подключить к питанию, то при вращении ручки на крайних увидим следующую картину:



          Здесь изображены датчики которые реагируют на «дырки» — это могут быть оптические датчики работающие на просвет или отражение, магнитные, реагирующие на намагниченность диска или контактные, размыкаемые диэлектрическим диском.

          А как насчёт рассматриваемого нами экземпляра?



          При вращении ручки вращается скользящий контакт сложной формы над тремя секторами, которые соединены с соответствующими выводами. Вот он и замыкает средний сектор на контактные площадки то левого, то правого вывода в нужной последовательности.

          Если записать уровни напряжения как биты 2-битного числа, то последовательность двоичных кодов за один «щелчок» ручкой энкодера будет такой:

          00 - 0
          01 - 1
          11 - 3
          10 - 2
          Если крутить ручку в обратную сторону, последовательность кодов тоже будет обратной. На первый взгляд, особого смысла в этой последовательности нет. А на второй взгляд, это(:
          Приведённая выше последовательность кодов — это ничто иное, как числа от 0 до 3, записанные в коде Грея. То есть, после декодирования мы получим такую последовательность:

          00 - 0
          01 - 1
          10 - 2
          11 - 3
          Если крутить ручку энкодера в другую сторону, получим обратную последовательность: 3, 2, 1, 0. То есть, зная последовательность чисел, поступающую с энкодера, можно легко понять, в какую сторону крутится ручка.

          Рассматриваемый энкодер EC12E24404A6 имеет 24 отсчёта, то есть его выводы сменят состояние от 00 до 11 ровно 24 раза за один полный оборот ручки, а это значит, что за один щелчок ручка поворачивается на 15 градусов.

          С теорией ознакомились — пора проверять на практике. Подсоединим энкодер к Arduino следующим образом:
          • средний вывод — к +5 В
          • остальные два вывода — прижаты к земле (GND) через резисторы 10 кОм и подключены к пинам 2 и 3 Arduino


          При нейтральном положении ручки с энкодера будут считываться нули. Наваяем простой скетч, считывающий значения с энкодера и показывающий направление вращения. В статье на Википедии приведена функция декодирования двоичного кода Грея в обычный двоичный, которой мы и воспользуемся, лишь слегка улучшив стиль кода.

          /* Пины, к которым подключен энкодер */
          enum { ENC_PIN1 = 2, ENC_PIN2 = 3 };

          void setup()
          {
           pinMode
          (ENC_PIN1, INPUT);
           pinMode
          (ENC_PIN2, INPUT);
           
           
          Serial.begin(9600);
          }

          /* Функция декодирования кода Грея, взятая с Википедии.
          * Принимает число в коде Грея, возвращает обычное его представление.
          */

          unsigned graydecode(unsigned gray)
          {
           
          unsigned bin;

           
          for (bin = 0; gray; gray >>= 1)
              bin
          ^= gray;

           
          return bin;
          }

          void loop()
          {
           
          static uint8_t previous_code = 0; // предыдущий считанный код
           
           
          /* gray_code - считанное с энкодера значение
            * code - декодированное значение
            */

           uint8_t gray_code
          = digitalRead(ENC_PIN1) | (digitalRead(ENC_PIN2) << 1),
                   code
          = graydecode(gray_code);
           
           
          /* Если считался нуль, значит был произведён щелчок ручкой энкодера */
           
          if (code == 0)
           
          {
             
          /* Если переход к нулю был из состояния 3 - ручка вращалась
              * по часовой стрелке, если из 1 - против.
              */

             
          if (previous_code == 3)
               
          Serial.println("->");
             
          else if (previous_code == 1)
               
          Serial.println("<-");
           
          }
           
           
          /* Сохраняем код и ждём 1 мс - вполне достаточно опрашивать энкодер
            * не более 1000 раз в секунду.
            */

           previous_code
          = code;
           delay
          (1);
          }

          Залейте скетч и откройте Serial monitor. Повращайте ручку энкодера и посмотрите, как скетч определяет направление вращения. Выглядеть это будет так:



          Ну, покрутили ручкой, а дальше что? А дальше — покрутим ещё раз, но на сей раз визуализировав процесс при помощи 10-разрядной шкалы DC-10GWA:



          Шкала представляет собой банальную сборку независимых светодиодов с катодами со стороны надписи на корпусе. Ставить 10 резисторов к шкале на макетную плату мне было жутко влом, так что я применил резисторную сборку из 9 резисторов по 470 Ом и один резистор на 510 Ом. Сборка резисторов выглядит так:



          Общий провод — там, где нарисован белый ромбик. Подключаем общий провод к земле, оставшиеся 9 выводов — к анодам шкалы, а один оставшийся анод подключаем к земле отдельным резистором. Катоды шкалы подключаем последовательно к пинам 4-13 Arduino. Далее шьём такой скетч:

          enum
          {
           ENC_PIN1
          = 2,
           ENC_PIN2
          = 3,
           BAR_FIRST_PIN
          = 4, // первый пин, поключённый к шкале
           BAR_PINS_COUNT
          = 10 // число пинов (делений шкалы)
          };

          void setup()
          {
           pinMode
          (ENC_PIN1, INPUT);
           pinMode
          (ENC_PIN2, INPUT);

           
          /* Конфигурируем все пины, к которым подключена шкала, на выход */
           
          for (int i = 0; i < BAR_PINS_COUNT; ++i)
             pinMode
          (BAR_FIRST_PIN + i, OUTPUT);

           
          Serial.begin(9600);
          }

          unsigned graydecode(unsigned gray)
          {
           
          unsigned bin;

           
          for (bin = 0; gray; gray >>= 1)
             bin
          ^= gray;

           
          return bin;
          }

          void loop()
          {
           
          static uint8_t previous_code = 0;
           
          static int bar_level = 0; // "уровень", который показывает шкала

           uint8_t gray_code
          = digitalRead(ENC_PIN1) | (digitalRead(ENC_PIN2) << 1),
                   code
          = graydecode(gray_code);

           
          if (code == 0)
           
          {
             
          /* Увеличиваем или уменьшаем "уровень" шкалы, запихивая его
              * в диапазон от 0 до 10 стандартной для Arduino функцией constrain().
              */

             
          if (previous_code == 3)
             
          {
               bar_level
          = constrain(bar_level + 1, 0, BAR_PINS_COUNT);
               
          Serial.println(bar_level);
             
          }
             
          else if (previous_code == 1)
             
          {
               bar_level
          = constrain(bar_level - 1, 0, BAR_PINS_COUNT);
               
          Serial.println(bar_level);
             
          }
           
          }

           
          /* Зажигаем количество полосок на шкале, соответствующее
            * текущему "уровню", гасим остальные полоски.
            */

           
          for (int i = 0; i < BAR_PINS_COUNT; ++i)
             digitalWrite
          (BAR_FIRST_PIN + i, (i < bar_level ? HIGH : LOW));

           previous_code
          = code;
           delay
          (1);
          }

          Теперь в Serial monitor будет закидываться текущий «уровень», который будет отображаться на шкале:



          Так как самый правый катод шкалы оказался подключен к 13 пину, при выкручивании максимального уровня дополнительно загорается светодиод L (:

          Шкала занимает аж 10 выводов, а ведь хочется подключать и другие устройства — эту проблему можно решить, используя сдвиговые регистры 74HC595, о использовании которых я писали.

          Кроме зажигания шкалы, у энкодеров могут найтись и более интересные применения: регулировка звука в магнитоле, навигация в меню, и вообще можно задавать всякие дискретные величины. Ещё энкодеры применяют для получения обратной связи от вращающихся механизмов (моторов, например), обычно оптические и магнитные. Оптический можно сделать из двухили дискретных ИК-передатчиков и приёмников, а магнитный — из датчиков Холла (или). Механические энкодеры тут не подойдут — сотрутся контакты.

          UPDATE: по однократной просьбе представителя прекрасной половины человечества выкладываю видео-демонстрацию (:

          • ,
          • ,
          • ,
          • +6
          • 31 августа 2011, 16:19

 

Получите рост посещаемости, рост ссылочной массы, рост ТИЦ до 80, PR до 4base‑all.ru   А мы её уже нашли! Заходи и выбирай!price.ua   Материнские платы. Сравните все цены магазинов. Экономьте время и деньги!vcene.ua

 

        • Комментарии (15)

          /
          +
          0
          А есть идеи как определить направление вращения с помощью датчиков Холла? Нужно определять кол-во оборотов и направление вращения лебедки.

          • 31 августа 2011, 18:38
          +
          +1
          Ну, тут принцип будет такой же: размещаешь два датчика на небольшом расстоянии друг от друга на неподвижной части, а на лебёдке — магнит. При вращении лебёдки и прохождении магнита напротив датчиков они будут поочерёдно срабатывать, выдавая тот же код Грея. Как на цветной схеме вначале статьи, только вместо «дырки» на вращающемся диске — магнит.
          Может, следующую статью посвятить датчикам Холла? (:

          • 31 августа 2011, 18:50
          +
          0
          А какое максимальное расстояние между датчиком и магнитом?
          Да, статья про датчики Холла была бы интересна)

          • 31 августа 2011, 19:03
          +
          0
          А какое максимальное расстояние между датчиком и магнитом?

          Этого я пока не знаю, но предполагаю, что около сантиметра или даже больше. Как с датчиками поиграю — будет ясно (:

          • 31 августа 2011, 19:13
          0
          Буду с нетерпением ждать статью:)
          Расстояние важно так как лебедка достаточно быстрая ( около 100об\мин) и мощная (около 250тонн). Не хотелось бы чтобы при работе разнесло конструкцию из датчиков)

          • 31 августа 2011, 19:16
          +
          0
          Мне не нравятся такие схемы, как в этом топике и поэтому я создал схему в Fritzing.

          «» на

          За место энкодера тут потенциометр!!! Просто в стандартной библиотеке элементов нет энкодера, поэтому изобразил так. Надо считать, что энкодер расположен ножками к нам.
          Датчик Холла — это датчик магнитного поля. Он был так назван из-за принципа своей работы — : если в магнитное поле поместить пластину с протекающим через неё током, то электроны в пластине будут отклоняться в направлении, перпендикулярном направлению тока. В какую именно сторону будут отклоняться электроны, зависит от полярности магнитного поля:

           
          1. Электроны
          2. Пластина
          3. Магниты
          4. Магнитное поле
          5. Источник тока
          Различная плотность электронов на сторонах пластины создаёт разность потенциалов, которую можно усилить и измерить, что датчики Холла и делают.

          Датчики Холла (далее просто ДХ) бывают аналоговыми и цифровыми. Аналоговый преобразует индукцию магнитного поля в напряжение, знак и величина которого будут зависеть от полярности и силы поля. Цифровой же выдаёт лишь факт наличия/отсутствия поля, и обычно имеет два порога: включения — когда значение индукции выше порога, датчик выдает логическую единицу; и выключения — когда значение ниже порога, датчик выдаёт логический ноль. Наличие зоны нечувствительности между порогами называется  и служит для исключения ложного срабатывания датчика на всяческие помехи — аналогично работает цифровая электроника с логическими уровнями напряжения. Цифровые ДХ делятся ещё на униполярные и биполярные: первые включаются магнитным полем определённой полярности и выключаются при снижении индукции поля; биполярные же включаются полем одной полярности, а выключаются полем противоположной полярности.

          Аналоговый ДХ 

          Его размер — всего 4x3 мм, и он имеет три вывода:



          Как видно, питание датчику нужно биполярное — тогда на южный полюс магнита датчик будет реагировать положительным уровнем на выходе, на северный — отрицательным, а на отсутствие поля — нулевым. Однако можно обойтись однополярным питанием — в этом случае уровень на выходе (Vo) в половину напряжения питания (Vdc/2) будет означать отсутствие магнитного поля, Vo > Vdc/2 — южный полюс, Vo < Vdc/2 — северный.

          Характеристики при однополярном питании 5 В и температуре от -40 до 85 °C:
          • Потребляемый ток: от 6 до 10 мА
          • Выходной ток: от 1.0 до 1.5 мА
          • Выходное напряжение: от 1.0 до 1.75 мВ/Гс, в среднем 1.4 мВ/Гс (милливольт на)
          • Нулевая точка: от 2.25 до 2.75 В, в среднем 2.5 В
          • Магнитный диапазон: от ±650 Гс до ±1000Гс
          • Время отклика: 3 мс
          Из этих данных следует, что при стандартном питании от Arduino (+5V, GND) при 25 °C датчик в отсутствие магнитного поля будет выдавать 2.5 В, а на поле силой 1000 Гс — 2.5 ± 1.4 В. Соответственно, если воспользоваться АЦП, разброс значений будет примерно в диапазоне от 280 до 800 со нулевой точкой в 512.

          Приступим к экспериментам. Подключаем вывод “+” к 5V Arduino, вывод “-” к GND, оставшийся — к Analog 0:



          Заливаем в Arduino следующий скетч:

          void setup()
          {
           
          Serial.begin(9600);
          }

          void loop()
          {
           
          Serial.println(analogRead(0));
            delay
          (500);
          }

          Не спеша подносим магнит вплотную сначала одним полюсом, потом другим, глядя в Serial monitor:



          Цифровой биполярный ДХ 

          Выглядит он точно так же, как и аналоговый, даже выводы расположены так же:



          Тут можно не бояться, биполярный он только в магнитном смысле, а питание ему можно подавать вполне себе обычное, однополярное. К слову, питание этот датчик принимает в довольно широком диапазоне — от 3.8 до 24 В, а ток может отдавать до 100 мА, что позволяет непосредственно от него запитывать управляемые им устройства (например, реле). Чувствительность у него почти точь-в-точь как у аналогового SS49E: от -600 Гс до -1000 Гс (северный полюс магнита) и от 600 Гс до 1000 Гс.

          Подключается он чуть посложнее, чем аналоговый: выход датчика Q нужно подтянуть к питанию резистором в 10 кОм, так как выход у него с открытым коллектором:



          А вот и суперсложное подключение, где выход Q подключен к цифровому пину 2:



          Зальём в Arduino ещё один крутой скетч:

          void setup()
          {
           
          Serial.begin(9600);
          }

          void loop()
          {
           
          static uint8_t prev_state = LOW;

           uint8_t state
          = digitalRead(2);

           
          if (state != prev_state)
           
          {
              prev_state
          = state;
             
          Serial.println(state == LOW ? "OFF" : "ON");
           
          }
          }

          Теперь подносим магнит то одним полюсом, то другим и смотрим в Serial monitor:



          Обратите внимание — датчик не переключается, пока не поднесёшь магнит другим полюсом, а ещё он очень чувствительный и переключается магнитом, вытащенным из дохлого CD-ROM’а, на расстоянии около 2 см!

          Применение

          Датчики Холла используются в качестве бесконтактных выключателей, как замена герконам, для бесконтактных замеров тока в проводниках, управления моторами, чтения магнитных кодов, измерения уровня жидкости (магнитный поплавок) и т.д.

          Ну а я, имея два цифровых биполярных ДХ, сделаю бесконтактный магнитный . Принцип прост: на вращающийся диск лепим рядышком два магнита разными полюсами вверх (для униполярных ДХ хватит одного), а над ними размещаем цифровые ДХ и снимаем показания. Можно использовать скетч из статьи про энкодеры, но смотреть на стрелочки скучно, ведь хочется ещё посчитать обороты, так что напишем новый:

          #include <LiquidCrystal.h>

          LiquidCrystal lcd(13, 12, 11, 10, 9, 8);

          /* Пины, к которым подключен энкодер */
          enum { ENC_PIN1 = 2, ENC_PIN2 = 3 };

          enum { FORWARD = 1, BACKWARD = -1 };

          /* Если что, revolutions здесь и далее - обороты, а не революции (: */
          long revolutions = 0, revolutions_at_last_display = 0;
          int direction = FORWARD;
          uint8_t previous_code
          = 0;

          /* Реакция на событие поворота */
          void turned(int new_direction)
          {
           
          if (new_direction != direction)
           
          {
              revolutions
          = 0;
              revolutions_at_last_display
          = 0;
           
          }
           
          else
             
          ++revolutions;

            direction
          = new_direction;
          }

          /* Объеденил чтение кода Грея с энкодера с его декодированием */
          uint8_t readEncoder
          (uint8_t pin1, uint8_t pin2)
          {
            uint8_t gray_code
          = digitalRead(pin1) | (digitalRead(pin2) << 1), result = 0;

           
          for (result = 0; gray_code; gray_code >>= 1)
              result
          ^= gray_code;

           
          return result;
          }

          void setup()
          {
            pinMode
          (ENC_PIN1, INPUT);
            pinMode
          (ENC_PIN2, INPUT);

            lcd
          .begin(8, 2);
          }

          void loop()
          {
           
          /* Читаем значение с энкодера */
            uint8_t code
          = readEncoder(ENC_PIN1, ENC_PIN2);

           
          /* Обрабатываем его */
           
          if (code == 0)
           
          {
             
          if (previous_code == 3)
                turned
          (FORWARD);
             
          else if (previous_code == 1)
                turned
          (BACKWARD);
           
          }

            previous_code
          = code;

           
          /* Раз в секунду выводим накопленную информацию */

           
          static unsigned long millis_at_last_display = 0;

           
          if (millis() - millis_at_last_display >= 1000)
           
          {
             
          /* Выводим на экран направление вращения */
              lcd
          .clear();
              lcd
          .print(direction == FORWARD ? ">> " : "<< ");
             
          /* ... скорость вращения в оборотах в секунду */
              lcd
          .print(revolutions - revolutions_at_last_display);
              lcd
          .print("/s");
             
          /* ... и общее число обротов в текущем направлении */
              lcd
          .setCursor(0, 1);
              lcd
          .print(revolutions);
             
              millis_at_last_display
          = millis();
              revolutions_at_last_display
          = revolutions;
           
          }
          }

          Выглядеть конечная установка может так:





          Я разобрал старый нерабочий жёсткий диск и установил на его пластину два магнита от системы позиционирования головки CD-ROMа на расстоянии ~5 мм друг от друга, а датчики разместил на креплении над пластиной, на расстоянии ~15 мм друг от друга. Вот как оно работает:



          Если не нужно знать направление вращения, а хочется просто считать обороты, то можно обойтись вообще одним униполярным датчиком и одним магнитом (:

          TC15-11 - матрица светодиодная 8х8


          — это светодиодная матрица, размерности 8х8 светодиодов :)

          Размеры


          Снизу матрицы, находится два ряда пинов, со стандартным шагом 2.54, что позволяет удобно воткнуть сетодиодную матрицу в две беспаечные макетные платы.

          Осчёт пинов ведётся от угла, на котором сходятся стороны матрицы с выступами.

          Принципиальная схема


          — матричное включение светодиодов.
          Т.о., подключив столбец (Column) на землю и подавая +5V (лучше через токоограничительный резистор) на строчку (Row), мы зажгём светодиод находящийся в заданном узле матрицы.

          Нарисуем выводы матрицы более понятно :)


          Используем 5-вольтовый стабилизатор питания CraftDuino и зажгём светодиод в первой строчке и первом столбце.
          Для этого: подключим 9-й пин (R1) к выходу 5V контроллера, а 13-й пин (C1) через токоограничительный резистор (470 Ом) на выход GND.

          Работает :)
          С этим разобрались, а значит теперь можно подключить светодиодную матрицу к контроллеру Ardunio/CraftDuino для различной световой индикации.
          Лучший вариант, конечно, использовать, но в простом случае, достаточно задействовать почти все порты контроллера Arduino/CraftDuino.

          Вариант подключения



          — обратите внимание, что на картинке диоды подключены наоборот — и для светодиодной матрицы TC15-11 их нужно перевернуть (см. TC15-11 internal circuit diagram выше) :)

          Исходя из другого включения светодиодов в TC15-11, поправим скетчи:

          — скетч выводит «HELLO »:

           
          /*
            * Show messages on an 8x8 led matrix,
            * scrolling from right to left.
            *
            * Uses FrequencyTimer2 library to
            * constantly run an interrupt routine
            * at a specified frequency. This
            * refreshes the display without the
            * main loop having to do anything.
            *
            * http://www.arduino.cc/playground/Main/DirectDriveLEDMatrix
            * edited by noonv
            * http://robocraft.ru
            */


          #include <FrequencyTimer2.h>

          #define SPACE { \
               
          {0, 0, 0, 0, 0, 0, 0, 0},  \
               
          {0, 0, 0, 0, 0, 0, 0, 0}, \
               
          {0, 0, 0, 0, 0, 0, 0, 0}, \
               
          {0, 0, 0, 0, 0, 0, 0, 0}, \
               
          {0, 0, 0, 0, 0, 0, 0, 0}, \
               
          {0, 0, 0, 0, 0, 0, 0, 0}, \
               
          {0, 0, 0, 0, 0, 0, 0, 0}, \
               
          {0, 0, 0, 0, 0, 0, 0, 0} \
           
          }

          #define H { \
               
          {0, 1, 0, 0, 0, 0, 1, 0}, \
               
          {0, 1, 0, 0, 0, 0, 1, 0}, \
               
          {0, 1, 0, 0, 0, 0, 1, 0}, \
               
          {0, 1, 1, 1, 1, 1, 1, 0}, \
               
          {0, 1, 0, 0, 0, 0, 1, 0}, \
               
          {0, 1, 0, 0, 0, 0, 1, 0}, \
               
          {0, 1, 0, 0, 0, 0, 1, 0}, \
               
          {0, 1, 0, 0, 0, 0, 1, 0}  \
           
          }

          #define E  { \
               
          {0, 1, 1, 1, 1, 1, 1, 0}, \
               
          {0, 1, 0, 0, 0, 0, 0, 0}, \
               
          {0, 1, 0, 0, 0, 0, 0, 0}, \
               
          {0, 1, 1, 1, 1, 1, 1, 0}, \
               
          {0, 1, 0, 0, 0, 0, 0, 0}, \
               
          {0, 1, 0, 0, 0, 0, 0, 0}, \
               
          {0, 1, 0, 0, 0, 0, 0, 0}, \
               
          {0, 1, 1, 1, 1, 1, 1, 0}  \
           
          }

          #define L { \
               
          {0, 1, 0, 0, 0, 0, 0, 0}, \
               
          {0, 1, 0, 0, 0, 0, 0, 0}, \
               
          {0, 1, 0, 0, 0, 0, 0, 0}, \
               
          {0, 1, 0, 0, 0, 0, 0, 0}, \
               
          {0, 1, 0, 0, 0, 0, 0, 0}, \
               
          {0, 1, 0, 0, 0, 0, 0, 0}, \
               
          {0, 1, 0, 0, 0, 0, 0, 0}, \
               
          {0, 1, 1, 1, 1, 1, 1, 0}  \
           
          }

          #define O { \
               
          {0, 0, 0, 1, 1, 0, 0, 0}, \
               
          {0, 0, 1, 0, 0, 1, 0, 0}, \
               
          {0, 1, 0, 0, 0, 0, 1, 0}, \
               
          {0, 1, 0, 0, 0, 0, 1, 0}, \
               
          {0, 1, 0, 0, 0, 0, 1, 0}, \
               
          {0, 1, 0, 0, 0, 0, 1, 0}, \
               
          {0, 0, 1, 0, 0, 1, 0, 0}, \
               
          {0, 0, 0, 1, 1, 0, 0, 0}  \
           
          }

          byte col = 0;
          byte leds[8][8];

          // pin[xx] on led matrix connected to nn on Arduino (-1 is dummy to make array start at pos 1)
          //             00 01 02 03 04  05  06  07  08  09  10  11  12 13 14 15 16  
          int pins[17]= {-1, 5, 4, 3, 2, 14, 15, 16, 17, 13, 12, 11, 10, 9, 8, 7, 6};

          // col[xx] of leds = pin yy on led matrix
          //                  1         2        3        4         5         6         7         8
          int cols[8] = {pins[13], pins[3], pins[4], pins[10], pins[06], pins[11], pins[15], pins[16]};

          // row[xx] of leds = pin yy on led matrix
          //                  1        2         3        4         5        6        7        8
          int rows[8] = {pins[9], pins[14], pins[8], pins[12], pins[1], pins[7], pins[2], pins[5]};

          const int numPatterns = 6;
          byte patterns[numPatterns][8][8] = {
             H
          ,E,L,L,O,SPACE
          };

          int pattern = 0;

          void setup() {
             
          // sets the pins as output
             
          for (int i = 1; i <= 16; i++) {
               pinMode
          (pins[i], OUTPUT);
             
          }

             
          // set up cols and rows
             
          for (int i = 1; i <= 8; i++) {
               digitalWrite
          (cols[i - 1], LOW);
             
          }

             
          for (int i = 1; i <= 8; i++) {
               digitalWrite
          (rows[i - 1], LOW);
             
          }

             clearLeds
          ();

             
          // Turn off toggling of pin 11
             FrequencyTimer2
          ::disable();
             
          // Set refresh rate (interrupt timeout period)
             FrequencyTimer2
          ::setPeriod(2000);
             
          // Set interrupt routine to be called
             FrequencyTimer2
          ::setOnOverflow(display);

             setPattern
          (pattern);
          }

          void loop() {
               pattern
          = ++pattern % numPatterns;
               slidePattern
          (pattern, 60);
          }

          void clearLeds() {
             
          // Clear display array
             
          for (int i = 0; i < 8; i++) {
               
          for (int j = 0; j < 8; j++) {
                 leds
          [i][j] = 0;
               
          }
             
          }
          }

          void setPattern(int pattern) {
             
          for (int i = 0; i < 8; i++) {
               
          for (int j = 0; j < 8; j++) {
                 leds
          [i][j] = patterns[pattern][i][j];
               
          }
             
          }
          }

          void slidePattern(int pattern, int del) {
             
          for (int l = 0; l < 8; l++) {
               
          for (int i = 0; i < 7; i++) {
                 
          for (int j = 0; j < 8; j++) {
                   leds
          [j][i] = leds[j][i+1];
                 
          }
               
          }
               
          for (int j = 0; j < 8; j++) {
                 leds
          [j][7] = patterns[pattern][j][0 + l];
               
          }
               delay
          (del);
             
          }
          }

          // Interrupt routine
          void display() {
             digitalWrite
          (cols[col], HIGH);  // Turn whole previous column off
             col
          ++;
             
          if (col == 8) {
               col
          = 0;
             
          }
             
          for (int row = 0; row < 8; row++) {
               
          if (leds[col][7 - row] == 1) {
                 digitalWrite
          (rows[row], HIGH);  // Turn on this led
               
          }
               
          else {
                 digitalWrite
          (rows[row], LOW); // Turn off this led
               
          }
             
          }
             digitalWrite
          (cols[col], LOW); // Turn whole column on at once (for equal lighting times)
          }

          — скетч с игрой:

           
          /*
            * Conway's "Life"
            *
            * Adapted from the Life example
            * on the Processing.org site
            *
            * Needs FrequencyTimer2 library
            *
            * http://www.arduino.cc/playground/Main/DirectDriveLEDMatrix
            * edited by noonv
            * http://robocraft.ru
            */


          #include <FrequencyTimer2.h>

          byte col = 0;
          byte leds[8][8];

          // pin[xx] on led matrix connected to nn on Arduino (-1 is dummy to make array start at pos 1)
          int pins[17]= {-1, 5, 4, 3, 2, 14, 15, 16, 17, 13, 12, 11, 10, 9, 8, 7, 6};

          // col[xx] of leds = pin yy on led matrix
          int cols[8] = {pins[13], pins[3], pins[4], pins[10], pins[06], pins[11], pins[15], pins[16]};

          // row[xx] of leds = pin yy on led matrix
          int rows[8] = {pins[9], pins[14], pins[8], pins[12], pins[1], pins[7], pins[2], pins[5]};

          #define DELAY 0
          #define SIZE 8
           
          extern byte leds[SIZE][SIZE];
          byte world[SIZE][SIZE][2];
          long density = 50;

          void setup() {
             setupLeds
          ();
             randomSeed
          (analogRead(5));
             
          for (int i = 0; i < SIZE; i++) {
               
          for (int j = 0; j < SIZE; j++) {
                 
          if (random(100) < density) {
                   world
          [i][j][0] = 1;
                 
          }
                 
          else {
                   world
          [i][j][0] = 0;
                 
          }
                 world
          [i][j][1] = 0;
               
          }
             
          }
          }

          void loop() {
             
          // Display current generation
             
          for (int i = 0; i < SIZE; i++) {
               
          for (int j = 0; j < SIZE; j++) {
                 leds
          [i][j] = world[i][j][0];
               
          }
             
          }
             delay
          (DELAY);

             
          // Birth and death cycle
             
          for (int x = 0; x < SIZE; x++) {
               
          for (int y = 0; y < SIZE; y++) {
                 
          // Default is for cell to stay the same
                 world
          [x][y][1] = world[x][y][0];
                 
          int count = neighbours(x, y);
                 
          if (count == 3 && world[x][y][0] == 0) {
                   
          // A new cell is born
                   world
          [x][y][1] = 1;
                 
          }
                 
          if ((count < 2 || count > 3) && world[x][y][0] == 1) {
                   
          // Cell dies
                   world
          [x][y][1] = 0;
                 
          }
               
          }
             
          }

             
          // Copy next generation into place
             
          for (int x = 0; x < SIZE; x++) {
               
          for (int y = 0; y < SIZE; y++) {
                 world
          [x][y][0] = world[x][y][1];
               
          }
             
          }
          }

          int neighbours(int x, int y) {
           
          return world[(x + 1) % SIZE][y][0] +
                    world
          [x][(y + 1) % SIZE][0] +
                    world
          [(x + SIZE - 1) % SIZE][y][0] +
                    world
          [x][(y + SIZE - 1) % SIZE][0] +
                    world
          [(x + 1) % SIZE][(y + 1) % SIZE][0] +
                    world
          [(x + SIZE - 1) % SIZE][(y + 1) % SIZE][0] +
                    world
          [(x + SIZE - 1) % SIZE][(y + SIZE - 1) % SIZE][0] +
                    world
          [(x + 1) % SIZE][(y + SIZE - 1) % SIZE][0];
          }

          void setupLeds() {
             
          // sets the pins as output
             
          for (int i = 1; i <= 16; i++) {
               pinMode
          (pins[i], OUTPUT);
             
          }

             
          // set up cols and rows
             
          for (int i = 1; i <= 8; i++) {
               digitalWrite
          (cols[i - 1], LOW);
             
          }

             
          for (int i = 1; i <= 8; i++) {
               digitalWrite
          (rows[i - 1], LOW);
             
          }

             clearLeds
          ();

             
          // Turn off toggling of pin 11 and 3
             FrequencyTimer2
          ::disable();
             
          // Set refresh rate (interrupt timeout period)
             FrequencyTimer2
          ::setPeriod(2000);
             
          // Set interrupt routine to be called
             FrequencyTimer2
          ::setOnOverflow(display);

          }

          void clearLeds() {
             
          // Clear display array
             
          for (int i = 0; i < 8; i++) {
               
          for (int j = 0; j < 8; j++) {
                 leds
          [i][j] = 0;
               
          }
             
          }
          }

          // Interrupt routine
          void display() {
             digitalWrite
          (cols[col], HIGH);  // Turn whole previous column off
             col
          ++;
             
          if (col == 8) {
               col
          = 0;
             
          }
             
          for (int row = 0; row < 8; row++) {
               
          if (leds[col][7 - row] == 1) {
                 digitalWrite
          (rows[row], HIGH);  // Turn on this led
               
          }
               
          else {
                 digitalWrite
          (rows[row], LOW); // Turn off this led
               
          }
             
          }
             digitalWrite
          (cols[col], LOW); // Turn whole column on at once (for equal lighting times)
          }

          Для работы скетчей требуется библиотека.
          Библиотеку, в примерах которой уже находятся вышеприведённые скетчи можно скачать здесь:

          Для подключения TC15-11 потребуются:
          — 1 шт.
          /— 1 шт.
          — 2 шт.
          гибкие — 20 шт.

          Загружаем скетч DirectDriveLEDMatrix и любуемся результатом:


          Видео
          и светодиодная матрица 8x8 (TC15-11)


          Ссылки:
          документация (datasheet) на TC15-11 ()




          Arduino Forum:




          По теме: