STM32Cube. Прием и передача данных по USART.

Продолжаем настраивать и использовать все периферийные модули микроконтроллеров STM32 при помощи STM32Cube. Вот ссылка на все статьи курса — ссылка — а сегодня у нас на очереди инициализация, а также прием и передача данных при помощи USART.

USART в STM32Cube

Для тестирования реализуем следующее — будем передавать данные при помощи USART2, а принимать посредством USART1. Таким образом, проверив отправляемые через один модуль и принятые другим модулем данные, мы сможем убедиться, правильно ли мы настроили приемопередатчик USART. Проект будем создавать для STM32F4, соответственно, для экспериментов используем отладочную плату STM32F4-Discovery.

Для того, чтобы принять отправленные данные нам надо соединить выводы Rx/Tx USART1 с аналогичными выводами USART2. Только не забываем их перекрестить:

Настройка USART

У микроконтроллера STM32F407VGT6 выводы расположены следующим образом:

Подключение модулей USART1USART2 микроконтроллера STM32

Получается, что нам надо соединить выводы так:

  • PA2 — PA10
  • PA3 — PA9

С электрическими подключениями разобрались, переходим к работе непосредственно в STM32Cube.

Создаем новый проект и активируем нужные нам модули USART1 и USART2:

Прием и передача данных по USART

Никаких дополнительных настроек тут не требуется, так что переходим на вкладку Configuration. Здесь мы можем настроить стандартные параметры USART — скорость передачи данных, количество бит, четность, количество стоп-бит и другие. В данном примере давайте поменяем только скорость, поставим 9600. Кроме того, включим прерывания

Прерывания в STM32Cube

На этом работу с Cube заканчиваем, генерируем проект и переходим в IAR. Вся инициализация портов и модуля USART уже включена в функцию main():

/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
MX_USART2_UART_Init();

Кроме того, Cube сгенерировал обработчики прерываний, в которых уже реализованы механизмы обмена данными и сброса всех нужных флагов — все это находится внутри функции HAL_UART_IRQHandler():

/**
* @brief This function handles USART2 global interrupt.
*/
void USART2_IRQHandler(void)
{
  /* USER CODE BEGIN USART2_IRQn 0 */
 
  /* USER CODE END USART2_IRQn 0 */
  HAL_UART_IRQHandler(&huart2);
  /* USER CODE BEGIN USART2_IRQn 1 */
 
  /* USER CODE END USART2_IRQn 1 */
}
 
/**
* @brief This function handles USART1 global interrupt.
*/
void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */
 
  /* USER CODE END USART1_IRQn 0 */
  HAL_UART_IRQHandler(&huart1);
  /* USER CODE BEGIN USART1_IRQn 1 */
 
  /* USER CODE END USART1_IRQn 1 */
}

Как же нам реализовать отправку и прием данных? А все просто, для этого в HAL_Driver есть функции:

HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);
HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);
HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);

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

  • прием и передача в обычном режиме
  • прием и передача с использованием прерываний
  • прием и передача при помощи DMA

Поскольку в нашем проекте кроме работы с USART больше ничего не будет, особо оценить преимущества работы на прерываниях мы не сможем, но тем не менее реализуем обмен именно таким образом 😉 Кроме того, объявим в файле main.c массивы для отправляемых и принимаемых данных и в итоге получим следующее:

/* Includes ------------------------------------------------------------------*/
#include "stm32f4xx_hal.h"
 
/* USER CODE BEGIN Includes */
 
/* USER CODE END Includes */
 
/* Private variables ---------------------------------------------------------*/
UART_HandleTypeDef huart1;
UART_HandleTypeDef huart2;
 
/* USER CODE BEGIN PV */
uint8_t transmitBuffer[32];
uint8_t receiveBuffer[32];
 
/* USER CODE END PV */
 
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART1_UART_Init(void);
static void MX_USART2_UART_Init(void);
 
/* USER CODE BEGIN PFP */
 
/* USER CODE END PFP */
 
/* USER CODE BEGIN 0 */
 
/* USER CODE END 0 */
 
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();
 
  /* Configure the system clock */
  SystemClock_Config();
 
  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  MX_USART2_UART_Init();
 
  /* USER CODE BEGIN 2 */
  for (unsigned char i = 0; i < 32; i++)
  {
    transmitBuffer[i] = i + 1;
    receiveBuffer[i] = 0;
  }
 
  HAL_UART_Receive_IT(&huart1, receiveBuffer, 32);
  HAL_UART_Transmit_IT(&huart2, transmitBuffer, 32);
  /* USER CODE END 2 */
 
  /* USER CODE BEGIN 3 */
  /* Infinite loop */
  while (1)
  {
 
  }
  /* USER CODE END 3 */
}

Собираем проект, программируем микроконтроллер и проверяем работоспособность программы. Для этого запускаем отладку и смотрим, что у нас попадает в буфер receiveBuffer[32]:

Прием данных

Принятые данные в точности соответствуют отправленным, соответственно наша программа работает абсолютно правильно 😉 На этом и закончим сегодняшнюю статью =)

Понравилась статья? Поделись с друзьями!

STM32Cube. Прием и передача данных по USART.: 48 комментариев
  1. Добрый день! Подскажите а как принимать данные побайтно в прерывании?

  2. Еще вопросик) а как узнать что прием 32 байт уже закончен?
    Заранее благодарен.

    • Счетчик заведи и суммируй в прерывании — все по старинке ) Либо, если через HAL — там в структуре huart насколько я помню есть поле, в котором количество принятых байт записано.

    • если по хорошему, то надо переопределить функции

      void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart);
      void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);

      они автоматически вызываются из прерываний USART, когда обработан последний байт из числа указанных в поле длинны. (в статье это 32).

      Еще раз отмечу что функции вызываются обработчиком прерываний
      HAL_UART_IRQHandler(UART_HandleTypeDef *huart); поэтому код в колл-бэках должен быть минимальный. Выставляйте глобальные флаги или выдавайте семафоры в случае с работай с ОС.

      • На мой взгляд эти call-бэки не слишком удобны. Может у меня просто такие задачи обычно, что требуется на лету анализировать данные принятые, а не только после принятия всей посылки. Поэтому все вручную в прерываниях.

  3. А не подскажите как запустить debug?
    Компилируя проект в CubeMX отладчик отваливается.
    BOOT_0 подтянут к земле, разрешается запуск программы, если собрать обычный проект, то отладчику пофиг запущена программа или в режиме загрузчика.
    Если собираю в CubeMX, то в дальнейшем загрузку можно сделать только через загрузчик, т.е. убрать перемычку с BOOT_0. Соотвественно и отладка не работает.
    Камень STM32F107RB

    • а вы попробуйте назначить программатору его законные ноги в контроллере при инициализации. пункт из дерева SYS

  4. Прошу совета. Проблема связана с применением УАРТа для передачи данных с АЦП. Результат при вызове HAL_ADC_GetValue 32х битный (из которых я беру 16), а аргумент pData в HAL_UART_Transmit — 8ми битный. Передаю побайтно. В итоге в терминале на ПК мешанина из подряд идущих байт, которую нужно делить на парные байты. Может как-то можно по-людски организовать так, чтобы в терминале было, к примеру не 00000111 11100110 а соответствующее число 2022?

    • 00000111 11100110 — как то это не похоже совсем на 2022 )
      А вообще это же от терминала зависит и от его настроек отображения.

  5. 00000111 11100110 — это одно число, двухбайтовое (или как там его правильно BCD что-ли), Так что это как раз 2022. Т.е. это надо найти прогу-терминал которая это правильно интерпретирует?

  6. Приветствую уважаемые знатоки. Решаю похожую задачу, только с SPI. Пытаюсь связать два микроконтроллера STM32. Интересует следующее, хочу проверить, попадает ли контроллер в прерывание при приеме данных. Видимо оно называется SPI1_IRQHandler. Не ясен механизм. При при обработке прерывания таймера (TIM3_IRQHandler к примеру) понятно, что тело функции пишем мы сами. А тут оно как-бы уже должно быть, дак где же посмотреть его тело и соответственно воткнуть туда точку останова для проверки. Извиняюсь за примитивные вопросы, заранее спасибо за помощь.

    • Если используешь Cube, то он генерирует прерывание, внутри которого вызывает свою функцию обработчик — ставишь на ней брейкпоинт и смотришь попадает туда или нет. Само прерывание Cube помещает в файл stm32fxxxx_it.c, как и все остальные прерывания.

  7. Спасибо, нашел. Тогда еще вопрос. Пытаюсь на более простом примере еще разобраться. Плата discoveryf4, настраиваю SPI1 как master, SPI2 как slave. Посмотрел пример. Там сначала вне бесконечного цикла инициируется прерывание для SPI2 , а в цикле идет передача данных по SPI1. И в таком виде ничего не работает. Если беру инициализацию прерывания и добавляю в бесконечный цикл начинает что-то работать. В чем тут смысл и как вообще это правильно настроить, чтоб понятно было, что за чем следует. Заранее спасибо

    • Ну что тут сказать…Правильно так — инициализация один раз, отправка столько сколько надо раз.

  8. А зачем нужна вот эта конструкция? Видел ее в примере.
    void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi)
    {
    if (hspi->Instance == hspi2.Instance)
    {
    HAL_SPI_Receive_IT(&hspi2, (uint8_t*) data_get, 1);
    }
    }
    С нем все работает вообще дико странно. Результат получается половина от того, что я отправлял. Как такое может быть?

  9. Сталкивался с этим: куб не включает выводы арма для jtag — ручками в куб-проекте отредактируй их как используемые для jtag. Тогда отладка и программирование заработает. А прошить можешь так: зажать ресет, запустить программирование и отпустить ресет.

  10. Снова у меня вопрос. Ситуация все та же. STM32f4 — Master, STM32f100 — Slave. Настроил передачу данных Slave. Но удалось заставить принимать правильные данные только воткнув в цикле задержку после отправки данных. В связи с этим вопросы
    1) Что за задержка тогда 10000 в функции HAL_SPI_Transmit(&hspi1, (uint8_t*) data_send, BITS, 10000) и чего она дает
    2) Что вообще является событием для прерывания:
    приход данных по SLK или NSS, или же вообще по MOSI.
    3) Какова правильная последовательность действий при приеме и передаче данных между Slave и Master. Должно ли это обязательно ли быть прерывание или нет и что за чем идет. Кто и за кем обращается к сдвиговому регистру
    4) Нужно ли Master контролировать скорость отправки данных на конкретной частоте или достаточно сконфигурировать скорость передачи данных
    Заранее спасибо

  11. Всем здравствуйте. Имею такой вопрос. Настроил SPI между двумя stm32. Master — f4, slave — f100. Вопрос следующий. Отсылаю значение одной 16 битовой переменной. При приеме данных слейвом в первый раз младший бит всегда теряется. Отправляю 4 (b100), получаю 2 (b10). При приеме данных мастером все наоборот, появляется лишний бит. Отправил 2 (b10), получаю 4 (b100). При повторном приеме, передаче, приходят правильные данные и больше такое не возникает. В чем причина? Подскажите, заранее спасибо

  12. Ребята, не могу настроить USART DMA в circular mode через Cube. Достаточно ли в Cube поставить галочку в DMA request setting — mode circular? Запускаю командой HAL_UART_Transmit_DMA (&huart1, uartTX, 16) в main. Первый раз при запуске передает данные, потом висит, пока снова не запускаю HAL_UART_Transmit_DMA (&huart1, uartTX, 16) в цикле while. Хочу что бы постоянно передавались данные из массива. Подскажите в чем ошибка

  13. Здравствуйте!
    А если не известна заранее длина принимаемых данных? Как в таком случае организовать прием?

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

  14. static void MX_GPIO_Init(void);
    static void MX_USART1_UART_Init(void);
    static void MX_USART2_UART_Init(void);
    на эти три строки ругается компилятор
    да и вообще что то не могу проследить изменение receiveBuffer[32] правда использую эклипс

    • Там через адрес receiveBuffer в него кладутся принятые данные.

      А вот почему ругается — это вопрос, все эти строки генерирует Cube. Может дело действительно в среде разработки.

    • Может в новой версии Куба другая немного организация или названия, суть то не в этом, а в том, что из статьи в свой проект надо копировать только пользовательский код.

  15. в общем один буфер заполняется нулями, другой числами, и потом ничего (зависает на определенном адресе), где ошибка хз

  16. Как то кривопопово работает HAL_UART_Receive_IT….
    Никто не победил как ей сбросить в начало кольцевой буфер…
    может появилось более менее человеческое описание ….

  17. у кого не получается проследить значение переменной receiveBuffer, строки
    HAL_UART_Receive_IT(&huart1, receiveBuffer, 32);
    HAL_UART_Transmit_IT(&huart2, transmitBuffer, 32);
    ставим в цикл вайл, дли наглядности можно поставить чекпоинт

  18. Приветствую. Цикла для CubeMX под SPI нет, так что пишу сюда. Принцип по идее один и тот же.
    Пытаюсь подружить два микроконтроллера STM32f103. Но как-то функции передачи непонятно работают. Отсюда вопросы:
    1) Чем отличаются функции с препиской IT (как я понял, они прерывания вребают) от обычных бункций приема и передачи?
    2) Что конкретно означает задержка Timeout — аргумент функции приемо-передачи без IT. Когда она вступает в силу, эта задержка?
    3) Какова логика вызова колбеков и зачем они нужны. Почему, если сам не вызвал ни разу прерывание, то и колбек неактивен. И зачем он вообще нужен?
    Заранее спасибо)

    • Добрый день)

      1). Обычная функция «заблокирует» контроллер пока будет идти прием/передача. То есть он будет крутиться в ней пока процесс не закончится.
      2). Параметр timeout, насколько я помню, нужен для того, чтобы контроллер не завис навсегда в этих функциях. То есть если прием/передача не завершатся в течение этого времени, то функция вернет ошибку и контроллер выйдет из нее.
      3). Callback’и нужны для удобства — чтобы не нужно было ничего менять в самих прерываниях, а просто писать код в соответствующем callback’е. Но я, к примеру, ими не пользуюсь.

  19. Да, и еще вопросик:
    4) Когда в дело вступает прерывание, доступное для мастера. Когда оно вообще используется?

    • 4). Скорее всего там сделано так — само прерывание срабатывает по приему/передаче одного байта, а callback функция будет вызвана после полного окончания приема/передачи. Но вот этот момент я точно не помню, можно посмотреть по коду или экспериментальным путем выявить)

  20. Приветствую. Понадобилось освоить USART в режиме Half Duplex. Подскажите пожалуйста, какая должна быть последовательность действий при отправке и приеме данных. Я действовал так. Само собой сначала соответствующим образом инициализируем интерфейс. Затем отправляем так:
    HAL_HalfDuplex_EnableTransmitter(…);
    HAL_UART_Transmit_IT(…);
    И данные уходят, вижу их на осциллографе. После этого принимающий девайс шлет ответ и его я тоже вижу. Прием осуществляю так:
    void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
    {
    HAL_HalfDuplex_EnableReceiver(…);
    HAL_UART_Receive_IT(…);
    }
    И данных процессор в итоге не видит, хоть они и идут. Что странно, HAL_UART_TxHalfCpltCallback Keil даже не компилит, видит что он тут не используется, хотя именно этот колбек по идее и должен работать в таком режиме передачи. Что я делаю нет так?

      • Компиляция то идет, но данный калбек не активен. Это видно, если запустить отладку кейла. Что не так то делаю?

          • Колбек определен в моем файле, только дело явно не в этом, Другие то колбеки там же прописаны и работают. Подкажите сам алгоритм. Как в таком режиме USARTом управлять?

        • Конкретно про этот режим не могу ничего сказать определенного. Но раз данные через функцию Transmit_IT уходят логично было бы предположить, что и callback должен срабатывать.
          Надо по коду функции пройтись и посмотреть не происходит ли там чего-то специфичного при таком режиме.

  21. Здравствуйте, спасибо за статью. Вы не могли пояснить процесс передачи. Почему вначале мы пишем функцию приема, а затем передачи?

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

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *