Работаем с UART на AVR
Пару слов о источнике информации
В начале поста, я стараюсь приводить источники, откуда я беру информацию. Обычно это ссылки на сайты разработчиков, где, я указываю какие стандарты и даташиты описывают данное устройство или технологию. Редко, в порядке исключения даю ссылки на хорошие (на мой взгляд) посты блогов, которым можно доверять. У технической информацию должен быть незыблемый источник
С UART так не выйдет … сама идея последовательной асинхронной передачи данных настолько стара, что, к сожалению, на технологию UART нет прямого стандарта, где было-бы рассказана его логика работы.
Но, забегу вперед, описание UART все таки есть, но косвенное … его можно найти в описаниях на физический протоколы RS-232, RS-422, RS-423, RS-485, но об этом позже.
Немного теории
UART (универсальный асинхронный приёмопередатчик) — одна из старейших и самых распространенных на сегодняшний день технологий передачи данных. Слово «асинхронный » означает, что интерфейс не использует линию для синхросигнала, приемник и передатчик заранее настраиваются на одну частоту.
Можно сказать с уверенностью, что каждый микроконтроллер/микропроцессор имеет в своем составе универсальный последовательный интерфейс — UART. Умея работать с данным портом вы развязываете себе руки, когда необходимо согласовать работу старых и современных электронных устройств, принять или передать данные в устройства.
В современных микроконтроллерах, часто вместо UART используют полностью с ним совместимый — USART (универсальный асинхронный/синхронный приёмопередатчик).
USART это более гибкий в настройки UART с дополнительными возможностями. Например в USART можно регулировать длину слова с более большим диапазоном (от 5 до 9) чем в UART (от 8 до 9). В USART как видно из названия возможна как асинхронная так и синхронная передача данных (в UART только асинхронная). При синхронной передачи помимо 2-ух лини — данные и питания, используется дополнительная линия (XCK) с синхросигналом. С такой конфигурацией USART уже пересекается с интерфейсом SPI и его можно использовать как «ведущий » в интерфейсе SPI.
Мы будем рассматривать классический случай, когда интерфейс асинхронный (т.е. без линии синхросигнала).
Передача данных в UART осуществляется по одному биту в равные промежутки времени. Этот временной промежуток определяется заданной скоростью UART и для конкретного соединения указывается в бодах, что соответствует количество бит в секунду. Существует общепринятый ряд стандартных скоростей: 300; 600; 1200; 2400; 4800; 9600; 19200; 38400; 57600; 115200; 230400;460800; 921600 бод;
Скорость (S, бод) и длительность бита (T, секунд) связаны соотношением T=1/S.
Байт данных отправляются в пакетах (1 -й бит перед байтом данных и 2 -а бита после, количество бит опциональны).
(p.s. наличие и количество проверочного и стопового бита опциональны)
Для приема и передачи данных UART использует две линии данных и земля:
- передающая данные (TXD или TX);
- принимающая данные (RXD или RX);
- земля (GND).
Уровню логической единицы и нуля соответствует уровням TTL:
- т.е. 1-ца это +5В;
- а 0 это 0В.
Немного отступлю… все равно этот вопрос рано или поздно у вас возникнет…
UART понятно, а что тогда такое RS-232, COM?
читайте в посте по ссылке
Как работать с USART на AVR?
В качестве AVR микроконтроллера я буду рассматривать ATmega8.
- за прием данных в USART (RxD) отвечает ножка PDO
- за передачу данных из USART (TxD) отвечает ножка PD1
- если будет использоваться линия синхронизации, то данную функцию несет ножка XCK
Для передачи и приема данных необходимо записать или считать данные из регистра UDR. При чтение вы обращаетесь к буферу приемника, при записи к буферу передатчика, важно заметить, что при считывание состояние UDR изменяется. Это означает что считывать информацию можно только однажды. С передачей и приемом все просто, идем далее, как управлять USART?
Для управлением работы с USART используются следующие регистры:
- UCSRA — содержит в основном флаги состояния приема/передачи данных.
- UCSRB — определяет какие прерывания генерировать при наступление событий, разрешает / запрещает передачу / прием, совместно с регистром UCSRC определяет разрядность передаваемого/принимаемого слова.
- UCSRC — задает режим работы синхронный / асинхронный, определяет правила работы контроля данных — проверка на четность / не честность или отключено, количество стоп битов, совместно с регистром UCSRB определяет разрядность передаваемого/принимаемого слова, определяет по какому фронту принимать / передавать данные — по спадающему или по нарастающему.
- UBRR — определяет скорость приема/передачи данных
О каждом регистре немного поподробнее…
Регистр UCSRA состоит из следующих бит:
- RXC — флаг завершения приема, устанавливается в 1 при наличие непрочитанных данных в буфере приемник — UDR;
- TXC — флаг завершения передачи, устанавливается в 1 при передачи всех разрядов из передатчика — UDR;
- UDRE — флаг опустошения регистра передатчика, устанавливается в 1 при пустом буфере передатчика — UDR после передачи;
- FE — флаг ошибки кадрирования, устанавливается в 1 при обнаружение неправильного кадра, когда стоп бит равен 0-лю
- DOR — флаг переполнения регистра приемника, устанавливается в 1, когда байт данных принят, а предыдущий еще не прочитан из UDR;
- PE — флаг ошибки контроля четности, устанавливается в 1 при обнаружение ошибки контроля четности (если включена проверка);
- U2X — бит установки удвоенной скорости обмена, если установлена 1, то скорость передачи удваивается (частота делится на 8, а не на 16), данный бит используется только при асинхронном режиме работы;
- MPCM — бит мультипроцессорного обмена, если установлена 1, то контроллер аппаратно не принимает информацию, а только кадры с адресами, далее устанавливается бит завершения приема (или прерывание) и программа обрабатывает адрес, её ли это адрес. Отличие информации от адреса определяется с помощью 9-ого бита в режиме 9-и битового обмена.
Регистр UCSRB состоит из следующих бит:
- RXCIE — бит разрешения прерывания по завершению приема, если установлена 1, то при установке флага RXC регистра UCSRA произойдет прерывание «прием завершен «;
- TXCIE — бит разрешения прерывания по завершению передачи, если установлена 1, то при установке флага TXC регистра UCSRA произойдет прерывание «передача завершена «;
- UDRIE — бит разрешения прерывания по опустошению регистра передатчика, если установлена 1, то при установке флага UDRE регистра UCSRA произойдет прерывание «регистр данных пуст «;
- RXEN — бит разрешения приема, при установки 1 разрешается работа приемника USART и переопределяется функционирование вывода RXD;
- TXEN — бит разрешения передачи, при установки 1 разрешается работа передатчика USART и переопределяется функционирование вывода TXD;
- UCSZ2 — бит формат посылок, данный бит совместно с битами UCSZ1 и UCSZ0 регистра UCSRC определяют количество бит данных в кадрах
- RXB8 — 9-ый разряд принимаемых данных при использование 9-и битого режима, считывать из данного бита нужно до считывание регистра UDR;
- TXB8 — 9-ый разряд передаваемых данных при использование 9-и битного режима, записывать в данный бит нужно до записи в регистр UDR.
Регистр UCSRC состоит из следующих бит:
- URSEL — тут надо пояснить, это немного странный бит, он отвечает за выбор регистра UCSRC или UBRR, при установке 1 мы работаем с регистром UCSRC, при 0 мы работаем с регистром UBRR;
- UMSEL — бит выбора режима асинхронный или синхронный, если установлен 1 — режим синхронный (т.е. с использованием линии синхронизации XCK), если 0 — режим асинхронный;
- UPM1, UPM0 — биты выбора режима проверки на четность / нечетность;
- USBS — бит отвечающий за количество стоп-битов, если установлена 1 — два стоп-бита, если 0 — один стоп-бит;
- UCSZ1, UCSZ0 — совместно с битом UCSZ2 регистра UCSRB определяют количество бит данных в кадрах (см. таблицу выше);
- UCPOL — бит полярность тактового сигнала, при синхронном режиме определяет по какому фронту принимать / передавать данные – по спадающему или по нарастающему.
Регистр UBRR отвечает за скорость обмена, он состоит из двух 8-и битных регистров — UBRRH и UBRRL
Повторюсь, бит URSEL отвечает за выбор регистра UCSRC или UBRR, при установке 1 мы работаем с регистром UCSRC, при 0 мы работаем с регистром UBRR. Биты с UBRR0 до UBR11 устанавливают скорость передачи в бодах, но не прямую, а через следующие формулы:
UBRR = (fCK /(BAUD * 16)) — 1
UBRR = (fCK /(BAUD * 8)) — 1
Где fCK — тактовая частота микроконтроллера в герцах;
BAUD — требуемая скорость в бодах;
16 и 8 коэффициент делителя частоты, зависит от бита U2X регистра UCSRA,
- при 1 — коэффициент 8;
- при 0 — коэффициент 16;
UBRR — содержимое регистра UBRRH и UBRRL.
Мы будем использовать «классический UART » (т.е. асинхронный без линии синхронизации) с скоростью 9600 бод, 8-бит данных, 1-ин стоп-бит, без проверки на четность.
(в сокращенной записи данную конфигурацию записывают в виде «9600/8-N-1 «, где 9600 это скорость, 8-количество бит данных в кадре, N-без проверки четности, 1-количество стоп-битов, если интересно описание сокращенного формата записи легко можно нагуглить)
Итак, я опишу последовательность действий для передатчика UART и далее приведу ассемблерный и С код для ATmega8, который можно будет опробовать на проекте Proteus.
Последовательность действий для инициализации UART в режиме 9600/8-N-1 и передачи байта данных:
1. пусть ATmega8 тактируется от 8 МГц, первым делом, установим скорость 9600 бод, для этого рассчитаем значение регистра UBRR по формуле UBRR = (8 000 000 /(9 600 * 16)) – 1, результат равен 52,083, округлим его до 52 (или 0x34), запишем в UBRR значение 0x34;
2. разрешаем работу передатчика, записываем в регистр UCSRB значение 00001000 (установили бит TXEN);
3. устанавливаем формат кадра 8 бит данных, без проверки четности и асинхронный режим, записываем в UCSRC значение 10000110 или 0x86 (установили биты URSEL, UCSZ1, UCSZ0);
4. отправляем байт данных, записываем байт в регистр UDR и делаем задержку пока бит UDRE регистра UCSRA не будет равен нулю.
Код на ассемблере для ATmega8
.nolist;данная директива отключает генерацию кода в листинг, т.е. далее в файле *.lss не будет фиксироваться ассемблерный код .include "m8def.inc" ;подключение стандартного заголовочного файла для ATmega8 .list ;данная директива включает генерацию кода в листинг, т.е. далее в файле *.lss будет фиксироваться ассемблерный код .equ fCK = 8000000;частота в герцах .equ BAUD = 9600;скорость для UART в бодах .equ UBRR_value = (fCK/(BAUD*16))-1;расчитываем значение для регистра UBRR .cseg ;данная директива означает, что дальше идет код программы .org 0 ;данная директива означает, что код программы будет располагаться с 0ого адреса в FLASH ;ВЕКТОР ПРЕРЫВАНИЙ rjmp initial;прерывание от …, ссылаемся на обработчик прерывания - initial rjmp 0 ;rjmp service_INT0;внешнее прерывание 0 rjmp 0 ;rjmp service_INT1;внешнее прерывание 1 rjmp 0 ;rjmp service_OC2;совпадение TCNT2 и OCR2 rjmp 0 ;rjmp service_OVF2;переполнение TCNT2 rjmp 0 ;rjmp service_ICP1;захват в ICP1 rjmp 0 ;rjmp service_OC1A;совпадение TCNT1 и OCR1A rjmp 0 ;rjmp service_OC1B;совпадение TCNT1 и OCR1B rjmp 0 ;rjmp service_OVF1;переполнение TCNT1 rjmp 0 ;rjmp service_OVF0;переполнение TCNT0 rjmp 0 ;rjmp service_SPI;прерывание от модуля SPI rjmp 0 ;rjmp service_URXC;получение байта по USART rjmp 0 ;rjmp service_UDRE;опустошение UDR в USART rjmp 0 ;rjmp service_UTXC;передача байта по USART rjmp 0 ;rjmp service_ADCC;прерывание от АЦП rjmp 0 ;rjmp service_ERDY;завершение записи в EEPROM rjmp 0 ;rjmp service_ACI;прерывание от компаратора rjmp 0 ;rjmp service_TWI;прерывание от модуля TWI rjmp 0 ;rjmp service_SPMR;завершение выполнения spm ;УСТАНОВКА СТЕКА initial: ldi R16,low(RAMEND);скопируем в R16 младщий байт из константы RAMEND, которая определена в m8def.inc и хранит размер SRAM out SPL,R16;скопируем значение из R16 в SPL ldi R17,high(RAMEND);скопируем в R16 старший байт из константы RAMEND, которая определена в m8def.inc out SPH,R17;скопируем значение из R17 в SPH ;КОД ОСНОВНОЙ ПРОГРАММЫ main: rcall init_USART ldi R16,0b01010011 rcall USART_send ;шлем 0x53, это ASCII код знака 'S' ldi R16,0b00101101 rcall USART_send;шлем 0x2D, это ASCII код знака '-' ldi R16,0b01000101 rcall USART_send;шлем 0x45, это ASCII код знака 'E' loop: rjmp loop ;ПОДПРОГРАММА ИНИЦИАЛИЗАЦИИ USART МОДУЛЯ init_USART:ldi R16,high(UBRR_value);устанавливаем скорость 9600 бод out UBRRH,R16 ldi R16,low(UBRR_value) out UBRRL,R16 ldi R16,(1<<TXEN);разрешаем работу передатчика out UCSRB,R16 ldi R16,(1<< URSEL)|(1<< UCSZ0)|(1<< UCSZ1) out UCSRC,R16;устанавливаем режим 8 бит данных, без проверки четности, асинхронный режим ret USART_send:sbis UCSRA,UDRE;ждем пока бит UDRE регистра UCSRA не будет пуст rjmp USART_send out UDR,R16 ;посылаем байт по UART, кладем данные в регистр UDR ret
.nolist;данная директива отключает генерацию кода в листинг, т.е. далее в файле *.lss не будет фиксироваться ассемблерный код .include "m8def.inc";подключение стандартного заголовочного файла для ATmega8 .list;данная директива включает генерацию кода в листинг, т.е. далее в файле *.lss будет фиксироваться ассемблерный код .equ fCK = 8000000;частота в герцах .equ BAUD = 9600;скорость для UART в бодах .equ UBRR_value = (fCK/(BAUD*16))-1;расчитываем значение для регистра UBRR .cseg;данная директива означает, что дальше идет код программы .org 0;данная директива означает, что код программы будет располагаться с 0ого адреса в FLASH ;ВЕКТОР ПРЕРЫВАНИЙ rjmp initial;прерывание от …, ссылаемся на обработчик прерывания - initial rjmp 0;rjmp service_INT0;внешнее прерывание 0 rjmp 0;rjmp service_INT1;внешнее прерывание 1 rjmp 0;rjmp service_OC2;совпадение TCNT2 и OCR2 rjmp 0;rjmp service_OVF2;переполнение TCNT2 rjmp 0;rjmp service_ICP1;захват в ICP1 rjmp 0;rjmp service_OC1A;совпадение TCNT1 и OCR1A rjmp 0;rjmp service_OC1B;совпадение TCNT1 и OCR1B rjmp 0;rjmp service_OVF1;переполнение TCNT1 rjmp 0;rjmp service_OVF0;переполнение TCNT0 rjmp 0;rjmp service_SPI;прерывание от модуля SPI rjmp 0;rjmp service_URXC;получение байта по USART rjmp 0;rjmp service_UDRE;опустошение UDR в USART rjmp 0;rjmp service_UTXC;передача байта по USART rjmp 0;rjmp service_ADCC;прерывание от АЦП rjmp 0;rjmp service_ERDY;завершение записи в EEPROM rjmp 0;rjmp service_ACI;прерывание от компаратора rjmp 0;rjmp service_TWI;прерывание от модуля TWI rjmp 0;rjmp service_SPMR;завершение выполнения spm ;УСТАНОВКА СТЕКА initial: ldi R16,low(RAMEND);скопируем в R16 младщий байт из константы RAMEND, которая определена в m8def.inc и хранит размер SRAM out SPL,R16;скопируем значение из R16 в SPL ldi R17,high(RAMEND);скопируем в R16 старший байт из константы RAMEND, которая определена в m8def.inc out SPH,R17;скопируем значение из R17 в SPH ;КОД ОСНОВНОЙ ПРОГРАММЫ main: rcall init_USART ldi R16,0b01010011 rcall USART_send;шлем 0x53, это ASCII код знака 'S' ldi R16,0b00101101 rcall USART_send;шлем 0x2D, это ASCII код знака '-' ldi R16,0b01000101 rcall USART_send;шлем 0x45, это ASCII код знака 'E' loop: rjmp loop ;ПОДПРОГРАММА ИНИЦИАЛИЗАЦИИ USART МОДУЛЯ init_USART:ldi R16,high(UBRR_value);устанавливаем скорость 9600 бод out UBRRH,R16 ldi R16,low(UBRR_value) out UBRRL,R16 ldi R16,(1<<TXEN);разрешаем работу передатчика out UCSRB,R16 ldi R16,(1<< URSEL)|(1<< UCSZ0)|(1<< UCSZ1) out UCSRC,R16;устанавливаем режим 8 бит данных, без проверки четности, асинхронный режим ret USART_send:sbis UCSRA,UDRE;ждем пока бит UDRE регистра UCSRA не будет пуст rjmp USART_send out UDR,R16;посылаем байт по UART, кладем данные в регистр UDR ret |
Код на С для ATmega8
/* * GccApplication4.cpp * * Author: Admin */ #define F_CPU 8000000 // Рабочая частота контроллера #define BAUD 9600L // Скорость обмена данными #define UBRRL_value (F_CPU/(BAUD*16))-1 //Согластно заданной скорости подсчитываем значение для регистра UBRR #include <avr/io.h> #include <util/delay.h> void init_USART() { UBRRL = UBRRL_value; //Младшие 8 бит UBRRL_value UBRRH = UBRRL_value >> 8; //Старшие 8 бит UBRRL_value UCSRB |=(1<<TXEN); //Бит разрешения передачи UCSRC |=(1<< URSEL)|(1<< UCSZ0)|(1<< UCSZ1); //Устанавливем формат 8 бит данных } void send_UART(char value) { while(!(UCSRA & (1 << UDRE))); // Ожидаем когда очистится буфер передачи UDR = value; // Помещаем данные в буфер, начинаем передачу } int main(void) { init_USART(); //инициализация USART в режиме 9600/8-N-1 send_UART(0x53); //посылаем ASCII код знака 'S' send_UART(0x2D); //посылаем ASCII код знака '-' send_UART(0x45); //посылаем ASCII код знака 'E' while(1) { _delay_ms(1000); } }
/* * GccApplication4.cpp * * Author: Admin */ #define F_CPU 8000000 // Рабочая частота контроллера #define BAUD 9600L // Скорость обмена данными #define UBRRL_value (F_CPU/(BAUD*16))-1 //Согластно заданной скорости подсчитываем значение для регистра UBRR #include <avr/io.h> #include <util/delay.h> void init_USART() { UBRRL = UBRRL_value; //Младшие 8 бит UBRRL_value UBRRH = UBRRL_value >> 8; //Старшие 8 бит UBRRL_value UCSRB |=(1<<TXEN); //Бит разрешения передачи UCSRC |=(1<< URSEL)|(1<< UCSZ0)|(1<< UCSZ1); //Устанавливем формат 8 бит данных } void send_UART(char value) { while(!(UCSRA & (1 << UDRE))); // Ожидаем когда очистится буфер передачи UDR = value; // Помещаем данные в буфер, начинаем передачу } int main(void) { init_USART(); //инициализация USART в режиме 9600/8-N-1 send_UART(0x53); //посылаем ASCII код знака 'S' send_UART(0x2D); //посылаем ASCII код знака '-' send_UART(0x45); //посылаем ASCII код знака 'E' while(1) { _delay_ms(1000); } } |
Результат работы в Proteus:
Скачать hex файл прошивки и проект в Proteus — ссылка
Если к TxD микроконтроллера подключить логический анализатор в Proteus, то можно увидеть осциллограмму работы USART. Я продемонстрирую скриншот с логического анализатора, с фрагментом импульсов для знака ‘E’ (0x45).
P.S. Если вы захотите создать собственный проект в Proteus, то вам необходимо настроить контролер ATmega8 на 8 МГц (для работы UART), как это сделать см. скриншот ниже: