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’ом с использованием прерываний, без использования стандартной библиотеки. Также внутри статьи есть полезные ссылки:

  • Что такое UART вообще
  • Как связаться через старый добрый COM порт.
  • Как подключить к компьютеру через USB
  • Как передать по воздуху через радиоканал
  • Как просунуть через Ethernet
  • Как из 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 необходимо проделать ряд действий. А именно:

  • Корректно подключить к микроконтроллеру минимальную обвязку.
  • Согласовать уровни приема/передачи данных, скорость передачи данных, длину пакета данных, контроль четности, количество стоповых бит, способ управления потоком. Для работы в синхронном режиме необходимо согласовать полярность и фазу тактового сигнала (по спаду или по фронту импульса происходит прием данных), а также будет ли тактироваться последний переданный бит.
  • Настроить соответствующие ноги микроконтроллера
  • Настроить параметры 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’а вдвое выше.

    Удобно, что стандартная библиотека избавляет нас от необходимости брать три листа бумаги А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’);

    }

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

    Оставить комментарий

    Вы можете использовать следующие теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>