Перейти к содержанию
    

Лютый баг в DMA STM32F4: кто-нибудь в курсе?

Всех приветствую!

Задумал я для одного проекта на STM32F405 реализовать полуаппаратный FIFO на DMA для UART-модуля, причем как на прием, так и на передачу.

Скрытый текст

Для приемного FIFO соответствующий канал DMA настраивается в круговой режим (circular mode). Под сам буфер в ОЗУ выделяется, например, 8 кБ. В формате транзакций 115200-8-N-1 этот буфер заполнится за ~0.7 секунды. Теперь в каком-нибудь периодическом процессе, например, в прерывании таймера (и хотя бы в 2 раза чаще от тех ~0.7 секунд), опираясь на счетчик NDTR DMA-канала, двигается указатель записи. Пусть, для примера, этот указатель меняется раз в 1 мс (от этого зависит скорость обнаружения входящего потока символов в FIFO). Процесс, который ждет данные из очереди, как и обычно, оценивает наличие данных в буфере по разности указателей записи и чтения, и при необходимости, читает, двигая указатель чтения. Тут главное "не тупить" и вовремя читать буфер, иначе указатель записи снова перепрыгнет указатель чтения и целостность очереди нарушится. Можно, конечно, попробовать перенастраивать DMA на количество свободных элементов в буфере, но из-за отсутствия FIFO в самом UART можно потерять символы, да и не кольцо это получится, т.к. DMA не очень умеет корректно прерываться с дальнейшим продолжением с указанного места.

Да и ладно, не суть.

Скрытый текст

Передающий же FIFO должен быть построен по принципу "несколько писателей -> один читатель" (чтобы можно было из разных потоков/прерываний швырять данные в очередь для отправки по UART). Пока что идея в том, чтобы завести несколько указателей записи: первый (указатель следующей позиции для записи) прямо сразу в начале функции write(buf, len) эксклюзивно (атомарно) двигается на len, чтобы, например, вклинившееся далее (но внутри write(), т.к. хрен знает сколько ей данных копировать - авось много) прерывание могло тоже записать в FIFO. Второй указатель записи (указатель готовых для чтения данных) двигается только в конце write(), причем с учетом первого указателя (вдруг его уже кто-то сместил?). Где-то тут тоже дается команда на старт нужного канала DMA.

В общем ладно, тоже не суть - детали реализации.

И тут я нахожу ссылки (раз, два) и выпадаю в осадок. NDTR, вопреки указанному в RM поведению, изменяется тогда, когда транзакция peripheral-to-memory лишь начата, но не факт, что закончена. Из-за этого (и это подтверждает человек в комьюнити) наблюдается ситуация, когда NDTR уже изменился, а данные в нужной ячейке ОЗУ еще не появились. Т.е. полагаться на NDTR для приемного FIFO, чтобы правильно двигать указатель записи, как бы и нельзя... Кто-то сталкивался с этим? Это реально баг такой или там в комьюнити что-то путают?:unknw::negative:

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

1 час назад, Arlleex сказал:

Для приемного FIFO соответствующий канал DMA настраивается в круговой режим (circular mode). Под сам буфер в ОЗУ выделяется, например, 8 кБ. В формате транзакций 115200-8-N-1 этот буфер заполнится за ~0.7 секунды. Теперь в каком-нибудь периодическом процессе, например, в прерывании таймера (и хотя бы в 2 раза чаще от тех ~0.7 секунд), опираясь на счетчик NDTR DMA-канала, двигается указатель записи. Пусть, для примера, этот указатель меняется раз в 1 мс (от этого зависит скорость обнаружения входящего потока символов в FIFO). Процесс, который ждет данные из очереди, как и обычно, оценивает наличие данных в буфере по разности указателей записи и чтения, и при необходимости, читает, двигая указатель чтения. Тут главное "не тупить" и вовремя читать буфер, иначе указатель записи снова перепрыгнет указатель чтения и целостность очереди нарушится. Можно, конечно, попробовать перенастраивать DMA на количество свободных элементов в буфере, но из-за отсутствия FIFO в самом UART можно потерять символы, да и не кольцо это получится, т.к. DMA не очень умеет корректно прерываться с дальнейшим продолжением с указанного места.

