Архитектура AVR в примерах/Последовательный периферийный интерфейс
Последовательный периферийный интерфейс (англ. SPI) используется для взаимодействия со многими распространенными периферийными устройствами, включая:
- устройства ввода — акселерометры и магнитометры, приемники GPS, часы реального времени;
- устройства отображения и хранения информации — ЖК-экраны, ИС flash-памяти, карты SD;
- интерфейсы — в частности, интерфейсы Ethernet.
В этой работе мы рассмотрим использование встроенного в МК ATmega8 порта SPI[1] для взаимодействия с, пожалуй, одним из простейших возможных таких устройств на основе ИС регистра сдвига:
Перед началом
[править]Для данной работы нам потребуется дополнить простейшее устройство следующими цепями и компонентами.
- 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 */
Чтение кода
[править]Рассмотрим код программы выше, начиная с элементов основного цикла и обращаясь к инициализационной части программы и преамбуле где необходимо. При этом, мы опустим фрагменты, уже рассмотренные в «простейшей программе» и коде управления ШИМ.
Строка
SPDR = i;
загружает в буфер последовательного периферийного интерфейса подлежащее отправке на исполнительное устройство значение целочисленной переменной i.Сразу же после этого
i++;
увеличивает значение i на единицу. При этом, поскольку в объявлении переменной указан типuint8_t i;
, используется арифметика по модулю 256 (2⁸); другими словами: 255 + 1 = 0 (mod 256).Строка
PORTB &= (~ (1 << PB2));
перед отправкой данных формирует низкий уровень на выводе PB2 (SS; D 11 для Arduino Uno и подобных).Строка
PORTB |= (1 << PB2);
, выполняемая перед началом работы, а также на следующей итерации цикла (после отправки данных), формирует высокий уровень сигнала на этом же выводе.При использовании ИС 74HC595, этот сигнал используется в качестве строба, — при переключении уровня данного сигнала с низкого на высокий, данные из регистра сдвига ИС будут переданы в ее регистр хранения, состояние которого (при низком уровне на входе OE) отражается на выходах Q₀, …, Q₇.
Код
DDRB |= ((1 << DDB5) | (1 << DDB3) | ((USE_PB2_SS ? 1 : 0) << DDB2));
настраивает выводы PB5, PB3, PB2 для использования в качестве выходов. Первые два из них, при использовании последовательного периферийного интерфейса, используются для передачи сигналов интерфейса SCk и MOSI, соответственно.Вывод PB2, однако, заслуживает особого внимания: если на этот вывод, настроенный как вход, будет подан сигнал низкого уровня, то интерфейс прервет текущую операцию, сформирует прерывание (если разрешено), и перейдет в режим ведомого. Возврат в режим ведущего потребует явной установки флага MSTR регистра SPCR со стороны программы.
Чтобы избежать такого поведения МК, а также поскольку для управления ИС 74HC595 так или иначе требуется отдельный сигнал, этот вывод в данном примере настроен как выход. В этом случае, МК не связывает с выводом PB2 никаких особых функций.
Флаги и битовые поля регистра SPCR установлены следующим образом:
- SPIE
- 1 — события, связанные с последовательным периферийным интерфейсом, будут приводить к формированию прерывания;
- SPE
- 1 — разрешает использование интерфейса как такового;
- MSTR
- 1 — интерфейс будет работать в режиме ведущего;
- SPR
- 11₂ — для тактирования интерфейса будут использованы деленные на 256 импульсы тактовой частоты МК.
Обработчик прерывания
SPI_STC_vect
не выполняет совершенно никаких действий. Единственное назначение данного прерывания в рассматриваемом примере — завершать ожидание функциейsleep_cpu
очередного события.Битовое поле CS1 управляющего регистра TCCR1B установлено в 011₂ — на вход счетчика будут поступать деленные на 64 импульсы тактовой частоты МК.[4]
Учитывая разрядность счетчика (16 бит), ожидаемый диапазон частоты переполнений (а значит и прерываний по переполнению) — 1.76 ÷ 4.77 Hz для тактовых частот МК 7.3728 ÷ 20 MHz.
Прочие битовые поля этого регистра, равно как и регистр TCCR1A в целом, — обнулены, что, в частности, указывает на отказ от использования счетчика-таймера 1 в качестве широтно-импульсного модулятора.
Сборка
[править]Внесем в созданный ранее
Makefile
следующие зависимости:default: ledspi.hex ledspi ledspi: ledspi.c
Создадим файл
ledspi.c
приведенного выше содержания.Соберем рассматриваемый пример выполнив команду
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
Удостоверимся в отсутствии ошибок сборки и в появлении файла
ledspi.hex
, содержащего результирующий машинный код в формате Intel hex.
Загрузка и проверка работоспособности
[править]Загрузку кода в МК выполним аналогично ранее рассмотренному примеру.
Проверим работоспособность кода и устройства, для чего:
- подключим питание;
- сбросим МК;
- пронаблюдаем счет в двоичной системе счисления от 0 до 11111111₂ (или в обратном направлении, при подключении светодиодных цепей катодом к МК и анодом к + Uи. п.);
- оценим время, за которое индицируемое на дисплее двоичное число увеличивается на 2ⁿ (полупериод сигнала на «n-том» выходе ИС регистра сдвига) и сопоставим его с вычисленным исходя из тактовой частоты МК по формуле t = 2²⁴⁺ⁿ ∕ ƒMCU.
Исследование
[править]В данном примере, частота изменения индицируемого на дисплее числа образуется делением частоты процессора на 64 (
(1 << CS11) | (1 < CS10)
).[4] Исследуйте работу системы при использовании других делителей:- 8 —
(1 << CS11)
; - 256 —
(1 << CS12)
; - 1024 —
(1 << CS12) | (1 << CS10)
.
Если работоспособность программы нарушится при использовании каких-либо из этих делителей, — объясните причины такого нарушения. (Указание: вычислите ожидаемый временной интервал между последовательными итерациями основного цикла.)
- 8 —
Попробуйте изменить программу так, чтобы счет времени велся в секундах, полусекундах, или минутах:
- для некоторой конкретной используемой тактовой частоты МК;
- для любой тактовой частоты в диапазоне 7.3728 ÷ 20 MHz.
Попробуйте опытным путем установить максимальную частоту последовательного интерфейса, обеспечивающую надежную передачу данных на исполняющее устройство. Предположите, какие факторы ограничивают эту частоту. Если возможно, проверьте свое предположение:
- собрав вариант устройства, в котором устранены один или более из этих факторов;
- удостоверившись в том, что в новом варианте максимальная частота устойчивой передачи данных от МК к последовательному светодиодному дисплею действительно увеличилась.
Измените программу так, чтобы счет велся в коде Грея.
Примечания
[править]- ↑ Serial Peripheral Interface – SPI. ATmega8, ATmega8L: 8-bit Atmel with 8 KBytes In-System Programmable Flash. Проверено 29 апреля 2013.
- ↑ 74HC595; 74HCT595 – 8-bit serial-in, serial or parallel-out shift register with output latches; 3-state (2011-12-12). Проверено 22 ноября 2012.
- ↑ 8-Bit Parallel-Out Serial Shift Registers. Проверено 9 ноября 2013.
- ↑ 4,0 4,1 Timer/Counter0 and Timer/Counter1 Prescalers. ATmega8, ATmega8L: 8-bit Atmel with 8 KBytes In-System Programmable Flash. Проверено 29 апреля 2013.