Работа с UART на ATmega несколько проще чем с таймерами, но на мой взгляд, в целом эта тема более мутная, хотя и более интересная. Зависит от того, с какой стороны посмотреть.
Так же как и с таймерами, для работы c USART имеется набор прерываний:
и регистров:
В книге "Практическое программирование МК Atmel AVR" Ю.Ревич очень не советует использовать прерывания для работы c USART, потому-что, к примеру, чтение из буфера USART подразумевает ожидание когда в этом буфере что-то появится. Если это делать из прерывания, то другие прерывания повиснут в ожидании пока медленый USART не получит свои данные. В то время как цикл ожидания в главном теле программы вполне нормальная вещь и не мешает работе периферии.
Примеры инициализации USART приведены в оффициальном руководстве к ATmega8:
Сначала в регистр UBRRL заносится скорость соединения которая расчитывается по формуле:
скорость=частота_кварца/(16*битрейт -1);
.т.е. для 16МГц кварца и битрейта 9600 будет: 16000000/(16*9600)-1 = 103.1(6)
т.е. 103 если округлить до целого. Скорость также можно посмотреть в таблицах официального руководства:
После уcтановки скорости, следут разрешить прием и передачу по USART установкой флагов TXEN и RXEN в регистре UCSRB:
Последнее, что нужно сделать, это указать режим работы 8N1 установкой флагов UCSZ0, UCSZ1, URSEL в регистре UCSRC:
Простейшая программа для работы с USART:
/* uart.c sends 'H' symbol every second via UART for AVR ATmega8 compile: avr-gcc -mmcu=atmega8 -Wall -Os -o uart.elf uart.c avr-objcopy -O ihex uart.elf uart.hex */ #define F_CPU 16000000UL #include <avr/io.h> #include <util/delay.h> int main(void) { UBRRL=103; UCSRB=(1<<TXEN)|(1<<RXEN); UCSRC=(1<<URSEL)|(3<<UCSZ0); for (;;) { _delay_ms(1000); UDR='H'; } return 0; }
Здесь каждую секунду, через USART, микроконтроллер посылается символ H.
Этот пример шлет уже строку:
/* send.c sends string every second via UART for AVR ATmega8 compile: avr-gcc -mmcu=atmega8 -Wall -Os -o send.elf send.c avr-objcopy -O ihex send.elf send.hex */ #define F_CPU 16000000UL #include <avr/io.h> #include <util/delay.h> #include <string.h> register unsigned char i asm("r28"); int writeSerial(char* str) { for(i=0;i<strlen(str); i++) //fixed см. коментарии (25.07.2020) { while(!(UCSRA&(1<<UDRE))){}; // wait ready of port UDR = str[i]; } return 0; } int main(void) { UBRRL=103; UCSRB=(1<<TXEN)|(1<<RXEN); UCSRC=(1<<URSEL)|(3<<UCSZ0); for (;;) { _delay_ms(1000); writeSerial("Hello World!\n"); } return 0; }
Последний пример сначала читает из USART строку с символом двоеточия на конце, и затем посылает ее обратно уже без двоеточия:
/* serial.c read string via UART with symbol ':' as End-Of-Line and write this string in UART backward compile: avr-gcc -mmcu=atmega8 -Wall -Os -o serial.elf serial.c avr-objcopy -O ihex serial.elf serial.hex */ #define F_CPU 16000000UL #include <avr/io.h> #include <util/delay.h> #include <avr/interrupt.h> /10/#include <string.h> const unsigned char MAX_STRING=32; register unsigned char i asm("r28"); int writeSerial(char* str) { for(i=0;(i<MAX_STRING && str[i] != ':'); i++) { while(!(UCSRA&(1<<UDRE))){}; UDR = str[i]; } return 0; } int readSerial(char* str) { i=0; do { while(!(UCSRA&(1<<RXC))) {}; str[i]=UDR; i++; } while (str[i-1] != ':' && i <MAX_STRING); return 0; } int main(void) { UBRRL=103; UCSRB=(1<<TXEN)|(1<<RXEN); UCSRC=(1<<URSEL)|(3<<UCSZ0); DDRB |= (1<<PB5); // pinMode(13,OUTPUT); в Wiring for (;;) { char data[MAX_STRING]; _delay_ms(1000); readSerial(data); PORTB ^= (1<<PB5); // blink _delay_ms(1000); writeSerial(data); PORTB ^= (1<<PB5); // blink for visual control uint8_t k; // clear array for(k=0; k < MAX_STRING; k++) data[k]=0; } return 0; }
Регистр данных UDR который используется в функциях отправки и приема строки является какбы "сдвоенным". На чтение и на запись, физически это разные регистры.
Пример работы последней программы: