Lekce 19 - Arduino - Využití I2C sběrnice
V minulé lekci, { PREVIOUS}, jsme se naučili pracovat s SD kartou.
V tomto tutoriálu Arduina se zaměříme na popis a využití I²C sběrnice. Vysvětlíme si, jak tato sběrnice funguje. Dozvíme se také, jak s její pomocí dokážeme rozšířit schopnosti našeho Arduina.
I²C sběrnice v Arduinu
I²C neboli Two Wire Interface je sériová sběrnice od společnosti
Philips, která využívá ke komunikaci dva vodiče –
SDA
(Synchronous Data) a SCL
(Synchronous Clock). SDA
vodič slouží k přenosu dat a
na SCL
vodič je přiveden hodinový signál. Obě linky je možno
používat jako obousměrné. Výhodou je stálý hodinový signál, který
vysílá pouze jedno zařízení. Nebude nám proto vadit jeho posun s časem od
zapnutí. Další výhodou je možnost zapojení několika zařízení najednou.
V tom se I²C zásadně liší od sběrnice UART, která umožňuje propojit
pouze dvě zařízení (RX
– recieve, TX
–
transmit). Výběr zařízení ke komunikaci se na I²C provádí vysláním
signálu s adresou zařízení, s nímž chceme komunikovat. Nevýhodou je
ztráta dat na vyšší vzdálenosti. I²C se proto obvykle využívá např. na
deskách plošných spojů ke komunikaci mezi procesorem a např. AD/DA
převodníky.
A v čem je kouzlo této sběrnice? Pomocí čtyř vodičů na ní můžeme
připojit až 128 zařízení. Naše Arduino bude pracovat jako
tzv. master. To je zařízení, které je na sběrnici pouze
jedno. Ovládá veškerou komunikaci a generuje hodinový signál na pinu
SCL
. Zbylých 127 zařízení jsou tzv. slave. To
jsou zařízení, které komunikují s masterem a čekají na jeho pokyny.
Co tedy můžeme na sběrnici připojit? Skoro všechno! Například LCD display, RTC modul (ten na čas), IO expander (přidá další digitální piny), jiné Arduino a spoustu dalšího.
Jen pozor - piny SCA
s SDL
jsou u
Arduin UNO a NANO připojeny na analogové piny A5
a
A4
, tudíž je nemůžeme používat současně se sběrnicí.
Teorie také tvrdí, že na SDA
a SCL
bychom měli
odporem připojit VCC
. Ze zkušenosti vám můžeme říci, že na
krátké vzdálenosti (zapojování pár zařízení v breadbordu atd.) to
vůbec není potřebné. Případný rezistor budeme potřebovat pouze pokud
nám na delších vzdálenostech něco nebude správně fungovat. Sběrnice je
tedy určena pro krátké vzdálenosti, ale s odpory je možné na UTP kabelu
dosáhnout i délky 10 metrů. Na druhém konci pak bude bez problému fungovat
LCD i s expandery.
I²C adresa
Každé zařízení má svoji adresu. Dost často na modulu nalezneme tři
piny označované jako A0
, A1
a A2
(hardwarové adresovací linky). Přivedením napětí na jednotlivé piny
dokážeme měnit adresu zařízení. Například tabulka adres pro IO expandery
PCF8574 použité v tomto tutoriálu bude vypadat takto:
A0 | A1 | A2 | Dec | Hex |
---|---|---|---|---|
L | L | L | 32 | 20 |
H | L | L | 33 | 21 |
L | H | L | 34 | 22 |
H | H | L | 35 | 23 |
L | L | H | 36 | 24 |
H | L | H | 37 | 25 |
L | H | H | 38 | 26 |
H | H | H | 39 | 27 |
L
- Low, pin bez napětí; H
- High, 5 V na
pinu
Adresu lze zapsat decimálně (32
) nebo hexadecimálně
(0x20
). Rozdíl v tom podstatě není žádný, používají se oba
typy zápisů. Při koupi součástek se dost často stává, že zařízení
má jinou adresu, než uvádí prodejce. Adresu si tedy zjistíme pomocí I²C
scanneru ze stránek Arduina.
Sketch nahrajeme do desky, otevřeme Serial Monitor a Arduino vypíše
všechna zařízení na sběrnici.
LCD displej
Když si koupíme LCD displej s I2C sběrnicí, bude k němu připájena
destička se čtyřmi vývody - VCC
, GND
,
SDA
a SCL
. Zapojení těchto vodičů je jednoduché -
VCC
připojíme na 5 V, GND
na zem, SDA
na SDA
a SCL
na SCL
. Ještě budeme
potřebovat knihovnu. Arduino IDE už v sobě má manažera knihoven,
tudíž rozklikneme Sketch -> Include Library ->
Manage Libraries, vyhledáme knihovnu LiquidCrystal_I2C
a
nainstalujeme ji. Klasický LCD displej se od displeje se sběrnicí I2C liší
pouze zapojením a použitím jiné knihovny.
Program na obsluhu displeje
Pro jednoduché zobrazení hlášky Hello world!
použijeme
tento kód:
#include <Wire.h> #include <LiquidCrystal_I2C.h> LiquidCrystal_I2C lcd(0x27,20,4); void setup() { lcd.init(); lcd.backlight(); lcd.setCursor(3,0); lcd.print("Hello, world!"); lcd.setCursor(2,1); lcd.print("ITnetwork.cz!"); } void loop() { }
Zajímavý je řádek LiquidCrystal_I2C lcd(0x27, 20, 4)
. Zde
vytvoříme objekt LCD displeje. V parametrech není použit seznam
RS
, E
a DX
pinů, nýbrž pouze I²C
adresa, počet znaků na řádek a počet řádků. Tudíž displej je
připojený na adrese 0x27
, má čtyři řádky a 20 znaků. Poté
náš LCD displej inicializujeme pomocí lcd.init()
. Jelikož i
piny pro podsvícení jsou připojené na I2C čip, musíme pomocí
lcd.backlight()
zapnout také podsvícení. Funkce
print()
a setCursor()
jsou nám již dobře známé z
lekce o LCD
displeji.
IO expander
Řekněme, že se dostaneme do situace, kdy potřebujeme připojit další zařízení a nemáme už volné piny. V takové chvíli můžeme sáhnout po tzv. IO expanderu, například po tomto PCF8574. Na sběrnici jich můžeme připojit až osm, což je 8*8=64 pinů. Na expanderu tedy máme osm digitálních pinů. Pro následující vysvětlení je třeba rozumět principu dvojkové soustavy.
IO expander jako výstup
Pokud expander chceme použít jako výstupní piny, zapíšeme na něj hodnotu v rozmezí 0-255. Pokud jako výstupní, budeme hodnotu číst. Nyní ovšem nastává otázka jak požadované signály na pinech převést na hodnoty a zase zpátky?
Osm pinů na expanderu si označíme čísly 0-7, jak jsou popsány na čipu. Tyto čísla pro nás budou mocniny dvojky. Tudíž první pin (0) dostane číslo 1, druhý (1) dostane 2, třetí (2) dostane 4 a tak dále. Ve výsledku získáme tabulku:
Pin | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|
Číslo pinu | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
Mocnina dvojky | 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128 |
Dejme tomu, že budeme chtít mít na pinech číslo 0 a 5 napětí (logická 1) a na všech ostatních ne (logická 0). Naši tabulku tedy doplníme:
Pin | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|
Číslo pinu | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
Mocnina dvojky | 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128 |
Výstup | 1 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
Tím nám v posledním řádku vznikne číslo v dvojkové soustavě
- 10000100. To už jenom převedeme do desítkové soustavy a máme
číslo, které můžeme odeslat na expander - tedy 132. Pro
zápis na expander musíme mít naimportovanou knihovnu Wire.h
. V
setupu z ní zavoláme funkci Wire.begin()
. Poté můžeme na
expander posílat data:
Wire.beginTransmission(33); Wire.write(132); Wire.endTransmission();
V prvním řádku otevřeme spojení k příslušnému expanderu na adrese
33
(také lze zapsat 0x21). V druhém řádku zapíšeme naši
hodnotu 132
. Ve třetím řádku se pak provede samotné odeslání
na expander. Funkce endTransmission()
také vrací číslo podle
úspěšnosti. Pokud se data zapsat povedlo, vrátí nám 0
, pokud
zařízení neexistuje, vrátí 4
.
Na expanderu nemusí být na výstupech čistých 5 V, napětí může být nižší.
IO expander jako vstup
Pokud budeme potřebovat použít piny jako vstupní, je princip fungování
expanderu úplně stejný (jen obrácený). Číslo přečteme v desítkové
soustavě. Pak jej musíme převést zpět na jedničky a nuly (úrovně
napětí) pro jednotlivé piny. Jak tedy na to? Nejprve si pomocí funkce
Wire.requestFrom()
vyžádáme zařízení na sběrnici. Pokud je
dostupné, přečteme z něj hodnotu pomocí Wire.read()
a
uložíme ji do proměnné. Arduino má funkci bitRead()
, která
vstupní číslo v desítkové soustavě převede na dvojkový zápis. Jako
druhý parametr funkce vezme pozici čísla (tj. mocninu dvojky) a vrátí, zda
je na ní 1 nebo 0. Funkci zavoláme postupně se všemi čísly od 0 do 7,
vrácenou hodnotu převedeme na string
a přidáme do proměnné.
Tím získáme původní řetězec, se kterým jsme pracovali na začátku při
zapisování:
Wire.requestFrom(33, 1); if (Wire.available()) { byte readExpander = 255 - Wire.read(); String readed; for (byte i = 0; i < 8; i++) { readed += String(bitRead(readExpander, i)); } if (String(readed[0]) == "1") { pin1(); } else if(String(readed[1]) == "1") { pin2(); } else if(String(readed[2]) == "1") { pin3(); } else if(String(readed[3]) == "1") { pin4(); } else if(String(readed[4]) == "1") { pin5(); } else if(String(readed[5]) == "1") { pin6(); } else if(String(readed[6]) == "1") { pin7(); } else if(String(readed[7]) == "1") { pin8(); } }
Samozřejmě můžeme vynechat ukládání do proměnné readed
i smyčku a v podmínkách používat přímo
if (bitRead(readExpander, 0) == 1) { pin1(); }
V další lekci, Zapisovátko Morseovky s Arduinem, sestavíme a naprogramujeme zapisovač Morseovy abecedy.