Почему бы не всегда использовать DMA вместо прерываний с UART на STM32? [закрыто]

В прошлом месяце я потратил много времени на то, чтобы UART (для MIDI) работал с STM (STM32F103C8T6) с использованием прерываний, но без особого успеха.

Однако в этот вечер при использовании DMA все работало довольно быстро.

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

Я использую STM32CubeMx/HAL.

Не все µC получили DMA. Если вы можете использовать его, тогда отлично, используйте его, если вам нужна скорость.
@HarrySvensson Я не знаю, действительно ли мне нужна скорость, но DMA у меня заработало за несколько часов, в то время как я пробовал прерывания в течение нескольких недель (свободное время для хобби). Я подумал, что будет лучше сначала попробовать напрямую (это сработало), а не прерывания (не сработало), а затем DMA (используя прерывания).
Почему бы нет? Это либо вопрос мнения, пытающийся угадать, какая возможная техническая причина, либо, таким же образом, слишком широкий и, следовательно, неуместный здесь вопрос. Чтобы назвать случайный пример, DMA будет означать большую задержку при запросе данных, тем более что вы не получите никакой реальной выгоды, если не позволите ему собирать несколько символов. Часто это может быть хорошо, иногда нет.
Если на то, чтобы заставить работать прерывания, ушли недели, это потому, что вы неправильно подошли к задаче; работа с прямым доступом к памяти может занять больше времени — на самом деле это более сложная задача, поэтому кажущаяся легкость более сложной задачи по сравнению с более простой, по-видимому, сводится к ресурсам, которые вы использовали для руководства с каждой из них, а не к самому механизму.
@Michel Keijzers: вы никогда не говорили битрейт вашего приложения, или я его пропустил. Я очень удивлен, что у вас не сработали прерывания. При частоте 72 МГц и скорости 115200 бод у вас будет целых 5000 тактовых импульсов на символ.
Никогда не думайте, что dma освобождает процессор, иногда да, процессор продолжает работать, иногда нет, процессор зависает, чтобы удерживать шину для механизма dma. Тривиально сделать это с реализацией руки, поэтому нельзя просто сказать, что все руки такие, а все x86 такие или что-то в этом роде, это не так просто, вы всегда должны исследовать дизайн системы и, возможно, немного взломать. Чип, который у вас есть, вполне может освободить ядро ​​руки, это просто комментарий к dma. Что касается вашего вопроса, не имеет смысла, что вы не можете идти в ногу, и dma + int, вероятно, является полным решением, если вы не можете просто опросить.
поскольку вы используете HAL и / или любую библиотеку, предоставленную поставщиком, проблема может быть не в чипе, а в HAL или предоставленной библиотеке. Вы внимательно изучили каждый дюйм библиотеки, чтобы убедиться, что проблема не в коде или комбинации вашего кода, их кода и компилятора?
Примечание. MIDI имеет скорость 31250 бод, поэтому, вероятно, рука вообще не потеет, имея дело с этим.
@old_timer ... даже если я получаю по 1 байту за раз (что настройка нового прерывания DMA занимает слишком много времени?)
Прерывания на последовательном порту STM32F довольно тривиальны. Почему бы вам не опубликовать вопрос со своим кодом, чтобы некоторые из нас могли попытаться определить, где вы ошибаетесь? Никогда не стоит взламывать код, пока он не заработает, не понимая, в чем заключалась основная проблема.
@Jon Совершенно верно ... Я буду сегодня вечером (сейчас не дома). Что ж, учитывая множество сообщений о UART с прерываниями (не DMA) на STM, это не совсем тривиально.
По моему (не очень) скромному мнению, это один из недостатков использования ужасного раздутого куба. Напишите программное обеспечение с нуля, вы узнаете, как именно работает UART (потому что вам нужно), вы намного лучше поймете периферийные устройства, и в конечном итоге это сэкономит вам много времени.
@Jon ... может быть, я подожду немного ... сначала я хочу иметь рабочую версию с DMA, после этого я вернусь к прерываниям, чтобы посмотреть, работает ли это или нет ... чем я задам вопрос (я у меня очень мало времени извините).
@DiBosco правда ... однако я сначала продолжу некоторые проверки с помощью DMA (поскольку он, кажется, работает), затем вернусь к прерываниям, а затем, вероятно, использую способ «низкого уровня».

Ответы (6)

Хотя DMA разгружает ЦП и, таким образом, может уменьшить задержку других приложений, управляемых прерываниями, работающих на том же ядре, с этим связаны затраты:

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

    Например, если у вас есть массовая передача I2C каждые 5 мс, это кажется лучшим кандидатом для DMA, чем случайная команда отладки, поступающая на UART2.

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

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

  • Для работы DMA требуются буферы памяти (если только вы не выполняете DMA от периферии к периферии), поэтому с ним связаны некоторые затраты памяти.

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

  • DMA создает задержку , потому что ЦП получает уведомление только тогда, когда передача завершена / наполовину завершена (см. Другие ответы).

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

    • Это может означать, что необходимо обрабатывать первые символы сообщения с использованием посимвольных прерываний: например, при взаимодействии с XBee вы должны сначала прочитать тип и размер пакета, а затем инициировать передачу DMA в выделенный буфер.

    • Для других протоколов это может быть вообще невозможно, если они используют только разделители конца сообщения: например, текстовые протоколы, которые используют '\n'в качестве разделителя. (Если только периферийное устройство прямого доступа к памяти не поддерживает сопоставление символов.)

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

Чтобы добавить некоторые неофициальные данные, я столкнулся со всеми этими компромиссами в хобби-проекте, в котором использовалось много разных периферийных устройств с очень разными протоколами. Приходилось идти на некоторые компромиссы, в основном из-за вопроса «сколько данных я передаю и как часто я собираюсь это делать?». По сути, это дает вам приблизительную оценку влияния простой передачи, управляемой прерываниями, на ЦП. Таким образом, я отдал приоритет вышеупомянутой передаче I2C каждые 5 мс по сравнению с передачей UART каждые несколько секунд, которая использовала тот же канал DMA. Другая передача UART, происходящая чаще и с большим количеством данных, с другой стороны, имеет приоритет над другой передачей I2C, которая происходит реже. Это все компромиссы.

Конечно, использование DMA тоже имеет свои преимущества, но это не то, о чем вы просили.

Спасибо за ваш подробный ответ. MIDI будет самой важной частью, поэтому я думаю, что DMA подходит для него (хотя скорость низкая: 31250 бод). У меня достаточно каналов DMA, позже я собираюсь использовать еще один STM32 при использовании 4 USART. Мне не нужно приостанавливать работу процессора, так как он будет иметь питание USB 5 В, и мне нужно выполнять обработку между сообщениями (для обработки сообщений в основном цикле). У меня есть буфер чтения 256 байт и буфер передачи 256 байт. Я могу увеличить его позже, если это необходимо. STM32f103c8t6 имеет 20 КБ ОЗУ, а возможный STM, который я буду использовать, имеет 192 КБ.
И вы даете мне очень хорошую идею, как улучшить. До сих пор я всегда читал 1 байт и непрерывно проверял получение полного (MIDI) сообщения. Но я могу прочитать первый байт, и в зависимости от этого в основном известен размер и могу запросить остальные. Это стоило мне еще одного небольшого буфера, но это нормально.
Чтение отдельных байтов с помощью DMA очень неэффективно. Для уменьшения задержки и повышения эффективности рекомендуется использовать посимвольные прерывания до тех пор, пока вы не узнаете размер, а затем переключиться на DMA.
Ну, у меня было много проблем с использованием прерываний (без DMA), я думаю, что буду использовать 1-байтовый прием DMA, и после этого я знаю, сколько байтов я ожидаю, и делаю запрос DMA, чтобы получить больше.
Вероятно, это ошибка - вы должны исправить свой простой код прерывания без DMA.

Использование DMA обычно означает, что вы больше не выполняете прерывание по каждому символу, а только после того, как «буфер заполнен» символами был получен (или передан). Это увеличивает задержку обработки этих символов — первый символ не обрабатывается до тех пор, пока не будет получен последний символ в буфере.

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

Что я делаю, так это получаю по 1 байту за раз (то есть буфер «DMA» из 1 байта) и после каждого обратного вызова DMA этого одного байта сохраняю его в кольцевом буфере, который я обрабатываю вручную. В моем основном цикле я собираюсь проверять полные MIDI-сообщения и обрабатывать их.
DMA обычно используется для получения нескольких байтов и прерывания только после их получения. Прерывание после всего лишь одного байта является нормальным, когда не используется DMA, поэтому я задаюсь вопросом: какой смысл в дополнительном усложнении использования DMA для этого?
@MichelKeijzers Тогда то, что вы делаете, почти то же самое, что и в реализациях, полностью управляемых прерываниями. Следовательно, в этом случае нет смысла использовать DMA, и ваша первоначальная проблема, вероятно, решается не DMA, а переписыванием вашего (ISR, setup) кода.
@JimmyB ... спасибо ... однако из-за ответа Джонаса ниже я сделаю улучшение, чтобы прочитать столько байтов, сколько сообщение длинное. Я знаю это после получения первого байта (в большинстве случаев). Чем выгоднее будет использовать DMA вместо прерываний.

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

Действительно, может быть, просто на STM32 механизм прерывания (чистый без DMA) немного неуклюж по сравнению с прямым DMA.
@duskwuff Не совсем; вы можете опросить, чтобы увидеть, когда DMA будет выполнен, и вы вполне можете захотеть, потому что одна из ключевых причин использования DMA состоит в том, чтобы не беспокоиться о последовательном порте, пока ваша программа не находится в состоянии, когда она может действовать на полученный данные. Или для исходящего DMA вы можете просто опросить, чтобы узнать, можно ли добавить больше в буфер отправки.
@MichelKeijzers: IDK конкретного чипа, но обычно альтернативой DMA являются не буквально прерывания, а запрограммированный ввод-вывод (где вы используете инструкции ЦП для чтения/записи данных из/в регистр ввода-вывода). В обработчике прерывания вы, как правило, выполняете одно чтение, а затем, возможно, еще одно, в случае, если во время чтения первого вошел символ, особенно если это не вызовет другого прерывания. Или читать до тех пор, пока внутренний буфер не станет пустым, если такой буфер есть. Очевидно, вам нужно больше прерываний для PIO и настроить их по-другому.
@ChrisStratton Хороший вопрос ... до сих пор я не проверял, можно ли передавать, я просто что-то передаю, не проверяя, все ли в порядке. Возможно, если нет, я попробую позже.
@PeterCordes Кажется, у STM32 достаточно прерываний для DMA, и я каждый раз читаю только 1 байт. Даже самый простой STM32 (F103c8t6) имеет достаточно портов/прерываний DMA.
Чтение одного байта с помощью DMA имеет ограниченные возможности; вероятно, зарезервировано для случая, когда периферийное устройство не имеет регистра хранения (в отличие от большинства современных микроконтроллеров), поэтому полученный байт должен быть извлечен немедленно, чтобы освободить место для остальных, но процессор по какой-то причине не может обслуживать прерывание. Обратите внимание, что DMA по-прежнему может искажать синхронизацию основной программы, поскольку он вызывает арбитраж шины, если ЦП также необходимо получить доступ к ОЗУ или той же периферийной шине, хотя и не так сильно, как обслуживание прерывания.

Использование прямого доступа к памяти ставит некоторые интересные вопросы и проблемы, выходящие за рамки всех других соображений использования периферийных устройств UART. Я приведу несколько примеров: Предположим, что ваш UC подключен к шине RS485 (или любой другой) с другими устройствами. В шине много сообщений, некоторые предназначены для вашего УК, некоторые нет. Кроме того, предположим, что все эти соседи по шине говорят по разным протоколам данных, что означает, что длина сообщений различна.

Некоторые вопросы, которые возникают только при использовании DMA:

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

В любом случае, просто пища для размышлений.

Спасибо за эти соображения. В настоящее время я всегда получаю 1 байт и сохраняю его в кольцевом буфере, так как действительно мои сообщения (MIDI) могут иметь разную длину, и я не знаю, что я получу дальше. В моем основном цикле я проверяю полные сообщения для их обработки (и если они завершены, я удаляю их из кольцевого буфера). Таким образом, я всегда буду получать достаточно данных (если только я не пропущу байты, я должен это проверить). Мой буфер RX составляет всего 1 байт, но я копирую его в кольцевой/круговой буфер. Я не делал проверки, если он заполнен (нужно добавить).
Эй, не беспокойся. Я уверен, что ваше приложение будет хорошо запрограммировано. Как уже упоминалось, DMA великолепен, но не бесплатен. Это вводит в систему дополнительные соображения, которых не существует, если вы можете обойтись без их использования.
ну я надеюсь, я все еще новичок.

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

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

Я использовал STM32CubeMx/HAL в нескольких проектах и ​​обнаружил, что программное обеспечение для обработки UART, которое он генерирует, имеет определенные недостатки на приемной стороне.

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

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

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

Я делаю точно так же (я думаю). Я читаю по 1 байту за раз и сохраняю его в циклическом буфере, и я собираюсь проверить в основном цикле полные сообщения. Хотя можно немного усилить.
Как вы думаете, я могу столкнуться с проблемой, что настройка DMA каждый раз будет перегружать мой процессор/отсутствующие символы на скорости 31 250 бод?
Если вы настроите DMA на передачу нескольких символов за раз, это не будет проблемой. У меня 4 UART с 115200 и выше и I2C с использованием DMA без проблем. Все передачи UART составляют ~ 20 байт или больше. Проблема заключалась в использовании DMA для приема на UART (процессор L4 на частоте 80 МГц, 9600 бод).
В настоящее время я устанавливаю его на 1 байт за раз, но я могу улучшить его (сделав первый байт, а затем проверив, сколько дополнительных байтов необходимо).