Asembler mikrokontrolerów AVR     Programowa obsługa magistrali I2C         Kompilacja




Powyższy schemat przedstawia prawidłowy sposób podłączenia zewnętrznych elementów podczas
korzystania z procedur, które całkowicie programowo (bit-bang) obsługują magistralę I2C. Umożliwiają
one wysyłanie warunków START/Repeated START/STOP oraz danych do urządzenia podrzędnego
(slave) ze stałą szybkością, taktowaną zegarem o częstotliwości 100 kHz (standard mode). Działają na
prawie każdym mikrokontrolerze ATtiny/ATmega, pracującym z częstotliwością CLK = 4/6/8/10/12/14/
16/18/20 MHz. Linie SCL/SDA magistrali I2C można definiować na dowolnych pinach mikrokontrolera
(nawet na różnych portach). Przebiegi czasowe sygnałów SCL/SDA są zgodne ze specyfikacją magi-
strali I2C oraz generowane z dokładnością do 1 cyklu zegarowego, niezależnie od częstotliwości pracy
CLK mikrokontrolera. Procedura wysyłająca dane, obsługuje wstrzymywanie transmisji przez urządze-
nie podrzędne (clock stretching) po wysłaniu każdego bajtu (przed bitem potwierdzenia ACK).
Dokładny opis tego schematu, prawidłowej konfiguracji mikrokontrolera, magistrali I2C oraz sposobu
transmisji danych, znajduje się na tej stronie.

1. Program sterujący
2. Podsumowanie

1. Program sterujący

Poniżej znajduje się kod programu przeznaczonego dla mikrokontrolerów serii ATmega48/88/168/328,
który wysyła dane do urządzenia podrzędnego (slave) na magistrali I2C (programowo).
.include  "m48def.inc"    ;definicje dla ATmega48
;.include  "m88def.inc"    ;definicje dla ATmega88
;.include  "m168def.inc"   ;definicje dla ATmega168
;.include  "m328def.inc"   ;definicje dla ATmega328

#define   CLK     8       ;częstotliwość pracy mikrokontrolera (CLK): 4/6/8/10/12/14/16/18/20 [MHz]

;Definiowanie linii SCL/SDA
#define   SCL     5       ;numer pinu dla linii SCL
#define   SDA     4       ;numer pinu dla linii SDA
#define   SCLP    PORTC   ;adres portu PORTx dla linii SCL
#define   SDAP    PORTC   ;adres portu PORTx dla linii SDA
#define   SCLD    SCLP-1  ;adres rejestru DDRx linii SCL
#define   SDAD    SDAP-1  ;adres rejestru DDRx linii SDA
#define   SCLI    SCLP-2  ;adres rejestru PINx linii SCL
#define   SDAI    SDAP-2  ;adres rejestru PINx linii SDA

#if CLK!=4 && CLK!=6 && CLK!=8 && CLK!=10 && CLK!=12 && CLK!=14 && CLK!=16 && CLK!=18 && CLK!=20
#error "Frequency (CLK) must be 4/6/8/10/12/14/16/18/20 MHz"
#endif
Zdefiniowana częstotliwość pracy CLK mikrokontrolera, musi mieć parzystą wartość z zakresu 4-20
MHz. W przeciwnym razie podczas kompilacji pojawi się komunikat o błędzie "Frequency (CLK) must
be 4/6/8/10/12/14/16/18/20 MHz" i zostanie ona przerwana. Takie wartości częstotliwości CLK zape-
wniają, że przebiegi czasowe sygnałów SCL/SDA są idealnie symetryczne względem siebie. Dolna
granica częstotliwości CLK = 4 MHz wynika z minimalnej liczby instrukcji (10), potrzebnych do wyko-
nania precyzyjnego opóźnienia 2.5us, na którym opiera się generowanie przebiegów czasowych sy-
gnałów SCL/SDA. Linie SCL/SDA magistrali I2C są definiowane na pinach PC5/PC4 (takich samych
używa moduł TWI), ale można je przypisać do dowolnych pinów mikrokontrolera (nawet na różnych
portach).
.cseg             ;pamięć programu (FLASH)
.org      $0000   ;wektory przerwań (dla ATmega48/88: 26 wektorów 2-bajtowych, o adresach $0000-$0019,
                  ;dla ATmega168/328: 26 wektorów 4-bajtowych, o adresach $0000-$0032).

;Jeśli żadne przerwania nie będą używane, to w tym miejscu można bezpośrednio umieścić kod programu

;Wektor nr 1 (Reset)
          ;rjmp    RESET   ;dla ATmega48/88 musi to być instrukcja RJMP o rozmiarze 2 bajtów,
          ;jmp     RESET   ;dla ATmega168/328 musi to być instrukcja JMP o rozmiarze 4 bajtów,
                          ;ponieważ wszystkie wektory w tych mikrokontrolerach mają taki rozmiar.

;RESET:
;Inicjalizacja wskaźnika stosu (zbędna dla ATmega48/88/168/328)
          ;ldi     R16,LOW(RAMEND)
          ;out     SPL,R16
          ;ldi     R16,HIGH(RAMEND)
          ;out     SPH,R16

;Konfiguracja mikrokontrolera
          ldi     R16,$EF   ;zatrzymanie zbędnych modułów (ADC, TWI, SPI, USART, TC0, TC1, TC2)
          sts     $64,R16   ;zapis rejestru PRR
          ldi     R16,$80   ;wyłączenie zasilania komparatora analogowego
          sts     $50,R16   ;zapis rejestru ACSR
Aby zmniejszyć pobór prądu, zostaje wyłączony komparator analogowy oraz zatrzymane taktowanie
nieużywanych modułów mikrokontrolera (ADC, TWI, SPI, USART, TC0, TC1, TC2).
;Poniższe procedury umożliwiają obsługę magistrali I2C i wysyłanie danych do urządzenia podrzędnego
;(slave) ze stałą szybkością, taktowaną zegarem o częstotliwości 100 kHz (standard mode). Magistrala
;I2C jest obsługiwana całkowicie programowo (bit-bang), a linie SCL/SDA można definiować na dowolnych
;pinach mikrokontrolera. Przebiegi czasowe sygnałów SCL/SDA są generowane z dokładnością do 1 cyklu
;zegarowego, niezależnie od częstotliwości pracy CLK mikrokontrolera, która musi wynosić 4/6/8/10/12/
;14/16/18/20 MHz. Procedura wysyłająca dane, obsługuje wstrzymywanie transmisji przez urządzenie
;podrzędne (clock stretching) po wysłaniu każdego bajtu (przed bitem potwierdzenia ACK).

;Wysłanie warunku START na magistralę I2C
          rcall   I2Csta    ;rozpoczęcie transmisji I2C
;Wysłanie adresu urządzenia na magistralę I2C
          ldi     R17,$78   ;7-bitowy adres (najmłodszy bit RW określa kierunek danych:
                            ;0 = zapis do urządzenia, 1 = odczyt z urządzenia)
          rcall   I2Cwri    ;wysłanie adresu (w kolejności od najstarszego do najmłodszego bitu)
;Sprawdzenie poprawności transmisji adresu
          cpi     R17,$00   ;wartość po udanym wysłaniu adresu z potwierdzeniem (ACK=0)
          brne    I2Cerr    ;skok, jeśli wystąpił błąd - urządzenie nie odpowiada (ACK=1)
;Wysłanie bajtu do urządzenia na magistrali I2C
          ldi     R17,$55   ;bajt danych
          rcall   I2Cwri    ;wysłanie bajtu
;Sprawdzenie poprawności transmisji bajtu
          cpi     R17,$00   ;wartość po udanym wysłaniu bajtu z potwierdzeniem (ACK=0)
          brne    I2Cerr    ;skok, jeśli wystąpił błąd - urządzenie nie odpowiada (ACK=1)
;Wysłanie warunku STOP na magistralę I2C
          rcall   I2Csto    ;zakończenie transmisji I2C

;Po wysłaniu warunku START musi wystąpić minimum 1 bajt danych (START + $xx), który jest adresem
;urządzenia. Jeśli urządzenie nie odpowiedziało na adres (ACK=1), to nadzorca musi wysłać warunek
;STOP kończący transmisję (START + $xx + STOP) lub powtórzony warunek START (Repeated START) z innym
;adresem (START + $xx + RepSTART + $xx). Jeśli urządzenie odpowiedziało (ACK=0), to nadzorca może
;zakończyć transmisję (START + $xx + STOP) lub wysyłać kolejne bajty danych, zakończone warunkiem STOP
;(START + N*$xx + STOP). Może też wielokrotnie wysyłać sekwencje powtórzonego warunku START (Repeated
;START) z minimum 1 bajtem danych, a następnie zakończyć warunkiem STOP (START + N*$xx + N*[RepSTART +
;N*$xx] + STOP). Nie jest dozwolona transmisja, składająca się z samych warunków (START+START, START+
;STOP, STOP+START, STOP+STOP).

;Główna pętla programu
Loop:     rjmp    Loop      ;zatrzymanie wykonywania programu
Na magistralę I2C jest wysyłany warunek START - procedura nie sprawdza, czy magistrala jest dostę-
pna tylko od razu wysyła warunek START. Powoduje to rozpoczęcie transmisji nadzorcy i następnie
wysłanie na magistralę adresu urządzenia podrzędnego. W tym przypadku jest to adres $78, należący
np. do modułu monochromatycznego wyświetlacza OLED o rozdzielczości 128x32 pikseli.
Jeśli urządzenie o takim adresie nie jest podłączone do magistrali (złącze CON2), to po jego wysłaniu
nie odpowie bitem potwierdzenia (ACK=1). Wtedy transmisja zostaje zakończona wysłaniem warunku
STOP, na wyjściu PB1 jest ustawiany niski stan logiczny, co powoduje zaświecenie czerwonej diody
LED1 sygnalizującej wystąpienie błędu, a wykonywanie programu zostaje zatrzymane w głównej pętli.
Przebiegi czasowe sygnałów SCL/SDA w takiej sytuacji, są przedstawione na poniższym rysunku.



Jeśli urządzenie o takim adresie jest podłączone do magistrali, to po jego wysłaniu rozpozna go
i odpowie bitem potwierdzenia (ACK=0). Następnie do urządzenia jest wysyłany bajt $55, na który
powinno ono odpowiedzieć bitem potwierdzenia (ACK=0). Wtedy transmisja zostaje zakończona
wysłaniem warunku STOP, dioda LED1 pozostaje zgaszona, a program zatrzymany w głównej pętli.
Przebiegi czasowe sygnałów SCL/SDA w takiej sytuacji, są przedstawione na poniższym rysunku.



Jeśli z jakiegoś powodu urządzenie nie odpowie bitem potwierdzenia (ACK=1) na wysłany bajt, to
wtedy transmisja zostaje zakończona wysłaniem warunku STOP, dioda LED1 zostaje zaświecona,
a program zatrzymany w głównej pętli.
Jak łatwo zauważyć dioda LED1 zostanie zaświecona zawsze, gdy wystąpi błąd transmisji: brak
odpowiedzi urządzenia na wysłany adres/bajt. Wtedy zostanie też wysłany warunek STOP. Dioda
LED1 pozostanie zgaszona, gdy transmisja adresu i bajtu do urządzenia powiodła się.

Instrukcje sprawdzające poprawność transmisji adresu/bajtu ("cpi R17,$00", "brne I2Cerr"), wydłu-
żają czas trwania niskiego stanu na linii SCL (10us) o 2 cykle, jeśli błąd/skok nie wystąpił (ACK=0)
lub o 3 cykle, jeśli błąd/skok wystąpił (ACK=1).

Przedstawione przebiegi czasowe sygnałów SCL/SDA, zostały zarejestrowane analizatorem logicznym
Kingst LA2016 z próbkowaniem o częstotliwości 200 MHz. Przebiegi znajdują się też w archiwum, jako
pliki "TWI2 ACK=0.kvdat" i "TWI2 ACK=1.kvdat" w formacie programu sterującego "Kingst VIS 3.5.4".
;Wysłanie warunku START na magistralę I2C
I2Csta:
;Sprawdzenie dostępności magistrali I2C
          ;sbis    SCLI,SCL  ;sprawdzenie stanu na linii SCL
          ;rjmp    I2Csta    ;skok, jeśli linia SCL ma stan niski (jest zajęta)
          ;sbis    SDAI,SDA  ;sprawdzenie stanu na linii SDA
          ;rjmp    I2Csta    ;skok, jeśli linia SDA ma stan niski (jest zajęta)
;Wysłanie warunku START/Repeated START na magistralę I2C
I2Crep:   nop               ;wyrównanie przebiegu czasowego sygnału SCL/SDA
          cbi     SCLD,SCL  ;uwolnienie linii SCL (stan wysoki)
          rcall   WaitU2m1  ;opóźnienie 2.5us (-1 cykl)
          rcall   WaitU2m1  ;opóźnienie 2.5us (-1 cykl)
          sbi     SDAD,SDA  ;ustawienie niskiego stanu na linii SDA
          #if CLK == 4
          rcall   WaitU5m10 ;opóźnienie 5us (-10 cykli)
          #else
          rcall   WaitU2m5  ;opóźnienie 2.5us (-5 cykli)
          rcall   WaitU2m5  ;opóźnienie 2.5us (-5 cykli)
          #endif
          ret

;Błąd transmisji I2C
I2Cerr:   rcall   I2Csto    ;zakończenie transmisji I2C
          sbi     DDRB,$01  ;ustawienie niskiego stanu na wyjściu PB1 (zaświecenie diody LED1)
          rjmp    Loop      ;zatrzymanie wykonywania programu

;Wysłanie warunku STOP na magistralę I2C
I2Csto:   nop               ;wyrównanie przebiegu czasowego sygnału SCL/SDA
          sbi     SDAD,SDA  ;ustawienie niskiego stanu na linii SDA
          rcall   WaitU2m1  ;opóźnienie 2.5us (-1 cykl)
          rcall   WaitU2m1  ;opóźnienie 2.5us (-1 cykl)
          cbi     SCLD,SCL  ;uwolnienie linii SCL (stan wysoki)
          rcall   WaitU2m1  ;opóźnienie 2.5us (-1 cykl)
          rcall   WaitU2m1  ;opóźnienie 2.5us (-1 cykl)
          cbi     SDAD,SDA  ;uwolnienie linii SDA (stan wysoki)
          #if CLK == 4
          rcall   WaitU5m10 ;opóźnienie 5us (-10 cykli)
          #else
          rcall   WaitU2m5  ;opóźnienie 2.5us (-5 cykli)
          rcall   WaitU2m5  ;opóźnienie 2.5us (-5 cykli)
          #endif
          ret

;Wysłanie bajtu adresu/rozkazu/danych do urządzenia na magistrali I2C
I2Cwri:   sbi     SCLD,SCL  ;ustawienie niskiego stanu na linii SCL
;Poniższe opóźnienie nie może zmienić wartości rejestru R17
          #if CLK == 4
          .dw     $C000,$C000 ;opóźnienie 2.5us (-6 cykli)
          #else
          rcall   WaitU2m6  ;opóźnienie 2.5us (-6 cykli)
          #endif
          sec               ;znacznik ostatniego bitu z wysyłanego bajtu
          rol     R17       ;najstarszy wysyłany bit (kopiowany do bitu C w rejestrze SREG)
I2Cwri0:  brcc    I2Cwri1   ;skok, jeśli wysyłany bit ma wartość 0
          nop               ;wyrównanie przebiegu czasowego sygnału SCL/SDA
          cbi     SDAD,SDA  ;uwolnienie linii SDA (stan wysoki)
          rjmp    I2Cwri2
I2Cwri1:  sbi     SDAD,SDA  ;ustawienie niskiego stanu na linii SDA
          rjmp    I2Cwri2   ;wyrównanie przebiegu czasowego sygnału SCL/SDA
I2Cwri2:
          #if CLK == 4
          .dw     $C000,$C000,$C000 ;opóźnienie 2.5us (-4 cykle)
          #else
          rcall   WaitU2m4  ;opóźnienie 2.5us (-4 cykle)
          #endif
          cbi     SCLD,SCL  ;uwolnienie linii SCL (stan wysoki) - urządzenie pobiera bit z linii SDA
          rcall   WaitU2m1  ;opóźnienie 2.5us (-1 cykl)
          rcall   WaitU2m1  ;opóźnienie 2.5us (-1 cykl)
          sbi     SCLD,SCL  ;ustawienie niskiego stanu na linii SCL
          #if CLK == 4
          .dw     $C000,0   ;opóźnienie 2.5us (-7 cykli)
          #else
          rcall   WaitU2m7  ;opóźnienie 2.5us (-7 cykli)
          #endif
          lsl     R17       ;kolejny wysyłany bit (kopiowany do bitu C w rejestrze SREG)
          brne    I2Cwri0
;Cały bajt (8-bitów) został wysłany
          .dw     $C000,0   ;wyrównanie przebiegu czasowego sygnału SCL/SDA (RJMP+NOP)
          cbi     SDAD,SDA  ;uwolnienie linii SDA (stan wysoki)
          rcall   WaitU2m2  ;opóźnienie 2.5us (-2 cykle)
          cbi     SCLD,SCL  ;uwolnienie linii SCL (stan wysoki) - 9 rosnące zbocze zegara
                            ;(urządzenie wysyła bit ACK na linii SDA)
          .dw     $C000,0   ;czekanie 1.5-2.5 cykli zegarowych na aktualizację stanu linii SCL
                            ;w rejestrze PINx (takie opóźnienie wprowadza układ synchronizacji
                            ;i detekcji zbocza sygnału na pinie mikrokontrolera).
I2Cwri3:  sbis    SCLI,SCL  ;czekanie na wysoki stan na linii SCL - urządzenie nie jest gotowe
                            ;(clock stretching)
          rjmp    I2Cwri3   ;po uwolnieniu linii SCL może dodatkowo upłynąć ok. 250-375ns, zanim
                            ;zbocze sygnału SCL osiągnie poziom wysokiego stanu logicznego, który
                            ;zostanie wykryty przez mikrokontroler. Wtedy czas trwania niskiego
                            ;stanu w 9 cyklu zegarowym sygnału SCL, wydłuży się o czas wykonywania
                            ;tej pętli (nie powinno się to zdarzyć przy częstotliwości CLK=4/6 MHz).
          #if CLK == 4
          .dw     $C000,0   ;opóźnienie 2.5us (-7 cykli)
          #else
          rcall   WaitU2m7  ;opóźnienie 2.5us (-7 cykli)
          #endif
          sbic    SDAI,SDA  ;sprawdzenie stanu na linii SDA - odczyt wartości bitu ACK z linii SDA
          ldi     R17,$01   ;błąd, jeśli linia SDA ma stan wysoki - urządzenie nie wysłało bitu ACK
          rcall   WaitU2m2  ;opóźnienie 2.5us (-2 cykle)
          sbi     SCLD,SCL  ;ustawienie niskiego stanu na linii SCL
          #if CLK == 4
          rcall   WaitU5m10 ;opóźnienie 5us (-10 cykli)
          #else
          rcall   WaitU2m5  ;opóźnienie 2.5us (-5 cykli)
          rcall   WaitU2m5  ;opóźnienie 2.5us (-5 cykli)
          #endif
;Rejestr R17 może mieć następujące wartości:
; $00 - bajt został wysłany z potwierdzeniem (ACK=0)
; $01 - bajt został wysłany bez potwierdzenia (ACK=1)
          ret
Procedury wysyłające warunek START/Repeated START ("I2Csta"), STOP ("I2Csto") oraz dane ("I2C-
wri") do urządzenia podrzędnego, po wykonaniu ostatniej zmiany stanu logicznego na linii SCL/SDA,
zawierają na końcu opóźnienie czasowe 5us pomniejszone o 10 cykli zegarowych.
Gdy kolejną wywoływaną procedurą będzie "I2Csta" / "I2Csto", to cykle te zostaną przeznaczone na:
powrót z aktualnej procedury ("ret" - 4 cykle), rozgałęzienie do kolejnej procedury ("rcall I2Csta" / "rcall
I2Csto" - 3 cykle), a w kolejnej procedurze na wyrównanie przebiegu czasowego ("nop" - 1 cykl) oraz
uwolnienie linii SCL ("cbi SCLD,SCL" - 2 cykle) / ustawienie niskiego stanu na linii SDA ("sbi SDAD,
SDA" - 2 cykle).
Natomiast, gdy kolejną wywoływaną procedurą będzie "I2Cwri", to cykle te zostaną przeznaczone na:
powrót z aktualnej procedury ("ret" - 4 cykle), załadowanie wysyłanego bajtu do rejestru R17 ("ldi R17,
$xx" - 1 cykl), rozgałęzienie do procedury "I2Cwri" ("rcall I2Cwri" - 3 cykle), a w procedurze "I2Cwri" na
ustawienie niskiego stanu na linii SCL ("sbi SCLD,SCL" - 2 cykle).
Dzięki temu wywołanie każdej kolejnej procedury, powoduje zmianę stanu na linii SCL/SDA po upływie
dokładnie 5us. To gwarantuje, że przebiegi czasowe sygnałów SCL/SDA są symetryczne względem
siebie oraz zgodne ze specyfikacją magistrali I2C. Kod procedur jest kompilowany warunkowo,
w zależności od zdefiniowanej częstotliwości CLK.
Procedura wysyłająca dane, wykrywa i obsługuje wstrzymywanie transmisji przez urządzenie podrzę-
dne (clock stretching) po wysłaniu każdego bajtu (przed bitem potwierdzenia ACK). Wtedy linie SDA
i SCL zostają uwolnione, a jeśli urządzenie nie jest gotowe (np. potrzebuje więcej czasu na przetwo-
rzenie danych), to może wstrzymać transmisję przez ustawienie niskiego stanu na linii SCL. Procedura
czeka, aż na linii SCL pojawi się wysoki stan (9 rosnące zbocze zegara). Po uwolnieniu linii SCL może
upłynąć 1.5-2.5 cykli zegarowych, zanim mikrokontroler rozpozna i zapisze zmianę jej stanu w rejestrze
PINx. Jest to maksymalne opóźnienie, jakie wprowadza układ synchronizacji i detekcji zbocza sygnału
na pinie mikrokontrolera. Ponadto zanim zbocze sygnału SCL osiągnie poziom wysokiego stanu logi-
cznego, który zostanie wykryty przez mikrokontroler może dodatkowo upłynąć 1000ns. Jest to maksy-
malny czas narastania zbocza Tr sygnału SCL, podany w specyfikacji magistrali I2C (zależy od rezy-
stancji Rp i pojemności Cb linii). W czasie narastania zbocza Tr sygnału SCL jest wykonywana pętla
("I2Cwri3: sbis SCLI,SCL", "rjmp I2Cwri3"), która wydłuża czas trwania niskiego stanu na linii SCL (5us)
w 9 cyklu zegarowym. W praktyce na mojej płytce stykowej z Rp=10k, czas Tr wynosił 3-6 cykli zega-
rowych (1-2 przebiegi pętli) przy CLK=8-20 MHz (250-375ns). Przy CLK=4-6 MHz cykl zegarowy trwa
na tyle długo, że zbocze sygnału SCL osiągało wysoki poziom bez dodatkowego czekania (pętla nie
była wykonywana).
;Pętla opóźniająca o 2.5us
#if CLK == 4
WaitU5m10: nop
WaitU2m1: nop
WaitU2m2: nop
          ret
#else
WaitU2m1: nop
WaitU2m2: .dw     $C000
WaitU2m4: nop
WaitU2m5: nop
WaitU2m6: nop
WaitU2m7:
          #if CLK == 6
          nop
          #elif CLK == 8
          ldi     R16,$02
          #elif CLK == 10
          .dw     $C000
          ldi     R16,$03
          #elif CLK == 12
          nop
          ldi     R16,$05
          #elif CLK == 14
          ldi     R16,$07
          #elif CLK == 16
          .dw     $C000
          ldi     R16,$08
          #elif CLK == 18
          nop
          ldi     R16,$0A
          #else
          ldi     R16,$0C
          #endif
          #if CLK > 6
WaitU2a:  dec     R16
          brne    WaitU2a
          #endif
          ret
#endif
Precyzyjna pętla opóźniająca o 2.5us, pomniejszona o różne liczby cykli zegarowych. Stanowi podsta-
wę do generowania dokładnych przebiegów czasowych sygnałów SCL/SDA. Kod pętli jest kompilowa-
ny warunkowo, w zależności od zdefiniowanej częstotliwości CLK.

2. Podsumowanie

W porównaniu do obsługi magistrali I2C przez sprzętowy moduł TWI, opisana metoda programowa
(bit-bang) ma większe ograniczenia. Przed wysłaniem warunku START procedura nie czeka, aż magi-
strala będzie dostępna i od razu go wysyła. Prawidłowe sprawdzenie zajętości magistrali I2C, wymaga
kilkukrotnego próbkowania linii SCL/SDA w stałych odstępach czasu (zależnych od częstotliwości jej
pracy SCL) i sprawdzania stanu linii SCL/SDA, co nie jest łatwe do zrealizowania programowo. Prymi-
tywne sprawdzenie zajętości magistrali, wyłączone w kodzie procedury "I2Csta" polega na wykryciu
momentu, gdy obie linie SCL/SDA mają wysoki stan logiczny i dopiero wtedy wysłaniu warunku START.
Jednak samo wykrycie wysokiego stanu na obu liniach nie daje pewności, że magistrala nie jest uży-
wana/zajęta, dlatego ta funkcja nie została zaimplementowana. Gdyby ta funkcja była włączona, to wa-
runek START musiałby być wysyłany procedurą "I2Csta", a powtórzony warunek START osobną pro-
cedurą "I2Crep". Przy wyłączonej funkcji obie te procedury wykonują to samo zadanie, bo wysyłany
warunek rozpoczęcia (START) i ponowienia (Repeated START) transmisji jest identyczny.
Jeśli na magistrali znajduje się tylko jeden nadzorca, to sprawdzanie jej dostępności nie jest w ogóle
konieczne. Po wysłaniu warunku START procedura nie sprawdza, czy się to udało (zakłada, że linie
SDA/SCL mają właściwy stan). Po wysłaniu każdego bajtu procedura sprawdza jedynie, czy urządzenie
odpowiedziało bitem potwierdzenia (ACK=0) lub nie (ACK=1).
Kolejnym ograniczeniem procedur w porównaniu do modułu TWI jest stała częstotliwość sygnału ze-
garowego SCL = 100 kHz. Aby ją zmienić, trzeba by całkowicie zmodyfikować wszystkie procedury,
z zachowaniem nowych opóźnień czasowych odpowiadających innej częstotliwości SCL. Ponadto
żadne przerwania nie mogą być wykonywane podczas działania procedur, bo zaburzą one przebiegi
czasowe sygnałów SCL/SDA (każdy cykl zegarowy ma znaczenie). Inną negatywną cechą procedur
jest trochę dłuższy czas, potrzebny na transmisję danych (sygnały SCL/SDA są wydłużone o margi-
nesy czasowe, które zapewniają większą bezbłędność transmisji). Teoretycznie można by skrócić
czas trwania niskiego stanu na linii SCL (z 10us do 7.5us), pomiędzy kolejnymi wysyłanymi bajtami.
Kosztem byłby nieco większy rozmiar, ale w praktyce nie przyspieszy to znacznie przesyłu danych.
Ostatnią i najmniej znaczącą wadą programowej obsługi magistrali I2C jest to, że jej procedury zaj-
mują trochę więcej pamięci FLASH, niż procedury do obsługi modułu TWI.

Poza wymienionymi ograniczeniami, są już tylko same zalety. Po pierwsze, dzięki procedurom progra-
mowym można dodać obsługę magistrali I2C do prawie każdego modelu mikrokontrolera ATmega/
ATtiny. Dotyczy to zarówno modeli wyposażonych w sprzętowy moduł TWI (ATmega 8/16/32/64/128,
48/88/168/328, 164/324/644/1284) lub prostszy sprzętowo-programowy moduł USI (ATtiny 24/44/84,
25/45/85, 261/461/861), jak i modeli bez żadnego modułu do komunikacji szeregowej (ATtiny 11/12/
13/22).
Po drugie, procedury programowe umożliwiają definiowanie linii SCL/SDA na dowolnych pinach
mikrokontrolera (nawet na różnych portach). Niezależnie do którego pinu zostanie przypisana dana
linia, zawsze będzie działać tak samo. Zapewnia to ogromną swobodę przy projektowaniu systemu
(np. połączeń na płytce drukowanej PCB).
Ostatnią i najmniej znaczącą zaletą programowej obsługi magistrali I2C jest to, że na przebiegach
czasowych sygnału SDA, nie występują krótkie impulsy (szpilki) wysokiego stanu logicznego po zaniku
wysokiego stanu w 9 cyklu zegarowym sygnału SCL (widoczne w przypadku działania modułu TWI).