AT91SAM7X: контроллер PDC Печать
Добавил(а) microsin   

Технология прямого доступа к памяти DMA (Direct Memory Access) в микропроцессорах и микроконтроллерах применяется для разгрузки CPU от рутинных операций по пересылке данных между оперативной памятью (RAM, SRAM) и различными периферийными устройствами (UART, SPI, контроллеры дисплея и т. д.). Поскольку при этом пересылками данных занимается отдельное аппаратное устройство (не CPU), то быстродействие программ и эффективность работы значительно повышается. Периферийное устройство DMA в микроконтроллерах Atmel серий AT91SAM называется PDC (расшифровывается как Peripheral DMA Controller). Здесь переведен кусок даташита на микроконтроллер AT91SAM7X512 (а также AT91SAM7X128, AT91SAM7X256), посвященный PDC.

[Общий обзор PDC AT91SAM7X]

Peripheral DMA Controller (PDC) передает данные между оперативной памятью и встроенными в микроконтроллер периферийными устройствами, такими как UART, USART, SSC, SPI, MCI. Для микроконтроллеров AT91SAM7X128, AT91SAM7X256 и AT91SAM7X512 оперативная память может быть только встроенная (SRAM), тогда как другие микроконтроллеры, оснащенные контроллером внешней памяти, могут использовать также память, подключенную снаружи (статическую или динамическую). Использование PDC позволяет устранить вмешательство программной обработки CPU для поддержки процедур перемещения данных, и снимает с CPU нагрузку на пересылку данных и обработку прерываний. Это значительно снижает количество циклов, требуемых для переноса данных, и в результате снижает энергопотребление и повышает скорость работы программы.

Каналы PDC реализованы в виде пар, каждая пара выделена для отдельного периферийного устройства. Один канал в паре выделен для канала приема, другой канал для передачи для каждого UART, USART, SSC и SPI.

Программный интерфейс пользователя (регистры) каналов PDC интегрированы в адресное пространство каждого периферийного устройства. Он содержит:

• 32-битный регистр указателя на память
• 16-битный регистр счетчика переносимых байт
• 32-битный регистр следующего указателя на память
• 16-битный регистр следующего счетчика переносимых байт

Соответствующее периферийное устройство (UART, USART, SSC, SPI) переключает передачи PDC с использованием сигналов передачи и приема. Когда запрограммированные данные перенесены, соответствующим периферийным устройством генерируется прерывание окончания процедуры переноса данных (это может быть прием или передача).

AT91SAM7X-PDC-Block-Diagram

Рис. 22-1. Блок-схема контроллера прямого доступа к памяти PDC (Peripheral DMA Controller).

В микроконтроллерах AT91SAM7X512 (а также AT91SAM7X128, AT91SAM7X256) функциями PDC (DMA) оснащены следующие периферийные устройства: SPI0, SPI1, USART0, USART1, SSC. К сожалению, периферийные устройства USB, CAN, Ethernet функциями DMA не оснащены.

[Функциональное описание PDC]

Интерфейс пользователя каналов PDC (канал приема, канал передачи) позволяют программисту управлять потоками данных и конфигурировать их для каждого канала. Интерфейс пользователя канала PDC интегрирован в интерфейс пользователя соответствующего периферийного устройства, к которому PDC относится, со смещением адреса 0x100 (карту регистров PDC см. в разделе "Интерфейс пользователя PDC"). На каждое периферийное устройство приходится по 4 32-битных регистра (RPR, RNPR, TPR и TNPR, о них подробнее в разделе "Указатели на память") и 4 16-битных регистра (RCR, RNCR, TCR и TNCR, о них подробнее в разделе "Счетчики переноса данных").

Размер буфера в единицах передаваемых порций данных (единица может быть байт, 2 байта или 4 байта) конфигурируется в 16-битном счетчике пересылки (TCR, TNCR для передачи или RCR, RNCR для приема). Счетчик может быть прочитан в любой момент, чтобы определить, сколько единиц данных осталось передать для каждого канала.

Базовый адрес пересылки конфигурируется в 32-битном указателе на память. Этим определяется место первого адреса в блоке памяти, куда будет осуществляться доступ. В любой момент времени можно прочитать значение этого указателя, и по нему определить место назначения следующей порции данных. PDC имеет выделенные регистры состояния, которые показывают - разрешен или нет перенос данных для каждого канала. Статус каждого канала размещен в регистре состояния соответствующего периферийного устройства. Передачи могут быть разрешены или запрещены через установку бит TXTEN/TXTDIS (для передачи) и RXTEN/RXTDIS (для приема) в регистре PDC Transfer Control Register. Эти биты управления делают возможным безопасно читать регистры указателей и счетчиков - без риска, что они изменятся между чтениями.

