Najważniejsze rzeczy, które trzeba ustawić poprawnie
- UBRR jest preskalerem generatora baud rate, a nie „prędkością portu” samą w sobie.
- Wartość zależy od F_CPU i wybranego baud rate, a wzór zmienia się dla trybu normalnego i U2X=1.
- W wielu przypadkach lepszy wynik daje zaokrąglanie do najbliższej liczby całkowitej, a nie zwykłe obcięcie po dzieleniu.
- Rejestr jest dzielony na część wysoką i niską, więc zapis trzeba wykonać w poprawnej kolejności.
- Jeśli terminal pokazuje „krzaki”, najpierw sprawdzam zegar, fusy, preskaler i ustawienia programu terminalowego.
Jak działa rejestr i co naprawdę ustawia
W AVR rejestr UBRR steruje generatorem baud rate, czyli układem, który wylicza tempo transmisji dla USART. To ważne rozróżnienie: ten zapis nie „ustawia portu szeregowego” w całości, tylko określa, jak szybko ma pracować jego zegar komunikacyjny. W nowszych układach spotkasz nazwy typu UBRR0H i UBRR0L, a na starszych po prostu UBRRH i UBRRL, ale zasada pozostaje ta sama.
W dokumentacji Microchip widać jeszcze jedną istotną rzecz: w wielu AVR jest to rejestr 12-bitowy, więc jego wartość rozdziela się na część wysoką i niską. Dodatkowo zapis dolnego bajtu zwykle powoduje natychmiastową aktualizację preskalera, dlatego przy zmianie konfiguracji w locie trzeba zachować porządek i nie robić tego podczas aktywnej transmisji. Gdy to rozumiemy, policzenie właściwej wartości dla konkretnego zegara staje się dużo prostsze.
Jak obliczyć wartość dla swojego zegara
Podstawowy wzór jest prosty, ale łatwo tu popełnić błąd, jeśli pomylisz tryb pracy albo użyjesz nieaktualnej wartości F_CPU. Dla trybu asynchronicznego normalnego liczę tak: UBRR = F_CPU / (16 * BAUD) - 1. Dla trybu U2X=1 wzór zmienia się na UBRR = F_CPU / (8 * BAUD) - 1.
Ja zawsze robię jeszcze jeden krok: sprawdzam, jaki będzie rzeczywisty baud po zaokrągleniu wyniku do liczby całkowitej. To często ważniejsze niż sam wynik z wzoru, bo przy niektórych kombinacjach zegara i prędkości dopiero to pokazuje, czy komunikacja będzie stabilna.
| F_CPU | Baud | Tryb | UBRR | Rzeczywista prędkość | Błąd |
|---|---|---|---|---|---|
| 16 MHz | 9600 | normal | 103 | 9 615,38 | +0,16% |
| 16 MHz | 57 600 | U2X=1 | 34 | 57 142,86 | -0,79% |
| 8 MHz | 115 200 | U2X=1 | 8 | 111 111,11 | -3,55% |
W tabelach Microchip widać to bardzo wyraźnie: popularne kombinacje częstotliwości i baud rate są dobrane tak, żeby błąd był możliwie mały, a najlepiej poniżej 0,5%. To dobry punkt odniesienia również w praktyce, bo im większy błąd, tym szybciej pojawiają się problemy przy dłuższych ramach i mniej odpornym torze komunikacji. Sama arytmetyka jednak nie wystarczy, bo wybór trybu U2X potrafi zmienić wynik na lepszy albo gorszy.
Kiedy U2X=1 pomaga, a kiedy nie warto go wymuszać
Tryb podwójnej prędkości nie jest magicznym przyciskiem „napraw UART”. Ja traktuję go jako narzędzie do poprawy dokładności wtedy, gdy normalny tryb daje zbyt duży błąd. Najczęściej przydaje się przy wyższych prędkościach, niższym zegarze albo wtedy, gdy zwykły podział przez 16 kończy się słabym dopasowaniem do żądanego baud rate.
- Włączam U2X=1, gdy zmniejsza błąd i daje wynik bliższy docelowemu baud.
- Zostaję przy trybie normalnym, gdy oba warianty są podobne albo gdy prostsza konfiguracja jest wystarczająca.
- Nie wybieram trybu „na oko”, tylko porównuję rzeczywisty błąd po obu stronach.
- Sprawdzam też zegar układu, bo przy wewnętrznym RC i źle ustawionych fusach sam U2X nie uratuje transmisji.
Jeżeli projekt dopiero powstaje, często lepiej dobrać taki kwarc lub rezonator, który naturalnie „lubi się” z popularnymi prędkościami transmisji, niż później walczyć z marginalnym błędem. To szczególnie ważne w urządzeniach, które mają działać bez ręcznego strojenia po stronie użytkownika. Po wyborze trybu trzeba to jeszcze wpisać do rejestrów tak, żeby sprzęt nie dostał przypadkowej wartości po drodze.
Jak ustawić rejestry w kodzie AVR
Najprostszy zapis ręczny wygląda tak: najpierw liczysz wartość, potem wpisujesz starszy bajt, a na końcu młodszy. To ważne, bo zapis do dolnego bajtu uruchamia aktualizację preskalera. W praktyce robię to tak:
uint16_t ubrr = 103;
UBRR0H = (uint8_t)(ubrr >> 8);
UBRR0L = (uint8_t)ubrr;
Jeśli nie chcę liczyć tego ręcznie, korzystam z AVR-LibC i pliku setbaud.h. To wygodny wariant, bo makra same wyliczają wartość, zaokrąglają wynik i sprawdzają tolerancję baud rate. Domyślnie biblioteka pracuje z tolerancją 2%, więc od razu odcina kombinacje, które są zbyt odległe od celu.
#define F_CPU 16000000UL
#define BAUD 9600
#include
void usart_init(void) {
UBRR0H = UBRRH_VALUE;
UBRR0L = UBRRL_VALUE;
#if USE_2X
UCSR0A |= _BV(U2X0);
#else
UCSR0A &= ~_BV(U2X0);
#endif
UCSR0B = _BV(TXEN0) | _BV(RXEN0);
UCSR0C = _BV(UCSZ01) | _BV(UCSZ00);
}
Właśnie taki układ kodu lubię najbardziej: najpierw deklaruję realny zegar, potem baud rate, a dopiero na końcu pozwalam bibliotece wyliczyć resztę. Jeśli mimo tego terminal dalej sypie znakami, przyczyna zwykle leży w konfiguracji całego toru, nie tylko w samym rejestrze.
Dlaczego terminal pokazuje krzaki
„Krzaki” prawie zawsze oznaczają brak zgodności między tym, co zakłada mikrokontroler, a tym, co widzi druga strona. Najczęstszy scenariusz jest banalny: wartość w programie zakłada 16 MHz, a układ realnie pracuje z inną częstotliwością albo z włączonym podziałem zegara. Drugi klasyk to rozjazd ustawień terminala, na przykład inny baud, inna liczba bitów stopu albo zła parzystość.
- Sprawdzam F_CPU w projekcie i porównuję je z realnym zegarem układu.
- Weryfikuję fusy i preskaler, bo CKDIV8 lub zły wybór źródła zegara potrafią zmienić wszystko.
- Porównuję ustawienia terminala z konfiguracją USART, zwłaszcza baud, 8N1 i parzystość.
- Oceniając błąd, nie ufam samemu wynikowi z wzoru, tylko sprawdzam rzeczywisty baud po zaokrągleniu.
- Zmniejszam prędkość testową, jeśli tor jest długi, adapter jest słaby albo zegar ma większą tolerancję.
Jeżeli problem znika po obniżeniu baud rate, to nie jest przypadek. Taka próba szybko mówi mi, czy problem wynika z błędu obliczeń, granicznych parametrów zegara, czy może z samego połączenia sprzętowego. W praktyce najlepiej mieć prostą, powtarzalną procedurę startu, zamiast liczyć na szczęście.
Najkrótsza droga do stabilnego UART-u w AVR
Gdy uruchamiam nowy projekt, zaczynam od trzech rzeczy: realnego zegara, sensownego baud rate i poprawnego zaokrąglenia wartości. Dopiero potem patrzę na sam kod transmisji. To oszczędza czas, bo w wielu przypadkach problem nie leży w funkcjach wysyłających znaki, tylko w tym, że jeden element toru pracuje na innych założeniach niż reszta.
Jeśli mam wpływ na hardware, wybieram częstotliwość zegara, która dobrze dzieli się przez popularne prędkości, a jeśli nie mam takiej swobody, korzystam z obliczeń i testuję wynik na realnym terminalu. Przy pierwszych uruchomieniach nie podkręcam od razu prędkości do maksimum, tylko zaczynam od ustawienia, które daje wyraźny margines błędu. Potem, gdy komunikacja jest stabilna, dopiero zwiększam tempo. W praktyce właśnie to podejście daje najmniej niespodzianek i najlepiej porządkuje pracę z UBRR.