UART (USART) на STM32L (STM32)

Введение
На момент написания данной статьи в сети существовало множество примеров по работе с UART’ом на микроконтроллерах серии STM32. В основном данные примеры сводятся к приему/передачи одного байта без использования прерываний. Основной акцент в статьях делается на инициализации портов ввода/вывода и UART’а, с небольшими примерами. В некоторых статьях описывается работа прерываний, но не создаются полноценные функции приема/передачи.Однако все эти примеры достаточно разрознены и мне не удалось найти одного исчерпывающего примера по работе с UART'ом. Поэтом, в данной статье ставиться целю объединение воедино накопившегося опыта и создание полноценных функций для работы с UART с использованием прерываний, для чего детально разбирается устройство интерфейса применительно к микроконтроллерам STM32L.
1. Обзор существующих статей:
[1] Статья уважаемого DI HALT на сайте easyelectronics.ru.
ARM Учебный курс. USART (http://easyelectronics.ru/arm-uchebnyj-kurs-usart.html)[2] Статья на сайте easyelectronics.ru.
Подробно рассматривается пример работы с UART'ом с использованием прерываний, без использования стандартной библиотеки. Также внутри статьи есть полезные ссылки:
STM32 usart на прерываниях на примере RS485 (http://we.easyelectronics.ru/STM32/stm32-usart-na-preryvaniyah-na-primere-rs485.html)[3] Серия статей на сайте chipspace.ru.
Рассматривается пример работы с UART'ом с использованием прерываний, без написания специальных функций.
STM32. USART. Часть 1 (http://chipspace.ru/stm32-usart-1/)[4] Статья на сайте cxem.net
Небольшие общие слова об интерфейсе UART и сопутствующих интерфейсах (LIN, IrDA, ISO/IEC 7816). Пример подключения FT232L. Описание регистров (USART_SR, USART_DR, USART_BRR, USART_CR1, USART_CR2, USART_CR3). Приведен пример кода без использования SPL.STM32. USART. Часть 2 (http://chipspace.ru/stm32-usart-2/)
Изменения к части 1, касающиеся STM32LSTM32. USART. Часть 3 (http://chipspace.ru/stm32-usart-3/)
Дополнение к части 1,2. Использование стандартной библиотеки.STM32. USART. Часть 3 (http://chipspace.ru/stm32-usart-4/)
Пример кода двустороннего обмена данными
STM32. Урок 3. UART (http://cxem.net/mc/mc198.php)[5] Статья на сайте alex-exe.ru
Подключение STM32F4-Discovery к компьютеру через COM порт. Схема переходника на MAX3232. Использование PuTTY. Пример кода с написанием функции send_to_uart, и приемом данных с использованием прерывания.
STM32. 4. Последовательный порт (UART) (http://alex-exe.ru/radio/stm32/stm32-uart-spl/)[6] Цикл статей на easystm32.ru
Подключение STM32VL-Discovery к компьютеру. Схема переходника USP-UART на CP2102. Пример кода с написанием функции send_Uart, getch_Uart, send_Uart_str.Без использования прерываний.
UART в STM32. Часть 0 (http://easystm32.ru/interfaces/14-uart-in-stm32-part-0)
Общие сведения о UART’е. Пример осциллограммы. Переходники на CP2102 и MAX3232.UART в STM32. Часть 1 (http://easystm32.ru/interfaces/15-uart-in-stm32-part-1)
Последовательность настройки. Описание регистров (USART_DR, USART_BRR, USART_CR1, USART_CR2). Пример отправки без прерываний.UART в STM32. Часть 2 (http://easystm32.ru/interfaces/16-uart-in-stm32-part-2)
Пример приема данных без прерываний.
[7] Серия статей на сайте ziblog.ru.
1. STM32 Часть 12 — Универсальный асинхронный приёмопередатчик (UART) (http://ziblog.ru/2011/04/15/stm32-chast-12-universalnyiy-asinhronnyiy-priyomoperedatchik-uart.html)
Рассмотрена работа UART'а с использованием прерываний применительно к STM32. Приводиться пример файла инициализации startup.c и makefile'а.2. STM32–USART FIFO (http://ziblog.ru/2011/04/18/stm32-ndash-usart-fifo.html)
Статья посвящена портированию буфера FIFO из библиотек STM8L
[8] Статья на сайте microtechnics.ru.
STM32 с нуля. USART. Пример программы (http://microtechnics.ru/stm32-uchebnyj-kurs-usart/)
Краткое описание регистров (USART_SR, USART_DR, USART_BRR, USART_CR1, USART_CR2, USART_CR3, USART_GTPR). Пример обмена данными с компьютером. Отправка без прерываний. Прием по прерыванию.
Вот вроде бы и все, что выдается в первых результатах googl'а. Стоит отметить, что в STM32L регистры немного отличаются от STM32F. Будьте внимательны!
2. Что необходимо сделать, чтобы все заработало.
Для того чтобы воспользоваться всеми удобствами, которые предоставляет нам интерфейс UART необходимо проделать ряд действий. А именно:- Корректно подключить к микроконтроллеру минимальную обвязку.
- Согласовать уровни приема/передачи данных, скорость передачи данных, длину пакета данных, контроль четности, количество стоповых бит, способ управления потоком. Для работы в синхронном режиме необходимо согласовать полярность и фазу тактового сигнала (по спаду или по фронту импульса происходит прием данных), а также будет ли тактироваться последний переданный бит.
- Настроить соответствующие ноги микроконтроллера
- Настроить параметры USART’а
- Настроить прерывания
- Написать необходимые функции.
Далее рассмотрим по порядку перечисленные действия.
3. Подключение микроконтроллера и минимальная обвязка.
Основной особенностью при проектировании схемы с использованием UART’а является наличие внешнего кварца. Дело в том, что при работе в асинхронном режиме интервалы стробирования бит данных определяются на основе тактовой частотой процессора, и если частоты приемника и передатчика за время передачи посылки разойдутся более чем на 2%, то правильность приема данных не гарантирована. Поэтому при использовании внутреннего источника тактирования могут возникнуть проблемы, по причине не высокой стабильности его частоты. Эти проблемы могут и не возникнуть, однако, я лично предпочитаю для надежности устанавливать кварц.В остальном, нет ничего специфичного в подключении микроконтроллера для работы с UART’ом.
4. Согласование уровней, настройка параметров передачи.
В общем и целом схема подключения по UART’у выглядит следующем образом:

Данные от передающего пина (Tx) одного модуля поступают на вход принимающего пина (Rx) другого модуля. Необходимо наличие общей земли.
Однако при работе с UART’ом не стоит забывать, что разные устройства, использующие данный интерфейс, могут иметь различные логические уровни. А подключение устройств без согласования уровней может привести к поломке. Так в документации на STM32L приводиться следующая информация относительно логических уровней (информация приводиться в datasheet’е на конкретный чип):


Из таблиц 42 и 43 видно, что низкий уровень входного сигнала должен быть ниже 0.3*Vdd, а высокий уровень должен быть выше 0.7*Vdd, но не выше Vdd+0.3 (5.25 для толерантных входов). Также видно, что производители обещают, что выходной сигнал не будет отличаться от 0/Vdd более чем на 0.45 вольта.
При этом логические уровни COM порта персонального компьютера составляют от +12 V до -12V, микроконтроллеров AVR от +5 до 0 (при питании от 5 вольт), GSM модуля SIM900 от +3 до 0.
Исходя из этого, можно сделать несколько выводов:
- подключить напрямую STM32 к COM порту компьютера не удастся, необходимо будет использовать преобразователи серии MAX232, рассчитанным на работу от 3.3 вольта (на пример MAX3232).
- подключить напрямую STM32 к AVR (при питании +5V), можно. Для этого необходимо подключить Tx пин AVR к толерантному Rx пину STM32. (список толерантных пинов можно посмотреть в datasheet’е на контроллер). Tx пин STM32 (не обязательно толерантный) напрямую можно подключить к AVR, если питание STM32 больше 3,3 вольта. AVR согласно datasheet’у принимает за высокий уровень сигнал выше 0.6*Vdd=3 вольта.
- подключить напрямую STM32 (при питании больше 3,0 вольта) к GSM модулю SIM900 нельзя, т.к. высокий уровень STM32 будет превышать максимально допустимый уровень GSM модуля. Необходимо согласование уровней.
Лучше всего согласование уровней делать с применением специальных микросхем, например 74LVC2T, MAX232 и т.п., главное обращать внимание на максимальную скорость работы. Например, для MAX232 она составляет 120 kbit/s, то есть можно установить типичный битрейт в 115,2 кбод, не больше. У микросхемы 74LVC2T максимальная скорость не указана, но указано время задержки прохождения сигнала < 8 нс, для сравнения у MAX232 время прохождения сигнала < 500 нс. То есть 74LVC2T примерно в 60 раз быстрее, и максимальный битрейт можно установить в несколько мегабит.
Помимо специальных микросхем применяют согласование на транзисторах. Например, как на рисунке.

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

Как замечание, стоит сказать, что если используется аппаратный контроль потока, то необходимо также произвести согласование дополнительных линий управления потоком.
5. Настройка ног микроконтроллера.
Для работы UART’а необходимо правильно настроить ноги микроконтроллера. Я обычно использую стандартную библиотеку для работы с переферией (SPL).
В моем случае, для использования USART2, надо настроить ногу PD5 на выход, а ногу PD6 на вход.
Настройка ног в данном случае выглядит следующим образом:
- Разрешаем тактирование соответствующего порта.
- Назначаем альтернативную функцию для ног
- Выбираем режим работы ноги
- Инициализируем ногу
Соответственно в datasheet'е необходимо убедиться какая у данной ноги альтернативная функция

GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOD, ENABLE); //разрешаем тактирование
//назначаем альтернативные функции
GPIO_PinAFConfig(GPIOD, GPIO_PinSource5, GPIO_AF_USART2); //PD5 to TX USART2
GPIO_PinAFConfig(GPIOD, GPIO_PinSource6, GPIO_AF_USART2); //PD6 to RX USART2
//заполняем поля структуры
// PD5 -> TX UART.
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_40MHz;
GPIO_Init(GPIOD, &GPIO_InitStructure); //инициализируем
//PD6 -> RX UART.
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_Init(GPIOD, &GPIO_InitStructure);//инициализируем
Здесь ничего сложного нет. Главное убедиться, что настроены именно нужные ноги и нужный UART.
6. Настройка UART’а
Настройка UART’а происходит подобным образом с использованием стандартной библиотеки.
Первым делом не забываем подключить библиотечные файлы (stm32l1xx_usart.x).
Далее в файле stm32l1xx_conf.h раскоментим строчку #include «stm32l1xx_usart.h»
Затем смотрим в datasheet, отмечаем, что USART2 находится на шине APB1. Именно частота данной шины будет использоваться в качестве Fclk в формулах расчета битрейта. Если есть желание посчитать битрейт в ручную, необходимо сначала точно определить частоту данной шины, и иметь в виду, что при установленном бите OVER8 в регистре настроек, скорость работы UART'а вдвое выше.
Удобно, что стандартная библиотека избавляет нас от необходимости
В целом настройка UART выглядит следующим образом:
USART_InitTypeDef USART_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); //Разрешаем тактирование
/* Стандартные настройки COM порта ПК----------------------------------*/
/* USART2 configured as follow:
- BaudRate = 9600 baud
- Word Length = 8 Bits
- One Stop Bit
- No parity
- Hardware flow control disabled (RTS and CTS signals)
- Receive and transmit enabled
*/
USART_InitStructure.USART_BaudRate = 9600;// скорость
USART_InitStructure.USART_WordLength = USART_WordLength_8b; //8 бит данных
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_OverSampling8Cmd(ENABLE); //можно уменьшить частоту семплирования
//USART_OneBitMethodCmd(ENABLE); //можно уменьшить количество стробирований
//USART_HalfDuplexCmd (ENABLE); // можно выбрать полудуплексный режим.
USART_Init(USART2, &USART_InitStructure); //инизиализируем
Поясню некоторые функции
USART_OverSampling8Cmd(ENABLE) позволяет уменьшить количество семплов и колчичество стробирований. Дело в том, что изначально на каждый принятый бит происходит 16 замеров уровня, из них для определения уровня выбираются 3 средних, остальные используются для контроля за ошибками уровня (см. рис.)

По этой причине, скорость передачи данных не может быть быстрее чем 1/16 от частоты шины APB1.
Функция USART_OverSampling8Cmd(ENABLE) позволяет уменьшить количество семплирований вдвое, тем самым увеличить максимальную скорость передачи до 1/8. А функция USART_OneBitMethodCmd(ENABLE) позволяет уменьшить количество стробирований до 1. (5 семпл на рисунке) (см. рис.)

Функция USART_HalfDuplexCmd (ENABLE) переводит UART в полудуплексный режим, при котором передача осуществляется по одному проводу, подключенному к ноге (Tx) при этом пин Rx освобождается (см. рис.)

Тут надо подумать как именно настраивается пин Tx, я склонен думать, что как обычно: на выход, в режиме пуш-пул. Но нигде не подвернулась документация по этому поводу. Сам лично не проверял. Если кто из специалистов точно знает — пожалуйста подскажите.
Глядя на рисунок, так надо настроить в режиме Открытый сток с подтяжкой к плюсу.
Помимо данных настроек при работе в синхронном режиме USART, необходимо настроить параметры тактирования, а именно, по спадающему или возрастающему переднему или заднему фронту сигнала происходит стробирование, выводить ли последний импульс (см. рис.)

Пример инициализации
USART_ClockInitTypeDef USART_ClockInitStructure;
USART_ClockInitStructure.USART_Clock=USART_Clock_Enable;
USART_ClockInitStructure.USART_CPHA=USART_CPHA_1Edge;
USART_ClockInitStructure.USART_CPOL=USART_CPOL_High;
USART_ClockInitStructure.USART_LastBit=USART_LastBit_Enable;
USART_ClockInit(USART2, &USART_ClockInitStructure);
Стоит отметить, что после настройки периферии, т.е. после выполнения пунктов 1-4 из раздела 2, можно начинать работу с UART’ом без использования прерываний (разрешив его использование командой USART_Cmd(USART2, ENABLE)). Так, например, сделано в некоторых из приведенных в самом начале статьи примеров. В данном случае отправка байта сводиться к заполнению регистра USART2->DR данными, предварительно проверив, завершена ли предыдущая передача.
Пример
while(!(USART2->SR & USART_SR_TC)); //Проверка завершения передачи предыдущих данных
USART2->DR = data; //Передача данных
или с использованием стандартной библиотеки командами
while (USART_GetFlagStatus(USART2, USART_FLAG_TC) == RESET);
USART_SendData(USART2,data);
Прием данных осуществляется аналогично: считыванием данных из регистра USART2->DR, предварительно проверив, пришли ли данные.
while(!(USART2->SR & USART_SR_RXNE)); //Ждем поступления данных от компьютера
data = USART2->DR; //считываем данные
или с использованием стандартной библиотеки командами
while (USART_GetFlagStatus(USART2, USART_FLAG_RXNE) == RESET);
data =USART_ReceiveData (USART2);
Не нужно пугаться, что мы и пишем и читаем из одного и того же регистра USART2->DR. На самом деле за одним именем скрываются два различных регистра, поэтому чтение и запись происходят в разных местах и не влияют на данные друг друга.
Такой способ отправки и приема очень прост в реализации, но имеет существенный недостаток: необходимость ожидания установления соответствующего флага. Пока флаг не установился – программа висит в ожидании, что не есть хорошо.
Для того чтобы избавиться от длительного ожидания факта поступления/передачи данных используют прерывания
7. Настройка прерываний.
Настройку прерываний также произведем с использованием стандартной библиотеки.
Первым делом не забываем подключить библиотечные файлы (misc.x).
Далее в файле stm32l1xx_conf.h раскоментим строчку #include «misc.h»
Настройка выглядит следующим образом
NVIC_InitTypeDef NVIC_InitStructure;
/* Configure the Priority Group to 2 bits */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //конфигурируем количество групп и подгрупп прерываний, пока у нас одно прерывание нам это особо ничего не дает
/* Enable the USARTx Interrupt */
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn; //прерывание по uart2
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //задаем приоритет в группе
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //задаем приоритет в подгруппе
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //разрешаем прерывание
NVIC_Init(&NVIC_InitStructure); //инициализируем
Данным участком программы была разрешена генерация прерываний от UART’а в принципе, в частности же, прерывания от UART’а могут генерироваться по факту наступления одного из событий:
- USART_IT_CTS: Прерывание по изменению состояния CTS
- USART_IT_LBD: LIN Break detection interrupt.
- USART_IT_TXE: Прерывание по опустошению регистра передачи
- USART_IT_TC: Прерывание по окончанию передачи.
- USART_IT_RXNE: Прерывание по факту приема данных.
- USART_IT_IDLE: Idle line detection interrupt.
- USART_IT_PE: Прерывание по факту ошибки четности.
- USART_IT_ERR: Прерывание по факту ошибки (Frame error, noise error, overrun error).
Необходимо разрешить одно из этих событий. Разрешим генерировать прерывание по приему и передачи
UPD:
Спасибо комментариям, так делать нельзя, правильно
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);
USART_ITConfig(USART2, USART_IT_TXE, ENABLE);
Стоит отметить отличие между флагами USART_IT_TXE и USART_IT_TС. Первый срабатывает, как только освободился буфер и мы можем смело кидать еще данных, второй — когда передача полностью завершилась и мы можем смело отключать UART.

На последок разрешим использовать UART.
USART_Cmd(USART2, ENABLE);
С настройкой прерываний все. Осталось написать обработчики прерываний.
8. Написание необходимых функций
Общая идея работы с UART’ом будет следующая: Создадим два буфера – буфер приема и буфер передачи. В программе вызываются привычные функции: put_char(), put_string(), get_char() которые работают с этими буферами. Работа с буферами не требует циклов ожидания.
Далее как только прилетели какие-нибудь данные, обработчик прерывания складывает данные в буфер, а программа уже по необходимости их от туда забирает. Никакие данные не теряются. В случае передачи обработчик при первом удобном случае сам возмет очередной байт из буфера и самостоятельно отправит его.
Будем организовывать два кольцевых буфера (на прием и передачу). Хорошо про реализацию кольцевого буфера написано в статье «Учебный курс. Организация обмена по USART `у с использованием кольцевого буфера» (http://chipenable.ru/index.php/programming-avr/item/44-uchebnyy-kurs-organizatsiya-obmena-po-usart-u-s-ispolzovaniem-koltsevogo-bufera.html)
Определим буферы
// Буфер на прием
#define RX_BUFFER_SIZE 350 // размер буфера
volatile uint8_t rx_buffer[RX_BUFFER_SIZE];
volatile uint16_t rx_wr_index=0, //индекс хвоста буфера (куда писать данные)
rx_rd_index=0, //индекс начала буфера (откуда читать данные)
rx_counter=0; //количество данных в буфере
volatile uint8_t rx_buffer_overflow=0; //информация о переполнении буфера
// Буфер на передачу
#define TX_BUFFER_SIZE 350 //размер буфера
volatile uint8_t tx_buffer[TX_BUFFER_SIZE];
volatile uint16_t tx_wr_index=0, //индекс хвоста буфера (куда писать данные)
tx_rd_index=0, //индекс начала буфера (откуда читать данные)
tx_counter=0; //количество данных в буфере
Напишем обработчик прерывания в следующем виде.
void USARTx_IRQHandler(void)
{
if(USART_GetITStatus(USART2, USART_IT_RXNE) == SET) //прерывание по приему данных
{
if ((USART2->SR & (USART_FLAG_NE|USART_FLAG_FE|USART_FLAG_PE|USART_FLAG_ORE)) == 0)) //проверяем нет ли ошибок
{
rx_buffer[rx_wr_index++]= (uint8_t) (USART_ReceiveData(USART2)& 0xFF); //считываем данные в буфер, инкрементируя хвост буфера
if (rx_wr_index == RX_BUFFER_SIZE) rx_wr_index=0; //идем по кругу
if (++rx_counter == RX_BUFFER_SIZE) //переполнение буфера
{
rx_counter=0; //начинаем сначала (удаляем все данные)
rx_buffer_overflow=1; //сообщаем о переполнении
}
}
else USART_ReceiveData(USART2); //в идеале пишем здесь обработчик ошибок, в данном случае просто пропускаем ошибочный байт.
}
if(USART_GetITStatus(USART2, USART_IT_ORE_RX) == SET) //прерывание по переполнению буфера
{
USART_ReceiveData(USART2); //в идеале пишем здесь обработчик переполнения буфера, но мы просто сбрасываем этот флаг прерывания чтением из регистра данных.
}
if(USART_GetITStatus(USART2, USART_IT_TXE) == SET) //прерывание по передачи
{
if (tx_counter) //если есть что передать
{
--tx_counter; // уменьшаем количество не переданных данных
USART_SendData(USART2,tx_buffer[tx_rd_index++]); //передаем данные инкрементируя хвост буфера
if (tx_rd_index == TX_BUFFER_SIZE) tx_rd_index=0; //идем по кругу
}
else //если нечего передать, запрещаем прерывание по передачи
{
USART_ITConfig(USART2, USART_IT_TXE, DISABLE);
}
}
}
UPD:
Поясню, что команда USART_ReceiveData(USART2)& 0xFF введена для того, что существует режим работы UART'а в котором передаются 9 бит, в данном случае (поскольку буферы 8 битные) старший бит теряется. Если мы не хотим его терять, то необходимо изменить тип буферов.
Отдельно необходимо прокомментировать поведение флагов прерывания. Многие могут заметить, что необходимо сбрасывать флаги прерываний командой USART_ClearITPendingBit(). Однако некоторые флаги прерывания сбрасываться автоматически после выполнения определенной последовательности комманд. В частности, флаг приема данных USART_IT_RXNE сбрасывается, как только происходит чтение из регистра USART2->DR командой USART_ReceiveData. UPD: Если чтение не произошло, то его необходимо сбросить вручную. Флаг успешной передачи данных USART_IT_TС сбрасывается после последовательного чтения регистра USART2->SR и записи в регистр USART2->DR командами USART_GetITStatus иUSART_SendData. Флаг USART_IT_TXE сбрасывается только записью в регистр USART2->DR командой USART_SendData. Для удобства существует команда USART_ClearITPendingBit(), которая позволяет вручную сбрасывать флаги прерываний, но она не может сбрасывать флаг USART_IT_TXE, который всегда установлен, если буфер пуст. Поэтому нам необходимо запретить генерацию прерывания командой USART_ITConfig(USART2, USART_IT_TXE, DISABLE). В противном случае, сразу же после выхода из прерывания мы бы попали туда снова и программа бы зациклилась.
Рассмотрим более подробно флаги прерываний. Поскольку, как уже отмечалось выше, прерывание для USART только одно, внутри обработчика используются флаги, для того, чтобы определить источник прерывания. Также флаги используются еще и для сигнализирования об ошибках. Для определения источника прерывания в стандартной библиотеке используется команда USART_GetITStatus с параметрами
USART_IT_CTS: прерывание по изменению CTS (не доступно для UART4 и UART5)
USART_IT_LBD: LIN Break detection interrupt
USART_IT_TXE: прерывание по опустошению буфера передачи
USART_IT_TC: прерывание по завершению передачи
USART_IT_RXNE: прерывание по наличию данных в приемном буфере
USART_IT_IDLE: Idle line detection interrupt
USART_IT_ORE_RX: Прерывание по переполнении при установленном бите RXNEIE
USART_IT_ORE_ER: Прерывание по переполнении при установленном бите EIE
USART_IT_NE: прерывание из-за «шума» на линии
USART_IT_FE: прерывание из-за ошибки пакета
USART_IT_PE: прерывание из-за ошибки четности
Данная команда проверяет наличие флага прерывания, а также наличие разрешающего данное прерывания бита в соответствующем регистре (Поскольку возможна ситуация, когда флаг установлен, а данный источник прерывания запрещен. Следовательно прерывание вызвал кто-то другой.) Флаг прерывания необходимо сбросить в обработчике прерывания. Это можно сделать либо явно вызвав функцию USART_ClearITPendingBit(), либо неявно выполнив необходимую последовательность действий, например, чтение USART2->SR и USART2->DR.
Что касается флагов, сообщающих об ошибках, то к ним относятся флаги
USART_FLAG_CTS: CTS Change flag (not available for UART4 and UART5).
USART_FLAG_LBD: LIN Break detection flag.
USART_FLAG_TXE: Transmit data register empty flag.
USART_FLAG_TC: Transmission Complete flag.
USART_FLAG_RXNE: Receive data register not empty flag.
USART_FLAG_IDLE: Idle Line detection flag.
USART_FLAG_ORE: OverRun Error flag.
USART_FLAG_NE: Noise Error flag.
USART_FLAG_FE: Framing Error flag.
USART_FLAG_PE: Parity Error flag.
состояние которых можно узнать функцией USART_GetFlagStatus, либо напрямую в чтением регистра USART2->SR.
Нас интересуют ошибки по приему данных
USART_FLAG_ORE: Переполнение буфера.
USART_FLAG_NE: ишибка из-за «шумов» на линии.
USART_FLAG_FE: ошибка пакета.
USART_FLAG_PE: ошибка четности.
Данные ошибки мы проверяем и, по-хорошему, обрабатываем. (В примере, битый байт просто пропускается). Стоит отметить, что данные флаги нельзя сбросить явно с помошью функции USART_ClearFlag(), можно только неявно прочитав сначала USART2->SR затем USART2->DR.
Далее отмечу, что разрешая прерывание по приему данных, автоматически разрешается прерывание по переполнению буфера. Его тоже необходимо обработать. Флаг о переполнении сбрасывается последовательностью чтения регистров USART2->SR и USART2->DR командами USART_GetITStatus и USART_ReadData. Поскольку флаг о переполнении буфера устанавливается, только если уже установлен флаг RXNE, то в большинстве случаев сброс флага данного прерывания произойдет в обработчике прерывания по приему. Однако существует вероятность, что переполнение произошло в момент между чтением регистра USART2->SR и регистра USART2->DR. В таком случае прерывание о переполнении произойдет отдельно от прерывания по приему и именно такой случай нужно обработать отдельно.
Теперь напишем функции для передачи/приема одного байта
uint8_t get_char(void) //прием данных
{
uint8_t data; //переменная для данных
while (rx_counter==0); //если данных нет, ждем
data=rx_buffer[rx_rd_index++]; //берем данные из буфера
if (rx_rd_index == RX_BUFFER_SIZE) rx_rd_index=0; //идем по кругу
USART_ITConfig(USART2, USART_IT_RXNE, DISABLE); //запрещаем прерывание
--rx_counter; //чтобы оно не помешало изменить переменную
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);//разрешаем прерывание
return data;
}
void put_char(uint8_t c) //вывод данных
{
while (tx_counter == TX_BUFFER_SIZE); //если буфер переполнен, ждем
USART_ITConfig(USART2, USART_IT_TXE, DISABLE); //запрещаем прерывание, чтобы оно не мешало менять переменную
if (tx_counter || (USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET)) //если в буфере уже что-то есть или если в данный момент что-то уже передается
{
tx_buffer[tx_wr_index++]=c; //то кладем данные в буфер
if (tx_wr_index == TX_BUFFER_SIZE) tx_wr_index=0; //идем по кругу
++tx_counter; //увеличиваем счетчик количества данных в буфере
USART_ITConfig(USART2, USART_IT_TXE, ENABLE); //разрешаем прерывание
}
else //если UART свободен
USART_SendData(USART2,c); //передаем данные без прерывания
}
Здесь стоит обратить внимание на строчки:
while (rx_counter==0);
...
while (tx_counter == TX_BUFFER_SIZE);
В данном случае все же была организована преднамеренная задержка. В первом случае необходимо ждать, пока данные не поступили, во втором — пока буфер переполнен. Важно отметить, что переменные rx_counter и tx_counter должны быть volatile. Иначе компилятор может провести оптимизацию этих циклов в бесконечные, не подозревая, что переменные могут быть изменены в обработчике прерываний. И вообще, все указатели на индексы буфера и сам буфер лучше сделать volatile, чтобы
Для удобства можно написать еще функцию передачи строки
void put_string(unsigned char *s)
{
while (*s != 0)
put_char(*s++);
}
И функцию передачи целого числа
void put_int(int32_t data)
{
unsigned char temp[10],count=0;
if (data<0)
{
data=-data;
put_char('-');
}
if (data)
{
while (data)
{
temp[count++]=data%10+'0';
data/=10;
}
while (count)
put_char(temp[--count]);
}
else put_char('0');
}
После этого можно полностью насладиться работой UART’а.
Теперь коротко пробежимся по основным этапам:
1. Собираем схему, желательно с внешним кварцем
2. Согласовываем логические уровни USART.
3. Настраиваем ноги, USART, вектор прерываний
a. Подключаем библиотечные файлы stm32l1xx_rcc.x, stm32l1xx_gpio.x, stm32l1xx_usart.x, misc.x, stm32l1xx_conf.h в последнем файле комментируем подключение лишних заголовочных фалов, в настройках компилятора объявляем USE_STDPERIPH_DRIVER, прописываем пути к файлам. В main() добавляем #include «stm32l1xx_conf.h»
b. Настраиваем ноги по аналогии с примером
void USART_init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOD, ENABLE);
GPIO_PinAFConfig(GPIOD, GPIO_PinSource5, GPIO_AF_USART2); //PD5 to TX USART2
GPIO_PinAFConfig(GPIOD, GPIO_PinSource6, GPIO_AF_USART2); //PD6 to RX USART2
// PD5 -> TX UART.
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_40MHz;
GPIO_Init(GPIOD, &GPIO_InitStructure);
//PD6 -> RX UART.
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_Init(GPIOD, &GPIO_InitStructure);
//USART
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
USART_InitStructure.USART_BaudRate = 9600;
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_Init(USART2, &USART_InitStructure);
/* NVIC configuration */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
/* Enable USART */
USART_Cmd(USART2, ENABLE);
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);
}
4. Пишем обработчик прерываний
....
// USART Receiver buffer
#define RX_BUFFER_SIZE 350
volatile uint8_t rx_buffer[RX_BUFFER_SIZE];
volatile uint16_t rx_wr_index=0,rx_rd_index=0;
volatile uint16_t rx_counter=0;
volatile uint8_t rx_buffer_overflow=0;
// USART Transmitter buffer
#define TX_BUFFER_SIZE 350
volatile uint8_t tx_buffer[TX_BUFFER_SIZE];
volatile uint16_t tx_wr_index=0,tx_rd_index=0;
volatile uint16_t tx_counter=0;
...
main()
{
....
}
....
void USART2_IRQHandler(void)
{
if(USART_GetITStatus(USART2, USART_IT_RXNE) == SET)
{
if ((USART2->SR & (USART_FLAG_NE|USART_FLAG_FE|USART_FLAG_PE|USART_FLAG_ORE)) == 0)
{
rx_buffer[rx_wr_index++]=(uint8_t)(USART_ReceiveData(USART2)& 0xFF);
if (rx_wr_index == RX_BUFFER_SIZE) rx_wr_index=0;
if (++rx_counter == RX_BUFFER_SIZE)
{
rx_counter=0;
rx_buffer_overflow=1;
}
}
else USART_ReceiveData(USART2);//вообще здесь нужен обработчик ошибок, а мы просто пропускаем битый байт
}
if(USART_GetITStatus(USART2, USART_IT_ORE_RX) == SET) //прерывание по переполнению буфера
{
USART_ReceiveData(USART2); //в идеале пишем здесь обработчик переполнения буфера, но мы просто сбрасываем этот флаг прерывания чтением из регистра данных.
}
if(USART_GetITStatus(USART2, USART_IT_TXE) == SET)
{
if (tx_counter)
{
--tx_counter;
USART_SendData(USART2,tx_buffer[tx_rd_index++]);
if (tx_rd_index == TX_BUFFER_SIZE) tx_rd_index=0;
}
else
{
USART_ITConfig(USART2, USART_IT_TXE, DISABLE);
}
}
}
5. Пишем необходимые функции для работы
uint8_t get_char(void)
{
uint8_t data;
while (rx_counter==0);
data=rx_buffer[rx_rd_index++];
if (rx_rd_index == RX_BUFFER_SIZE) rx_rd_index=0;
USART_ITConfig(USART2, USART_IT_RXNE, DISABLE);
--rx_counter;
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);
return data;
}
void put_char(uint8_t c)
{
while (tx_counter == TX_BUFFER_SIZE);
USART_ITConfig(USART2, USART_IT_TXE, DISABLE);
if (tx_counter || (USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET))
{
tx_buffer[tx_wr_index++]=c;
if (tx_wr_index == TX_BUFFER_SIZE) tx_wr_index=0;
++tx_counter;
USART_ITConfig(USART2, USART_IT_TXE, ENABLE);
}
else
USART_SendData(USART2,c);
}
void put_str(unsigned char *s)
{
while (*s != 0)
put_char(*s++);
}
void put_int(int32_t data)
{
unsigned char temp[10],count=0;
if (data<0)
{
data=-data;
put_char('-');
}
if (data)
{
while (data)
{
temp[count++]=data%10+'0';
data/=10;
}
while (count)
{
put_char(temp[--count]);
}
}
else put_char('0');
}
Добавляем в main() функцию USART_init(). Наслаждаемся!
Одно замечание, если вы создаете файл, в котором написан код, в формате C++ (.cpp), а не в формате CИ (.c) то перед объявлением обработчика прерывания необходимо вставить extern «C», получиться так:
extern "C" void USART2_IRQHandler(void)
Для удобства полный код файла main.c
#include "stm32l1xx_conf.h"
// USART Receiver buffer
#define RX_BUFFER_SIZE 350
volatile uint8_t rx_buffer[RX_BUFFER_SIZE];
volatile uint16_t rx_wr_index=0,rx_rd_index=0;
volatile uint16_t rx_counter=0;
volatile uint8_t rx_buffer_overflow=0;
// USART Transmitter buffer
#define TX_BUFFER_SIZE 350
volatile uint8_t tx_buffer[TX_BUFFER_SIZE];
volatile uint16_t tx_wr_index=0,tx_rd_index=0;
volatile uint16_t tx_counter=0;
void USART_init(void);
uint8_t get_char(void);
void put_char(uint8_t);
void put_str(unsigned char *s);
void put_int(int32_t data);
int main()
{
USART_init();
put_str((unsigned char *)"Hello!");
while (1)
{
put_char(get_char());
}
}
void USART_init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOD, ENABLE);
GPIO_PinAFConfig(GPIOD, GPIO_PinSource5, GPIO_AF_USART2); //PD5 to TX USART2
GPIO_PinAFConfig(GPIOD, GPIO_PinSource6, GPIO_AF_USART2); //PD6 to RX USART2
// PD5 -> TX UART.
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_40MHz;
GPIO_Init(GPIOD, &GPIO_InitStructure);
//PD6 -> RX UART.
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_Init(GPIOD, &GPIO_InitStructure);
//USART
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
USART_InitStructure.USART_BaudRate = 9600;
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_Init(USART2, &USART_InitStructure);
/* NVIC configuration */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
/* Enable USART */
USART_Cmd(USART2, ENABLE);
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);
}
void USART2_IRQHandler(void)
{
if(USART_GetITStatus(USART2, USART_IT_RXNE) == SET)
{
if ((USART2->SR & (USART_FLAG_NE|USART_FLAG_FE|USART_FLAG_PE|USART_FLAG_ORE)) == 0)
{
rx_buffer[rx_wr_index++]=(uint8_t)(USART_ReceiveData(USART2)& 0xFF);
if (rx_wr_index == RX_BUFFER_SIZE) rx_wr_index=0;
if (++rx_counter == RX_BUFFER_SIZE)
{
rx_counter=0;
rx_buffer_overflow=1;
}
}
else USART_ReceiveData(USART2);//вообще здесь нужен обработчик ошибок, а мы просто пропускаем битый байт
}
if(USART_GetITStatus(USART2, USART_IT_ORE_RX) == SET) //прерывание по переполнению буфера
{
USART_ReceiveData(USART2); //в идеале пишем здесь обработчик переполнения буфера, но мы просто сбрасываем этот флаг прерывания чтением из регистра данных.
}
if(USART_GetITStatus(USART2, USART_IT_TXE) == SET)
{
if (tx_counter)
{
--tx_counter;
USART_SendData(USART2,tx_buffer[tx_rd_index++]);
if (tx_rd_index == TX_BUFFER_SIZE) tx_rd_index=0;
}
else
{
USART_ITConfig(USART2, USART_IT_TXE, DISABLE);
}
}
}
uint8_t get_char(void)
{
uint8_t data;
while (rx_counter==0);
data=rx_buffer[rx_rd_index++];
if (rx_rd_index == RX_BUFFER_SIZE) rx_rd_index=0;
USART_ITConfig(USART2, USART_IT_RXNE, DISABLE);
--rx_counter;
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);
return data;
}
void put_char(uint8_t c)
{
while (tx_counter == TX_BUFFER_SIZE);
USART_ITConfig(USART2, USART_IT_TXE, DISABLE);
if (tx_counter || (USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET))
{
tx_buffer[tx_wr_index++]=c;
if (tx_wr_index == TX_BUFFER_SIZE) tx_wr_index=0;
++tx_counter;
USART_ITConfig(USART2, USART_IT_TXE, ENABLE);
}
else
USART_SendData(USART2,c);
}
void put_str(unsigned char *s)
{
while (*s != 0)
put_char(*s++);
}
void put_int(int32_t data)
{
unsigned char temp[10],count=0;
if (data<0)
{
data=-data;
put_char('-');
}
if (data)
{
while (data)
{
temp[count++]=data%10+'0';
data/=10;
}
while (count)
{
put_char(temp[--count]);
}
}
else put_char('0');
}
В приложении собранный проект.
Внимание!!! По ходу комментариев обнаружились некоторые ошибки, которые были исправлены в статье, в приложенном файле еще не поправил. Как поправлю сотру эту надпись.
- +6
- 27 января 2014, 17:23
- sasha_tvo
- 1
Файлы в топике:
test-usart.zip
STM32. Урок 3. UART (http://cxem.net/mc/mc198.php)Ссылки на статьи лучше было ссылками и оформить:
STM32. Урок 3. UART
Кроме того, в тексте упоминаются картинки, которых нет, «проект по ссылке» — которой тоже нет, да и просто косяки форматирования (скажем, часть текста попала в блок code — видимо, он некорректно закрыт).
Это что код для CodevisionARM? :)
Ничего не забыли?volatile, хотя зачем он тута?
uint8_t rx_buffer[RX_BUFFER_SIZE];
unsigned int rx_wr_index=0,rx_rd_index=0,rx_counter=0;
uint8_t rx_buffer_overflow=0;
Ничего не забыли?
Хотя на счет volatile, может быть есть смысл перед tx_counter написать, чтобы вот здесь не зависло while (tx_counter == TX_BUFFER_SIZE); а больше вроде бы негде. Подскажите, специалисты, надо оно тут или нет?
Если понравятся, добавь ссылки на две мои записи:
ziblog.ru/2011/04/15/stm32-chast-12-universalnyiy-asinhronnyiy-priyomoperedatchik-uart.html
ziblog.ru/2011/04/18/stm32-ndash-usart-fifo.html
Правда они старенькие, но все же.
ziblog.ru/2011/04/15/stm32-chast-12-universalnyiy-asinhronnyiy-priyomoperedatchik-uart.html
ziblog.ru/2011/04/18/stm32-ndash-usart-fifo.html
Правда они старенькие, но все же.
Да, я томич :)
Не знаю почему не видел, под именем ziblog.ru кажется четвертый год уже.
Было время пиарил немного, пока реклама от яндекса была думал окупиться само имя и хостинг, но не получилось.
Так что теперь дома (собственно как и начиналось) стоит вот такой карапуз:
Не знаю почему не видел, под именем ziblog.ru кажется четвертый год уже.
Было время пиарил немного, пока реклама от яндекса была думал окупиться само имя и хостинг, но не получилось.
Так что теперь дома (собственно как и начиналось) стоит вот такой карапуз:

Лично я радиаторы к хардам с боков прикручиваю. Наклейку сдирать производитель запрещает, да и не вижу смысла охлаждать жестяную крышку, на которую она наклеена. А сбоку радиатор как раз прижимается к алюминиевому корпусу харда и неплохо охлаждает (SMART показывает идентичную температуру у двух соединенных радиаторами дисков, при этом на нижнем висит активное охлаждение).
У меня они двумя передними отверстиями закреплены в корзине, а к задним прикручены радиаторы с двух сторон, через КПТ-8. От самих по себе радиаторов толка мало, они в десктопе практически не обдуваются, но их основная задача — уравнивание температур хардов, и с этим они справляются. А охлаждает кулер, подвешенный на нижнем харде.
Статья, конечно, капитальная, очень основательный подход, но… Не кажется ли вам, что она несколько из прошлого века? Честно сказать, не могу придумать ситуацию, когда обосновано принимать/отправлять большие буфера данных на прерываниях — для этого же есть DMA.
Честно сказать, не могу придумать ситуацию, когда обосновано принимать/отправлять большие буфера данных на прерываниях — для этого же есть DMA.
Зря Вы так категорично. ПДП однозначно рулит, если вам нужно передать большой линейный блок в памяти, или принять линейный блок, размер которого вам известен. Но часто данные для отправки формируются «на лету», или принимаемый пакет парсится «на лету», без сохранения в памяти самого пакета. В этих случаях рулит связка «кольцевой буфер + прерывания», а, иногда, даже, (старый дедушкин метод) прием и отправка без прерываний, «по опросу бита готовности».
Нет, конечно можно (как частный случай) принимать/отравлять через ПДП блоками по одному байту. Только, боюсь, накладные расходы на перенастройку и запуск ПДП будут больше чем реализация через прерывания.
кстати при отправке через DMA есть одно ограничение, нельзя использовать буфер, в котором хранятся отправляемые данные, пока не завершится отправка(иначе отправятся обновленные данные(настроили на отправку буфер с текстом «Привет», а после записали в этот же буфер «Кочка», на выходе c USART получите «Почка»)), я при первом запуске запнулся об этот камень, нашел корень зла быстро, но на момент написания он мне не показался…
А что, при работе по прерываниям такого ограничения нет? Впрочем, в приличных контроллерах DMA аппаратно умеет дабл-буфферинг (ping-pong). В STM32 есть «типа режим кольцевого буфера», но я там и не смог придумать, как его использовать по назначению при отправке — недоделанный он.
в dma stm32 есть два адреса назначения для буфера, менять один на другой можно установкой одного бита, прерывания есть по половине буфера, по полному буферу (для каждого из двух адресов буфера), по поводу кольцевого — могу сказать как использовал я — прекраснейшая вещь для ADC c кучей каналов, забирает и пихает в буфер, в буфере всегда свежие показания, в одной ячейке — один канал, удобно, есть и другие назначения у кольцевого буфера
>>в dma stm32 есть два адреса назначения для буфера,
Хм… Интересно, в F2, действительно есть, похоже там таки заметно более навороченный DMA, чем в L1/F3 с которыми мне приходилось общаться. Зато там нет режима периферия-периферия и всё так же блоки периферии прибиты гвоздями к каналам =(.
Буду знать, спасибо.
>по поводу кольцевого — могу сказать как использовал я
Ну на чтение-то понятно (в память), на эту тему у них даже апнот есть. А мне в обратную сторону надо было =).
Хм… Интересно, в F2, действительно есть, похоже там таки заметно более навороченный DMA, чем в L1/F3 с которыми мне приходилось общаться. Зато там нет режима периферия-периферия и всё так же блоки периферии прибиты гвоздями к каналам =(.
Буду знать, спасибо.
>по поводу кольцевого — могу сказать как использовал я
Ну на чтение-то понятно (в память), на эту тему у них даже апнот есть. А мне в обратную сторону надо было =).
Пожалуй соглашусь с e_mc2 в том, что для маленьких посылок DMA не так эффективен. Часто стоит задача, просто вовремя забрать данные из регистра, пока что-нить еще не прилетело поверх. Однако согласен с Вами в том, что надо будет добавить пример с использованием DMA для полноты картины — мыслю старыми AVR'овскими стереотипами. Спасибо :-)
В копилку существующих статей:
easyelectronics.ru/arm-uchebnyj-kurs-usart.html
Там как раз все на прерываниях и никаких стандартных библиотек. Все ручками.
easyelectronics.ru/arm-uchebnyj-kurs-usart.html
Там как раз все на прерываниях и никаких стандартных библиотек. Все ручками.
Хм, да замахивайся или нет, can сетевой протокол, промышленный стандарт, куча вариаций, основное применение — транспорт и спецтехника, у каждого производителя (за исключением нескольких европейцев) свой, меняется от модели к модели… махать что вилами по воде… если только кому время потратить хочется…
Я слоупок, но всё-таки пара замечаний.
Так делать, как я понимаю, нельзя ни в коем случае.
Это не битовые маски, их нельзя логически складывать.
Вчера наткнулся на знатные грабли, на которых матерился и топтался пару часов, пока не дошло что к чему.
Вот этот код потенциально опасен:
Дело в том, что, как минимум в STM32F100 и STM32L151 (за другие не поручусь), есть коварный флаг USART_FLAG_ORE. Взводится он при переполнении приемного регистра, когда данные из него ещё не забрали, а из сдвигового регистра уже пришел следующий байт. А коварность его в том, что он вызывает прерывание, если включено прерывание по USART_FLAG_RXNE. Если его не сбросить, то в случае переполнения приемника получим вечное прерывание. Да, потеря байта при приёме — ситуация аварийная, обычно такого быть не должно, но все-таки возможно, если длительное время запретить прерывание или долго висеть в другом прерывании.
Помимо этого, флаг USART_FLAG_RXNE неплохо бы сбрасывать принудительно, даже в случае ошибок. Он сбрасывается сам, если прочитать данные из USART_DR, если же байт игнорируется из-за ошибок, как в приведенном выше коде, то RXNE не будет сброшен и можно опять получить вечное прерывание.
USART_ITConfig(USART2, USART_IT_RXNE| USART_IT_TXE, ENABLE);
Так делать, как я понимаю, нельзя ни в коем случае.
#define USART_IT_TXE ((uint16_t)0x0727)
#define USART_IT_RXNE ((uint16_t)0x0525)
Это не битовые маски, их нельзя логически складывать.
Вчера наткнулся на знатные грабли, на которых матерился и топтался пару часов, пока не дошло что к чему.
Вот этот код потенциально опасен:
void USART2_IRQHandler(void)
{
if(USART_GetITStatus(USART2, USART_IT_RXNE) == SET)
{
if ((USART2->SR & (USART_FLAG_NE|USART_FLAG_FE|USART_FLAG_PE|USART_FLAG_ORE)) == 0)
{
...
}
}
Дело в том, что, как минимум в STM32F100 и STM32L151 (за другие не поручусь), есть коварный флаг USART_FLAG_ORE. Взводится он при переполнении приемного регистра, когда данные из него ещё не забрали, а из сдвигового регистра уже пришел следующий байт. А коварность его в том, что он вызывает прерывание, если включено прерывание по USART_FLAG_RXNE. Если его не сбросить, то в случае переполнения приемника получим вечное прерывание. Да, потеря байта при приёме — ситуация аварийная, обычно такого быть не должно, но все-таки возможно, если длительное время запретить прерывание или долго висеть в другом прерывании.
Помимо этого, флаг USART_FLAG_RXNE неплохо бы сбрасывать принудительно, даже в случае ошибок. Он сбрасывается сам, если прочитать данные из USART_DR, если же байт игнорируется из-за ошибок, как в приведенном выше коде, то RXNE не будет сброшен и можно опять получить вечное прерывание.
А принудительный сброс USART_FLAG_ORE в случае ошибок?
Либо даже лучше, просто убрать проверку на ошибку USART_FLAG_ORE. Ибо если ошибка и была, то байт из USART_DR всё равно забирать надо.
В реальности оно может никогда и не случится, но все-таки.
И не знаю, что насчёт других ошибок, NE, PE, FE. Если они проверяются, то и в случае их возникновения их надо сбрасывать чтением из USART_DR. К примеру PE, если включить передачу с контролем чётности, то первая же ошибка отключит приём данных.
Либо даже лучше, просто убрать проверку на ошибку USART_FLAG_ORE. Ибо если ошибка и была, то байт из USART_DR всё равно забирать надо.
В реальности оно может никогда и не случится, но все-таки.
И не знаю, что насчёт других ошибок, NE, PE, FE. Если они проверяются, то и в случае их возникновения их надо сбрасывать чтением из USART_DR. К примеру PE, если включить передачу с контролем чётности, то первая же ошибка отключит приём данных.
Вообще по даташиту эти биты нельзя скинуть функцией USART_ClearFlag(). Они скидываются только последовательностью чтения из USART_SR и USART_DR. Поэтому я там в коде пометку делал «в идеале пишем здесь обработчик ошибок». Но если уж его не писать, то тогда правильнее вместо USART_ClearITPendingBit(USART2,USART_IT_RXNE) написать USART_ReceiveData(USART2). Он прочитает битый байт и сбросит все флаги. Ща поправлю в тексте на такой вариант…
Да, я думаю это наиболее правильный и глюко-безопасный путь. Ну а в плане обработчика ошибок можно какой-то флаг поднимать, также как rx_buffer_overflow при переполнении буфера, остальное уже внешний код пусть думает, кто виноват и что делать.
Я там еще вычитал в даташите, что возможна ситуация, когда флаг ORE установлен и вызывает прерывание, но флаг RXNE не установлен и обработчик прерывания по приему (USART_GetITStatus(USART2, USART_IT_RXNE) == SET) не вызывается и флаг ORE не сбрасывает. Надо отдельно это прерывание обработать. Я там еще чуток текст подправил.
p.s. до сих пор удивляюсь, как много тонкостей в STM!
p.s. до сих пор удивляюсь, как много тонкостей в STM!
Да, точно. Я у себя тупо обрабатываю RXNE и ORE в одном условии. Ибо байт из DR забирать надо в обеих случаях (оно же сбросит эти флаги), а уж произошло переполнение или нет, всё равно ничего не поделаешь. Разве что флагом сигнализировать. Но такой вариант, пожалуй, даже лучше.
p.s. Да уж, и это ещё только UART в самом простом варианте. STM32 классные по возможностям, но вот такие приколы, как с ORE, выводят из себя, после пары часов ловли глюка.
p.s. Да уж, и это ещё только UART в самом простом варианте. STM32 классные по возможностям, но вот такие приколы, как с ORE, выводят из себя, после пары часов ловли глюка.
Вопрос касательно функции void put_char()
При заходе туда мы запрещаем прерывание. НО разрешаем его только в ветке
if(tx_counter||(USART_GetFlagStatus(USART2,USART_FLAG_TXE)==RESET))
Получается что если мы отправляем данные сразу, без прерывания, то прерывание так и остается запрещенным после выхода из функции. Это бага или фича?
При заходе туда мы запрещаем прерывание. НО разрешаем его только в ветке
if(tx_counter||(USART_GetFlagStatus(USART2,USART_FLAG_TXE)==RESET))
Получается что если мы отправляем данные сразу, без прерывания, то прерывание так и остается запрещенным после выхода из функции. Это бага или фича?
Логика здесь такая: если UART свободен, то мы сразу отправляем дынные, не занося их в буфер, следовательно прерывание не требуется. Но если UART еще передает данные, или в буфере что-то имеется, тогда мы добавляем данные в буфер, и разрешаем прерывание, которое будет эти данные из буфера забирать. Запрещение прерывания в начале на всякий случай, вообще оно запрещается в самом обработчике прерываний когда буфер опустошился. Но так как мы работаем с буфером и в обработчике и в этой функции, я на всякий случай запретил прерывание, чтобы изменения в буфер вносились не одновременно.
в прикрепленном файле надо поправить как и в статье. обработчик прерывания
… перенес и чуть чуть причесал…
если честно то это первый полностью рабочий кож для работы с уартом на stm32…
не понравилось что надо ждать данные… (может не прав еще не пробовал в работе);
а вообще спасибо… перенес для stm32f10x Your text to link...
… перенес и чуть чуть причесал…
если честно то это первый полностью рабочий кож для работы с уартом на stm32…
не понравилось что надо ждать данные… (может не прав еще не пробовал в работе);
а вообще спасибо… перенес для stm32f10x Your text to link...
Что имеется ввиду «Ждать данные»? Как только они прилетят по UART, так увеличиться rx_counter. Можно ставить проверку этой переменной до вызова get_char().
Кстати, сам недавно переносил на stm32f, заметил что там немного другая SPL.
Постараюсь на днях поправить файл, за одно положить вариант под stm32f.
Кстати, сам недавно переносил на stm32f, заметил что там немного другая SPL.
Постараюсь на днях поправить файл, за одно положить вариант под stm32f.
сейчас работаю над stm32+simm900 для работы с фтп сервером… так вот сервер может и не ответить. и тогда мы будем ждать вечно. но это на данном этапе. Сделаю или функцию проверки данных или флаг или еще какой нибудь сигнализатор… или по времени буду вываливаться… это уже второстепенно. Главное что модем ужо начал отвечать… счас буду делать логирование всех обращений с модемом в UART отладки… чтобы наблюдать… а в последующем и собирать эти данные.
Я когда с сим 900 работал, то на ножку RX stm32 вешал еще UART компа — удобнее отлаживать. Там по умолчанию режим эхо стоит, как результат видно и те данные что в него отдал, и то что он ответил.
В контроллере я делел так: отправил данные, завел таймер и жду ответа (rx_counter>0), если таймер сработал а данных нет, значить надо ресетить sim900. Еще имей в виду, что там на уровнях RX, TX у SIM900, уровень 3.0 вольта, от 3.3 может и сгореть!
В контроллере я делел так: отправил данные, завел таймер и жду ответа (rx_counter>0), если таймер сработал а данных нет, значить надо ресетить sim900. Еще имей в виду, что там на уровнях RX, TX у SIM900, уровень 3.0 вольта, от 3.3 может и сгореть!
Автор, в функции передачи байта есть строка
USART_ITConfig(USART2, USART_IT_TXE, ENABLE); //разрешаем прерывание
Она находится в теле условия if, и если условие не выполнится — мы выйдем из функции так и не разрешив прерывание. Это разве нормально?
USART_ITConfig(USART2, USART_IT_TXE, ENABLE); //разрешаем прерывание
Она находится в теле условия if, и если условие не выполнится — мы выйдем из функции так и не разрешив прерывание. Это разве нормально?
Вроде правильнее будет так:
void put_char(uint8_t c)
{
while (tx_counter == TX_BUFFER_SIZE);
if (tx_counter || (USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET))
{
tx_buffer[tx_wr_index++]=c;
if (tx_wr_index == TX_BUFFER_SIZE) tx_wr_index=0;
USART_ITConfig(USART2, USART_IT_TXE, DISABLE);
++tx_counter;
USART_ITConfig(USART2, USART_IT_TXE, ENABLE);
}
else
USART_SendData(USART2,c);
}
Комментарии (75)
RSS свернуть / развернуть