PDC выставляет флаги статуса в регистры состояния периферийного устройства (ENDRX, ENDTX, RXBUFF и TXBUFE).

ENDRX устанавливается, когда регистр PERIPH_RCR (счетчик приема) достигает нуля.
RXBUFF устанавливается, когда оба счетчика приема, и PERIPH_RCR, и PERIPH_RNCR достигли нуля.
ENDTX устанавливается, когда регистр PERIPH_TCR (счетчик передачи) достиг нуля.
TXBUFE устанавливается, когда оба счетчика передачи, и PERIPH_TCR и PERIPH_TNCR достигли нуля.

Эти флаги статуса описаны в регистре статуса соответствующего периферийного устройства (peripheral status register). К примеру, если это периферийное устройство SPI, то это будет регистр статуса SPI_SR.

31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16
не используется SPIENS
- - - - - - - - - - - - - - - r
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
не используется TXEMPTY NSSR TXBUFE RXBUFF ENDTX ENDRX OVRES MODF TDRE RDRF
- - - - - - r r r r r r r r r r

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

ENDRX: End of RX buffer (признак окончания буфера приема)
0 = Receive Counter Register (счетчик приема) не достиг 0 с момента последней записи в SPI_RCR(1) или в SPI_RNCR(1).
1 = Receive Counter Register (счетчик приема) достиг 0 с момента последней записи в SPI_RCR(1) или в SPI_RNCR(1).
ENDTX: End of TX buffer (признак окончания буфера передачи)
0 = Transmit Counter Register (счетчик передачи) не достиг 0 с момента последней записи в SPI_TCR(1) или в SPI_TNCR(1).
1 = Transmit Counter Register (счетчик передачи) достиг 0 с момента последней записи в SPI_TCR(1) или в SPI_TNCR(1).
RXBUFF: RX Buffer Full (буфер приема заполнен)
0 = SPI_RCR(1) или SPI_RNCR(1) имеют значение, не равное 0.
1 = Оба регистра, и SPI_RCR(1), и SPI_RNCR(1) равны 0.
TXBUFE: TX Buffer Empty (буфер передачи пуст)
0 = SPI_TCR(1) или SPI_TNCR(1) имеют значение, не равное 0.
1 = Оба регистра, и SPI_TCR(1), и SPI_TNCR(1) равны 0.

Примечание (1). Регистры SPI_RCR, SPI_RNCR, SPI_TCR, SPI_TNCR физически размещены в PDC, хотя находятся в адресном пространстве интерфейса пользователя SPI.

[Указатели на память (Memory Pointers)]

Каждое периферийное устройство (UART, SPI и т. п.) соединено с PDC двумя каналами - каналом приема и каналом передачи. Каждый канал снабжен внутренним 32-битным указателем на память. Каждый указатель на память указывает на какое-то место в адресном пространстве памяти (эта встроенная в чип оперативная память как у микроконтроллеров AT91SAM7X, или это может быть даже внешняя память, подключенная через внешнюю шину и контроллер памяти).

В зависимости от типа переноса данных периферийного устройства (побайтно, полусловами в 2 байта или словами по 4 байта), указатель на память с каждой передачей порции данных будет инкрементирован на 1, 2 или 4.

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

[Счетчики переноса данных (Transfer Counters)]

Имеется внутренний 16-битный счетчик для каждого канала, используемый для подсчета уже переданных данных из всего блока. Счетчики декрементируются после переноса каждой отдельной порции данных. Когда счетчик достигает 0, то перенос данных завершен, и PDC останавливает процедуру переноса данных.

Если Next Counter Register (счетчик для следующего переноса) равен 0, то PDC запрещает переключение на следующую операцию переноса и устанавливает соответствующий флаг завершения (TNCR для передачи и RNCR для приема).

Если счетчик был перепрограммирован во время активной операции PDC, то количество передач (или приемов) порций данных обновляется, и PDC считает передачи (или приемы) от новой величины счетчика (обычно так никогда не делают, счетчики настраивают перед началом переноса данных, когда процесс переноса еще не активен, или уже завершен).

