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)
Подробно рассматривается пример работы с UART'ом с использованием прерываний, без использования стандартной библиотеки. Также внутри статьи есть полезные ссылки:
[2] Статья на сайте easyelectronics.ru.
STM32 usart на прерываниях на примере RS485 (http://we.easyelectronics.ru/STM32/stm32-usart-na-preryvaniyah-na-primere-rs485.html)
Рассматривается пример работы с UART'ом с использованием прерываний, без написания специальных функций.
[3] Серия статей на сайте chipspace.ru.
STM32. USART. Часть 1 (http://chipspace.ru/stm32-usart-1/)
Небольшие общие слова об интерфейсе 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, касающиеся STM32L
STM32. USART. Часть 3 (http://chipspace.ru/stm32-usart-3/)
Дополнение к части 1,2. Использование стандартной библиотеки.
STM32. USART. Часть 3 (http://chipspace.ru/stm32-usart-4/)
Пример кода двустороннего обмена данными
[4] Статья на сайте cxem.net
STM32. Урок 3. UART (http://cxem.net/mc/mc198.php)
Подключение STM32F4-Discovery к компьютеру через COM порт. Схема переходника на MAX3232. Использование PuTTY. Пример кода с написанием функции send_to_uart, и приемом данных с использованием прерывания.
[5] Статья на сайте alex-exe.ru
STM32. 4. Последовательный порт (UART) (http://alex-exe.ru/radio/stm32/stm32-uart-spl/)
Подключение STM32VL-Discovery к компьютеру. Схема переходника USP-UART на CP2102. Пример кода с написанием функции send_Uart, getch_Uart, send_Uart_str.Без использования прерываний.
[6] Цикл статей на easystm32.ru
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 необходимо проделать ряд действий. А именно:

  1. Корректно подключить к микроконтроллеру минимальную обвязку.
  2. Согласовать уровни приема/передачи данных, скорость передачи данных, длину пакета данных, контроль четности, количество стоповых бит, способ управления потоком. Для работы в синхронном режиме необходимо согласовать полярность и фазу тактового сигнала (по спаду или по фронту импульса происходит прием данных), а также будет ли тактироваться последний переданный бит.
  3. Настроить соответствующие ноги микроконтроллера
  4. Настроить параметры USART’а
  5. Настроить прерывания
  6. Написать необходимые функции.


Далее рассмотрим по порядку перечисленные действия.

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'а вдвое выше.

Удобно, что стандартная библиотека избавляет нас от необходимости брать три листа бумаги А4, ручку, инженерный калькулятор, схему тактирования, настройки прескаллеров и аккуратно выплясывать танцы с бубнами вокруг расчетных формул самостоятельно рассчитывать значение регистра USART_BRR, при определении битрейта. Однако алгоритм, заложенный в стандартную библиотеку, опирается на заданные константы источников тактирования. По умолчанию предполагается, что частота внешнего кварца равна 8 Мгц. Если в проекте используется кварц другого номинала необходимо переопределить константу HSE_VALUE в файле stm32l1xx_conf.h.
В целом настройка 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).


Необходимо разрешить одно из этих событий. Разрешим генерировать прерывание по приему и передачи

USART_ITConfig(USART2, USART_IT_RXNE| USART_IT_TXE, ENABLE);

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



В приложении собранный проект.
Внимание!!! По ходу комментариев обнаружились некоторые ошибки, которые были исправлены в статье, в приложенном файле еще не поправил. Как поправлю сотру эту надпись.
Файлы в топике: test-usart.zip

Комментарии (75)

RSS свернуть / развернуть
ККККККААААААТТТТТТТТ!!! Но статья хорошая, спасибо
0
Спасибо! Целью ставил собрать все воедино, чтобы когда опять заводить буду — было где глянуть)
0
Опять эта унылая стандартная библиотека.
0
Может быть чуть погодя по просьбе желающих добавлю и без нее.
0
У меня без нее :)
0
STM32. Урок 3. UART (http://cxem.net/mc/mc198.php)
Ссылки на статьи лучше было ссылками и оформить:
STM32. Урок 3. UART

Кроме того, в тексте упоминаются картинки, которых нет, «проект по ссылке» — которой тоже нет, да и просто косяки форматирования (скажем, часть текста попала в блок code — видимо, он некорректно закрыт).
0
  • avatar
  • Vga
  • 27 января 2014, 20:02
На этапе черновика было в личных блогах, не знал что они тоже светятся в теме :( Поправил…
0
Для черновика есть специальная кнопка «черновик» рядом с «опубликовать» в редакторе. Тогда не будет нигде светиться.
0
Ага, разобрался. Спасибо!
0
ссылки решил оставить текстом, так удобнее копировать в буфер.
0
Это что код для CodevisionARM? :)

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, хотя зачем он тута?
0
Ага, взял частично реализацию буфера от туда. (только CodevisionAVR) Ностальгирую по привычным переменным)
0
CodevisionARM
Это я шуткую… нету такой тулзы… жжааль…
0
Хотя на счет volatile, может быть есть смысл перед tx_counter написать, чтобы вот здесь не зависло while (tx_counter == TX_BUFFER_SIZE); а больше вроде бы негде. Подскажите, специалисты, надо оно тут или нет?
0
Ставьте volatile для всех указателей буфера — не ошибётесь. Например бывает нужно сбросить буфер приёма или передачи — тогда индексы должны быть volatile. Буфер тоже можно объявить как volatile — код всё равно такой же будет, но на душе спокойнее.
0
Попрвил. К тому же в примере 7.2 Как раз так и сделано.
0
примере 7.2
Пункт 7 вижу, а 2 — где?
0
[7] Серия статей на сайте ziblog.ru. там второй примерчик.
0
А понятно…
0
После внимательного изучения местной статьи на душе спокойнее от volatile не будет.
+1
Я тоже внимательно ее читал. Как раз таки, думаю что бояться нечего.
0
Поправил текст. Спасибо за замечание!
0
Если понравятся, добавь ссылки на две мои записи:
ziblog.ru/2011/04/15/stm32-chast-12-universalnyiy-asinhronnyiy-priyomoperedatchik-uart.html
ziblog.ru/2011/04/18/stm32-ndash-usart-fifo.html
Правда они старенькие, но все же.
0
  • avatar
  • ZiB
  • 28 января 2014, 08:24
Добавил! А это что, Томский сайт? Я чет про него не слышал…
0
Да, я томич :)
Не знаю почему не видел, под именем ziblog.ru кажется четвертый год уже.
Было время пиарил немного, пока реклама от яндекса была думал окупиться само имя и хостинг, но не получилось.
Так что теперь дома (собственно как и начиналось) стоит вот такой карапуз:

0
Бесшумная работа — это классно!
А на счет блога — полазил, вспомнил, что даже пару примерчиков от туда брал, когда только-только заводил DISCOVERY. Спасибо — хороший блог!
0
Да, при работе сервера дома в режиме 24х7 шум играет немаловажную роль.
Не за что, жаль только, что фактически забросил его. Раньше когда по стм32 стм8 ни чего не было интересно было, а сейчас уже статей валом. Разобраться, что куда не составляет проблем.
0
А начинал в 2008 или 2009-ом на ASUS 701:

0
Хы, круто :) Но надо было радиатор игольчатый найти, это же EEEжик :)
0
Он и без радиатора грелся мало, но т.к. у меня клавиатура была неработоспособная, то я её выкинул и «прилепил» на термопасту радиатор какой был под рукой.
0
Потом это же радиатор перекочевал на жесткий диск следующего сервака:

0
О, а расскажи про радиатор на хард. Эффективно? Как лепил? С термопастой? Наклейку с харда сдирал?
0
Нет, не снимал. Вот как есть на картинке, так сверху и налепил с приличным слоем термопасты.
Сказать точно на сколько уменьшилось не смогу, но без него рукой проблемно было держать, с радиатором значительно легче было. Кажется тоже около года работал 24х7.
0
Лично я радиаторы к хардам с боков прикручиваю. Наклейку сдирать производитель запрещает, да и не вижу смысла охлаждать жестяную крышку, на которую она наклеена. А сбоку радиатор как раз прижимается к алюминиевому корпусу харда и неплохо охлаждает (SMART показывает идентичную температуру у двух соединенных радиаторами дисков, при этом на нижнем висит активное охлаждение).
0
А закрепляешь харды как? или радиаторы сбоку и получаются креплением?
0
У меня они двумя передними отверстиями закреплены в корзине, а к задним прикручены радиаторы с двух сторон, через КПТ-8. От самих по себе радиаторов толка мало, они в десктопе практически не обдуваются, но их основная задача — уравнивание температур хардов, и с этим они справляются. А охлаждает кулер, подвешенный на нижнем харде.
0
Статья, конечно, капитальная, очень основательный подход, но… Не кажется ли вам, что она несколько из прошлого века? Честно сказать, не могу придумать ситуацию, когда обосновано принимать/отправлять большие буфера данных на прерываниях — для этого же есть DMA.
0
Честно сказать, не могу придумать ситуацию, когда обосновано принимать/отправлять большие буфера данных на прерываниях — для этого же есть DMA.

Зря Вы так категорично. ПДП однозначно рулит, если вам нужно передать большой линейный блок в памяти, или принять линейный блок, размер которого вам известен. Но часто данные для отправки формируются «на лету», или принимаемый пакет парсится «на лету», без сохранения в памяти самого пакета. В этих случаях рулит связка «кольцевой буфер + прерывания», а, иногда, даже, (старый дедушкин метод) прием и отправка без прерываний, «по опросу бита готовности».

Нет, конечно можно (как частный случай) принимать/отравлять через ПДП блоками по одному байту. Только, боюсь, накладные расходы на перенастройку и запуск ПДП будут больше чем реализация через прерывания.
0
размер которого вам известен
не обязательно знать размер для принимаемого пакета, чтобы определить конец пакета нужно задействовать прерывание USART IDLE, по нему можно поймать окончание пакета и вызвать обработчик DMA.
0
кстати при отправке через DMA есть одно ограничение, нельзя использовать буфер, в котором хранятся отправляемые данные, пока не завершится отправка(иначе отправятся обновленные данные(настроили на отправку буфер с текстом «Привет», а после записали в этот же буфер «Кочка», на выходе c USART получите «Почка»)), я при первом запуске запнулся об этот камень, нашел корень зла быстро, но на момент написания он мне не показался…
0
А что, при работе по прерываниям такого ограничения нет? Впрочем, в приличных контроллерах DMA аппаратно умеет дабл-буфферинг (ping-pong). В STM32 есть «типа режим кольцевого буфера», но я там и не смог придумать, как его использовать по назначению при отправке — недоделанный он.
0
в dma stm32 есть два адреса назначения для буфера, менять один на другой можно установкой одного бита, прерывания есть по половине буфера, по полному буферу (для каждого из двух адресов буфера), по поводу кольцевого — могу сказать как использовал я — прекраснейшая вещь для ADC c кучей каналов, забирает и пихает в буфер, в буфере всегда свежие показания, в одной ячейке — один канал, удобно, есть и другие назначения у кольцевого буфера
0
>>в dma stm32 есть два адреса назначения для буфера,
Хм… Интересно, в F2, действительно есть, похоже там таки заметно более навороченный DMA, чем в L1/F3 с которыми мне приходилось общаться. Зато там нет режима периферия-периферия и всё так же блоки периферии прибиты гвоздями к каналам =(.
Буду знать, спасибо.

>по поводу кольцевого — могу сказать как использовал я
Ну на чтение-то понятно (в память), на эту тему у них даже апнот есть. А мне в обратную сторону надо было =).
0
Пожалуй соглашусь с e_mc2 в том, что для маленьких посылок DMA не так эффективен. Часто стоит задача, просто вовремя забрать данные из регистра, пока что-нить еще не прилетело поверх. Однако согласен с Вами в том, что надо будет добавить пример с использованием DMA для полноты картины — мыслю старыми AVR'овскими стереотипами. Спасибо :-)
0
Как уже тут отметили DMA не всегда хорошо, в частности когда борешься за минимальное потребление, а этот блок в МК как правило кушает очень прилично.
0
Если верить даташитам, примерно столько же, сколько USART. Зато пока он работает можно спокойно спать — энергетически это выгоднее, в общем случае.
0
Вот именно, примерно :) В моем случае это на 25% больше, вернее +125% ведь он должен работать вместе с uart-ом.
И если есть возможность спать, то хорошо, а когда её нет, то уже плохо :(
Конечно, всё зависит от поставленной задачи.
0
В копилку существующих статей:
easyelectronics.ru/arm-uchebnyj-kurs-usart.html

Там как раз все на прерываниях и никаких стандартных библиотек. Все ручками.
0
Спасибо! Добавил!
0
за статью незачёт. Нету DMA и не прикручено к RTOS (через семафоры).
0
На счет DMA — да, стоит дописать. А вот RTOS — это уже далеко. Все же статья ориентирована на тех, кто первый раз. А те кто RTOS запускают, уж UART то как таблицу умножения 8-битных чисел должны знать :-)
0
А не пора ли замахнаться на CAN. По USART только ленивый не пишет.
0
  • avatar
  • x893
  • 31 января 2014, 20:45
Хм, да замахивайся или нет, can сетевой протокол, промышленный стандарт, куча вариаций, основное применение — транспорт и спецтехника, у каждого производителя (за исключением нескольких европейцев) свой, меняется от модели к модели… махать что вилами по воде… если только кому время потратить хочется…
0
Замахнуться
0
  • avatar
  • x893
  • 31 января 2014, 20:46
99% не понимают как вообще работает CAN (достаточно посмотреть ветку от Voldemar). с 2012 года бьются с прерываниями от TX CAN но при этом просто CAN не работает. А таких на просторах страны миллионы (я их называю LED гуру)
0
  • avatar
  • x893
  • 31 января 2014, 22:13
Здрасьте! Я родом из Бобруйска.
Я — гуру, по-вашему это будет «учитель»
Я щас вам расскажу о смысле жизни.
Я, в натуре, профессионал, а не любитель.

не удержался :)
0
Повеселил!
0
Я слоупок, но всё-таки пара замечаний.

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 не будет сброшен и можно опять получить вечное прерывание.
0
  • avatar
  • ACE
  • 14 марта 2014, 20:54
Согласен полностью! Спасибо за замечания, сейчас поправлю в статье и в своих проектах.
0
А принудительный сброс USART_FLAG_ORE в случае ошибок?
Либо даже лучше, просто убрать проверку на ошибку USART_FLAG_ORE. Ибо если ошибка и была, то байт из USART_DR всё равно забирать надо.
В реальности оно может никогда и не случится, но все-таки.
И не знаю, что насчёт других ошибок, NE, PE, FE. Если они проверяются, то и в случае их возникновения их надо сбрасывать чтением из USART_DR. К примеру PE, если включить передачу с контролем чётности, то первая же ошибка отключит приём данных.
0
Вообще по даташиту эти биты нельзя скинуть функцией USART_ClearFlag(). Они скидываются только последовательностью чтения из USART_SR и USART_DR. Поэтому я там в коде пометку делал «в идеале пишем здесь обработчик ошибок». Но если уж его не писать, то тогда правильнее вместо USART_ClearITPendingBit(USART2,USART_IT_RXNE) написать USART_ReceiveData(USART2). Он прочитает битый байт и сбросит все флаги. Ща поправлю в тексте на такой вариант…
0
Да, я думаю это наиболее правильный и глюко-безопасный путь. Ну а в плане обработчика ошибок можно какой-то флаг поднимать, также как rx_buffer_overflow при переполнении буфера, остальное уже внешний код пусть думает, кто виноват и что делать.
0
Я там еще вычитал в даташите, что возможна ситуация, когда флаг ORE установлен и вызывает прерывание, но флаг RXNE не установлен и обработчик прерывания по приему (USART_GetITStatus(USART2, USART_IT_RXNE) == SET) не вызывается и флаг ORE не сбрасывает. Надо отдельно это прерывание обработать. Я там еще чуток текст подправил.
p.s. до сих пор удивляюсь, как много тонкостей в STM!
0
Да, точно. Я у себя тупо обрабатываю RXNE и ORE в одном условии. Ибо байт из DR забирать надо в обеих случаях (оно же сбросит эти флаги), а уж произошло переполнение или нет, всё равно ничего не поделаешь. Разве что флагом сигнализировать. Но такой вариант, пожалуй, даже лучше.
p.s. Да уж, и это ещё только UART в самом простом варианте. STM32 классные по возможностям, но вот такие приколы, как с ORE, выводят из себя, после пары часов ловли глюка.
0
Вопрос касательно функции void put_char()
При заходе туда мы запрещаем прерывание. НО разрешаем его только в ветке
if(tx_counter||(USART_GetFlagStatus(USART2,USART_FLAG_TXE)==RESET))

Получается что если мы отправляем данные сразу, без прерывания, то прерывание так и остается запрещенным после выхода из функции. Это бага или фича?
0
Логика здесь такая: если UART свободен, то мы сразу отправляем дынные, не занося их в буфер, следовательно прерывание не требуется. Но если UART еще передает данные, или в буфере что-то имеется, тогда мы добавляем данные в буфер, и разрешаем прерывание, которое будет эти данные из буфера забирать. Запрещение прерывания в начале на всякий случай, вообще оно запрещается в самом обработчике прерываний когда буфер опустошился. Но так как мы работаем с буфером и в обработчике и в этой функции, я на всякий случай запретил прерывание, чтобы изменения в буфер вносились не одновременно.
0
А.ну да.
Пр иследубщей отправке все равно будет вызвана функция, и при передаче большого колчиества инфы разрешены прерывания.
0
в прикрепленном файле надо поправить как и в статье. обработчик прерывания
… перенес и чуть чуть причесал…
если честно то это первый полностью рабочий кож для работы с уартом на stm32…
не понравилось что надо ждать данные… (может не прав еще не пробовал в работе);
а вообще спасибо… перенес для stm32f10x Your text to link...
0
Что имеется ввиду «Ждать данные»? Как только они прилетят по UART, так увеличиться rx_counter. Можно ставить проверку этой переменной до вызова get_char().
Кстати, сам недавно переносил на stm32f, заметил что там немного другая SPL.
Постараюсь на днях поправить файл, за одно положить вариант под stm32f.
0
у F4 и F1 тоже SPL отличаются.
0
сейчас работаю над stm32+simm900 для работы с фтп сервером… так вот сервер может и не ответить. и тогда мы будем ждать вечно. но это на данном этапе. Сделаю или функцию проверки данных или флаг или еще какой нибудь сигнализатор… или по времени буду вываливаться… это уже второстепенно. Главное что модем ужо начал отвечать… счас буду делать логирование всех обращений с модемом в UART отладки… чтобы наблюдать… а в последующем и собирать эти данные.
0
Я когда с сим 900 работал, то на ножку RX stm32 вешал еще UART компа — удобнее отлаживать. Там по умолчанию режим эхо стоит, как результат видно и те данные что в него отдал, и то что он ответил.
В контроллере я делел так: отправил данные, завел таймер и жду ответа (rx_counter>0), если таймер сработал а данных нет, значить надо ресетить sim900. Еще имей в виду, что там на уровнях RX, TX у SIM900, уровень 3.0 вольта, от 3.3 может и сгореть!
0
а я повесил 2 для отладки… первый тх модема а второй для отправки лога работы модема с сервером… пс достаточно только одного… подключенного на тх модема с вкюченым эхом.
0
Автор, в функции передачи байта есть строка
USART_ITConfig(USART2, USART_IT_TXE, ENABLE); //разрешаем прерывание
Она находится в теле условия if, и если условие не выполнится — мы выйдем из функции так и не разрешив прерывание. Это разве нормально?
0
Вроде правильнее будет так:
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);
}
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.