Top.Mail.Ru

STM32F4 и USART. Прерывания и прием данных.

Итак, совсем недавно мы познакомились с приемо-передатчиком USART в микроконтроллерах STM32F4 и создали пример для передачи данных в окружающий мир (вот). Как и обещал, сегодня разберемся с приемом данных. Теории не будет, все уже вроде обсудили при работе с передачей, так что без лишних прелюдий переходим сразу к написанию программы )

Время традиционной вставки: поскольку компания STMicroelectronics прекратила поддержку библиотеки SPL, которая использовалась в этом курсе, я создал новый, посвященный работе уже с новыми инструментами, так что буду рад видеть вас там - STM32CubeMx. Кроме того, вот глобальная рубрика по STM32, а также статья на смежную тему из нового курса: STM32 UART. Прием и передача данных по UART в STM32CubeMx.

Для начала рассмотрим общую последовательность действий, необходимых для запуска USART в STM32F4 и настройки его для работы в качестве приемника:

  • Работаем с тактированием – включаем тактирование приемо-передатчика, а заодно и тактирование порта, который отвечает за ножки Rx/Tx.
  • Сначала настраиваем нужные ножки микроконтроллера, а затем и сам USART.
  • Включаем прерывания – функция NVIC_EnableIRQ() и запускаем USART - USART_Cmd().
  • Наконец включаем прерывание, которое нам в данном случае понадобится, то есть прерывание по приему данных - для этого нам пригодится функция USART_ITConfig().

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

/***************************************************************************************/
// Подключаем файлы
#include "stm32f4xx.h"
#include "stm32f4xx_rcc.h"
#include "stm32f4xx_gpio.h"
#include "stm32f4xx_usart.h"


/***************************************************************************************/
// Объявляем переменные
GPIO_InitTypeDef gpio;
USART_InitTypeDef usart;
// Будем принимать 16 байт, к примеру
uint8_t receivedData[16];
uint8_t bytesToReceive = 16;
// Счетчик принятых байт
uint8_t receivedDataCounter = 0;
// Пока все идет точно также, как и в случае с передачей


/***************************************************************************************/
// Инициализируем все подряд
void initAll()
{
	// Глобальное разрешение прерываний
	__enable_irq();

	// Тактирование, куда ж без него-то
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);

	// Настраиваем ножки  контроллера, тут все понятно
	GPIO_StructInit(&gpio);

	gpio.GPIO_Mode = GPIO_Mode_AF;
	gpio.GPIO_Pin = GPIO_Pin_9;
	gpio.GPIO_Speed = GPIO_Speed_50MHz;
	gpio.GPIO_OType = GPIO_OType_PP;
	gpio.GPIO_PuPd = GPIO_PuPd_UP;
	GPIO_Init(GPIOA, & gpio);

	gpio.GPIO_Mode = GPIO_Mode_AF;
	gpio.GPIO_Pin = GPIO_Pin_10;
	gpio.GPIO_Speed = GPIO_Speed_50MHz;
	gpio.GPIO_OType = GPIO_OType_PP;
	gpio.GPIO_PuPd = GPIO_PuPd_UP;
	GPIO_Init(GPIOA, & gpio);

	// Обязательно вызываем эту функцию
	GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_USART1);
	GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_USART1);

	// Настраиваем модуль USART
	USART_StructInit(&usart);
	usart.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
	usart.USART_BaudRate = 9600; 
	USART_Init(USART1, &usart); 

	// Включаем прерывания и запускаем USART
	NVIC_EnableIRQ(USART1_IRQn);
	USART_Cmd(USART1, ENABLE);
}


/***************************************************************************************/
// Функция main()
int main()
{
	// Тут нам нужно лишь вызвать функцию инициализации и запустить процесс – то есть включить прерывание по приему данных
	initAll();
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
	
	while(1)
	{
		__NOP();
	}
}


/***************************************************************************************/
// Прерывание
void USART1_IRQHandler()
{
	// Убеждаемся, что прерывание вызвано новыми данными в регистре данных
	if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
	{
		// Чистим флаг прерывания
		USART_ClearITPendingBit(USART1, USART_IT_RXNE);

		// То ради чего все затеяли – принимаем данные )
		receivedData[receivedDataCounter]  = USART_ReceiveData(USART1);

		// Приняли? Увеличиваем значение счетчика!
		receivedDataCounter ++;

		// Приняли 16 байт – выключаем
		if  (receivedDataCounter == bytesToReceive)
		{
			USART_ITConfig(USART1, USART_IT_RXNE, DISABLE);
		}
	}
}  


/***************************************************************************************/

Вот такой получился пример. За исключением обработчика прерывания все также как и с передачей данных, можно было, в принципе, и в одну статью все уместить. Отмечу, кстати вызов функции GPIO_PinAFConfig():

GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_USART1);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_USART1);

Я когда первый раз работал в STM32F4 с USART’ом, упустил из виду то, что надо вызывать эту функцию и некоторое время не мог понять, почему ничего не работает... После STM32F10x казалось, что достаточно вот этого:

gpio.GPIO_Mode = GPIO_Mode_AF;

Но нет, тут все чуть иначе. Так что при работе с любой периферией в STM32F4 не забываем про вызов функции GPIO_PinAFConfig(). Что еще можно сказать про USART? Да вроде бы все… Так что, если возникли какие-нибудь вопросы, обязательно спрашивайте в комментариях, а на сегодня это пожалуй все, до скорого!

P. S. Я тут кстати себе сделал небольшую библиотеку для ускорения работы с USART’ом, на днях обязательно выложу, буду рад, если кому-нибудь пригодится.

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

40 комментариев
Старые
Новые
Межтекстовые Отзывы
Посмотреть все комментарии
spectre
spectre
10 лет назад

Было бы классно объединить 2 статьи (прием и передача) в одну.
Скажем передаем с USART1 "led1" и загорается определенный светодиод, а если принят мусор или что-то ещё загорается другой... ну как-то так. Это было бы наглядно, и понятно. У меня пока только STMка активно передает, а вот с приемом что-то не то.
STM32F3[4]-discaveri (код для 3 от 4ой не очень отличается, проверил, даже пины для усарт1 одинаковы)
PS а ещё, было бы офигенно раскурить пример PWM с изменением яркости светодиода, на пальцах, и с STM либами 🙂

Joiny
Joiny
10 лет назад

В AVR прерывание срабатывает по приему одного байта. А в stm как тогда?

Михаил
Михаил
10 лет назад

Спасибо за статью. Пытаюсь разобраться с USART в STM32F3FDISCOVERY . С передачей проблем нет. Проблема с приемом - принимается только первый байт. Следующий байт не замещает первый. Как вытолкнуть принятый байт?..

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

Я пробовал, но что-то пошло не так... Инит, судя по всему, останавливал все процессы в организме отладочника... Заработало при такой комбинации:
(Ах, да, я использовал третий уарт, т.к. в СТМ_Дискавери на первом программатор ещё висит и мусорит мне в терминал, позже буду на первый переходить как свою плату сделаю...)

GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
USART_ClockInitTypeDef USART_ClockInitstructure;

/* Enable GPIOD clock */
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);

/* Enable USART clock */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE);

/* Connect PXx to USARTx_Tx*/
GPIO_PinAFConfig(GPIOD, GPIO_PinSource8, GPIO_AF_USART3);

/* Connect PXx to USARTx_Rx*/
GPIO_PinAFConfig(GPIOD, GPIO_PinSource9, GPIO_AF_USART3);

/* Connect PXx to USARTx_Ct*/
GPIO_PinAFConfig(GPIOD, GPIO_PinSource11, GPIO_AF_USART3);

/* Connect PXx to USARTx_Rt*/
GPIO_PinAFConfig(GPIOD, GPIO_PinSource12, GPIO_AF_USART3);

/* Configure USART as alternate function */
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
// Tx Rx Ct Rt
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_11 | GPIO_Pin_12;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOD, &GPIO_InitStructure);

USART_InitStructure.USART_BaudRate = bauld;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;

/* USART Clock Initialization */
USART_ClockInitstructure.USART_Clock = USART_Clock_Disable ;
USART_ClockInitstructure.USART_CPOL = USART_CPOL_High ;
USART_ClockInitstructure.USART_LastBit = USART_LastBit_Disable;
USART_ClockInitstructure.USART_CPHA = USART_CPHA_1Edge;

/* USART configuration */
USART_Init(USART3, &USART_InitStructure);
USART_ClockInit(USART3, &USART_ClockInitstructure);

/* Enable USART */
USART_Cmd(USART3, ENABLE);

// Включаем прерывания и запускаем USART
NVIC_EnableIRQ(USART3_IRQn);

USART_ITConfig(USART3, USART_IT_RXNE, ENABLE);

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

Спасибо за статью! Реально помогла без особой головной боли настроить приём информации с терминала.

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

Спасибо за пример, все отлично. Но вот вопрос: как отправить данные на плату?
Как реализовать общение пк и микросхемы?

Валерий
Валерий
10 лет назад

Подскажите, пожалуйста.
Если использую следующую инициализацию:
usart3.NVIC_IRQChannel = USART3_IRQn;
usart3.NVIC_IRQChannelPreemptionPriority = 2;
usart3.NVIC_IRQChannelSubPriority = 2;
usart3.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&usart3);
то есть ли необходимость использовать:
NVIC_EnableIRQ(USART3_IRQn);
Чем отличаются в этом контексте первая и последняя строки. Если последняя строка разрешает прерывания по USART3, то первая что делает?

Валерий
Валерий
10 лет назад

Т.е., если я буду использовать систему приоритетов прерываний, то мне НУЖНО использовать эти строки с полями структуры. При этом НЕТ НЕОБХОДИМОСТИ использовать NVIC_EnableIRQ(USART3_IRQn)? (это вопрос один). И, наоборот. Если используются прерывания и НЕ НУЖНЫ приоритеты, то ДОСТАТОЧНО использовать ТОЛЬКО NVIC_EnableIRQ(USART3_IRQn) БЕЗ ЗАПОЛНЕНИЯ полей структур? (это вопрос два)
Я правильно понимаю?

Паша
Паша
9 лет назад

что делает строчка
gpio.GPIO_PuPd = GPIO_PuPd_UP;
на F100 F303 вроде она не используется, стало интересно!

Fargk
Fargk
8 лет назад

Извините, что немного оффтоп, но не могли бы вы подсказать почему заголовок функции прерывания по USART3 не видит прототипа?Плата STM32F429-Discovery.
void USART3_IRQHandler(void)
{}
В хейдере:
#include "stm32f4xx_it.h"
#include "main.h"
#include "stm32f4xx_usart.h"
#include "stm32f4xx_gpio.h"
#include "init_UART.h"
#include "GSM_WISMO228.h"

Т.е. 1 и 2 определяет но 3 нет, хотя в файле startup_stm32f429_439xx.s есть конструкции для этого прерывания.

Fargk
Fargk
8 лет назад

проблемой разобрался. Я забыл в хейдере написать прототип прерывания.

Vladimir
8 лет назад

Здравствуйте! Зашил предложенную тут программу, но на приёме вместо 0х11 ... 0х88 вижу массив из этого:
"\300\300w\367\200\300\300w\367\200\300\300w\367\200\300"
Что это? Пишу в CooCox. смотрю через Debug.
И массив при отправке выглядит вот так:
"21\"3DUfw"
Что то с отображением?

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

2 платы STM32F4Discovery

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

Код именно этот и Tx/Rx перекрещены.

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

Могу скинуть, но код из этой статьи, я его не изменял.

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

т.е. из предыдущей.

Владимир
Владимир
8 лет назад

А для работы USART сколько надо проводников? 2? TX и RX? или надо ещё что-то?

Владимир
Владимир
8 лет назад

а что такое тогда USART1_CK ??? в каких случаях это нужно?

Esn
Esn
11 месяцев назад

Попробовал объединить оба примера в один, отправка и получение данных, но не работает:
USART_ITConfig(USART2, USART_IT_TC | USART_IT_RXNE, ENABLE); 

А вот по отдельности работает:
USART_ITConfig(USART2, USART_IT_TC, ENABLE); 
или USART_ITConfig(USART2, USART_IT_RXNE, ENABLE); 

Всё делал один к одному как в этом примере и в примере https://microtechnics.ru/programmirovanie-stm32f4-usart-primer-programmy/

Что надо сделать чтобы одновременно работал приём и отправка?

Esn
Esn
Ответ на комментарий  Aveal
11 месяцев назад

Салют!
В актуальных инструментах используется пример с двумя: USART1 и USART2, что не подходит, так как у меня только один: USART2.

Всё таки как объединить пример этой страницы https://microtechnics.ru/programmirovanie-stm32f4-usart-priem-dannyx/
с примером https://microtechnics.ru/programmirovanie-stm32f4-usart-primer-programmy/ чтобы одновременно работали и отправка и приём?

Где здесь ошибка?:
USART_ITConfig(USART2, USART_IT_TC | USART_IT_RXNE, ENABLE); 

Esn
Esn
Ответ на комментарий  Aveal
11 месяцев назад

"самому руками обработать регистры/ошибки"
это использовать?:

int main()
{
....
while(!(USART2->SR & USART_SR_TC))
{
USART2->DR = data_to_send; // Передача данных в терминал
}

while(!(USART2->SR & USART_SR_RXNE))
{
data_received = USART2->DR; // Получение данных из терминала
}

}

Esn
Esn
Ответ на комментарий  Aveal
11 месяцев назад

Спасибо огромное!:
USART_ITConfig(USART2, USART_IT_TC, ENABLE);
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);
:это заставило работать.

Оказывается надо было не в одну кучу "USART_IT_TC | USART_IT_RXNE", а последовательно шаг за шагом:

stepbystep

USART_ITConfig(USART2, USART_IT_TC, ENABLE);
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);

Esn
Esn
Ответ на комментарий  Aveal
11 месяцев назад

Осталось прикрутить printf к USART2.

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