Микроконтроллеры AVR, как и большинство современных контроллеров, имеют на своем борту аппаратный модуль UART, являющийся отличным решением для передачи данных. Именно про UART будет эта статья - разберем, как настроить этот модуль в AVR'ках, посмотрим куда там надо помещать свои данные и откуда забирать, ну и небольшой примерчик по традиции в конце статьи.
Сам протокол UART мы расписывать не будем, поэтому сразу начнем с описания регистров. Кстати, я для своих опытов буду использовать ATMega2560, но вообще принципиальной разницы нет, так что для ATMega16 или любой другой меги практически все работает точно также.
Итак, для того, чтобы произвести необходимую настройку UART'а выделено три регистра - UCSRxA, UCSRxB и UCSRxC. Вместо символа x надо использовать номер используемого модуля UART. Например, хотим мы передать данные через UART1 - тогда нас интересуют регистры UCSR1A, UCSR1B и UCSR1C. Аналогично для UART0, UART2... У моей ATMega2560, кстати, их целых четыре штуки ) Ну а теперь поподробнее обо всем этом.
Регистр UCSRxA.
В этом регистре содержатся три важнейших флага - RXCx, TXCx, UDREx. Первый из этих флагов оповещает нас об окончании приема данных, второй об окончании передачи, ну а третий флаг взлетит в том случае, если регистр данных пуст. Кроме того, в этом регистре есть еще флаги, сигнализирующие об ошибках, которые могут случиться в процессе передачи данных.
Ну и еще один бит регистра UCSRxA - бит U2Xx. Это бит удвоения скорости передачи при работе в асинхронном режиме. Давайте двигаться дальше.
Регистр UCSRxB.
В первую очередь нас тут должны интересовать биты RXENx и TXENx. Это биты разрешения приема и передачи. То есть, чтобы иметь возможность передать какие-нибудь данные мы должны выставить бит TXENx в единицу.
А еще в регистре UCSRxB содержатся биты разрешения прерываний. Прерываний у нас три штуки (по окончанию приема, по окончанию передачи и по опустошению регистра данных), а значит и флагов тоже три - RXCIEx, TXCIEx, UDRIEx. С этим все понятно.
Регистр UCSRxC.
Там всевозможные настройки - количество бит данных, количество стоп-битов итд. В пдфке на любую ATMeg'у можно легко найти значения, которые надо записывать в разные биты этого регистра.
Кстати, с этим регистром есть небольшая тонкость. В некоторых микроконтроллерах AVR (например в той же 16-ой меге) регистры UCSRxC и UBRRxH находятся по одному и тому же адресу. И выбор между ними осуществляется следующим образом. Если мы записываем в этот регистр число, у которого старший бит равен 0, то оно попадает прямиком в регистр UBRRxH, а если старший бит - единица, то ему прямая дорога в регистр UCSRxC. Эта особенность есть не у всех AVR'ок, в уже упомянутой ATMega2560, например, такого нет.
Вроде бы все настроили, кроме самого главного - скорости передачи данных. Для этого выделено два регистра, один из которых уже упоминался - UBRRxH и UBRRxL. В даташите можно подглядеть формулу, по которой для каждого значения скорости можно определить числа, которые надо записать в эти регистры. А еще в даташите есть большая табличка, в которой уже рассчитаны все эти значения для разных скоростей и частот.
Ну и напоследок, самый важный регистр - UDRx. Именно сюда надо загружать данные для отправки и именно отсюда нужно забирать принятые данные.
Теперь давайте, пока не забыли, какие регистры для чего нужны, напишем простенькую программу-пример. Настолько простенькую, что проще просто некуда ) Давайте организуем передачу 8 байт во внешний мир:
/***************************************************************************************/ #include<avr/io.h> /***************************************************************************************/ // Тестовый буфер данных для хранения передаваемой информации unsigned char testBuffer[8]; /***************************************************************************************/ void main(void) { unsigned char i = 0 // Вот такие вот данные мы будем передавать for (uint8_t i = 0; i < 8; i++) { testBuffer[i] = i; } // Настраиваем нужную скорость, частота у меня 16 МГц, скорость - 19200 кб/с, // из таблички в даташите берем нужное значение UBRR0 = 51; // Разрешаем передачу UCSR0B |= (1 << TXEN0); while(1) { for (i = 0; i < 8; i++) { // Запихиваем данные в регистр данных UDR0 = testBuffer[i]; // И ждем пока передача завершится while(!(UCSR0A && (1 << TXC0))); } } } /***************************************************************************************/
Вот такой вот примерчик, в следующей статье обязательно реализуем и прием, и передачу данных с использованием прерываний, так что будьте на связи!