Наш канал в telegram

Программирование ARM-контроллеров STM32 на ядре Cortex-M3. Часть 12. Работа с модулями USART и UART.

Общее описание

USART — это модуль универсального синхронно-асинхронного приёмо-передатчика. В первую очередь эти модули используются для организации связи по интерфейсу RS-232 и другим, имеющим аналогичный формат фреймов (RS-422, RS-485). Модули, естественно, реализуют не весь интерфейс, а только нижний уровень логики (формат передачи одного фрейма, байта), к которому потом ещё нужно прикрутить правильную физику и, возможно, протокол более высокого уровня, но это не суть.

Кроме описанных выше, модули USART позволяют реализовать интерфейсы IrDA (обмен данными по ИК-каналу), LIN (используется в автомобилях совместно с CAN), SmartCard (ISO7816-3: чип-карты, симки и так далее).

Всего в контроллерах stm32 может быть до 3-х модулей USART (USART1, USART2, USART3) и до 2-х модулей UART (UART4, UART5) в зависимости от конкретной модели.

Модули UART предоставляют практически те же самые возможности, что и USART за исключением возможностей синхронного обмена данными и аппаратного контроля передачи (соответственно, для этих модулей недоступен режим Smartcard). Кроме того, для модуля UART5 недоступна работа через DMA.

Таблица возможностей различных модулей USART:

Таблица возможностей модулей USART контроллеров stm32

Физически выводы USART мультиплексированы с выводами GPIO. Для того, чтобы контроллер понял, что вы хотите использовать эти выводы именно в качестве выводов USART, — они должны быть специальным образом настроены в регистрах настройки GPIO:

Настройка выводов контроллера stm32 для работы с USART

Кроме того, с помощью регистров настройки AFIO можно «сремапить» (перенести) выводы некоторых модулей USART на другие ноги контроллера. В таблице ниже указано какие ноги используются модулями USART по умолчанию и куда их можно сремапить:

Выводы, используемые модулями USART контроллеров stm32

Управление модулями осуществляется через специальные регистры. Подробно эти регистры описаны в Reference manual RM0008, но коротенько я на них всё равно остановлюсь.

Регистры

USART_SR — регистр статуса. Младшие 10 бит этого регистра позволяют отслеживать состояние и различные события модуля USART. По каждому из этих событий может быть сгенерировано прерывание (настройка прерываний осуществляется в регистрах USART_CRx).

регистр USART_SR

  • CTS: устанавливается в 1 при изменении состояния линии CTS, если это разрешено настройками. Сбрасывается программно, записью в него нуля.
  • LBD: устанавливается в 1 при обнаружении сигнала Break для интерфейса LIN. Сбрасывается программно, записью в него нуля. По этому событию может генерироваться прерывание.
  • TXE (transmit data register empty): устанавливается в 1 после того, как все биты регистра TDR будут переданы в сдвиговый регистр передатчика. Сбрасывается автоматически при записи в регистр USART_DR.
  • TC (transmission complete): устанавливается в 1 после того как будут переданы все биты из сдвигового регистра передатчика, если при этом установлен бит TXE (то есть если в регистре данных передатчика нет новых данных для передачи). Сбрасывается автоматически, выполнением программной последовательности: чтение регистра USART_SR с последующей записью в регистр USART_DR. Также можно сбросить записью в этот бит нуля.
  • RXNE (read data register not empty): устанавливается в 1 после того, как очередная порция принятых данных будет перемещена из сдвигового регистра приёмника в регистр данных приёмника. Сбрасывается автоматически при чтении регистра USART_DR. Также можно сбросить записью в этот бит нуля.
  • IDLE: устанавливается в 1 после обнаружения на линии сигнала idle. Сбрасывается автоматически, выполнением программной последовательности: чтение регистра USART_SR с последующим чтением регистра USART_DR.
  • ORE (overrun error): устанавливается в 1, если в сдвиговом регистре приёмника готова очередная порция данных для передачи в регистр данных приёмника, но при этом из регистра данных приёмника ещё не считали предыдущие принятые данные. При возникновении этой ошибки данные в регистре RDR сохраняются, а в сдвиговом регистре приёмника перезаписываются.
  • NE (noise error): устанавливается в 1, если в принятом фрейме были обнаружены помехи (результаты нескольких выборок для какого-либо бита не совпали).
  • FE (framing error): устанавливается в 1 при обнаружении рассинхронизации (например, если приёмник в нужное время не обнаружил ожидаемые стоповые биты).
  • PE: устанавливается в единицу при обнаружении ошибки чётности.

