UART в STM32. Часть 1.

Благодаря предыдущей статье, у нас теперь есть базовые представления об интерефейсе UART. Руководствоясь ими и даташитом на контроллер STM32F100RBT6B попробуем разобраться, как что-то передать через UART в компьютер и наоборот, заставить контроллер принять что-то от компьютера. Начать нужно с того, что UART интерфейс в контроллерах STM32 не один, в вышеупомянутом контроллере их аж три штуки. Я планирую использовать UART1, следовательно передающий провод (TxD) от USB-UART преобразователя я подсоединяю к ноге контроллера работющей на приём (PA10), а передающую ногу (PA9) я подключаю ко входу (RxD) преобразователя USB-UART. Для работы с этим интерфейсом существует великое множество регистров. Для облегчения работы программиста, вместе с CooCox'ом поставлятся библиотека предназначеная для работы с UART'ом. Я довольно хорошо изучил её и она мне понравилась, но для лучшего понимания принципов её работы нужно вкратце познакомится с регистрами. В следующей статье, в которой будет описан практический пример использования UART'a, я буду использовать именно её. Для начала стоит сказать что нужно настроить чтоб интерфейс заработал:  

  1. Включить тактирование порта А
  2. Настроить ногу PA9 как выход push-pull в альтернативном режиме. Частота 50 МГц
  3. Настроить ногу PA10 как вход без подтяжки
  4. Включить тактирование UART1
  5. Настроить параметры UART1: Скорость, кол-во стоп бит, проверку четности итд.

С первыми четырьмя пунктами проблем быть не должно, всё это знакомо из статьи про порты микроконтроллера, а вот с пятым пунктом немного сложнее, но попробуем разобраться. 

Начнем с регистра USART_BRR при помощи которого задаётся скорость приёма/передачи: 

usart_brr

Регистр делится на две части: Целая (DIV_Mantissa) и дробная (DIV_Fraction). Для получения значения которое нужно записать в этот регистр, нужно воспользоваться формулой:

USART_BRR = (fck + baudrate /2 ) / baudrate

где fck это частота тактирования UART1, а baudrate это желаемая скорость передачи/приёма. Со скоростью всё понятно, а как определить fck ? Забегая вперед, скажу, что это отдельная тема для большой статьи (которая будет). Эта частота будет равна 24 Мгц если использовать кварц на 8 МГц и ничего не менять в настройках тактирования, а оставить их по умолчанию. Приведу пример рассчётов: Я хочу настроить уарт на скорость 9600. Путем нехитрых рассчётов получаем (24000000 + 9600 / 2)/ 9600 = 2500.5 потом отсекаем дробную часть, переводим число в шестнадцатеричную систему счисления и получаем 0x9c4. Еще есть другая формула, руководствуясь которой можно вычислить значение регистра USART_BRR. В биты DIV_Mantissa следует записать целое число(без округления) полученное в результате выполнения арифметической операции:

fck / (16*baudrate)

Дробную часть нужно округлить до сотых и умножить на 16. Потом еще раз округлить но уже до целого. После этого записать её в биты DIV_Fraction. Попробуем рассчитать регистр USART_BRR но уже с использованием этого способа. 24000000 / (16 * 9600) = 156,25. Целая часть 156 - пойдет в DIV_Mantissa без изменений, а дробную часть 0.25 умножаем на 16 и получаем 4. Округлять тут нам не пришлось, поэтому в DIV_Fraction записываем 4. Переведем 156 и 4 в шестнадцатиричную систему счисления и получим 0x9c и 0x04 а вместе они образуют 0x9c4. В итоге мы получили тот же самый результат. Но лично мне первый способ больше по душе :) Следующий регистр USART_CR1 

usart cr1

UE - Бит предназначен для включения UART'a. То есть просто подать тактирование мало, чтоб уарт заработал надо установить этот бит в единицу. 

M - задает количество бит даннных которое будет передаваться за раз. Если бит равен 0, тогда уарт будет отправлять/принимать по 8 бит, если единице то 9 бит. Почему бит называется М для меня так и осталдось загадкой.

PCE - Если бит равен 1 то контроль чётности включен, в противном случае выключен.
PS - тип контроля чётности: 0 - чёт, 1 - нечет.
TE - Если бит равен 1 то разрешена работа передатчика (нога TxD)
RE - Если бит равен 1 то разрешена работа приёмника (нога RxD)

Как видно по двум последним битам в этой таблице - передатчик и приёмник друг от друга не зависят. Если не требуется принимать(или передавать) данные, то можно сэкономить одну ножку контроллера (TxD или RxD) просто не включая не нужную нам часть UART'a.

Следующий регистр настроек это USART_CR2

usart_cr2

В нем нас интересуют только два бита: STOP1, STOP0

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

STOP1   STOP0   Количество стоповых бит
0 0 1 стоп бит
0 1 0.5 стоп бита
1 0 2 стоп бита
1 1 1.5 стоп бита


Конечно я описал далеко не все биты, но все нам и не понадобятся, во всяком случае пока. Чтобы произвести минимальную настройку будет достаточно знать то что я написал выше. После того как всё настройки уарта произведены можно попробовать что-то отправить/получить через UART. Для приёма/передачи служит регистр USART_DR:

usart_dr

В нем используются первые 8 или 9 бит (в зависимости от бита M в регистре USART_CR1). Чтоб отправить в UART данные просто записываем их в этот регистр. Чтоб прочитать данные просто читаем этот регистр. С этим ни каких сложностей, но не понятно когда мы можем читать и передавать данные. Ведь отправлять данные очень быстро нельзя, так как уарт может быть настроен на очень маленькую скорость передачи. И пытаясь начать новую передачу данных в то время как предыдущая передача не завершена не имеет особого смысла. С приёмом данных похожая ситуация. Бессмысленно читать регистр данных (USART_DR) если в него ничего не пришло. Для отслеживания состояния в котором находится UART, служит регистр USART_SR.

usart_sr

RXNE - Этот бит устанавливается когда в UART что-то пришло. Если не забрать из USART_DR, данные то они перезатрутся новыми если таковые будут.
TC - Если этот бит установлен в единицу, то это означает что передача данных завершена и можно опять что-то записывать в регистр данных (USART_DR). Остальные биты нам пока не потребуются. Ну все, довольно табличек и теории. Попробуем передать в комп строку с приветствием и смайликом. 

 
#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_rcc.h"

//Функция предназначена для формирования небольшой задержки
void Delay(void) {
  volatile uint32_t i;
  for (i=0; i != 0x70000; i++);
}
 
//Функция отправляющая байт в UART
void send_to_uart(uint8_t data) {
  while(!(USART1->SR & USART_SR_TC)); //Ждем пока бит TC в регистре SR станет 1
  USART1->DR=data; //Отсылаем байт через UART
}
 
int main(void) {
  GPIO_InitTypeDef PORTA_init_struct;
  // Включаем тактирование порта А и USART1
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE);
  // Настраиваем ногу TxD (PA9) как выход push-pull c альтернативной функцией
  PORTA_init_struct.GPIO_Pin = GPIO_Pin_9;
  PORTA_init_struct.GPIO_Speed = GPIO_Speed_50MHz;
  PORTA_init_struct.GPIO_Mode = GPIO_Mode_AF_PP;
  GPIO_Init(GPIOA, &PORTA_init_struct);
  //Настраиваем UART
  USART1->BRR=0x9c4; //BaudRate 9600
  USART1->CR1 |= USART_CR1_UE; //Разрешаем работу USART1
  USART1->CR1 |= USART_CR1_TE; //Включаем передатчик
  //Все остальные параметры (стоп биты, чётность,кол-во байт данных) уже настроены
  //как надо, (во все эти биты по умолчанию записаны нули), таким образом мы имеем
  // скорость 9600 1 стоп бит, 8 бит данных, без проверки чётности
  while(1) {
    //Выдаем в UART слово Hello
    send_to_uart('H');
    send_to_uart('e');
    send_to_uart('l');
    send_to_uart('l');
    send_to_uart('o');
    send_to_uart(' ');
    send_to_uart(':');
    send_to_uart(')');
    send_to_uart('\n');
    Delay(); //небольшая задержка
  }
}

Создаем пустой проект в CooCox'е, копируем код, компилируем и прошиваем. Я тестировал этот код на платке STM32VL Discovery с кварцем 8 МГц. На ногу PA9 я подключил вход (RxD) моего USB-UART преобразователя, настроил терминальную программу на скорость 9600 бит/сек, 1 стоп бит, без проверки чётности.  В результате получил следующую картинку: 

terminal hello

В следующей статье мы попробуем сделать с использованием UART'a нечто более полезное с практической точки зрения.