Архитектура AVR в примерах/Последовательный периферийный интерфейс

Последовательный периферийный интерфейс (англ. SPI) используется для взаимодействия со многими распространенными периферийными устройствами, включая:

В этой работе мы рассмотрим использование встроенного в МК ATmega8 порта SPI[1] для взаимодействия с, пожалуй, одним из простейших возможных таких устройств на основе ИС регистра сдвига:

  • 74HC595 (КР1564ИР52)[2] — с «защелкой»;
  • или 74HC164 (К1564ИР8)[3] — без «защелки».

Перед началом править

Для данной работы нам потребуется дополнить простейшее устройство следующими цепями и компонентами.

  • 2–8 цепями, каждая из которых состоит из последовательно соединенных:
    • светодиода — на напряжение порядка 1.5 V;
    • балластного резистора — сопротивлением порядка 470 ÷ 1100 Ω; (для напряжений питания 3 ÷ 5 V; может быть снижено до порядка 360 ÷ 820 Ω при подключении к выходам ИС регистра сдвига не более, чем 4–5 светодиодов.)
  • ИС 74HC595 (74HCT595, КР1564ИР52), подключаемой к:
    • «общему» (вывод 8) и + Uи. п. (16);
    • через резисторы порядка 100 kΩ — к + Uи. п. — выводами MR, OE (10, 13);
    • вышеописанным светодиодным цепям (15, 1–7);
    • выводам SS, MOSI, SCk (16, 17, 19) МК ATmega8 — выводами StCP, DS, ShCP (12, 14, 11).
  • Или ИС 74HC164 (74HCT164, К1564ИР8), подключаемой к:
    • «общему» (вывод 7) и + Uи. п. (14);
    • через резисторы порядка 100 kΩ — к + Uи. п. — выводами A, Clr (1, 9);
    • вышеописанным светодиодным цепям (3–6, 10–13);
    • выводам MOSI, SCk (17, 19) МК ATmega8 — выводами B, Clk (2, 8);
    • Обратите внимание, что вывод SS (16) МК ATmega8 при этом необходимо оставить неподключенным.
  • Светодиодные цепи, в свою очередь, соединяются с «общим» или + Uи. п. — в соответствии с выбранной полярностью светодиодов.

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

Двоичный счет времени править

/*** ledspi.c — Control a row of LEDs via SPI (74HC164)  -*- C -*- */
#include <avr/interrupt.h>      /* for sei (), ISR () */
#include <avr/io.h>             /* for DDRB, PORTB, etc. */
#include <avr/sleep.h>          /* for sleep_enable (), etc. */
#include <avr/wdt.h>            /* for wdt_enable (), etc. */

#if (defined (TIMSK) && ! defined (TIMSK1))
/* ATmega8 compatibility */
#define TIMSK1  TIMSK
#endif

#ifndef USE_PB2_SS
/* use PB2 as SPI Slave Select (/SS) */
#define USE_PB2_SS      1
#endif

/* NB: main loop gets its first event for free */
static volatile uint8_t seen_interrupt_p  = 1;
ISR (TIMER1_OVF_vect) {
  seen_interrupt_p   = 1;
}

#if USE_PB2_SS
ISR (SPI_STC_vect) {
  /* do nothing */
}
#endif

int
main ()
{
  /* expecting a reset about every 0.6 s */
  wdt_enable (WDTO_1S);

  /* Set up Port B:
     - PB2 (D 10) is /SS
     – PB3 (D 11) is MOSI
     – PB5 (D 13) is SCk */
#if USE_PB2_SS
  /* raise /SS */
  PORTB |=    (1 << PB2);
#endif
  DDRB    |= ((1    << DDB5)
              | (1  << DDB3)
              | ((USE_PB2_SS ? 1 : 0)
                 <<    DDB2));

  /* set up Timer/Counter 1 */
  TCCR1A   =  0;
  TCCR1B   = (0
              /* CS1    =  011 _2: Divide clock by 64 */
              | (1  << CS11)
              | (1  << CS10));
  /* Timer overflow frequency is thus F_CPU / 64 / 65536,
     or 1.76 Hz to 4.77 Hz for F_CPU of 7.3728 MHz to 20 MHz */

  /* Set up the SPI master */
  SPCR     = (((USE_PB2_SS ? 1 : 0)
               <<      SPIE)
              | (1  << SPE)
              | (1  << MSTR)
              | (1  << SPR1)
              | (1  << SPR0));

  /* NB: ignoring the datasheet recommendation! */
  sleep_enable ();

  /* enable interrupts */
  TIMSK1  |= (1     << TOIE1);  /* timer 1 overflow */
  sei ();

  /* main loop */
  uint8_t i;
  for (i = 0; ; ) {
    if (seen_interrupt_p) {
#if USE_PB2_SS
      /* lower /SS */
      PORTB &= (~ (1 << PB2));
#endif
      SPDR  = i;
      i++;
      seen_interrupt_p  = 0;
    } else {
#if USE_PB2_SS
      /* raise /SS back */
      PORTB |=    (1 << PB2);
#endif
    }

    /* reset the watchdog timer */
    wdt_reset ();

    /* sleep until the next event */
    sleep_cpu ();
  }

  /* not reached */
  /* . */
  return 0;
}
/*** ledspi.c ends here */

