======Работа с DMA в процессорах серии 1967ВНxx======
Контроллер прямого доступа в память, он же DMA (Direct Memory Access) – механизм передачи данных без исполнения команд в ядре процессора. Это определение ни раз уже использовалось во всех источниках, в том числе и в соседней [[prog:dma:dma_intro|статье,]] о работе данного механизма в процессорах на базе ядра Cortex. В этом материале будет представлены основные понятия о DMA в ЦОС-процессорах серии 1967ВНхх, поскольку блок имеет существенные различия по сравнению с тем, что используется в микроконтроллерах на Cortex.
=====Архитектура DMA=====
Итак, в основном блок DMA одинаков для процессоров серии 1967, но все же есть свои особенности как для 028 так и для 044 процессора. В данном материале постараемся уточнить такие моменты.
Передача DMA может быть осуществлена между:
-внутренняя память процессора <-> внутренняя память процессора;
-внутренняя память процессора <-> внешняя память;
-внутренняя память процессора <-> внешняя периферия;
-внешняя память процессора <-> внешняя периферия;
-внешняя память процессора <-> порт связи;
-порт связи <-> порт связи;
-внутренняя память процессора <-> внутренняя память другого процессора; (только для 1967ВН028)
-внутренняя память процессора <-> хост-процессор; (только для 1967ВН028)
-ведущий на кластерной шине <-> внутренняя память через AutoDMA; (только для 1967ВН028)
====Каналы====
Мы привыкли к тому, что для DMA необходимо указать адрес источника, адрес приёмника, добавить управляющее слово и можно запускать цикл (так работает DMA в контроллерах на Cortex). В ЦОС-процессорах DMA реализован иначе. Здесь управляющая структура DMA отдельно задается на приём и на передачу. Управляющую структуру в терминах документации принято обозначать как TCB. Но к этому вернёмся ниже по тексту.
В ЦОС серии 1967ВН всего есть 14 каналов. Приведём краткую таблицу для каждого процессора (также см. спецификации). Первые 4 канала (0-3) являются универсальными и полностью программируемыми, например, используются для пересылки память-память. Инициирование транзакции скорее всего подразумевает источник запроса, то есть в случае с данными каналами мы можем выставлять запрос программно.
Каналы 4-7 предназначены для устройств передачи. То есть данные из внутренней/внешний памяти отправляются в периферийный передатчик, соответственно (вероятно) условие возникновение запроса к dma - пустота передатчика.
Каналы 8-11 предназначены для устройств приёма. Все тоже самое, только в обратном направлении, предполагается, что
условие возникновение запроса - наличие данных в приёмнике.
**1967ВН028**
^ Номер канала ^ Источник данных ^ Приёмник ^ Инициирование транзакции ^
| 0-3 | Программируется TCB | Программируется TCB | Программируется. Может использовать входы nDMAR0-3.|
| 4-7 | Программируется TCB | Передатчик портов связи каналы 0-3 | По запросу от периферии.|
| 8-11 | Приёмник портов связи каналы 0-3 | Программируется TCB | По запросу от периферии.|
| 12 | Запись данных внешним ведущим устройством | Программируется TCB | Всегда, при наличии данных во внутреннем буфере.|
| 13 | Запись данных внешним ведущим устройством | Программируется TCB | Всегда, при наличии данных во внутреннем буфере.|
**1967ВН044**
^ Номер канала ^ Источник данных ^ Приёмник ^ Инициирование транзакции ^
| 0-3 | Программируется TCB | Программируется TCB | Программируется. Может использовать запрос устройства.|
| 4-7 | Программируется TCB | Режим 1/2. Периферийное устройство/Адрес устройства задается в специальном регистре DCA | По запросу от периферии.|
| 8-11 | Режим 1/2. Периферийное устройство/Адрес устройства задается в специальном регистре DCA | Программируется TCB | По запросу от периферии.|
| 12 | Запись данных цифровым коррелятором в регистр канала | Программируется TCB | Всегда, при наличии данных во внутреннем буфере.|
| 13 | Запись данных внешним ведущим устройством | Программируется TCB | Всегда, при наличии данных во внутреннем буфере.|
====Запросы====
Каналы 0-3 предназначены для использования программных запросов и для запроса от внешних устройств с выводов nDMAR0-3 (для 044 можно использовать внешние выводы для всех каналов). Каналы же 4-11 предназначены для периферии. Если 1967ВН028 всё просто: все эти каналы предназначены для работы с передатчиками/приёмника link-порта, то в случае же с 1967ВН044 периферия представлена не только link-портами. Поэтому для того, чтобы указать DMA от какой периферии ожидать запрос, есть специальный регистр DMACFG, состоящий из двух 32 разрядных регистров DMACFGL и DMACFGH. Об этом подробнее описано в данной [[prog:spec:dma_req |статье]].
=====Управляющая структура TCB=====
Как уже было замечено выше блок управления передачей TCB представляет из себя квадрослово (128 бит), то есть 4 регистра по 32-бита. Именно в нём представлена информация для конфигурации канала DMA.
{{:dsp:tests:dma:dma_tcb.png}}
Квадролово TCB содержит 4 регистра: DI, DX, DY, DP.
====Регистр DI====
В регистр DI заносится адрес который может указывать на внутреннюю и внешнюю память. В случае если DMA программируется на передачу, то задается адрес, откуда взять данные (адрес источника), соответственно, если DMA настраивается на приём, то указывается адрес, куда положить их (адрес приёмника).
^ биты ^ поле ^ Описание ^
| 31..30 | DI | Содержит начальных адрес блока данных |
====Регистр DX====
^ биты ^ поле ^ Описание ^
| 31..16 | DXC | Количество слов блока данных, которое необходимо передать |
| 15..0 | DXM | Значение модификатора, используемое для изменения адреса после каждой транзакции |
====Регистр DY====
Данный регистр имеет описание аналогичное регистру DX и используется вместе с ним, когда режим работы двумерного DMA разрешен. Если двумерный режим выключить, то необходимо оставить данное поле равным нулю.
====Регистр DP====
**Основной регистр конфигурации управляющего слова.**
Здесь очень важно соблюдать определённые правила и ограничения при его программировании. Они приведены в документации. Когда канал завершает работу, поле TY переходит в состояние "канал выключен", то есть старшие биты сбрасываются.
^ биты ^ поле ^ Значения ^ Описание ^
| 31..29 | TY | 000 - канал выключен \\ 001 - линк порт \\ 010 - внутренняя память (16/16) \\ 011 - внутренняя память (22/10) \\ 100 - внешняя память (16/16) \\ 101 - внешнее устройство Flyby (только для 028, для 044 - резерв) \\ 110 - загрузочное EPROM \\ 111 - внешняя память | Тип обмена, выбор источника или приёмника |
| 28 | PR | 1 - высокий \\ 0 - обычный | Приоритет циклов обмена |
| 27 | 2D | 1 - двумерная посылка \\ 0 - одномерная посылка | Включение режима 2-х мерной посылки |
| 26..25 | LEN | 00 - резерв \\ 01- слово 32 бита \\ 10 - длинное слово 64 бита \\ 11 - квадрослово 128 бит | Длина передаваемых данных (операнда) в одном цикле |
| 24 | INT | 1 - разрешено \\ 0- запрещено | Разрешение запроса прерывания после окончания работы канала |
| 23 | DRQ | 1 - разрешено \\ 0- запрещено | Разрешение анализа от внешнего запроса/запроса от периферии |
| 22 | CHEN | 1 - разрешено \\ 0- запрещено | Значение модификатора, используемое для изменения адреса после каждой транзакции |
| 21..19 | CHTG | 000 – канал 8 \\ 001 – канал 9 \\ 010 – канал 10 \\ 011 – канал 11 \\ 100 – канал 4 \\ 101 – канал 5 \\ 110 – канал 6 \\ 111 – канал 7 | Канал следующей цепочки. Поле имеет значение только для каналов 4-11 |
| 18..0 | CHPT | | Указатель цепочки DMA. Поле включает в себя разряды 20-2 адреса внутренней памяти, где находится значение следующего регистра TCB |
=====Пример=====
Рассмотрим следующий пример работы с DMA: просто скопируем массив данных из одной области памяти в другую. То есть нам необходимо работать с полностью программируемыми каналами 0-3. Для этого откроем описание библиотеки HAL и воспользуемся стандартной функцией для копирования данных с помощью DMA HAL_DMA_MemCopy32 (). В принципе, пример можно запускать на обоих процессорах - 028/044, только необходимо подключить соответствующие библиотеки.
** Запуск на 1967ВН044.**
Ниже приведу листинг кода.
#include
#define N 1024
int data_tx32[N];
int data_rx32[N];
int main(void)
{ int i;
int errFlag;
for (i=0; i < N; i++)
data_tx32[i] =i ;
errFlag = HAL_DMA_MemCopy32 (2, &data_tx32, &data_rx32, N);
return 0;
}
**Комментарии.** В качестве параметров необходимо передать номер используемого канала DMA, указатель на массив передаваемых данных (его мы предварительно инициализируем последовательностью чисел от одного до N), указатель на массив, куда DMA сложит данные и количество самих данных. Функция возвращает флаг ошибки. Очень удобно, когда все делает одна функция, но для понимания работы DMA рассмотрим, что именно она делает.
Итак, в первую очередь происходит объявление структуры управляющих данных DMA. И здесь кроется очень важный момент, который необходимо выделить.
Управляющая структура DMA должна быть выровнена на квадрослово.
uint32_t __attribute((aligned(4))) tcb_dcs[4];
uint32_t __attribute((aligned(4))) tcb_dcd[4];
Теперь посмотрим на инициализацию самой структуры. Стоит отметить тот момент, поскольку мы пользуемся 2 каналом DMA, здесь мы будем инициализировать 2 управляющие структуры TCB: источника (куда мы положим данные для передачи) и назначения (куда DMA сложит отправленные нами данные). В терминах спецификации квадрослово источника именуется DCS, а квадрослово приёмника или назначения - DCD.
Рассмотрим DCS:
tcb_dcs[ 3 ] = 0;
HAL_DMA_InitMemType( ( uint32_t )src, tcb_dcs[ 3 ] );
tcb_dcs[ 0 ] = ( uint32_t ) src;
tcb_dcs[ 1 ] = ( size << 16 ) | 1;
tcb_dcs[ 2 ] = 0;
tcb_dcs[ 3 ] |= TCB_NORMAL;
Итак, для начала четвертый регистр DP сбрасывается. Затем с помощью макроса HAL_DMA_InitMemType проверяется, по какому адресу находится массив входных данных и в соответствии с ним задается поле TY регистра DP. В данном случае используется внутренняя память и поле TY=010;
#define TCB_INTMEM (0x40000000)
Затем в регистр DI заносится адрес массива данных для отправки. В регистр DX, в младшее полуслово заносим значение модификатора адреса DMA, используемое для изменения адреса после каждой транзакции, а в старшее полуслово заносим общее количество посылок, то есть размерность нашего массива для передачи.
Регистр DY инициализируется нулём, поскольку двумерный DMA мы не используем. И в конце в регистр DP, к ранее заполненному полю TY, добавляем поле LEN =010, то есть указываем длину передаваемых данных операнда в одном цикле - 32 бита.
#define TCB_NORMAL (0x02000000)
Собственно, инициализация управляющей структуры назначения DMA DCD ничем не отличается, разве что в регистр DI заносится указатель на массив принимаемых данных.
tcb_dcd[ 3 ] = 0;
HAL_DMA_InitMemType( ( uint32_t )dst, tcb_dcd[ 3 ] );
tcb_dcd[ 0 ] = ( uint32_t ) dst;
tcb_dcd[ 1 ] = ( size << 16 ) | 1;
tcb_dcd[ 2 ] = 0;
tcb_dcd[ 3 ] |= TCB_NORMAL;
Теперь сконфигурированные структуры с помощью функций HAL_DMA_WriteDCS () и HAL_DMA_WriteDCD () заносятся в память по соответствующем адресам DMA второго канала.
HAL_DMA_WriteDCD( ch_number, &tcb_dcd );
HAL_DMA_WriteDCS( ch_number, &tcb_dcs );
Можно запустить программу и поставить точку останова на функции HAL_DMA_WriteDCS( ch_number, &tcb_dcs ), то есть до записи структуры на отправку. И посмотреть, как прошла конфигурация принимающего блока DCD.
{{:dsp:tests:dma:dma_dcd2.png}}
Красным выделен регистр - DI.
В нём, как мы видим, лежит указатель на массив, куда мы сложим принятые данные.
{{:dsp:tests:dma:data_rx.png}}
Зелёным - DX;
Синим - DY;
Фиолетовым - DP;
Посмотреть значение, записанное в управляющую структуру передатчика не получится, поскольку как только конфигурационное квадрослово будет записано по адресу источника DSC2, регистры DCS и DCD тут же меняют свое значение.
Получается, как только мы помещаем квадрослово источника на передачу DMA в соответствующий регистр DCS программируемых каналов 0-3, это и есть выставление программного запроса к DMA.
{{:dsp:tests:dma:dcs_dcd.png}}
Это говорит о том, что используемый второй канал DMA отработал, и старшее поле TY регистра DP приняло значение 000, что соответствует выключенному каналу. И теперь можно увидеть, как DMA перенес наши данные из массива data_tx32 в массив data_rx32.
{{:dsp:tests:dma:mas_txrx.png}}