AVR. Работа с UART
UART — самый популярный интерфейс микроконтроллера. Так сложилось потому, что он может использован для связи с персональным компьютером. В этой статье мы постараемся познакомить вас основными сценариями использования UART.
Подготовка к работе
Arduino-совместимые платы имеют на борту переходник USB-to-COM, и наша EduBoard не исключение. Как вы уже знаете, при подключении, в системе она определяется как COM-порт. То есть для исследования работы нам не понадобится никакого дополнительного оборудования. Только любая Arduino-совместимая плата с микроконтроллером Atmega8/168/328. Мы проверили это только с микроконтроллером Atmega8.
Помимо аппаратного обеспечения и среды для программирования, которую мы разворачивали в другой части цикла. Дополнительно, вам понадобится терминальная программа, которая на стороне ПК будет подключаться к COM-порту, отправлять и принимать байты.
Можно скачать готовые самостоятельные терминальные программы, а можно добавить такую возможность в Atmel Studio. Второй способ нам кажется более логичным, поэтому мы будем использовать именно его в статье.
В запущенной среде выберите пункт меню Tools->Extension Manager… и найдите дополнение Terminal for Atmel Studio (это очень популярное дополнение и, скорее всего, оно будет вам предложено сразу).
После его установки, в пункте меню View появится дополнительный пункт Terminal Window.
Если вы запустите его, то увидите само окно для работы с COM-портом.
В окне довольно много настроек. При первом запуске его размер очень маленький. Раздвиньте его, чтобы видеть окно целиком. Органы управления имеют следующее назначение:
- Кнопка для подключения к COM-порту
- Выбор номера порта (проверьте к какому порту подключена ваша плата)
- Скорость передачи. Поскольку передача асинхронная, необходимо на обоих сторонах одинаково задать скорость передачи данных
- Способ отображения принятых данных
- Очистка истории приема
- Очистка истории передачи
- Дополнительные настройки
- Поле с полученными данными
- Поле с отправленными данными
- Поле для ввода данных на отправку
- Формат отправки
- Кнопка "отправить"
Итак, вся настройка выполнена и можно попробовать отправить что-нибудь в COM-порт из памяти микроконтроллера.
Первая программа
Для начала сделаем самое простое — попробуем отправлять в UART данные с микроконтроллера. Заведем переменную i, а затем будем в раз секунду увеличивать ее и отправлять через UART. Все это делает следующий код:
#include <avr/io.h> #define F_CPU 16000000UL //16MHz #include <util/delay.h> uint8_t i = 0; void UARTInit(void) { UBRRH = 0; UBRRL = 103; //baud rate 9600 UCSRB = (1<<RXEN)|(1<<TXEN); UCSRC = (1<<URSEL)|(1<<UCSZ1)|(1<<UCSZ0); //8 bit, 1 stop bit } void UARTSend(uint8_t data) { while(!(UCSRA & (1<<UDRE))); UDR = data; } int main(void) { UARTInit(); while(1) { i++; UARTSend(i); _delay_ms(1000); } }
Если все сделано правильно, то после открытия Terminal Window и нажатии кнопки "Connect" вы увидите как раз в секунду в окне приема будут появляться числа:
Если что-то идет не так, убедитесь, что правильно выбраны параметры передачи — номер порта, скорость, формат отображения. Более подробно параметры указаны в меню "Options".
Теперь разберемся с самой программой.
В первую очередь интересует функция инициализации UARTInit(). В ней необходимо выставить следующие обязатльные настройки:
- UBRR. Этот регистр состоит из двух байт — UBRRH и UBRRL. Они отвечают за скорость передачи. Чаще всего, достаточно обратиться к таблицам 60-63 на страницах 153-156 datasheet’а и посмотреть, что нужно записать в этот регистр для корректной настройки скорости. В нашем случае тактовая частота 16МГц, работа происходит без удвоения на скорости 9600 Бод. Следовательно, в UBRR нужно записать число 103. Это число помещается в один байт. В итоге UBRRH = 0, UBRRL = 103.
- UCSRB. В этом регистре необходимо выставить два бита, которые включают прием и передачу: RXEN и TXEN
- UCSRC. В этом регистре собраны основные параметры передачи. Нам необходимо установить только два бита: UCSZ1 и UCSZ2. Они задают количество бит при передаче равным восьми. Так остальные биты не тронуты, передача будет асинхронной, без проверочной суммы и с одним стоп-битом. Обратите внимание на бит URSEL. Это специальный защитный бит. Если вы будете пытаться изменить состояние регистра, не выставив этот бит в "1", то изменений не произойдет. Поэтому при настройке его необходимо установить вместе с другими настройками.
В завершении настройки нужно указать еще один важный момент. Обычно при выставлении настроек в регистре, не присваивают значение всему регистру, а меняют только нужные биты. Из-за того, что мы используем бутлоадер от Arduino (а он, в свою очередь, UART для загрузки), то лучше защитить себя от того, что могло остаться в этих регистрах после бутлоадера и сконфигурировать UART именно присваиванием.
Если вы пишите программу для чистого микроконтроллера и загружаете программу программатором, то тело функции настройки будет выглядеть так:
UBRRH = 0; UBRRL = 103; //baud rate 9600 UCSRB |= (1<<RXEN)|(1<<TXEN); CSRC |= (1<<URSEL)|(1<<UCSZ1)|(1<<UCSZ0); //8 bit, 1 stop bit
Теперь разберем функцию UARTSend(uint8_t data).
Строка
while(!(UCSRA & (1<<UDRE)));
…означает, что пока в бите UDRE регистра UCSRA "1" — будет выполняться пустой цикл. Бит UDRE (UART Data Register Empty) следит за состоянием регистра, в котором хранятся данные, принятые из UART. Фактически, программа дожидается того, что буфер на отправку чист (предыдущий байт передан) и можно передавать следующий байт.
Следующим шагом мы помещаем в регистр UDR (UART Data Register) данные. То что оказывается в это регистре — немедленно отправляется в COM-порт. Вот и все, что нужно для простейшей передачи восьмибитного числа.
Прием данных
Самый простой способ принимать байты через UART — делать это при помощи прерывания. Прерывание — очень мощный инструмент микроконтроллера, который мы до сих пор не применяли. Прерывание позволяет остановить выполнение основной программы и обработать какое-либо событие. Посмотрим, как это работает на практике.
В первую очередь необходимо подключить заголовочный файл прерываний:
#include <avr/interrupt.h>
Далее глобально разрешаем работу прерываний. Для этого можно воспользоваться простой инструкцией, которую обычно ставят в самом начале программы:
sei();
Далее необходимо разрешить конкретное прерывание. Вообще их много, но нас интересует прерывание UART. За них отвечает бит RXCIE (Receive Complete Interrupt Enable — прерывание по завершению приема) в регистре UCSRB.
UCSRB = 1<<RXCIE;
Когда срабатывает прерывание, то вызывается вектор прерывания. Каждое прерывание имеет свое имя. Конкретно вектор прерывания UART на прием называется USART_RXC_vect. Обработчик прерывания выглядит следующим образом:
ISR(USART_RXC_vect) { rx_data = UDR; rx_flag = 1; }
То есть когда прием через UART будет завершен, вызовется и выполнится этот кусок кода. В нем данные из приемного буфера UDR переносятся в глобальную переменную rx_data и выставляется флаг rx_flag, говорящий о том, что прием завершен.
Теперь добавим все эти доработки в первую программу и получим следующее:
#include <avr/io.h> #define F_CPU 16000000UL //16MHz #include <util/delay.h> #include <avr/interrupt.h> uint8_t receive = 0; uint8_t rx_data = 0; volatile uint8_t rx_flag = 0; void UARTInit(void) { UBRRH = 0; UBRRL = 103; //baud rate 9600 UCSRB = (1<<RXEN)|(1<<TXEN)|(1<<RXCIE); UCSRC = (1<<URSEL)|(1<<UCSZ1)|(1<<UCSZ0); //8 bit, 1 stop bit } void UARTSend(uint8_t data) { while(!(UCSRA & (1<<UDRE))); UDR = data; } unsigned char UARTGet() { while(!rx_flag); rx_flag = 0; return rx_data; } int main(void) { sei(); UARTInit(); while(1) { receive = UARTGet(); receive++; UARTSend(receive); } } ISR(USART_RXC_vect) { rx_data = UDR; rx_flag = 1; }
Обратите внимание на объявление переменных. Перед типом переменной rx_flag стоит служебное слово volatile. Это сигнал для компилятора, что эту переменную оптимизировать нельзя. В противном случае, компилятор выкидывает вот этот цикл:
while(!rx_flag);
Теперь перейдем к функции UARTGet(). Она ждет, пока значение флага не станет равно "1" и затем возвращет состояние переменной rx_data. Она нужна просто для того, чтобы сделать main удобочитаемым.
То есть сама программа доходит до строчки
receive = UARTGet();
и останавливает свое выполнение до тех пор, пока не придут данные. Как только МК что-то принимает, он увеличивает это значение на 1 и отправляет обратно. Откройте Terminal Window и убедитесь в этом (чтобы отправить что-то необходимо ввести цифру в нижнюю строку и нажать Enter).
Управление периферией
В последнем примере хочется уже начать чем-то управлять, чтобы наши команды с ПК оказывали видимое воздействие на реальный мир.
На плате EduBoard (как и на большинстве Arduino-совместимых плат) есть светодиод, подключенный к 13му выводу. В нашей следующей программе мы будем будем включать его, если пользователь отправил "0" и выключать в любом другом случае.
Доработки предельно простые, но мы приведем весь код целиком:
#include <avr/io.h> #define F_CPU 16000000UL //16MHz #include <util/delay.h> #include <avr/interrupt.h> uint8_t receive = 0; uint8_t rx_data = 0; volatile uint8_t rx_flag = 0; void UARTInit(void) { UBRRH = 0; UBRRL = 103; //baud rate 9600 UCSRB = (1<<RXEN)|(1<<TXEN)|(1<<RXCIE); UCSRC = (1<<URSEL)|(1<<UCSZ1)|(1<<UCSZ0); //8 bit, 1 stop bit } void UARTSend(uint8_t data) { while(!(UCSRA & (1<<UDRE))); UDR = data; } unsigned char UARTGet() { while(!rx_flag); rx_flag = 0; return rx_data; } int main(void) { DDRB |= 1<<PB5; PORTB |= 1<<PB5; sei(); UARTInit(); while(1) { receive = UARTGet(); if(receive == 0) PORTB |= 1<<PB5; else PORTB &= ~(1<<PB5); } } ISR(USART_RXC_vect) { rx_data = UDR; rx_flag = 1; }
Итоги
В этой статье мы описали самые простые случаи обмена. Недостатки в том, что на время ожидания команды программа останавливается и трудно наладить передачу блоков данные.
В реальных системах чаще всего организуют приемный буфер, а затем его разбирают. Информацию при этом дополнительно кодируют для детектирования ошибок передачи.
Тем не менее, описанных в статье примеров будет более чем достаточно для организации отладочного вывода или управления домашним роботом.
Индивидуальные задания
- Напишите программу, которая будет подсчитывать количество единиц в принятом из COM-порта байте и отправлять их обратно
- Подключите светодиод к выводу PB1 (как в этой статье) и напишите программу, которая будет выставлять яркость свечения светодиода, значение которой будет приниматься из COM-порта
Остальные статьи цикла можно найти здесь.
Мы будем очень рады, если вы поддержите наш ресурс и посетите магазин наших товаров shop.customelectronics.ru.
Метки: Atmel Studio, baudrate, COM-порт, terminal window, UART, настройка Просмотров: 37721