Перейти к основному содержимому

Работа с периферией

В составе любого процессора для встраиваемых применений обязательно присутствует набор периферийных портов, необходимых для связи с внешними устройствами. Это может быть порт UART для вывода пользовательской консоли, или SPI для связи с микросхемой-датчиком, или Ethernet для работы в составе локальной сети. Вариантов множество, но все они имеют общую черту: каждое такое устройство не входит в состав процессорного ядра и управляется с помощью набора регистров, как правило, отображаемых в соответствующую область адресного пространства процессора. С точки зрения программиста, любое периферийное устройство — это набор программно-доступных периферийных регистров, отображаемых в общее адресное пространство. Как правило, регистры делятся на три группы:

  • управляющие регистры — позволяют задать необходимый режим работы;
  • статусные регистры — позволяют отслеживать состояние порта;
  • буферные регистры — позволяют принимать и передавать данные через данный порт.

Фактически, любая работа с периферийным портом на программном уровне выглядит так.

Для передачи данных:

  • настройка порта (скорость передачи, длина передаваемого слова и так далее);
  • запись данных в передающий буфер;
  • чтение статусного регистра в ожидании установки флага, сигнализирующего об окончании передачи данных, находящихся в буфере, или о наличии свободного места в буфере. В любом случае, флаг будет обозначать — «можно записывать следующую порцию данных в буферный регистр».

Для приема данных:

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

«После» приемных или передающих буферов устройств находятся устройства, реализующие непосредственно трансляцию данных между буфером и физическим интерфейсом соответствующего порта. Скажем, для порта UART за блоками приемных и передающих FIFO находятся соответственно приемный и передающий сдвиговые регистры, реализующие прием/передачу байта данных в сопровождении необходимого количества стартовых и стоповых битов, а также, при необходимости — бита четности.

Работа с портом UART

Рассмотрим настройку порта UART в любом процессоре архитектуры MIPS. Она осуществляется с помощью следующей функции:

// Инициализация UART0
// варьируется только скорость обмена
// остальные параметры не задаются
// длина слова - 8 бит
// стоп-бит - 1
// четность не контролируется
// управление потоком данных отсутствует
// BaudRate - скорость обмена
// Freq - частота процессора
void UART_conf(int BaudRate, int Frq) {
int dlm;
dlm = (Frq)/(BaudRate*16);
LCR_UART0 = (int)0x80;
SCLR_UART0 = 0;
DLL_UART0 = dlm&0xFF;
DLM_UART0 = (dlm>>8);
LCR_UART0 = 3;
} // UART_conf

Здесь мы производим следующую последовательность действий:

  • устанавливаем в единицу бит LCR[7] порта UART0, тем самым переводя порт в режим установки делителей частоты (в этом режиме недоступны регистры приемного и передающего буфера, а значит, невозможна передача и прием данных);
  • задаем значения делителей частоты в регистрах SCLR, DLL, DLM;
  • переключаем порт обратно в рабочий режим, устанавливая настройки порта в соответствии с описанием регистра LCR.

Передача символа реализуется так:

void UART_sendByte(char Sym) {
// пишем символ в порт
THR_UART0 = Sym;
// ждем, пока он будет отправлен
while ( ( LSR_UART0 & 0x60 ) != 0x60 ) ;
} // UART_sendByte

В первой строке функции символ, подлежащий отправке, записывается в буфер передатчика. Во второй строке мы переходим в цикл, где происходит постоянное чтение статусного регистра LSR и проверка состояний разрядов THRE и TEMT.

Первый из них сообщает состояние буфера отправки. Если он выставлен в единицу — это значит, что данные из него переданы в сдвиговый регистр, осуществляющий отправку, и регистр THR готов к получению новых данных. Второй разряд (TEMT) сообщает о состоянии сдвигового регистра TSR, осуществляющего непосредственно отправку. Если оба этих разряда имеют значение "1" — значит, записанный ранее байт уже передан в линию UART. Именно этого момента мы дожидаемся с помощью цикла while().

Функция, реализующая отправку строки:

void UART_sendStr(char *Str) {
while (*Str != 0)
{
UART_sendByte(*Str++);
}
}

Таким образом, мы ждем флага, говорящего нам о том, что байт отправлен, и отправляем следующий. Полноценный код отсылки строки и приема через UART, можно найти в примерах UART, UART_echo, UART_FIFO_echo в составе среды разработки MCStudio 3M или MCStudio 4. Применительно к отладочному модулю NVCom-02TEM-3U, для работы с UART необходимо установить драйвер для микросхемы USB-UART CP2102.

Установка драйвера для USB-UART CP2102

Оригинальный драйвер можно скачать с сайта разработчика, во вкладке SOFTWARE&TOOLS. В зависимости от операционный системы пользователя можно выбрать CP210x Windows Drivers, CP210x Software package for Linux, CP210x Software package for Mac, includes VCP Drivers.

Для приема данных по COM порту можно использовать свободно распространяемый клиент PuTTy со следующими настройками:

  1. Линия для подключения: COMx
  2. Скорость: 9600 бод
  3. Биты данных: 8
  4. Стоп биты: 1
  5. Четность: нет
  6. Управление потоком: нет
Узнать линию подключения

Чтобы установить на каком порту подключен USB-UART, необходимо зайти в диспетчер устройств и узнать, какое устройство появляется в списке при подключении нашего устройства к USB-порту и исчезает из этого списка при отключении устройства от USB-порта.

Рисунок 1. Просмотр настроек PuTTY.

Последовательность действий при работе с примерами проектов такая:

  • запустить отладку «Debug Configurations… → Debug»;
  • открыть окно клиента PuTTy с выполненными настройками;

При запуске проекта UART в окне клиента появятся строки слова “Hello!” в бесконечном цикле, в проектах UART_fifo_echo и UART_echo в окно клиента выводятся символы, набранные пользователем на клавиатуре.

Рисунок 2. Проверка корректной работы PuTTY.

Работа с портом MFBSP в режиме линкового интерфейса LPORT

MFBSP в режиме линкового порта (LPORT) в процессоре 1892ВМ10Я может работать только в одном направлении — передатчик либо приемник, в зависимости от направления обмена данными. Рассмотрим алгоритм обмена между периферийными портами, реализованный в примере MFBSP_LPORT (он находится в составе среды разработки MCStudio 3M/4), иллюстрирующий простой обмен между LPORT2 и LPORT3:

  1. Объявляются два массива. Один из них заполняются нулями (туда будут приниматься данные), второй заполняется инкрементными значениями (данные из этого массива будут передаваться).
  2. Настройка портов на обмен.
  3. Отправка символа в буфер передачи.
  4. Ожидание приёма символа.
  5. Чтение символа в буфер приема.
  6. Проверка корректности принятых данных, по результатам формируется значение флага корректности данных.
  7. Чтобы увидеть результат работы программы, нужно в среде MCStudio 4 поставить точку останова после окончания проверки, как показано на рисунке ниже, создать конфигурацию отладки и запустить программу на исполнение. После останова программы во вкладке «Variables» найти значение флага корректности приемного массива и убедиться, что он равен единице, как показано на рисунке ниже.
Рисунок 3. Просмотр результата работы программы.

Рассмотрим описанные выше пункты в виде кода на Си с комментариями. Настройка портов на обмен осуществляется инициализацией регистров управления и состояния и заданием частоты обмена:

// LPORT2 передает:
CSR_MFBSP2 = ( (LCLK_RATE&0x1E)<<10 ) | // LCLK_RATE[4:1]
( (LCLK_RATE&1) << 2 ) | // LCLK_RATE[0]
( 1<<6) | // LDW = 1 (длина слова - 8 бит)
( 1<<1 ) | // LTRAN = 1 (передатчик)
1; // LEN = 1;
// LPORT3 принимает:
CSR_MFBSP3 = ( (LCLK_RATE&0x1E)<<10 ) | // LCLK_RATE[4:1]
( (LCLK_RATE&1) << 2 ) | // LCLK_RATE[0]
( 1<<6) | // LDW = 1 (длина слова - 8 бит)
1; // LEN = 1;
LCLK_RATE = (FREQ/(2*LPORT_FRQ)) - 1;

Приёмник осуществляет синхронный приём параллельно-последовательного кода с внешних выводов схемы и запись принятых данных в буфер LPORT. Передатчик осуществляет чтение данных из буфера LPORT и синхронную выдачу их параллельно последовательным кодом на внешние выводы схемы. Запись передаваемых данных в буфер LPORT осуществляется при записи элемента массива OutputArray[i] по адресу псевдорегистра TX_MFBSP. Бит [3] регистра управления и состояния CSR порта-приёмника MFBSP в режиме LPORT отображает состояние буфера приема, именно изменения его состояния с нуля на единицу мы ожидаем в цикле while кода, приведенного ниже. Чтение принятого элемента массива InputArray[i] из буфера LPORT осуществляется при чтении по адресу псевдорегистра RX_MFBSP:

for (i=0;i<ARRAY_LEN;i++)
{
TX_MFBSP2=OutputArray[i]; //передача очередного значения в буфер
while (!(CSR_MFBSP3 & 1<<4)); //ожидание получения новых данных
InputArray[i]=RX_MFBSP3; //чтение полученного значения из буфера
}

LPORT при обмене данными использует выводы LCLK, LACK, LDAT[7:0]. Передача данных по большинству периферийных интерфейсов может происходить по каналам DMA. Обмену по DMA посвящена глава 4 настоящего курса.

Принципы обмена по каналам DMA

Каналы DMA используются для обмена данными с внешними устройствами или между областями памяти без участия процессорных ядер. Это позволяет достичь двух целей:

  • разгрузить процессор: во время обмена он может выполнять другие действия;
  • увеличить скорость обмена, так как цикл вычитывания данных из DMA и сохранение данных в память - это минимум 5 инструкций процессора, что значительно дольше, нежели прямая передача данных с помощью DMA.

В случае процессоров серии "Мультикор", обмен по DMA может происходить 32- или 64-разрядными словами. В общем случае, размер слова для обмена по DMA зависит от конкретного типа канала:

  • DMA периферийных портов (кроме Ethernet) - 64 разряда;
  • DMA типа "память-память" - 32/64 разряда (настраивается);
  • DMA Ethernet - 8 разрядов.

Разрядность каналов DMA для конкретного процессора необходимо уточнять в руководстве пользователя. Данный ознакомительный курс иллюстрирует обмены данными на базе процессора 1892ВМ10Я. Соответственно, в примерах и алгоритмах, представленных ниже, настройки периферийных и системных регистров взяты из руководства пользователя на микросхему 1892ВМ10Я.

Алгоритм обмена по DMA периферийных портов

Рассмотрим обмен по DMA через порт MFBSP в режиме LPORT (обмен по DMA возможен также и в режиме I2S, и в режиме SPI) для процессора 1892ВМ10Я. Для каждого порта MFBSP предусмотрено два независимых канала DMA — один на приём и один на передачу. Обмен происходит пачками. Пачка — это количество слов, переданное за одно предоставление прямого доступа. Размер пачки определяется полем WN регистра CSR соответствующего канала DMA. В режиме DMA обмены возможны только 64-разрядными словами. Поэтому следует проектировать протокол обмена таким образом, чтобы размер передаваемых пакетов был выровнен по границе 64-разрядного слова. Это позволит избежать ситуации, когда принято нечетное количество 32-разрядных слов, и последнее слово остается в FIFO MFBSP до прихода следующих. Если избежать такой ситуации нельзя, в настройках каналов DMA необходимо задавать длину передачи на одно 64-разрядное слово меньше, и по окончанию приема данных дополнительно программно вычитывать оставшееся 32-разрядное слово через псевдорегистр RX_MFBSP.

Предусмотрена работа с обработкой прерываний от каналов DMA: прерывание может формироваться по завершению передачи или приема всего блока данных и, применительно к порту MFBSP, при его возникновении устанавливается в единицу в регистре QSTR2. Предварительно прерывание необходимо разрешить, установив соответствующие биты в регистре MASKR2.

Для корректного базового обмена между двумя периферийными портами необходимо произвести следующие действия с портами и каналами DMA:

  • сформировать массивы для приема и передачи;
  • как и при обмене без DMA, необходимо инициализировать порты;
  • настроить каналы DMA на прием и передачу;
  • ожидать окончания приема данных;
  • проверить содержимое входного массива (опционально);
  • обнулить регистры управления и состояния CSR.

Раскроем эти пункты подробнее.

Формирование массивов для приема и передачи подразумевает под собой создание двух массивов одинаковой длины, выравнивание их по границе 64-разрядного слова (для примера кода см. предыдущую главу), заполнение выходного массива OutputArray данными для передачи, заполнение входного массива InputArray нулями.

