Урок 20. Интерфейс UART в STM32. Работа с ним через регистры CMSIS. Использование прерывания UART.

Протокол UART

Получим необходимый минимум информации об интерфейсах UART микроконтроллеров STM32. Научимся управлять ими через регистры библиотеки CMSIS.

Предыдущий урок     Список уроков     Следующий урок

UART – это наиболее распространенный последовательный интерфейс передачи данных. Через него к микроконтроллеру могут быть подключены самые разные устройства: от миниатюрного цифрового датчика, до компьютера. На базе UART могут создаваться даже локальные сети. Нам этот интерфейс необходим как минимум для отладки программ.

 

Протокол интерфейса UART.

Данные передаются асинхронным последовательным кодом.

Протокол UART

Интерфейс называется асинхронным. Это означает, что данные могут быть переданы в любой момент, без использования между приемником и передатчиком синхронизирующих импульсов. В одну сторону данные предаются с помощью одного сигнала. Сам сигнал содержит как данные, так и синхронизирующую информацию.

Для того, чтобы приемник понял, что начинается передача данные сопровождаются специальными битами: стартовым и стоповым.

В режиме ожидания сигнал на входе приемника находится в высоком уровне. Первым передается стоповый бит. Он всегда имеет низкий уровень. По первому перепаду сигнала в низкий уровень приемник начинает отсчитывать внутренние синхроимпульсы. Зная скорость передачи, а значит и период поступления входных битов, приемник считывает состояние следующих 8 битов. В конце передатчик формирует стоп-бит, всегда высокого уровня.

Все биты передаются за одинаковые промежутки времени. Время передачи одного бита определяет скорость передачи интерфейса. Часто она указывается в бодах (бит в секунду). Как я писал выше, кроме собственно данных в поток добавляются биты синхронизации. Таким образом, для передачи байта требуется не 8, а 10 битов.

Погрешность скорости передачи не должна превышать 5% (рекомендуется не более 1,5 %.)

Я рассказываю о самом распространенном формате протокола UART. Бывают варианты с различными количествами битов данных, обычно от 5 до 9. Могут использоваться форматы с двумя стоповыми битами. Иногда добавляется бит контроля четности. Но такие варианты используются редко.

Главное, что надо знать:

  • в неактивном режиме (состоянии ожидания) выход передатчика UART находится в высоком состоянии;
  • передача байта начинается со стартового бита, который равен 0 (низкий уровень сигнала);
  • передача байта заканчивается стоповым битом, который равен 1 (высокий уровень сигнала);
  • данные передаются, начиная с младшего бита;
  • для передачи байта требуется 10 битов;
  • время передачи одного байта вычисляется исходя из скорости обмена и количества битов  в пакете (10 бит при передаче байта).

Существуют стандартные скорости передачи интерфейса UART. Наиболее распространены следующие.

 

Скорость передачи,
бод
Время передачи одного бита, мкс Время передачи байта,
мкс
4800 208 2083
9600 104 1042
19200 52 521
38400 26 260
57600 17 174
115200 8,7 87

Обмен данными через UART происходит в дуплексном режиме. Т.е. передача и прием данных могут происходить одновременно.

У интерфейса UART существуют 2 сигнала RX и TX. Иногда их маркируют RXD и TXD, подчеркивая, что это сигналы передачи данных.

При подключении двух UART устройств сигналы RX соединяются с сигналами TX. Используется перекрестная схема соединения.

 

UART в микроконтроллерах STM32.

У микроконтроллеров STM32F103C8T6 целых 3 интерфейса UART. Они имеют одинаковые структуры и функциональные возможности.

Не повторяя информацию об общепринятых функциях UART, я бы выделил следующие возможности этого интерфейса в микроконтроллерах STM32.

  • При частоте тактирования 72 МГц скорость передачи данных до 4,5 Мбит/сек.
  • Размерность передаваемых данных 8 или 9 бит.
  • Могут быть заданы форматы с 1 или 2 стоповыми битами.
  • Интерфейс поддерживает физический уровень протокола инфракрасного порта (IRDA).
  • Также поддерживается интерфейс контактных смарт-карт ISO 7816.
  • Передача и прием данных могут происходить с использованием контроллера прямого доступа к памяти (DMA).
  • Интерфейс может работать в синхронном режиме.

Я коротко, упрощенно расскажу, как работает UART STM32.

Вот часть функциональной схемы интерфейса, через которую происходит передача и прием данных. Все остальное – это управляющая часть. Я ее не привел.

Структурная схема

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

Собственно передатчик состоит из 2 регистров: регистра сдвига (Transmit Shift Register) и буферного регистра (TDR).

