AT91SAM7X: работа с портом SPI в режиме master Печать
Добавил(а) microsin   

В этой статье рассмотрены практические примеры работы с портом SPI (в режиме Master) микроконтроллеров ARM7 компании Atmel (AT91SAM7X128, AT91SAM7X256, AT91SAM7X512) на языке C (в среде программирования IAR Embedded Workbench). Интерфейс SPI широко используется для высокоскоростной связи между микроконтроллерами, работы с картами памяти SD (или SDHC, или MMC), управления внешней периферией и других целей. Например, LCD в некоторых телефонах подключены через SPI, интерфейс программирования ISP для микроконтроллеров AVR и для многих других - все тот же SPI.

AT91SAM7X-SPI

[Порт SPI микроконтроллеров ARM7]

В микроконтроллер ARM7 встроены 2 порта SPI: SPI0 и SPI1, которые имеют одинаковые возможности и программируются одинаково. Порт SPI может передавать данные (слово от 8 до 16 бит длиной) как под программным управлением, так и в режиме DMA. Все сигналы интерфейса SPI (выборка внешнего чипа CS, такты SPCK, выходные данные MOSI, входные данные MISO) генерируются и обрабатываются аппаратно. Данные выдвигаются побитно, при этом возможны одновременно как прием, так и передача данных (полный дуплекс). На рисунке приведена упрощенная блок-схема SPI.

AT91SAM7X-SPI-block-sch

Пояснения к упрощенной блок-схеме: серым цветом помечены направления и сигналы, относящиеся к режиму slave SPI (в данной статье не рассматривается).

APB - шина Advanced Peripheral Bus, подключенная к MCU (внутренняя мина микроконтроллера).
PMC - Power Management Controller, см. Словарик.
PDC - Peripheral DMA Controller, см. Словарик.
MCK - тактовая частота, предназначенная для работы периферии SPI.
SPI Interface - периферия (аппаратура) SPI, встроенная в микроконтроллер ARM7.
Interrupt Control - система управления прерываниями SPI.
SPI Interrupt - сигнал прерывания от периферии SPI.
PIO - контроллер ввода/вывода (I/O) PIO Controller A или PIO Controller B, который предназначен для коммутации функций ножек микроконтроллера (см. таблицы 10-2 и 10-3).
SPCK - внешняя ножка, тактовый сигнал SPI, см. Словарик.
MISO - внешняя ножка, вход данных в режиме master, см. Словарик.
MOSI - внешняя ножка, выход данных в режиме master, см. Словарик.
NSS - внешняя ножка, вход для сигнала выборки в режиме slave, в этой статье не рассматривается.
NPCS0, NPCS1, NPCS2, NPCS3 - внешние ножки, выходные сигналы для организации выбора внешних slave-устройств, подключенных к шине SPI.

Таблица 10-2. Мультиплексирование ножек кристалла ARM7 для PIO Controller A, относящееся к SPI.

Pin № I/O Line Peripheral A Peripheral B Примечание
86 PA2 SCK0 SPI1_NPCS1 High-Drive
85 PA3 RTS0 SPI1_NPCS2 High-Drive
88 PA4 CTS0 SPI1_NPCS3  
91 PA7 SCK1 SPI0_NPCS1  
13 PA8 RTS1 SPI0_NPCS2  
14 PA9 CTS1 SPI0_NPCS3  
20 PA12 SPI0_NPCS0    
21 PA13 SPI0_NPCS1 PCK1  
22 PA14 SPI0_NPCS2 IRQ1  
23 PA15 SPI0_NPCS3 TCLK2  
24 PA16 SPI0_MISO    
25 PA17 SPI0_MOSI    
26 PA18 SPI0_SPCK    
49 PA21 TF SPI1_NPCS0  
50 PA22   SPI1_SPCK  
55 PA23   SPI1_MOSI  
56 PA24   SPI1_MISO  
59 PA25   SPI1_NPCS1  
60 PA26   SPI1_NPCS2  
75 PA29   SPI1_NPCS3  

Таблица 10-3. Мультиплексирование ножек кристалла ARM7 для PIO Controller B, относящееся к SPI.

Pin № I/O Line Peripheral A Peripheral B Примечание
44 PB10 EXT2 SPI1_NPCS1  
45 PB11 EXT3 SPI1_NPCS2  
30 PB13 ERX2 SPI0_NPCS1  
29 PB14 ERX3 SPI0_NPCS2  
53 PB16 ECOL SPI1_NPCS3  
36 PB17 ERXCK SPI0_NPCS3  

Пояснения к таблицам:

Pin № - номера ножек корпуса LQFP100 для микроконтроллеров AT91SAM7X128, AT91SAM7X256, AT91SAM7X512.
I/O Line - мнемоническое имя ножки чипа, содержащее в себе номер бита (от 0 до 31) и имя контроллера PIO (A или B).
Peripheral A, Peripheral B - варианты настроек ножек как периферия A или периферия B. Серым шрифтом помечены не относящиеся к SPI конфигурации периферии для ножек, а жирным шрифтом - вариант настройки для Примера 1 (см. далее).
High-Drive - ножки выборок для внешних slave-устройств шины SPI, обладающие повышенной нагрузочной способностью.

Организация выборки внешних чипов, подключенных по SPI (slave устройств шины SPI) позволяет делать аппаратную выборку до 4 чипов без применения дешифратора, и до 15 чипов с применением внешнего дешифратора 4x16. Сигнал выборки CS может также генерироваться программно. Интерфейс SPI хорошо описан в даташите, и имеет довольно простой интерфейс программирования (набор регистров SPI). Можно задать набор прерываний по событиям SPI (опустошение регистра передачи, окончание приема слова, ошибка режима и другие события). Для начала работы с SPI нужно запрограммировать ножки чипа на использование периферии SPI, запрограммировать PMC на тактирование периферии SPI, задать скорость, настроить систему выборки внешних чипов и другие параметры.

[Управление выборкой внешних чипов (slave-устройства SPI)]

Чип ARM7 имеет 4 вывода, которые могут быть запрограммированы как аппаратные выходы для управления периферийными slave-устройствами на шине SPI (NPCS0/NSS, NPCS1, NPCS2, NPCS3). Выборка slave-устройств SPI (аппаратное управление сигналами CS) настраивается через регистры SPI_MR (битовые поля PS, PCSDEC, PCS, DLYBCS) и SPI_CSRx (поля CSAAT, DLYBS, DLYBCT). Дополнительное поведение сигнала выборки настраивается также через регистры SPI_CR (бит LASTXFER) и SPI_TDR (бит LASTXFER, поле PCS). В организации выборки возможны следующие варианты:

1. Выбор фиксированной периферии, когда SPI_MR.PS=0, SPI_MR.PCSDEC=0. В этом случае на ножках NPCS0, NPCS1, NPCS2, NPCS3 устанавливается логический сигнал в соответствии с полем PCS регистра SPI_MR. Для поля SPI_MR.PCS допустимы значения 14 (0xE, 1110b, активна выборка NPCS0), 13 (0xD, 1101b, активна выборка NPCS1), 11 (0xB, 1011b, активна выборка NPCS2), 7 (0x7, 0111b, активна выборка NPCS3). Поле PCS регистра передачи SPI_TDR при этом не используется. Вариант фиксированной периферии применяется, когда нужно вести передачу в режиме DMA только для одного чипа (SPI_TDR.PCS при этом использовать нельзя). Для того, чтобы запустить передачу DMA для другого чипа, нужно переустановить поле SPI_MR.PCS.

2. Выбор переменной периферии, когда SPI_MR.PS=1, SPI_MR.PCSDEC=0. Этот режим позволяет работать без DMA одновременно с несколькими slave-устройствами на шине SPI. Поле SPI_MR.PCS теперь не используется для активизации сигналов выборок, а применяется поле PCS регистра передачи, SPI_TDR.PCS. Для поля SPI_TDR.PCS допустимы значения 14 (0xE, 1110b, активна выборка NPCS0), 13 (0xD, 1101b, активна выборка NPCS1), 11 (0xB, 1011b, активна выборка NPCS2), 7 (0x7, 0111b, активна выборка NPCS3).

3. Выбор фиксированной периферии с внешним дешифратором, подключенным к ножкам NPCS0 .. NPCS3, когда SPI_MR.PS=0, SPI_MR.PCSDEC=1. Этот режим позволяет выбрать любое из 15 slave-устройств, подключенных параллельно к шине SPI. Для выборки используется значение из поля SPI_MR.PCS, допустимы значения от 0 до 14. Поле PCS регистра передачи SPI_TDR при этом не используется. Этот вариант также, как и вариант 1, может использоваться для передачи по шине SPI через DMA.

4. Выбор переменной периферии с внешним дешифратором, подключенным к ножкам NPCS0 .. NPCS3, когда SPI_MR.PS=1, SPI_MR.PCSDEC=1. Этот режим позволяет выбрать любое из 15 slave-устройств, подключенных параллельно к шине SPI. Для выборки используется значение из поля SPI_TDR.PCS, допустимы значения от 0 до 14. Поле PCS регистра передачи SPI_MR при этом не используется. Этот вариант также, как и вариант 2, может использоваться для передачи по шине SPI без использования DMA.

На длительность сигнала выборки влияют следующие регистры и их поля:

SPI_MR.DLYBCS это поле влияет на задержку передачи для всех режимов формирования выборок (режимы 1..4). Имеет смысл использовать при наличии нескольких slave-устройств на шине SPI. Программирование этого поля позволяет задать время задержки между отказом от выбора одного slave-устройства в пользу выбора другого, что гарантирует отсутствие конфликтов на шине SPI. Т. е. задержка вставляется только при переходе с одного slave-устройства на другое. Если поле DLYBCS меньше или равно 6, то время задержки будет установлено в значение 6 периодов MCK (или 6*N периодов MCK, если установлено поле SPI_MR.FDIV). В противном случае задержка вычисляется по формуле DLYBCS/MCK (если FDIV=0) или (DLYBCS*N)/MCK (если FDIV=1).

SPI_CSRx.DLYBS это поле программируется отдельно для каждой ножки NPCSx (NPCS0 .. NPCS3), и влияет на задержку между спадом на ножке NPCSx (активизация выборки) и появлением тактов SPCK. Если поле DLYBS=0, то время задержки составляет полпериода частоты SPCK. В противном случае время задержки вычисляется по формуле DLYBS/MCK (если FDIV=0) или (32 * DLYBCS)/MCK (если FDIV=1).

SPI_CSRx.DLYBCT это поле программируется отдельно для каждой ножки NPCSx (NPCS0 .. NPCS3), и влияет на задержку между между двумя отдельными передачами, приходящимися на одно и то же slave-устройство. Когда поле DLYBCT=0, то никакой задержки нет, и синхросигнал SPCK сохраняет свой рабочий цикл, который был во время передачи. Иначе время задержки определяется уравнением (32 * DLYBCT)/MCK + SCBR/(2 * MCK) (если FDIV=0), или уравнением (32 * 32 * DLYBCT)/MCK + (32 * SCBR)/(2 * MCK) (если FDIV=1).

Внимание: при перенастройке выборки не забывайте о регистрах SPI_CSRx - соответствующий выборке регистр также должен быть корректно настроен, иначе передача и прием будут невозможны (не будут корректно устанавливаться флаги SPIO_SR.TDRE, SPIO_SR.TXBUFE, SPIO_SR.RDRF).

[Настройка скорости передачи SPI]

Скорость передачи по шине SPI определяется тактовой частотой, выводимой на ножку синхросигнала SPCK. Этот сигнал генерирует master шины SPI, в нашем случае это микроконтроллер ARM7. Частота SPCK влияет на скорость передачи, и определяется полем SPI_MR.FDIV и полем SPI_CSRx.SCBR. В поле SCBR может быть записано значение от 1 до 255, значение 0 запрещено (если записать 0, то поведение программы станет непредсказуемым). Скорость определяется уравнением MCK/SCBR (если FDIV=0), MCK/(32 * SCBR) (если FDIV=1).

[Формирование сигнала тактов SPCK]

Формат передачи зависит от фазы синхросигнала SPCK по отношению к сигналу данных (MOSI, MISO). Это настраивается полями NCPHA и CPOL регистров SPI_CSRx. Поле SPI_CSRx.CPOL определяет полярность синхросигнала: если SPI_CSRx.CPOL=0, то неактивное состояние SPCK лог. 0, а если SPI_CSRx.CPOL=1, то неактивное состояние SPCK лог. 1. Поле SPI_CSRx.NCPHA определяет фазу синхросигнала. Вместе поля поля CPOL и NCPHA определяют 4 варианта формирования сигнала SPCK.

1. NCPHA=0, CPOL=0. Данные фиксируются по спаду SPCK, меняются по фронту SPCK, начальное состояние SPCK лог. 0.
2. NCPHA=0, CPOL=1. Данные фиксируются по фронту SPCK, меняются по спаду SPCK, начальное состояние SPCK лог. 1.
3. NCPHA=1, CPOL=0. Данные фиксируются по фронту SPCK, меняются по спаду SPCK, начальное состояние SPCK лог. 0.
4. NCPHA=1, CPOL=1. Данные фиксируются по спаду SPCK, меняются по фронту SPCK, начальное состояние SPCK лог. 1.

[Передача SPI с использованием DMA / без использования DMA]

Когда не используется DMA, то может быть выбран любой вариант настройки периферии (любые значения SPI_MR.PS, SPI_MR.PCSDEC), в зависимости от применяемой схемы подключения slave-устройств, их количества и предпочтений программиста.

Когда DMA используется, то возможности по настройке выборки slave-устройств несколько сужаются, если используется несколько slave-устройств на шине SPI. Причина в том, что при передаче одного блока данных через DMA нельзя перенастроить регистр SPI_MR. Так что можно задать режим фиксированной периферии (SPI_MR.PS=0), с декодером или без (SPI_MR.PCSDEC=0 или SPI_MR.PCSDEC=1), тогда буфер PDC можно выбрать 8-битный или 16-битный (в зависимости от выбранного формата передачи). В пределах передачи одного блока через DMA можно вести обмен только с одним slave-устройством, но расход памяти на буферы DMA получается экономичным. Если же выбрать для DMA режим переменной периферии (SPI_MR.PS=1), с декодером или без (SPI_MR.PCSDEC=0 или SPI_MR.PCSDEC=1), то тогда необходим 32-битный буфер DMA, в данных которого заполнены поля выбора slave-устройства и генерации выборки. Это позволяет в пределах одной передачи DMA вести обмен с несколькими slave-устройствами, однако увеличивает накладные расходы по памяти под буферы DMA. Для запуска передачи в режиме DMA также необходима настройка регистров PDC.

[Интерфейс пользователя SPI]

Под интерфейсом пользователя понимается карта памяти регистров SPI. Через эти регистры осуществляется как обмен данными, так и настройка, управление SPI, получение его состояния и работа с SPI через DMA. У микроконтроллеров AT91SAM7X два интерфейса SPI, и каждому соответствует 2 блока адресного пространства. Для SPI0 это адресное пространство 0xFFFE0000..0xFFFE0124, для SPI1 0xFFFE4000..0xFFFE4124. В таблице ниже перечислены все регистры, краткое описание их назначения, а также смещение в адресном пространстве относительно базового адреса (базовый адрес для SPI0 равен 0xFFFE0000, а для SPI1 0xFFFE4000).

Смещение Регистр Имя Доступ Сброс
0x000 Control Register (регистр управления) SPI_CR W -
0x004 Mode Register (регистр режима) SPI_MR R/W 0
0x008 Receive Data Register (данные приема) SPI_RDR R 0
0x00C Transmit Data Register (передаваемые данные) SPI_TDR W -
0x010 Status Register (регистр состояния) SPI_SR R 0x000000F0
0x014 Interrupt Enable Register (регистр разрешения прерываний) SPI_IER W -
0x018 Interrupt Disable Register (регистр запрещения прерываний) SPI_IDR W -
0x01C Interrupt Mask Register (регистр маски прерываний) SPI_IMR R 0
0x020..0x02C Зарезервировано (не используется)      
0x030 Chip Select Register 0 (регистр выборки 0) SPI_CSR0 R/W 0
0x034 Chip Select Register 1 (регистр выборки 1) SPI_CSR1 R/W 0
0x038 Chip Select Register 2 (регистр выборки 2) SPI_CSR2 R/W 0
0x03C Chip Select Register 3 (регистр выборки 3) SPI_CSR3 R/W 0
0x040..0x0FC Зарезервировано (не используется)
0x100..0x124 Регистры PDC (см. [5]).      

За основу примера взят рабочий код из библиотеки EFSL (работа с картами SD/SDHC/MMC по SPI в однобитном режиме). Используется порт SPI1, данные передаются по 8 бит, выборка формируется в режиме фиксированной периферии, только для одного slave-устройства (стандартная карта памяти SD/SDHC/MMC [1]), DMA и прерывания не используются. Сигнал выборки формируется на каждый передаваемый / принимаемый байт. Рассматривается только низкоуровневый код, который относится к настройке SPI и приему/передаче байта (нет кода настройки режима карты и поддержки протокола). Пример физического подключения карты памяти см. в [1].

SD-SDHC-card-connect

Подключение карты памяти SD/SDHC/MMC макетной платы Olimex SAM7-EX256-REV-B.

Настройка

Перед использованием SPI необходимо настроить, и настройка заключается в программировании контроллера PMC, настройке формирования выборки и настройке скорости SPI. Пример настройки приведен в подпрограмме if_spiInit, которая принимает в качестве параметра базовый адрес интерфейса SPI. Например, для SPI0 базовый адрес равен AT91C_BASE_SPI0 (0xFFFE0000), а для SPI1 базовый адрес равен AT91C_BASE_SPI1 (0xFFFE4000).

#define SPI_CSR_NUM 0 //номер выборки для карты SD/SDHC/MMC, сигнал CS
#define SPI_MR_PCS ((0<<0)|(1<<1)|(1<<2)|(1<<3))
#define PIN_SPI1_MISO {1 << 24, AT91C_BASE_PIOA, AT91C_ID_PIOA, PIO_PERIPH_B, PIO_PULLUP}
#define PIN_SPI1_MOSI {1 << 23, AT91C_BASE_PIOA, AT91C_ID_PIOA, PIO_PERIPH_B, PIO_DEFAULT}
#define PIN_SPI1_SPCK {1 << 22, AT91C_BASE_PIOA, AT91C_ID_PIOA, PIO_PERIPH_B, PIO_DEFAULT}
#define PIN_SPI1_CS {1 << 21, AT91C_BASE_PIOA, AT91C_ID_PIOA, PIO_PERIPH_B, PIO_DEFAULT}
#define PINS_MMC PIN_SPI1_MISO, PIN_SPI1_MOSI, PIN_SPI1_SPCK, PIN_SPI1_CS
#define SDCARD_BASE_SPI AT91C_BASE_SPI1#define SDCARD_ID_SPI AT91C_ID_SPI1
#define SDCARD_SPI_SCBR_MIN 2

const Pin pinsMMC[] = {PINS_MMC};
void if_spiInit(hwInterface *iface)
{
////////////////////////////////////////////////////////////////////////
// Начало настройки SPI AT91PS_SPI pSPI = SDCARD_BASE_SPI; AT91PS_PMC pPMC = AT91C_BASE_PMC; PIO_Configure(pinsMMC, PIO_LISTSIZE(pinsMMC)); //настройка ножек SPI   // Программирование PMC: разрешение тактирования для SPI pPMC->PMC_PCER = ( (euint32) 1 << SDCARD_ID_SPI ); //ID это просто номер бита   // SPI enable and reset pSPI->SPI_CR = AT91C_SPI_SPIEN | AT91C_SPI_SWRST;   // Режим SPI: master, фиксированная периферия, FDIV=0, // запрет определения ошибочной конфигурации pSPI->SPI_MR = AT91C_SPI_MSTR | AT91C_SPI_PS_FIXED | AT91C_SPI_MODFDIS; // установка поля PCS для фиксированного режима выбора периферии pSPI->SPI_MR |= ( (SPI_MR_PCS << 16) & AT91C_SPI_PCS );   // Настройка регистра SPI_CSR0: // 8 бит данных на 1 передачу, CPOL=1, ClockPhase=0, DLYBCT=0 pSPI->SPI_CSR[SPI_CSR_NUM] = AT91C_SPI_CPOL | AT91C_SPI_BITS_8;   // Настройка скорости SPI (FE попадет в поле SCBR, что обеспечит // малую скорость, необходимую для инициализации карты памяти). if_spiSetSpeed(0xFE);   // Разрешение работы SPI pSPI->SPI_CR = AT91C_SPI_SPIEN; // Конец настройки SPI
////////////////////////////////////////////////////////////////////////   // Отправка 20 команд SPI, когда карта не выбрана. for(u8 i=0;i<21;i++) { if_spiSend(iface,0xFF); }
}

Подпрограмма настройки скорости if_spiSetSpeed заполняет поле SCBR регистра SPI_CSR0 с проверкой на корректность входного параметра SCBRval.

void if_spiSetSpeed (u8 SCBRval)
{ u32 reg; AT91PS_SPI pSPI = SDCARD_BASE_SPI;   if ( SCBRval < SDCARD_SPI_SCBR_MIN ) SCBRval = SDCARD_SPI_SCBR_MIN; if ( SCBRval > 1 ) SCBRval &= 0xFE;   reg = pSPI->SPI_CSR[SPI_CSR_NUM]; reg = ( reg & ~(u32)(AT91C_SPI_SCBR) ) | ( (u32)SCBRval << 8 ); pSPI->SPI_CSR[SPI_CSR_NUM] = reg;
}

Передача и прием

Передача и прием происходят одновременно, поскольку SPI дуплексный (у него отдельные линии для приема и передачи, MISO и MOSI). Передача и прием осуществляются вызовом функции if_spiSend. На входе у неё базовый адрес интерфейса SPI (iface), передаваемый байт outgoing, а на выходе функция возвращает принятый байт.

u8 if_spiSend(hwInterface *iface, u8 outgoing)
{ u8 incoming;   AT91PS_SPI pSPI = SDCARD_BASE_SPI; //ожидание завершения передачи while( !( pSPI->SPI_SR & AT91C_SPI_TDRE ) ); //запуск передачи байта pSPI->SPI_TDR = (u16)( outgoing ); //ожидание завершения приема while( !( pSPI->SPI_SR & AT91C_SPI_RDRF ) ); //считывание и возврат полученного байта incoming = (u8)( pSPI->SPI_RDR ); return incoming;
}

В Примере 1 выборка slave-устройства генерируется аппаратно на каждый посылаемый байт (передается блок данных из 4 байт 00 FF 0F 0F):

SPI CSHW1 DMA0

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

//#define PIN_SPI0_CS {1 << 12, AT91C_BASE_PIOA, AT91C_ID_PIOA, PIO_PERIPH_A, PIO_DEFAULT}
#define PIN_SPI0_CS {1 << 12, AT91C_BASE_PIOA, AT91C_ID_PIOA, PIO_OUTPUT_1, PIO_DEFAULT}

При передаче блока данных нужно теперь из кода программировать логическое состояние ножки выборки. В примере кода ниже показано, как программно управлять выборкой (SPI1_NPCS0). В Gameduino передается блок данных из буфера buffer (определения портов, код инициализации SPI остаются без изменений).

   //выборку опускаем в 0 перед отправкой блока байт
   PIO_Clear(&pinsGAMEDUINO[3]);
   for (int idx=0; idx<sizeof(buffer); idx++)
   {
      //ожидание завершения передачи
      while( !( pSPI->SPI_SR & AT91C_SPI_TDRE ) );
      //запуск передачи байта
      pSPI->SPI_TDR = buffer[idx];
      //ожидание завершения приема
      while( !( pSPI->SPI_SR & AT91C_SPI_RDRF ) );
      //считывание полученного байта
      incoming = (u8)( pSPI->SPI_RDR );
   }
   //возвращаем выборку в 1 по окончании передачи блока байт
   PIO_Set(&pinsGAMEDUINO[3]);

В результате получится такая осциллограмма (передается блок данных из 4 байт 00 FF 0F 0F, сравните с осциллограммой из Примера 1):

SPI CSHW0 DMA0

В этом примере 8-битные посылки данных передаются единым блоком с использованием DMA. Преимущество использования DMA в следующем: можно написать программу так, что во время передачи микроконтроллер сможет заниматься другой работой, что значительно ускоряет обработку данных. В этом примере для упрощения используется просто ожидание окончания передачи путем опроса бита статуса SPI_SR.TXBUFE, хотя в реальной задаче лучше использовать прерывание для сигнализации об окончании передачи блока данных. Выборка slave-устройства может генерироваться как программно, так и аппаратно (по флажку useCSHW).

//набор ножек SPI для аппаратного управления выборкой
#define PINS_GAMEDUINO_CSHW PIN_SPI0_MISO, PIN_SPI0_MOSI, PIN_SPI0_SPCK, PIN_SPI0_CSHW
const Pin pinsGAMEDUINO_CSHW[]    = {PINS_GAMEDUINO_CSHW};
//набор ножек SPI для программного управления выборкой
#define PINS_GAMEDUINO_CSSW PIN_SPI0_MISO, PIN_SPI0_MOSI, PIN_SPI0_SPCK, PIN_SPI0_CSSW
const Pin pinsGAMEDUINO_CSSW[]    = {PINS_GAMEDUINO_CSSW};
 
//буфер DMA для передачи
u32 TXSPIDMAbuf[(4+ZXSPECTRUM_SCREEN_SIZE)];
 
void init_spi_Gameduino (void)
{
   if (useCSHW)
      PIO_Configure(pinsGAMEDUINO_CSHW, PIO_LISTSIZE(pinsGAMEDUINO_CSHW));
   else
      PIO_Configure(pinsGAMEDUINO_CSSW, PIO_LISTSIZE(pinsGAMEDUINO_CSSW));
   AT91C_BASE_PMC->PMC_PCER = ( 1 << GAMEDUINO_ID_SPI );
   // Запрет SPI
   GAMEDUINO_BASE_SPI->SPI_CR = AT91C_SPI_SPIDIS;
   // Инициализация контроллера PDC (DMA) для SPI:
   // запрет PDC TX и RX
   GAMEDUINO_BASE_SPI->SPI_PTCR = AT91C_PDC_TXTDIS | AT91C_PDC_RXTDIS;
   // инициализация счетчиков и указателей на буфер в 0
   // "следующий" буфер TX
   GAMEDUINO_BASE_SPI->SPI_TNPR = 0;
   GAMEDUINO_BASE_SPI->SPI_TNCR = 0;
   // "следующий" буфер RX
   GAMEDUINO_BASE_SPI->SPI_RNPR = 0;
   GAMEDUINO_BASE_SPI->SPI_RNCR = 0;
   // буфер TX
   GAMEDUINO_BASE_SPI->SPI_TPR = 0;
   GAMEDUINO_BASE_SPI->SPI_TCR = 0;
   // буфер RX
   GAMEDUINO_BASE_SPI->SPI_RPR = 0;
   GAMEDUINO_BASE_SPI->SPI_RCR = 0;
   // Разрешение SPI и его сброс
   // "Кажется, что у машины состояний для revB версии должно быть
   // 2 программных сброса SPI, сброс прошел успешно."
   GAMEDUINO_BASE_SPI->SPI_CR = AT91C_SPI_SWRST;
   GAMEDUINO_BASE_SPI->SPI_CR = AT91C_SPI_SWRST;
   GAMEDUINO_BASE_SPI->SPI_CR = AT91C_SPI_SPIEN;
 
   // Режим SPI: master, FDIV=0, запрет детектирования ошибки конфигурации.
   GAMEDUINO_BASE_SPI->SPI_MR  = AT91C_SPI_MSTR | AT91C_SPI_MODFDIS;
 
   // Установка регистра выборки чипа:
   // 8 бит на передачу, CPOL=1, ClockPhase=0, DLYBCT = 0
   GAMEDUINO_BASE_SPI->SPI_CSR[SPI_CSR_NUM] = AT91C_SPI_CPOL | AT91C_SPI_BITS_8;
   // Разрешение работы SPI
   GAMEDUINO_BASE_SPI->SPI_CR = AT91C_SPI_SPIEN;
}
 
void send_block_to_Gameduino (void *buf, int bytes)
{
   int idx;
 
   init_spi_Gameduino();
 
   LED(1);
   if (!useCSHW)
      PIO_Clear(&pinsGAMEDUINO_CSSW[3]);
   //передача
   if (GAMEDUINO_BASE_SPI->SPI_MR & AT91C_SPI_PS)
   {
      //Подготовка буфера DMA, если SPI_MR.PS=1 (режим переменной периферии)
      for (idx=0; idx<(bytes-1); idx++)
      {
         TXSPIDMAbuf[idx] = ((u8*)buf)[idx];
      }
      TXSPIDMAbuf[idx] = ((u8*)buf)[idx] | AT91C_SPI_LASTXFER;
      while (0 == SPI_WriteBuffer(GAMEDUINO_BASE_SPI, TXSPIDMAbuf, bytes));
   }
   else
      while (0 == SPI_WriteBuffer(GAMEDUINO_BASE_SPI, buf, bytes));
   //ожидание окончания передачи
   while (0 == (GAMEDUINO_BASE_SPI->SPI_SR & AT91C_SPI_TXBUFE));
 
   if (!useCSHW)
      PIO_Set(&pinsGAMEDUINO_CSSW[3]);
   LED(0);
 
   close_spi_Gameduino();
}
 
//------------------------------------------------------------------------------
/// Посылает содержимое буфера buffer через периферийное устройство SPI,
/// используя PDC для обслуживания транзакции.
/// \param spi    указатель на экземляр AT91S_SPI.
/// \param buffer указатель на отправляемый буфер.
/// \param length длина данных в буфере.
//------------------------------------------------------------------------------
unsigned char SPI_WriteBuffer(AT91S_SPI *spi,
                              void *buffer,
                              unsigned int length)
{
   // Проверка, пуст ли первый банк:
   if (spi->SPI_TCR == 0)
   {
      spi->SPI_TPR = (unsigned int) buffer;
      spi->SPI_TCR = length;
      spi->SPI_PTCR = AT91C_PDC_TXTEN;
      return 1;
   }
   // Проверка, пуст ли второй банк:
   else if (spi->SPI_TNCR == 0)
   {
      spi->SPI_TNPR = (unsigned int) buffer;
      spi->SPI_TNCR = length;
      return 1;
   }
      
   // Нет свободных банков:
   return 0;
}

 

Обратите внимание, что в случае выбора переменной периферии SPI_MR.PS=1 для DMA используется специально подготовленный 32-битный буфер. В последнюю ячейку буфера записывается признак SPI_TDR.LASTXFER, который нужен для аппаратного возврата сигнала выборки в неактивное состояние. На первой осциллограмме DMA работает с программно формируемой выборкой, используется 8-битный буфер DMA (передается блок данных из 4 байт 00 FF 0F 0F):

SPI CSHW0 DMA1

На второй осциллограмме DMA работает вместе с аппаратно формируемой выборкой, для этого используется 32-битный буфер DMA (передается блок данных из 4 байт 00 FF 0F 0F):

SPI CSHW1 DMA1

[Словарик]

CS сигнал выборки устройства на шине SPI. Если это вход, то для микроконтроллера ARM7 он называется также NSS (в этой статье не рассматривается), а если выход, то NPCSx (NPCS0, NPCS1, NPCS2, NPCS3).

DMA аббревиатура от Direct Memory Access, что означает "прямой доступ к памяти". Имеется в виду, что периферия (в нашем случае это интерфейс SPI) получает доступ к памяти без участия вычислительного ядра ARM, что разгружает ядро для других задач и ускоряет работу firmware. См. также PDC.

MMC MultiMedia Card - типоразмер стандартных карт памяти (обычно емкостью до 512 мегабайт). Это уже устаревший тип карт, который не выпускается. Он физически совместим с форматом SD снизу вверх, т. е. карту MMC можно вставить в слот для карт SD, но не наоборот (карта MMC тоньше, имеет толщину 1.45 мм, а карта SD имеет толщину 2.1 мм). См. также SD, SDHC.

MISO Master Input Slave Output провод шины SPI, вход главного устройства (master), выход подчиненного (slave).

MOSI Master Output Slave Input провод шины SPI, выход главного устройства (master), вход подчиненного (slave).

PMC Power Management Controller - контроллер управления питанием, который находится внутри чипа ARM7. Название немного неочевидное, поскольку на самом деле PMC отвечает за включение/выключение тактирования периферии (в нашем случае PMC необходимо настроить, чтобы заработало тактирование периферии SPI).

PDC Peripheral DMA Controller, контроллер прямого доступа к памяти. См. также DMA.

SD карты Security Digital (иногда их называют SDC, Security Digital Card) - типоразмер стандартных карт памяти размером до 2 гигабайт включительно. Карты SD и SDHC имеют одинаковый типоразмер. См. также MMC, SDHC.

SDHC карты Secure Digital High Capacity - типоразмер стандартных карт памяти повышенной емкости (до 32 гигабайт включительно). Карты SD и SDHC имеют одинаковый типоразмер. См. также MMC, SD.

SPI Serial Peripheral Interface, популярная последовательная шина для обмена данными. Шина имеет архитектуру точка-точка, т. е. на шине в любой момент времени может присутствовать только 2 активных участника обмена данными - главное устройство (SPI master) и подчиненное устройство (SPI slave). Между master и slave возможен дуплексный обмен данными.

SPI_CR SPI Control Register - регистр управления SPI.

SPI_MR SPI Mode Register - регистр режима SPI.

SPI_CSRx SPI Chip Select Register - регистр выбора чипа SPI. Имеется 4 регистра SPI_CSRx: SPI_CSR0, SPI_CSR1, SPI_CSR2, SPI_CSR3.

SPCK тактовый сигнал шины SPI, генерируется устройством master (в нашем случае это микроконтроллер ARM7).

[Ссылки]

1. Как использовать карты памяти MMC/SDC.
2. Макетная плата AT91SAM7X.
3. Исходный код, иллюстрирующий работу с SPI - проект 121003ZX-screen-test. Передается экран ZX Spectrum в адаптер Gameduino, экраны записываются в каталог SCR карты SD. Можно также запустить на передачу по SPI только один файл test.bin произвольного размера (от 1 до 6912 байт).
4. Gameduino: экран для ZX Spectrum.
5. AT91SAM7X: контроллер PDC