UART-ul rezolvă bine legătura cu un singur partener, dar un sistem real are un senzor de temperatură, un accelerometru, un ceas de timp real, o memorie externă și, poate, un afișaj - toate deodată. Interfața asincronă nu poate duce această încărcătură: nu are ceas comun, deci viteza e plafonată de precizia oscilatoarelor, și e strict punct la punct, deci fiecare periferic nou cere fire noi. Cursul de față prezintă cele două magistrale sincrone care rezolvă problema, cu compromisuri opuse: I²C, minimalistă în fire dar elaborată ca protocol, și SPI, generoasă în fire dar aproape fără protocol.
1Obiectul și structura cursului6 min
Ambele magistrale sunt sincrone - transportă explicit un semnal de ceas alături de date, ceea ce elimină dintr-o lovitură problema abaterii oscilatoarelor discutată în Cursul 08. Vom urmări mecanismul electric care face posibilă partajarea firelor pe I²C, tranzacțiile complete de scriere și citire, apoi principiul registrului de deplasare circular al SPI, modurile de ceas și topologiile posibile, și în final registrele concrete ale ATmega328P pentru ambele.
- Comunicația asincronă cere resincronizare la fiecare cadru, iar eroarea de ceas se acumulează - motivul pentru care viteza UART e plafonată (Cursul 08)
- UART e strict punct la punct - fără mecanism de adresare (Cursul 08)
- Să explicați de ce transportul unui semnal de ceas elimină problema abaterii oscilatoarelor
- Să descrieți mecanismul colector deschis + pull-up și rolul lui în partajarea magistralei I²C
- Să urmăriți pas cu pas o tranzacție I²C de scriere și una de citire, inclusiv rolul ACK/NACK
- Să explicați de ce pe SPI nu se poate citi fără a scrie, pornind de la registrul de deplasare circular
- Să identificați cele patru moduri SPI (CPOL, CPHA) și să recunoașteți simptomul unei nepotriviri
- Să alegeți motivat între UART, SPI și I²C pentru o aplicație dată
2De ce e nevoie de o magistrală sincronă10 min
Trei limitări ale UART-ului devin vizibile de îndată ce aplicația crește. Prima: absența unui ceas comun - receptorul se agață de frontul de start și eșantionează pe încredere; dacă abaterea relativă dintre cele două oscilatoare depășește ~2,5%, ultimul bit al cadrului e eșantionat greșit. A doua, care decurge din prima: viteza e mărginită de precizia oscilatoarelor, nu de electronica liniei - o legătură UART pe rezonator ceramic devine nesigură cu mult înainte de un megabit pe secundă, insuficient pentru un afișaj grafic sau un card SD.
A treia: numărul de dispozitive. UART e strict punct la punct, fără adresare - trei dispozitive pe aceleași două fire nu au cum să știe cui îi e destinat un octet. Un ATmega328P are un singur modul USART; dacă fiecare periferic ar cere o legătură dedicată, capacitatea de extindere s-ar epuiza după câteva module.
3I²C: prezentare generală, colector deschis, adresare13 min
I²C (Inter-Integrated Circuit, Philips, anii 1980) folosește două linii: SDA (Serial Data) și SCL (Serial Clock), plus masa comună. Numărul de fire rămâne același indiferent dacă sunt legate două dispozitive sau douăsprezece - marele avantaj al magistralei.
Protocolul e maestru-sclav: maestrul (de regulă microcontrolerul) generează ceasul, inițiază fiecare transfer și decide când se încheie. Sclavii (senzori, memorii, ceasuri de timp real) nu vorbesc niciodată nesolicitați - răspund doar la cerere.
Colectorul deschis și rezistoarele de ridicare
Dacă mai multe dispozitive sunt legate la același fir, cine impune un nivel când altul impune opusul? Soluția: etajul de ieșire al fiecărui dispozitiv e de tip colector deschis - un singur tranzistor, între linie și masă. Când conduce, trage linia la zero; când e blocat, o eliberează. Niciun dispozitiv nu poate forța linia în unu logic. Fiecare linie e legată la alimentare printr-un rezistor de ridicare (pull-up), care readuce linia în unu logic atunci când nimeni nu o trage în jos - rezultatul e o funcție ȘI cablată: linia e în unu numai dacă toate dispozitivele o eliberează.
Multe module au deja rezistoarele montate pe placa lor - patru sau cinci astfel de module pe aceeași magistrală pun rezistoarele în paralel, coborând rezistența echivalentă sub un kiloohm, crescând curentul și putând destabiliza magistrala. Remediu: îndepărtați rezistoarele de pe toate modulele în afară de unul.
Adresarea pe șapte biți
Fiecare sclav are o adresă pe 7 biți (128 combinații, din care 112 utilizabile, restul rezervate). Tot transferul începe cu emiterea acestei adrese - toți sclavii ascultă, compară cu adresa proprie, iar numai cel care se recunoaște continuă dialogul. Adresa e stabilită de producător, nu de proiectant; unele circuite au pini de configurare a ultimilor biți, pentru mai multe exemplare pe aceeași magistrală.
Cei 7 biți de adresă sunt urmați de un al optulea bit de sens (0=scriere, 1=citire). Aceeași adresă apare uneori ca 0x68 (convenția pe 7 biți, folosită de bibliotecile Arduino, care fac deplasarea) și alteori ca 0xD0 (octetul complet emis pe fir) - o confuzie frecventă care face dispozitivul să nu răspundă deloc.
Viteze standardizate: modul standard 100 kHz, modul rapid 400 kHz, variante mai recente 1 MHz și 3,4 MHz - toate sunt maxime, nu valori impuse; maestrul poate merge mai lent fără nimic să se strice, tocmai pentru că ceasul e transmis, nu presupus.
4Condițiile de start, de stop și confirmarea ACK/NACK11 min
Rămâne de rezolvat delimitarea mesajelor. Regula generală: SDA are voie să-și schimbe starea numai când SCL e jos; cât timp SCL e sus, datele trebuie stabile, pentru că exact atunci le eșantionează receptorul. O tranziție a lui SDA cât SCL e sus e, prin definiție, imposibilă în timpul transportului de date - poate fi deci folosită ca semnal de control.
Stop: SDA din 0 în 1 cât SCL e sus - eliberează magistrala.
Start repetat: identic cu start, dar fără stop înainte - permite schimbarea sensului transferului fără eliberarea magistralei, evitând ca un alt maestru să se strecoare între cele două jumătăți.
Confirmarea ACK/NACK
Spre deosebire de UART, I²C cere confirmare după fiecare octet. Fiecare octet ocupă 9 perioade de ceas: opt pentru date (MSB primul), a noua pentru confirmare. La a noua perioadă, emițătorul eliberează SDA; dacă receptorul acceptă octetul, trage linia la masă (ACK); dacă nu face nimic, rezistorul de ridicare menține linia sus (NACK).
5Tranzacții I²C: scriere, citire, arbitraj13 min
Toate elementele de mai sus se combină în două tipare pe care orice bibliotecă I²C le execută.
Tranzacția de scriere
Pentru a scrie 0x1F în registrul 0x07 al unui sclav la adresa 0x68: maestrul generează start, emite 0xD0 (adresă+scriere), citește ACK; emite 0x07 (numărul registrului, o convenție a perifericului, nu a magistralei), citește ACK; emite 0x1F (valoarea), citește ACK; generează stop. Pentru protocolul I²C, transferul e: start, adresă cu scriere, trei confirmări, doi octeți de date, stop - interpretarea „registru + valoare" ține de foaia de catalog a perifericului.
Tranzacția de citire
Mai subtilă, pentru că de obicei trebuie mai întâi spus sclavului ce vrem, ceea ce înseamnă o scriere. Faza 1: start, adresă cu scriere (0xD0), ACK, numărul registrului (0x07), ACK - sclavul și-a poziționat pointerul intern, dar n-a trimis nimic. Faza 2: start repetat, adresă cu citire (0xD1), ACK - rolurile pe SDA se inversează, sclavul devine emițător, maestrul receptor, dar maestrul continuă să genereze ceasul. La fiecare octet primit, maestrul confirmă cu ACK dacă mai vrea date, sau cu NACK dacă a fost ultimul dorit, apoi generează stop.
Dacă maestrul ar emite stop imediat după un ACK, sclavul ar putea rămâne cu ieșirea activă, ținând SDA la masă - magistrala rămâne blocată până la oprirea alimentării. Dacă microcontrolerul se resetează în mijlocul unei citiri, sclavul continuă să țină SDA jos, așteptând ceasul; maestrul nu mai poate genera nici măcar un start. Deblocarea: generarea manuală, prin software, a până la nouă impulsuri pe SCL, urmate de o condiție de stop.
Arbitrajul și întinderea ceasului
Arbitrajul rezolvă coliziunea când doi maeștri încep simultan: fiecare emite biții adresei și citește simultan SDA - dacă citește 0 când a emis 1, altcineva trage linia, iar el se retrage imediat, fără pierdere de biți. Câștigă maestrul cu adresa numeric mai mică (zero domină). Întinderea ceasului (clock stretching) rezolvă sclavul lent: SCL e tot colector deschis, deci un sclav care are nevoie de timp o ține trasă jos după ce maestrul a eliberat-o; maestrul, care verifică nivelul real înainte de a considera perioada încheiată, așteaptă. E un mecanism de control al fluxului pe care nici UART, nici SPI nu-l au.
6SPI: registrul de deplasare circular11 min
SPI (Serial Peripheral Interface) pornește din premise opuse - nu economisește fire, transferă cât mai repede și cu cât mai puțină logică. Nu are o specificație oficială unică; e un mecanism hardware de schimb de biți peste care fiecare periferic definește propriile convenții.
Comunicația e full-duplex, maestru-sclav, pe patru linii: SCLK (ceas, generat exclusiv de maestru), MOSI (Master Out Slave In), MISO (Master In Slave Out), și SS (Slave Select, activ pe zero logic - un sclav neselectat își pune MISO în înaltă impedanță).
Deplasarea în cele două registre e comandată de același ceas - nu există „doar transmisie" sau „doar recepție". Dacă maestrul vrea doar să trimită, primește oricum înapoi octetul din registrul sclavului (de regulă ignorat). Dacă vrea doar să citească, trebuie totuși să trimită un octet fictiv (dummy byte), pentru că altfel n-ar avea de ce să genereze impulsuri de ceas. De aici forma tuturor funcțiilor de bibliotecă SPI: o singură funcție de transfer, care primește un octet și întoarce un octet.
A doua consecință: sclavul nu poate răspunde imediat. Octetul pe care îl trimite e cel deja pregătit în registru la începutul transferului, nu unul calculat pe baza octetului primit simultan. Dialogul obișnuit are două etape: primul transfer trimite o comandă și primește un octet fără valoare; al doilea trimite un octet fictiv și primește răspunsul la comanda anterioară. Cine ignoră acest decalaj citește sistematic răspunsul cu o poziție întârziere.
Linia SS are două roluri: selectează sclavul, și delimitează tranzacția - multe periferice își resetează logica internă la fiecare front descrescător al selecției, iar comenzile lor sunt interpretate relativ la începutul tranzacției.
7Modurile SPI: polaritate și fază11 min
Un registru de deplasare are nevoie de ambele fronturi ale ceasului, pentru operații diferite: pe unul, ieșirea e actualizată (bitul următor iese pe linie); pe celălalt, intrarea e eșantionată (bitul prezent e capturat). Cele două se fac obligatoriu pe fronturi opuse, la jumătate de perioadă distanță, altfel receptorul ar citi linia exact în momentul comutării.
CPHA (faza): care front eșantionează - 0 = eșantionare pe primul front, schimbare pe al doilea; 1 = invers.
| Mod | CPOL | CPHA | Eșantionare |
|---|---|---|---|
| 0 | 0 | 0 | front crescător (primul) |
| 1 | 0 | 1 | front descrescător (al doilea) |
| 2 | 1 | 0 | front descrescător (primul) |
| 3 | 1 | 1 | front crescător (al doilea) |
Modurile 0 și 3 sunt cele mai răspândite - în ambele, eșantionarea se face pe frontul crescător, diferă doar starea de repaus. Modul 0 e implicit pentru biblioteca Arduino și pentru majoritatea memoriilor, cardurilor SD și convertoarelor.
Dacă maestrul e în modul 0 (scoate pe descrescător, citește pe crescător) și sclavul e în modul 1 (invers), toată succesiunea se deplasează cu o jumătate de perioadă - sclavul capturează bitul următor în loc de cel curent. SPI nu are paritate, sumă de control sau confirmare - registrul maestrului se umple cu opt biți la fel de conștiincios ca și cum totul ar fi corect. Simptomul: un senzor raportează valori absurde, un afișaj arată pixeli împrăștiați, un card SD nu se inițializează, iar erorile pot apărea intermitent, în funcție de întârzierile parazite ale traseului. Diagnostic: foaia de catalog a perifericului, un osciloscop, sau încercarea metodică a celor patru moduri.
8Topologii SPI și modulul SPI al ATmega328P13 min
Cazul cu un singur sclav leagă cele patru linii direct - SS poate fi uneori legat permanent
la masă, dacă perifericul o acceptă. Configurația cu selecție independentă: toți sclavii
împart SCLK, MOSI, MISO, dar fiecare are propria linie SS - independență completă, dar costă
3+n pini pentru n sclavi. Înlănțuirea (daisy chain): MISO al primului merge la
MOSI al celui de al doilea ș.a.m.d., SCLK și SS comune tuturor; registrele formează un singur lanț
lung - numărul de pini rămâne 4 indiferent de câți sclavi, dar dispozitivele nu mai pot fi adresate
individual, iar durata unui transfer crește proporțional. Se folosește doar pentru dispozitive
identice care se actualizează oricum împreună (un șir de drivere de afișaj), niciodată pentru
senzori diferiți.
Modulul SPI al ATmega328P
Patru pini fizici: PB2 (SS), PB3 (MOSI), PB4 (MISO), PB5 (SCK) - pinii 10-13 pe Arduino Uno.
| Registru | Bit | Rol |
|---|---|---|
| SPCR | SPE / MSTR | activare modul / rol (1=maestru) |
| SPCR | CPOL / CPHA / DORD | mod de funcționare, ordinea biților |
| SPCR | SPR1:0 | prescalare ceas: f_osc/4, /16, /64, /128 |
| SPSR | SPIF | transfer încheiat (8 impulsuri consumate) |
| SPSR | SPI2X | dublează frecvența, până la f_osc/2 |
| SPDR | - | registrul de date - scrierea pornește transferul, citirea întoarce octetul primit |
Dacă rămâne intrare și cineva îl trage jos, modulul comută automat din maestru în sclav, ștergând bitul MSTR - un mecanism prevăzut pentru sisteme cu mai mulți maeștri, dar care, într-o aplicație obișnuită, produce o defecțiune greu de explicat: comunicația se oprește brusc, fără motiv aparent.
void SPI_MasterInit(void) {
DDRB |= (1<<PB3)|(1<<PB5)|(1<<PB2); /* MOSI, SCK, SS: iesiri */
DDRB &= ~(1<<PB4); /* MISO: intrare */
SPCR = (1<<SPE)|(1<<MSTR)|(1<<SPR0); /* maestru, ceas f_osc/16 */
PORTB |= (1<<PB2); /* SS inactiv (High) */
}
uint8_t SPI_MasterTransfer(uint8_t data) {
PORTB &= ~(1<<PB2); /* selecteaza sclavul */
SPDR = data; /* scrierea porneste transferul */
while (!(SPSR & (1<<SPIF))) { }
PORTB |= (1<<PB2);
return SPDR; /* octetul primit de la sclav */
}
ATmega328P la 16 MHz, periferic limitat la 2 MHz pe SCLK.
Vezi rezolvarea
Divizoarele disponibile (fără SPI2X): 4, 16, 64, 128, dând 4 MHz, 1 MHz, 250 kHz, 125 kHz. Prima valoare (4 MHz) depășește limita - se alege divizarea cu 16 (SPR0=1, SPR1=0), ceas de 1 MHz. Perioada unui bit: 1 µs, deci opt biți se transferă în 8 µs. La viteze mari, cheltuiala de program (scrierea în SPDR, testarea SPIF, comanda liniei de selecție) devine comparabilă cu durata transferului propriu-zis, iar rata utilă efectivă rămâne sensibil sub cea teoretică.
9Bibliotecile SPI și Wire, scanarea magistralei I²C9 min
Biblioteca SPI pune la dispoziție noțiunea de tranzacție, care declară viteza, ordinea biților și modul cerut de periferic:
SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0));
digitalWrite(PIN_SS, LOW);
SPI.transfer(adresa | 0x80); /* comanda; bitul 7 marcheaza citire */
uint8_t valoare = SPI.transfer(0x00); /* octet fictiv, primeste raspunsul */
digitalWrite(PIN_SS, HIGH);
SPI.endTransaction();
Selecția rămâne comandată explicit de program (numărul pinului depinde de montaj); cele două transferuri reflectă exact decalajul discutat mai devreme.
Biblioteca Wire, pentru I²C, implementează tranzacțiile discutate:
Wire.beginTransmission(ADRESA); /* pregateste START + adresa + W */
Wire.write(reg);
Wire.endTransmission(false); /* false = START repetat, fara STOP */
Wire.requestFrom(ADRESA, (uint8_t)1);
if (Wire.available()) valoare = Wire.read();
beginTransmission nu trimite nimic - acumulează octeții într-un buffer intern; abia
endTransmission generează start, adresă, date și (dacă nu i se dă false)
stop. Valoarea întoarsă e codul de eroare: 0=succes, 2=NACK la adresă (periferic absent), 3=NACK la
date.
Scanarea magistralei I²C
Decurge direct din mecanismul de confirmare: start + adresă + stop, fără date - dacă vine ACK, există un dispozitiv la acea adresă; dacă NACK, nu există.
for (uint8_t adresa = 8; adresa < 120; adresa++) {
Wire.beginTransmission(adresa);
if (Wire.endTransmission() == 0) {
Serial.print("Gasit la 0x"); Serial.println(adresa, HEX);
}
}
Un rezultat gol: verificați inversarea SDA/SCL, lipsa masei comune, absența pull-up-urilor. Un rezultat cu dispozitive la toate adresele: indică de obicei o linie SDA blocată în zero logic - fiecare interogare pare confirmată.
ATmega328P la 16 MHz. Modulul TWI: f_SCL = f_osc / (16 + 2·TWBR·4^TWPS).
Vezi rezolvarea
Cu TWPS=0 (4^0=1): f_SCL = f_osc/(16+2·TWBR).
Pentru 100 kHz: 16×10⁶/100000 = 160 = 16+2·TWBR ⇒ TWBR=72.
Pentru 400 kHz: 16×10⁶/400000 = 40 = 16+2·TWBR ⇒ TWBR=12.
Ambele valori sunt întregi, exacte. Spre deosebire de UART, unde eroarea ratei e o problemă permanentă, aici o abatere de la nominal n-ar avea nicio consecință funcțională - sclavii urmăresc ceasul primit, oricare ar fi el, cu condiția să nu depășească limita lor maximă.
10Comparație între UART, SPI și I²C6 min
| Criteriu | UART | SPI | I²C |
|---|---|---|---|
| Fire | 2 | 3 comune + n selecție | 2 |
| Ceas comun | nu | da | da |
| Viteză uzuală | 9600-115200 bps | până la 10 Mbit/s | 100-400 kHz |
| Dispozitive | 2, punct la punct | 1 maestru + n sclavi | până la 112 adrese |
| Confirmare | doar paritate opțională | niciuna | ACK/NACK per octet |
| Utilizare tipică | calculator, depanare | memorii, SD, afișaje rapide | senzori lenți, RTC, EEPROM |
Cele trei coexistă fără conflict pe același microcontroler, pentru că folosesc module hardware și pini diferiți - un proiect obișnuit are simultan UART către calculator pentru depanare, I²C cu doi-trei senzori, și SPI către un card de memorie sau un afișaj.
11Erori frecvente4 min
- „Am legat SDA și SCL fără rezistoare de pull-up - dispozitivele oricum au pini de alimentare." Fals - fără rezistoarele de ridicare, liniile eliberate de toate dispozitivele rămân în aer, la un nivel nedefinit, în loc de unu logic. Fiecare linie I²C cere un rezistor de ridicare, extern sau de pe un modul deja prezent pe magistrală.
- „Perifericul SPI nu răspunde bine, dar codul pare corect - trebuie să fie o problemă de alimentare." Adesea nu - e un mod SPI (CPOL/CPHA) nepotrivit, care nu generează niciun mesaj de eroare, doar date greșite. Verificați foaia de catalog pentru modul cerut și încercați metodic cele patru combinații dacă nu e specificat clar.
- „Am confirmat (ACK) și ultimul octet la o citire I²C, ca să fiu sigur că totul a mers bine." Fals - ACK pe ultimul octet spune sclavului să continue transmisia, nu că totul e în regulă. Trimiteți NACK deliberat după ultimul octet dorit, apoi generați condiția de stop.
12Rezumat și glosar5 min
Ambele magistrale rezolvă limitele UART-ului transportând explicit un semnal de ceas. SPI o face cu patru linii și aproape fără protocol: maestrul și sclavul formează, prin MOSI și MISO, un singur registru de deplasare circular, iar la fiecare impuls un bit iese și un bit intră simultan - citirea și scrierea sunt inseparabile. Cele patru moduri (CPOL, CPHA) trebuie potrivite exact între maestru și sclav, altfel apar date greșite fără nicio semnalizare. Selecția independentă consumă un pin per sclav; înlănțuirea economisește pini cu prețul adresării individuale. I²C face același lucru cu două linii, plătind cu un protocol elaborat: colectorul deschis și rezistoarele de ridicare fac magistrala partajabilă fără conflicte, condițiile de start/stop folosesc singurele tranziții interzise în timpul datelor, iar confirmarea ACK/NACK după fiecare octet oferă un control de flux pe care nici UART, nici SPI nu îl au. Alegerea între ele nu e preferință, ci cerințe: viteză pentru SPI, economie de pini pentru I²C, distanță și compatibilitate pentru UART.
13Întrebări de verificare7 min
- Explicați de ce o legătură UART nu poate lega trei dispozitive pe aceleași fire, în timp ce I²C poate lega zece. Ce element al protocolului face diferența?
- De ce transportul explicit al ceasului elimină problema abaterii oscilatoarelor?
- Descrieți ce s-ar întâmpla electric pe SDA dacă un dispozitiv ar avea ieșire în contratimp în loc de colector deschis, iar un al doilea ar trage linia la masă simultan.
- Explicați de ce condiția de start nu poate fi confundată cu un bit de date oarecare.
- Un maestru citește cinci octeți de la un sclav I²C. Cine confirmă fiecare octet, și ce se întâmplă dacă maestrul confirmă și ultimul?
- Justificați afirmația că pe SPI nu se poate citi fără a scrie, pornind de la registrul de deplasare circular.
- Un periferic SPI cere modul 2, programul îl configurează pe modul 0. Ce se întâmplă la nivelul fronturilor, și de ce nu apare niciun mesaj de eroare?
14Direcții de aprofundare2 min
Cursurile următoare trec de la mecanismele individuale ale perifericelor la proiectarea unei aplicații complete: cum se aleg și se combină resursele discutate până acum - porturi, întreruperi, timere, ADC, comunicație - într-un sistem funcțional, testat și documentat.
Configurarea SPI, protocolul I²C și scanarea magistralei din acest curs devin montaj real în Laboratorul 06.