Теперь, когда мы познакомились с UART’ом довольно подробно, попробуем создать устройство которое возможно даже послужит для чего-то полезного. Одно я могу сказать точно — оно поможет еще лучше понять как работать с UARTом. Устройство не особо хитрое — есть 10 светодиодов, состоянием которых мы можем управлять с компьютера через UART. Когда мы отправляем с компьютера символ «1» у нас загорается первый светодиод, отправляем еще раз — светодиод гаснет. Отправляем символ «?» и контроллер выдает нам состояние всех светодиодов. Аппаратно ничего нового (кроме светодиодов) тут нет. Я использовал свою любимую STM32VL Discovery и беспаячную макетную платку, в результате был рождён этот монстр 🙂
Практическое применение данного устройства — управлять нагрузками с компьютера. Разумеется, придётся внести некоторые аппаратные изменения — вместо светодиодов поставить транзисторные ключи с реле. Ну а если практического использование не интересно, то поиграем со светодиодами
В схеме ничего интересного нет, 10 светодиодов, 10 резисторов и контроллер. Преобразователь интерфейсов USB-UART (или RS-232 — UART) на схеме не показан для простоты. Его можно собрать, например по схеме из самой первой статьи про UART. А вот на коде остановимся поподробнее. Во-первых в этом примере мы не только передаем что-то из устройства в компьютер, но и принимаем данные от компьютера. Вторая особенность — использование специализированной библиотеки для работы с UARTом. Код выглядит страшновато, но попробуем разобраться что к чему.
#include "stm32f10x.h" #include "stm32f10x_gpio.h" #include "stm32f10x_rcc.h" #include "stm32f10x_usart.h" //Макрос для проверки состояние определенного светодиода. #define get_led_state(pin) ((GPIOB->ODR & pin) != 0) //Функция отправляет байт в UART void send_to_uart(uint8_t data) { while(!(USART1->SR & USART_SR_TC)); USART1->DR=data; } //Функция отправляет строку в UART void send_str(char * string) { uint8_t i=0; while(string[i]) { send_to_uart(string[i]); i++; } send_to_uart('\r'); send_to_uart('\n'); } int main(void) { uint8_t uart_data; uint16_t led; //Включаем порты и UART1 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_AFIO | RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB , ENABLE); //Выключаем JTAG (он занимает ноги нужные нам) GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE); //Настраиваем первые 10 ног порта B на выход GPIO_InitTypeDef gpio_port; gpio_port.GPIO_Pin = 0x3FF; gpio_port.GPIO_Mode = GPIO_Mode_Out_PP; gpio_port.GPIO_Speed = GPIO_Speed_2MHz; GPIO_Init(GPIOB, &gpio_port); GPIOB->ODR=0x00; // Настраиваем ногу PA10 как вход UARTа (RxD) gpio_port.GPIO_Pin = GPIO_Pin_10; gpio_port.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &gpio_port); // Настраиваем ногу PA9 как выход UARTа (TxD) // Причем не просто выход, а выход с альтернативной функцией gpio_port.GPIO_Pin = GPIO_Pin_9; gpio_port.GPIO_Speed = GPIO_Speed_50MHz; gpio_port.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_Init(GPIOA, &gpio_port); //Заполняем структуру настройками UARTa USART_InitTypeDef uart_struct; uart_struct.USART_BaudRate = 9600; uart_struct.USART_WordLength = USART_WordLength_8b; uart_struct.USART_StopBits = USART_StopBits_1; uart_struct.USART_Parity = USART_Parity_No ; uart_struct.USART_HardwareFlowControl = USART_HardwareFlowControl_None; uart_struct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //Инициализируем UART USART_Init(USART1, &uart_struct); //Включаем UART USART_Cmd(USART1, ENABLE); while(1) //Бесконечный цикл в нем мы проверяем ... { if (USART1->SR & USART_SR_RXNE) { // ... не пришло ли что-то в UART ? uart_data=USART1->DR; //Считываем то что пришло в переменную... switch(uart_data) { //И выполняем определённое действие... case '0': GPIOB->ODR^=GPIO_Pin_0; //Инвертируем состояние ноги №0 break; case '1': GPIOB->ODR^=GPIO_Pin_1; break; case '2': GPIOB->ODR^=GPIO_Pin_2; break; case '3': GPIOB->ODR^=GPIO_Pin_3; break; case '4': GPIOB->ODR^=GPIO_Pin_4; break; case '5': GPIOB->ODR^=GPIO_Pin_5; break; case '6': GPIOB->ODR^=GPIO_Pin_6; break; case '7': GPIOB->ODR^=GPIO_Pin_7; break; case '8': GPIOB->ODR^=GPIO_Pin_8; break; case '9': GPIOB->ODR^=GPIO_Pin_9; break; case '?': //Печатаем состояние каждого светодиода send_str("===LEDs state:==="); for (led=1;led<=0x200;led=led<<1) { if (get_led_state(led)) { send_str("ON"); } else { send_str("OFF"); } } send_str("================="); break; } } } }
Для начала пару слов о настройке UARTa. Как я уже говорил, я использовал в этом примере специальную библиотеку, чего и вам рекомендую. Конечно, знать для чего нужны все эти регистры для управления UARTом надо, но напрямую настраивать UART через них это дольше и сложнее. Куда проще заполнить структуру и вызвать функцию для настройки и все готово к работе. Тем более код становится более читабельным. Теперь о передаче данных в компьютер. Я написал две функции send_to_uart и send_str. Первая передаёт байт в UART, вторая передаёт строку в UART. Передача строки представляет собой вызов send_to_uart для каждого её символа. Строка должна заканчиваться нульсимволом. Приём данных от компьютера реализован очень тупо и просто. В бесконечном цикле постоянно проверяется бит RXNE статусного регистра USART1. Если этот бит установился в единицу, значит что-то пришло и надо забрать данные и обработать их. После того как байт данных принят он проверяется на соответствие символам от 0 до 9 или знаку вопроса. Если в UART пришла цифра то мы просто меняем состояние соответствующей ноги на противоположное. Ну а если пришел знак вопроса то при помощи макроса get_led_state проверяем состояние каждого светодиода и печатаем соответствующую информацию в UART. Для экспериментов я использовал свой любимый терминал:
Ну собственно вот что у меня получилось после нажатия 012789?. Естественно при этом соответствующие светодиоды загорелись. Если у вас получился иной результат, пишите ниже. Попробуем разобраться.
P.S. На всякий случай выкладываю свой проект который у меня получился.