Cześć,
dzisiejsza część kursu będzie poświęcona I2C, postaramy się omówić I2C, przedstawić zasadę działania oraz sposób obsługi przez Arduino. Zapraszamy
Spis treści:
- Co to jest I2C ?
- Zasada działania I2C
- Podstawowe funkcje
- Przykładowy program #1: Skaner adresów I2C
- Przykładowy program #2: Obsługa układu MPU-6050 przy pomocy I2C
- Podsumowanie
1. Co to jest I2C ?
I2C (ang. Inter-Integrated Circuit ; pol. pośredniczący pomiędzy układami scalonymi), nazywaną również TWI (ang. Two Wire Interface) jest magistralą, której głównym zadaniem jest nawiązanie komunikacji pomiędzy urządzeniami podrzędnymi, zwanymi dalej slave (niewolnikami) z urządzeniami głównymi master, zarządzającymi tymi to układami. Do komunikacji potrzebuje jedynie dwóch sygnałów SDA (Serial Data) i SCL (Serial Clock), odpowiednio sygnał przesyłający dane, oraz sygnał zegarowy.
Magistrala tego typu może obsługiwać do 1008 urządzeń typu slave (przy 10-bitowym adresowaniu) albo 127 (przy 7-bitowym adresowaniu). Dodatkowo nic nie stoi na przeszkodzie, aby do takiej magistrali podłączyć inne urządzenia typu master, w związku z czym np. 2 urządzenia typu master mogą odbierać dane od paru urządzeń typu slave.
Jak widzimy na schemacie powyżej, magistrala potrzebuje podciągnięcia linii danych oraz zegara do +5V przez rezystory. Przeważnie spotykamy w elektronice podciągnięcie (pull-up) do 5V przy pomocy rezystorów 4,7k ohma. Można by pomyśleć, że są to sztywne wartości, ale jak się okazuje to nie, a wręcz te rezystory pełnią ważną funkcję. Dzięki tym rezystorom możemy definiować prędkość transmisji pomiędzy układami. Tutaj można podać zakres wartości rezystorów 1,8k do 47k- przy czym mniejsza wartość rezystora stanowi o większej prędkości transmisji. Dla rezystora 2k ohm prędkość transmisji będzie wynosić około 400kb/s. Warto mieć również na względzie fakt (teoretyczny), że przy większych prędkościach transmisji, pobór energii przez urządzenie jest większy (nie są to co prawda duże wartości, ale jednak). Dlatego takim wypośrodkowaniem, pomiędzy poborem energii, a prędkością transmisji są rezystory o wartości 4,7k ohma.
Kolejną ważną funkcją rezystorów to jest fakt, że urządzenia I2C są urządzeniami typu otwartego drenu, co oznacza, że są w stanie sterować tylko stanem niskim, przez co muszą być w jakiś sposób podciągane do stanu wysokiego – i w tym momencie pojawia się podciągnięcie do 5V po przez rezystor. Pewnie zastanawiacie się dlaczego tak to zostało zrobione, otóż odpowiedź jest bardzo prosta :) .
Jak się zastanowimy to mamy taką sytuację: parę urządzeń podłączonych do jednej magistrali, jedno z nich w momencie wysyłania sygnału jest w stanie niskim, drugie natomiast jest akurat w stanie wysokim. Ze względu na zaistniałą różnicę potencjałów może dojść – w najgorszym scenariuszu- do uszkodzenia tych urządzeń (wliczając w to urządzenie typu master). Tak więc te układy mogą jedynie „sterować” stanem niskim, a kiedy nim nie sterują to sygnał zostaje automatycznie „podbity” do stanu wysokiego. Tyle względem wstępu teoretycznego. Teraz przyjrzymy się w jaki sposób wykonywana jest transmisja pomiędzy urządzeniami.
2. Zasada działania I2C
Zasada działania I2C jest niemalże banalna. Na obrazku poniżej przedstawiono w sposób „blokowy” jak odbywa się transmisja:
Na samym początku wysyłany jest bit startu, który inicjuje transmisje. Charakteryzuje się on tym, że linia SDA przechodzi z ze stanu wysokiego na niski, kiedy linia SCL jest dalej w stanie wysokim. Następnie wysyłanych jest 7 bitów zawierających adres urządzenia (A6 to MSB, a A0 to LSB) oraz jeden bit R/W, który informuje czy urządzenie typu master chce dokonać odczytu (1 logiczna) czy zapisu (0 logiczne).
Następnie urządzenie slave powinno zwrócić nam 9. bit, którym jest Ack (Acknowledge). Jeżeli wysłaliśmy poprawnie adres urządzenia docelowego, to to urządzenie powinno automatycznie na linię SDA, podać stan niski co będzie oznaczało poprawny odbiór adresu. Natomiast, jeżeli na linii SDA ciągle będzie stan wysoki (NAck- Not Acknowledge) to znaczy, że urządzenie slave nie rozpoznało adresu bądź jest jakiś inny błąd związany z komunikacją pomiędzy urządzeniami i w tym momencie następuję przerwanie procedury.
Jeżeli nasz adres został poprawnie rozpoznany to jest podawany kolejny adres, tzw. adres wewnętrznego rejestru, który nam zwraca daną wartość z układu. Np. jeżeli chcemy aby zegar RTC zwrócił nam godzinę to podajemy adres, odpowiedzialny za godzinę.
1 |
#include <Wire.h> |
Teraz w pętli głównej programu musimy poinformować nasz mikrokontroler, że chcemy korzystać z I2C. W tym celu piszemy:
1 |
Wire.begin(); |
Od tego momentu, Arduino „już wie”, że będziemy wykorzystywać I2C.
Następnie musimy rozpocząć transmisję. W tym celu, korzystamy z funkcji:
1 2 3 |
Wire.beginTransmission(adres); // adres - adres główny naszego urządzenia |
Adres urządzenia możemy podać w postaci szesnastkowej (hex), albo zwykłej binarnej. Przeważnie adresy w I2C zapisywane są w postaci szesnastkowej. Od tego momentu możemy podawać adresy rejestrów wewnętrznych, odpowiedzialnych za dane funkcje układu.
Aby wysłać adres rejestru to korzystamy z funkcji:
1 2 3 |
Wire.write(adres); // adres- adres rejestru wewnętrznego |
Tutaj postać adresu jest niemalże dowolna, ponieważ może być ona w postaci szesnastkowej, binarnej, albo i dziesiętnej :) Przy pomocy tej funkcji możemy nie tylko wysyłać główny adres urządzenia, ale i adres wewnętrznego rejestru.
Gdy już wyślemy adresy to możemy zamknąć transfer do urządzenia przy pomocy funkcji:
1 |
Wire.endTransmission(); |
Domyślnie ustawienie funkcji, automatycznie wysyła bit stop, jednocześnie zwalniając magistralę dla innych urządzeń. Natomiast jak w nawiasie wstawimy false to bit stopu nie zostanie wysłany i magistala będzie cały czas utrzymywać komunikację z pomiędzy urządzeniem master, a slave.
Ponadto ta funkcja może zwracać następujące wartości:
- 0: transmisja zakończona sukcesem
- 1: odebrane dane są za duże, aby zmieścić je w buforze
- 2: urządzenie zwróciło NAck przy wysyłanym adresie
- 3: urządzenie zwróciło NAck przy wysyłanych danych
- 4: inny błąd (nieznany)
Następnie po zakończeniu transmisji musimy dać informację dla Arduino, żeby to „nasłuchiwało” przychodzących danych z urządzenia slave. W tym celu korzystamy z funkcji:
1 2 3 4 |
Wire.requestFrom(adres, ilosc) // adres - główny adres urządzenia slave // ilosc - ilosc bajtow ile Arduino ma przyjac |
Gdy już odbierzemy dane z urządzenia to możemy sprawdzić bufor, jakie dane zostały pobrane z urządzenia. Sprawdzić możemy za pomocą funkcji:
1 |
Wire.available() |
To tyle wstępem podstawowych funkcji. Nie są one trudne, należy tylko z nich korzystać w odpowiedniej kolejności :)