STM32 с нуля. Настройка и использование USART.

Продолжаем нашу рубрику STM32 с нуля, диодом помигали, таймер задействовали, пора наладить связь с внешним миром! Для этого сегодня будем разбираться как работает модуль USART в микроконтроллерах STM32. И, собственно, напишем пример программы для передачи данных

Что такое USART и зачем он нужен думаю объяснять не надо 🙂 Так что перейдем сразу к реализации интерфейса в 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. У меня описаны довольно поверхностно, только основное, если что, спрашивайте в комментариях, буду рад помочь!

Собственно, пришло время немного попрограммировать. Пусть в нашем USART-примере программа опрашивает кнопку, и если она нажата, то контроллер вышлет в USART сообщение “Pressed”, а если кнопка так и будет скучать не нажатой, то во внешний мир полетит “Not Pressed”.

Итак, как же мы будем это реализовывать? А очень просто (вот на всякий случай статья про создание нового проекта). Создадим функцию, которая будет опрашивать кнопку и записывать в буфер соответствующее сообщение. Кроме того, она будет в специальную переменную записывать количество байт, которые надо передать. В прерывании мы будем совать очередной байт в USART и увеличивать счетчик переданных байт.

Когда значение счетчика станет равно количеству байт, которые необходимо передать (это означает, что все сообщение передано), снова будем вызывать функцию-опросчик кнопки. И так бесконечно. Перейдем к реализации алгоритма (не забываем добавить в проект файлы stm32f10x_usart.h и stm32f10x_usart.c из Standard Peripheral Library). Ниже приведен полный код примера с пояснениями:

/***************************************************************************************/
#include "stm32f10x.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_tim.h"
#include "stm32f10x_usart.h"


/***************************************************************************************/
#define BAUDRATE												 9600


/***************************************************************************************/
GPIO_InitTypeDef port;
USART_InitTypeDef usart;
// Переменная для хранения передаваемых данных
uint8_t usartData[10];
uint16_t button;
// Счетчик переданных байт
uint16_t usartCounter = 0;
// А тут будет количество байт, которые нужно передать
uint16_t numOfBytes;


/***************************************************************************************/
void initAll()
{
	// Включаем тактирование
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

	// Пины PA9 и PA10 в режиме альтернативных функций – Rx и Tx USART’а
	GPIO_StructInit(&port);
	port.GPIO_Mode = GPIO_Mode_AF_PP;
	port.GPIO_Pin = GPIO_Pin_9;
	port.GPIO_Speed = GPIO_Speed_2MHz;
	GPIO_Init(GPIOA, &port);

	port.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	port.GPIO_Pin = GPIO_Pin_10;
	port.GPIO_Speed = GPIO_Speed_2MHz;
	GPIO_Init(GPIOA, &port);

	// Настройка USART, все поля оставляем дефолтными, кроме скорости обмена
	USART_StructInit(&usart);
	usart.USART_BaudRate = BAUDRATE;
	USART_Init(USART1, &usart);

	// Здесь будет висеть наша кнопка
	port.GPIO_Mode = GPIO_Mode_IPD;
	port.GPIO_Pin = GPIO_Pin_2;
	port.GPIO_Speed = GPIO_Speed_2MHz;
	GPIO_Init(GPIOB, &port);
}


/***************************************************************************************/
// Вот она, функция-опросчик кнопки
void setData()
{
	button = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_2);
	
	if (button == 0)
	{
		usartData[0] = 'N';
		usartData[1] = 'o';
		usartData[2] = 't';
		usartData[3] = 'P';
		usartData[4] = 'r';
		usartData[5] = 'e';
		usartData[6] = 's';
		usartData[7] = 's';
		usartData[8] = 'e';
		usartData[9] = 'd';
		numOfBytes = 10;
	}
	else
	{
		usartData[0] = 'P';
		usartData[1] = 'r';
		usartData[2] = 'e';
		usartData[3] = 's';
		usartData[4] = 's';
		usartData[5] = 'e';
		usartData[6] = 'd';
		numOfBytes = 7;
	}
	
	usartCounter = 0;
}


/***************************************************************************************/
int main()
{
	__enable_irq();
	initAll();
	
	// Включаем прерывания по приему байта и по окончанию передачи
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
	USART_ITConfig(USART1, USART_IT_TC, ENABLE);
	
	// Запускаем сам USART
	USART_Cmd(USART1, ENABLE);
	
	// Первая установка сообщения, вне цикла
	setData();
	NVIC_EnableIRQ(USART1_IRQn);
	
	while(1)
	{
		// Собственно вот он - весь описанный ранее алгоритм
		if (usartCounter >= numOfBytes)
		{
			setData();
		}
	}
}


