Top.Mail.Ru

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

Продолжаем развивать тему STM32CubeMx, и речь в этой статье пойдет о совместном использовании модулей SPI и DMA. Создадим базовый проект, в котором осуществим деятельность по передаче данных через интерфейс SPI при помощи DMA. Я буду использовать отладочную плату STM32F429Discovery в этот раз, но никаких особенностей к процессу это не добавит, для любого другого контроллера суть будет точно такой же.

Создаем проект и первым делом активируем модуль SPI, пусть будет SPI1, по классике:

Настройки SPI в STM32CubeMx

Режим выбираем "Full-Duplex Master", и поскольку наша задача - создать простой, базовый проект - то остальные настройки можем оставить дефолтными.

CubeMx сразу же "забронировал" 3 вывода контроллера для сигналов SPI:

  • SPI1_SCK
  • SPI1_MOSI
  • SPI1_MISO
SPI pinout

Хотя настройки SPI мы и оставили в покое, тем не менее необходимо вернуться в соответствующий раздел и произвести настройку DMA на вкладке "DMA Settings". Нажав на "Add" добавляем канал DMA, для которого задаем:

  • DMA Request - SPI1_TX, поскольку будем использовать его для передачи данных

Результат выглядит следующим образом:

Добавление канала DMA

Кликнув на добавленный канал, получаем доступ к его настройкам:

Настройки DMA в STM32CubeMx

Соответственно, здесь у нас выбрано:

  • режим работы - Normal
  • инкрементирование адреса в периферии отключено, поскольку адрес регистра данных SPI1 всегда один и тот же
  • а для памяти инкрементирование включено
  • размер данных - один байт

Прерывания DMA будут включены автоматически, можно просто для надежности проверить выполнение данного пункта на вкладке "NVIC Settings". На этом с настройкой заканчиваем, генерируем проект и открываем его в выбранной IDE. Вся инициализация, как обычно, была сгенерирована автоматически, можно сразу переходить к сути дела.

Для того, чтобы отследить, когда передача данных завершена, используем соответствующую callback-функцию:

void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)

Переопределяем данную функцию в main.c:

/* USER CODE BEGIN 4 */
void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)
{
  if (hspi->Instance == SPI1)
  {
    // Передача завершена
  }
}

/* USER CODE END 4 */

HAL_SPI_TxCpltCallback() будет вызвана автоматически по окончанию процесса передачи. Далее проверяем, что событие произошло именно для нашего модуля SPI1 и в случае необходимости добавляем некие действия, которые надо выполнить после передачи.

Итак, остается только непосредственно осуществить многократно упомянутую отправку данных. Для этого объявим массив тестовых данных (transmitBuffer[]) и зададим его размер (BUFFER_SIZE), передачу производим при помощи:

HAL_SPI_Transmit_DMA()

И итогом будет следующий код:

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define BUFFER_SIZE                                                     32

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
SPI_HandleTypeDef hspi1;
DMA_HandleTypeDef hdma_spi1_tx;

/* USER CODE BEGIN PV */
uint8_t transmitBuffer[BUFFER_SIZE];

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_DMA_Init(void);
static void MX_SPI1_Init(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_SPI1_Init();
  /* USER CODE BEGIN 2 */
  for (uint8_t i = 0; i < BUFFER_SIZE; i++)
  {
    transmitBuffer[i] = i + 1;
  }

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
    HAL_SPI_Transmit_DMA(&hspi1, transmitBuffer, BUFFER_SIZE);
    HAL_Delay(1000);
  }
  /* USER CODE END 3 */
}

Отправляем в данном случае циклически, раз в секунду. По окончанию процесса, как уже обсудили, будет вызван callback, в котором можно выставить, к примеру, флаг готовности к следующей передаче. Это уже зависит от задачи. А создание базового примера на данном этапе завершаем 👍

Подписаться
Уведомление о
guest
40 комментариев
старее
новее
Inline Feedbacks
View all comments
Роман
Роман
6 лет назад

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

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

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

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

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

Alexey
Alexey
6 лет назад

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

Sergey
Sergey
6 лет назад

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

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

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

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

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

Lexa
Lexa
6 лет назад

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

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

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

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

Lexa
Lexa
6 лет назад

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

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

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

у меня при передаче по 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 разве должно быть прерывание по приему?

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

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

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

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

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

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

Marat
Marat
6 лет назад

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

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

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

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

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

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

Gerraks
Gerraks
5 лет назад

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

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

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

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

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

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

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

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

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

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

Благодарю

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

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

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

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

Евгений
1 год назад

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

SCB_CleanDCache_by_Addr((uint32_t*)pTxData, 1);
HAL_SPI_Transmit_DMA(&hspi1, pTxData, 1)

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

HAL_SPI_Receive_DMA(&hspi1, pRxData, 1) 
while (HAL_DMA_GetState(&hdma_spi1_rx) != HAL_DMA_STATE_READY);
SCB_InvalidateDCache_by_Addr((uint32_t*)pRxData, 1);  
40
0
Would love your thoughts, please comment.x
()
x