Все биты ошибок (ORE, NE, FE, PE) сбрасываются автоматически при выполнении программной последовательности: чтение регистра USART_SR с последующим чтением регистра USART_DR.

USART_DR — регистр данных. Младшие 9 бит этого регистра используются для доступа к регистрам данных приёмника и передатчика. Фактически регистры для приёма и передачи — это разные регистры (TDR и RDR), но к ним нет прямого доступа, доступ к обоим этим регистрам осуществляется через USART_DR. Когда мы читаем данные из регистра USART_DR, то фактически они читаются из регистра RDR, а когда пишем данные в USART_DR, то фактически они пишутся в регистр TDR.

регистр USART_DR

USART_BRR — регистр настройки скорости передачи.

формула для вычисления USART_BRR

Младшие 4 бита регистра USART_BRR называются DIV_Fraction и содержат информацию о дробной части значения USARTDIV. Биты [4:15] регистра USART_BRR называются DIV_Mantissa и содержат информацию о целой части значения USARTDIV. Правила тут следующие: в DIV_Mantissa записывается целая часть значения USARTDIV, а в DIV_Fraction — умноженная на 16 и округлённая до ближайшего целого дробная часть USARTDIV. Если полученное после округления число занимает больше 4 бит, то старший бит добавляется к целой части, а в DIV_Fraction записываются только младшие 4 бита.

регистр USART_BRR

Например, при вычислениях получилось, что USARTDIV = 0d50.99, то есть целая часть = 0d50 = 0x32, а умноженная на 16 дробная часть = 0d15.84. При округлении до целого умноженной на 16 дробной части получаем 0d16 = 0x10. Число 0x10 занимает больше 4-х бит, следовательно DIV_Fractional = 0, а старшая единица добавляется к целой части, которая получается равной 0d50 + 1 = 0d51 = 0x33. Соответственно в USART_BRR нужно записать значение 0x330.

USART_СR1 — регистр настроек.

регистр USART_CR1

  • UE (usart enable): 1 — включение модуля USART, 0 — выключение.
  • M: определяет количество информационных битов в посылке (0 — 8 бит, 1 — 9 бит).
  • WAKE: метод пробуждения из режима ожидания (0 — по сигналу IDLE, 1 — по приёму спецслова, адреса).
  • PCE (parity control enable): 1 — включить контроль чётности, 0 — выключить.
  • PS (parity selection): 1 — проверяется нечётность, 0 — проверяется чётность.
  • PEIE: 1 — включить прерывание по ошибке чётности (флаг PE регистра статуса), 0 — выключить.
  • TXEIE: 1 — включить прерывание по опустошению регистра передатчика (флаг TXE регистра статуса), 0 — выключить.
  • TCIE: 1 — включить прерывание по завершению передачи (флаг TC регистра статуса), 0 — выключить.
  • RXNEIE: 1 — включить прерывание по приёму данных (флаги ORE или RXNE регистра статуса).
  • IDLEIE: 1 — включить прерывание по обнаружению на линии сигнала IDLE (флаг IDLE регистра статуса).
  • TE (transmitter enable): 1 — включение передатчика, 0 — выключение.
  • RE (receiver enable): 1 — включение приёмника, 0 — выключение.
  • RWU: 1 — переключение приёмника в режим ожидания, 0 — активный режим.
  • SBK: 1 — послать в линию сигнал break. Сбрасывается автоматически во время передачи стоповых битов сигнала break.

USART_СR2 — регистр настроек.

регистр USART_CR2

  • LINEN: 1 — включение режима LIN, 0 — выключение.
  • STOP[1:0]: определяет количество стоповых битов (00 — 1, 01 — 0.5, 10 — 2, 11 — 1.5).
  • CLKEN (clock enable): 1 — включить пин CK, 0 — выключить.
  • PCE (parity control enable): 1 — включить контроль чётности, 0 — выключить.
  • CPOL (clock polarity): определяет уровень сигнала на линии CK в отсутствии импульсов (0 — низкий, 1 — высокий).
  • CPHA (clock phase): определяет по которому фронту импульсов на линии CK происходит захват бита с линии данных (0 — по переднему, 1 — по заднему).
  • LBCL (last bit clock pulse): позволяет выбрать нужно ли тактировать через линию CK последний передаваемый бит (0 — не нужно, 1 — нужно).
  • LBDIE: 1 — включить прерывание по обнаружению сигнала break (в режиме LIN), 0 — выключить.
  • LBDL: определяет длительность ожидаемого сигнала break (0 — 10 бит, 1 — 11 бит).
  • ADD[3:0]: адрес узла USART. Здесь задаётся адрес текущего узла. Получив этот адрес приёмник выйдет из режима ожидания (если выбран соответствующий метод пробуждения).

