dma что это такое

DMA для новичков или то, что вам нужно знать

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

DMA, что это? О чем вы говорите?

DMA, или Direct Memory Access – технология прямого доступа к памяти, минуя центральный процессор. В эпоху 486-ых и первых Pentium во всю царствовала шина ISA, а также метод обмена данными между устройствами – PIO (Programmed Input/Output).

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

Кстати говоря, DMA используется не только для обмена данными между устройством и ОЗУ, но также между устройствами в системе, возможен DMA трансфер между двумя участками ОЗУ (хотя данный маневр не применим к x86 архитектуре). Также в своем процессоре Cell, IBM использует DMA как основной механизм обмена данными между синергетическими процессорными элементами (SPE) и центральным процессорным элементом (PPE). Также каждый SPE и PPE может обмениватся данными через DMA с оперативной памятью. Данный прием – на самом деле большое преимущество Cell, ибо избавляет от проблем когерентности кешей при мультипроцессорной обработке данных.

И снова теория

Прежде чем мы перейдем к практике, я бы хотел осветить несколько важных аспектов программирования PCI, PCI-E устройств.

Я вскользь упомянул о регистрах устройства, но как же к ним имеет доступ центральный процессор? Как многие из вас знают, есть такая сущность в компьютерных технологиях, как IO порты (Input/Output ports). Они предназначены для обмена информацией между центральным процессором и периферийными устройствами, а доступ к ним возможен с помощью специальных ассемблерных инструкций — in/out. BIOS (или OpenFirmware на PPC based системах) на ранних этапах инициализации PCI устройств, а также некоторых других (Super IO контроллера, контроллера PS/2 устройств, ACPI timer и т.д.), закрепляет за определенным контроллером собственный диапазон IO портов, куда и отображаются регистры устройства.

Итак, существует два метода утилизации DMA: contiguous DMA и scatter/gather DMA.

Contiguous DMA

Scatter/gather DMA

С ростом скорости Ethernet адаптеров, contiguous DMA показал свою несостоятельность. В основном из-за того, что требовались области памяти достаточно большого размера, которые подчас невозможно было выделить, так как в современных системах фрагментация физической памяти достаточно высока. Во всем виноват механизм виртуальной памяти, без которого нынче никуда 🙂

Решение напрашивается само собой: использовать вместо одного большого участка памяти несколько, но в разных регионах этой самой памяти. Возникает вопрос, но как же сообщить контроллеру устройства, как инициировать DMA трансфер и по какому адресу писать данные? И тут нашли решение, использовать дескрипторы, чтобы описывать каждый вот такой участок в оперативной памяти.

На сегодня пожалуй все, иначе информации станет слишком много. В следующей статье я покажу вам, как с этой уличной магией работает IOKit. Жду отзывов и дополнений 😉

Источник

DMA вообще и в частности

Знал бы где упадешь, соломки подстелил бы

О существовании DMA (Direct Memory Access) — русскоязычное ПДП (Прямой Доступ к Памяти) многие разработчики встроенных устройств слышали, но вот применяют его гораздо реже, чем он (ПДП) этого заслуживает. Кстати, я буду упоминать именно эту аббревиатуру, но не потому, что я такой упрямый патриот и противник англоязычных заимствований, а всего лишь от того, что мне лень лишний раз переключать раскладку клавиатуры.

Основных причин недостаточного использования ПДП в программах для МК три: 1) относительная сложность данного устройства, которая вместе с 2) непониманием выгод его применения приводит к нежеланию данное устройство изучать и осваивать (как говорят в таких случаях, старшая сестра не велит — для тех, кто в танке — это про лень, котороая раньше нас родилась), отягощенному 3) отсутствием хороших и понятных примеров применения ПДП в поставляемых с МК руководствами. И если первые две причины носят явно субъективный характер, то третья несомненно объективна и внутри меня просыпается параноик и настойчиво утверждает, что это сделано специально с целью не допустить отечественных разработчиков МК на продвинутые уровни, где-то выше 60 (то, что при этом страдают и остальные разработчики по всему миру, параноиком игнорируется, поскольку либо 1) за пределами России распространяются правильные примеры, либо 2) ради великой цели не допустить вставания, сами понимаете кого, с колен буржуины готовы пойти на любые жертвы).

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

Тем не менее, должен предостеречь нетерпеливого читателя, что он не найдет тут серебряной пули, которую можно смело включать в свои разработки, а всего лишь (но и это немало) обнаружит некоторые мысли и подходы, которые облегчат ему построение своих собственных систем на МК с применением ПДП. То есть я поставлю флажки в тех местах, где точно лежат грабли, но не гарантирую, что непомеченных граблей не останется, что, впрочем, не мешает Вам повыкидывать флажки на фиг и пройтись по граблям самостоятельно. Вообще, те, кто читал мои посты, наверняка обратили внимание, что я делаю упор не на то, ЧТО следует сделать и КАК именно, а на то, ПОЧЕМУ я рекомендую сделать именно так.

Итак, ПДП — это часть аппаратуры МК, которая позволяет производить пересылку данных между различными составными частями данного МК (и системы с его участием) без привлечения ресурсов процессора (точнее говоря, с минимальным привлечением, поскольку делать что либо в МК системе вообще БЕЗ участия процессора — мысль смелая и далеко заводящая).

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

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

Встречалась картина (например в ДВК), когда одно ВУ работало по ПДП, а второе — по прерываниям или даже в цикле опроса (драйвер MX, если кто помнит). Когда появились первые МК, в которых память интегрирована в чип, создание внешних по отношению к кристаллу устройств с ПДП стало весьма нетривиальной задачей и, естественно, ПДП перекочевало внутрь МК и стало частью его архитектуры. Этот процесс не имел линейного поступательного характера и можно встретить как МК на основе 51й архитектуры с поддержкой ПДП, так и МК на основе Cortex-M3 с богатым набором периферии, но без поддержки ПДП (например, Stellaris). Тем не менее, в бОльшей части современных МК на основе ARM ПДП присутствует и можем перейти к их изучению и для начала остановиться на рассмотрению их особенностей.

Как было сказано выше, основное назначение ПДП — передача данных между разными устройствами с минимальным привлечением процессора. Как правило, одним из этих устройств является основная память МК, хотя иногда возможны как передачи между двумя внешними устройствами, так из памяти в память. Некоторые ПДП поддерживают такие возможности, некоторые нет, и это следует учитывать при выборе МК.

Следующей особенностью можно считать механизм обслуживания запросов в одном канале. Остановимся на этом моменте поподробнее и разберем в общих чертах работу одного запроса одного канала ПДП.

Для того, чтобы совершить передачу, нам следует сообщить ПДП о своих намерениях, то есть откуда мы хотим передавать данные, куда их следует направить, сколько именно данных следует передать, режимы передачи (об этом чуть позже), и, возможно, служебная информация. Поместить данную информацию мы можем либо в регистры, ответственные за работу ПДП (устаревший способ и дальше мы поймем, почему) либо в оперативную память системы, причем в последнем случае это должна быть либо специальная выделенная область памяти, чтобы ПДП знал, откуда надо взять информацию, либо в какой то регистр ПДП должна быть помещена информация о том, где именно в оперативной памяти лежат данные (говоря привычным языком, мы должны поместить ссылку). Именно последний метод и используется, поскольку он имеет следующие преимущества: оперативная память МК — ценный ресурс и сегментировать ее на детерминированные блоки не есть хорошая практика.

На практике встречаются разнообразные комбинированные схемы, в основном с целью экономии аппаратуры МК — указатель на область памяти, в которой размещаются 2 или более БУП, циклически переключающиеся по мере исполнения, указатель на следующий БУП в служебном поле текущего БУП, иерархический доступ, когда БУП содержит указания на последовательность БУП, которые собственно и выполняются и так далее.

Теперь перейдем от рассмотрения ПДП вообще к рассмотрению конкретной реализации, а именно к МК фирмы «Миландр» 1986ВЕ1Т.

Почему именно к нему? Ну, во-первых, я с ним работаю, во-вторых, у него достаточно богатый по возможностям ПДП, а в-третьих, у него богато фич (это не баг это фича такая — да да именно из этого разряда), которые делают работу с МК увлекательным занятием, после которого работа с аналогами от известных производителей покажется легкой и простой.

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

Читайте также:  какой метод предполагает обязательное подведение итогов и оценивание результатов эффективности тест

Далее, ПДП поддерживает разные форматы данных: 1 байт, 2 байта и 4 байта (слово в нашей архитектуре), а также разные виды инкремента адреса отдельно источника и приемника: на 1, на 2 и на 4 (декремент не поддерживается). ПДП имеет систему арбитража обслуживаемых каналов с возможностью назначения гибких приоритетов для каждого канала и настраиваемого размера элементарной транзакции (количества передач одного канала, по выполнению которых производится арбитраж). Кроме того, каждый канал может иметь до 2 БУП, которые могут сменяться по циклической системе, либо работать в иерархическом режиме, вместе с тем возможен и однократный режим.

Чтобы соблюсти баланс, скажем и о менее хорошем. Опять про документацию — если Вы на знаете, как работает ПДП, то из документации фирмы вы об этом точно не узнаете. Документация явно переводная, есть и ошибки перевода, которые сильно искажают смысл, есть и весьма невнятно описанные места, но в целом для подготовленного разработчика ее может и хватить, если вы привыкли домысливать за автора. Из более существенных недостатков (конечно по сравнению с идеальным устройством) — значительные затраты ресурса шины (6 доступов на 1 пересылку, хотя может я чего-то недопонял) и еще ряд фич, о которых чуть позже.

ЗАСАДА №1 от разработчиков — прерывание от окончания передачи не проявляются в внешних устройствах. То есть у нас есть один вектор прерывания по окончанию транзакции от любого из запрограммированных каналов. Более того, нет никакого регистра, в котором хранился бы номер канала, завершившего транзакцию, либо хотя бы битовый регистр с флагами. То есть единственный способ определить номер завершившего передачу канала — перебирать все каналы и смотреть соответствующие поля TCB, и это нам придется делать в обработчике прерывания, который должен занимать минимальное время. Напрашивающееся решение — перенести поиск канала в нижнюю половину драйвера обработки не проходит, поскольку нас ожидает:

ЗАСАДА № 2 от разработчиков — прерывание является потенциальным и прекратить работу верхней половниы, не сбросив его явным образом, мы не можем. Более того, есть еще и