Инициализация регистров управления и состояния портов, то есть настройка одного порта на передачу, другого на приём, происходит аналогично их инициализации при обмене без DMA. В случае нашего примера, обмен производится между портами LPORT2 и LPORT3:

void LPORT_Init() {
unsigned int LCLK_RATE;
CSR = 1<<1; // TR_CRAM бит, вектора прерывания размещаются во внутренней памяти //CRAM //(ад
реса типа 0xB8000000)
LCLK_RATE = (FREQ/(2*LPORT_FRQ)) - 1;
// LPORT2 передает
CSR_MFBSP2 = ( (LCLK_RATE&0x1E)<<10 ) | // LCLK_RATE[4:1]
( (LCLK_RATE&1) << 2 ) | // LCLK_RATE[0]
( 1<<6) | // LDW = 1 (длина слова - 8 бит)
( 1<<1 ) | // LTRAN = 1 (передатчик)
1; // LEN = 1;
// LPORT3 принимает
CSR_MFBSP3 = ( (LCLK_RATE&0x1E)<<10 ) | // LCLK_RATE[4:1]
( (LCLK_RATE&1) << 2 ) | // LCLK_RATE[0]
( 1<<6) | // LDW = 1(длина слова - 8 бит)
1; // LEN = 1;
}

Далее необходимо настроить каналы DMA на прием и передачу (регистры управления и состояния CSR приемного и передающего каналов) и определить расположение выходного и входного массивов (регистры IR приемного и передающего каналов).

// Настраиваем DMA LPORT3 на прием
IR_MFBSP_RX_CH3 = ((unsigned int) &InputArray[0]) - 0xA0000000; // записываем физический адрес в
// регистр индекса

32-разрядный индексный регистр IR содержит физический начальный адрес памяти с точностью до байта. После каждой передачи данных к индексу IR прибавляется смещение, равное количеству переданных байтов. DMA, в отличие от процессорного ядра, работает с физическими адресами, поэтому только их, но не виртуальные адреса нужно задавать при определении регистра IR.

CSR_MFBSP_RX_CH3 =  ((ARRAY_LEN/2) << 16) | // WCX = LEN/2
(15 << 2) | // WN = 15 (размер пачки - 16 слов)
1; // RUN = 1
// Настраиваем DMA LPORT2 на передачу и стартуем обмен
IR_MFBSP_TX_CH2 = ((unsigned int) &OutputArray[0]) - 0xA0000000; // записываем физический адрес
// в регистр индекса
CSR_MFBSP_TX_CH2 = ((ARRAY_LEN/2) << 16) | // WCX = LEN/2
(15 << 2) | // WN = 15 (размер пачки - 16 слов)
1; // RUN = 1

В поле WCX должна содержаться информация о числе 64-разрядных слов данных, которые должен передать канал DMA (блок данных). Количество передаваемых слов: WCX + 1. Содержимое этого поля уменьшается на 1 после передачи каналом DMA очередного слова данных. В поле WN содержится информация о числе слов данных, передаваемых в одной пачке. Размер пачки равен WN + 1 слов.

В поле RUN мы записываем единицу (переводим канал в состояние обмена), то есть, канал начнет работать сразу после записи.

IM - маска разрешения установки признака END, используется только в процедуре инициализации. В поле IM мы пишем ноль, поскольку не используем цепочки передач, нам безразлично состояние этого бита. Он разрешает прерывание по окончанию передачи одного звена цепочки. В остальные поля регистра CSR записываем нули.

Окончание обмена, то есть момент, когда приемный канал DMA закончит работу, можно проконтролировать, отслеживая признак завершения передачи данных — бит DONE регистра CSR DMA (установится в 1). Состояние этого бита читаем через псевдорегистр RUN, чтобы предотвратить сброс CSR после его чтения:

while ( (RUN_MFBSP_RX_CH3&0x8000)==0 ) ;

В заключительных пунктах алгоритма проверим, совпадают ли входной и выходной массивы и обнулим регистры CSR. В составе MCStudio 3M/4 существуют следующие примеры обмена по DMA, в которых присутствует описанный выше алгоритм: MFBSP_LPORT_DMA, MFBSP_I2S_DMA, MFBSP_SPI_DMA.

Алгоритм обмена по DMA периферийных портов с обработкой прерываний

Архитектура процессоров серии “Мультикор” предусматривает возникновение и обработку прерываний при окончании пересылки данных по каналам DMA. Для корректного обмена по DMA с обработкой прерываний необходимо произвести следующие действия с портами и каналами DMA:

  • установить значения регистра CP0. Status, разрешая прерывания;
  • установить соответствующие биты масок системного регистра MASKR2;
  • настроить режим размещения векторов прерываний TR_CRAM во внутренней памяти;
  • инициализировать порты;
  • настроить каналы DMA на прием и передачу;
  • ожидать окончания обработки прерывания;
  • проверить содержимое входного массива (опционально).
  • обнулить регистры управления и состояния CSR.

Рассмотрим эти пункты подробнее. В регистр CP0. Status необходимо записать значение “0x1001”, разрешая, таким образом, прерывания вообще (бит CP0.Status[0]) и, конкретно, прерывания от портов MFBSP (бит CP0.Status[11]). Функция, реализующая запись в регистр CP0.Status:

// установка значения регистра CP0 Status
void SetCP0_Status(unsigned int value) {
asm volatile ("mtc0 %0, $12" ::"r"(value));
}

Непосредственно, запись:

SetCP0_Status(0x1001);

Записывая единицу в соответствующий разряд регистра MASKR2, мы разрешаем прерывание от конкретного канала DMA:

// установка регистра MASKR2 на прерывание
void MASKR2_set() {
// настраиваем регистр маски прерываний (MASKR2)
// выставляем разряд MASKR2[29], разрешающий прерывание от DMA канала
SYS_REG.MASKR2.data |= (1<<29);
}

Адрес обработчика прерываний зависит от состояния разрядов BEV, EXL регистра CP0.Status, IV регистра CP0.Cause, TR_CRAM регистра CSR. Прерываниям посвящена отдельная глава, посему, не вдаваясь здесь в подробные описания, записываем единицу в бит TR_CRAM — разряд [1] системного регистра CSR. Инициализация регистров управления и состояния портов LPORT2 и LPORT3 происходит аналогично их инициализации при базовом обмене по каналу DMA, описанном в предыдущей главе:

// инициализация регистров управления и состояния портов LPORT2 и LPORT3
void LPORT_Init() {
unsigned int LCLK_RATE;
SYS_REG.CSR.data = 1 << 1; // TR_CRAM бит, вектора прерывания размещаются во внутренней
// памяти CRAM (адреса типа 0xB8000000)
LCLK_RATE = (FREQ/(2*LPORT_FRQ)) - 1;
// LPORT2 передает
MFBSP2.CSR.bits.LCLK_RATE_1_4 = 2;
MFBSP2.CSR.bits.LCLK_RATE_0 = 1;
MFBSP2.CSR.bits.LDW = 1; // длина слова - 8 бит
MFBSP2.CSR.bits.LTRAN = 1;// передатчик
MFBSP2.CSR.bits.LEN = 1;
// LPORT3 принимает
MFBSP3.CSR.bits.LCLK_RATE_1_4 = 2;
MFBSP3.CSR.bits.LCLK_RATE_0 = 1;
MFBSP3.CSR.bits.LDW = 1; // длина слова - 8 бит
MFBSP3.CSR.bits.LEN = 1;
}

Далее необходимо настроить каналы DMA на прием и передачу (регистры управления и состояния CSR приемного и передающего каналов) и определить расположение выходного и входного массивов (регистры IR приемного и передающего каналов).

// Настраиваем DMA LPORT3 на прием
DMA_MFBSP_RX_CH3.IR.data = ((unsigned int) &InputArray[0]) - 0xA0000000; // записываем физический
// aдрес в регистр индекса
DMA_MFBSP_RX_CH3.CSR.bits.WCX = ARRAY_LEN/2; // WCX = ARRAY_LEN/2
DMA_MFBSP_RX_CH3.CSR.bits.WN = 15; // WN = 15 (размер пачки - 16 слов)
DMA_MFBSP_RX_CH3.CSR.bits.RUN = 1; // RUN = 1
// Настраиваем DMA LPORT2 на передачу
DMA_MFBSP_TX_CH2.IR.data = ((unsigned int) &OutputArray[0]) - 0xA0000000; // записываем физический
// адрес в регистр индекса
DMA_MFBSP_TX_CH2.CSR.bits.WCX = ARRAY_LEN/2; // WCX = ARRAY_LEN/2
DMA_MFBSP_TX_CH2.CSR.bits.WN = 15; // WN = 15 (размер пачки - 16 слов)
DMA_MFBSP_TX_CH2.CSR.bits.RUN = 1; // RUN = 1