Мой драйвер UART для STM32F4 работает немного по-другому. Не в циклическом, а в режиме двойного буфера. У меня весь буфер состоит из N частей по UART_RDMA_CHUNK байт каждая. Я запускаю DMA для текущей части размером UART_RDMA_CHUNK, и также прописываю указатель на следующий кусочек UART_RDMA_CHUNK. В прерывании завершения предыдущего кусочка, спокойно перемещаю указатель записи и вычисляю оставшийся свободный размер и положение следующего сегмента UART_RDMA_CHUNK в буфере (и есть ли он). В это время поток продолжает приниматься. Поэтому ничего не может переполниться или перепрыгнуть. Переполнение буфера обнаруживается и обслуживается корректно (при необходимости запускаю DMA для очередного UART_RDMA_CHUNK не в основной буфер, а в отдельную "мусорку").

 

1 час назад, Arlleex сказал:

И тут я нахожу ссылки (раз, два) и выпадаю в осадок. NDTR, вопреки указанному в RM поведению, изменяется тогда, когда транзакция peripheral-to-memory лишь начата, но не факт, что закончена. Из-за этого (и это подтверждает человек в комьюнити) наблюдается ситуация, когда NDTR уже изменился, а данные в нужной ячейке ОЗУ еще не появились. Т.е. полагаться на NDTR для приемного FIFO, чтобы правильно двигать указатель записи, как бы и нельзя... Кто-то сталкивался с этим? Это реально баг такой или там в комьюнити что-то путают?:unknw::negative:

Не знаю. Не наблюдал такого. Мой драйвер работает с DMDIS=0 для приёмного потока. И при этом - всё ок. В проекте 3 UART. Все работают с DMA на приём и передачу (на передачу DMDIS=1). Все могут работать одновременно. Один из UART-ов работает на 1843200 бод (подключен ESP8266). Девайс работает давно уже (не первый год), каждый день по многу часов, иногда и сутки напролёт. Проблем не замечал.

Хотя возможно, что они просто не проявляются. Так как NDTR обрабатывается и указатель по нему двигается в ISR, а символы из очереди читаются уже в задаче ОС (в ISR - нет). К тому моменту когда выполнение доходит до задачи, символы вполне могут успеть записаться в ОЗУ (из FIFO DMA).

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

32 минуты назад, jcxz сказал:

К тому моменту когда выполнение доходит до задачи, символы вполне могут успеть записаться в ОЗУ (из FIFO DMA).

Вот я об этом тоже подумал, что по сути только это и может спасти...

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

1 минуту назад, Arlleex сказал:

Вот я об этом тоже подумал, что по сути только это и может спасти...

Для надёжности можете в периодическом прерывании, обнаружив изменение NDTR от предыдущего состояния, брать не всю величину изменения, а без последнего символа (брать NDTR+1). И только если между двумя периодическими ISR NDTR не изменился - принимать теперь уже NDTR как есть. Если период периодического прерывания == 1мсек, то за 1 мсек символ всяко должен слиться в ОЗУ.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

10 часов назад, jcxz сказал:

Мой драйвер UART для STM32F4 работает немного по-другому. Не в циклическом, а в режиме двойного буфера...

Понял. Хороший механизм. Двойной буфер тоже, кстати, работает в circular mode. Но это мелочи уже. А как у Вас обрабатываются "хвосты", когда буфер не заполнился на UART_RDMA_CHUNK байт, а движухи по UART-у уже и нет? Ведь прерывание по окончанию передачи не придет. Тоже в периодическом таймере мониторите?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

1 час назад, Arlleex сказал:

Ведь прерывание по окончанию передачи не придет. Тоже в периодическом таймере мониторите?

В прерывании IDLE останавливаю ПДП, перенастраиваю его на остаток буфера и посылаю сигнал готовности данных. Работет на F0, на F4 не проверял.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

1 час назад, Arlleex сказал:

Тоже в периодическом таймере мониторите?

Да, также как у Вас.

45 минут назад, Сергей Борщ сказал:

В прерывании IDLE останавливаю ПДП, перенастраиваю его на остаток буфера и посылаю сигнал готовности данных. Работет на F0, на F4 не проверял.

IDLE от UART? На F4 его нет, насколько помню.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

51 минуту назад, Сергей Борщ сказал:

В прерывании IDLE останавливаю ПДП, перенастраиваю его на остаток буфера и посылаю сигнал готовности данных...

А нет опасений, что пока DMA перенастраиваете, вновь прилетевший символ может потеряться? Ведь FIFO на UART-периферии нету...

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Вообще - принудительная остановка ПДП - так себе решение. Имхо. Так как часто это вызывает проблемы. Стараюсь никогда не использовать.

И в мануалах часто не рекомендуют принудительно останавливать ПДП.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Если вообще априори не известно, когда могут прилетать символы, да еще и не имея аппаратный FIFO на самом UART, то да, могут быть баги...

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Это насколько я понимаю связано с тем, что при принудительной остановке, в FIFO DMA могут остаться данные, и нужно ждать пока они не вылетят в ОЗУ. Ждать в ISR - не очень хорошая идея.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

7 минут назад, Arlleex сказал:

А нет опасений, что пока DMA перенастраиваете, вновь прилетевший символ может потеряться?

Волков бояться... FIFO есть, но на 1 байт (собственно Receive data register), то есть на перенастройку ПДП у нас есть время чуть меньше приема двух байтов плюс остаток той паузы, которая вызвала IDLE. Если сильно страшно - можно прерыванию IDLE дать приоритет повыше.

22 минуты назад, jcxz сказал:

IDLE от UART? На F4 его нет, насколько помню.

Ну не знаю - у моего есть:
image.thumb.png.0909169294a18f898c991c0cf3c997c2.png

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

10 минут назад, Сергей Борщ сказал:

Волков бояться... FIFO есть, но на 1 байт (собственно Receive data register), то есть на перенастройку ПДП у нас есть время чуть меньше приема двух байтов плюс остаток той паузы, которая вызвала IDLE. Если сильно страшно - можно прерыванию IDLE дать приоритет повыше.

Времени есть только чуть больше одного символа. Так как когда вошли в ISR, может как раз приниматься стоп-бит нового символа. И потом остаётся чуть больше одного символа до переполнения.

И всем прерываниям "приоритет повыше" не дашь - портов и прерываний в системе может быть много - кому давать повыше? В любом случае кто-то будет ждать.

Цитата

Ну не знаю - у моего есть:
image.thumb.png.0909169294a18f898c991c0cf3c997c2.png

Может быть. Не использую его.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

3 минуты назад, jcxz сказал:

Времени есть только чуть больше одного символа. Так как когда вошли в ISR, может как раз приниматься стоп-бит нового символа.

Это прерывание IDLE, то есть была пауза и ничего там не принимается.

4 минуты назад, jcxz сказал:

И всем прерываниям "приоритет повыше" не дашь

Да все уже поняли, что ваше решение единственно правильное.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Только что, Сергей Борщ сказал:

Это прерывание IDLE, то есть была пауза и ничего там не принимается.

Ещё раз: прерывание IDLE; задержка входа в ISR (из-за других ISR или запретов прерываний); за время задержки начался приём нового символа; вход в ISR (приём нового символа ещё не закончен - как раз принимается его стоп-бит); ...

Так сколько времени пройдёт от входа в ISR до переполнения UART?

 

Только что, Сергей Борщ сказал:

Да все уже поняли, что ваше решение единственно правильное.

Видимо Вы знаете решение. Когда в проекте есть скажем 5-6 UART. Так поделитесь им с нами, вашим, правильным решением!

Как всем им одновременно дать приоритет повыше? А если ещё какому-то ISR тоже нужен "повыше" - как быть?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.

Гость
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...