STM32. Урок 3. UART

Рано или поздно у каждого разработчика появляется необходимость подключить свое устройство к компьютеру. Для этого можно использовать LPT, COM или USB порты. Если первый морально устарел и зачастую отсутствует на материнских платах, а последний слишком сложен для начинающего, то COM порт является наиболее подходящим. Он все еще есть на материнских платах, и он относительно прост в использовании. Как и раньше, я не буду очень сильно углубляться в теоретические дебри, которых уйма на просторах интернета, а постараюсь максимально доступно объяснить основы работы на практике.

Постановка задачи:
1) Подключить STM32F4Discovery к компьютеру через COM порт;
2) Реализовать режим echo;
3) Набираемые символы должны сохраняться в текстовую переменную, по нажатию Enter в консоль должна вернуться полная строка;
4) При отправки строки “led” должны загореться 4 светодиода на плате.
Подключение к компьютеру является наиболее сложной частью задачи. У UART есть 2 основных вывода: Rx (Received Data) и Tx (Transmitted Data). Главная проблема – согласование уровней напряжения, так как в порте компьютера оно составляет 12В, а в порте микроконтроллера — 3,3В. Для решения этой проблемы используется микросхема MAX3232. Схема переходника показана на рисунке.

Схема переходника

Диод VD1 – индикатор питания, светодиод в корпусе 0805, VD2 – защита от переполюсовки, можно взять любой маломощный. Микросхема – MAX3232 в корпусе SO16.

К статье прилагается проект платы в Sprint Layout.

2.png

Плата выполнена на одностороннем стеклотекстолите толщиной 2мм. Ответная часть для COM порта берется для монтажа на провод и одевается на текстолит, получается компактно и аккуратно.
Заранее отмечу, что я не изготавливал этот переходник для урока, так как у меня уже есть платка с аналогичной микросхемой, но такие переходники мною делались ранее.
У переходника 4 вывода:
1) Vcc – подключается к 3В питанию на отладочной плате;
2) Rx – подключается к Rx UART2 (PA3) микроконтроллера;
3) Tx – подключается к Tx UART2 (PA2);
4) GND – подключается к «земле» на плате.
Для связи с компьютером используется программа-терминал Putty.

1.jpg

Настройка:
Во вкладке Session выбираем Serial.
В строке Serial line пишем номер порта, к которому вы подключили плату.
В строке Speed пишем 9600 – это скорость работы.
Переходим во вкладку Serial.

2.jpg

Data bits ставим 8.
Stop bits – 1.
Parity и Flow control надо выставить None.
Нажимаем Open и получаем консоль.

3.jpg

Если подключить переходник к COM порту, выводы Vcc и GND подключить к плате, а Tx и Rx соединить, тогда то, что вы будете писать в консоли, будет возвращаться в нее же. Если при нажатии клавиш ничего не происходит и консоль остается чистой – ищите и исправляйте ошибку.

Итак, с подключением вроде справились. Теперь начинаем писать программу.
Первым делом нужно инициализировать UART, для этого создается функция:

 //Инициализируем USART2 void usart_init(void) {   GPIO_InitTypeDef GPIO_InitStructure; //Структура содержащая настройки порта   USART_InitTypeDef USART_InitStructure; //Структура содержащая настройки USART    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); //Включаем тактирование порта A   RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); //Включаем тактирование порта USART2    GPIO_PinAFConfig(GPIOA, GPIO_PinSource3, GPIO_AF_USART2); //Подключаем PA3 к TX USART2   GPIO_PinAFConfig(GPIOA, GPIO_PinSource2, GPIO_AF_USART2); //Подключаем PA2 к RX USART2    //Конфигурируем PA2 как альтернативную функцию -> 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_2;   GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;   GPIO_Init(GPIOA, &GPIO_InitStructure);    //Конфигурируем PA3 как альтернативную функцию -> RX UART. Подробнее об конфигурации можно почитать во втором уроке.   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;   GPIO_Init(GPIOA, &GPIO_InitStructure);    USART_StructInit(&USART_InitStructure); //Инициализируем UART с дефолтными настройками: скорость 9600, 8 бит данных, 1 стоп бит    USART_Init(USART2, &USART_InitStructure);   USART_Cmd(USART2, ENABLE); //Включаем UART } 

Готово, теперь для инициализации достаточно написать usart_init() в main.
В этой функции UART настраивается по дефолтным значениям. Для более тонкой настройки вместо:

 USART_StructInit(&USART_InitStructure);

Пишем:

 USART_InitStructure.USART_BaudRate = 9600; //Скорость обмена 9600 бод USART_InitStructure.USART_WordLength = USART_WordLength_8b; //Длина слова 8 бит USART_InitStructure.USART_StopBits = USART_StopBits_1; //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; //Включен передатчик и приемник USART2

Теперь надо научится отправлять в COM порт байты, а потом и строки.

 //Функция отправляет байт в UART void send_to_uart(uint8_t data) {  while(!(USART2->SR & USART_SR_TC));  USART2->DR=data; }  //Функция отправляет строку в UART, по сути пересылая по байту в send_to_uart void send_str(char * string) {  uint8_t i=0;  while(string[i])  {   send_to_uart(string[i]);   i++;  } }

Принцип работы прост: send_to_uart отправляет в COM порт байт, а send_str пересылает строку по байту в send_to_uart.

Теперь нужно включить прерывание по приему сообщений от COM порта:

 usart_init(); //Инициализируем UART //Настраиваем прерывания по приему __enable_irq(); //Глобальное включение прерывания NVIC_EnableIRQ(USART2_IRQn); //Включаем прерывания от UART NVIC_SetPriority(USART2_IRQn, 0); //Прерывание от UART, приоритет 0, самый высокий USART2->CR1 |= USART_CR1_RXNEIE; //Прерывание по приему  Установка приоритета актуальна при использовании нескольких прерываний во избежание конфликта. Теперь инициализируем порты, на которых висят светодиоды, как во втором уроке.  GPIO_InitTypeDef GPIO_InitStructure; //Структура содержащая настройки порта RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE); //Включаем тактирование порта D GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12| GPIO_Pin_13| GPIO_Pin_14| GPIO_Pin_15; //Выбераем нужные вывод GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; //Включаем режим выхода GPIO_Init(GPIOD, &GPIO_InitStructure); //вызов функции инициализации 

И самое главное – функция обработки прерываний UART:

 void USART2_IRQHandler (void) {    char uart_data;    if (USART2->SR & USART_SR_RXNE) //Проверяем, прило ли чтонибудь в UART    {    USART2->DR = USART2->DR; //Echo по приему, символ отправленный в консоль вернется    uart_data=USART2->DR; //Считываем то что пришло в переменную...    uart2_rx_buf[uart2_rx_bit]=USART2->DR; //Помещаем принятый байт в буфер.    uart2_rx_bit++; //Наращиваем счётчик байтов буфера.     if(uart_data=='r') //Если пришло сообщение о нажатии Enter...    {    GPIO_ResetBits(GPIOD, GPIO_Pin_12|GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15); //Сбрасываем все пины в «0»    if(strcmp(uart2_rx_buf,"led0r")==0) //Если пришла команда "led0"    {    GPIO_SetBits(GPIOD, GPIO_Pin_12); //Подаем «1» на PD12    }    else if(strcmp(uart2_rx_buf,"led1r")==0) //Если пришла команда "led1"    {    GPIO_SetBits(GPIOD, GPIO_Pin_13); //Подаем «1» на PD13    }    else if(strcmp(uart2_rx_buf,"led2r")==0) //Если пришла команда "led2"    {    GPIO_SetBits(GPIOD, GPIO_Pin_14); //Подаем «1» на PD14    }    else if(strcmp(uart2_rx_buf,"led3r")==0) //Если пришла команда "led3"    {    GPIO_SetBits(GPIOD, GPIO_Pin_15); //Подаем «1» на PD15    }    else    {    send_str("n"); //Переходим на новую строку    send_str("String: ");    send_str(uart2_rx_buf); //Отправляем ее обратно в консоль    }     memset(uart2_rx_buf, 0, sizeof(uart2_rx_buf)); //Очищаем буфер    uart2_rx_bit=0; //Сбрасываем счетчик    send_str("n");    }    } }

Вот и все, теперь запускаем консоль, прошиваем плату, подключаем и набираем строку, а затем жмем Enter и наслаждаемся результатом. Команды led0, led1, led2, led3 управляют светодиодами на плате. Как это все работает видно на видео. Исходники и плата прикреплены к статье.

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