I2C – arduino tutorial

I2C – arduino tutorial

Witam!

W przypływie wolnego czasu  (wakacje (: ) postanowiłem napisać kolejny tutorial na temat arduino. Tym razem na warsztat wgziąłem interface I2C. 

Podstawowe informacje o I2C

  • I2C to szeregowa dwukierunkowa magistrala służąca do przesyłania danych pomiędzy układami elektronicznymi.
  • Składa się ona z dwóch pinów, SCL – Pin przez który przesyłany jest sygnał zegarowy, SDA, pin przez który przesyłane są dane. 
  • Dane są przesyłane w sposób synchroniczny, synchronizowany za pomocą sygnału zegarowego podawanego na pinie SCL.
  • Magistrala ta działa w układzie Urządzenie nadrzędne / urządzenia podrzędne.  Dopuszczalna jest jednak obecność kilku urządzeń nadrzędnych. 
  • Każde urządzenie podrzędne ma przypisany adres.
  • Magistrala ta jest szeroko stosowana w różnych modułach / czujnikach. 

Kwestie sprzętowe 

Pod względem sprzętowym magistrala składa się z dwóch pinów :

  • SCL – sygnał zegarowy
  • SDA – dane

Magistrala działa w konfiguracji open-drain co oznacza, że jedynka logiczna odpowiada zwolnienie magistrali a zero logiczne podciągnięciu magistrali do GND. 

To oznacza, że do prawidłowego działania magistrali potrzebne są rezystory podciągające. Zazwyczaj stosuje się rezystory o wartości ok 4.7 K. 

Rezystory te mogą być wbudowane w dane urządzenie ale zazwyczaj należy dodać zewnętrzne (należy to sprawdzić w dokumentacji). 

Podłączenie urządzeń do magistrali I2C jest proste. 

Przykład : 

Należy jednak pamiętać o następujących kwestiach :

  • Pojemność i indukcyjność połączenia w istotny sposób wpływa na jakość przesyłu danych. Oba te parametry powinny być jak najmniejsze.
  • Zazwyczaj za wartość graniczną pojemności przewodów uważa się wartość 470 pF. 
  • Zbyt duża prędkość przesyłu może wywołać błędy.  
  • Zbyt duża długość przewodów również może wywołać problemy. 
  • Linie do przesyłu danych przewodzą szybkozmienne sygnału elektryczne. Mogą one wywoływać szum na pobliskich połączeniach. Koniecznie należy je odseparować od wrażliwych na zakłócenia elementów (na przykład od części analogowej układu). 

Poniższy schemat przedstawia prosty przykład podłączenia dwóch płytek arduino po I2C. Został on wykorzystany do uzyskania wyników przedstawionych w dalszej części tutoriala. 

Sygnał zegarowy

Jednak zanim przedstawię protokół transmisji muszę przedstawić najpierw jak działa sygnał zegarowy.

Sygnał zegarowy jest generowany przez urządzenie nadrzędne i ma postać prostokątnego sygnału. 

Sygnał zegarowy warunkuje kiedy dane są odczytywane. 

Generalnie panuje zasada, że kiedy sygnał ma stan niski to wtedy zarówno urządzenie nadrzędne jak i podrzędne mogą zmieniać stan linii danych a kiedy panuje stan wysoki to wtedy dane są odczytywane. 

Protokół przesyłu danych 

Zasadniczo na magistrali I2C mogą zaistnieć dwa typy transferu danych :

  • Od urządzenia nadrzędnego do podrzędnego. 
  • Od urządzenia podrzędnego do nadrzędnego. 

Każdy z tych transferów zaczyna i kończy urządzenie nadrzędne. 

Każdy z tych transferów rozpoczyna się od nadania magistrali stanu początkowego. Polega ono na zmianie stanu linii SDA na niski a następnie na zmianie stanu linii SCL na niski. Stan początkowy nadaje magistrali urządzenie nadrzędne. Nadanie stanu początkowego jest równoznaczne z rezerwacją magistrali przez dane urządzenie nadrzędne. Inne urządzenia muszą czekać z rozpoczęciem transmisji do czasu zakończenia obecnej. 

Przykład stanu początkowego (fioletowy wykres to linia danych a żółty to zegar )

Każdy transfer danych kończy się nadaniem magistrali stanu końcowego. Polega on na przywróceniu stanu wysokiego na linii SCL a następnie na SDA. 

Stan końcowy również nadaje urządzenie nadrzędne. Jest to równoznaczne ze zwolnieniem magistrali. Od tej chwili inne urządzenia nadrzędne mogą rozpocząć transfer danych. 

Przykład stanu końcowego : 

(Następne wykresy są już z analizatora :p)

Następnie urządzenie nadrzędne wysyła adres urządzenia podrzędnego. Adres ma zazwyczaj 7 bitów choć nie jest to regułą.

Adres jest wysyłany od najbardziej znaczącego bitu do bitu najmniej znaczącego. 

Następny bit sygnalizuje co chce zrobić urządzenie nadrzędne. Ma on wartość zero w przypadku gdy chcemy wysłać dane oraz wartość 1 w przypadku gdy prosimy o dane. 

Następnie urządzenie podrzędne informuje czy odebrało dane czy nie. 

Robi to wysyłając bit który jest 0 (ACK). Jeśli ten bit to 1 (NAK) to nie jest ono w stanie obsłużyć żądania lub coś się sknociło. 

Tutaj mamy przykład żądania odebrania danych do urządzenia o adresie 0000 000. 

Na górze widzimy linię danych a na dole sygnał zegarowy. 

Odczytując bity wtedy kiedy linia zegarowa ma stan wysoki uzyskujemy coś takiego : 000000000  ( :p ) 

Pierwsze 7 bitów to adres (0000000) następnie mamy bit odczytu /zapisu, w tym wypadku zapisu bo ma wartość 0. Następnie mamy informację od urządzenia podrzędnego, że odebrało transmisję czyli kolejne 0. 

Teraz pora na inny adres : 0101010, zobaczmy jak teraz będzie wyglądała transmisja. 

Teraz odczytując dane możemy uzyskujemy 010101000 Pierwsze 7 bitów to nasz adres 0101010 a następnie mamy polecenie zapisu 0 i potwierdzenie odbioru czyli 0. 

Dalej to zależy od tego czy chcemy odebrać dane czy je wysłać. 

Przykład w którym urządzenie nadrzędne wysyła znak ‘a’ 

Po wywołaniu urządzenia podrzędnego następuje mała przerwa, ta przerwa jest dyktowana przez urządzenie podrzędne i ma na celu wstrzymania wysyłania danych do czasu aż urządzenie podrzędne przygotuje się do odbioru. Jest ona sygnalizowana stanem niskim na sygnale zegarowym. 

Ma to swoją nazwę : “Clock Stetching” (Niestety nie wiem jak to na polski przetłumaczyć, może ktoś mnie w komentarzach oświeci (: ) 

Jednak część urządzeń nie obsługuje tej opcji. W takiej sytuacji aby potwierdzić czy dane urządzenie jest gotowe do odbioru / nadawania, stosuje się tak zwany ” Acknowledge Polling” (Również brak mi polskiej nazwy). Polega on na wysyłaniu żądania odbioru / wysłania danych do czasu aż urządzenie wyśle potwierdzenie odbioru co oznacza, że jest gotowe do odbioru / wysłania danych.

Następnie sygnał zegarowy powraca i zgodnie z nim wysyłanych jest 8 bitów danych. Następnie mamy potwierdzenie odbioru pakietu danych przez odbiornik. Następnie transakcja kończy się wysłaniem sygnału STOP przez urządzenie nadrzędne. 

W przypadku wysłania kilku bajtów naraz to transmisja będzie wyglądała tak :

Każdy następny pakiet kończy się potwierdzeniem odbioru i przerwą wywołaną przez urządzenie podrzędne. 

Następnie przykład z urządzeniem nadrzędnym odbierającym dane. 

Tutaj procedura jest podobna : 

Z tym, że bit odczytu/zapisu m teraz wartość 1 co oznacza, że urządzenie nadrzędne oczekuje danych od urządzenia podrzędnego. 

Następnie urządzenie podrzędne wysyła dane a urządzenie nadrzędne sygnalizuje ich odebranie za pomocą bitu potwierdzenia.

Jednak jest tu mały haczyk bo kiedy urządzenie nadrzędne nie chce już odebrać więcej danych to sygnalizuje to ustawiając ten bit na 1 (NAK) a następnie wysyłając sygnał STOP. Jeśli natomiast chce odebrać następne bity to są one normalnie sygnalizowane 0 w tym miejscu. 

Powtórny start 

W protokole I2C istnieje opcja powtórnego startu. Polega ona na tym, że na końcu danej transmisji danych nie ma sygnału STOP. Zamiast tego na obu liniach danych pojawia się stan niski. W ten sposób można niejako zawiesić łączność na jakiś czas a potem odwiesić komunikację wysyłając powtórny sygnał Start wraz z adresu itd. Po odwieszeniu transmisji można również zmienić kierunek transmisji a nawet adresata. 

Ma to sens głównie gdy na danej linii jest więcej niż jedno urządzenie nadrzędne, w takiej sytuacji, brak sygnału STOP powoduje, że linia danych jest cały czas zarezerwowana przez jedno urządzenie nadrzędne. W ten sposób można zapobiec przejęciu linii przez inne urządzenie nadrzędne w trakcie np. wykonywania priorytetowej wymiany danych. 

Programowanie w arduino

W arduino IDE za obsługę portu szeregowego odpowiada bibliotek Wire.h oraz funkcje podchodzące pod klasę wire. 

Oto lista funkcji :

begin()

Funkcja która inicjalizuje I2C na danym urządzeniu. 

Jako argument należy wpisać adres (jeśli chcemy by mikrokontroler dołączył do szyny I2C jako urządzenie podrzędne) lub nic (wtedy arduino dołączy do szyny jako urządzenie nadrzędne). 

Adres może być zapisany jako liczba w systemie szesnastkowym, binarnym (np 0b10001010) lub dziesiętnym. Warto jednak zauważyć, że najbardziej znaczący bit jest ignorowany. 

Syntax : 

requestFrom()

Funkcja która wysyła żądanie wysłania danych przez urządzenie podrzędne. 

Argumenty :

  • Adres 
  • Ilość bajtów które chcemy odebrać 

Lub :

  • Adres 
  • Ilość bajtów które chcemy odebrać 
  • Informacja czy chcemy aby transakcja zakończyła się od razu po odebraniu danych czy trwała dalej. (Prawda lub fałsz). 

W przypadku jeśli wybierzemy 3 opcję ( wstawiając w to pole true) wtedy urządzenie nadrzędne nie wyśle sygnału STOP a tylko ustawi na obu liniach stan niski. W ten sposób linia danych będzie blokowana i inne urządzenie nadrzędne nie będzie jej mogło przez ten czas użyć. 

Syntax :

beginTransmission()

Funkcja rozpoczynająca transmisję danych do danego urządzenia podrzędnego. 

Jako argument należy podać adres urządzenia podrzędnego. 

Syntax :

endTransmission()

Funkcja kończąca wysyłanie danych do urządzenia podrzędnego. 

Jako argument można podać czy transmisja ma się zakończyć sygnałem STOP czy nie. 

Jeśli nie to wtedy sygnału stop nie będzie i transmisję danych będzie można zrestartować wysyłając powtórny sygnał start (za pomocą funkcji begin transmittion) można będzie również odebrać dane używając funkcji requestFrom(). 

Jako argument należy wpisać :

  • True jeśli chcemy aby transmisja zakończyła się sygnałem STOP. 
  • False jeśli tego nie chcemy. 
  • Możemy nic nie wpisywać, domyślna wartość to true. 

Funkcja zwraca status właśnie zakończonej transmisji :

  • 0 – Sukces
  • 1 – Bufor danych wysyłanych uległ przepełnieniu
  • 2 – Urządzenie podrzędne nie potwierdziło odbioru adresu (NACK)
  • 3 – Urządzenie podrzędne nie potwierdziło odbioru danych (NACK)
  • 4 – Wystąpił inny błąd. 

Syntax : 

write()

Funkcja która wysyła dane przez I2C. 

Jako argument mamy 3 opcje : 

  • Zmienną 
  • Zmienną typu string
  • Tablicę zmiennych ( W tym przypadku jako drugi argument należy podać wielkość tablicy). 

Syntax :

available()

Funkcja która zwraca ilość bajtów znajdujących się w buforze odebranych danych. 

Argumentów nie przyjmuje żadnych.

Syntax : 

read()

Funkcja która zwraca pierwszy bajt z bufora odebranych danych. 

Bufor działa jak kolejka  FIFO (first in first out) co oznacza, że dane są z niego odczytywane w takiej kolejności jak były odebrane. 

syntax :

SetClock()

Funkcja która ustawia prędkość zegara na linii SCL. 

Jako argument należy podać prędkość. 

Standardowo prędkość zegara to 100 kHz (ustawiona domyślnie) ale możliwe są również prędkości :

  • 10000 ( Tryb niskiej prędkości)
  • 400000 ( Tryb szybki). 
  • 1000000 (Tryb wysokich prędkości). 
  • 3400000 (Tryb wysokich prędkości) 

Wartości wpisywane do funkcji należy podawać w hercach. 

Informacje o kompatybilności danego urządzenia z daną prędkością należy szukać w dokumentacji.

Jeśli nie wiemy to należy to sprawdzić eksperymentalnie lub po prostu zostawić na domyślnych ustawieniach. 

Syntax :

Funkcja ta nic nie zwraca. 

onReceive()

Funkcja która umożliwia automatyczne uruchomienie innej funkcji kiedy do urządzenia zostaną wysłane dane. 

Jako argument należy podać uchwyt do tej funkcji. 

Syntax : 

onRequest()

To samo co powyżej z tym, że funkcja jest wywoływana przy otrzymaniu żądania wysłania danych. 

Syntax :

Kilka słów na koniec 

I to by było na tyle. 

Mam nadzieję, że ten tutorial okazał się pomocny (:

Jak zwykle komentarze pozytywne jak i negatywne mile widziane. 

Ocena: 5/5 (głosów: 5)

Podobne posty

13 komentarzy do “I2C – arduino tutorial

Odpowiedz

anuluj

Masz uwagi?