Jump to content

    
Sign in to follow this  
Arlleex

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

Recommended Posts

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

Задумал я для одного проекта на 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:

Share this post


Link to post
Share on other sites
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).

Share this post


Link to post
Share on other sites
32 минуты назад, jcxz сказал:

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

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

Share this post


Link to post
Share on other sites
1 минуту назад, Arlleex сказал:

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

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

Share this post


Link to post
Share on other sites
10 часов назад, jcxz сказал:

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

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

Share this post


Link to post
Share on other sites
1 час назад, Arlleex сказал:

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

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

Share this post


Link to post
Share on other sites
1 час назад, Arlleex сказал:

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

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

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

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

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

Share this post


Link to post
Share on other sites
51 минуту назад, Сергей Борщ сказал:

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

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

Share this post


Link to post
Share on other sites

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

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

Share this post


Link to post
Share on other sites

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

Share this post


Link to post
Share on other sites
7 минут назад, Arlleex сказал:

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

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

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

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

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

Share this post


Link to post
Share on other sites
10 минут назад, Сергей Борщ сказал:

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

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

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

Цитата

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

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

Share this post


Link to post
Share on other sites
3 минуты назад, jcxz сказал:

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

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

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

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

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

Share this post


Link to post
Share on other sites
Только что, Сергей Борщ сказал:

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

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

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

 

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

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

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

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

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Sign in to follow this