Итак, совсем недавно мы познакомились с приемо-передатчиком 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’ом, на днях обязательно выложу, буду рад, если кому-нибудь пригодится.
Было бы классно объединить 2 статьи (прием и передача) в одну.
Скажем передаем с USART1 "led1" и загорается определенный светодиод, а если принят мусор или что-то ещё загорается другой... ну как-то так. Это было бы наглядно, и понятно. У меня пока только STMка активно передает, а вот с приемом что-то не то.
STM32F3[4]-discaveri (код для 3 от 4ой не очень отличается, проверил, даже пины для усарт1 одинаковы)
PS а ещё, было бы офигенно раскурить пример PWM с изменением яркости светодиода, на пальцах, и с STM либами 🙂
Постараюсь скоро намутить такие примеры )
В AVR прерывание срабатывает по приему одного байта. А в stm как тогда?
Аналогично
Спасибо за статью. Пытаюсь разобраться с USART в STM32F3FDISCOVERY . С передачей проблем нет. Проблема с приемом - принимается только первый байт. Следующий байт не замещает первый. Как вытолкнуть принятый байт?..
Все должно само замещаться..Если все в линии нормально, то есть байты действительно высылаются контроллеру, то что-то не так в коде
Я пробовал, но что-то пошло не так... Инит, судя по всему, останавливал все процессы в организме отладочника... Заработало при такой комбинации:
(Ах, да, я использовал третий уарт, т.к. в СТМ_Дискавери на первом программатор ещё висит и мусорит мне в терминал, позже буду на первый переходить как свою плату сделаю...)
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);
Из явного могу отметить только отсутствие включения прерываний ручного, да блок инициализации часов уарта. Из прерывания я сделал эхо, а так оно работает без изменений...
Спасибо за статью! Реально помогла без особой головной боли настроить приём информации с терминала.
Спасибо за пример, все отлично. Но вот вопрос: как отправить данные на плату?
Как реализовать общение пк и микросхемы?
Как вариант, на плате USART преобразовать в RS232, и с ПК в любом терминале отправлять
Подскажите, пожалуйста.
Если использую следующую инициализацию:
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, то первая что делает?
Можно и так и так в принципе. В первом варианте заполняются поля структуры usart3 и затем вызывается функция инициализации
Т.е., если я буду использовать систему приоритетов прерываний, то мне НУЖНО использовать эти строки с полями структуры. При этом НЕТ НЕОБХОДИМОСТИ использовать NVIC_EnableIRQ(USART3_IRQn)? (это вопрос один). И, наоборот. Если используются прерывания и НЕ НУЖНЫ приоритеты, то ДОСТАТОЧНО использовать ТОЛЬКО NVIC_EnableIRQ(USART3_IRQn) БЕЗ ЗАПОЛНЕНИЯ полей структур? (это вопрос два)
Я правильно понимаю?
Да
что делает строчка
gpio.GPIO_PuPd = GPIO_PuPd_UP;
на F100 F303 вроде она не используется, стало интересно!
Подтяжку вверх просто активизирует. Там еще есть PuPd_DOWN.
Извините, что немного оффтоп, но не могли бы вы подсказать почему заголовок функции прерывания по 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 есть конструкции для этого прерывания.
проблемой разобрался. Я забыл в хейдере написать прототип прерывания.
Здравствуйте! Зашил предложенную тут программу, но на приёме вместо 0х11 ... 0х88 вижу массив из этого:
"\300\300w\367\200\300\300w\367\200\300\300w\367\200\300"
Что это? Пишу в CooCox. смотрю через Debug.
И массив при отправке выглядит вот так:
"21\"3DUfw"
Что то с отображением?
Отправка из чего осуществляется?
2 платы STM32F4Discovery
Электрически все верно, скорости совпадают, Rx|Tx перекрещены?
Код именно этот и Tx/Rx перекрещены.
Если хочешь скинь на почту код - я посмотрю, может удастся найти в чем проблема.
Могу скинуть, но код из этой статьи, я его не изменял.
Ну в этой статье вообще нет кода для отправки
т.е. из предыдущей.
А для работы USART сколько надо проводников? 2? TX и RX? или надо ещё что-то?
Да, 2, но обязательно необходимо обеспечить общую землю на коммутируемых устройствах.
а что такое тогда USART1_CK ??? в каких случаях это нужно?
При использовании синхронного режима
Попробовал объединить оба примера в один, отправка и получение данных, но не работает:
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/
Что надо сделать чтобы одновременно работал приём и отправка?
Доброго дня! Здесь библиотеки устаревшие используются, лучше сразу смотреть в сторону актуальных инструментов - https://microtechnics.ru/stm32-uart-priem-i-peredacha-dannyh-po-uart-v-stm32cubemx/
Салют!
В актуальных инструментах используется пример с двумя: 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);
А что конкретно не работает? Прерывание по одному из событий или вообще прерывание как таковое? Пробуй так:
По поводу актуальных инструментов ) USART2:
Но там тоже полно своих нюансов и проблем, оптимально - самому руками обработать регистры/ошибки итд
"самому руками обработать регистры/ошибки"
это использовать?:
int main()
{
....
while(!(USART2->SR & USART_SR_TC))
{
USART2->DR = data_to_send; // Передача данных в терминал
}
while(!(USART2->SR & USART_SR_RXNE))
{
data_received = USART2->DR; // Получение данных из терминала
}
}
Не совсем, также на прерываниях, если они требуются, в этом плане логика и структура сохраняется.
Просто в HAL (новые библиотеки) для USART'а в части приема есть особенности. В частности, насколько я помню, в начале обработчика прерывание деактивируется, затем на выходе включается заново. Если принимать по одному байту, то есть отличные шансы словить overrun, потому что байт может прийти в тот момент, когда прием "отключен". Надо смотреть по конкретной задаче, если использование HAL приводит к проблемам, то нужно критичные куски переписать либо полностью на работу с регистрами напрямую, либо у ST есть LL драйвер, более низкоуровневый.
Спасибо огромное!:
USART_ITConfig(USART2, USART_IT_TC, ENABLE);
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);
:это заставило работать.
Оказывается надо было не в одну кучу "
stepbystep
USART_IT_TC | USART_IT_RXNE", а последовательно шаг за шагом:USART_ITConfig(USART2, USART_IT_TC, ENABLE);
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);
Ну да, я там пробежался мельком по коду библиотеки, явно она может переварить только один флаг.
Отлично, что заработало!
Осталось прикрутить printf к USART2.