ЗАСАДА № 3 от них же — мы не можем сбросить прерывание путем манипуляции реистрами ПДП, а должны проводить сброс разрешения выработки запросов в регистрах внешнего устройства. Да да, именно так, драйвер ПДП должен что то знать о составе регистров обслуживаемых устройств, более чудовищного нарущения принципа инкапсуляции (а он справедлив и для проектирования аппаратуры) трудно себе представить. Я не знаю, что курили разработчики ПДП, но, как написано у Гайдука, «что то очень интересное». То есть, вы можете не верить, но если мы запретим прохождение запросов соостветствующего канала и запретим его обработку, то прерывание мы все равно НЕ СБРОСИМ.
Что-то получилось больше букв, чем было запланировано, поэтому оставим читателей размышлять о сложной ситуации, в которой оказался главный герой истории, что особенно актуально в пятницу вечером, а сам напишу Продолжение следует…

Источник

Прямой доступ к памяти

Прямой доступ к памяти (англ. Direct Memory Access, DMA ) — режим обмена данными между устройствами или же между устройством и основной памятью (RAM) без участия Центрального Процессора (ЦП). В результате скорость передачи увеличивается, так как данные не пересылаются в ЦП и обратно.

Кроме того, данные пересылаются сразу для многих слов, расположенных по подряд идущим адресам, что позволяет использование т. н. «пакетного» (burst) режима работы шины — 1 цикл адреса и следующие за ним многочисленные циклы данных. Аналогичная оптимизация работы ЦП с памятью крайне затруднена.

В оригинальной архитектуре IBM PC (шина ISA) был возможен лишь при наличии аппаратного DMA-контроллера (микросхема с индексом Intel 8237).

DMA-контроллер может получать доступ к системной шине независимо от центрального процессора. Контроллер содержит несколько регистров, доступных центральному процессору для чтения и записи. Регистры контроллера задают порт (который должен быть использован), направление переноса данных (чтение/запись), единицу переноса (побайтно/пословно), число байтов, которое следует перенести.

ЦП программирует контроллер DMA, устанавливая его регистры. Затем процессор даёт команду устройству (например, диску) прочитать данные во внутренний буфер. DMA-контроллер начинает работу, посылая устройству запрос чтения (при этом устройство даже не знает, пришёл ли запрос от процессора или от контроллера DMA). Адрес памяти уже находится на адресной шине, так что устройство знает, куда следует переслать следующее слово из своего внутреннего буфера. Когда запись закончена, устройство посылает сигнал подтверждения контроллеру DMA. Затем контроллер увеличивает используемый адрес памяти и уменьшает значение своего счётчика байтов. После чего запрос чтения повторяется, пока значение счётчика не станет равно нулю. По завершении цикла копирования устройство инициирует прерывание процессора, означающее завершение переноса данных. Контроллер может быть многоканальным, способным параллельно выполнять несколько операций.

Содержание

Захват шины (bus mastering)

В шинах MicroChannel, SBus, разработанной под их большим влиянием PCI и её концептуальных производных AGP и PCI-X, используется иная реализация DMA. Эти шины позволяют любому устройству заявить о возникновении потребности к захвату шины, таковая потребность удовлетворяется т. н. арбитром при первой возможности. Устройство, успешно осуществившее захват шины, самостоятельно выставляет на шину сигналы адреса и управления и исполняет в течение какого-то времени ту же ведущую роль на шине, что и ЦП. Доступ ЦП к шине при этом кратковременно блокируется.

В такой реализации DMA не существует DMA-контроллера, а также номера входа DMA-контроллера.

Некоторые старые устройства PCI, а именно, реализации звуковых карт семейства Sound Blaster, использовали тот же DMA-контроллер 8237 из оригинальной архитектуры IBM PC. Такое использование является, безусловно, устаревшим для PCI, но поддерживалось с целью обеспечить полную совместимость по ПО и драйверам с версиями Sound Blaster для шины ISA.

Данная поддержка называется Distributed DMA (D-DMA) и реализована аппаратным образом как в устройстве, так и в логике моста PCI-ISA, в которой на PCI-системах размещена и логика оригинального IBM PC DMA контроллера 8237. Реализация включает в себя 2 запроса: сначала от устройства мосту PCI-ISA, затем от моста основной памяти.

Кроме упомянутых реализаций Sound Blaster, практически никакие устройства PCI не используют понятие «номер входа DMA-контроллера», как и 8237 вообще.

DMA и виртуальная память, IOMMU и AGP GART

В операционных системах со страничной виртуальной памятью, таких, как Windows и семейство UNIX, непрерывный регион виртуальных адресов может быть реализован разрывно расположенными физическими страницами.

Исполнение DMA по такому региону представляет собой довольно сложную задачу. Также сложной задачей является исполнение DMA по отгружаемой памяти.

Решение этой задачи требует выявления физических страниц, реализующих регион, и их блокировку от отгрузки обращением к подсистеме виртуальной памяти. Далее становится возможным нахождение физических адресов страниц региона, которые в общем случае не являются непрерывными и формируют так называемый «список рассеяния/сборки» (англ. scatter-gather listSGL).

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

1. Выделение подряд идущей физической памяти в ядре операционной системы и промежуточное копирование всех данных туда/оттуда (т. н. «буфер отскока» — англ. bounce buffer ).

Недостатки: трата времени процессора на копирование, потребление крайне ограниченного ресурса непрерывной физической памяти, занятие места в ограниченной части памяти, к которой есть доступ у DMA (первый гигабайт на x86).

2. Разбиение операции на подоперации по границам элементов SGL, с прерыванием в конце каждой операции.

Использовалось в старых 8-битных SCSI-контроллерах, поставляемых со сканерами типа HP ScanJet.

Недостатки: большое количество прерываний.

3. Поддержка SGL самим устройством, с требованием копирования SGL, преобразованного в формат, специфичный для устройства, в устройство через многочисленные обращения к регистрам устройства.

Недостатки: крайне высокая сложность устройства, невысокая производительность большого числа записей в регистры.

4. Поддержка SGL самим устройством, с требованием размещения SGL, преобразованного в формат, специфичный для устройства, в физически непрерывном регионе основной памяти.

Устройство читает SGL тем же механизмом DMA с захватом шины, что и собственно данные, тем самым реализуя функциональность некоего процессора, читающего и исполняющего свою собственную «программу», реализованную как список дескрипторов SGL. Данная архитектура называется «цепной DMA» (англ. chain DMA ), реализована в практически всем стандартном оборудовании современного компьютера — Intel IDE (в примитивном виде), UHCI и OHCI USB, OHCI 1394, а также в большинстве PCI-адаптеров, Ethernet и SCSI (даже в устаревшем AIC78xx). Хороший пример реализации данной архитектуры в очень сложном и развитом виде дан в спецификации оборудования OHCI 1394. По некоторым сведениям, данная архитектура под названием «канальные программы» использовалась ещё в IBM 360, известных в СССР как ЕС ЭВМ.

Недостатки: высокая сложность устройства, хотя и ниже в числе транзисторов, чем предыдущий вариант. Например, контроллер UHCI USB (согласно спецификации на сайте Intel) требует около 5000 транзисторов.

5. Поддержка SGL в межшинном оборудовании, при которой представление физически разрывного буфера для стороны устройства выглядит физически непрерывным.

Недостатки: требование сложной логики уже не в устройстве, а в платформе.

DMA и IDE/ATA, Ultra DMA

Первоначальный контроллер жесткого диска IBM PC/AT не поддерживал DMA, и требовал передачи всех данных дискового ввода/вывода инструкциями REP INSW/REP OUTSW через порт 0x1f0.

В начале 90х годов диски MFM/RLL вымерли, сменившись дисками IDE, но регистровый интерфейс ПО к контроллеру не изменился.

Низкая производительность такого контроллера стала серьёзной проблемой, особенно на системах PCI. Помимо требования нескольких циклов PCI на 2 байта переданных данных, это приводило к загрузке процессора дисковым вводом-выводом.

Для решения проблемы ряд компаний, в том числе Intel, разработали контроллеры IDE с поддержкой DMA. Контроллеры были и есть несовместимы по ПО между различными производителями, хотя совместимость всех Intel IDE/ATA/SATA снизу вверх более или менее поддерживается.

Также особенностью этой поддержки является использование новых команд протокола IDE/ATA, а значит, и требование поддержки DMA не только контроллером, но и самим жестким диском.

Около 2000 года поддержка DMA по шине IDE/ATA развилась в сторону увеличения тактовой частоты шины, что потребовало нового типа кабеля от контроллера к диску с удвоенным числом проводников меньшего размера. Эта технология называлась Ultra DMA (UDMA).

Многие операционные системы требовали действий администратора для использования IDE DMA. Так, например, стандартные ядра Linux до примерно 2004 года не имели такой поддержки, требовалось перестроение ядра с отредактированным файлом конфигурации.

В семействе Windows поддержка IDE DMA появилась сначала только для Intel в пакетах обновлений к Windows NT4, и требовала на большинстве систем ручного редактирования реестра для задействования.

Читайте также:  какой инструмент используют в проектном менеджменте для управления рисками портфеля проектов

В Windows 2000 это требование исчезло, но появилось требование обязательной вписки даже не-загрузочных дисков в BIOS и обязательного выставления режима DMA для них в настройках BIOS. Эти настройки BIOS становились видимы ядру ОС через технологию ACPI, и ОС не позволяла включить DMA для диска, не вписанного в BIOS. Для сравнения: NT4 поддерживала и произвольный размер диска, и DMA без вписки диска в BIOS.

В системах Linux для включения или выключения IDE DMA вручную может применяться команда hdparm (см. ниже). Современные версии ядра автоматически включают DMA режим, что можно наблюдать в сообщениях отладки (строки вида ata1.00: configured for UDMA/133 или hda: UDMA/33 mode selected).

Источник

Электроника для всех

Блог о электронике

Контроллер прямого доступа к памяти (DMA) контроллера STM32

Работа с контроллером DMA

▌Что это?
Есть в современных контроллерах такой блок DMA — контроллер прямого доступа к памяти (ПДП). Штука очень простая, несмотря на умное название. Вся ее суть заключается в том, чтобы по команде от периферии или ядра взять и скопировать кусок памяти с одного места на другой.

Что с этим можно делать? Ой да много чего. Можно, задать в качестве источника адрес какой-либо периферии, скажем выходной регистр АЦП, а в качестве приемника адрес массива в ОЗУ, дать приказ DMA по команде завершения оцифровки АЦП хватать результат и пихать в массив. При этом DMA сам скопирует, сам увеличит адрес, а как заполнит буфер, то обнулится и начнет его переписывать по кругу заново.

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