Данное загружается в буферный регистр передатчика. Программно доступен только он. Если передача предыдущего данного закончена и регистр сдвига пуст, то данное перегружается в него, сдвигается и побитно поступает на выход TX. Как только данное перегружено в регистр сдвига, буферный регистр освобождается и в него может быть загружено новое слово.  Оно будет ждать окончания передачи и автоматически перегрузится в сдвиговый регистр.

Таким образом, происходит буферизация данных передатчика. Это позволяет реализовывать передачу данных без пауз между словами. После загрузки буферного регистра есть время на запись нового данного, равное времени передачи. Если успевать заполнять буферный регистр, то данные будут передаваться сплошным потоком.

Об окончании передачи данных регистром сдвига и освобождении буферного регистра сообщают специальные флаги. По ним могут быть сформированы прерывания.

Приемная часть устройства также состоит из двух регистров:  регистра сдвига (Receive Shift Register) и буферного регистра (RDR). С входа RX данные поступают на сдвиговый регистр, сдвигаются и, после формирования полного слова, перегружаются в буферный регистр. Буферный регистр доступен программно. Из него считывается полученное данное. Если данные поступают сплошным потоком, без пауз, то после приема данного есть время, равное времени передачи слова, на то, чтобы считать его из буферного регистра. Иначе придет новое данное, а старое будет потеряно.

 

Регистры UART.

Регистров много, битов еще больше. Я не претендую на подробное описание регистров UART. Приведу только информацию, необходимую нам в ближайших уроках.

USART_SR  - регистр состояния.

Формат SR

Прежде всего, нам интересны флаги, сообщающие о состоянии приема и передачи.

  • Бит 7 TXE. Буферный регистр передатчика пуст. Флаг становится равным 1 после того, как данное перегружается в регистр сдвига. Т.е. флаг сообщает о том, что буферный регистр пуст, может быть загружено новое данное. Флаг устанавливается аппаратно и сбрасывается после записи байта в буферный регистр USART_DR.
  • Бит 6 TC. Флаг устанавливается аппаратно и сообщает о том, что передача данного закончена, сдвиговый регистр пуст. Если предыдущий флаг (TXE) говорит о том, что можно в буферный регистр загружать новое данное. Физическая передача может продолжаться, на выходе TX возможно идет поток битов. То флаг TC сообщает, что все биты переданы. Часто этот момент необходимо определять для отключения передатчика шинного интерфейса сети, например, в интерфейсе RS-485. Сбрасывается флаг последовательным чтением регистров USART_SR и USART_DR.
  • Бит 5 RXNE. Буферный регистр приема не пуст. Флаг сообщает, что в буферном регистре приемника есть данное. Данное должно быть считано до прихода следующего, иначе оно будет потеряно. Сбрасывается флаг при чтении регистра USART_DR.

UART способен определить некоторые ошибки приема данных, возникающие из-за искажения сигнала при передаче или за счет неправильных программных операций с ним. Об ошибках сигнализируют следующие 4 бита.

  • Бит 3 ORE. Ошибка переполнения. Флаг устанавливается в случае, если в приемный буферный регистр поступило новое данное, а предыдущее считано не было. Т.е. при потере данного.
  • Бит 2 NE. Флаг устанавливается при выделении шума во входном сигнале. Наличие шума определяется как слишком частое переключение входного сигнала.
  • Бит 1 FE. Ошибка приема фрейма (кадра). Возникает, когда не был выделен стоп-бит. Т.е. после приема стартового бита и  8 битов данных на месте стопового бита UART считал сигнал низкого уровня.
  • Бит 0 PE.  Ошибка паритета. Сигнализирует об ошибке при включенном контроле четности.

Все биты сбрасываются программно последовательным чтением сначала регистра USART_SR, затем USART_DR.

 

USART_DR  - регистр данных.

Формат DR

Используется для записи данных в буферный регистр передатчика и чтения данных из буферного регистра приемника. На самом деле это 2 регистра, для обращения к которым используется один адрес.

 

USART_BRR - регистр, задающий скорость обмена.

Формат BRR

Регистр содержит значение делителя частоты (USARTDIV), который определяет скорость передачи и приема данных.

Скорость вычисляется по формуле:

BAUD = Fck / (16 * USARTDIV), где

  • BAUD – скорость, бод.
  • Fck – входная частота тактирования UART:
    • сигнал PCLK2 для UART1 (шина APB2);
    • сигнал PCLK1 для UART2 и 3 (шина APB1).
  •  USARTDIV – значение регистра USART_BRR.

USARTDIV задано в формате с фиксированной запятой с дробной частью.

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
Целая часть (12 разрядов) Дробная часть (4 разр)
11 10 9 8 7 6 5 4 3 2 1 0 -1 -2 -3 -4

В верхней строке – разряды регистра USART_BRR.

В нижней строке – разряды дробного числа USARTDIV. Отрицательные – это дробные разряды. Разряд – 1 это ½ или в десятичном виде 0,5. -2 это ¼ или 0,25. И т.д. Это стандартная форма представления дробного числа с фиксированной запятой. Для перевода в привычную десятичную форму можно воспользоваться формулой:

Десятичное значение USARTDIV = целая часть + (дробная часть / 16).

Например, если целая часть равна 78, а дробная 5, то

USARTDIV = 78 + (5 / 16) = 78,3125.

Можно перевести в десятичный код содержимое всего регистра (разряды 0…15) и разделить его на 16.

Скорость при частоте тактирования 72 Мгц

BAUD = 72000000 / (16 * 78,3125) = 57462 бод.

Для обратного вычисления USARTDIV по скорости BAUD можно использовать формулу:

USARTDIV = Fck / (16 * BAUD)

 

USART_CR1 – управляющий регистр 1.

Формат CR1

  • Бит 13 UE. Разрешение работы UART. Отключение UART (UE=0) уменьшает ток потребления микроконтроллера.
  • Бит 12 M. Задает длину слова 8 бит (M=0) или 9 бит (M=1).
  • Бит 10 PCE. Разрешение контроля четности (PCE=1).
  • Бит 7 TXEIE. Разрешение прерывания по флагу TXE, т.е. когда буферный регистр передатчика пуст.
  • Бит 6 TCIE. Разрешение прерывания по флагу TC, т.е. когда пуст сдвиговый регистр передатчика.
  • Бит 5 RXNEIE. Разрешение прерывания по флагу RXNE, т.е. когда в буферном регистре приемника есть непрочитанное данное.
  • Бит 3 TE. Разрешение работы передатчика.
  • Бит2 RE. Разрешение работы приемника.

У всех разрядов активный уровень единица.

 

USART_CR2 – управляющий регистр 2.

Формат CR2

Нам интересно только одно поле.

  • Биты 13:12 STOP. Биты задают формат стопового признака слова:
    • 00 – 1 стоп-бит;
    • 01 – 0,5 стоп-бита;
    • 10 – 2 стоп-бита;
    • 11 – 1,5 стоп бита.

 

USART_CR3 – управляющий регистр 3.

Формат CR3

Пока при инициализации UART запишем в этот регистр 0.

 

Работа с UART через регистры CMSIS.

Большей частью мы будем управлять UART с помощью HAL- функций. Но бывают задачи, когда без прямого обращения к регистрам UART не обойтись.

Например, сейчас я разрабатываю центральный контроллер для системы управления шаговыми двигателями. Передача данных происходит со скоростью 1 Мбит/сек, и операции обмена крайне критичны ко времени выполнения. Тратить время на вызовы функций библиотеки HAL при такой задаче очень расточительно. Все удачно реализовывается при прямом обращении к регистрам UART.

Поэтому, я решил, коротко рассказать о таком способе управления передачей данных.

Поставим задачу – реализовать эхо-терминал. Т.е. устройство, которому мы посылаем данные и получаем их в ответ. Будем использовать UART1. В нашей системе он уже подключен к компьютеру.

Создадим проект Lesson20_1. В нем настроим только систему тактирования на частоту 72 мГц.

В файле main.c создаем блок инициализации UART.

  /* USER CODE BEGIN SysInit */

  // инициализация UART1

Включаем тактирование UART1. Он подключен к шине APB2.

RCC->APB2ENR |= RCC_APB2ENR_USART1EN; // включаем тактирование UART1

UART1 использует выводы: PA9 (для сигнала TX) и PA10 (сигнал RX). Надо задать конфигурацию для них.

RCC->APB2ENR |= RCC_APB2ENR_IOPAEN; // разрешаем тактирование порта GPIOA

// настройка вывода PA9 (TX1) на режим альтернативной функции с активным выходом
// Биты CNF = 10, ,биты MODE = X1
GPIOA->CRH &= (~GPIO_CRH_CNF9_0);
GPIOA->CRH |= (GPIO_CRH_CNF9_1 | GPIO_CRH_MODE9);

// настройка вывода PA10 (RX1) на режим входа с подтягивающим резистором
// Биты CNF = 10, ,биты MODE = 00, ODR = 1
GPIOA->CRH &= (~GPIO_CRH_CNF10_0);
GPIOA->CRH |= GPIO_CRH_CNF10_1;
GPIOA->CRH &= (~(GPIO_CRH_MODE10));
GPIOA->BSRR |= GPIO_ODR_ODR10;

Теперь конфигурация самого UART.

// конфигурация UART1
USART1->CR1 = USART_CR1_UE; // разрешаем USART1, сбрасываем остальные биты

Устанавливаем скорость обмена.

Частота тактирования UART1 72 мГц, нам нужна скорость 9600 бод. Вычисляем значение USARTDIV.

USARTDIV = Fck / (16 * BAUD) = 72000000 / (16 * 9600) = 468,75

Значение регистра USART_BRR = 468,75 * 16 = 7500.

USART1->BRR = 7500; // скорость 9600 бод

Разрешаем работу приемника  и передатчика. Прерывания по событиям UART не разрешаем.

USART1->CR1 |= USART_CR1_TE | USART_CR1_RE ; // разрешаем приемник и передатчик
USART1->CR2 = 0;
USART1->CR3 = 0;

Инициализация закончена.

Теперь для передачи необходимо дождаться, когда бит TXE станет равным 1 и загрузить в регистр данных передаваемое число.

while ((USART1->SR & USART_SR_TXE) == 0) {}
USART1->DR = d;

Для приема надо дождаться, когда бит RXNE станет равным 1 и считать из регистра данных принятое число.

while ((USART1->SR & USART_SR_RXNE) == 0) {}
d = USART1->DR;

Для реализации эхо-терминала поместим такой блок в цикл while:

/* Infinite loop */
  /* USER CODE BEGIN WHILE */

  while (1)
  {

    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */

      // получить данное
      while ((USART1->SR & USART_SR_RXNE) == 0) {}
      uint8_t d = USART1->DR;

      // отослать данное назад
      while ((USART1->SR & USART_SR_TXE) == 0) {}
      USART1->DR = d;
  }

  /* USER CODE END 3 */

Загрузить проект можно по ссылке:

  Зарегистрируйтесь и оплатите. Всего 40 руб. в месяц за доступ ко всем ресурсам сайта!  

Проверяем.

Загружаем программу в микроконтроллер.

Запускаем программу CoolTerm.

Окно CoolTerm

Нажимаем Options, выбираем COM-порт, скорость обмена.

Окно CoolTerm

Нажимаем кнопку Connect.

В верхнем меню, в закладке Connection выбираем Send String (Послать строку).

Набираем текстовую информацию, нажимаем Send и посланная строка появляется в окне принятых данных.

Окно терминала CoolTerm

Эхо-терминал работает.

Программу CoolTerm можно не закрывать. Если необходимо загрузить программу в STM32, то можно нажать кнопку Disconnect. CoolTerm  освободит COM-порт и даст возможность запрограммировать микроконтроллер. Для возобновления работы CoolTerm достаточно нажать Connect.

 

Использование прерываний UART.

В предыдущей программе в основном цикле while постоянно происходила проверка состояния флага RXNE. На остальные задачи времени не оставалось. И это притом, что флаг становился активным не чаще чем с периодом 1 мс. Заставить микроконтроллер тратить минимум вычислительных ресурсов на работу с UART можно за счет применения прерываний.

Реализуем ту же задачу с использованием прерываний.

Я создал новый проект Lesson20_2, в котором настроена только система тактирования на 72 мГц.

Открываем урок 18 и кто забыл, повторяем необходимые действия для работы с прерываниями.

Копируем блок инициализации UART из предыдущей программы. В нем делаем изменения только в одной строке. В регистре CR1 разрешаем прерывание по флагу RXNE.

USART1->CR1 |= USART_CR1_TE | USART_CR1_RE | USART_CR1_RXNEIE; // разрешаем приемник, передатчик и прерывание по приему

Разрешаем прерывание в контроллере прерываний.

// разрешения прерывания UART1 в контроллере прерываний
NVIC_EnableIRQ (USART1_IRQn);

В конце файла stm32f1xx_it.c размещаем функцию обработки прерывания UART1.

/* USER CODE BEGIN 1 */

void USART1_IRQHandler(void) {

}

Не забываем добавить в файл stm32f1xx_it.h прототип функции обработки прерывания.

/* Exported functions prototypes ---------------------------------------------*/
void USART1_IRQHandler(void);

Остается заполнить функцию обработки прерывания.

/* USER CODE BEGIN 1 */

void USART1_IRQHandler(void) {

uint8_t d = USART1->DR; // получить данное
USART1->DR = d; // отослать данное назад
}

/* USER CODE END 1 */

Загрузить проект можно по ссылке:

  Зарегистрируйтесь и оплатите. Всего 40 руб. в месяц за доступ ко всем ресурсам сайта!  

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

 

