Представленная здесь статья сохранила структуру исходного документа. Основным мотивом написания статьи стало прочтение перевода исходной статьи на сайте gaw.ru, однако, обнаружив много неточностей, я решил написать свой вариант. Любые замечания, дополнения и предложения по улучшению представленного здесь материала приветствуются.
Стандарт SPI наращивает свою популярность за счет возможности организации более быстродействующей связи по сравнению с двухпроводными интерфейсами, а также за счет простоты реализации на кристалле полупроводника. Хотя большинство старших представителей семейства AVR содержат в своем составе полнофункциональный интерфейс SPI, в младшие версии он не входит. В данном документе описывается набор процедур, написанных на языке низкого уровня, реализующих программно протокол SPI в режиме ведущего (мастера). Данная разработка может использоваться для организации доступа к последовательной памяти ЭСППЗУ семейства 25XXX производства Atmel, а также для связи с другими ведомыми (подчинёнными) периферийными устройствами, например, с контроллером индикатора или другим микроконтроллером.

Рисунок 1. Пример подключения к AVR-микроконтроллеру подчиненного устройства (последовательное ЭСППЗУ)

Рисунок 2. Временная диаграмма интерфейса SPI
Режим 0 интерфейса SPI подразумевает выполнение следующих условий:
Рассматриваемые ниже процедуры написаны с учетом этих требований. Особое внимание уделено граничным условиям, таким как tVALID и tSETUP. Эти временные параметры рассмотрены ниже.

Рисунок 3. Блок-схема алгоритма работы интерфейса SPI в режиме ведущего (мастера)
init_spi
Данная подпрограмма инициализирует линии порта SPI. Изначально предполагается, что под интерфейс SPI отведены линии порта B. Если планируется использовать другой порт, следует отредактировать используемые в процедуре макросы (подробнее смотри раздел "Макросы" ниже в этом документе). При необходимости также можно изменить соответствие между линиями интерфейса SPI и линиями порта ввода/вывода задавая значения EQU для SCK, MOSI, MISO и SS в разделе программы “PORT DEFINITIONS”. Данная подпрограмма не имеет ни входных, ни выходных параметров.
ena_spi
Данная подпрограмма сначала устанавливает линию SCK в 0 (в соответствии с режимом 0 интерфейса SPI), а потом устанавливает линию SS в 0, активируя ведомое устройство). Она не имеет ни входных, ни выходных параметров.
disa_spi
Данная подпрограмма устанавливает линию SS в 1, отключая ведомое устройство. Это необходимо для предотвращения ложного тактирования ведомого устройства. Вызывается после завершения передачи данных. Также не имеет ни входных, ни выходных параметров.
rw_spi
Эта подпрограмма отсылает/принимает 16-ти разрядный блок данных (или 8-ми разрядный, если в исходный код были внесены соответствующие изменения). После возврата управления этой функцией линия SCK установлена в 0. Состояние линии SS не изменяется, что делает возможным передачу большого блока данных просто вызвав эту подпрограмму многократно. Передаваемые данные необходимо до вызова этой подпрограммы поместить в регистр spi_lo (и spi_hi, если используется 16-ти разрядный режим). После завершения работы этой подпрограммы принятые от ведомого устройства данные будут содержаться в этих же регистрах.
При необходимости использования 8-разрядного формата передаваемых данных требуется изменить всего 2 строки программы. Оба изменения касаются подпрограммы rw_spi. Первое – начальное значение счётчика бит необходимо изменить с 16 на 8, второе – строку “rol spi_hi” необходимо закомментировать. Следует отметить, что в этом случае регистр spi_hi не нужен и в дальнейшем этой программой не используется. Его можно задействовать для других целей.
Макросы используются для того, чтобы сделать код более читаемым. Исходно предполагается, что для интерфейса SPI будут задействованы линии порта B. Если планируется использовать другой порт, следует самостоятельно изменить имя порта в макросах ss_active, ss_inactive, sck_hi, sck_lo, mosi_hi, mosi_lo, а так же четыре строки в подпрограмме init_spi, в которых используется регистр ddrb, который нужно заменить на соответствующий регистр установки направления данных выбранного вами порта. Например, для порта A вместо регистра ddrb следует указать регистр ddra. Линии порта ввода/вывода, задействованные под четыре сигнала порта SPI, определены в разделе “PORT DEFINITIONS” и могут быть легко изменены.
Макрокоманда функции задержки требует дополнительного разъяснения. Двойное назначение регистра TEMP позволило исключить необходимость применения дополнительных регистров. Регистр TEMP используется для хранения значения 5-разрядного счетчика задержки (младшие 5 разрядов регистра TEMP) и счетчика для задания длительности высокого и низкого уровней сигнала SCK (старшие 3 разряда регистра TEMP). За счет простого увеличения старших трех разрядов и определения переполнения можно задавать временные интервалы, не влияя на младшие 5 разрядов. Обратите внимание, что фактически выполняется операция вычитания вместо сложения. Конечный результат тот же, за исключением того, что флаг переноса C сбрасывается (а не наоборот), когда значение старших трех бит изменяется из 7 в 0.

