STM32 SPI и DMA. Настройка и запуск в STM32CubeMx.

SPI и DMA

Продолжаем развивать тему STM32CubeMx! Речь в этой статье пойдет о совместном использовании модулей STM32 SPI и DMA.

Давайте чуть усложним задачу и задействуем 4 модуля SPI, ну и, соответственно, нам понадобятся 4 канала DMA, по одному на каждый модуль SPI. Я буду использовать отладочную плату STM32F429Discovery, но никаких особенностей к процессу это не добавит, так что те, кто используют другую плату с другим микроконтроллером, просто не забывайте указать это при создании проекта (при выборе МК).

Проект мы создали, теперь выбираем и активируем нужные нам модули:

Настройка SPI

Cube сразу же “забронировал” 12 выводов контроллера для сигналов SPI:

  • SPI1_SCK
  • SPI1_MOSI
  • SPI1_MISO
  • SPI2_SCK
  • SPI2_MOSI
  • SPI2_MISO
  • SPI3_SCK
  • SPI3_MOSI
  • SPI3_MISO
  • SPI4_SCK
  • SPI4_MOSI
  • SPI4_MISO

Переходим на вкладку Configuration:

И выбираем SPI1. В появившемся окне нас интересует пункт DMA Settings. Там мы можем, нажав на кнопку Add, добавить использование DMA для приема и передачи данных по SPI. Давайте для примера ограничимся передачей:

Настройка DMA

И, кроме того, давайте включим глобальное прерывание DMA, это нам понадобится для того, чтобы понимать, когда можно запускать новую отправку:

Использование прерываний

Больше никакие настройки для SPI1 менять не будем. Понятно, что в зависимости от устройства, с которым предстоит наладить общение, необходимо также настраивать параметры передачи, такие как скорость и т. д. Теперь производим аналогичные действия для трех оставшихся модулей SPI и просим Cube сгенерировать для нас проект 🙂

Давайте рассмотрим некоторые из полученных функций на примере все того же модуля SPI1. Вот так выглядит инициализация непосредственно SPI:

void MX_SPI1_Init(void)
{
	hspi1.Instance = SPI1;
	hspi1.Init.Mode = SPI_MODE_MASTER;
	hspi1.Init.Direction = SPI_DIRECTION_2LINES;
	hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
	hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
	hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
	hspi1.Init.NSS = SPI_NSS_SOFT;
	hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8;
	hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
	hspi1.Init.TIMode = SPI_TIMODE_DISABLED;
	hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLED;
	HAL_SPI_Init(&hspi1);
}

Настройка нужного канала DMA осуществляется в функции HAL_SPI_MspInit():

hdma_spi1_tx.Instance = DMA2_Stream3;
hdma_spi1_tx.Init.Channel = DMA_CHANNEL_3;
hdma_spi1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
hdma_spi1_tx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_spi1_tx.Init.MemInc = DMA_MINC_ENABLE;
hdma_spi1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_spi1_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_spi1_tx.Init.Mode = DMA_NORMAL;
hdma_spi1_tx.Init.Priority = DMA_PRIORITY_VERY_HIGH;
hdma_spi1_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
HAL_DMA_Init(&hdma_spi1_tx);

__HAL_LINKDMA(hspi,hdmatx,hdma_spi1_tx);

Прерывания как обычно находятся в файле stm32f4xx_it.c (если вы используете другой контроллер, то и файл может называться иначе, к примеру, stm32f1xx_it.c). Как вы помните, мы активировали прерывания для каждого модуля DMA. Давайте добавим в нашу программу 4 переменные-флага, которые будут символизировать статус текущей отправки для каждого из используемых модулей. Добавим в main.c:

uint8_t completed1 = 1;
uint8_t completed2 = 1;
uint8_t completed3 = 1;
uint8_t completed4 = 1;

Если значение переменной равно 1, то передача завершена, если 0, то передача в данный момент продолжается. Таким образом, нам нужно перед отправкой обнулять флаг, а в прерывании по завершению передачи ставить его равным 1. Так и поступим:

Важное замечание!

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

  • окончание передачи
  • передача половины данных
  • ошибка передачи
  • ошибка Direct mode
  • ошибка FIFO

Для простоты примера мы эту проверку опускаем, но в “боевых” проектах обязательно проверяйте флаги!

Кроме того, можно использовать специально созданные разработчиками HAL callback-функции. Тут идея такая – в функции HAL_DMA_IRQHandler (определенной в библиотеке HAL) уже происходит проверка, каким именно событием вызвано прерывание, и в соответствии с этим будет вызвана соответствующая callback-функция. Таким образом, нам не потребуется проверять, какое именно событие вызвало прерывание, вручную.

