Soros kommunikáció

A fejezet tartalma:

Az MKL25Z128VLK4 mikrovezérlő számos kommunikációs perifériával rendelkezik:
Ezek közül az USB perifériával bonyolultsága miatt későbbi fejezetekben foglalkozunk, a többi kommunikációs csatornát pedig ebben a fejezetben vesszük sorra.

Univerzális aszinkron soros kommunikáció

Az univerzális aszinkron soros adatküldés és fogadás története a géptávírók koráig nyúlik vissza. Fő feladata, hogy a párhuzamos adatokat (egy-egy karakter 5, 7, vagy 8 bites kódja) egyetlen vonalon, adatbitenként időben egymás után küldje el egy előre megszabott ütemben (pl. 110 bit/s), a vevő oldalon pedig a soros adatokat vissza kell állítani karakterkódokká. A kapcsolat jellegétől függően a kommunikáció lehet egyirányú (simplex), felváltva kétirányú (half duplex), vagy egyidejűleg kétirányú (full duplex). A teletype az első miniszámítógépek általános perifériájaként is kitűnő eszköz volt. Az első UART áramkörök a DEC PDP miniszámítógépeihez készültek, amelyekben a bejövő jel mintavételezésével jobb időzítést sikerült megvalósítani, mint a korábbi eszközökkel.

Az aszinkron soros kapcsolat egyik fontos jellemzője, hogy nincs külön szinkronjel, ezért az adónak és a vevőnek előre rögzített ütemben (bitrátával) kell küldeni/fogadni az adatokat. A másik fontos jellemző pedig az, hogy az adatbájtok (vagy karakterek) "keretbe foglalva" kerülnek kiküldésre. Erre egyrészt azért van szükség, hogy a végtelen bitfolyamban meg lehessen találni az egyes adategységeket, másrészt minden újabb adatbájt kezdete lehetőséget ad az újraszinkronizálásra.    

A keretezés azt jelenti, hogy minden adatbájt előtt van egy start bit, az adatbájt után pedig egy (vagy két) stop bit. A start és stop bit között adatküldési formátumtól függően lehet 5, 7, 8 vagy 9 adatbit és opcionálisan egy paritásbit. Mi a legelterjedtebb 8, N, 1 formátumot fogjuk használni, azaz 8 adatbitet küldünk, nem használunk paritásbitet, s a stop bitek száma 1 lesz. Egy tipikus jelalak (a TX kimenet jelszintjének időbeli lefutása) az alábbi ábrán látható. Az ábrán látható jelsorozat a bináris 0b01001101 adatbájt (ne feledjük: visszafelé kell olvasni!), ami a hexadecimális 0x4D-nek, vagy a decimális 77-nek felel meg, s az ASCII kódtábla szerint az „M” karakter kódját jelenti.

1. ábra:  Egy tipikus UART jelalak

Az UART kommunikáció eredetileg bipoláris, NRZ (Non Return to Zero) jelszintekkel történik. Egy példa erre az RS-232 ahol az '1' -12 V és -5 V közötti, a '0' pedig +12 V és +5 V közötti jelszint. A mikrovezérlők és a logikai IC-k világában azonban természetesebb megoldás az unipoláris jelszint alkalmazása: '1' a digitális rendszer logikai  magas jelszintje (régebben +5 V, az újabb eszközöknél, és így esetünkben is +3,3 V), a '0' pedig az alacsony logikai szint (0 V). Tétlen állapotban, amikor nincs adatküldés, az adás vonalat (TX) magas szinten kell tartani. Minden elküldött karakter egy start jellel kezdődik, azaz a TX vonal egy bitidő tartamára alacsony szintre vált. Ezt követően kerülnek kiküldésre egymás után az adatbitek, a legkisebb helyiértékű bittel kezdve. A legmagasabb helyiértékű bit után egy stop bit következik, amikor egy bitidő tartamára a kimenet magas szintre vált.

Ha a vevő azt tapasztalja, hogy a stop bit helyén (ami esetünkben a 10 bitet jelenti, ami a start bit lefutó élét 9 bitidőnyi késéssel követi) alacsony szintet észlel, akkor „keretezési hibát” (Framing error) jelez és eldobja a feltehetően hibásan vett adatot.

Egy bit időtartama az adatküldés sebességétől függ. A szabványos adatsebességek (300, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200 bit/s) közül alapértelmezetten a 9600 bit/s-ot használjuk. A 9600 bit/s azt jelenti, hogy egy bitre 1 s/9600  = 0,00010416667 s, azaz 104,1667 µs idő jut. Vegyük észre, hogy a karakterek "keretezése" miatt a hasznos adatok továbbítási sebessége kisebb, mint az aktuális adatküldési sebesség, mivel a 8 bites adaton kívül legalább egy START és egy STOP bitet is ki kell küldenünk.

A Serial objektumosztály

Az MKL25Z128VLK4 mikrovezérlő UART perifériáit a Serial objektumosztály segítségével konfigurálhatjuk és használhatjuk. A konfigurálás egyik fontos eleme, hogy kivezetéseket rendelünk az adott perifériához. A kivezetés megadása egyúttal kiválasztja az UART eszközt is, mivel az UART0, UART1, és az UART2 perifériákk más-más kivezetésekhez rendelhetők.

Az alábbi táblázatban csak a legfontosabb tagfüggvényeket foglaltuk össze. A részletes dokumentáció a Serial periféria-könyvtár honlapján található. Mivel a Serial objektumosztály leszármazottja az mbed::SerialBase, valamint az mbed::Stream osztályoknak, így tagfüggvényeit azoktól örökölte. A putc, puts, printf, getc, gets, scanf öröklött tagfüggvények nincsenek dokumentálva, de a hasonló nevű C stdio függvényeknek felelnek meg.

1. táblázat: A Serial objektumosztály legfontosabb saját és öröklött tagfüggvényei
Függvény
Használat
Serial név(txpin, rxpin)
Létrehoz egy "név" nevű Serial  objektumot, a txpin, rxpin paraméterek szerint kiválasztja a hozzá rendelt UARTx periférát, és UART ki/bemenetként konfigurálja a megnevezetett kivezetések funkcióját (lásd 2. táblázat)
baud(baudrate)
Az adatküldési sebesség beállítása (alapértelmezetten 9600 bit/s)
format(bits, parity, stop)
Az adatküldés formátumának megadása (alapértelmezetten 8,N,1)
readable ()
Megvizsgálja, hogy van-e beérkezett karakter
writeable ()
Megvizsgálja, hogy van-e szabad hely a kimeneti tárban
putc(c)
Egy karakter küldése, blokkoló típusú várakozással.
getc() Egy karakter fogadása, blokkoló típusú várakozással.
puts(s) Szövegkonstans karakterfüzér kiíratása
gets(s,n) n db karakter fogadása és eltárolása karakterfüzérként az s tömbbe
printf()
Formázott kiíratás
scanf()
Formázott szöveg beolvasása
Az MKL25Z128VLK4 mikrovezérlő kivezetés kiválasztás (pin multiplexing) funkciója lehetővé teszi, hogy a három UART periféria lábkivezetéseinek kiosztását több lehetőség közül kiválasszuk. Az, hogy az egyes UART modulok adó (TX) és vevő (RX) vezetéke egyáltalán mely lábakon vezethető ki,  a FRDM-KL25Z Pin usage and pinout chart dokumentumban található. Ezen lehetőségek közül némelyik a FRDM-KL25Z kártya esetében hardver ütközés miatt nem használható. A ténylegesen használható kivezetéseket az mbed forráskódjának PeripheralPins.c állománya alapján az alábbi táblázatban foglaltuk össze. A táblázatban vastagon szedett nevű kivezetések az elsődlegesen ajánlottak (az mbed Platforms szekciójában található kártyaismertető oldalon is ezeket jelölik meg UART kivezetésként).

2. táblázat: A Serial objektumokhoz rendelhető UART portok és választható kivezetéseik
Modul
TX
RX
Megjegyzés
UART0
PTA2
PTA1
Az OpenSDA USB-UART protokol konverterhez csatlakozik
PTD7
PTD6
Alternatív kivezetési lehetőség
PTE20
PTE21
Alternatív kivezetési lehetőség
UART1
PTE0
PTE1
A Pin diagram szerinti támogatott kivezetés
PTC4
PTC3
Alternatív kivezetési lehetőség
UART2
PTE22
PTE23
A Pin diagram szerinti támogatott kivezetés
PTD3
PTD2
Alternatív kivezetési lehetőség
PTD5
PTD4
Alternatív kivezetési lehetőség

Megjegyzés: A FRDM-KL25Z kártyán a mikrovezérlő PTA1, PTA2 kivezetései egy-egy 1 kΩ ellenálláson keresztül vannak összekötve az OpenSDA eszköz USB-UART protokol konverter TX/RX kivezetéseivel. Ez elvileg azt is lehetővé teszi, hogy "erősebb meghajtással" az OpenSDA  eszköz felől érkező adatvonal felett átvegyük az irányítást egy másik UART eszközzel. Ennek kipróbálását azonban kezdőknek nem ajánljuk, használják inkább a többi UART kivezetést!

Mintapélda: Egyszerű kiíratás és beolvasás

Az alábbi program egy egyszerű "Helló világ!" szintű mintaprogram, amely az alapértelmezett UART0 csatornán és az OpenSDA eszközön keresztül kommunikál a számítógéppel, s minden beérkező karaktert visszatükröz és kiírja a kódját (tízes számrendszerben). Az alapértelmezett beállításokat használjuk: 9600 bit/s, 8-bit, nincs paritásbit, 1 stop bit.

Hardver követelmények:

1. lista: A 06_frdm_serial/main.cpp program listája
#include "mbed.h"

Serial pc(USBTX,USBRX); //UART0 via OpenSDA

int main()
{
pc.printf("\r\nWelcome to FRDM-KL25Z board!\r\n");
while(1) {
char c = pc.getc(); //Read one character
pc.printf("received char: %c = %d\r\n",c,c);
}
}

Keressük meg az OpenSDA-hoz tartozó virtuális soros portot a számítógépen, és csatlakozzunk egy terminál programmal (PuTTY, Hyperterminal, Termite, stb.). Ha küldünk egy karaktert a PC-ről, a program visszatükrözi a vett karaktert és kiírja a karakter kódját is (decimálisan).


2. ábra: A 06_frdm_serial program futtatási eredménye


Mintapélda: Kommunikáljunk az UART2 porton!

Ha nem a számítógéppel, hanem olyan eszközökkel akarunk kommunikálni, amelyek UART interfésszel rendelkeznek (például GPS modul, HC-05 bluetooth modul, ESP8266 WiFi eszköz), akkor nem használhatjuk  az OpenSDA-val összekötött PTA1, PTA2 kivezetéseket, hanem valamelyik másik UART portot, illetve kivezetést kell választanunk helyette. Az alábbi programban az UART2 portot és a PTE22/PTE23 kivezetéseket rendeljük egy Serial objektumhoz, és azon keresztül kommunikálunk. A példánkban az egyszerűség kedvéért egy FTDI USB-UART konverter modult kötöttünk a PTE22/PTE23 kivezetésekhez, és a számítógépen figyeljük a kiírásokat, de bármilyen más, UART kommunikációra alkalmas eszközt is használhatnánk helyette.

Mivel a program letöltése után nincs szükségünk az OpenSDA eszköz csatlakoztatására, a FRDM-KL25Z kártya tápellátását is az FTDI modullal oldottuk meg. Ha 5 V áll rendelkezésre (pl. FTDI TTL-232R-3V3 kábel VCC kimenete), akkor azzal a kártya Vin (J2-16) pontjára csatlakozzunk! Ha pedig 3.3 V-os tápkimenetünk van, akkor azzal a a kártya 3V3 (J2-8) feliratú pontjára csatlakozzunk!

Hardver követelmények:

3. ábra: Az USB-UART konverter modul csatlakoztatása

2. lista: A 06_frdm_uart2/main.cpp program listája
#include "mbed.h"

DigitalOut myled(LED_GREEN);
Serial pc(PTE22, PTE23);

int main()
{
int i = 0;
pc.printf("Hello World!\r\n");

while (true) {
wait_ms(1000); // wait a small period of time
i++; // increment the variable
myled = !myled; // toggle a led
int s = myled;
pc.printf("%d. ledstate = %d \r\n", i,s); // print variable i and LED state
}
}
A programban a zöld LED-et villogtatjuk, kb. 0.5 Hz frekvenciával, s minden állapotváltáskor kiíratjuk az i változó értékét (vagyis az állapotváltások számát) és a LED aktuális állapotát. Az i változót minden állapotváltáskor megnöveljük eggyel.

Keressük meg az eszközünkhöz (pl.  FTDI TTL-232R-3V3 kábel) tartozó virtuális soros portot a számítógépen, csatlakozzunk egy terminál programmal (PuTTY, Hyperterminal, Termite) és figyeljük meg a kimenetet!

4. ábra: A 06_frdm_uart2 program futási eredménye


Az SPI kommunikációs csatorna

Az SPI (soros periféria illesztő = Serial Peripheral Interface) busz kétirányú szinkron soros kommunikációt valósít meg két eszköz között. A kommunikációban résztvevő eszközök között master/slave (mester/szolga) viszony áll fenn.  Az SPI buszt "négyvezetékes" busznak is szokták nevezni, bár az SPI kommunikáció egyes változatai 3, 5, vagy ötnél is több vezetéket használnak. Az SPI busz kiterjeszthető: egy master több slave eszközhöz is kapcsolódhat, ám a kommunikációra kiválasztott slave eszközt egyedi választó vonallal (Slave Select) hardveresen kell kijelölni.

5. ábra: Több SPI eszköz kezelése külön választó vonalakkal

Az SPI busz kiterjesztésének egy másik módja az eszközök soros felfűzése (daisy chain). Ezt a módszert csak azoknál az eszközöknél használhatjuk, amelyek támogatják a felfűzést. Ilyen SPI eszközök például az AD5204 digitális potenciométerek, a MAX7219 LED vezérlő IC-k, MAX5233 dual 10-bites DAC-ok.

6. ábra: Felfűzött SPI eszközök vezérlése

Az SPI órajel polaritása és fázisa

Az SPI buszon az adatátvitel szinkronizálását a busz vezérlő eszköze (SPI master) által keltett szinkron órajel (SCK) biztosítja. Azon túlmenően, hogy a master megszabja az órajel frekvenciáját, a megszólított slave eszközhöz illeszkedően biztosítani kell az órajel megfelelő polaritását és fázisát. Az adatlapok tartalmazzák azt az információt, hogy az adott SPI eszköz milyen üzemmódban vagy üzemmódokban képes működni. Az alábbi ábrán az SPI busz jeleinek a különböző üzemmód tartozó jelalakjait szemléltetjük.

SCK - Az SPI busz szinkronizálást biztosító órajele. A master eszköz állítja elő.
#SS - Slave select, azaz a slave eszköz kiválasztására szolgáló jel, melynek '0' állapota aktivizál. A master eszköz állítja elő.
MOSI - A master eszköz kimeneti adatvonala (Master out, Slave in). A master eszköz állítja elő.
MISO - A slave eszköz kimeneti adatvonala, melyet a master olvas (Master in, Slave out). A slave eszköz állítja elő.


7. ábra: Az SPI busz jelalakjai a különböző üzemmódokban

A lehetséges üzemmódok az órajel polaritásának és fázisának kombinációjaként állnak elő. Az órajel polaritását (CPOL) egyszerű megérteni: CPOL = 0 azt jelenti, hogy inaktív állapotban SCK alacsony szintű, CPOL = 1 esetén pedig az SCK órajel inaktív állapotban magas.

A fázis (CPHA) esete valamivel bonyolultabb: CPHA = 0 esetén az adatvonalak bekapuzása az órajel páratlan számú átmenetein történik (az ábrán rózsaszín vonalakkal jelölt időpontokban). CPHA = 1 esetén pedig a páros számú átmeneteknél (kék vonalakkal jelölt időpontokban) történik az adatvonalak bekapuzása.

Fentiek alapján az órajel polaritás és fázisa az alábbi négy üzemmódot jelenti:
Üzemmód
CPOL
CPHA
0
0
0
1
0
1
2
1
0
3
1
1

Az SPI objektumosztály

Az SPI objektumosztály segítségével a a mikrovezérlőnket master módban használhatjuk, ami lehetővé teszi számunkra, hogy a mikrovezérlőhöz kötött SPI perifériákat (mint pl. EEPROM memória, TFT kijelző, digitális potenciométer, külső SPI DAC, LED mátrix kijelző, SD kártya, ENC28J60 Ethernet vezérlő) vezéreljünk. Van SPIslave programkönyvtár is, melynek segítségével a mikrovezérlőt slave módban használhatjuk, de ennek ismertetésével itt nem foglalkozunk.

Az MKL25Z128VLK4 mikrovezérlő két SPI modullal rendelkezik. A konfigurálás egyik fontos eleme, hogy kivezetéseket rendelünk az adott perifériához. A kivezetés megadása egyúttal kiválasztja az SPI eszközt is, mivel az SPI0 és az SPI1 perifériák más-más kivezetésekhez rendelhetők. Az alábbi táblázatban láthatjuk az SPI modulokhoz rendelhető kivezetéseket. A MOSI és MISO oszlop azért közös, mert lényegében megegyeznek, a benne felsorolt kivezetések bármelyike konfigurálható MOSI vagy MISO kivezetésként.

3. táblázat: Az SPI modulokhoz rendelhető kivezetések
SPI modul
SCLK
MOSI/MISO
SPI0
PTA15, PTC5, PTD1
PTA16, PTA17, PTC6, PTC7, PTD2, PTD3
SPI1
PTB11, PTD5, PTE2
PTB16, PTB17, PTD6, PTD7, PTE1, PTE3
Megjegyzés: Az Arduino kompatibils bekötéshez a táblázatban kövéren szedett kivezetések tartoznak: MOSI=PTD2, MISO=PTD3, SLCK=PTD1

4. táblázat: Az SPI objektumosztály legfontosabb tagfüggvényei
Függvény
Használat
SPI név(mosipin, misopin, sclkpin)
Létrehoz egy "név" nevű SPI  objektumot, a mosipin, misopin és sclkpin paraméterek szerint kiválasztja a hozzá rendelt SPIx periférát, és SPI buszként konfigurálja a megnevezetett kivezetések funkcióját.
frequency(hz)
Az adatküldési sebesség beállítása (alapértelmezetten 1 MHz)
format(bits, mode)
Az üzemmód beállítása (alapértelmezetten 8-bit, 0 mód)
write(data)
Adat kiküldés és olvasás (a beolvasott értékkel tér vissza)

Mintapélda: 8x8 LED mátrix vezérlése MAX7219 IC-vel

Az alábbi egyszerű mintapéldában egy 8x8-as LED mátrixot vezérlünk MAX7219 IC-vel. A program az SPI periféria konfigurálásával és a MAX7219 LED vezérlő inicializálásával kezdődik.  Inicializáláskor a LED vezérlő teszt üzemmódját is engedélyezzük. Ekkor minden LED kigyullad. Egy fél másodperc eltelte után letiltjuk a teszt üzemmódot, és nullára törölt adatregiszterekkel normál megjelenítési módba kapcsolunk (minden LED elalszik). Ezután végtelen ciklusban felváltva két karakter (H és W) jelenítünk meg.



8. ábra: 8x8 LED mátrix MAX7219 vezérléssel, mint SPI periféria

A MAXIM gyártmányú MAX7219 LED vezérlő IC egy 8x8-as LED mátrix vagy egy 8 digites 7-szegmenses LED kijelző meghajtására alkalmas. SPI buszon vezérelhető, több eszköz felfűzését (daisy chain) is támogatja.


9. ábra: A MAX7219 ED vezérlő IC tokrajza és tipikus alkalmazása

Hardver követelmények:
Bekötési vázlat:
MAX7219
FRDM-KL25Z
Megjegyzés
VCC
3V3
Tápfeszültség (3,3 V)
GND
GND
Tápegység közös pontja ("föld")
DIN
D11 (PTD2)
MOSI adatkimenet
CS
D10 (PTD0)
Chip select jel
CLK
D13 (PTD1)
SCLK szinkron órajel
Megjegyzés: A FRDM-KL25Z kártyánál elsődlegesen az Arduino kompatibilis kimenetek neveit tüntettük fel.

3. lista: A 06_spi_max7219_led8x8/main.cpp program listája
#include "mbed.h"

SPI spi(PTD2, PTD3, PTD1); // Arduino compatible MOSI, MISO, SCLK
DigitalOut cs(PTD0); // Chip select

const unsigned char led1[]= {
0xFF,0x18,0x18,0x18,0x18,0x18,0x18,0xFF
}; //H
const unsigned char led2[]= {
0x1F,0x60,0x80,0x40,0x40,0x80,0x60,0x1F
}; //W

/// Send two bytes to SPI bus
void SPI_Write2(unsigned char MSB, unsigned char LSB)
{
cs = 0; // Set CS Low
spi.write(MSB); // Send two bytes
spi.write(LSB);
cs = 1; // Set CS High
}

/// MAX7219 initialisation
void Init_MAX7219(void)
{
SPI_Write2(0x09, 0x00); // Decoding off
SPI_Write2(0x0A, 0x08); // Brightness to intermediate
SPI_Write2(0x0B, 0x07); // Scan limit = 7
SPI_Write2(0x0C, 0x01); // Normal operation mode
SPI_Write2(0x0F, 0x0F); // Enable display test
wait_ms(500); // 500 ms delay
SPI_Write2(0x01, 0x00); // Clear row 0.
SPI_Write2(0x02, 0x00); // Clear row 1.
SPI_Write2(0x03, 0x00); // Clear row 2.
SPI_Write2(0x04, 0x00); // Clear row 3.
SPI_Write2(0x05, 0x00); // Clear row 4.
SPI_Write2(0x06, 0x00); // Clear row 5.
SPI_Write2(0x07, 0x00); // Clear row 6.
SPI_Write2(0x08, 0x00); // Clear row 7.
SPI_Write2(0x0F, 0x00); // Disable display test
wait_ms(500); // 500 ms delay
}

int main()
{
cs = 1; // CS initially High
spi.format(8,0); // 8-bit format, mode 0,0
spi.frequency(1000000); // SCLK = 1 MHz
Init_MAX7219(); // Initialize the LED controller
while (1) {
for(int i=1; i<9; i++) // Write first character (8 rows)
SPI_Write2(i,led1[i-1]);
wait(1); // 1 sec delay
for(int i=1; i<9; i++) // Write second character
SPI_Write2(i,led2[i-1]);
wait(1); // 1 sec delay
}
}
A program elején az SPI objektum példányosítása mellett egy digitális kimenetet is definiálnunk kell a kiválasztójel vezérléséhez (cs), ami alaphelyzetben magas szintre kell beállítanunk (cs = 1).

Az SPI_Write2() függvény egy kétbájtos tranzakciót haj végre: aktiválja a cs kimenetet, kiküld két bájtot az SPI buszra (regisztercím és regiszterbe írandó tartalom), majd inaktív állapotba helyezi a cs kimenetet.

Az Init_MAX7219() függvényben adatlap szerint alaphelyzetbe állítjuk a LED vezérlőt (közben egy fél másodpercig engedélyezzük a teszt módot, amikor minden LED kigyullad). A fényerőt közepes szintre állítjuk be.

A végtelen ciklusban felváltva a led1[], illetve a led2[] tömb tartalmával töltjük fel a LED mátrixot vezérlő IC adatregisztereit, ami egy H, illetve egy W bebtű 8x8 pixeles rajzolatát képviseli.


10. ábra: Pillanatkép a program működéséről


Az I2C kommunikációs csatorna

Az I2C (Inter-Integrated Circuit = integrált áramkörök közötti) kétvezetékes soros kommunikációs sínt a Philips fejlesztette ki. Az 1990-es évek közepétől számos versenytárs is fejlesztett ki I2C kompatibilis eszközöket. Az  MKL25Z128VLK4 mikrovezérlő I2C modulja teljes körű hardver támogatást nyújt mind a slave (szolga), mind a master (mester)  vagy a multi-master (egyidejűleg több mester csatlakozik a kommunikációs sínre) üzemmódhoz. Mindegyik módban támogatja a 7 és 10 bites címzést.

Az I2C busz legfontosabb jellemzői:

Az I2C busz esetében a "busz" csak formális elnevezés, fogalma csupán annyit takar, hogy olyan kommunikációs csatornáról van szó, amelyre több eszköz (egy vagy több adó és számos vevő) csatlakozhat. Az aktuális "mester" üzeneteit mindegyik eszköz látja, és dekódolja a címzést. Csak az az eszköz válaszol, amelyiket a címzés kijelöl.

Mivel az I2C busz vonalain mindkét irányba áramolhatnak a jelek, ezért a kimenetek szembekapcsolódásának megakadályozására minden kimenet nyitott nyelőelektródás (ez a nyitott kollektoros tranzisztorok CMOS megfelelője), amelyek csak lefelé húznak. Felfelé kizárólag a felhúzó ellenállások húzhatják a vonalakat.

Az I2C buszon folyó kommunikáció jellemző tulajdonságainak megismeréséhez nézzük meg egy bájt kiküldésének idődiagramja az alábbi ábrán! Adatküldésnél az adatvonal állapota csak az órajel alacsony állapotában változhat. Az órajel magas állapota alatt az adatvonalnak stabilnak kell maradnia.

Ha magas órajelszint mellett változik az adatvonal állapota, az mindig speciális feltételt (START, RESTART vagy STOP) jelent:
  1. Az adatküldés kezdetét a START feltétel jelzi (magas órajelszint mellett az adatvonal magas állapotból alacsonyra vált).
  2. Az adatküldés végét (nem feltétlenül az első bájt után!) egy STOP feltétel jelzi, amelynél magas órajelszint mellett alacsonyról magas szintre vált az adatvonal.
  3. A RESTART feltétel annyiból áll, hogy egy újabb START feltételt generálunk, anélkül, hogy a korábbi küldést egy STOP jellel lezártuk volna. Ez arra jó, hogy egy összetett tranzakció közben (mint pl. írást követő beolvasás) a master eszköz ne veszítse el a busz feletti irányítást.


11. ábra: Az I2C kommunikáció idődiagramja (egy bájt kiküldése)


Az I2C buszon a kommunikáció alapegysége a bájt (8 bit). Az első kiléptetett adatbit (az ábrán B1) a legmagasabb helyiértékű bit. A nyolcadik adatbit kiléptetése után az adatvonal adatáramlási iránya megváltozik (a master vételre kapcsol), s egy kilencedik óraimpulzus is kimegy az SCL vonalra. Ekkor a megcímzett slave eszköz nyugtázás céljából az adatvonalat lehúzhatja (ACK), jelezve, hogy felismerte a címet és rendelkezésre áll. 

A kommunikáció mindig úgy kezdődik, hogy a master generál egy START feltételt, majd kiküldi a megszólítani kívánt eszköz címét. Az első bájt 8 bitjéből azonban csak az első 7 bit szolgál a címzésre, a 8. bit az írás vagy olvasás funkció kiválasztására szolgál. A bit 0 értéke írást, az 1 állapota pedig olvasást jelöl.

Az, hogy a kommunikáció további bájtok küldésével, vagy fogadásával folytatódik, vagy a slave-től fogadunk adatokat, az attól függ, hogy milyen eszközzel kommunikálunk. Ezért a kommunikációs protokoll részleteivel csak konkrét eszközök kapcsán foglalkozunk majd.

Az I2C objektumosztály

Az I2C objektumosztály segítségével a a mikrovezérlőnket master módban használhatjuk, hogy ezáltal a mikrovezérlőhöz kötött I2Cperifériákat (mint pl. EEPROM memória, LM75 vagy TCN75  hőmérő, BMP085/BMP180 nyomásmérő, DS1307 RTC óra, TLS2951 fénymérő, HMC5883 mágneses térmérő) vezéreljünk. Megjegyezzük, hogy az mbed API I2Cslave programkönyvtárat is biztosít, melynek segítségével a mikrovezérlőt I2C slave módban használhatjuk, de ennek ismertetésével itt nem foglalkozunk.

Az MKL25Z128VLK4 mikrovezérlő két I2C modullal rendelkezik. A konfigurálás egyik fontos eleme, hogy kivezetéseket rendelünk az adott perifériához. A kivezetés megadása egyúttal kiválasztja az I2C eszközt is, mivel az I2C0 és az I2C1 perifériák más-más kivezetésekhez rendelhetők. Az alábbi táblázatban összefoglaltuk az I2C modulokhoz rendelhető kivezetéseket.

5. táblázat: Az I2C modulokhoz rendelhető kivezetések
I2C modul
I2C_SDA
I2C_SCL
I2C0
PTE25, PTB1, PTB3, PTC9
PTE24, PTB0, PTB2, PTC8
I2C1
PTE0, PTA4, PTC2, PTC11
PTE1, PTC1, PTC10
Megjegyzések:
  1. Az Arduino kompatibils bekötéshez elsődlegesen a táblázatban kövéren szedett kivezetések tartoznak: I2C_SDA=PTE0, I2C_SCL=PTE1, s ezekre a kivezetésekre I2C_SDA és I2C_SCL névvel is hivatkozhatunk. A másik Arduino kompatibilis kivezetés az  A4 (SDA) és A5 (SCL) analóg benetek használata a táblázatban szereplő PTC2, PTC1 kivezetéspárnak felel meg.
  2. Az első megjegyzésben említett Arduino kompatibilis kivezetéseken kívül még két további I2C csatlakozási lehetőseg szerepel az mbed kártyaismertető oldalon: PTC9, PTC8 (I2C0), valamint PTC11, PTC10 (I2C1).
  3. A PTE25, PTE24 kivezetéspár foglalt a FRDM-KL25Z kártyára szerelt MMA8451Q gyorsulásmérő IC számára!

6. táblázat: Az I2C objektumosztály legfontosabb tagfüggvényei
Függvény
Használat
I2C név(sdapin, sclpin)
Létrehoz egy "név" nevű I2C  objektumot, az sdapin és sclpin paraméterek szerint kiválasztja a hozzá rendelt I2Cx periférát, és I2C buszként konfigurálja a megnevezetett kivezetések funkcióját.
frequency(hz)
Az adatküldési sebesség beállítása (alapértelmezetten 100 kHz)
read(ack)
Egyetlen bájt olvasása nyugtázással vagy negatív nyugtázással
read(addr,*data,length)
Több bájt olvasása az addr című eszközről
write(data)
Egyetlen bájt küldése
write(addr,*data,length)
Több bájt írása az addr című eszközre
start()
START vagy RESTART feltétel generálása
stop()
STOP feltétel generálása
Megjegyzések:
  1. Az mbed API 8 bites I2C címeket kezel, tehát az adatlapokban található 7 bites címeket egy bináris helyiértékkel balra léptetve kell megadni a fenti függvényekben!
  2. Ne felejtsük el, hogy az I2C busz SDA és SCL vezetékeit egy-egy ellenállással fel kell húzni a tápfeszültségre! Ennek értéke a busz kialakításától és a busz sebességétől függően 2,2 k  - 4,7 k közötti érték legyen.
Vegyük észre, hogy a read()/write() függvényeket paraméterezéstől függően kétféle módon használhatjuk:

Mintapélda: A TCN75 digitális hőmérő kiolvasása

Az alábbi programban a Microchip TCN75 digitális hőmérőjét használjuk, ami I2C slave eszközként kezelhető, 7 bites címzést használva. Megjegyezzük, hogy néhány apró részlettől eltekintve, ehhez hasonlóan használható a Microchip TCN75A, a Maxim DS1621 vagy a National LM75 digitális hőmérője is, ezért a mintapélda könnyen adaptálható ezen típusokra.

A TCN75 hőmérő a -55 °C - +125 °C hőmérséklet-tartományban működik, pontossága optimális esetben 0.5 °C. Megjegyezzük, hogy a TCN75 digitális hőmérő mindegyik típusa működőképes a 2,7 - 5.5 V-os tápfeszültség- tartományban, de a TCN75-3.3 típusjelzésű példányok a 3,3 V-os tápfeszültséghez, a TCN75-5.0 típusjelzésűek pedig az 5,0 V-os tápfeszültséghez vannak kalibrálva. A névlegestől eltérő tápfeszültség esetén a tápfeszültség 1 V-os megváltozására a pontosság 1°C-ot is romolhat.

A hőmérő és a mikrovezérlő összekötése az adatlapból átvett ábrán látható: az SDA és az SCL lábakat össze kell kötni a mikrovezérlő megfelelő lábaival (esetünkben az SDA1 és SCL1 lábakkal), s mindkét vonalat fel kell húzni egy-egy ellenállással. A 100 kHz-es adatátviteli sebességnél 4,7 k ohm a szokásos érték, nagyobb sebességnél azonban keményebb felhúzásra van szükség (pl. 2 k ohm). Az ALERT láb bekötését most elhagyhatjuk.

Ar ábrán nincs feltüntetve, de természetesen a TCN75 hőmérőnél (mint minden más digitális IC-nél) is kell egy 100 nF-os kondenzátor a VDD és a GND kivezetések közé!

A 7 bites címzés felső négy bitje kötött (0b1001), az alsó három bitet viszont az az A0, A1, A2 bemenetek le- vagy felhúzásával állíthatjuk be. Ez lehetővé teszi, hogy  ugyanarra az I2C buszra max. 8 db. digitális hőmérő csatlakozzon, s a címzéssel ki tudjuk választani a megfelelőt.

A TCN75 hőmérő termosztát funkcióval is rendelkezik: az ALERT kimeneten jelez, ha a hőmérséklet egy előre beállított hőmérséklet fölé emelkedik. Ez a kimenet nyitott nyelőelektródás, s a mikrovezérlő külső programmegszakítás bemenetre köthető, vagy közvetlen fűtés-szabályozásra használható. Az ALERT kimenet üzemmódja (interrupt vagy komparátor mód, azaz impulzus vagy folyamatos szint kiküldése) és hiszterézise (hogy mekkora hőmérséklet-különbség esetén kapcsoljon vissza) az I2C buszon küldött parancsokkal beállítható. Bekapcsoláskor a TCN75 digitális hőmérő komparátor módba áll be, s az alapértelmezett billenési szint 80 °C lesz, 5 °C-os hiszterézissel. Ez lehetővé teszi, hogy a TCN75 IC akár önállóan is használható legyen termosztátként. Természetesen a lehetőségei akkor tudjuk maximálisan kihasználni, ha mikrovezérlővel az I2C buszon kommunikálunk vele. Arra is lehetőség van, hogy az I2C buszon küldött Shutdown paranccsal leállítsuk (alacsony fogyasztású módba kapcsoljuk) a hőmérőt. Az ALERT kimenet polaritása is programozható, s az interrupt/komparátor mód megválasztásával mind lekérdezéses (polling), mind programmegszakításos (interrupt) módban jól használhatjuk. 

A hőmérő belső felépítése és programozói modellje a következő ábrán látható. A félvezető hőérzékelő jelét egy 9 bites delta-szigma analóg-digitális átalakító digitalizálja. A négy belső regiszter közül a 8 bites CONFIG regiszter az üzemmód beállítására szolgál. A többi regiszter 16 bites, de csak a felső 9 bitet használjuk, balra igazított, kettes komplemensű előjeles számként. A képzeletbeli tizedespont a két bájt között van, tehát a regiszterek magasabb helyiértékű fele Celsius fokokban értendő (előjelesen), az alacsonyabb helyiértékű bájt legfelső bitje pedig fél fokot jelent. A TEMP regiszterből olvashatjuk ki az aktuális hőmérsékeletet, a TSET regiszterbe írhatjuk be a kapcsolási hőmérsékletet, a THYST regiszterbe pedig a hiszterézis értékét.

12. ábra: A TCN75 hőmérő belső felépítése és programozói modellje


Írásnál ügyeljünk arra, hogy a címzés után elsőnek kiküldött bájt alsó két bitje a regiszter-mutatót állítja be (a felső hat bit kötelezően nulla!). A CONFIG regiszter írásához tehát az i2c.write(addr, cmd, 2); függvényhívást használhatjuk, ahol addr a TCN75 címe (0x90, ha minden címvonal a földre van lehúzva), cmd[0] = 1 (a CONFIG regiszter címe), cmd[1] = 0 pedig a CONFIG regiszterbe írni kívánt adat. A TSET és THYST regiszterek írásánál értelemszerűen eggyel több adatbájtot kell kiküldeni, mivel ezek 16 bites regiszterek. Ne feledjük el, hogy több-bájtos adatok esetén előbb mindig a magas helyiértékű bájtot küldjük, s utána az alacsonyabb helyiértékűt. A TEMP regiszter (melynek címe = 0) csak olvasható.
 
Olvasásnál két lehetőségünk van:
  1. Előbb egybájtos írással (az i2c.write(addr,cmd,1); függvény hívásával, ahol cmd[0]=0) beállítjuk a regiszter-mutatót, majd egy új művelettel (vagy RESTART-tal, ha elemi műveletekből építkezünk) olvasást indítunk, ami az előzőleg beállított regiszter tartalmát olvassa ki. (CONFIG olvasása esetén egybájtos, a többi regiszter esetén egy vagy kétbájtos olvasást kezdeményezünk).
  2. Ha ugyanazt a regisztert olvassuk többször egymás után, akkor elhagyhatjuk a regiszter-mutató beállítását.
A TCN75 digitális hőmérő kezelésén keresztül az I2C objektumosztály tagfüggvényeinek használatát bemutató mintaprogram az alábbi listán látható. A program kb. 1 másodpercenként kiolvassa a hőmérőt, és a kiolvasott értéket Celsius fokokra átszámítva kiírja a standard kimenetre (UART0).

4. lista: A 06_i2c_tcn75/main.cpp program listája
#include "mbed.h"
I2C i2c(I2C_SDA, I2C_SCL); // Arduino compatible pins
const int addr = 0x90; // TCN75 adress: 0x48<<1

int main()
{
char cmd[2]; // data buffer
printf("\r\nTCN75 I2C thermometer\r\n");
cmd[0] = 0x01; // Pointer to CONFIG register
cmd[1] = 0x00; // Data for CONFIG (Normal operation, comparator mode)
i2c.write(addr, cmd, 2); // Send Address/comman and two bytes
while (1) {
wait(1);
cmd[0] = 0x00; // Pointer to TEMP register
i2c.write(addr, cmd, 1); // Write adress/command byte, then register address
i2c.read(addr, cmd, 2); // Read 2 bytes from TEMP register
float temp = cmd[0]<<8|cmd[1];
printf("Temperatue = %.2f C\r\n", temp/256);
}
}
A program futási eredménye az alábbi ábrán látható. A 7. sortól kezdve a hőmérőt melegítettük.


13. ábra: A 06_i2c_tcn75 program futási eredménye