/***************************************************************************************/
// А в прерывании выдаем байт и увеличиваем счетчик
void USART1_IRQHandler()
{
	if (USART_GetITStatus(USART1, USART_IT_TC) != RESET)
	{
		if (usartCounter < numOfBytes)
		{
			USART_SendData(USART1, usartData[usartCounter]);
			usartCounter++;
		}
		USART_ClearITPendingBit(USART1, USART_IT_TC);
	}
}


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

Как видите все довольно просто и понятно! Давайте посмотрим что получилось – компилируем и запускаем отладчик. Там идем в View->Serial Windows и выбираем наш модуль USART – то есть USART номер один. В правом нижнем углу появится окошко, в котором будем видеть все сообщения, которые выдает USART. Запускаем программу и эмулируем нажатие кнопки, выставляя нужный бит в окне GPIOB. И вот что получилось:

Использование USART в STM32.

Согласен, не слишком наглядно и красиво, но то, что программа работает как мы и задумали, не вызывает ни малейших сомнений 🙂 С передачей все прошло гладко, давайте попробуем что-нибудь принять. Итак, реализуем еще один пример.

Пусть будет такая задача – в зависимости от принятого байта зажигать один из двух диодов. То есть приняли “1” – зажгли первый, приняли “2” – зажгли второй. Тут мы снова прибегнем к языку сценариев (об этом писалось в одной из предыдущих статей – вот в этой) для эмуляции отправки сообщений по USART. Создаем файл usart.ini и заполняем его следующим кодом:

/**************************************************************************************************/
func void usart1()
{
	S1IN = '1';
}


/**************************************************************************************************/
func void usart2()
{
	S1IN = '2';
}


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

Кода немного, но достаточно для нашей цели!

S1IN – вход для эмуляции входа последовательного порта. Таким образом, вызвав в командной строке функцию usart1() получим на вход USART’a – “1”, а вызвав usart2() – получим “2”. Теперь код основной программы:

/**************************************************************************************************/
#include "stm32f10x.h" 
#include "stm32f10x_rcc.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_usart.h"


/**************************************************************************************************/
#define BAUDRATE                                                 9600


/**************************************************************************************************/
GPIO_InitTypeDef port;
USART_InitTypeDef usart;
uint8_t usartData;


/**************************************************************************************************/
void initAll()
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

	GPIO_StructInit(&port);
	port.GPIO_Mode = GPIO_Mode_AF_PP;
	port.GPIO_Pin = GPIO_Pin_9;
	port.GPIO_Speed = GPIO_Speed_2MHz;
	GPIO_Init(GPIOA, &port);

	port.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	port.GPIO_Pin = GPIO_Pin_10;
	port.GPIO_Speed = GPIO_Speed_2MHz;
	GPIO_Init(GPIOA, &port);

	USART_StructInit(&usart);
	usart.USART_BaudRate = BAUDRATE;
	USART_Init(USART1, &usart);

	port.GPIO_Mode = GPIO_Mode_Out_PP;
	port.GPIO_Pin = GPIO_Pin_1;
	port.GPIO_Speed = GPIO_Speed_2MHz;
	GPIO_Init(GPIOB, &port);

	port.GPIO_Mode = GPIO_Mode_Out_PP;
	port.GPIO_Pin = GPIO_Pin_2;
	port.GPIO_Speed = GPIO_Speed_2MHz;
	GPIO_Init(GPIOB, &port);
}


/**************************************************************************************************/
int main()
{
	__enable_irq();
	initAll();
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
	USART_Cmd(USART1, ENABLE);
	NVIC_EnableIRQ(USART1_IRQn);
	while(1)
	{
		// Если приняли "1" зажигаем диод 1 и гасим диод 2, приняли "2" - все наоборот
		if (usartData == '1')
		{
			GPIO_SetBits(GPIOB, GPIO_Pin_1);
			GPIO_ResetBits(GPIOB, GPIO_Pin_2);
		}
		if (usartData == '2')
		{
			GPIO_SetBits(GPIOB, GPIO_Pin_2);
			GPIO_ResetBits(GPIOB, GPIO_Pin_1);
		}
	}
}


/**************************************************************************************************/
void USART1_IRQHandler()
{
	// Проверяем, действительно ли прерывание вызвано приемом нового байта
	if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
	{
		usartData = USART_ReceiveData(USART1);
	}
}


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

Опять все очень несложно, даже комментировать толком не пришлось 🙂

Запускаем отладчик Keil, настраиваем логический анализатор на порты PB1 и PB2, подключаем наш файл .ini (написав в командной строке include usart.ini) и поочередно вызываем функции usart1() и usart2(). Вот что из этого безобразия вышло:

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

То есть не вышло абсолютно ничего! Можно долго ломать голову, где же ошибка в программе, а ответ прост. Почему-то логический анализатор иногда отказывается работать правильно. В общем, раз на раз не приходится. В таких случаях, прежде чем мучить себя отлавливанием несуществующей ошибки, открываем окно GPIOB  и видим, что на самом деле все работает корректно:

Отладка в Keil.

Диоды мигают так как надо 🙂

Итак, мы разобрались с работой USART, с приемом и передачей, следите за развитием событий в следующих статьях!

Поделиться!

Подписаться
Уведомление о
guest
24 Комментарий
старее
новее большинство голосов
Inline Feedbacks
View all comments
Алиса Алексеева
7 лет назад

Ну, я мало че поняла :), но думаю полезно 🙂

andries5
andries5
7 лет назад

port.GPIO_Mode = GPIO_Mode_AF_PP;
port.GPIO_Pin = GPIO_Pin_10;

А разве не вход должен быть?

Гордон Фриман
Гордон Фриман
7 лет назад

У меня такая херотень с USART: Имеется код, который работает, но инициируется не понятно. Я использую библиотечные функции для инициирования USART, но при установке скорости 9600 в терминале наблюдаю 4 непонятных байта, когда я высылал 1. И прикол в том что у меня появился нормальный прием, когда я выставил ему в коде скорость 28800, т.е 3 раза большую, хотя в терминалке стоит 9600, остальное стандартно и дефолтно. Смотрел по асциллу и на самом деле скорость USART у меня была в 3 раза занижена. В чем может быть дело, если при вызове библиотечных функций скорость должна быть 9600 при любой частоте контроллера?

Виталик
Виталик
Reply to  Гордон Фриман
7 лет назад

Вибачте, що українською (переведете)))))
це може бути пов’язано з тим, що у файлі system_stm32бла.c частота HSE_VALUE зовнішнього джерела встановлена 25 МГц, а на платі кварц стоїть 8 МГц. Це приблизно дає різницю втричі.

Гордон Фриман
Гордон Фриман
Reply to  Виталик
7 лет назад

Да, на плате стоит внешний кварц на 8. Я тоже подозреваю на стартапный файл. Программирую я под Keil 4, и, кстати, контроллеру похер на то что в настройках проекта у меня стоит 8 Мгц если подключен опять же стартапный файл – он будет ориентировать тактирование по этому файлу

Гордон Фриман
Гордон Фриман
Reply to  Виталик
7 лет назад

Кстати, спасибо за помощь. Проблема была решена. Дело было действительно в этом параметре

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

Здравствуйте
на сайте ST в мануалах на контроллеры отсутствует информация о регистрах периферийных устройств с их описанием. Видимо это вынесено в отдельные документы. Напишите, пожалуйста, в каких документах можно найти описание периферии контроллеров, а так же библиотек

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

Буду признателен если объясните:
1) написал этот пример для STM32f050 где все таймеры и нужный мне USART сидят на одной единственной шине APB.
когда делал Шим тактировал соответственно: RCC_APB1Periph_TIM2 все работало.
также по идее надо и для USART RCC_APB1Periph_USART1 , но тут компилятор начинает ругаться и говорит что нужно тактировать APB2, эксперемента ради затактировал APB2 заработало. но как так получается если по даташиту там одна единственная шина????
2) в терминале после запуска выходит
NotPressed и дальше хаотичный набор символов,
т.е. я так понимаю при первом вызове setData() все нормально, а в цикле уже совсем не то…..???

Ринат
Ринат
Reply to  Паша
1 год назад

У меня похожая проблема выдает не то что отправляю. Попробовал ради теста отправлять переменную, каждый следующий раз увеличивая на единицу. Но в терминале мне выводятся символы по таблице ANSCII, начиная с начала и до последнего символа. Это получается что отправлять нужные мне данные надо по таблице ANSCII? Или есть какое-то другое решение проблемы?

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

Вопрос может глупый, но есть желание полностью разобраться, в первом примере передаем usartData,
8битная переменная типа “unsigned int”
а как например передать результат с 12битного АЦП,
строку char[4]=”Start”; или просто int c=500; ?

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

Пытаюсь разобраться с Usart ( у вас используется связь rs252-usb ) при этом usb mini для контроллера rs идет в комп так ведь?

Mikhail
Mikhail
4 лет назад

Кажется для полноты картины надо добавить тактирование альтернативной функции RCC_APB2PERIPH_AFIO

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