/**
* @brief This function handles DMA2 Stream3 global interrupt.
*/
void DMA2_Stream3_IRQHandler(void)
{
	/* USER CODE BEGIN DMA2_Stream3_IRQn 0 */
	completed1 = 1;
	/* USER CODE END DMA2_Stream3_IRQn 0 */

	HAL_DMA_IRQHandler(&hdma_spi1_tx);

	/* USER CODE BEGIN DMA2_Stream3_IRQn 1 */

	/* USER CODE END DMA2_Stream3_IRQn 1 */
}


/**
* @brief This function handles DMA2 Stream1 global interrupt.
*/
void DMA2_Stream1_IRQHandler(void)
{
	/* USER CODE BEGIN DMA2_Stream1_IRQn 0 */
	completed4 = 1;
	/* USER CODE END DMA2_Stream1_IRQn 0 */

	HAL_DMA_IRQHandler(&hdma_spi4_tx);

	/* USER CODE BEGIN DMA2_Stream1_IRQn 1 */

	/* USER CODE END DMA2_Stream1_IRQn 1 */
}


/**
* @brief This function handles DMA1 Stream4 global interrupt.
*/
void DMA1_Stream4_IRQHandler(void)
{
	/* USER CODE BEGIN DMA1_Stream4_IRQn 0 */
	completed2 = 1;
	/* USER CODE END DMA1_Stream4_IRQn 0 */

	HAL_DMA_IRQHandler(&hdma_spi2_tx);

	/* USER CODE BEGIN DMA1_Stream4_IRQn 1 */

	/* USER CODE END DMA1_Stream4_IRQn 1 */
}


/**
* @brief This function handles DMA1 Stream5 global interrupt.
*/
void DMA1_Stream5_IRQHandler(void)
{
	/* USER CODE BEGIN DMA1_Stream5_IRQn 0 */
	completed3 = 1;
	/* USER CODE END DMA1_Stream5_IRQn 0 */

	HAL_DMA_IRQHandler(&hdma_spi3_tx);

	/* USER CODE BEGIN DMA1_Stream5_IRQn 1 */

	/* USER CODE END DMA1_Stream5_IRQn 1 */
}

Все приготовления завершены, давайте уже наконец-то что-нибудь отправим. Объявляем массив тестовых данных:

uint8_t testDataToSend[128];

Заполняем массив данными:

for (uint8_t i = 0; i < 128; i++)
{
	testDataToSend[i] = i + 1;
}

И вызываем функцию отправки, не забыв перед этим обнулить флаг готовности передачи:

completed1 = 0;
HAL_SPI_Transmit_DMA(&hspi1, testDataToSend, 128);

Передача данных через любой из оставшихся модулей SPI выглядит абсолютно аналогично:

completed2 = 0;
HAL_SPI_Transmit_DMA(&hspi2, testDataToSend, 128);

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

Как видите, все оказалось довольно просто, STM32CubeMx сгенерировал нужный нам код инициализации всех периферийных модулей, мы же дописали верхний уровень программы так, как нам это было нужно. На этом я с вами прощаюсь, до новых статей! 🙂

Поделиться!

Подписаться
Уведомление о
guest
39 Комментарий
старее
новее большинство голосов
Inline Feedbacks
View all comments
Роман
Роман
4 лет назад

Здравствуйте! Спасибо огромное за ваши полезнейшие уроки! Скажите, можно ли рассчитывать на подобный урок с hal+cube, с использованием I2C? Желательно с памятью.

Роман
Роман
4 лет назад

Буду ждать с нетерпением. Вот с памятью как раз и проблема, никак не могу заставить работать 24с02. функция HAL_I2C_Mem_Read(&hi2c1, 0x50, 0x00, 1, &receive, 1, 500); не пишет, а HAL_I2C_IsDeviceReady(&hi2c1, 0x50, 20, 500) == HAL_ERROR верно сразу.
прошу прощения за оффтоп, может вы это учтете или поможет кто-нибудь.

Роман
Роман
4 лет назад

уже нафлудил немало, в общем все работает, надо было адрес сместить на 1 влево, извиняйте)

Alexey
Alexey
4 лет назад

Да, спасибо огромное за материалы! Как раз сейчас с этим разбирался, прямо угадали!
Скажите, успели ли Вы поработать с STM32F7? Насколько они лучше F4 в плане DSP? Или еще сырой материал?

Sergey
Sergey
4 лет назад

Я пробовал STM32F7.
То же самое что и F4 по периферии. только есть кое где дополнительные режимы. Отличия в основном в ядре. Тесты показали, что ядро быстрее примерно в 1.5 раза при одинаковых частотах, благодаря кешу, который, кстати, нужно еще и включать.

Игорь
Игорь
4 лет назад

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

Алексей
Алексей
4 лет назад

Статья планируется с использованием DMA? Если да, то тоже буду ждать с нетерпением.

Lexa
Lexa
4 лет назад

