Jump to content
    

STM32F4xx. Последовательность нескольких SPI-транзакций без участия CPU.

Пытаюсь на дохлом STM32F401 (SPI-мастер, обычный 4-проводный режим = SCLK/MOSI/MISO/CS) реализовать последовательность из нескольких транзакций по шине SPI со слэйвом. 

Одна транзакция: 1) CS=0; 2) передача/приём 16 бит; 3) CS=1; 4) пауза заданной длительности (например CS_HIGH_TIME=200нс). Всё просто и типично, ничего экстравагантного.

За этой транзакцией сразу идёт 2-я, 3-я и т.п. - несколько штук непрерывно. И только в конце - прерывание к CPU. CPU должен только изначально всё проинициализировать (SPI, DMA, таймеры, etc.), запустить эту последовательность и идти заниматься другими своими делами, не дёргаясь много раз в ISR. И только после завершения всей последовательности получить уведомление (прерывание), войти в ISR и обработать результаты. Исходные данные для SPI.TX - в буфере ОЗУ, результаты SPI.RX - также в буфере в ОЗУ.

 

На XMC4xxx такое делается элементарно. Можно даже без DMA и таймера - с помощью возможностей одного только SPI-интерфейса. Но как это сделать на STM32F4?

Попробовал следующий вариант (1 SPI, 3 DMA канала, 1 таймер):

SPI программирую с CR2.RXDMAEN=1, CR2.TXDMAEN=0. Таймер инициализирую в однократный режим: 1-й его COMPARE-регистр (значение == 1) формирует CS=1 (роль CS выполняет один из выходов таймера) и DMA-запрос для 2-го DMA-канала; 2-й его COMPARE-регистр (значение на CS_HIGH_TIME позже 1-го COMPARE) формирует CS=0 и DMA-запрос для 3-го DMA-канала. Таймер генерит CS-сигнал в режиме ШИМ: при CNT<1 - CS=0, при CNT>=1 - CS=1. Исходное значение CNT>=ARR.

Должно работать так:

  1. После инициализации всего, CPU сбрасывает таймер (EGR=1) и записывает передаваемое слово в SPI_DR. На этом роль CPU заканчивается до получения завершающего IRQ. 
  2. Таймер обнуляется (становится CS=0). По SPI стартует передача/приём слова.
  3. По завершении SPI-транзакции, генерится DMA-запрос SPI.RX.
  4. По этому запросу 1-й DMA-канал записывает в CR1 таймера слово, запускающее его в режим однократного счёта (OPM=1). Приоритет (DMA_stream.CR.PL) - ниже максимального.
  5. По достижению счётчиком таймера значения ==1, генерится DMA-запрос к 2-му DMA-каналу и становится CS=1.
  6. 2-й DMA-канал выполняет чтение полученного слова из SPI_DR в ОЗУ. Приоритет этого чтения - максимальный: DMA_stream.CR.PL=3.
  7. По достижению счётчиком таймера значения ==1 + CS_HIGH_TIME, генерится DMA-запрос к 3-му DMA-каналу и становится CS=0.
  8. 3-й DMA-канал выполняет запись следующего передаваемого слова в SPI_DR из ОЗУ. Приоритет (DMA_stream.CR.PL) - ниже максимального.
  9. ... и т.д.

От п.9 и далее пока не реализовывал - пока не работает как нужно эта единственная транзакция. В этой последовательности работает почти всё кроме чтения слова из SPI в п.6 и записи нового слова в п.8. Причём - по статусу DMA-каналов (2-го и 3-го) выглядит как будто они свою работу выполнили (регистр NDTR становится ==0 и CR.EN становится ==0), но считанного значения в ОЗУ не появляется и по статусу SPI_SR.RXNE=1 видно, что его чтения не было. Также не видно записи нового слова 3-м DMA-каналом в SPI. Если далее прочитать с помощью или CPU или отладчика SPI_DR, то принятое значение нормально считывается.

Что видно на шине SPI:

image.thumb.png.e6097502ea58f32667e91d46c6b34056.png

Работа CPU (п.1) заканчивается примерно около вертикальной красной линии. Далее видно, что осциллограмма сигналов получилась какая нужно. Но, из-за того что чтение и запись SPI_DR через DMA реально не были выполнены, то дальнейшая работа невозможна.  :sad:

Результирующее содержимое DMA-каналов:

image.thumb.png.60749ecbcbaa8dea40dd79107928030c.png

выглядит так, как будто все запланированные операции выполнены успешно. Но это не так.

Перепробовал разные вариации настроек SPI, DMA, таймера - безрезультатно. Всё биты разрешения всевозможных уведомлений-прерываний об ошибках от SPI и DMA - все разрешены. Но ни одна ошибка не возникла.

 

Вопрос: Кто-нить реализовывал что-то подобное на STM32F4 (или других STM32 с аналогичной периферией)? Возможно ли такое в принципе?

На протяжении от старта этой последовательности до её полного завершения (завершающего IRQ), CPU не должен быть задействован никак.

Share this post


Link to post
Share on other sites

может четыремя каналами DMA, просто по времени, от трех СС таймера:

по первому (СС=0) писать в регистр GPIO или SPICR для CS->0,

по второму (СС=1, CS_SETUP_TIME) писать в SPIDR для начала передачи,

по третьему (СС=1 + 16.5*Tspiclk) CS->1.

с периодом таймера 16.5 * Tspiclk + CS_HIGH_TIME

а на приём SPIRX DMA справится сам.

Share this post


Link to post
Share on other sites

34 минуты назад, _pv сказал:

может четыремя каналами DMA, просто по времени, от трех СС таймера:

по первому (СС=0) писать в регистр GPIO или SPICR для CS->0,

по второму (СС=1, CS_SETUP_TIME) писать в SPIDR для начала передачи,

по третьему (СС=1 + 16.5*Tspiclk) CS->1.

"Просто по времени" - негодный вариант. Так как DMA-транзакции обслуживаются не мгновенно. Из-за этого будет джиттер, тем больший, чем более занята шина. А значит возможны и промахи активности SPI за пределы CS=0, и возможна болтанка длительностей CS=0 и CS=1. Придётся делать километровые запасы везде, чтобы более-менее стабильно работало.

Мой алгоритм (приведённый выше) так построен, что не чувствителен к загрузке шины и задержкам DMA-транзакций. Он обеспечивает точную длительность CS=1 и гарантированное попадание всех SPI-активностей внутрь CS=0. Ещё и даёт возможность выставить желаемое минимальное расстояние от CS=fall до первого SCLK и от последнего SCLK до CS=rise. Т.е. - почти как в нормальных, серьёзных контроллерах, с хорошей реализацией SPI-периферии.

Главная проблема - это событие SPI.RX. Которое почему-то криво работает, если обслуживающая её DMA-транзакция выполняет не собственно чтение SPI_DR, а нечто иное. Если выключить CR2.RXDMAEN=1, то слово читается DMA-каналом из SPI_DR, но не генерится DMA-запрос на это; если его включить, то DMA-запрос генерится, но слово не читается по какой-то причине... :sad:

Может я где-то накосячил и не вижу очевидного? Потому и спрашиваю - делал ли кто-то нечто подобное?

Share this post


Link to post
Share on other sites

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

расстояние от CS=fall до первого SCLK и от последнего SCLK до CS=rise.

Это умеет делать например H7. Всё-таки, F401 - очень старый микроконтроллер, ему уже более 10 лет.

Share this post


Link to post
Share on other sites

11 минут назад, EdgeAligned сказал:

Это умеет делать например H7. Всё-таки, F401 - очень старый микроконтроллер, ему уже более 10 лет.

Нужно на STM32F4xx. А насчёт "старого" - XMC4xxx умеют так работать. И насколько помню - разные LPC и Tiva - тоже умеют. А они не менее старые.

Даже на древнем TMS320VC5502 (который тут обсуждается в одной из тем), тоже возможно организовать работу так. А ему уже ~20 лет.

Share this post


Link to post
Share on other sites

CS можно таймером и без ДМА сделать. Но сам старт передачи да, будет с джиттером, с другой стороны старт всё равно к "низкочастотным" клокам SPI привязан, то есть "километровый запас" - это по периоду sclk до и после.

А чтобы железно всё синхронизовать есть ещё более упоротый вариант spi переключить в слэйв, а sclk генерить вторым таймером. Но боюсь внутри это всё завернуть друг на друга в stm32f401 не получится.

Share this post


Link to post
Share on other sites

58 минут назад, _pv сказал:

CS можно таймером и без ДМА сделать. Но сам старт передачи да, будет с джиттером, с другой стороны старт всё равно к "низкочастотным" клокам SPI привязан, то есть "километровый запас" - это по периоду sclk до и после.

Этого слишком мало. Вы видимо никогда такое не реализовывали. А я реализовывал: при запасе всего в 1 SCLK будут гарантированные вылеты за пределы CS=0. Иногда запаса даже в 5-10 SCLK бывает недостаточно. На частотах SCLK=~9...10МГц.

О хочется, чтобы работало гарантированно, а не "как фишка ляжет".

58 минут назад, _pv сказал:

А чтобы железно всё синхронизовать есть ещё более упоротый вариант spi переключить в слэйв, а sclk генерить вторым таймером.

И как же опять синхронизировать сигналы таймера с пересылками по шине? Опять будут те же проблемы.

Share this post


Link to post
Share on other sites

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

На XMC4xxx такое делается элементарно. Можно даже без DMA и таймера - с помощью возможностей одного только SPI-интерфейса.

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

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

Одна транзакция: 1) CS=0; 2) передача/приём 16 бит; 3) CS=1; 4) пауза заданной длительности (например CS_HIGH_TIME=200нс). Всё просто и типично, ничего экстравагантного.

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

Share this post


Link to post
Share on other sites

2 hours ago, jcxz said:

Этого слишком мало. Вы видимо никогда такое не реализовывали. А я реализовывал: при запасе всего в 1 SCLK будут гарантированные вылеты за пределы CS=0. Иногда запаса даже в 5-10 SCLK бывает недостаточно. На частотах SCLK=~9...10МГц.

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

2 hours ago, jcxz said:

И как же опять синхронизировать сигналы таймера с пересылками по шине? Опять будут те же проблемы.

spi в режиме слэйва сам будет дма дергать, просто ему клоки и CS железно генерят таймеры, но там и ноги местами поменяются и клоки изнутри не подать, а вытаскивать на разные пины.

Share this post


Link to post
Share on other sites

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

Share this post


Link to post
Share on other sites

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

Такое нет. Но в микросекундные задержки т.е. сотню тактов верится с трудом.

Мне тоже так думалось, до того как попробовал реализовать такую работу в позапрошлом проекте. Попробуйте тоже - удивитесь. Пришлось ставить километровые gap-ы после CS=0 и перед CS=1. Иначе изредка глючило.

Если найду осциллограммы из того проекта - выложу.

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

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

На STM32F401 микросекунда это не сотня тактов, а максимум = 84. А в моём случае = 72. Это во-первых, во-вторых: только вход в прерывание на CM4F может занимать до ~40 тактов. А если предположить, что первыми инструкциями ISR идут: PUSH {R3-R11,LR}, VPUSH {много регистров}? А если ещё предположить, что этот вход в прерывание случился сразу после пакетной DMA-транзакции, занимавшей до того шину? То всё это время шина будет занята.

А если предположить, что вход в прерывание случился сразу после выхода из другого ISR? Ведь приоритет доступа CPU к шине - максимальный.

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

spi в режиме слэйва сам будет дма дергать, просто ему клоки и CS железно генерят таймеры

Для такого потребуется несколько таймеров: один для генерации CS, другой - для SCLK, потом ещё как-то надо дырки генерить и координировать все их в целом. С учётом того, что на этом SPI у меня не один ведомый, то будет это весьма громоздко. Чё-то не хочется так сильно нагромождать.  :mda:

44 минуты назад, amaora сказал:

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

Не понял... А где эти ограничения описаны? Где уточнять?

Если так, то это вполне может быть причиной.

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

Вообще-то обычный порт только передает данные без всяких непонятно зачем вставленных задержек

Смотрю ваши темы, и откуда вы такие экзотичные ТЗ берете

Не "задержка", а "пауза". Если когда-нить попробуете почитать даташиты на различные чипы - SPI-ведомые, то тогда может и узнаете зачем эти паузы.

Share this post


Link to post
Share on other sites

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

Не понял... А где эти ограничения описаны? Где уточнять?

А какие номера физических периферийных таймеров и SPI используете?

Да, не все DMA умеют лезть в любую память. Ограничения нарисованы на картинках в RM:

image.thumb.png.d0542986b9801756184a9d90553e5c7a.png

Share this post


Link to post
Share on other sites

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

SPI-ведомые, то тогда может и узнаете зачем эти паузы.

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

Share this post


Link to post
Share on other sites

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

А какие номера физических периферийных таймеров и SPI используете?

SPI1.RX - генерит DMA-запрос, по которому происходит копирование слова включения таймера в TIM3.CR1. Это выполняет DMA2.STREAM2. Это выполняется нормально.

Далее - TIM3 стартует и кроме фронтов/спадов на выходе TIM3_CH3, генерит 2 DMA-запроса: на DMA1.STREAM7 и на DMA1.STREAM2. Соответственно - для чтения принятого слова из SPI1_RX и записи нового передаваемого слова в SPI1_RX. Эти stream-ы судя по их регистрам отрабатывают нормально, но самой операции чтения и записи - не выполняется. И никаких флагов ошибок тоже не взводится.

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

Да, не все DMA умеют лезть в любую память. Ограничения нарисованы на картинках в RM:

Получается - DMA1 не может обращаться к APB2-периферии? Судя по картинке. У меня как раз такое обращение и происходит.  :mda:

Странно, что в RM0368 это явно никак не указано (кроме не очень вызывающей доверие картинки). А в параграфе 9.3.6 RM0368 указано: "Both source and destination transfers can address peripherals and memories in the entire 4 GB area, at addresses comprised between 0x0000 0000 and 0xFFFF FFFF.". Т.е. - вроде как не должно быть ограничений по адресам.

 

Если следовать картинке, то надо попробовать заменить TIM3 на TIM1. Это возможно по схеме. Заменить SPI1 на SPI2 или SPI3 - к сожалению не получится.

Спасибо! попробую. Я кстати и хотел вчера попробовать заменить TIM3, на TIM1. Но времени не хватило. Из-за того, что много потерял, пытаясь разобраться с причинами неработы моей схемы.

 

 

PS: Кстати - кроме приведённого в 1-м посте алгоритма, также пробовал один DMA-запрос подавать на два разных DMA-stream-а. Например: по запросу SPI1.RX одновременно двумя разными DMA-stream-ами и копировать слово из SPI1_DR и записывать слово в TIM3_CR1. Но так тоже не работало - только один запрос из двух отрабатывал. Второй DMA-stream оставался несработавшим. Как будто и вовсе запрос не получил. Срабатывал всегда самый приоритетный из двух stream-ов.

Share this post


Link to post
Share on other sites

А еще вопрос, что пишете в DMAStream->NDTR для передающего и принимающего каналов SPI? И в каком режиме они настроены? Память-память, там, или память-периферия/периферия-память?

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.

×
×
  • Create New...