Есть такая задача. Надо организовать двухсторонний обмен данными между двумя устройствами по радиоканалу. Радиомодемы подключаются по UART. Решил использовать UART DMA режим. Настройку и сборку проекта делал через Cube. Проблема заключается в следующем: после некоторого времени работы устройства прекращается прием данных. В тоже время если на одном устройстве оставить только передающую, а на другом приемную часть программы – все прекрасно работает.
Прошу помощи в решении этого вопроса (к сожалению большого опыта в программировании этих процессоров не имею). Может подскажите готовое решение для двухстороннего обмена данными по UART.
Для примера и обсуждения выкладываю часть программы одного устройства (на втором все организовано аналогично, есть небольшие отличия в обработке принимаемого сигнала).
Инициализация UART
[code]/* USART1 init function */
void MX_USART1_UART_Init(void)
{

huart1.Instance = USART1;
huart1.Init.BaudRate = 19200;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
huart1.Init.OneBitSampling = UART_ONEBIT_SAMPLING_DISABLED ;
huart1.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
HAL_UART_Init(&huart1);

}
[/code]
Инициализация DMA
[code]void MX_DMA_Init(void)
{
/* DMA controller clock enable */
__DMA1_CLK_ENABLE();
__DMA2_CLK_ENABLE();

/* DMA interrupt init */
HAL_NVIC_SetPriority(DMA1_Channel4_IRQn, 0, 0); // DMA1_Channel4 – передача uart
HAL_NVIC_EnableIRQ(DMA1_Channel4_IRQn);
HAL_NVIC_SetPriority(DMA1_Channel5_IRQn, 0, 0); // DMA1_Channel5 – прием UART
HAL_NVIC_EnableIRQ(DMA1_Channel5_IRQn);
HAL_NVIC_SetPriority(DMA2_Channel1_IRQn, 0, 0);// DMA2_Channel1 – ADC
HAL_NVIC_EnableIRQ(DMA2_Channel1_IRQn);

}
[/code]
В main делаю отправку данных вот так:
[code]
if(TX_Ready == 1)
{
TX_Ready = 0;
HAL_UART_Transmit_DMA(&huart1, (uint8_t *)uartTX, 9);
}
[/code]
Флаг сбрасывается вот здесь TX_Ready:
[code]void HAL_UART_TxCpltCallback(UART_HandleTypeDef *UartHandle)
{
TX_Ready = 1;
}
[/code]
Прием данных делаю вот здесь:
[code]void HAL_UART_RxCpltCallback(UART_HandleTypeDef *UartHandle)
{
if(huart1.RxXferSize == 1)
{
if(RXbyte[0] == START_BYTE)
HAL_UART_Receive_IT(&huart1, (uint8_t *)uartRX, 3);
else
HAL_UART_Receive_IT(&huart1, (uint8_t *)RXbyte, 1);
}
else
{
if (uartRX[2] == 2*START_BYTE)
IRdist = (uartRX[1] << 8) | uartRX[0];
HAL_UART_Receive_IT(&huart1, (uint8_t *)RXbyte, 1);
}
}
[/code]
Немного опишу теорию приема.
Запускаю прием одного байта командой HAL_UART_Receive_IT(&huart1, (uint8_t *)RXbyte, 1).
Если принятый байт равен стартовому байту в отправленной пачке, то принимаю пачку из 3 байт HAL_UART_Receive_IT(&huart1, (uint8_t *)uartRX, 3). Если не равен, снова принимаю байт (ожидаю нужный мне байт)
Если принял пачку и контрольный третий байт равен ожидаемому, то формирую сигнал IRdist и снова принимаю один байт. if(huart1.RxXferSize == 1) этим смотрю что я принимал один байт или пачку.
В ответном устройстве аналогично принимается пачка из 9 байт, а отправляется 3 байта.

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

Вылезает такая ошибка, всю голова поломал.
.\Primer.axf: Error: L6218E: Undefined symbol USART_Cmd (referred from main.o). Использую Keil v.5

Павел
Павел
2 лет назад

У вас в программе ошибка:
В обработчике прерывания никак не проверяется что
usartCounter<numOfBytes
Из-за этого:
1. Передачe по USART нельзя будет остановить
2. Если по какой-либо причине пропустите проверку (usartCounter==numOfBytes) в основном цикле (например процессор будет занят обработкой других прерываний) – начнется передача содержимого памяти не предназначенного к отправке.

Gimran
Gimran
1 год назад

Если вы напишите статью про настройку логического анализатора в KEIL – это будет единственная статья в рунете на эту тему)

Ринат
1 год назад

Доброго дня (или ночи). Запустил USART и начал передавать данные на ПК. При отправке 1ки выдает первый символ по таблице ANSCII, 2ки второй символ и т. д. Это получается нужно отправлять данные закодированные по ANSCII или есть другой способ?

Присоединяйтесь!

Profile Profile Profile Profile Profile
Vkontakte
Twitter

Язык сайта

Август 2020
Пн Вт Ср Чт Пт Сб Вс
 12
3456789
10111213141516
17181920212223
24252627282930
31  

© 2013-2020 MicroTechnics.ru