Добрый день.
В этой статье рассмотрели SPI. Хочу коснуться вопроса про его спутника – интерфейс I2S.
Есть ли какая-то статья про его использование? И планируется ли она?

Сейчас осваиваю данный интерфейс и столкнулся с неожиданной проблемой при использовании DMA.

Мой пример такой: пытаюсь передать синус по I2S, используя полнодуплексный режим с ПДП. Плата STM32F3Discovery. Замкнул ножки I2S2_SD и I2S2_extSD. И пересылаю данные из одно буфера другой. При однократной передаче проблем не возникает, однако стоит добавить функцию HAL_I2SEx_TransmitReceive_DMA в бесконечный цикл и всё… Передача осуществляется только один раз, а если пробежаться по отладчику, то функция ничего не принимает и не передает и возвращает состояние HAL_BUSY.

Может надо как-то останавливать передачу после приема данных или же буфер-приемник каким-то образом очищать?

Lexa
Lexa
4 лет назад

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

Есть ещё вопрос по DMA. Обработчик прерывания в DMA, при использовании стандартной HAL функции, вызывается по окончании передачи в память целого буфера данных или его половины?

Автондил
Автондил
4 лет назад

у меня при передаче по UART через DMA возникает прерывание по приему – проверял флаг
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
int recieve = __HAL_UART_GET_IT_SOURCE(&huart1, UART_IT_RXNE);
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
if (recieve)
{
IndexInBuf1++;
HAL_UART_Receive_IT(&huart1, &Uart1InBuffer[IndexInBuf1], 1);
}
/* USER CODE END USART1_IRQn 1 */
}
и принимал неверный байт. При отправке по DMA разве должно быть прерывание по приему?

Автандил
Автандил
4 лет назад

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

Автандил
Автандил
4 лет назад

в общем нужно использовать HAL_UART_RxCpltCallback.

Builder
Builder
Reply to  Автандил
3 лет назад

Плюсую, для опрделения момента окончания приема нужно использовать callback функцию. У меня по варианту как в статье были тонкие проблемы при определенных ситуациях.

Marat
Marat
4 лет назад

Переменные completed не видны в файле stm32f4xx_it.c – они объявлены в main.c

Получается, их надо объявить в stm32f4xx_it.c с модификатором extern?

Марат
Марат
4 лет назад

пытаюсь реализовать подобное с Усартом. На прием без проблем, а на передача только один раз, потом прерывание не срабатывает. В чем может быть проблема?

Марат
Марат
Reply to  Aveal
4 лет назад

принимаю и передаю побайтно одним усартом

Gerraks
Gerraks
3 лет назад

А можно пример использования DMA и I2C?

Александр
Александр
3 лет назад

Добрый день. Не нашел в данной статье ничего про CS. Так понял, NSS в режиме мастера не работает. Надеюсь, я правильно понял, что нужно как то ловить прерывания SPI и выставлять бит CS на определенное время. Или это бесполезно и нужно каждый новый байт конфигурировать отдельно?

Александр
Александр
Reply to  Александр
3 лет назад

Проверил, в режиме SPI Master аппаратный NSS не работает. Поэтому нужно ставить его программно. И кстати, если необходимо вывести сигнал на ЦАП, то очень помогает ШИМ в качестве формирователя CS. Данный трюк позволяет несколько снизить нагрузку на ЦП.

Адлан
Адлан
Reply to  Александр
2 лет назад

А подробнее можно про использование ШИМ? А то не могу договориться с MAX7219

Александр
Александр
Reply to  Адлан
2 лет назад

ШИМ настраивается на период, равный передаче данных по spi, а длительность импульса чуть меньше чем окно между пакетами. Но метод довольно костыльный, так как приходится осцилографом и задержкой подгонять фазу.

Адлан
Адлан
Reply to  Александр
2 лет назад

Благодарю

Сергей
Сергей
2 лет назад

Добрый день, много путешествую по сети, но все никак не могу найти информацию о том, как реализовать вывод звука, используя библиотеку HAL, I2S3 инициализировано, так же DAC и DMA, функция HAL_I2S_Transmit() срабатывает и отправляет данные, но в наушниках по прежнему тишина, в чем может быть причина?

Алексей
1 год назад

В обработчик прерываний DMA2_Stream3_IRQHandler вы просто помещаете completed1 = 1;
А вы уверены, что у вас прерывание вызвано по событию окончания передачи? Может у вас ошибка возникла? Вы ведь даже флаг не проверяете, просто устанавливаете значение переменной 1… А ведь там 5 видов прерываний может быть…

Присоединяйтесь!

Profile Profile Profile Profile Profile
Vkontakte
Twitter

Язык сайта

Июнь 2020
Пн Вт Ср Чт Пт Сб Вс
« Май    
1234567
891011121314
15161718192021
22232425262728
2930  

© 2013-2020 MicroTechnics.ru