Рисунок 4. Временная диаграмма установки и удержания данных
Временная задержка с момента установки уровня на линии MOSI до нарастающего фронта на линии SCK является критичным параметром и составляет время установки данных (tSETUP) для подключенного ведомого устройства. В данной подпрограмме длительность времени установки данных составляет 2 цикла при изменении линии MOSI в низкий уровень и 3 цикла при изменении линии MOSI в высокий уровень. При тактовой частоте 10 МГц это время составляет 200-300 нс. Если требуется более длительная задержка, необходимо добавить команды NOP непосредственно перед макрокомандой sck_hi.
На линии SCK большую часть времени присутствует логический ноль. Это обусловлено тем, что логический ноль установлен на этой линии во время "накладных расходов" цикла передачи данных.
Если подключенное периферийное устройство может работать на высокой скорости, время высокого уровня (tHILO) может быть уменьшено в соответствии со спецификациями на периферийное устройство и интерфейс SPI. Если значение задержки установлено в 1 (по умолчанию установлено значение 4, смотри параметр макрокоманды set_delay перед меткой time_hi), подпрограмме требуется в среднем 22,5 тактов для передачи одного бита данных. При этом высокий уровень на линии SCK сохраняется в течении 4 тактов. При частоте тактового генератора 10 МГц время пребывания высокого уровня на линии SCK составляет 400 нс, а общая пропускная способность шины будет около 444 килобайт в секунду.
Длительность присутствия низкого уровня на линии SCK (tVALID) определяется требованием периферийного устройства к параметру, который, как правило, называется "SCK fall to MISO (slave data out) Valid". При значении задержки, равной 1 (по умолчанию установлено значение 4, смотри параметр макрокоманды set_delay перед меткой time_lo), эта подпрограмма считает значение на линии MISO, установленное периферийным устройством, через 3 цикла. При той же частоте тактового генератора в 10 МГц эта задержка составит 300 нс. Увеличить время этой задержки можно увеличив значение задержки, передаваемой в качестве параметра макроса set_delay.
;**** A P P L I C A T I O N N O T E A V R 3 2 0 ***************** ;* ;* Title : Software SPI Master ;* Version : 1.0 ;* Last updated : 98.04.21 ;* Target : AT90S1200 ;* Easily modified for : Any AVR microcontroller ;* ;* Support E-mail :avr[dog]atmel.com ;* ;* DESCRIPTION ;* This is a collection of 8/16-bit word, Mode 0, Master SPI routines. ;* It simultaneously transmits and receives SPI data in 8- or 16-bit ;* word format. Data is sent and received MSB-first. One pair of ;* registers is used both to send and to receive; i.e., when one bit ;* is shifted out (transmitted), the vacated bit position is used to ;* store the new received bit. These routines are low-level ;* interface routines, and do not inherently contain a command ;* structure; that is dictated by the connected SPI peripheral(s). ;* ;* Due to having separate Enable/Disable and Read/Write-Word ;* routines, larger blocks of data can be sent simply by calling ;* the RW_SPI routine multiple times before disabling /SS. ;* ;* MAJOR ROUTINES: ;* init_spi: initializes the port lines used for SPI. ;* No calling requirements, returns nothing. ;* ena_spi: forces SCK low, and activates /SS signal. ;* No calling requirements, returns nothing. ;* disa_spi: brings /SS signal hi (inactive). ;* No calling requirements, returns nothing. ;* rw_spi: sends/receives a an 8-bit or 16-bit data word. ;* Must set up data to be sent in (spi_hi,spi_lo) ;* prior to calling; it returns received data in ;* the same register pair (if 8-bit, uses only ;* the spi_lo register). ;* ;* VARIABLES: ;* The spi_hi and spi_lo variables are the high and low data bytes. ;* They can be located anywhere in the register file. ;* ;* The temp variable holds the bit count, and is also used in timing ;* the high/low minimum pulse width. This must be located in an ;* upper register due to the use of an IMMEDIATE-mode instruction. ;* ;* HISTORY ;* V1.0 98.04.21 (rgf) Created ;* ;*************************************************************************** ;**** includes **** .include "1200def.inc" ;you can change this to any device ;*************************************************************************** ;* ;* CONSTANTS ;* ;*************************************************************************** ;**** Revision Codes **** .equ SW_MAJOR = 1 ; Major SW revision number .equ SW_MINOR = 0 ; Minor SW revision number .equ HW_MAJOR = 0 ; Major HW revision number .equ HW_MINOR = 0 ; Minor HW revision number ;*************************************************************************** ;* ;* PORT DEFINITIONS ;* ;*************************************************************************** .equ sck = 0 ;PB0 pin .equ nss = 1 ;PB1 pin .equ mosi = 2 ;PB2 pin .equ miso = 3 ;PB3 pin ;*************************************************************************** ;* ;* REGISTER DEFINITIONS ;* ;*************************************************************************** .def spi_lo =r0 ;change as needed .def spi_hi =r1 ; " .def temp =r16 ;misc usage, must be in upper regs for IMMED mode ;*************************************************************************** ;* ;* MACROS ;* Program Macros ;* ;* DESCRIPTION ;* Change the following macros if a port other than PORTB is used. ;* ;*************************************************************************** .macro ss_active cbi portb,nss .endm .macro ss_inactive sbi portb,nss .endm .macro sck_hi sbi portb,sck .endm .macro sck_lo cbi portb,sck .endm .macro mosi_hi sbi portb,mosi .endm .macro mosi_lo cbi portb,mosi .endm .macro addi subi @0, -@1 ;subtract the negative of an immediate value .endm .macro set_delay ;set up the time delay amount, from 1 to 7 subi @0, (@1