Top.Mail.Ru

STM32F4 и USART. Настройка и передача данных.

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

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

По большому счету, тут все похоже на то, что мы видели при работе с контроллерами STM32F10x (обязательно посмотрите). В файле stm32fxx_usart.h видим определение структуры, в которой мы зададим нужные нам параметры работы:

typedef struct
{
	uint32_t USART_BaudRate;
	uint16_t USART_WordLength;
	uint16_t USART_StopBits;
	uint16_t USART_Parity;
	uint16_t USART_Mode;
	uint16_t USART_HardwareFlowControl;
} USART_InitTypeDef;

Собственно, все что надо тут есть – и скорость и формат кадра, так что для настройки модуля USART в STM32F4xx нам надо лишь создать структуру USART_InitTypeDef, заполнить ее поля нужными значениями и передать ее в качестве параметра в функцию USART_Init(). Эта функция, как и все остальные функции, находятся в SPL в файле sm32f4xx_usart.c.

Для отправки данных в USART нужно вызвать функцию USART_SendData(), для приема - USART_ReceiveData(). В STM32 USART может работать в режиме, поддерживающем протокол передачи данных LIN, соответственно, для этого в Standard Peripheral Library есть ряд функций для работы с LIN. Вообще приемо-передатчик в контроллерах STM32, как и все остальное, довольно навороченный – там и обычные прием и передача, и, опять же, поддержка LIN, и работа совместно с DMA. Вот для всего этого есть множество готовых функций, ну и, конечно же, еще функции для работы с прерываниями и для проверки различных флагов.

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

Итак, что вообще нужно сделать, чтобы USART запустился и начал работать:

  • Во-первых включаем тактирование модуля.
  • Затем включаем тактирование нужных портов контроллера (ножки Tx/Rx).
  • Настраиваем пины для работы в режиме альтернативных функций.
  • Запускаем модуль USART.
  • И, наконец, включаем нужные прерывания и начинаем высылать/принимать данные.

Вроде бы все, что нужно упомянул, переходим к делу. Создаем новый проект и пишем программу:

/***************************************************************************************/
// Цепляем нужные файлы
#include "stm32f4xx.h"
#include "stm32f4xx_rcc.h"
#include "stm32f4xx_gpio.h"
#include "stm32f4xx_usart.h"


/***************************************************************************************/
// Объявляем переменные
GPIO_InitTypeDef gpio;
USART_InitTypeDef usart;
// Пусть нам надо передать 8 байт, объявим массив для этих данных
uint8_t sendData[8];
uint8_t bytesToSend = 8;
// Счетчик отправленных байт
uint8_t sendDataCounter = 0;


/***************************************************************************************/
// Инициализация всего, что нам надо
void initAll()
{
	// Включаем прерывания
	__enable_irq();

	// Запускаем тактирование
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);

	// Инициализация нужных пинов контроллера, для USART1 – PA9 и PA10
	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);

	// И вот еще функция, которой не было при работе с STM32F10x, но которую нужно вызывать при использовании STM32F4xx
	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()
{
	// Для начала заполним массив тестовыми данными
	for (uint8_t i = 0; i < 8; i++)
	{
		sendData[i] = i;
	}

	// Вызываем функцию инициализации
	initAll();

	// Включаем прерывание по окончанию передачи
	USART_ITConfig(USART1, USART_IT_TC, ENABLE);
	
	while(1)
	{
	// А тут мы ничего не делаем, вся работа у нас в прерывании
		__NOP();
	}
}


/***************************************************************************************/
// Обработчик прерывания
void USART1_IRQHandler()
{
	// Проверяем, действительно ли прерывание вызвано окончанием передачи
	if (USART_GetITStatus(USART1, USART_IT_TC) != RESET)
	{
		// Очищаем флаг прерывания
		USART_ClearITPendingBit(USART1, USART_IT_TC);

		// Отправляем байт данных
		USART_SendData(USART1, sendData[sendDataCounter]);

		// Увеличиваем счетчик отправленных байт
		sendDataCounter++;

		// Если отправили все данные, начинаем все сначала
		if (sendDataCounter == bytesToSend)
		{
			sendDataCounter = 0;
		}
	}
}  


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

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

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

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

Это хорошо что есть люди которым не только интересно самому разобраться, но и находят время чтоб выложить свои достижения, пояснить профанам еще))) успехов!!!

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