В следующем уроке будем работать с UART через HAL-функции. Поговорим об отладке программ с помощью UART.

Предыдущий урок     Список уроков     Следующий урок

0

Автор публикации

не в сети 1 день

Эдуард

74
Комментарии: 1405Публикации: 152Регистрация: 13-12-2015

10 комментариев на «Урок 20. Интерфейс UART в STM32. Работа с ним через регистры CMSIS. Использование прерывания UART.»

  1. Привет. Прочитал только про UART, 20 урок. Так держать. Молодец. CMSIS рулит. Обращаюсь ко всем читателям вот таких уроков. Все кто читает такие уроки не скупитесь, заплатите. Хороший труд необходимо поддерживать. Кто в поисках интересной и познавательной информации о микроконтроллерах STM32 на русском языке, тот должен меня понять. Обратите внимание на уроки nordic energy в you tube. На мой взгляд похвально. Автору платежеспособных читателей и прекрасных творческих идей в области просвещения о насыщенных внутренних процессах, протекающих в микроконтроллерах. Описание механики работы микросхемы в картинках плюс примеры кода на CMSIS хорошая база знаний для нормального освоения железа. Краткие записи заполнения регистров периферии с комментариями гораздо лучше усваиваются, чем километры говнокода без комментариев. Хотел бы обратить внимание на знаки «=» и «|=» при записи в регистры при инициализации периферии. Очень хорошо иногда писать просто «=», а иногда только «|=». В примерах Автора хорошо показан этот момент. Видимо понимает о чём пишет;). Тем кто замечает какие либо ошибки или не понимает, что написано автором, сообщайте. Совершенству в обучении микроконтроллеров нет предела.

    3
  2. Эдуард, здоровья Вам и всех благ за Ваш труд.
    Как скоро можно ждать урока по LCD с отображением данных от источников с управлением от кнопок?

    0
    • Здравствуйте! Спасибо за пожелания.
      В планах, конечно, есть LCD дисплеи. Но по срокам ничего не могу обещать. Времени катастрофически не хватает.

      0
  3. Здравствуйте, Эдуард! Спасибо за отличный цикл статей. С другой стороны есть пожелание, не изобретайте пожалуйста велосипед. Отладка проводится через ST Link. Зачем отлаживать проекты через низкоскоростной UART? Это же не ардуино…

    0
  4. Михаил, изобретение велосипеда является лучшим учебным пособием для изучения ребёнком основ механики 🙂

    0
  5. Добрый день. А как можно задать для А10 режим альтернативной функции с помощью библиотеки LL? Я так понял важно для 10ой ножки поставить биты MODE10 регистра CRH в 00, то есть в режим входа.
    Но функция SetPinMode трогает только биты CONF как я понял
    Функция SetPinSpeed меняет MODE10 биты на 10, 01 или 11.
    Как тогда задать на вход 10ую ножку с помощью этих функций?

    0
    • Здравствуйте!
      Почему. Биты MODE функция SetPinMode тоже устанавливает.

      __STATIC_INLINE void LL_GPIO_SetPinMode(GPIO_TypeDef *GPIOx, uint32_t Pin, uint32_t Mode)
      {
      register uint32_t *pReg = (uint32_t *)((uint32_t)(&GPIOx->CRL) + (Pin >> 24));
      MODIFY_REG(*pReg, ((GPIO_CRL_CNF0 | GPIO_CRL_MODE0) < < (POSITION_VAL(Pin) * 4U)), (Mode << (POSITION_VAL(Pin) * 4U))); }

      0
      • MODIFY_REG(*pReg, ((GPIO_CRL_CNF0 | GPIO_CRL_MODE0) < < (POSITION_VAL(Pin) * 4U)), (Mode << (POSITION_VAL(Pin) * 4U)));

        Эта функция, как я понял: 1. очищает регистр в соответствии с маской (CNF0 | MODE0). 2. затем устанавливает "Mode". Если мод будет равен LL_GPIO_MODE_ALTERNATE, то значит в соответствующие биты будет записано (GPIO_CRL_CNF0_1 | GPIO_CRL_MODE0_0)
        то есть в CONF будет 10 (альтернативная функция) а в MODE будет записано 01 (Выход, синхронизация 10 мГц)
        То есть как ни крути, на вход он отсюда не настроится
        И если я нигде не ошибся, то как скинуть эти биты (_MODE) в 00 (настроить на вход)

        0
    • Здравствуйте!
      Самому стыдно. Начал. Сейчас справочник для HAL UART пишу. Далее следующий урок по работе с UART через HAL.

      0

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *