Как настроить DMA для получения сообщений USART переменной длины?

Я пытаюсь получить сообщения USART на микроконтроллере, исходящие от ПК, которые будут приказывать ему выполнять определенные тесты. Я использую STM32F4, я решил использовать DMA, поскольку сообщения на том же USART, исходящие от микроконтроллера, должны иметь скорость 2000000 бит/с, поскольку я не мог заставить его работать с более низким битрейтом или без DMA. Я использую двоичный код, а не ASCII для обоих направлений.

То, что я разработал до сих пор, «работает» (обновляется почти в реальном времени) при условии, что скорость отправки сообщений постоянна, поскольку каждое новое сообщение заставляет предыдущее сбрасываться из регистра DMA. Однако при запуске и отправке только одной инструкции/кадра USART, который оказывается меньше объема данных, которые должны быть получены для HAL_UART_Receive_DMA(), сообщение застревает до тех пор, пока не будет вытеснено следующим сообщением.

введите описание изображения здесь

Еще худший сценарий был бы, если бы USART_take_sizeон был равен 100, а в буфере DMA был бы один кадр размером 10 байт. В этом случае мне нужно будет подождать еще 9 (10 байт) кадров, прежде чем получить их все сразу.

Вот как сейчас выглядит мой обратный вызов.

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
    
    //              uart handle, global array, numb of bytes to trigger interrupt       
    HAL_UART_Receive_DMA (huart, raw_serial, USART_take_size);
    
    // function that adds received bytes to the circular buffer 
    AppendSerial(raw_serial,USART_take_size);
            
}

Поток извлекает байты из циклического буфера, перебирает их и на основе байтов SOF и EOF извлекает сообщение внутри кадра.

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

Я вижу три способа решения этой проблемы, и каждый из них не кажется правильным:

  1. Установите USART_take_sizeна 1, что побеждает весь смысл прямого доступа к памяти.

  2. Иметь фиксированный размер кадра и заполнять содержимое нулями перед EOF, если содержимое меньше, чем USART_take_size. Это только кажется неряшливым и расточительным. Он также должен запускаться в каждом кадре, даже если общая скорость в этом случае может быть намного выше.

  3. Настройте DMA для запуска ISR для переменного количества полученных байтов, при условии, что перед этим было определенное количество бездействий.

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

Какой микроконтроллер? Есть ли на нем FIFO? Есть ли справочный код или заметки по применению, из которых вы можете взять?
Используя STM32, решить эту проблему довольно сложно. Вам действительно нужно использовать DMA?
@ pjc50 это STM32F407, и у DMA есть два регистра, один для половинного обратного вызова. Я спасаю код в основном от других, я не мог найти код от AN, который бы соответствовал моим потребностям.
@Peter Smith, почему STM32 труднее для него, чем другие микроконтроллеры? Мне нужно собирать частые сообщения, и я могу заставить их отправляться на ПК только с DMA на 2000000. Потенциально можно попробовать даже более высокий битрейт, но тогда будет использоваться еще больше тактов, если использовать без DMA, и мне нужен запас для другой логики используется для управления другими периферийными устройствами, которые еще не созданы.
(В ответ): некоторые микроконтроллеры могут генерировать прерывание приемника по запрограммированному «специальному символу» (который может быть любым), но серия STM32 этого не делает. В таких ситуациях я всегда могу знать, когда конкретное сообщение завершено.
@PeterSmith, если бы они были, это означало бы, что мне пришлось бы убедиться, что ни одно из управляющих сообщений на самом деле не имеет этого специального символа, я думаю, в SOF / EOF нет необходимости, верно?
@PeterSmith STM32 UART имеет символьное прерывание. Я использую его именно для этой цели вместе с DMA. Это называется сопоставлением символов и разделяет регистры с сопоставлением адресов.

Ответы (2)

У меня была похожая проблема в одном из моих проектов. Я решил эту проблему, создав достаточно длинный буфер для хранения максимально длинного сообщения и используя DMA для передачи из USART. Чтобы определить конец сообщения, я использовал прерывание обнаружения незанятой линии, которое срабатывает, когда приемник USART прекращает прием данных.

То же самое здесь, обнаружение линии IDLE работает как шарм с DMA. Пришлось добавить поддержку этого в библиотеку STM HAL. Как только срабатывает прерывание IDLE, я отключаю RX DMA и снова включаю его с другим буфером.

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

Я действительно могу думать только о двух вариантах вашей проблемы:

  1. Просто обработайте каждый байт в ISR приема USART. У STM32 довольно низкие накладные расходы на прерывания. Если ваше приложение может справиться с этим, то иногда лучше использовать простое, даже если оно может быть не таким элегантным.

  2. Вы можете легко настроить DMA для работы в качестве буфера FIFO. DMA загрузит байты в буфер, а затем вы установите периодическое прерывание по таймеру для проверки буфера на наличие данных и разбора сообщений. Это промежуточный подход по сравнению с вариантом 1, поскольку он позволяет уменьшить накладные расходы на прерывания за счет задержки. Вы можете настроить период опроса таймера в зависимости от размера FIFO, чтобы сбалансировать свои конкретные потребности в обработке в реальном времени. Есть также трюки, которые вы можете проделать с триггерами уровня DMA, хотя полезность этого будет зависеть от формата вашего сообщения.

№2 наверное лучший вариант. Не нужно было бы даже использовать таймер. Можно просто позволить DMA FIFO данные и синхронно опросить буфер FIFO в какой-то момент во время выполнения программы, где это уместно. Или можно запустить таймер только при приеме первого символа, затем слить буфер и остановить таймер, когда таймер истечет.
@JimmyB Опорожнение буфера, скорее всего, просто пометит, что теперь в него можно снова записать, возможно, путем перезапуска прямого доступа к памяти или перемещения индекса чтения/записи обратно в начало. Я синхронно опрашиваю флаг для анализа, установленный завершением DMA или прерываниями совпадения символов, чтобы узнать, когда получено полное сообщение. Таймеры слишком ценны, чтобы тратить их впустую.
@Toor Под «сливом» я имел в виду «извлечение и обработку любых и всех данных в буфере», например, анализ любых (частичных) уже полученных сообщений.