А можно сделать и наоборот. Сказать DMA что мол вот тебе буфер, там лежит пара сотен байт, возьми и запихай их все в жерло UART и пойти по своим делам, а DMA трудолюбиво отправит в передачу весь буфер.

▌Ближе к теме
Контроллеры
Если рассмотреть конкретно STM32 то там находится один (на малых контроллерах вроде STM32F103C8T6) или два DMA контроллера DMA1 и DMA2 соответственно. На DMA1 есть 7 каналов, на DMA2 всего 5. Оба канала DMA сидят на шине AHB и перед тем как начать с ним работать надо на него подать тактирование, подняв биты DMA1EN и DMA2EN в регистре RCC_AHBENR, как и с любой другой периферией на STM32. В остальном они идентичные и работа с первым и вторым одинакова.

Каналы контроллера
Каждый канал независимый и может работать сам по себе со своими настройками. Но в один момент времени может работать только один канал у каждого контроллера. Чтобы избежать коллизий у каждого канала есть два уровня приоритета. Первый, программный. Мы просто в битах настройки задаем один из четырех уровней. А второй аппаратный, если придут запрос на два канала с одинаковым приоритетом в настройках, то победит тот, чей номер меньше. Сходно с обработкой прерываний. Там такой же двухступенчатый арбитраж.

За выбор приоритета отвечают два бита PL регистра DMA_CCRx Для каждого канала регистр свой. Вариантов там немного, всего четыре:

Каждый канал привязан к конкретной периферии. Т.е. если вам нужно чтобы DMA пинал АЦП, то ищите к какому каналу подключено именно АЦП.


Обратите внимание на то, что помимо аппаратных событий в мультиплексор еще ведет софтверный триггер от бита MEM2MEM. Для каждого канала:

Размер данных
За раз DMA может копировать порцию данных в 1, 2 или 4 байта. В первую очередь это влияет на приращение адресов при вычислении куда класть. Так что это настройка жизненно важна. Сколько и куда класть определяется обычно периферией. Т.е. если UART принимает и выдает по байту, то результат у нас 8 битный. А вот АЦП, может, например, выдать 16 битный результат. Значит размер указывать надо два байта, чтобы сразу за один заход их все забрать. Ну и, очевидно, что размер принимаемых и сохраняемых данных обычно совпадает. Хотя, вам никто не запретить класть однобайтные данные периферии в 32 разрядный массив, выравниваясь по двойному слову. Тогда размер может быть и разный.

За размер данных периферии и памяти отвечают два бита PSIZE и MSIZE регистра DMA_CCRx

00 — 1 байт
01 — 2 байта
10 — 4 байта
11 — не используется.

Откуда куда
Адрес периферии для каждого канала задается в регистре DMA_CPARх этого канала. Просто пишем туда адрес нужного регистра периферии. Но есть два важных момента. Во-первых, нельзя писать в этот регистр при включенном DMA. Т.е. при изменении их бит EN должен быть снят. Второе, адрес зависим от битов PSIZE регистра DMA_CCRx. Т.е. если у нас указан размер данных как 1 байт (PSIZE = 00), то активные все биты регистра DMA_CPARx. Но если данные указаны как слова или как двойное слово, по 16 или 32 бита соответственно, то один или два младших бита этого регистра игнорируются вообще. Т.е. получается, что адрес выравнивается по словам или двойным словам. Т.е. DMA не сможет записать данные словами начиная с нечетного адреса, но адреса все выровнены по словам, так что это пофигу.

Адрес памяти лежит в аналогичном регистре DMA_CMARx и там все то же, что и для DMA_CPARx только за размер отвечают биты MSIZE и его тоже нельзя трогать на включенном канале.

Также надо указать направление копирования. За него отвечает бит DIR регистра DMA_CCRx.

Когда он 0 то мы читаем из адреса DMA_CPARx и пишем по адресу DMA_CMARx. А когда он 1, то наоборот, соответственно. Название у бита идиотское. Не, ну понятно, что направление, но лучше бы назвали его M2P, то есть если 1, то из памяти в периферию. Или как то так. Долго никак не мог запомнить направление, пока не связал, что 0 он такой округлый и похож на такую же округлую букву P — Periph. А 1 угловатая, прям как буква М — Мemory.

Ну и опции инкремента адреса. биты PINC и MINC во все том же DMA_CCRx. Они отвечают за то, чтобы после каждой сработки у нас автоматически увеличивался адрес которые в DMA_CPARx или с DMA_CMARx соответственно.

Адрес периферии прибит намертво и редко когда надо его менять, так что обычно PINC всегда равно нулю. Т.е. никакого инкремента. Вам же не надо, чтобы после чтения из DR того же UART1 на следующем байте было уже из следующего по списку BRR :)))

Хотя… в некоторых случаях таким образом можно сдернуть по DMA содержимое всех регистров настройки какой-нибудь периферийной штуки. Например для отладки, чтобы сделать это предельно быстро, на лету. Правда тут надо быть очень осторожным. Многие биты событий и флаги прерываний снимаются при чтении. И чтение через DMA тоже считается. Так что побыть бесстрастным наблюдателем не выйдет.

Зачем же нужен инкремент DMA_CPARx? А для режима копирования из памяти в память. Тогда мы в DMA_CPARx пишем адрес одного буфера, в DMA_CMARx адрес другого, ставим бит MEM2MEM, даем разрешение и поехали!

А вот бит MINC ставится почти всегда. Мы указываем DMA_CMARx начало буфера и DMA, увеличивая адрес, его последовательно заполняет, или читает из него в периферию.
Впрочем, если нам надо гнать из периферии в периферию, скажем из АЦП сразу в SPI, то бит MINC тоже равен нулю будет.

Ну и есть еще один вариант, когда инкремента нет ни на адресе приемника, ни на адресе источника. Таким образом делаются прямые перегонки, например, из АЦП сразу в USART, минуя процессор. Или на SPI. Так можно в десяток строк кода превратить STM32 в какой нибудь вариант SPI АЦП 🙂

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

Сколько?
За то сколько раз должно отработать DMA отвечает регистр DMA_CNDTRx. На каждую сработку от входного сигнала (или от постоянно стоящего бита MEM2MEM) DMA копирует один обьект и уменьшает число в DMA_CNDTRx и так до нуля. Дойдет до нуля, канал выключится. Бит EN тут уже ничего решать не будет. Но если стоит бит CIRC, то регистр перезагрузится исходным значением и продолжит работу. Значение может быть до 65535, используются только младшие 16 бит, старшие два байта ДОЛЖНЫ БЫТЬ НУЛЕМ ВСЕГДА.

Записать в регистр DMA_CNDTRx можно только при выключенном DMA канале. Читать можно когда угодно, по нему можно определять сколько объектов осталось DMA передать.

И тут есть важный нюанс. Я намеренно выше говорил, что «сколько осталось объектов». DMA_CNDTRx Считает не БАЙТЫ, а сколько раз DMA сработал. А за одну сработку, он зависимости от настроек, может пересунуть 1, 2 или 4 байта.

Т.е. если вы откуда то скопипастите код в котором будет что-то вида:

Разумеется изменив PSIZE и MSIZE на 32 бита, то вы получите веселуху: sizeof(IN_Buffer) от 10 uint32_t даст вам 40 и DMA пропишет вам в оперативку 40 раз по четыре байта, захреначив все до куда дотянется :)))) Так что либо корректируйте результат операции sizeof с учетом разрядности данных, либо напрямую указывайте сколько у вас повторений в подходе.

▌Обратная связь
Жевать байты это замечательно, но должен же этот контроллер как то сообщать фоновой программе, что «он сделяль»? Само собой. И реализовано это совершенно традиционно, через прерывания. Их у него три:

За включение этих прерываний отвечают биты

TEIE: Transfer error interrupt enable
HTIE: Half transfer interrupt enable
TCIE: Transfer complete interrupt enable

Регистра DMA_CCRx. У каждого канала свои. С прерываниями тут тоже все щедро. У каждого канала свой вектор, если заглянете в startup_stm32f103xb.s файл, то там будет что то вида:

Это вектора прерываний и есть. Чуть ниже, если вообще есть, будут и вектора для контроллера номер два. А понять же по какому поводу нас вызвало можно из регистра DMA_ISR — Interrupt status registry. Он на каждый контроллер DMA свой. В нем стоят все флаги какие только можно, оптом для всех каналов сразу. Сюда можно только только смотреть. Регистр read only.

Для сброса флага нужно записать 1 в соответственный ему бит регистра DMA_IFCR — Interrupt flag clear registry. Запись нуля же не означает ничего. Так что пишем сразу маску и не паримся.

Читайте также:  co trimoxazole что это

▌Инциализация и запуск

Вот теперь самое интересное. Прочитали выше написанное, поняли что нужно сделать… А теперь важно сделать это все в правильном порядке. DMA все критические настройки у DMA требуют, чтобы канал был выключен в момент и изменения. За включение и выключение отвечает бит EN регистра DMA_CCRx. Причем крайне желательно ставить этот бит отдельно от всех и снимать отдельно от всех.

Иначе могут быть приколы. Я тут недавно прикольные вилы словил. Сделал процедурку инициализации, думаю, а чего это я их буду по одному ставить? В одном же слове все, дай их сразу и пропишу как надо? За один заход. Выставил все биты конфигурации, на UART1 — работает. Ну окей, взял ЭТИ ЖЕ функции и накатил их на инициализацию UART2 с ТОЧНО таким же кодом, только про UART2, т.е. поменял только имена регистров и каналов DMA. Запускаю… не работает, хоть убейся. Первый UART через DMA работает, второй нет. И так и эдак… Ничего не понимаю. Ладно я бы что-то не то сделал, так тогда бы оба не работали…

Стал под отладчиком ходить, смотреть по регистрам DMA, а у меня запись в регистр конфигурации втором случае DMA_CCRx не проиходит. Т.е. бит должен записаться, а не записывается. Стал разбираться что за фигня и как это так получается вообще? Оказалось, что это оптимизатор так решил, что ему удобней будет записать в одном случае (который работает) сначала старший байт в порт, а потом младший. При этом бит EN записывается последним, а во втором случае наоборот. Записывает младший, т.е.бит EN, а при записи старшего происходит аппаратный сброс этого бита EN. ИЧСХ в даташите ни разу не сказано, что бит EN может сниматься аппаратно. Нигде. Но это происходит.

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

#define DMAEnable (1 CCR; // Копируем биты настройки tmp &= CCR_CLEAR_Mask; // и стираем все кроме битов EN. А он и так будет 0 tmp |= Conf; // Закатываем на результат наши биты настроек. Channel->CNDTR = Size; // Заполняем все нужные поля. Размер передчи Channel->CPAR = Perif; // Адрес периферии Channel->CMAR = Mem; // Адрес в памяти Channel->CCR = tmp; // Записываем настройки в память. >

Эти две фукнции просто включают и выключают определенный канал.

void DMA_Enable(DMA_Channel_TypeDef* Channel) < Channel->CCR |= DMA_CCR1_EN; > void DMA_Disable(DMA_Channel_TypeDef* Channel) < Channel->CCR &= (uint16_t)(

Еще нужна процедурка деинициализации DMA, чтобы вернуть все настройки в изначальное состояние, как после сброса:

void DMA_DeInit(DMA_Channel_TypeDef* Channel) < Channel->CCR &= (uint16_t)(

DMA_CCR1_EN); Channel->CCR = 0; Channel->CNDTR = 0; Channel->CPAR = 0; Channel->CMAR = 0; if (Channel == DMA1_Channel1) < /* Reset interrupt pending bits for DMA1 Channel1 */ DMA1->IFCR |= DMA1_Channel1_IT_Mask; > else if (Channel == DMA1_Channel2) < /* Reset interrupt pending bits for DMA1 Channel2 */ DMA1->IFCR |= DMA1_Channel2_IT_Mask; > else if (Channel == DMA1_Channel3) < /* Reset interrupt pending bits for DMA1 Channel3 */ DMA1->IFCR |= DMA1_Channel3_IT_Mask; > else if (Channel == DMA1_Channel4) < /* Reset interrupt pending bits for DMA1 Channel4 */ DMA1->IFCR |= DMA1_Channel4_IT_Mask; > else if (Channel == DMA1_Channel5) < /* Reset interrupt pending bits for DMA1 Channel5 */ DMA1->IFCR |= DMA1_Channel5_IT_Mask; > else if (Channel == DMA1_Channel6) < /* Reset interrupt pending bits for DMA1 Channel6 */ DMA1->IFCR |= DMA1_Channel6_IT_Mask; > else if (Channel == DMA1_Channel7) < /* Reset interrupt pending bits for DMA1 Channel7 */ DMA1->IFCR |= DMA1_Channel7_IT_Mask; > else if (Channel == DMA2_Channel1) < /* Reset interrupt pending bits for DMA2 Channel1 */ DMA2->IFCR |= DMA2_Channel1_IT_Mask; > else if (Channel == DMA2_Channel2) < /* Reset interrupt pending bits for DMA2 Channel2 */ DMA2->IFCR |= DMA2_Channel2_IT_Mask; > else if (Channel == DMA2_Channel3) < /* Reset interrupt pending bits for DMA2 Channel3 */ DMA2->IFCR |= DMA2_Channel3_IT_Mask; > else if (Channel == DMA2_Channel4) < /* Reset interrupt pending bits for DMA2 Channel4 */ DMA2->IFCR |= DMA2_Channel4_IT_Mask; > else < if (Channel == DMA2_Channel5) < /* Reset interrupt pending bits for DMA2 Channel5 */ DMA2->IFCR |= DMA2_Channel5_IT_Mask; > > >

И, собственно, примеры:

▌Копирование одного массива в другой. Режим MEM2MEM

// Массив который копируем и куда копируем static uint32_t INbuff[10] = <0xFFFFFFF1,0xFFFFFFF2,0xFFFFFFF3,0xFFFFFFF4,0xFFFFFFF5,0xFFFFFFF6,0xFFFFFFF7,0xFFFFFFF8,0xFFFFFFF9,0xFFFFFF10>; static uint32_t OUTbuff[10] = <0>; // Включаем тактирование DMA RCC->AHBENR |= RCC_AHBENR_DMA1EN; // Обнуляем канал который будем использовать. Канал берем от балды. Для этой цели подойдет любой свободный. DMA_DeInit(DMA1_Channel3); // Настраиваем DMA_Init( DMA1_Channel3, // Какой канал работать будет (uint32_t)INbuff, // Откуда (uint32_t)OUTbuff, // Куда 10, // Сколько. 10 двойных слов, не байтов. Массив у нас на 10 элементов TransCompl_Int_Disable + // Прерывание по передаче выключено HalfCompl_Int_Disable + // Прерывание по половине выключено TransError_Int_Disable + // Прерывание по ошибке выключено ReadPerif + // Читаем из «периферии». CircularMode_Disable + // Циклический режим не нужен. Копируем один раз. PeripheralInc_Enable + // Увеличиваем адрес источника MemoryInc_Enable + // Увеличиваем адрес приемника PDataSize_DW + // Размер источника двойной слово MDataSize_DW + // Размер приемника двойное слово DMA_Priority_Low + // Низкий приоритет M2M_Enable ); // Копирование память-память. // Разрешаем копирование. DMA_Enable(DMA1_Channel3); >

Вуаля! Данные будут скопированы.

▌Копирование из периферии в буфер памяти.
В данном случае из выходного регистра USART в кольцевой буфер в ОЗУ. Все что попадет в USART окажется в памяти автоматом.

static volatile char BufferForRecieving1[256]; // Кольцевой приемный буфер. RCC->AHBENR |= RCC_AHBENR_DMA1EN; // Подали тактирование на DMA DMA_Disable(DMA1_Channel5); // Выключили канал. DMA_DeInit(DMA1_Channel5); // Обнулили DMA канал USART1->SR &=

Теперь все что попадает в USART будет DMA утаскивать прямо в буфер, остается только его проверять. Ну или включить прерывания по половине и/или окончании передачи и реагировать на них.

▌Копирование из буфера в периферию

И после подачи разрешения буфер будет загружен в UAR побайтно, по мере отправки байт через последовательный интерфейс.

Ну и сами два файлика которые я использую как библиотечку для DMA

Спасибо. Вы потрясающие! Всего за месяц мы собрали нужную сумму в 500000 на хоккейную коробку для детского дома Аистенок. Из которых 125000+ было от вас, читателей EasyElectronics. Были даже переводы на 25000+ и просто поток платежей на 251 рубль. Это невероятно круто. Сейчас идет заключение договора и подготовка к строительству!

А я встрял на три года, как минимум, ежемесячной пахоты над статьями :)))))))))))) Спасибо вам за такой мощный пинок.

22 thoughts on “Контроллер прямого доступа к памяти (DMA) контроллера STM32”

Я на СТМ8 пользовался как раз для фоновой обработки АЦП в кольцевой буфер. очень удобно оказалось

Там он вроде бы очень похож на тот, что в стм32.

Ещё есть интересные примеры использования DMA+таймеров есть в этой теме: http://kazus.ru/forums/showthread.php?t=107109
сообщение 8 — управление восьмисегментными индикаторами
сообщение 9 — управление дисплеями HD44780/WH1602A

Находил ещё вот такой интересный материал про DMA: https://habr.com/ru/post/437112/. Не пугайтесь, там только вначале про Cypress, а потом уже про STM32. Был несколько удивлен после прочтения и некоторых размышлений.

Хм, а чему тут удивляться? Дма это не про скорость, а про параллельность. В чем ее и прелесть.

насчет параллельности тоже ведь не все просто. Сколько одновременно потоков может обращаться к памяти? судя по статье с хабра — 2 (по крайней мере для описаного МК). при этом не важно кто: ядро или DMA каналы …

но опять же, в наших то применениях этого и не замечаешь особо.

кстати, а может кто знает: если ОЗУ физически разделена на отдельные регионы (SRAM1, SRAM2, …), то глядя на матрицу шин в даташите, возникает вопрос: возможен ли одновременный доступ ядра к одной области и DMA к другой?
Вопрос чисто теоритический, но вдруг кто на практике проверял)

В данном случае главное, что в это время код выполняется тоже.

Доступ вряд ли доступен одновременно. На то и приоритеты даны, ядро будет приоритетней.

Для приема по USART можно использовать IDLE line detected, чтобы не ждать середины/конца заполнения буфера.

А я DMA настроил на вывод картинки на экран. В памяти сделал фреймбуфер, натравил на этот буфер DMA, и теперь просто рисую в этот буфер, и всё это само появляется на экране. Удобно, быстро и не напрягает процессор кучей прерываний по TXE SPI.

Почитайте внимательно мануал раздел 9.13.17. После программного сброса бита DMA_CCR_EN, нужно дождаться его фактического сброса, циклически перечитывая CCR, и тестируя текущее значение CCR_EN. Иначе ваша инициализация на работающем DMA может с некоторой вероятностью обломиться. И ещё неплохо бы раскрыть тему FIFO, которое позволяет многократно сократить нагрузку на системную шину со стороны DMA.

А вы часом семейства не путаете? В F103 я ничего такого не нашел в описании (RM00008).

Глава 9 вообще про GPIO и там всего 5 подглав.

Да, действительно, я перепутал, это для F4 так, поэтому код от F1 нельзя просто без изменений перенести на старшие семейства, несмотря на внешнюю одинаковость регистров, он вроде и будет работать, но на самом деле не совсем:).

А вот это интересное замечание получилось! SPL она же вроде сквозная идет. В том числе и на ф4, но там я не увидел выборки контроллера в библиотеке и не проверяется бит ЕН.

SPL для каждого семейства своя, хотя у них кое-как совместимый внешний API.

>> …оптимизатор так решил, что ему удобней будет записать в одном случае (который работает) сначала старший байт в порт, а потом младший.
>>
>> …при записи в регистры DMA надо явно указывать тип данных, чтобы компилятор сделал запись обои байт, старшего и младшего.
>>
>> #define DMAEnable ((uint16_t)(1<>
>>

Регистры имеют тип uint32_t, константы (1 uint32_t при присвоении не должно порождать кода преобразования вообще. Откуда чудеса?

Я думаю корни растут из Thumb2 который позволяет упаковку данных в памяти побайтно и работу с отдельными байтами. Так что он вполне может упихать константу в байт.

Не в тему, но про надежность кода — плюсовать битовые параметры не есть хорошая идея, лучше использовать битовый OR

Согласен, если какой-нибудь бит по какой-либо причине продублируется, то в случае «+» будет непонятный глюк, а в случае OR всё в порядке.

Привет. Сори, не знаю где спросить, пишу сюда. Почему в руководстве по Си http://easyelectronics.ru/file/yazyk-programmirovaniya-s-spravochnik/124 после пункта 1.8 Not found страницы?

Они просто недооформлены. Можете попробовать по ключевым фразам из текста загуглить и найти эту методичку.

Источник

Сказочный портал