Программирование регистров Next Counter/Pointer позволяет организовать передачу буферов по цепочке. В этом случае счетчики переноса декрементируются в процессе передачи как обычно, но когда счетчик переноса достиг нуля, то значения из регистров Next Counter/Pointer автоматически загружаются в текущие регистры Counter/Pointer, и процесс переноса данных возобновляется. В этот момент генерируется прерывание, и можно программно загрузить в регистры Next Counter/Pointer значение для следующего буфера. Таким способом организуют непрерывную во времени передачу, при этом CPU загружается очень незначительно.

Для каждого канала 2 бита статуса показывают окончание текущего буфера (ENDRX, ENTX) и окончание сразу двух буферов, и текущего и следующего (RXBUFF, TXBUFE). Эти биты находятся прямо в регистре состояния соответствующего периферийного устройства, и могут вызывать срабатывание запроса на прерывание для AIC (Advanced Interrupt Controller). Флаги завершения периферийных устройств автоматически очищаются, когда был записан один из регистров-счетчикв (Counter или Next Counter Register).

Примечание: когда содержимое Next Counter Register загружается в Counter Register, значение Next Counter Register сбрасывается в 0.

[Процессы переноса данных (Data Transfers)]

Периферийное устройство переключает переносы данных PDC с использованием сигналов готовности к передаче (TXRDY) и приему (RXRDY).

Когда периферийное устройство приняло поступившие данные (например, символ, поступивший на UART), оно посылает сигнал Receive Ready к PDC, который затем запрашивает доступ к системной шине микроконтроллера. Когда доступ предоставлен, PDC запускает чтение регистра Receive Holding Register (RHR) и затем переключается на запись принятых данных в память. После приема каждой порции данных соответствующий указатель на память PDC инкрементируется, и количество оставшихся приемов (счетчик) декрементируется. Когда принят весь блок данных, то периферийному устройству посылается сигнал, и прием данных останавливается. Та же самая процедура происходит и при передаче, только в обратном порядке (т. е. сначала считывается порция данных из памяти, и затем передается в периферийное устройство).

[Приоритет запросов PDC (PDC Transfer Requests)]

Каждый Peripheral DMA Controller обрабатывает запросы на перенос данных в соответствии с приоритетами, фиксированными для каждой модели чипа. Эти приоритеты заданы в даташите на соответствующий микроконтроллер. Приоритет одновременных запросов одинакового типа (приемник или передатчик), произошедшие на одинаковых периферийных устройствах, определяется по нумерации периферийных устройств.

Если запросы на перенос поступили не одновременно, то они обрабатываются в порядке их поступления. Запросы от приемников обрабатываются в первую очередь, и затем обрабатываются запросы передатчиков.

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

Все регистры в таблице доступны и на чтение и запись (доступ Read-write), кроме регистра PERIPH_PTCR (доступ Write-only) и регистра PERIPH_PTSR (доступ Read-only). Также у всех регистров, которые можно читать, начальное значение после сброса равно 0.

Таблица 22-1. Карта регистров PDC, привязанная к периферийному устройству.

Смещение Регистр Имя Доступ Сброс
0x100 Receive Pointer Register PERIPH_RPR R/W 0
0x104 Receive Counter Register PERIPH_RCR R/W 0
0x108 Transmit Pointer Register PERIPH_TPR R/W 0
0x10C Transmit Counter Register PERIPH_TCR R/W 0
0x110 Receive Next Pointer Register PERIPH_RNPR R/W 0
0x114 Receive Next Counter Register PERIPH_RNCR R/W 0
0x118 Transmit Next Pointer Register PERIPH_TNPR R/W 0
0x11C Transmit Next Counter Register PERIPH_TNCR R/W 0
0x120 PDC Transfer Control Register PERIPH_PTCR W -
0x124 PDC Transfer Status Register PERIPH_PTSR R 0

Примечание: приставка PERIPH заменяется на имя соответствующего периферийного устройства. Все 10 регистров PDC привязаны к области памяти периферийного устройства с одним и тем же смещением, которое относится к этому периферийному устройству. Это позволяет определить адреса для регистров PDC с использованием смещений нужного периферийного устройства (DBGU, USART, SSC, SPI, MCI и т. д.).

PDC Receive Pointer Register (PERIPH_RPR). 32-битный регистр, который указывает на адрес, куда будет записана следующая принимаемая порция данных (RXPTR: Receive Pointer Address). Доступен и на чтение, и на запись.

PDC Receive Counter Register (PERIPH_RCR). 16-битный регистр, в котором содержится значение счетчика оставшихся для приема порций данных (RXCTR: Receive Counter Value). Доступен и на чтение, и на запись.

PDC Transmit Pointer Register (PERIPH_TPR). 32-битный регистр, который указывает на адрес, откуда будет прочитана следующая передаваемая порция данных (TXPTR: Transmit Pointer Address). Доступен и на чтение, и на запись.

PDC Transmit Counter Register (PERIPH_TCR). 16-битный регистр, в котором содержится значение счетчика оставшихся для передачи порций данных (TXCTR: Transmit Counter Value). Доступен и на чтение, и на запись. Когда достигает нуля, то процесс передачи через DMA автоматически останавливается.

PDC Receive Next Pointer Register (PERIPH_RNPR). Доступен и на чтение, и на запись. Регистр содержит 32-битное поле RXNPTR: Receive Next Pointer Address. Это адрес (указатель) на следующий буфер, который будет далее наполняться принимаемыми данными, когда заполнится текущий буфер.

PDC Receive Next Counter Register (PERIPH_RNCR). Доступен и на чтение, и на запись. Регистр содержит 16-битное поле RXNCR: Receive Next Counter Value. Это адрес (указатель) на следующий буфер, который будет далее заполняться, когда переполнится текущий буфер.

PDC Transmit Next Pointer Register (PERIPH_TNPR). Доступен и на чтение, и на запись. Регистр содержит 32-битное поле TXNPTR: Transmit Next Pointer Address. Это адрес (указатель) на следующий буфер, который будет далее передаваться, когда опустошится текущий буфер.

PDC Transmit Next Counter Register (PERIPH_TNCR). Доступен и на чтение, и на запись. Содержит 16-битное поле счетчика TXNCR: Transmit Next Counter Value для следующего буфера передачи.

PDC Transfer Control Register (PERIPH_PTCR). Доступен только на запись. Это регистр управления передачей, содержит следующие поля:

• RXTEN: Receiver Transfer Enable
0 = Не производит эффекта.
1 = Разрешает выдачу запросов PDC приемника, если не установлен бит RXTDIS.
• RXTDIS: Receiver Transfer Disable
0 = Не производит эффекта.
1 = Запрещает запросы PDC от приемника.
• TXTEN: Transmitter Transfer Enable
0 = Не производит эффекта.
1 = Разрешает выдачу запросов PDC передатчика.
• TXTDIS: Transmitter Transfer Disable
0 = Не производит эффекта.
1 = Запрещает запросы PDC от передатчика.

PDC Transfer Status Register (PERIPH_PTSR). Доступен только на чтение. Содержит только 2 бита, показывающие рабочее состояние PDC для данного периферийного устройства.

• RXTEN: Receiver Transfer Enable
0 = Запросы PDC от приемника запрещены.
1 = Запросы PDC от приемника разрешены.
• TXTEN: Transmitter Transfer Enable
0 = Запросы PDC от передатчика запрещены.
1 = Запросы PDC от передатчика разрешены.

[Примеры работы c периферийными устройствами AT91SAM7X через DMA]

В этом примере микроконтроллер AT91SAM7X ведет обмен с DSP Blackfin через порт SSC с использованием DMA. Чтобы не загромождать пример, некоторые подпрограммы опущены (например, вычисление CRC16). Прием и передача запускаются поочередно. AT91SAM7X ожидает пакет DSP (постоянно работает на прием), и сразу после получения пакета переключается на передачу и выдает ответ. Такая модель обмена данными выбрана для упрощения отладки протокола. Пакеты и на приеме, и на передаче имеют одинаковую структуру и размер 560 байт. Каждый пакет в начале имеет синхрозаголовок из 6 байт 0xA5, в конце контрольную сумму CRC16 и маркер конца пакета из 6 байт 0xE6. О начале передачи DSP оповещает специальным логическим сигналом, по которому срабатывает прерывание ISR_INTdsp, запуская приемник SSC. AT91SAM7X оповещает о начале своей передаче аналогичным сигналом outINTarm. Здесь описан только один из возможных многочисленных вариантов организации обмена данными через SSC, поэтому не нужно его принимать как догму.

Определения вспомогательных типов:

#define SSC_A5_LEN 6 /*синхробайты начала 0xA5*/
#define SSC_CMD_LEN 2 /*код команды*/
#define SSC_SERVICE_LEN 32 /*сервисные данные*/
#define SSC_DATA_LEN 512
#define SSC_CRC_LEN 2
#define SSC_E6_LEN 6 /*синхробайты конца 0xE6*/
#define SSC_HEADER_LEN (SSC_A5_LEN+SSC_CMD_LEN+SSC_SERVICE_LEN)
#define SSC_TAIL_LEN (2/*CRC*/+SSC_E6_LEN)
#define SSC_BLOCK_LEN (SSC_HEADER_LEN + SSC_DATA_LEN + SSC_TAIL_LEN)
#define SSC_BIT_PER_FRAME 32
  
#define FillSscHeader(buf) memset(buf, 0xA5, SSC_A5_LEN)
#define FillSscFooter(buf) memset(&(buf[SSC_BLOCK_LEN-SSC_E6_LEN]), 0xE6, SSC_E6_LEN)
  
//биты tSSC.flags
#define SSCRX_HANDLER_UPDATED (1 << 0) //обновлен хендлер приема
#define SSCTX_HANDLER_UPDATED (1 << 1) //обновлен хендлер передачи
#define SSCTX_PACKET_UPDATED (1 << 2) //обновился буфер передачи. Это значит, что перед запуском передачи
                                              // обработчик ISR должен обновить данные в буфере PDC (DMA).
#define SSCRX_CANDATA (1 << 3) //есть данные для CAN
#define SSCRX_NEWCANDATA (1 << 4) //данные для CAN пришли только что
#define SSCTX_UPDATE_DSP_RQ (1 << 5) //сигнал вызвать TXhandler, чтобы подготовить новые данные 
                                              // передачи для DSP. Флаг устанавливается в любом месте
                                              // основной программы, а обрабатывается и сбрасывается 
                                              // в DSPhandler (цикл main).
#define SSCTX_DSP_PAUSE (1 << 6) //пауза для работы DSP
#define SSCTX_COMPLETED (1 << 7) //передача полностью завершена
  
typedef __packed struct _SSC
{
   u8 pdcbufRX[SSC_BLOCK_LEN]; // буфер PDC (DMA) приема
   u8 pdcbufTX[SSC_BLOCK_LEN]; // буфер PDC (DMA) передачи
   u8 bufRX   [SSC_BLOCK_LEN-(SSC_A5_LEN+SSC_E6_LEN)]; // выходной буфер приема
   u8 bufTX   [SSC_BLOCK_LEN-(SSC_A5_LEN+SSC_E6_LEN)]; // выходной буфер передачи
   void (*RXhandler)(u8 idxSSCrx);
   void (*TXhandler)(void);
   u8 tail_detected;           // синхробайты конца 0xE6 0xE6 0xE6 0xE6 0xE6 0xE6 прошли
   u8 txflags;                   // флаги для передачи
   u8 rxflags;                   // флаги для приема
   u32 rectimeout;
   int reciv_shift;
   tERRSSCcounters errcnt;
   tSSCcounters cnt;
} tSSC;
  
typedef __packed struct _ERRSSCcounters
{
   u32 missheader;          // счетчик не найденных заголовков
   u32 crc;                 // счетчик ошибок CRC
   u32 timeout;             // ошибка таймаута
}tERRSSCcounters;
  
typedef __packed struct _SSCcounters
{
   u32 tx;                  // сколько было передач
   u32 rx;                  // сколько было успешных приемов
   u32 intdsp;
   u32 rxOK;                // сколько было успешных приемов
}tSSCcounters;
  
typedef __packed struct _SSC
{
   u8 pdcbufRX[SSC_BLOCK_LEN]; // буфер PDC (DMA) приема
   u8 pdcbufTX[SSC_BLOCK_LEN]; // буфер PDC (DMA) передачи
   u8 bufRX   [SSC_BLOCK_LEN-(SSC_A5_LEN+SSC_E6_LEN)]; // выходной буфер приема
   u8 bufTX   [SSC_BLOCK_LEN-(SSC_A5_LEN+SSC_E6_LEN)]; // выходной буфер передачи
   void (*RXhandler)(u8 idxSSCrx);
   void (*TXhandler)(void);
   u8 tail_detected;           // синхробайты конца 0xE6 0xE6 0xE6 0xE6 0xE6 0xE6 прошли
   u8 txflags;                   // флаги для передачи
   u8 rxflags;                   // флаги для приема
   u32 rectimeout;
   int reciv_shift;
   tERRSSCcounters errcnt;
   tSSCcounters cnt;
} tSSC;
  
