Top.Mail.Ru

STM32 UART. Прием и передача данных по UART в STM32CubeMx.

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

Начнем с небольшого экскурса в теоретические аспекты.

Что такое UART в целом, и зачем он нужен думаю объяснять не надо 🙂 Так что перейдем сразу к реализации в STM32. Предлагаю сначала посмотреть, какие регистры за что отвечают, а потом уже набросать какой-нибудь проект для наглядности.

Небольшое лирическое отступление... Как вы уже догадываетесь, модуль USART в STM32, как и все остальное в этих контроллерах, имеет множество настроек и режимов. Тут тебе и обычный прием/передача и поддержка LIN (об этом протоколе как-нибудь поговорим отдельно). И если в AVR частенько приходилось реализовывать программные UART’ы, поскольку аппаратных просто не хватало, то, к примеру, в STM32F103CB их как минимум три штуки! А это уже немало. Итак, начинаем копаться в даташите.

Регистр USART_SR.

Статусный регистр. Тут содержатся флаги, отражающие текущее состояние дел, то есть текущее состояние модуля USART. Поглядим на некоторые из них поближе:

  • LBD - Lin Break Detection flag. Выставляется при обнаружении брейка при использовании LIN (о том, что это такое обязательно расскажу в статье про LIN).
  • TXE - Transmit data register empty. Регистр данных пуст, пора его заполнить.
  • TC - Transmission complete. Передача завершена.
  • RXNE - Read data register not empty. Приемный регистр не пуст, надо срочно прочитать данные.

Есть там еще 4 флага для классификации ошибок – ошибка кадра, наличие шума, ошибка переполнения и т. д.

Регистр USART_DR.

Регистр данных. Именно тут мы будем забирать ценнейшую информацию, которая к нам пришла извне, сюда же будем записывать свои данные.

Регистр USART_BRR.

Здесь настраивается скорость обмена, которую мы хотим получить.

Регистр USART_CR1.

Регистр контроля – контролирует весь процесс. Биты по порядку:

  • UE - разрешение работы USART
  • M - длина посылки: 0 – 8 бит данных, 1 – 9 бит данных
  • Wake - будильник для USART’a – то есть метод его пробуждения
  • PCE - контроль паритета – ВКЛ или ВЫКЛ
  • PS - тип четности: 0 – четный, 1 – нечетный
  • PEIE - разрешение прерывания при обнаружении ошибки четности
  • TXEIE - прерывание от TXE
  • RXNEIE - прерывание от RXNE

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

Регистр USART_CR2 - в этом регистре такие биты как включение/выключение режима работы по LIN, установка количества стоп-битов, настройки clock’а, установка параметров LIN брейка и некоторые другие.

Регистр USART_CR3 - а здесь биты для использования DMA и SmartCard.

Регистр USART_GTPR - биты, отвечающие за предделитель.

В общем, вот они, 7 регистров, которые контролируют весь модуль USART в STM32. У меня описаны довольно поверхностно, только основное, если что, спрашивайте в комментариях, буду рад помочь.

Плавно переходим к практической части в виде небольшого базового примера. Забегая вперед скажу, что в данном случае проект будет действительно несложный, ознакомительный, а вот еще парочка материалов для STM32 с использованием UART:

Сегодня же для тестирования реализуем следующее - будем передавать данные при помощи USART2, а принимать посредством USART1. Таким образом, сравнив отправляемые через один и принятые другим модулем данные, мы сможем убедиться, правильно ли функционирует приемопередатчик. Я буду использовать отладочную плату STM32F4Discovery, но это не особо важно, так как механизм взаимодействия с CubeMx по сути один и тот же для любого контроллера и семейства.

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

STM32 UART пример программы.

В итоге коммутируем так:

  • PA2 (USART2_TX) - PA10 (USART1_RX)
  • PA3 (USART2_RX) - PA9 (USART1_TX)

С электрико-физическими подключениями разобрались, переходим к работе непосредственно в STM32CubeMx. Создаем новый проект и активируем нужные нам модули USART1 и USART2, установив для них режим работы:

Настройка USART в STM32CubeMx

Здесь же доступны для изменения классические параметры UART:

  • скорость передачи данных
  • длина пакета
  • четность
  • количество стоп-битов
  • направление обмена данными
  • over sampling

Для нашего тестового проекта это не очень важно, важно лишь, чтобы настройки для USART1 и USART2 были одинаковыми. Поэтому аналогичным же образом конфигурируем и второй модуль. На вкладке "NVIC_Settings" также активируем прерывание:

Прерывание UART в STM32

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

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

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

/**
* @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 реализованы следующие функции:

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

Поскольку в нашем проекте кроме работы с UART больше ничего не будет, особо оценить преимущества работы на прерываниях мы не сможем, но тем не менее осуществим обмен именно таким образом. Кроме того, объявим в файле main.c массивы (transmitBuffer[], receiveBuffer[]) и их размер (BUFFER_SIZE) для отправляемых и принимаемых данных и в итоге получаем следующее:

/* 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 ---------------------------------------------------------*/
UART_HandleTypeDef huart1;
UART_HandleTypeDef huart2;

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

/* 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 */

/* 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_USART1_UART_Init();
  MX_USART2_UART_Init();
  /* USER CODE BEGIN 2 */

  for (unsigned char i = 0; i < BUFFER_SIZE; i++)
  {
          transmitBuffer[i] = i + 1;
          receiveBuffer[i] = 0;
  }

  HAL_UART_Receive_IT(&huart1, receiveBuffer, BUFFER_SIZE);
  HAL_UART_Transmit_IT(&huart2, transmitBuffer, BUFFER_SIZE);

  /* USER CODE END 2 */

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

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

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

Прием данных по UART.

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

  • HAL_UART_TxCpltCallback()
  • HAL_UART_RxCpltCallback()

Их можно, к примеру, переопределить прямо в main.c:

/* USER CODE BEGIN 4 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  if (huart->Instance == USART1)
  {
    // USART1 завершил прием данных
  }
}

void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
  if (huart->Instance == USART2)
  {
    // USART2 завершил отправку данных
  }
}

/* USER CODE END 4 */

В эти функции программа попадет автоматически при возникновении соответствующих событий, то есть окончания процесса отправки, либо приема - типичный механизм для библиотеки HAL.

И на этом на сегодня завершаем деятельность, знакомство с UART в STM32 осуществлено, спасибо за внимание, и до скорого 🤝

Подписаться
Уведомить о
guest

52 комментариев
Старые
Новые
Межтекстовые Отзывы
Посмотреть все комментарии
Дмитрий
Дмитрий
7 лет назад

Добрый день! Подскажите а как принимать данные побайтно в прерывании?

Дмитрий
Дмитрий
7 лет назад

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

Сергей
Сергей
Ответ на комментарий  Дмитрий
6 лет назад

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

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

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

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

Дмитрий
Дмитрий
7 лет назад

Спасибо) как непривычно после Standard Peripheral Library

Денис
Денис
7 лет назад

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

RusikOk
RusikOk
Ответ на комментарий  Денис
5 лет назад

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

Евгений
Евгений
7 лет назад

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

Евгений
Евгений
7 лет назад

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

Евгений
Евгений
7 лет назад

все, нашел подходящий терминал

Дмитрий
Дмитрий
7 лет назад

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

Дмитрий
Дмитрий
7 лет назад

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

Дмитрий
Дмитрий
7 лет назад

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

Максим
Максим
7 лет назад

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

Дмитрий
Дмитрий
7 лет назад

Снова у меня вопрос. Ситуация все та же. 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 контролировать скорость отправки данных на конкретной частоте или достаточно сконфигурировать скорость передачи данных
Заранее спасибо

Дмитрий
Дмитрий
7 лет назад

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

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

Ребята, не могу настроить 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. Хочу что бы постоянно передавались данные из массива. Подскажите в чем ошибка

Виктор
Виктор
6 лет назад

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

Тимофей
Тимофей
6 лет назад

Зачем счётчик, есть callback-функция HAL_UART_RxCpltCallback?

Ефим
Ефим
6 лет назад

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

Efim25
Efim25
6 лет назад

перегенерировал код кубом, этих строк куб не генерит

Ефим
Ефим
6 лет назад

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

В.В.
В.В.
6 лет назад

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

Ефим
Ефим
6 лет назад

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

Дмитрий
Дмитрий
6 лет назад

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

Дмитрий
Дмитрий
6 лет назад

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

Дмитрий
Дмитрий
6 лет назад

Приветствую. Понадобилось освоить 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 даже не компилит, видит что он тут не используется, хотя именно этот колбек по идее и должен работать в таком режиме передачи. Что я делаю нет так?

Дмитрий
Дмитрий
Ответ на комментарий  Aveal
6 лет назад

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

Дмитрий
Дмитрий
Ответ на комментарий  Aveal
6 лет назад

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

Слава
Слава
6 лет назад

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

Слава
Слава
Ответ на комментарий  Aveal
6 лет назад

Спасибо

Alexander
Alexander
6 лет назад

Присоединяюсь к клубу теряющих последний байт. 🙁

ЭдМахалыч
ЭдМахалыч
5 лет назад

Здравствуйте! Немного не по теме. Сейчас поднимаю ModbussRTU на USARTe (портирую FreeModbuss). Вопрос, собственно, можно ли такой протокол передавать средствами DMA ? Еще не доконца разобрался с портированием и не могу понять как в DMA реализовать направление передачи (устройство будет мастером) или это скрыто под капотом?
Если вы сталкивались с подобными задачами - направите может.
В любом случае спасибо за ваш труд. По вашим урокам уже много полезных устройств трудится 🙂

ЭдМахалыч
ЭдМахалыч
5 лет назад

Спасибо за быстрый ответ! Выгода, думаю будет: на шине (rs-485) - 6 блоков InOut по восем каналов каждый + LCD 16x4 (то же на STM). Каждую итерацию главного цикла я должен обработать ввод-вывод. А в DMA привлекает прерывание обработки половины буфера. Т.е. он пока DMA обрабатывает первую половины, я работаю со второй и наоборот. Просто с МК я работаю только год (до этого я чистый ("десктопник"), и не уверен что в срок успею разобраться с портированием freeModBuss + DMA. Но попробую. Если положительно, то поделюсь. Думаю многим будет интересно (особенно когда сроки "жмуть" 🙂 )

52
0
Оставьте комментарий! Напишите, что думаете по поводу статьи.x