Далее ожидаем окончания обработки прерывания. Обработкой возникшего прерывания по окончании обмена занимается специальная программа — обработчик. Она сохраняет регистры 1-28,30,31 в стеке, когда приходит прерывание, и процессор его обрабатывает, затем мы (программист) снимаем прерывание, сбрасывая биты DONE и END регистров управления и состояния DMA портов, затем обработчик возвращает программу по адресу дальнейшего исполнения, восстанавливая регистры 1-28,30,31 в стеке и восстанавливая указатель стека.

В составе MCStudio 3M/4 существует следующий набор примеров обмена по DMA с обработкой прерывания: MFBSP_LPORT_DMA_int, MFBSP_I2S_DMA_int, MFBSP_SPI_DMA_int.

Алгоритм обмена по DMA типа “память-память”

В процессорах серии "Мультикор" предусмотрена передача данных по каналу DMA из одной области памяти в другую. Имеются 4 канала MEM_CH, которые обеспечивают обмен данными между двумя областями любых блоков памяти (внутренних или внешних).

Такой тип передачи отличается от рассмотренных выше DMA-передач тем, что не нужно инициализировать порты, так как данные будут передаваться внутри адресного пространства процессора. Отличие также будет в настройке канала DMA.

В регистрах индексов IR1 и IR0 определим начальные физические адреса источника и приемника данных:

DMA_MEM_CH0.IR0.data = ((unsigned int) &InputArray[0]) - 0xA0000000; // физический адрес приемника
DMA_MEM_CH0.IR1.data = ((unsigned int) &OutputArray[0]) - 0xA0000000; //физический адрес источника

Адрес источника и адрес приемной области памяти меняются местами в зависимости от бита DIR регистра CSR канала DMA, в котором мы работаем. В регистре OR задаем смещение в адресном пространстве, через которое DMA канал берет очередное слово из памяти для передачи и кладет в область памяти, являющуюся источником:

OR_MEM_CH0 = 0x00010001; // Старшая часть регистра задает смещение регистра IR1, а младшая часть
// - смещение регистра IR0 после передачи каждого слова

В регистре управления и состояния определяем число 64-разрядных слов данных, которые должен передать канал DMA:

CSR_MEM_CH0 = ((LEN)<<16) | // WCX
(1<<1) | // DIR =1
1; // RUN = 1

Разряд DIR регистра CSR_MEM_CH0 определяет направление обмена данными, в данном случае он равен единице и данные перемещаются из области памяти, указанной регистром IR1 в область памяти, указываемую регистром IR0. Определения значений других регистров для такой передачи не требуется. Далее ждем флага окончания DMA-передачи:

while (DMA_MEM_CH0.RUN.data &(1<<15) != 0) ;

Сравниваем полученный массив с отправленным, обнуляем регистр управления и состояния, индексные регистры. Код программы для описанного обмена содержится в примере MEM_DMA в составе MCStudio 3M/4.

Алгоритм обмена по DMA цепочкой передач

Каналы DMA MEM_CH могут выполнять процедуру самоинициализации - выполнять передачи DMA цепочкой. Особенность данного режима в том, что в этом случае обмены по DMA выполняются друг за другом без участия процессора, что позволяет осуществлять быструю передачу больших объемов данных.

Как пример можно привести передачу данных на дисплей в порте видеовыхода (VPOUT). Максимальный объем передаваемых данных за один DMA-обмен - 65536 64-разрядных слов, то есть, 512 Кбайт. В то же время, один кадр в разрешении 640х480 занимает в памяти более 1 Мбайт. Поэтому для вывода видеоизображений на дисплей необходимо использовать цепочки передач DMA. Для реализации цепочки передач необходимо формировать массив блоков параметров, содержащих значения регистров IR1, IR0, Y, OR, CSR, CP канала DMA MEM_CH. Блок параметров - это набор значений регистров, расположенных так, как показано на схеме ниже.

63______________________________0
{IR132, IR032 };
{{WCY16,ORY16},{ OR116,OR016 }};
{ CSR32, CP32 }.

Необходимо сформировать такое количество блоков параметров, сколько передач будет в цепочке. Эти параметры при самоинициализации аппаратно загружаются в 64-разрядном формате в соответствующие регистры канала DMA.

Для выполнения самоинициализации в каналах имеется 32-разрядный регистр CP, в котором хранится начальный адрес блока параметров очередного DMA обмена, то есть, физический адрес первого элемента первого вызываемого массива. Младший разряд регистра CP (бит CHEN) используется для старта режима самоинициализации.

Алгоритм обмена будет таким:

  • инициализируем массив параметров (регистров IR1, IR0, Y, OR, CSR, CP);
  • инициализируем массивы на передачу и прием, также как в обменах DMA, описанных в предыдущих главах;
  • указываем адрес первого блока обмена — регистра CP;
  • ждем окончания DMA-передачи — прерывание от канала;
  • проверяем значение входного массива;
  • обнуляем регистры CSR, IR0, IR1.

Рассмотрим новые пункты алгоритма подробнее, оперируя командами Си: Инициализируем массив параметров, создавая структуру с регистрами DMA в качестве ее полей:

for (j=0;j<N;j++) {
// физический адрес "источника" определяется регистром индекса
settings_array[j].IR1_reg = ((unsigned int) &OutputArray[j*LEN/N]) - 0xA0000000;
// физический адрес "приемника" также определяется регистром индекса
settings_array[j].IR0_reg = ((unsigned int) &InputArray[j*LEN/N]) - 0xA0000000;
// двумерной адресации нет, поэтому регистр смещения по направлению Y равен 0
settings_array[j].Y_reg = 0;
// смещение при чтении из "источника" - 1 слово
settings_array[j].OR_reg = 0x00010001;
if (j==(N-1))
// DATA_LEN | DIR (1->0) | RUN
settings_array[j].CSR_reg = (LEN/N<<16)|(1<<1)|1;
else
// DATA_LEN | CHEN | DIR (1->0) | RUN,
// если это последний блок параметров, бит CHEN не выставляется
// CHEN – бит разрешения выполнения процедуры самоинициализации.
settings_array[j].CSR_reg = (LEN/N<<16)|(1<<12)|(1<<1)|1;
// адрес следующего блока параметров
if (j==(N-1))
settings_array[j].CP_reg = 0;
else
settings_array[j].CP_reg = ((unsigned int)&settings_array[j+1])- 0xA0000000;
}

Далее задаем адрес первого блока параметров DMA передачи через регистр CP. Нулевой разряд CP определяем в единицу, это будет стартом самоинициализации:

CP_MEM_CH0 =  ((unsigned int)&settings_array[0] - 0xA0000000) | 1;

Когда в бит CP[0] записывается единица, соответствующий канал DMA производит чтение блока параметров по адресу CP[31:1], 0. Прочитанные значения заносятся в регистры данного канала DMA. Если в разряде CSR[0] окажется ноль - передача не запустится, канал DMA будет ждать записи единицы в этот разряд. После старта передачи обмен ничем не отличается от обмена без цепочек. По окончании DMA-обмена всегда (даже если обмен запускался обычным путем, как единичный обмен) проверяется состояние бита CHEN в регистре CSR. Если он выставлен в единицу - контроллер DMA считывает блок параметров по адресу, содержащемуся в регистре CP, и заносит эти значения в регистр. Соответственно, последний блок параметров (если цепочка не зацикленная) должен содержать ноль в разряде CHEN регистра CSR.

Флагом окончания DMA передачи по используемому нами в примере каналу DMA_MEM_CH0, является нулевой бит регистра QSTR1 (см. главу 2.4 руководства пользователя на микросхему 1892ВМ10Я), его мы и отслеживаем после старта процедуры самоинициализации:

while (!(QSTR1&1));

Дальше проверим корректность принятого массива и обнулим регистр управления и состояния и регистры индексов, как это было сделано в предыдущих примерах. Код на языке Си для описанного обмена содержится в примере MEM_DMA_chain. Цепочки передач могут использоваться как с каналами DMA типа "память-память", так и с каналами DMA портов. Процедура инициализации DMA портов MFBSP, EMAC, VPIN, VPOUT аналогична каналам MEM_CH. Параметры для самоинициализации размещаются в памяти в двух последовательных 64-разрядных словах, следующим образом (в порядке возрастания адресов):

СмещениеПараметр
0x00IR
0x04
0x08CSR
0x0CCP

Код программы для описанного обмена содержится в примере MEM_DMA_chain_LPORT в составе MCStudio 3M/4.