//общая схема передаваемого блока, всего 6+546+6 = 558 байт
typedef __packed struct _TRawBlock
{
    //u8 A5[6]; //0xA5 0xA5 0xA5 0xA5 0xA5 0xA5
    u16 cmd;        //код команды
    u8 serv[32];    //сервисные данные, которые зависят от команды
    u8 data[512];   //512 байт полезных данных
    u16 CRC16;      //2 байта CRC
    //u8 E6[6]; //0xE6 0xE6 0xE6 0xE6 0xE6 0xE6 синхробайты конца
}TRawBlock;
  
extern tSSC vSSC;  //служебные данные порта SSC

Общие переменные (буферы приема и передачи, статистика и т. п.), вспомогательные подпрограммы:

tSSC vSSC;  //служебные данные порта SSC
void FillSscHeader (u8 *buf) { memset(buf, 0xA5, SSC_A5_LEN); }
void FillSscFooter (u8 *buf) { //считаем CRC ((TRawBlock*)buf)->CRC16 = CRC_START_VAL; AddCRC16(&(buf[6]), 2+32+512, (u16*)&((TRawBlock*)buf)->CRC16); //заполняем хвост - шесть байт 0xE6 memset(&(buf[SSC_BLOCK_LEN-SSC_E6_LEN]), 0xE6, SSC_E6_LEN); }
//процедура запускает отправку буфера vSSC.pdcbufTX void SSCtransmit (void) { u16 donetimeout = 0; FillSscHeader(vSSC.pdcbufTX); FillSscFooter(vSSC.pdcbufTX); //Запустим передачу. while (!vSSC.txdone) { donetimeout++; if (donetimeout > 20000) vSSC.txdone = true; } //даем сигнал для DSP (сбрасываем в 0) PIO_Configure(&outINTarm, 1); vSSC.txdone = false; #if (SSC_BIT_PER_FRAME < = 8) SSC_WriteBuffer(AT91C_BASE_SSC, (void *) vSSC.pdcbufTX, SSC_BLOCK_LEN); #elif (SSC_BIT_PER_FRAME < = 16) SSC_WriteBuffer(AT91C_BASE_SSC, (void *) vSSC.pdcbufTX, SSC_BLOCK_LEN/2); #elif (SSC_BIT_PER_FRAME < = 32) SSC_WriteBuffer(AT91C_BASE_SSC, (void *) vSSC.pdcbufTX, SSC_BLOCK_LEN/4); #else #error "SSC_BIT_PER_FRAME invalid" #endif //разрешим прерывание по окончанию передачи SSC_EnableInterrupts(AT91C_BASE_SSC, AT91C_SSC_TXBUFE); //разрешаем передатчик SSC_EnableTransmitter(AT91C_BASE_SSC); vSSC.cnt.tx++; }
void setSSChandlers (void rx(void), void tx(void)) { vSSC.RXhandler = rx; vSSC.TXhandler = tx; }

Код инициализации SSC:

void ConfigureSSC (u32 baudrate)
{
    //конфигурируем прерывание от сигнала передачи от DSP
    PIO_EnableIt(&inINTdsp);
    //сбрасываем переменные SSC
    vSSC.cnt.tx             = 0;
    vSSC.cnt.rx             = 0;
    vSSC.cnt.rxOK           = 0;
    vSSC.tail_detected      = false;
    vSSC.txflags            = SSCTX_COMPLETED;
    vSSC.rxflags            = 0;
    vSSC.errcnt.missheader  = 0;
    vSSC.errcnt.crc         = 0;
    vSSC.errcnt.timeout     = 0;
    vSSC.rectimeout      = 0;
    vSSC.reciv_shift     = 0;
    memset(vSSC.pdcbufRX, 0, SSC_BLOCK_LEN);
    memset(vSSC.bufRX,    0, SSC_BLOCK_LEN);
    FillSscHeader(vSSC.pdcbufTX);
    FillSscFooter(vSSC.pdcbufTX);
    //конфигурируем ножки у SSC
    PIO_Configure(SSC_pins, PIO_LISTSIZE(SSC_pins));
    SSC_Configure(AT91C_BASE_SSC,
                  AT91C_ID_SSC,
                  baudrate,
                  BOARD_MCK);
    //конфигурируем передатчик и приемник:
    SSC_ConfigureTransmitter(AT91C_BASE_SSC,
           (AT91C_SSC_CKS_DIV | AT91C_SSC_CKO_CONTINOUS | AT91C_SSC_START_FALL_RF | 
SSC_STTDLY(0) | SSC_PERIOD(33) | AT91C_SSC_CKI), (SSC_DATLEN(SSC_BIT_PER_FRAME) | SSC_DATNB(1) | SSC_FSLEN(1) | AT91C_SSC_FSOS_POSITIVE)); SSC_ConfigureReceiver(AT91C_BASE_SSC, (AT91C_SSC_CKS_RK | AT91C_SSC_START_FALL_RF | SSC_STTDLY(0) | AT91C_SSC_CKI), (SSC_DATLEN(SSC_BIT_PER_FRAME) | SSC_DATNB(1) | SSC_FSLEN(1))); //конфигурируем и разрешаем SSC interrupt AIC_ConfigureIT(AT91C_ID_SSC, AT91C_AIC_PRIOR_LOWEST+2, ISR_Ssc); AIC_EnableIT(AT91C_ID_SSC); }

Обработчики прерываний:

//------------------------------------------------------------------------------
/// Interrupt handler для прерывания от DSP (переход в нолик сигнализирует 
/// о начале передачи).
//------------------------------------------------------------------------------
void ISR_INTdsp (void)
{
   if (!PIO_Get(&inINTdsp)) 
   {
      vSSC.cnt.intdsp++;
      
      //Настроим прием
      AT91C_BASE_SSC->SSC_RCR = 0;
#if (SSC_BIT_PER_FRAME < = 8)
      SSC_ReadBuffer(AT91C_BASE_SSC, vSSC.pdcbufRX, SSC_BLOCK_LEN);
#elif (SSC_BIT_PER_FRAME < = 16)
      SSC_ReadBuffer(AT91C_BASE_SSC, vSSC.pdcbufRX, SSC_BLOCK_LEN/2);
#elif (SSC_BIT_PER_FRAME < = 32)
      SSC_ReadBuffer(AT91C_BASE_SSC, vSSC.pdcbufRX, SSC_BLOCK_LEN/4);
#else
#error "SSC_BIT_PER_FRAME invalid"
#endif 
      SSC_EnableReceiver(AT91C_BASE_SSC);
      SSC_EnableInterrupts(AT91C_BASE_SSC, AT91C_SSC_RXBUFF);
   }
}
//------------------------------------------------------------------------------
/// Interrupt handler for the SSC. Срабатывает на события окончания передачи,
/// окончания приема. Сигнализирует об окончании передачи.
//------------------------------------------------------------------------------
void ISR_Ssc(void)
{
   unsigned int status = AT91C_BASE_SSC->SSC_SR;
   u16 RX_RECV_CRC, RX_CALC_CRC;
   TDspAnsw *dspAnsw;
   u16 *pCnt;
   u32 rxidx;
   if ((status & AT91C_SSC_TXBUFE)/* буфер DMA пуст */ && 
       (status & AT91C_SSC_TXENA)/* передатчик включен */) 
   {
      // Передача с использованием PDC закончена
      SSC_DisableInterrupts(AT91C_BASE_SSC, AT91C_SSC_ENDTX | AT91C_SSC_TXBUFE);
      AT91C_BASE_SSC->SSC_PTCR = AT91C_PDC_TXTDIS;
      //ждем, когда последний фрейм выскочит из передатчика
      while (!(AT91C_BASE_SSC->SSC_SR & AT91C_SSC_TXEMPTY));
      //раньше устанавливали в 1, а теперь делаем вывод PIN_PB23_INTARM_INPUT входом
      // с нагрузочным резистором, что делает то же самое.
      PIO_Configure(&inINTarm, 1);
      SSC_DisableTransmitter(AT91C_BASE_SSC);
      vSSC.txflags |= SSCTX_COMPLETED;
   }
   if ((status & AT91C_SSC_RXBUFF/* буфер DMA полон */) &&
       (status & AT91C_SSC_RXENA/* приемник включен */))
   {
      // Прием с использованием PDC закончен
      SSC_DisableInterrupts(AT91C_BASE_SSC, AT91C_SSC_RXBUFF);
      AT91C_BASE_SSC->SSC_PTCR = AT91C_PDC_RXTDIS;
      SSC_DisableReceiver(AT91C_BASE_SSC);
      vSSC.cnt.rx++;
      ///////////////////////////////////////////////////
      // Обработка пришедшего пакета. Проверим, что приняли,
      // ищем в буфере начало данных по синхробайтам.
      rxidx = 0;
      while (rxidx<20)
      {
         if (vSSC.pdcbufRX[rxidx++] == 0xA5)
            break;
      }
      while (rxidx<20)
      {
         if (vSSC.pdcbufRX[rxidx++] != 0xA5)
            break;
      }
      if (rxidx>=20)
      {
         //не найден конец синхрозаголовка (0xA5) в пределах первых 10 байт
         vSSC.errcnt.missheader++;
         return;
      }
      //заголовок старта найден
      rxidx--;
      //rxidx указывает на команду cmd
      vSSC.reciv_shift = rxidx - SSC_A5_LEN;
      //проверим CRC, для чего копируем в RX_RECV_CRC принятую CRC
      memcpy(&RX_RECV_CRC, &(vSSC.pdcbufRX[rxidx + (SSC_CMD_LEN+SSC_SERVICE_LEN+SSC_DATA_LEN)]), 2);
      RX_CALC_CRC = CRC_START_VAL;
      AddCRC16(&(vSSC.pdcbufRX[rxidx]), (SSC_CMD_LEN+SSC_SERVICE_LEN+SSC_DATA_LEN), &RX_CALC_CRC);
      if (RX_RECV_CRC != RX_CALC_CRC)
      {
         vSSC.errcnt.crc++;
         return;
      }
      else
      {
         ///////////////////////////////////////////////////
         // Конец обработки пришедшего пакета.
         // Если мы здесь, значит успешно завершен прием.
         memcpy(vSSC.bufRX, vSSC.pdcbufRX+rxidx, (SSC_BLOCK_LEN-(SSC_A5_LEN+SSC_E6_LEN)));
         dspAnsw = (TDspAnsw*)vSSC.bufRX;
         dspstate.rxcmd = dspAnsw->cmd;
         vSSC.cnt.rxOK++;
         ((TRawBlock*)vSSC.bufRX)->CRC16 = CRC_START_VAL;
         AddCRC16(&(vSSC.bufRX[6]), 2+32+512, (u16*)&((TRawBlock*)vSSC.bufRX)->CRC16);
      }
      ///////////////////////////////////////////////////
      // Ответная передача в сторону DSP
      if (vSSC.txflags & SSCTX_PACKET_UPDATED)
      {
         FillSscHeader(vSSC.pdcbufTX);
         memcpy(vSSC.pdcbufTX+SSC_A5_LEN, vSSC.bufTX, SSC_BLOCK_LEN-(SSC_A5_LEN+SSC_E6_LEN));
         FillSscFooter(vSSC.pdcbufTX);
         vSSC.txflags &= ~SSCTX_PACKET_UPDATED;
      }
      if ((0==(vSSC.txflags & SSCTX_HANDLER_UPDATED)) && (0!=pdcTXpacket->cmd))
      {
         SSCtransmit();
      }
   }/* if: буфер DMA полон и приемник включен */
}

В этом примере 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();
}

Обратите внимание, что в случае выбора переменной периферии 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

Поскольку и прием, и передача SPI работают одновременно, то все, что нужно сделать для передачи/приема через DMA, это подготовить 2 буфера (один для приема, другой для передачи), загрузить регистры DMA и взвести одновременно биты разрешения приема и передачи. Все весь остальной код остается без изменений.

//////////////////////////////////////////////////////////////////////////
// Подпрограмма запускает одновременные передачу и прием через DMA.
// Если передача успешно запущена, то будет возвращено 1, иначе (если
// все банки заняты) будет возвращен 0.
unsigned char SPI_RWbuf(AT91S_SPI *spi,
                        void *rxbuf, void *txbuf,
                        unsigned int length)
{
#if !defined(CHIP_SPI_DMA)
   // Проверка доступности первого банка
   if (spi->SPI_TCR == 0)
   {
      spi->SPI_RPR = (unsigned int) rxbuf;
      spi->SPI_TPR = (unsigned int) txbuf;
      spi->SPI_RCR = length;
      spi->SPI_TCR = length;
      spi->SPI_PTCR = (AT91C_PDC_RXTEN | AT91C_PDC_TXTEN);
      return 1;
   }
   // Проверка доступности второго банка
   else if (spi->SPI_TNCR == 0)
   {
      spi->SPI_RNPR = (unsigned int) rxbuf;
      spi->SPI_TNPR = (unsigned int) txbuf;
      spi->SPI_RNCR = length;
      spi->SPI_TNCR = length;
      return 1;
   }
#endif 
   // Нет свободных банков
   return 0;
}

[Ссылки]

1. AT91SAM7X: работа с портом SPI в режиме master.