USART_СR3 — регистр настроек.

регистр USART_CR3

  • CTSIE (CTS interrupt enable): 1 — включение прерывания по линии CTS (флаг CTS в регистре статуса), 0 — выключение.
  • CTSE (CTS enable): включение контроля линии CTS (данные начнут передаваться только когда на линии CTS установится низкий уровень).
  • RTSE (RTS enable): включение контроля линии RTS (на линии RTS выставляется низкий уровень только когда регистр приёмника пуст и готов к приёму).
  • DMAT (DMA enable transmitter): 1 — включить режим DMA для передатчика, 0 — выключить.
  • DMAR (DMA enable receiver): 1 — включить режим DMA для приёмника, 0 — выключить.
  • SCEN: 1 — включение режима Smartcard, 0 — выключение.
  • NACK: оперделяет посылать или нет NACK в режиме Smartcard при ошибке чётности (1 — посылать, 0 — не посылать).
  • HDSEL: 1 — включить однопроводный полудуплексный режим, 0 — выключить.
  • IRLP: 1 — включение режима low-power для IrDA, 0 — IrDA в режиме normal.
  • IREN (IrDA enable): 1 — включение режима IrDA, 0 — выключение.
  • EIE (error interrupt enable): 1 — включение прерывание по ошибкам передачи (флаги FE, ORE, NE регистра статуса), 0 — выключение.

Есть ещё регистр USART_GTPR, но в нём прописываются настройки только для режимов Smartcard и IrDA, так что про него сами в доке почитаете.

Техника программирования

Теперь давайте посмотрим, как и в какой последовательности нужно программировать модули USART для организации обмена данными в асинхронном режиме (ну, просто этот режим самый популярный).

Конфигурирование и настройка:

  • Включить тактирование USARTx, GPIO, AFIO. Выполняется в регистрах настройки RCC.
  • Если нужно, то сремапить выводы USART на другие пины. Выполняется в регистрах настройки AFIO.
  • Перевести используемые для USART выводы в нужные режимы. Как должны быть настроены выводы можно посмотреть в таблице 24 документа RM0008 (или вверху этой статьи, вторая картинка). Выполняется в регистрах настройки GPIO.
  • Настроить USART (скорость, режим, количество информационных и стоповых битов). Выполняется в регистрах настройки USART (там же сразу нужно выбрать, должен ли быть включен передатчик, приёмник или и то и другое).
  • Включить модуль USART. Выполняется в регистре USART_CR1.

Кроме того:

  • Если предполагается использовать прерывание от модуля USART, то в регистрах настройки USART необходимо разрешить прерывание от нужных вам событий. Помимо этого, если необходимо, то нужно настроить в регистрах NVIC_IPRx приоритет для прерывания от UART. И, наконец, в регистрах NVIC_ISERx нужно включить само прерывание.

    Учтите, что для каждого модуля USART генерируется всего одно прерывание. Далее нужно самостоятельно (по флагам регистра статуса) определять какое именно событие его вызвало.

  • Если для передатчика или приёмника будет использоваться DMA, то нужно включить для них режим DMA в регистре USART_CR3, а также настроить сам модуль DMA.

Отправка данных без использования DMA:

  • Убеждаемся, что бит TXE установлен (регистр передатчика пуст и в него можно писать данные).
  • Пишем данные для отправки в регистр USART_DR (это автоматически сбрасывает флаг TXE).
  • Анализируем флаг TXE (как только он снова взведётся — можно писать в регистр данных передатчика новый байт).
  • После записи последнего байта ждём пока последовательно установятся флаги TXE и TC в регистре статуса, что является признаком окончания передачи (последний байт ушёл из регистра данных передатчика и далее из сдвигового регистра передатчика).

Отправка данных с использованием DMA:

  • Заполняем буфер для передачи.
  • Включаем канал DMA, к которому подключен передатчик USART. Контроллер DMA будет сам анализировать флаг TXE и пихать очередной байт в регистр данных передатчика как только он освободится.
  • Ждём событие Transfer Complete для канала DMA (которое означает, что последний байт буфера был передан в регистр данных передатчика), после чего ждём последовательной установки флагов TXE и TC (последний байт ушёл из регистра данных передатчика и далее из сдвигового регистра передатчика).

Если для используемого канала DMA выбран режим Normal, то при каждом использовании DMA взводить счётчик переданных байт нужно вручную. Для этого выключают канал DMA, взводят счётчик (пишут в него количество байт для передачи) и снова включают канал DMA.

Если же для используемого канала DMA выбран режим Сircular, то после каждого обнуления счётчика он будет автоматически инициализироваться исходным значением. Соответственно, тут же начнётся передача новой порции данных. И так до тех пор, пока канал DMA не будет выключен.

Приём данных без использования DMA:

  • Анализируем бит RXNE в регистре статус. Его установка означает, что из сдвигового регистра приёмника в регистр данных приёмника был вытеснен очередной байт и его можно забирать.
  • Далее нужно прочитать и проанализировать регистр статуса на наличие ошибок, а также успеть забрать принятый байт до того, как придёт следующий. Чтение регистра данных приёмника автоматически очищает бит RXNE.

Приём данных с использованием DMA:

  • Включаем канал DMA, к которому подключен приёмник USART. С этого момента контроллер DMA будет сам анализировать флаг RXNE и при наличии принятого байта вычитывать его из регистра данных приёмника в буфер (что автоматически будет сбрасывать флаг RXNE).
  • Событие Transfer Complete для канала DMA в случае приёма будет означать, что приёмный буфер целиком заполнен. Но нам всё равно нужно после каждого принятого байта анализировать регистр статуса на наличие ошибок. Это можно сделать, установив прерывание по событию RXNE или просто включив прерывание по ошибкам.

Пример

В качестве примера рассмотрим программу, которая будет принимать байты в буфер через USART, и отправлять этот буфер обратно после того, как в нём накопится 5 байт.

Текст программы

#include "stm32f10x.h" /* стандартные хидеры */
 
/* сначала всякие переменные (ну а как вы хотели с языками высокого уровня) */
GPIO_InitTypeDef PORT_Structure;	/* структура для настройки линий порта */
USART_InitTypeDef USART_Structure;	/* структура для инициализации USART */
NVIC_InitTypeDef NVIC_Structure;	/* структура для настройки NVIC */
DMA_InitTypeDef DMA_Structure;		/* структура для настройки DMA */
 
uint8_t USART_Value[5];			/* буфер для USART */
uint8_t temp;				/* счётчик полученных байт */
 
/* Прототипы функций */
void GPIO_Configuration(void);		/* функция настройки GPIO */
void DMA_Configuration(void);		/* функция настройки DMA */
void USART_Configuration(void);		/* функция настройки USART */
void NVIC_Configuration(void);		/* функция настройки NVIC */
 
/* Основная программа */
int main(void) {
 
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);	/* включаем тактирование DMA */
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 |
				RCC_APB2Periph_GPIOA |
				RCC_APB2Periph_AFIO, ENABLE);	/* включаем тактирование USART1, GPIOA, AFIO */
	temp = 0;		/* обнуляем счётчик */
 
	GPIO_Configuration();		/* настраиваем GPIO */
	DMA_Configuration();		/* настраиваем DMA */
	USART_Configuration();		/* настраиваем USART */
	NVIC_Configuration();		/* настраиваем NVIC */
 
Work:
	goto Work;
}
 
 
/* Настройка GPIO */
void GPIO_Configuration(void)
{	/* Configure USART1 Tx (PA.09) as alternate function push-pull */
	PORT_Structure.GPIO_Pin = GPIO_Pin_9;			/* пин 9, сюда подключена линия Tx модуля USART */
	PORT_Structure.GPIO_Mode = GPIO_Mode_AF_PP;		/* альтернативная функция push-pull */
	PORT_Structure.GPIO_Speed = GPIO_Speed_50MHz;		/* максимальная скорость 50 МГц */
	GPIO_Init(GPIOA, &PORT_Structure);
 
	/* Configure USART1 Rx (PA.10) as input floating */
	PORT_Structure.GPIO_Pin = GPIO_Pin_10;			/* пин 10, сюда подключена линия Rx модуля USART */
	PORT_Structure.GPIO_Mode = GPIO_Mode_IN_FLOATING;	/* аналоговый вход */
	/* максимальная скорость не меняется, так что заново этот элемент структуры можно не записывать */
	GPIO_Init(GPIOA, &PORT_Structure);
}
 
void DMA_Configuration(void)
{	/* инициализируем четвёртый канал DMA1 (к этому каналу подключен передатчик USART1) */
	DMA_Structure.DMA_PeripheralBaseAddr = (uint32_t)&(USART1->DR);
	DMA_Structure.DMA_MemoryBaseAddr = (uint32_t)&USART_Value;
	DMA_Structure.DMA_DIR = DMA_DIR_PeripheralDST;		/* направление из памяти в периферию */
	DMA_Structure.DMA_BufferSize = 5;
	DMA_Structure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
	DMA_Structure.DMA_MemoryInc = DMA_MemoryInc_Enable;
	DMA_Structure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
	DMA_Structure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
	DMA_Structure.DMA_Mode = DMA_Mode_Normal;		/* после отправки всех данных нужно взводить счётчик вручную */
	DMA_Structure.DMA_Priority = DMA_Priority_Low;
	DMA_Structure.DMA_M2M = DMA_M2M_Disable;
	DMA_Init(DMA1_Channel4, &DMA_Structure);
}
 
void USART_Configuration(void)
{	/* инициализируем USART */
	USART_Structure.USART_BaudRate = 115200;					/* BaudRate = 115200 */
	USART_Structure.USART_WordLength = USART_WordLength_8b;				/* Word Length = 8 Bits */
	USART_Structure.USART_StopBits = USART_StopBits_1;				/* One Stop Bit */
	USART_Structure.USART_Parity = USART_Parity_No;					/* No parity */
	USART_Structure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;	/* Hardware flow control disabled */
	USART_Structure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;			/* Receive and transmit enabled */
	USART_Init(USART1, &USART_Structure);
 
	USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);	/* включаем режим DMA для передатчика USART */
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);	/* включаем прерывание по приёму очередного байта */
	USART_Cmd(USART1, ENABLE);			/* включаем USART */
}
 
void NVIC_Configuration(void)
{	/* включаем прерывание для USART1 */
	NVIC_Structure.NVIC_IRQChannel = USART1_IRQn;
	NVIC_Structure.NVIC_IRQChannelPreemptionPriority = 0;
	NVIC_Structure.NVIC_IRQChannelSubPriority = 0;
	NVIC_Structure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_Structure);
}
 
/* обработчик прерывания от USART1 */
void USART1_IRQHandler(void)
{	/* у нас для UART1 включено прерывание только по одному событию, поэтому */
	/* можно не выяснять что конкретно случилось, а сразу приступать к чтению */
	/* бит RXNE сбросится автоматически при чтении байта */
	USART_Value[temp] = USART_ReceiveData(USART1);	/* сохраняем принятый байт в буфер */
	temp = temp+1;
	if(temp == 5)							/* если приняли 5 байт */
	{	temp = 0;						/* обнуляем счётчик */
		DMA_Cmd(DMA1_Channel4, DISABLE);			/* выключаем четвёртый канал DMA1 */
		DMA1_Channel4->CNDTR = 5;				/* взводим счётчик */
		DMA_Cmd(DMA1_Channel4, ENABLE);				/* включаем четвёртый канал DMA1 */
	}
}

[свернуть]
  1. Часть 1. Установка MDK, создание проекта, основы Keil uVision
  2. Часть 2. Команды и директивы ассемблера, структура и синтаксис программы. Первая программа для STM32
  3. Часть 3. Карта памяти контроллеров STM32, методы работы с памятью
  4. Часть 4. Регистры, старт и режимы работы контроллеров STM32
  5. Часть 5. Как залить прошивку в контроллер
  6. Часть 6. Настройка системы тактирования
  7. Часть 7. Работа с портами ввода-вывода
  8. Часть 8. Процедуры на ассемблере для STM32
  9. Часть 9. Система прерываний
  10. Часть 10. CMSIS, использование стандартных библиотек и функций
  11. Часть 11. Подключение и использование драйверов из пакета StdPeriph
  12. Часть 12. Работа с модулями USART и UART.
  13. Часть 13. Работа с модулями ADC
  14. Часть 14. Использование DMA
  15. Приложение 1. Набор инструкций THUMB-2 и особенности их использования
  16. Приложение 2. Таблица векторов прерываний для семейств STM32F101, STM32F102, STM32F103
  17. Приложение 3. Драйвера и функции библиотеки StdPeriph

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