======Работа с 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}}