Не могу выразить признательность вашим трудам. Огромное человеческое спасибо !!! Мне недавно поставили задачу : к контролеру stm32f407vgt6 подключить дисплей + клавиатуру и написать управление.
Дали STM32f4 - Discavery и сказали : давай к июлю нужно разобраться и сделать. У меня выпали глаза из глазниц и волосы из фолликул, так как раньше не имел удовольствия работать с контролерами, только прошивки правил для Ethernet - платок. В общем благодаря вам начали появляться хоть кусочки этой мозаики в моей голове и приходит хоть какое-то понимание ... Если можно потом по терзаю вас вопросами, когда они из общего разряда "АААААА как это все сделать !?!!?!" перейдут в более конкретный, осмысленый разряд.

Ktif
10 лет назад

Доброго времени суток. Если я правильно понял то на РА9 - выходят данные, а на РА10 - 1ца если смотреть осциллографом так?

Ярослав
Ярослав
10 лет назад

Здравствуйте, у Вас хорошие статьи.
Не могли бы Вы помочь мне при освоении карты Ethernet (DP83848) под эту Discovery?
Ну никак платка работать не хочет. Уже все примеры, которые были, перепробовал, перечитывал множество топиков и форумов - результата не дало.
Если можете, дайте код с подробными объяснениями, схему подключения, что нибудь.
Спасибо.

Ярослав
Ярослав
Ответ на комментарий  Ярослав
10 лет назад

e-mail: rozmathplus@gmail.com
(если можете сбросить какие-то файлы)

Ярослав
Ярослав
Ответ на комментарий  Ярослав
10 лет назад

Проекти пишу в IAR, CoIDE

Ярослав
Ярослав
10 лет назад

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

PAvel
PAvel
10 лет назад

Здравствуйте . Очень нравится ваш блог ссылка в избранных уже:) Пытаюсь завести USART по вашему примеру (CooCox) но STM32f4 — Discavery молчит ничего не передает . Пытался с другими примерами тоже никак. Есть вопрос про тактирование ,что плата по умолчанию тактируется от 8МГЦ это где то нужно поправлять?

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

диодиками моргаю нормально, находил исходник где генерируют файл через макрос с ним работает вот у меня возникает вопрос, что может такая загвоздка и у меня, в KEIL может сразу проект под плату Discovery stm34f4 создается и автоматом настраивает, в кокосе под чип по дефаулту.

Виталик
Виталик
10 лет назад

Привет!
Зачем нужно писать GPIO_StructInit(&gpio);
Включаем все ноги на вход, а потом две из них на AF? Зачем?

ATH
ATH
10 лет назад

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

ATH
ATH
10 лет назад

по окончанию передачи*

Виталик
Виталик
10 лет назад

до речі
1. закрийте тіло main-a
2. програма не працюватиме, якщо не причепитись до правильних ніг b6 b7, а не а9 а10.
3. дякую)

CROW
CROW
9 лет назад

Хочу задействовать два USART в МК STM32F4, настраиваю два USARTA`а но работает тока один почему так? Нужно принимать данные по USART 2 затем сохранять их в памяти и со скоростью 9600 отправлять из USART6 в ПК.

CROW
CROW
9 лет назад

Товарищи как одновременно принимать данные по USART2 на скорости 115200 сохранять в память и передавать на ПК с USART6 на скорости 9600?

ali00ff
9 лет назад

Ну так забей 2 отдельных инит. структуры
к примеру :
USART_InitTypeDef _MY_USART_INIT2;
USART_InitTypeDef _MY_USART_INIT6;
точнее 4, т.к. GPIO нежно как AF сконфигурировать, значит
GPIO_InitTypeDef _GPIO_AF_USART2_INIT;
GPIO_InitTypeDef _GPIO_AF_USART6_INIT;

Дальше все так же как в статье выше, НО

в той что инициализирует USART2
_MY_USART_INIT2.USART_BaudRate = 115200;
_MY_USART_INIT2.USART_Mode = USART_Mode_Rx;
-----------------------------------------------------------------------------------
Ну а ту что USART 6
_MY_USART_INIT2.USART_BaudRate = 9600;
_MY_USART_INIT2.USART_Mode = USART_Mode_Tx;

//------------------------------------------
А в остальном все так же.

ali00ff
9 лет назад

Ну и если на прерываниях, то разные "Хендлеры" для вызова прерываний на прием со 2ого и передачи с 6ого.

Описываешь их в маин, как ф-ции
void USART2_IRQHandler(void)
{
Твой прием
}

void USART6_IRQHandler(void)
{
твоя передача
}

А в инициализации
тож 2 раза 🙂
NVIC_EnableIRQ(USART2_IRQn);
NVIC_EnableIRQ(USART6_IRQn);
----------------------------------------------
все "хендлеры", ака обработчики при необходимости смотрите. в startup_stm32f4xx.s

там вот такого плана будет :
DCD SPI1_IRQHandler ; SPI1
DCD SPI2_IRQHandler ; SPI2
DCD USART1_IRQHandler ; USART1
DCD USART2_IRQHandler ; USART2
DCD USART3_IRQHandler ; USART3

========================================
К автору и хозяину статьи - надеюсь не помешал ?, просто рад помочь в твоем нужном, альтруистическом деле 🙂

Марк
Марк
8 лет назад

Здравствуйте! Пытался повторить данный пример, и столкнулся с небольшой проблемой. После передачи 0 байта массива, регистр SR заполняется нулями, передача останавливается. Тот же эффект если передавать данные в while
while(1)
{

for(i=0;iSR & USART_SR_TC));
}
}

Марк
Марк
8 лет назад

Прошу прощения
while(1)
{
for(i=0;iSR&USART_SR_TC));
}
}

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

Помогите пожалуйста с такой проблемой:
Я сделал программу основываясь на вашем примере, но есть косяк - постоянно срабатывает прерывание на окончание передачи(даже если ничего не передавал), оно срабатывает настолько часто, что никакой другой код не выполняется.
Подскажите как поступать в данном случае?!

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

Прикрутил printf как указано в https://blablacode.ru/mikrokontrollery/450
Но он работает если только я отключу NVIC_EnableIRQ(USART2_IRQn); закоментарив.
А если включу прерывания то printf не работает так как перехватывается вот здесь USART2_IRQHandler()

Как сделать чтобы одновременно работало и printf и работали прерывания?

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

Вопрос решился:

// see https://blablacode.ru/mikrokontrollery/450
// MyPutChar is instead of MyLowLevelPutchar in write.c
// for C:\Program Files\IAR Systems\Embedded Workbench 9.1\arm\src\lib\file\write.c
int MyPutChar(int x) 
{

 while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);
 USART_SendData(USART2, x);
 return x;
}

write.c:

write.c code
/*******************
 *
 * Copyright 1998-2017 IAR Systems AB.
 *
 * This is a template implementation of the "__write" function used by
 * the standard library. Replace it with a system-specific
 * implementation.
 *
 * The "__write" function should output "size" number of bytes from
 * "buffer" in some application-specific way. It should return the
 * number of characters written, or _LLIO_ERROR on failure.
 *
 * If "buffer" is zero then __write should perform flushing of
 * internal buffers, if any. In this case "handle" can be -1 to
 * indicate that all handles should be flushed.
 *
 * The template implementation below assumes that the application
 * provides the function "MyLowLevelPutchar". It should return the
 * character written, or -1 on failure.
 *
 ********************/

#include <LowLevelIOInterface.h>
  
#include "charm.h"

#pragma module_name = "?__write"

int MyLowLevelPutchar(int x);

/*
 * If the __write implementation uses internal buffering, uncomment
 * the following line to ensure that we are called with "buffer" as 0
 * (i.e. flush) when the application terminates.
 */

size_t __write(int handle, const unsigned char * buffer, size_t size)
{
 /* Remove the #if #endif pair to enable the implementation */
// #if 0

 size_t nChars = 0;

 if (buffer == 0)
 {
  /*
   * This means that we should flush internal buffers. Since we
   * don't we just return. (Remember, "handle" == -1 means that all
   * handles should be flushed.)
   */
  return 0;
 }

 /* This template only writes to "standard out" and "standard err",
  * for all other file handles it returns failure. */
 if (handle != _LLIO_STDOUT && handle != _LLIO_STDERR)
 {
  return _LLIO_ERROR;
 }

 for (/* Empty */; size != 0; --size)
 {
  // if (MyLowLevelPutchar(*buffer++) < 0)
  if (MyPutChar(*buffer++) < 0) // minem
  {
   return _LLIO_ERROR;
  }

  ++nChars;
 }

 return nChars;

// #else

 /* Always return error code when implementation is disabled. */
 return _LLIO_ERROR;

// #endif

}

Сейчас всё работает.

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

В write.c надо добавить:
#include "*.h"

Где *.h это файл с функцией MyPutChar(int x).

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