Un microcontroler care lucrează singur este o excepție. Aproape orice sistem încorporat e format din mai multe circuite care trebuie să schimbe informație între ele, iar cele două capete ale unei legături sunt întotdeauna independente: ceasuri diferite, alimentări diferite, fără nicio memorie comună. Informația trebuie deci codificată ca o variație de tensiune pe un fir, transmisă în timp, apoi decodificată la celălalt capăt. Cursul de față răspunde la două întrebări: cum se codifică biții în timp, și cum știe receptorul unde începe și unde se termină fiecare bit, fără să existe vreun fir de ceas care să i-o spună.
1Obiectul și structura cursului6 min
Vom urmări logica de fond a comunicației seriale asincrone: de ce a înlocuit comunicația paralelă aproape peste tot, cum arată un cadru UART, cum reușește un receptor fără fir de ceas să găsească mijlocul fiecărui bit, ce limitează practic rata de transfer, și cum se traduce toată această teorie în registrele concrete ale modulului USART al ATmega328P.
- O rutină de întrerupere trebuie scurtă, comunică prin variabile volatile (Cursul 05)
- Formulele de prescalare - frecvență, divizor, valoare de comparare - revin identic, aplicate acum divizorului ratei de transfer (Cursul 06)
- Să explicați de ce comunicația serială a înlocuit-o pe cea paralelă și ce înseamnă sincron față de asincron
- Să descrieți structura unui cadru UART și rolul fiecărui bit
- Să explicați mecanismul de supraeșantionare de 16 ori și de ce ultimul bit al cadrului e cel mai vulnerabil la eroarea de ceas
- Să calculați valoarea registrului UBRR și eroarea rezultată pentru o rată de transfer dată
- Să scrieți inițializarea, transmisia și recepția UART, atât prin interogare cât și prin întrerupere cu tampon circular
- Să distingeți TTL, RS-232 și RS-485 după nivelurile electrice și domeniul de aplicare
2Sisteme și tipuri de comunicație12 min
Toate schimburile de date presupun un mecanism prin care o informație dintr-un registru al unui circuit ajunge, nealterată, într-un registru al altui circuit - un sistem de comunicație. Nevoia apare din interfațarea cu periferice externe (senzori, afișaje), din comunicația între mai multe microcontrolere, din legătura cu un calculator, și din integrarea în rețele IoT.
Serial față de paralel
În comunicația paralelă, opt biți circulă simultan pe opt fire; în cea serială, unul după altul, pe un singur fir. Intuiția spune că paralelul e de opt ori mai rapid - realitatea a mers exact invers.
Sincron față de asincron
Sincron: emițătorul trimite, pe un fir separat, un semnal de ceas care marchează fiecare bit - sincronizare perfectă prin construcție (SPI, I2C). Asincron: nu există fir de ceas - receptorul deduce singur momentele de eșantionare, cu propriul oscilator, pornind de la un reper transmis chiar în flux (tranziția care marchează începutul cadrului). Costul: cele două capete trebuie să fi convenit dinainte asupra vitezei, iar oscilatoarele trebuie suficient de apropiate ca frecvență. Câștigul: un fir mai puțin, și independență completă între cele două dispozitive.
Simplex, semiduplex, duplex integral
Simplex: un singur sens, permanent. Semiduplex: ambele sensuri, dar nu simultan - cele două capete își împart același mediu, pe rând. Duplex integral: simultan în ambele sensuri, prin fire sau canale distincte. UART-ul, cu RxD și TxD separate, e duplex integral.
Punct la punct față de magistrală
Punct la punct: exact două capete, fără adresare - tot ce se transmite e pentru celălalt. O magistrală (multipunct) leagă mai multe dispozitive la aceleași fire și cere adresare plus arbitrare. UART-ul e, în forma de bază, punct la punct; RS-485 (secțiunea finală) îl extinde la o magistrală de până la 32 de dispozitive.
3Modulul UART și formatul cadrului13 min
UART-ul (Universal Asynchronous Receiver Transmitter) traduce bidirecțional între lumea paralelă din interiorul microcontrolerului și lumea serială de pe fir. Linia TxD e ieșirea, RxD e intrarea; la conectarea a două dispozitive, cele două linii se încrucișează (TxD la RxD), plus o legătură de masă comună obligatorie. Pe ATmega328P: PD0 (RxD), PD1 (TxD).
Nu se transmite nimic și nu se arde nimic - eroarea e greu de depistat tocmai pentru că nu produce niciun simptom vizibil imediat, doar tăcere pe ambele canale.
Patru parametri trebuie identici la ambele capete: numărul de biți de date (de regulă 8, LSB primul), bitul de paritate (absent, par sau impar), rata de transfer (9600, 19200, 38400, 57600, 115200 fiind valorile uzuale) și numărul de biți de stop (1 sau 2). Notația 8N1 (opt biți de date, fără paritate, un bit de stop) e configurația implicită a aproape tuturor terminalelor.
Formatul cadrului
Codificarea e NRZ (Non Return to Zero): fiecare bit e un nivel constant menținut pe toată durata lui, fără revenire la repaus între biți - consecință: un șir lung de biți identici nu produce nicio tranziție, de aceea receptorul nu poate recupera ceasul din semnal și rata trebuie convenită dinainte. În repaus, linia stă în 1 logic.
Biții de date (5-9, LSB primul).
Bit de paritate (opțional).
Bit(i) de stop (întotdeauna 1): garantează revenirea liniei în repaus înainte de următorul cadru, ca frontul de start următor să fie detectabil.
Bitul de paritate detectează un număr impar de erori de bit, dar nu le corectează și nu detectează un număr par de erori - pe legături scurte e de obicei dezactivat; pe cabluri lungi, o sumă de control pe mesajul întreg e oricum de preferat.
4Sincronizarea între emițător și receptor12 min
Marginile unui bit sunt zona cea mai nesigură - acolo se produce tranziția, cu timp de creștere, posibile oscilații și jitter. La mijlocul bitului, nivelul e stabil și cât mai departe de ambele tranziții vecine. Receptorul eșantionează deci la mijlocul bitului, nu la margine.
Supraeșantionarea de 16 ori
Receptorul lucrează cu un ceas de 16 ori mai rapid decât rata de transfer, eșantionând linia RxD de 16 ori pe durata fiecărui bit. La un front descrescător din repaus, presupune început de bit de start și pornește un numărător; eșantioanele 8, 9, 10 cad, prin construcție, în jurul mijlocului bitului - receptorul le citește pe toate trei și decide prin vot majoritar. Dacă majoritatea confirmă 0, bitul de start e validat; altfel, frontul a fost un impuls parazit, și receptorul revine în așteptare. Aceeași procedură se repetă, fără nicio tranziție necesară, pentru fiecare bit următor.
16×115200 = 1,8432 MHz -
oscilatorul trebuie să poată furniza această frecvență prin divizare întreagă, ceea ce e sursa
principalei limitări practice (secțiunea următoare).De ce eroarea de ceas se acumulează
Receptorul se resincronizează o singură dată pe cadru, pe frontul de start. Dacă ceasul lui are o
eroare relativă e față de emițător, intervalul considerat un bit e T_b(1+e) -
eroarea nu se anulează, se adună: după n biți, deplasarea e n·e·T_b.
Într-un cadru 8N1, mijlocul bitului de stop e la 9,5 durate de bit după frontul de start. Ca
eșantionul să cadă încă în bit, deplasarea acumulată trebuie sub jumătate de bit:
9,5·|e| < 0,5 ⇒ |e| < 5,3% - limita geometrică ideală. În practică, incertitudinea
de detecție a frontului de start (1/16 dintr-un bit) și faptul că toate trei eșantioanele 8, 9, 10
trebuie să cadă în bit restrâng marja reală la aproximativ ±4,6% - împărțită între cele două
capete, regula practică e ca eroarea fiecărui capăt să nu depășească ±2,5%. Simptomul unei erori de
ceas prea mari, la un terminal serial: un flux de caractere aparent aleatoare în locul textului
așteptat.
5Calculul ratei de transfer13 min
Rata de transfer se obține prin divizarea întreagă a frecvenței oscilatorului, printr-un numărător programabil - registrul UBRR (USART Baud Rate Register).
UBRR = f_osc/(16·B) - 1, unde B e rata dorită.Rata efectivă:
B_ef = f_osc / (16·(UBRR+1)).Eroarea relativă:
ε = (B_ef/B - 1)×100%.Rezultatul primei formule rareori e întreg - rotunjirea introduce o eroare. Cu cât divizorul e mai mic (rată cerută mare față de frecvența oscilatorului), cu atât o unitate de rotunjire înseamnă o fracțiune mai mare din divizor - de aceea ratele mari sunt mult mai greu de generat exact.
Vezi rezolvarea
9600 bauzi: UBRR = 16×10⁶/(16×9600) - 1 = 104,17-1 = 103,17 → 103.
B_ef = 16×10⁶/(16×104) = 9615,4. ε = +0,16% - complet neglijabilă.
115200 bauzi: UBRR = 16×10⁶/(16×115200) - 1 = 8,68-1 = 7,68 → 8.
B_ef = 16×10⁶/(16×9) = 111 111. ε = -3,55% - peste limita practică de
±2,5%; dacă și celălalt capăt are eroare de același semn, recepția devine nesigură.
Modul de viteză dublă
Bitul U2X0 din UCSR0A coboară factorul de supraeșantionare la 8: UBRR = f_osc/(8·B) -
1. Divizorul se dublează, rotunjirea are un efect relativ mai mic. La 115200 bauzi, 16 MHz:
UBRR = 16,36 → 16, B_ef = 16×10⁶/(8×17) = 117 647, ε =
+2,1% - mult mai bun decât -3,55%, exact ce face biblioteca Arduino la 115200. Prețul: cu opt
eșantioane pe bit în loc de șaisprezece, votul se face pe eșantioanele 4, 5, 6, fereastra utilă se
îngustează, iar imunitatea la zgomot scade aproximativ la jumătate.
14 745 600 = 115200×128 = 9600×1536 - multiplu întreg al tuturor ratelor standard. La
115200 bauzi: UBRR = 14 745 600/(16×115200) - 1 = 8-1 = 7, exact întreg, eroare zero.
Arduino preferă 16 MHz, mai comod pentru temporizări zecimale, cu prețul erorii discutate mai sus - un
exemplu clar de compromis de proiectare: nu există o frecvență simultan convenabilă pentru temporizări
și pentru rate standard exacte.Un program corect la 16 MHz, mutat fără modificări pe o placă la 8 MHz, transmite la jumătate din rata dorită - la 8 MHz, 9600 bauzi cere UBRR=51, nu 103. În cod portabil, valoarea nu se scrie niciodată ca o constantă numerică, ci se calculează la compilare, din macroul F_CPU.
6Registrele modulului USART al ATmega328P8 min
Modulul USART0 are trei categorii de registre: un registru de date, registre de control și stare, și registrul ratei de transfer.
O scriere în UDR0 încarcă tamponul de emisie; o citire returnează tamponul de recepție - nu se poate citi înapoi ce s-a scris. La recepție, tamponul are două niveluri, formând un mic FIFO.
| Registru | Bit | Rol |
|---|---|---|
| UCSR0A | RXC0 | recepție completă (se șterge la citirea UDR0) |
| UCSR0A | UDRE0 | tamponul de emisie e gol, poate primi un octet nou |
| UCSR0A | FE0 / DOR0 / UPE0 | eroare de cadru / depășire / paritate |
| UCSR0B | RXCIE0 / RXEN0 / TXEN0 | întrerupere la recepție / activare receptor / emițător |
| UCSR0C | UPM01:00 / USBS0 / UCSZ01:00 | paritate / biți de stop / dimensiune cadru |
Trei indicatori de eroare, toți referitori la octetul din vârful tamponului: FE0 (frame error) - în poziția bitului de stop s-a citit 0, semn tipic al unei rate de transfer nepotrivite între capete; DOR0 (data overrun) - un cadru nou a sosit complet cât timp tamponul era plin, date pierdute prin eroare de arhitectură software, nu electrică; UPE0 (parity error) - bitul de paritate nu corespunde, semn de zgomot electric sau configurare diferită.
Citirea registrului de date avansează tamponul - dacă indicatorii de eroare sunt citiți după, se referă deja la următorul octet, nu la cel tocmai extras.
7Aplicație: transmisia și recepția unui caracter11 min
Inițializarea cere trei operații: divizorul în UBRR0, activarea emițătorului/receptorului în UCSR0B, formatul cadrului în UCSR0C. UBRR0 e de 12 biți, împărțit în UBRR0H și UBRR0L - partea superioară se scrie prima.
#define F_CPU 16000000UL
#define BAUD 9600UL
#define UBRR_VAL ((F_CPU + 8UL*BAUD) / (16UL*BAUD) - 1UL) /* rotunjire corecta */
void usart_init(void) {
UBRR0H = (uint8_t)(UBRR_VAL >> 8);
UBRR0L = (uint8_t)(UBRR_VAL & 0xFF);
UCSR0B = (1<<RXEN0) | (1<<TXEN0);
UCSR0C = (1<<UCSZ01) | (1<<UCSZ00); /* 8N1 */
}
Transmisia și recepția prin interogare
void usart_transmite(uint8_t data) {
while (!(UCSR0A & (1<<UDRE0))) { ; } /* asteapta golirea tamponului de emisie */
UDR0 = data;
}
uint8_t usart_date_disponibile(void) {
return (UCSR0A & (1<<RXC0)) != 0;
}
La emisie, așteptarea e limitată la durata unui cadru (~1 ms la 9600 bauzi). La recepție, dacă
celălalt capăt nu transmite nimic, o funcție blocantă oprește întregul program pe termen
nedefinit. Varianta corectă: testați doar disponibilitatea (usart_date_disponibile())
și citiți numai dacă există date, cu registrul de stare citit înaintea lui UDR0 pentru ca
indicatorii de eroare să se refere la octetul care urmează a fi extras.
Echivalentul cu clasa Serial din Arduino
void setup() { Serial.begin(9600); }
void loop() {
Serial.write('G');
if (Serial.available() > 0) { char c = Serial.read(); Serial.write(c); }
delay(500);
}
Serial.available() nu citește RXC0, ci numărul de octeți dintr-un tampon circular
de 64 de octeți întreținut de rutina de întrerupere de recepție. Serial.write() depune
octetul într-un tampon software și revine imediat - transmisia se face în fundal, prin întreruperi.
Aceasta e exact arhitectura discutată în secțiunea următoare.
8Recepția cu întrerupere și tamponul circular11 min
Interogarea e simplă, dar are un defect structural: programul trebuie să treacă prin punctul de verificare mai des decât sosesc caracterele. La 115200 bauzi, un cadru durează ~87 µs, adică ~1400 cicluri la 16 MHz între două caractere - orice funcție mai lentă (o conversie ADC cu așteptare, un delay()) face ca al doilea caracter sosit între timp să suprascrie tamponul hardware, ridicând DOR0, iar datele se pierd fără ca programul să observe, dacă nu testează explicit indicatorul.
cap (unde se scrie) și coadă (de unde se
citește), fiecare avansând ciclic. Gol când cei doi indici sunt egali, plin când avansarea capului
ar egala coada. Fără deplasare de date, fără alocare dinamică, timp constant - esențial într-o
rutină de întrerupere.#define BUF_SIZE 64 /* obligatoriu o putere a lui doi */
#define BUF_MASK (BUF_SIZE - 1)
static volatile uint8_t buf[BUF_SIZE];
static volatile uint8_t cap = 0, coada = 0;
ISR(USART_RX_vect) {
uint8_t stare = UCSR0A; /* stare INAINTE de citirea datelor */
uint8_t data = UDR0;
if (stare & ((1<<FE0)|(1<<DOR0)|(1<<UPE0))) return; /* cadru eronat, se arunca */
uint8_t cap_urmator = (cap + 1) & BUF_MASK;
if (cap_urmator != coada) { buf[cap] = data; cap = cap_urmator; }
/* altfel tamponul e plin, octetul se pierde controlat */
}
uint8_t buffer_citeste(void) {
uint8_t data = buf[coada];
coada = (coada + 1) & BUF_MASK;
return data;
}
Activarea cere doar UCSR0B |= (1<<RXCIE0) plus sei().
cap, programul principal scrie doar coadă - face scrierile atomice pe
variabile de un octet, fără nevoie de secțiune critică (dacă tamponul ar depăși 256 de poziții,
indicii ar deveni de 16 biți și accesul din bucla principală ar trebui protejat). La umplere, codul
verifică loc înainte de a scrie și aruncă octetul controlat - alternativa, suprascrierea celui mai
vechi octet, ar strica ordinea mesajelor.Nu interpretează mesajul, nu afișează nimic, nu apelează funcții de temporizare, nu transmite. Orice prelucrare introdusă în rutină reduce marja disponibilă și readuce, pe altă cale, exact problema pe care întreruperea trebuia să o rezolve.
9De la UART la USART5 min
USART (Universal Synchronous and Asynchronous Receiver Transmitter) adaugă, peste modul asincron descris până acum, un mod sincron, cu o linie suplimentară de ceas (XCK, pe PD4 la ATmega328P). Unul din parteneri generează ceasul (master), celălalt îl primește (slave); biții sunt validați de fronturile lui, iar receptorul nu mai are nevoie nici de supraeșantionare, nici de vot majoritar, nici de un ceas propriu precis.
10Standardele RS-232 și RS-48510 min
UART-ul spune totul despre logica transmisiei, dar nimic despre electrica ei - ce tensiune reprezintă un 1 logic, cât de lung poate fi cablul. Acestea fac obiectul standardelor RS-232 și RS-485.
Nivelurile TTL nu sunt RS-232
Pinii ATmega328P lucrează cu niveluri TTL/CMOS: ~0 V pentru 0, tensiunea de alimentare pentru 1. RS-232 folosește tensiuni negative pentru 1 logic (mark, -3 până la -15 V) și pozitive pentru 0 (space, +3 până la +15 V) - o zonă moartă între -3 și +3 V dă imunitate la zgomot.
Două motive independente, fiecare suficient: semnalul ar fi inversat (polaritatea RS-232 e opusă convenției TTL), iar tensiunea de -12 V aplicată unui pin de microcontroler îl distruge aproape sigur. Un convertor de nivel (MAX232) face traducerea în ambele sensuri, generând singur tensiunile negative dintr-o singură alimentare de 5 V, printr-o pompă de sarcină.
RS-485 și semnalul diferențial
RS-232 rezolvă amplitudinea, dar rămâne asimetric (fața de masă) - pe distanțe mari, diferența de
potențial dintre mase și perturbațiile electromagnetice induse se adaugă direct semnalului.
RS-485 elimină ambele probleme: informația e purtată de diferența de tensiune dintre două fire
torsadate, A și B, comandate în antifază. Receptorul citește doar U_A - U_B.
| RS-232 | RS-485 | |
|---|---|---|
| Topologie | punct la punct | multipunct |
| Tip de semnal | asimetric | diferențial, pereche torsadată |
| Distanță maximă | ~15 m | ~1200 m |
| Dispozitive | 2 | până la 32 |
| Mod de lucru | duplex integral | semiduplex (2 fire) |
Cablul e o linie de transmisie cu impedanță caracteristică (~120 Ω); un front care ajunge la un capăt neadaptat se reflectă și se suprapune peste semnal, producând false tranziții. Remediul: câte un rezistor de 120 Ω la fiecare din cele două capete fizice ale magistralei - nu la dispozitivele intermediare. O magistrală neterminată poate funcționa aparent corect la viteză mică și se defectează brusc la creșterea vitezei, când durata frontului devine comparabilă cu timpul de propagare dus-întors.
Important: ambele standarde descriu doar stratul fizic - formatul cadrului rămâne cel al UART-ului (start, date, paritate, stop). Un microcontroler care transmite prin RS-485 folosește exact același modul USART; se schimbă doar circuitul de interfață și un pin suplimentar care comandă activarea emițătorului.
11Erori frecvente4 min
- „Am legat TxD la TxD - dacă nu merge, măcar n-am ars nimic." Corect că nu se arde nimic, dar eroarea trece adesea neobservată multă vreme, tocmai pentru că lipsa oricărui simptom evident nu îndreaptă atenția spre cablaj. TxD se leagă întotdeauna la RxD al celuilalt capăt, niciodată la propriul lui omolog.
- „Am citit UDR0 și abia apoi am verificat bitul de eroare - dar nu găsesc niciodată o eroare de cadru." Fals pozitiv structural - citirea UDR0 avansează tamponul, deci indicatorii citiți după se referă deja la următorul octet, nu la cel tocmai extras. UCSR0A se citește întotdeauna înaintea lui UDR0.
- „Interogarea (polling) e suficientă pentru orice aplicație cu UART." Fals la rate mari sau bucle principale lente - orice funcție care durează mai mult decât intervalul dintre două cadre (87 µs la 115200 bauzi) face ca datele să se piardă prin depășirea tamponului hardware, fără niciun avertisment dacă indicatorul DOR0 nu e testat explicit. Pentru fluxuri rapide, folosiți o rutină de întrerupere scurtă plus un tampon circular, separând sosirea de prelucrare.
12Rezumat și glosar5 min
Comunicația serială asincronă rezolvă transferul de informație între circuite independente cu minimum de resurse hardware, renunțând la firul de ceas în schimbul unei convenții prealabile asupra vitezei și al unei resincronizări la fiecare cadru, pe bitul de start. Receptorul supraeșantionează de 16 ori pe bit și decide prin vot majoritar în jurul mijlocului fiecărui bit, tolerând o eroare de ceas de câteva procente, cu condiția ca ea să nu se acumuleze peste jumătate de bit până la ultimul bit al cadrului - cel mai vulnerabil. Divizorul întreg impus de registrul UBRR face ca unele combinații de frecvență și rată să fie exacte, altele nu; de aici cristalele de 14,7456 MHz și modul de viteză dublă. La nivel de programare, modulul USART se reduce la un registru de date, trei registre de control și stare, și un registru de divizor - recepția prin interogare e suficientă pentru programe simple, dar arhitectura corectă pentru fluxuri rapide combină o întrerupere scurtă cu un tampon circular. Protocolul și nivelul electric sunt lucruri distincte: aceleași cadre circulă la niveluri TTL pe câțiva centimetri, prin RS-232 pe cincisprezece metri, sau prin RS-485 diferențial pe peste un kilometru.
13Întrebări de verificare7 min
- Explicați de ce comunicația serială a înlocuit-o pe cea paralelă în aproape toate aplicațiile moderne. Ce e decalajul de propagare, și de ce efectul lui se agravează la frecvențe mari?
- Ce trebuie să cunoască dinainte receptorul într-o legătură asincronă, și de ce nu poate fi dedus din semnalul primit, pornind de la codificarea NRZ?
- Descrieți mecanismul de supraeșantionare de 16 ori. De ce se folosesc eșantioanele 8, 9, 10 și de ce prin vot majoritar?
- Un cadru e 8E2. La câte durate de bit după frontul de start se află mijlocul ultimului bit de stop? Deduceți eroarea relativă maximă de ceas tolerată, geometric.
- Un ATmega328P la 16 MHz trebuie să comunice la 57600 bauzi. Calculați UBRR0, rata efectivă și eroarea, atât normal cât și cu U2X0 activat.
- Distingeți indicatorii FE0, DOR0 și UPE0. Pentru fiecare, indicați cauza probabilă.
- Argumentați de ce dimensiunea unui tampon circular se alege ca putere a lui doi, și de ce indicii trebuie declarați volatile.
14Direcții de aprofundare2 min
Cursul următor rămâne în familia comunicației seriale, dar trece la protocoalele sincrone - SPI și I2C - unde firul de ceas readus în schemă elimină problema ratei de transfer discutată aici, în schimbul unui fir suplimentar și al unei topologii diferite.
Configurarea UART-ului, calculul UBRR și tamponul circular din acest curs devin montaj real în Laboratorul 06.