Рано или поздно у каждого разработчика появляется необходимость подключить свое устройство к компьютеру. Для этого можно использовать 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мм. Ответная часть для COM порта берется для монтажа на провод и одевается на текстолит, получается компактно и аккуратно.
Заранее отмечу, что я не изготавливал этот переходник для урока, так как у меня уже есть платка с аналогичной микросхемой, но такие переходники мною делались ранее.
У переходника 4 вывода:
1) Vcc – подключается к 3В питанию на отладочной плате;
2) Rx – подключается к Rx UART2 (PA3) микроконтроллера;
3) Tx – подключается к Tx UART2 (PA2);
4) GND – подключается к «земле» на плате.
Для связи с компьютером используется программа-терминал Putty.
Настройка:
Во вкладке Session выбираем Serial.
В строке Serial line пишем номер порта, к которому вы подключили плату.
В строке Speed пишем 9600 – это скорость работы.
Переходим во вкладку Serial.
Data bits ставим 8.
Stop bits – 1.
Parity и Flow control надо выставить None.
Нажимаем Open и получаем консоль.
Если подключить переходник к 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 о разработке которой можно почитать в этой теме