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
12 лет назад

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

Joiny
Joiny
12 лет назад

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

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

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

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

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

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);

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

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

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

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

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

Подскажите, пожалуйста.
Если использую следующую инициализацию:
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, то первая что делает?

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

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

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

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

Fargk
Fargk
10 лет назад

Извините, что немного оффтоп, но не могли бы вы подсказать почему заголовок функции прерывания по 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
10 лет назад

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

Vladimir
9 лет назад

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

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

2 платы STM32F4Discovery

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

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

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

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

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

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

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

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

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

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

Esn
Esn
2 лет назад

Попробовал объединить оба примера в один, отправка и получение данных, но не работает:
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
2 лет назад

Салют!
В актуальных инструментах используется пример с двумя: 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
2 лет назад

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

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
2 лет назад

Спасибо огромное!:
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
2 лет назад

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

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