Чтение кода править

Рассмотрим код программы выше, начиная с элементов основного цикла и обращаясь к инициализационной части программы и преамбуле где необходимо. При этом, мы опустим фрагменты, уже рассмотренные в «простейшей программе» и коде управления ШИМ.

  1. Строка SPDR = i; загружает в буфер последовательного периферийного интерфейса подлежащее отправке на исполнительное устройство значение целочисленной переменной i.

    Сразу же после этого i++; увеличивает значение i на единицу. При этом, поскольку в объявлении переменной указан тип uint8_t i;, используется арифметика по модулю 256 (2⁸); другими словами: 255 + 1 = 0 (mod 256).

  2. Строка PORTB &= (~ (1 << PB2)); перед отправкой данных формирует низкий уровень на выводе PB2 (SS; D 11 для Arduino Uno и подобных).

    Строка PORTB |= (1 << PB2);, выполняемая перед началом работы, а также на следующей итерации цикла (после отправки данных), формирует высокий уровень сигнала на этом же выводе.

    При использовании ИС 74HC595, этот сигнал используется в качестве строба, — при переключении уровня данного сигнала с низкого на высокий, данные из регистра сдвига ИС будут переданы в ее регистр хранения, состояние которого (при низком уровне на входе OE) отражается на выходах Q₀, …, Q₇.

  3. Код DDRB |= ((1 << DDB5) | (1 << DDB3) | ((USE_PB2_SS ? 1 : 0) << DDB2)); настраивает выводы PB5, PB3, PB2 для использования в качестве выходов. Первые два из них, при использовании последовательного периферийного интерфейса, используются для передачи сигналов интерфейса SCk и MOSI, соответственно.

    Вывод PB2, однако, заслуживает особого внимания: если на этот вывод, настроенный как вход, будет подан сигнал низкого уровня, то интерфейс прервет текущую операцию, сформирует прерывание (если разрешено), и перейдет в режим ведомого. Возврат в режим ведущего потребует явной установки флага MSTR регистра SPCR со стороны программы.

    Чтобы избежать такого поведения МК, а также поскольку для управления ИС 74HC595 так или иначе требуется отдельный сигнал, этот вывод в данном примере настроен как выход. В этом случае, МК не связывает с выводом PB2 никаких особых функций.

  4. Флаги и битовые поля регистра SPCR установлены следующим образом:

    SPIE
    1 — события, связанные с последовательным периферийным интерфейсом, будут приводить к формированию прерывания;
    SPE
    1 — разрешает использование интерфейса как такового;
    MSTR
    1 — интерфейс будет работать в режиме ведущего;
    SPR
    11₂ — для тактирования интерфейса будут использованы деленные на 256 импульсы тактовой частоты МК.
  5. Обработчик прерывания SPI_STC_vect не выполняет совершенно никаких действий. Единственное назначение данного прерывания в рассматриваемом примере — завершать ожидание функцией sleep_cpu очередного события.

  6. Битовое поле CS1 управляющего регистра TCCR1B установлено в 011₂ — на вход счетчика будут поступать деленные на 64 импульсы тактовой частоты МК.[4]

    Учитывая разрядность счетчика (16 бит), ожидаемый диапазон частоты переполнений (а значит и прерываний по переполнению) — 1.76 ÷ 4.77 Hz для тактовых частот МК 7.3728 ÷ 20 MHz.

    Прочие битовые поля этого регистра, равно как и регистр TCCR1A в целом, — обнулены, что, в частности, указывает на отказ от использования счетчика-таймера 1 в качестве широтно-импульсного модулятора.

Сборка править

  1. Внесем в созданный ранее Makefile следующие зависимости:

    default: ledspi.hex ledspi
    
    ledspi: ledspi.c
    
  2. Создадим файл ledspi.c приведенного выше содержания.

  3. Соберем рассматриваемый пример выполнив команду make:

    $ make ledspi.hex 
    avr-gcc -O2 -Wall -std=gnu11 -mmcu=atmega8 -DF_CPU=7372800   ledspi.c   -o ledspi
    avr-objcopy -O ihex ledspi ledspi.hex
    $ 
    
    NB

    При использовании микроконтроллера, отличного от ATmega8, или же кварцевого резонатора на частоту, отличную от 7.3728 MHz следует явно указать параметры сборки MCU или F_CPU, соответственно.

    Так, для платы Arduino Uno R3, поставляемой с МК ATmega328P и кварцевым резонатором на 20 MHz, команда сборки может быть следующей:

    $ make MCU=atmega328p F_CPU=20000000 ledspi.hex 
    
  4. Удостоверимся в отсутствии ошибок сборки и в появлении файла ledspi.hex, содержащего результирующий машинный код в формате Intel hex.

Загрузка и проверка работоспособности править

  1. Загрузку кода в МК выполним аналогично ранее рассмотренному примеру.

  2. Проверим работоспособность кода и устройства, для чего:

    1. подключим питание;
    2. сбросим МК;
    3. пронаблюдаем счет в двоичной системе счисления от 0 до 11111111₂ (или в обратном направлении, при подключении светодиодных цепей катодом к МК и анодом к + Uи. п.);
    4. оценим время, за которое индицируемое на дисплее двоичное число увеличивается на 2ⁿ (полупериод сигнала на «n-том» выходе ИС регистра сдвига) и сопоставим его с вычисленным исходя из тактовой частоты МК по формуле t = 2²⁴⁺ⁿ ∕ ƒMCU.

Исследование править

  1. В данном примере, частота изменения индицируемого на дисплее числа образуется делением частоты процессора на 64 ((1 << CS11) | (1 < CS10)).[4] Исследуйте работу системы при использовании других делителей:

    • 8 — (1 << CS11);
    • 256 — (1 << CS12);
    • 1024 — (1 << CS12) | (1 << CS10).

    Если работоспособность программы нарушится при использовании каких-либо из этих делителей, — объясните причины такого нарушения. (Указание: вычислите ожидаемый временной интервал между последовательными итерациями основного цикла.)

  2. Попробуйте изменить программу так, чтобы счет времени велся в секундах, полусекундах, или минутах:

    • для некоторой конкретной используемой тактовой частоты МК;
    • для любой тактовой частоты в диапазоне 7.3728 ÷ 20 MHz.
  3. Попробуйте опытным путем установить максимальную частоту последовательного интерфейса, обеспечивающую надежную передачу данных на исполняющее устройство. Предположите, какие факторы ограничивают эту частоту. Если возможно, проверьте свое предположение:

    1. собрав вариант устройства, в котором устранены один или более из этих факторов;
    2. удостоверившись в том, что в новом варианте максимальная частота устойчивой передачи данных от МК к последовательному светодиодному дисплею действительно увеличилась.
  4. Измените программу так, чтобы счет велся в коде Грея.

Примечания править

  1. Serial Peripheral Interface – SPI. ATmega8, ATmega8L: 8-bit Atmel with 8 KBytes In-System Programmable Flash. Проверено 29 апреля 2013.
  2. 74HC595; 74HCT595 – 8-bit serial-in, serial or parallel-out shift register with output latches; 3-state (2011-12-12). Проверено 22 ноября 2012.
  3. 8-Bit Parallel-Out Serial Shift Registers. Проверено 9 ноября 2013.
  4. 4,0 4,1 Timer/Counter0 and Timer/Counter1 Prescalers. ATmega8, ATmega8L: 8-bit Atmel with 8 KBytes In-System Programmable Flash. Проверено 29 апреля 2013.