Az I2C kommunikációs csatorna használata

A fejezet tartalma:

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 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.


1. á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 további részleteivel csak a konkrét eszközök kapcsán foglalkozunk majd.

A FRDM-KL25Z kártya I2C perifériái

Az MKL25Z128VLK4 mikrovezérlő két I2C modult tartalmaz (I2C1 és I2C2) amelyek az alábbi tulajdonságokkal rendelkeznek:

Az I2C modulokhoz rendelhető kivezetések konfigurálása

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. Zárójelben a megfeleló portvezérlő regiszter MUX bitcsoportjába írandó módválasztó kódot is megadtuk. Itt jegyezzük meg, hogy a Mazidi könyv 9-8.  táblázatában óriási a kavarodás, hibás adatok szerepelnek benne!

1. táblázat: Az I2C modulokhoz rendelhető kivezetések
I2C modul
I2Cx_SDA
I2Cx_SCL
I2C0
PTE25(5), PTB1(2), PTB3(2), PTC9(2)
PTE24(5), PTB0(2), PTB2(2), PTC8(2)
I2C1
PTE0(6), PTA4(2), PTC2(2), PTC11(2)
PTE1(6), PTC1(2), PTC10(2)
Megjegyzések:
  1. Az Arduino kompatibils bekötéshez elsődlegesen a táblázatban kövéren szedett kivezetések tartoznak: I2C1_SDA=PTE0, I2C1_SCL=PTE1. A másik Arduino kompatibilis kivezetés, az  A4 (SDA) és A5 (SCL) analóg bemenetek használata a táblázatban szereplő PTC2, PTC1 kivezetéspárnak felel meg.
  2. A PTE25, PTE24 kivezetéspár csak a FRDM-KL25Z kártyára szerelt MMA8451Q gyorsulásmérő IC számára elérhető, a csatlakozókra nincsenek kivezetve!
  3. Az I2C1_SCL jel elvileg a PTA3 kivezetéshez is hozzárendelhető, de ez hadverütközéshez vezetne, mivel a PTA3 láb egyúttal a programozó és nyomkövető SWDIO kivezetése, s a kártyára épített Open_SDA hibavadász eszközhöz csatlakozik..

Az I2C modulok engedélyezése

Az I2C modulok működését a többi soros perifériához hasonlóan a Rendszer-integrációs Modul SIM_SCGC4 regiszterében lehet engedélyezni, a megfelelő bit '1'-be állításával.


2. ábra: A SIM_SCGC4 regiszter bitkiosztása

Az I2C modulok regiszterkészlete

Az I2Cx modulok azonos regiszterkészlettel rendelkeznek. Az I2C0 modul báziscíme 0x4006_6000, az I2C1 modulé pedig 0x4006 7000. Az alábbi táblázatban az I2Cx regisztereknek (ahol x = 0, vagy 1) csak a báziscímekhez képesti eltolási címét (ofszet cím) adtuk meg.

2. táblázat: Az I2Cx modulok regiszterkészlete
Ofszet cím
Regiszter neve, funkciója
Méret
Elérés
Reset
0x0000
I2Cx_A1 address register 1. (1. cím regiszter)
8 bit
R/W
0x00
0x0001
I2Cx_F frequency divider register(frekvencia osztó reg.) 8 bit
R/W
0x00
0x0002
I2Cx_C1 control register 1. (1. vezérlő regiszter) 8 bit
R/W
0x00
0x0003
I2Cx_S status register (állapotjelző regiszter) 8 bit
R/W
0x80
0x0004
I2Cx_D data register (adatregiszter) 8 bit
R/W
0x00
0x0005
I2Cx_C2 control register 2. (2. vezérlő regiszter) 8 bit R/W 0x00
0x0006
I2Cx_FLT Input Glicth filter (bemeneti zavarszűrő reg.) 8 bit R/W 0x00
0x0007
I2Cx_RA range address register (tartomány cím regiszter) 8 bit
R/W
0x00
0x0008 I2Cx_SMB SMB bus control and status register (SMB busz vezérlő és állapotjelző regiszter) 8 bit R/W 0x00
0x0009 I2Cx_A2 address register 2. (SMB busz cím regiszter) 8 bit R/W 0xC2
0x000A I2Cx_SLTH SCL Low timeout high register (SCL lehúzás időtúllépés időzítés magas helyiértékű bitjei) 8 bit R/W 0x00
0x000B I2Cx_SLTL SCL Low timeout low register (SCL lehúzás időtúllépés időzítés alacsony helyiértékű bitjei) 8 bit R/W 0x00

I2Cx_F - az I2C adatsebesség beállítása

Az I2C kommunikáció adatátviteli sebességét mindig a master határozza meg (az SCL órajelet a master állítja elő). Az MKL25Z128VLK4 mikrovezérlő I2Cx moduljai órajelének forrása a buszfrekvencia, amelyből leosztással állíthatjuk elő a kívánt frekvenciájú SCL órajelet. A leosztási arányt az I2Cx_F regiszterben adhatjuk meg, egy előosztási és egy osztási arány kiválasztásával, az alábbi képlet szerint:

Adatátviteli bitráta = buszfrekvencia / ( 2MULT x SCL osztó)


3. ábra: Az I2Cx_F regiszter bitkiosztása

MULT - ez a bitcsoport az előosztási arányt szabja meg (00: 1/1, 01: 1/2,  10: /1/4, 11: fenntartott kód)
ICR - a leosztási arányt szabja meg az alábbi táblázat szerint.

3. táblázat: Leosztási arányok az ICR érték függvényében
ICR
(Hex)
SCL
osztó
ICR
(Hex)
SCL
osztó
ICR
(Hex)
SCL
osztó
ICR
(Hex)
SCL
osztó
0
20
10
48
20
160
30
640
1
22
11
56
21
192
31
768
2
24
12
64
22
224
32
896
3
26
13
72
23
256
33
1024
4
28
14
80
24
288
34
1152
5
30
15
88
25
320
35
1280
6
34
16
104
26
384
36
1536
7
40
17
128
27
480
37
1920
8
28
18
80
28
320
38
1280
9
32
19
96
29
384
39
1536
A
36
1A
112
2A
448
3A
1792
B
40
1B
128
2B
512
3B
2048
C
44
1C
144
2C
576
3C
2304
D
48
1D
160
2D
640
3D
2560
E
56
1E
192
2E
768
3E
3072
F
68
1F
240
2F
960
3F
3840
1. példa: 24 MHz-es buszfrekvencia esetén a 100 kHz-es I2C bitráta beállításához 240-szeres leosztás kell. Ez a fenti táblázat alapján MULT = 0 és SCR = 0x1F értékekkel, végeredményben az I2Cx_F = 0x1F; értékadással állítható be.

2. példa: 24 MHz-es buszfrekvencia esetén a 400 kHz-es I2C bitráta beállításához 60-szoros leosztás kell. Ez a fenti táblázat alapján MULT = 1 és SCR = 0x05 értékekkel, végeredményben tehát az I2Cx_F = 0x45; értékadással állítható be.

Az I2Cx_C1 vezérlő regiszter

Az I2Cx_C1 vezérlő regiszter központi szerepet játszik az I2C modul kezelésében, hiszen ebben letilthatjuk/engedélyezhetjük a modul működését, kiválaszthatjuk a master/slave módot, illetve az adatáramlás irányát (adatküldés vagy -fogadás).


4. ábra: Az I2Cx_C1 vezérlő regiszter bitkiosztása

IICEN - Az I2C modul engedélyezése (0: letiltás, 1: engedélyezés)
IICIE - Az I2C modul megszakításainak engedélyezése (0: tiltás, 1: engedélyezés)
MST - Master mód kiválasztása (0: slave mód, 1: master mód)
TX - Adatküldés mód választás (0: adatfogadás mód, 1: adatküldés mód)
TXAK - Nyugtázó bit választása (0: ACK pozitív nyugtázás küldése, 1: NAK negatív nyugtázás küldése)
RSTA - Úraindítás (ismételt START) küldése (1: írása új START feltételt indít)
WUEN - Felébresztés engedélyezése energiatakarékos módból (0: tiltás, 1: engedélyezés)
DMAEN - DMA adatátvitel engedélyezése (0: tiltás, 1: engedélyezés)

Megjegyzés: Master mód kiválasztásakor, amikor az MST bit 0-ból 1-re változik), automatikusan START feltétel generálódik. Amikor a master módból kilépünk (az MST bit 1-ből 0-ra változik), automatikusan STOP feltétel generálódik.

Az I2Cx_A1 címregiszter

Slave módban ez a regiszter tartalmazza a 7-bites címzéshez az eszköz címét (10-bites címzéshez a kiegészítő címbiteket az I2Cx_C2 regiszter alsó három bitje tartalmazza).

5. ábra: Az I2Cx_A1 regiszter bitkiosztása


Az I2Cx_D adatregiszter

Küldéskor ebbe a regiszterbe írjuk a kiküldendő 8-bites adatot. Vételnél ebből a regiszterből olvashatjuk ki a beérkezett 8-bites adatot.

Az I2Cx_S állapotjelző regiszter

Az I2Cx_S állapotjelző regiszter bitjei az I2C modul, mint állapotgép pillanatnyi állapotáról értesít bennünket.

6. ábra: Az I2Cx_S állapotjelző regiszter bitkiosztása

TCF - Adatküldés vége jelzőbit (0: adatküldés folyamatban, 1: adatküldés befejeződött)
         Megjegyzés: a jelzőbit az adatregiszter írásával (vételi módban az olvasásával) törlődik.
IAAS - "Slave eszközként megszólítva" állapotjelző (0: nincs megszólítva a mikrovezérlő, 1: egy külső eszköz slave módban megszólította a mikrovezérlőt)

BUSY - Busz foglaltság jelzése (0: az I2C busz tétlen, 1: az I2C busz foglalt)
ARBL - Arbitráció vesztés (0: normál busz művelet, 1: arbitráció vesztés történt)
          Megjegyzés: szoftveresen törlendő,  '1' beírásával.
RAM - Címtartomány egyezés (0: nincs megszólítva, 1: slave-ként megszólítva)
SRW - Slave írás/olvasás vezérlés (0: a mikrovezérlő adatot kell, hogy fogadjon, 1: a mikrovezérlőtől adatot kérnek)
IICIF - I2C megszakításjelző bit (0: nincs megszakítási kérelem, 1: függőben levő megszakítási kérelem)
RXAK - Nyugtázás vétele (0: nyugtázás érkezett, 1: negatív nyugtázás (NAK) érkezett)

Az adatküldés folyamata

Egy START feltétel után az I2C busz foglalt (BUSY) állapotba kerül, amíg egy STOP feltétel nem érkezik. A busz fogalalt állapotában csak akkor küldhetünk adatokat, ha mi generáltuk a START feltételt, és a slave címének kiküldése az arbitráció elvesztése nélkül sikerült. Ezért fontos a BUSY jelzőbit vizsgálata, mielőtt START feltétel generálásával új tranzakciót indítanánk. Hasonlóan fontos az ARBL bit vizsgálata a slave címének kiküldése után, mielőtt további adatokat küldenénk.

Az adatregiszter feltöltése után az adatátvitel elindul és az I2Cx_S regiszter TCF jelzőbitje '0'-ra vált és ebben az állapotban marad, amíg az adatbájt küldése be nem fejeződik. A program az I2Cx_S regiszter IICIF jelzőbitjének vizsgálatával értesül róla, hogy az adatküldés  folyamata mikor ért véget. Ekkor az I2Cx_S regiszter RXAK jelzőbitjének vizsgálatával értesülhetünk róla, hogy a slave eszköz nyugtázta-e az adat vételét. Ha az adatküldés végén az RXAK bit '1'-be áll, akkor hiba történt (vagy negatív nyugtázás érkezett).

Az I2C modul konfigurálásának lépései

A GPIO kivezetések I2C módba történő konfigurálása után az alábbi lépéseket követve konfigurálhatjuk az I2C modult, hogy adatot küldjön egy slave eszköznek:
  1. Engedélyezzük az I2C modul órajelét a SIM_SCGC4 regiszter tartalmának módosításával!
  2. Tiltsuk le az I2C modult a konfigurálás tartamára az I2Cx_C1 regiszter nullázásával!
  3. Állítsuk be a kívánt adatátviteli sebességhez tartozó osztási arányt az I2Cx_F regiszterben!
  4. Engedélyezzük az I2C modult az I2Cx_C1 regiszter IICEN bitjének '1'-be állításával!
Egy adatbájt küldése a slave eszköznek:
  1. Az I2Cx_S regiszter BUSY bitjének lekérdezésével várakozzunk, amíg az I2C busz szabaddá válik!
  2. Az I2Cx_C1 regiszter TX bitjét állítsuk '1'-be az adatküldéshez!
  3. Az I2Cx_C1 regiszter MST bitjét állítsuk '1'-be a master mód beállításához és a START feltétel generálásához!
  4. Írjuk a slave 7-bites címét és az adatküldést jelző 0 bitet az I2Cx_D adatregiszterbe!
  5. Az I2Cx_S regiszter IICIF bitjének lekérdezésével várakozzunk, amíg az átvitel le nem zajlik.
  6. Töröljük az IICIF bitet '1' beírásával!
  7. Vizsgáljuk meg az I2Cx_S regiszter ARBL bitjét, hogy nem vesztettük-e el az arbitrációt! Ha igen, akkor töröljük az ARBL bitet '1' beírásával és szakítsuk félbe a tranzakciót!
  8. Ellenőrizzük az I2Cx_S regiszter RXAK bitjét, hogy a slave küldött-e pozitív nyugtázást! Ha nem, akkor szakítsuk félbe a tranzakciót!
  9. Írjuk a kiküldeni kívánt adatot az I2Cx_D adatregiszterbe!
  10. Az I2Cx_S regiszter IICIF bitjének lekérdezésével várakozzunk, amíg az átvitel le nem zajlik.
  11. Töröljük az IICIF bitet '1' beírásával!
  12. Ellenőrizzük az I2Cx_S regiszter RXAK bitjét, hogy a slave küldött-e pozitív nyugtázást! Ha nem, akkor szakítsuk félbe a tranzakciót!
  13. Töröljük az I2Cx_C1 regiszter TX és MST bitjeit a STOP feltétel generálásához!

Mintaprogramok

Program9_1: Adatküldés az I2C buszon

Az alábbi programban egy DS1337 Real-time órával kommunikálunk az I2C buszon.  A DS1337 továbbfejlesztett változata a közismert DS1307 RTC-nek:  a tápfeszültség 1.8 - 5.5 V közötti érték lehet,  az adatátviteli sebesség pedig max. 100 kHz. A programban nem használtuk ki a DS1337 RTV speciális tulajdonságait, így a program más Dallas/Maxim RTC-kkel is használható, mint pl. DS1307 vagy DS3231.

A programban az időt és a dátumot (az RTC elsö hét regiszterét)  bájtonként külön-külön tranzakcióval töltjük fel, előre definiált értékekkel.  A programban definiált adatokkal 2009. október 19. hétfö, 16:58:55-ra állítjuk be az órát.

Megjegyzés: Ez a program nem tartalmaz kiíratást, így csak kiegészítő eszközökkel (pl. Bus Pirate, Arduino ArduPirate firmware-rel, esetleg a program9_3 mintapélda futtatása Keil MDK5 környzetben, nyomkövető módban) tudjuk ellenőrizni a programfutás eredményét.
 
Hardver követelmények:
RTC modul
FRDM-KL25Z
GND
GND
VCC
3,3 V
SDA
PTE0 (I2C1_SDA)
SCL
PTE1 (I2C1_SCL)
Megjegyzés: Az E-bay kínálatában kapható, kártyára szerelt DS1337 (vagy vele kompatibilis) modulok már tartalmazzák az I2C busz vonalainak felhúzó ellenállásait (általában 4,7 k). Ha mi fejlesztünk I2C eszközt, akkor ne feledkezzünk meg a szükséges felhúzásokról!

Az RTC modul az első hét regiszterében tárolja a futó idő értékét, bátonként BCD kódolásban. A Day érték a hét napjának sorszámát tartalmazza.


7. ábra: A futó idő tárolása a DS1337 eszköz első hét regiszterében

Az RTC írásakor az eszköz megcímzése (az eszköz I2C cím = 0x68) után egy regiszter címet kell kiküldeni, majd ezt követően egy vagy több adatbájtot küldhetünk. Több adatbájt küldése esetén a cím automatikusan növekszik minden adatbájt vétele után. a Program9_1 mintapéldában tranzakciónként csak egy adatbájtot küldünk, a Program9_2 mintapéldában pedig megmutatjuk, hogy hogyan írhatjuk át az első hét regisztert egyetlen tranzakcióval.


8. ábra: A DS1337 RTC modul regisztereinek írása

1. lista: A Program9_1/main.c program listája
/* program9_1: I2C adatküldés egy DS1337 RTC-nek
*
* A programban egy DS1337 Real-time órával kommunikálunk az I2C buszon.
* A DS1337 továbbfejlesztett változata a közismert DS1307 RTC-nek.
* A tápfeszültség 1.8 - 5.5 V közötti érték lehet,
* az adatátviteli sebesség pedig max. 100 kHz.
*
* A programban az időt és a dátumot (az RTC elsö hét regiszterét)
* bájtonként külön tranzakcióval töltjük fel, előre definiált értékekkel.
* A beírás 2009. október 19. hétfö, 16:58:55-ra állítja be az órát.
*
* Hardver követelmények:
* - FRDM-KL25Z kártya
* - DS1337 vagy kompatibilis RTC, az alábbi bekötéssel
*
* FRDM-KL25Z DS1337
* ----------------------------
* 3.3V ----> VCC
* GND ----- GND
* PTE1 ----> SCL
* PTE0 <---> SDA
*
* A program forrása: Mazidi et al., Freescale ARM Cortex-M Embedded Programming
* http://www.microdigitaled.com/ARM/Freescale_ARM/Code/Freescale_ARM_codes.htm
*
* A programon apróbb módosításokat hajtottunk végre:
* - A rendszer órajelek beállítása: CPU 48 MHz, Bus 24 MHz
* - A nagyobb buszfrekvencia miatt az I2C bitráta beállításánál 0x1C helyett 0x1F kell
* - Kijavítottuk a főprogram for ciklusában a hibás indexelést
*/

#include "MKL25Z4.h"

#define SLAVE_ADDR 0x68 // 1101 000
#define ERR_NONE 0 // Hibátlan, nyugtázott átvitel
#define ERR_NO_ACK 0x01 // Negatív nyugtázás
#define ERR_ARB_LOST 0x02 // Arbitráció vesztés
#define ERR_BUS_BUSY 0x03 // Busz foglaltság

void I2C1_init(void);
int I2C1_byteWrite(unsigned char slaveAddr, unsigned char memAddr, unsigned char data);
void delayUs(int n);

int main(void) {
unsigned char timeDateToSet[] = {0x55, 0x58, 0x16, 0x01, 0x19, 0x10, 0x09};
int rv;
int i;

I2C1_init();
for (i = 0; i < 7; i++) {
rv = I2C1_byteWrite(SLAVE_ADDR, i, timeDateToSet[i]);
if (rv != ERR_NONE)
for(;;) ; // Hiba kezelés jöhet ide!
}
for (;;) {
}
}

//---------------------------------------
// I2C1 és a kivezetések inicializálása
//---------------------------------------
void I2C1_init(void) {
SIM->SCGC4 |= 0x80; // I2C1 engedélyezése
SIM->SCGC5 |= 0x2000; // PORT E engedélyezése
PORTE->PCR[1] = 0x0600; // PTE1 legyen I2C1 SCL kivezetés
PORTE->PCR[0] = 0x0600; // PTE0 legyen I2C1 SDA kivezetés

I2C1->C1 = 0; // I2C1 letiltása konfiguráláshoz
I2C1->S = 2; // IICIF törlése
I2C1->F = 0x1F; // 100 kHz bitráta választása
I2C1->C1 = 0x80; // I2C1 engedélyezése
}

//---------------------------------------
// Egy adat kiküldése a slave eszköznek
// Tranzakció: S-(scím+w)-ACK-rcím-ACK-adat-ACK-P
//---------------------------------------
int I2C1_byteWrite(unsigned char slaveAddr, unsigned char memAddr, unsigned char data) {
int retry = 1000;
while (I2C1->S & 0x20) { // Várunk, míg a busz szabaddá válik
if (--retry <= 0) // Legfeljebb 1000-szer próbálkozunk
return ERR_BUS_BUSY;
delayUs(100);
}

I2C1->C1 |= 0x10; // TX beírása
I2C1->C1 |= 0x20; // MST beírása, START generálása
I2C1->D = slaveAddr << 1; // Slave cím és W bit küldése
while(!(I2C1->S & 0x02)); // Átvitel végére várunk
I2C1->S |= 0x02; // IICIF törlése
if (I2C1->S & 0x10) { // Arbitráció elvesztése esetén
I2C1->S |= 0x10; // ARBL bit törlése
return ERR_ARB_LOST; // Kilépés hibakóddal
}
if (I2C1->S & 0x01) // Negatív nyugtázás esetén
return ERR_NO_ACK; // Kilépés hibakóddal
I2C1->D = memAddr; // Regisztercím küldése
while(!(I2C1->S & 0x02)); // Átvitel végére várunk
I2C1->S |= 0x02; // IICIF törlése
if (I2C1->S & 0x01) // Negatív nyugtázás esetén
return ERR_NO_ACK; // Kilépés hibakóddal
I2C1->D = data; // Adat küldése
while(!(I2C1->S & 0x02)); // Átvitel végére várunk
I2C1->S |= 0x02; // IICIF törlése
if (I2C1->S & 0x01) // Negatív nyugtázás esetén
return ERR_NO_ACK; // Kilépés hibakóddal
I2C1->C1 &= ~0x30; // TX és MST törlése, STOP generálás
return ERR_NONE;
}


//---------------------------------------
// Késleltető függvény 48 MHz órajelhez
//---------------------------------------
void delayUs(int n) {
int i, j;
for(i = 0 ; i < n; i++)
for (j = 0; j < 8; j++);
}

Megjegyzések:

Program9_2: Tömbösített adatküldés az I2C buszon

Ez a program csupán abban különbözik az előzőtől, hogy egyetlen tranzakcióban töltjük fel mind a hét adatbájtot. Az ehhez definiált I2C1_burstWrite() függvény paraméterei:
Hardver követelmények:
RTC modul
FRDM-KL25Z
GND
GND
VCC
3,3 V
SDA
PTE0 (I2C1_SDA)
SCL
PTE1 (I2C1_SCL)
Hardver beállítások:
Megjegyzések:
  1. A kártyára szerelt DS1337 (vagy vele kompatibilis) modulok már tartalmazzák az I2C busz vonalainak felhúzó ellenállását. Ha mi fejlesztünk I2C eszközt, akkor ne feledkezzünk meg a felhúzásokról!
  2. Ez a program sem tartalmaz kiíratást, így csak kiegészítő eszközökkel (pl. Bus Pirate, Arduino ArduPirate firmware-rel, esetleg a program9_3 mintapélda futtatása Keil MDK5 környzetben, nyomkövető módban) tudjuk ellenőrizni a programfutás eredményét.
2. lista: A Program9_2/main.c program listája
/* program9_2: I2C tömbösített adatküldés egy DS1337 RTC-nek
*
* A programban egy DS1337 Real-time órával kommunikálunk az I2C buszon.
* A DS1337 továbbfejlesztett változata a közismert DS1307 RTC-nek.
* A tápfeszültség 1.8 - 5.5 V közötti érték lehet,
* az adatátviteli sebesség pedig max. 100 kHz.
*
* A programban az időt és a dátumot (az RTC elsö hét regiszterét)
* tömbösítve, egyetlen tranzakcióval töltjük fel, előre definiált értékekkel.
* A beírás 2009. október 19. hétfö, 16:58:55-ra állítja be az órát.
*
* Hardver követelmények:
* - FRDM-KL25Z kártya
* - DS1337 vagy kompatibilis RTC, az alábbi bekötéssel
*
* FRDM-KL25Z DS1337
* ----------------------------
* 3.3V ----> VCC
* GND ----- GND
* PTE1 ----> SCL
* PTE0 <---> SDA
*
* A program forrása: Mazidi et al., Freescale ARM Cortex-M Embedded Programming
* http://www.microdigitaled.com/ARM/Freescale_ARM/Code/Freescale_ARM_codes.htm
*
* A programon apróbb módosításokat hajtottunk végre:
* - A rendszer órajelek beállítása: CPU 48 MHz, Bus 24 MHz
* - A nagyobb buszfrekvencia miatt az I2C bitráta beállításánál 0x1C helyett 0x1F kell
* - Kihagytuk a fölöslóleges count változót
*/

#include "MKL25Z4.h"

#define SLAVE_ADDR 0x68 // 1101 000
#define ERR_NONE 0 // Hibátlan, nyugtázott átvitel
#define ERR_NO_ACK 0x01 // Negatív nyugtázás
#define ERR_ARB_LOST 0x02 // Arbitráció vesztés
#define ERR_BUS_BUSY 0x03 // Busz foglaltság

void I2C1_init(void);
int I2C1_burstWrite(unsigned char slaveAddr, unsigned char memAddr,
int byteCount, unsigned char* data);
void delayUs(int n);

int main(void) {
unsigned char timeDateToSet[] = {0x55, 0x58, 0x16, 0x01, 0x19, 0x10, 0x09};
int rv;

I2C1_init();
rv = I2C1_burstWrite(SLAVE_ADDR,0,7,timeDateToSet);
if(rv != ERR_NONE) {
for(;;); // Hibakezelés jöhet ide!
}
for (;;) {
}
}

//---------------------------------------
// I2C1 és a kivezetések inicializálása
//---------------------------------------
void I2C1_init(void) {
SIM->SCGC4 |= 0x80; // I2C1 engedélyezése
SIM->SCGC5 |= 0x2000; // PORT E engedélyezése
PORTE->PCR[1] = 0x0600; // PTE1 legyen I2C1 SCL kivezetés
PORTE->PCR[0] = 0x0600; // PTE0 legyen I2C1 SDA kivezetés

I2C1->C1 = 0; // I2C1 letiltása konfiguráláshoz
I2C1->S = 2; // IICIF törlése
I2C1->F = 0x1F; // 100 kHz bitráta választása
I2C1->C1 = 0x80; // I2C1 engedélyezése
}

//---------------------------------------
// Tömbösített adatcsomag kiküldése a slave eszköznek
// Tranzakció: S-(scím+w)-ACK-rcím-ACK-adat-ACK-...-adat-ACK-P
//---------------------------------------
int I2C1_burstWrite(unsigned char slaveAddr, unsigned char memAddr,
int byteCount, unsigned char* data) {
int retry = 1000;
while (I2C1->S & 0x20) { // Várunk, míg a busz szabaddá válik
if (--retry <= 0) // Legfeljebb 1000-szer próbálkozunk
return ERR_BUS_BUSY;
delayUs(100);
}

I2C1->C1 |= 0x10; // TX beírása
I2C1->C1 |= 0x20; // MST beírása, START generálása
I2C1->D = slaveAddr << 1; // Slave cím és W bit küldése
while(!(I2C1->S & 0x02)); // Átvitel végére várunk
I2C1->S |= 0x02; // IICIF törlése
if (I2C1->S & 0x10) { // Arbitráció elvesztése esetén
I2C1->S |= 0x10; // ARBL bit törlése
return ERR_ARB_LOST; // Kilépés hibakóddal
}
if (I2C1->S & 0x01) // Negatív nyugtázás esetén
return ERR_NO_ACK; // Kilépés hibakóddal
I2C1->D = memAddr; // Regisztercím küldése
while(!(I2C1->S & 0x02)); // Átvitel végére várunk
I2C1->S |= 0x02; // IICIF törlése
if (I2C1->S & 0x01) // Negatív nyugtázás esetén
return ERR_NO_ACK; // Kilépés hibakóddal
while(byteCount-- > 0) {
I2C1->D = *data++; // Adat küldése
while(!(I2C1->S & 0x02)); // Átvitel végére várunk
I2C1->S |= 0x02; // IICIF törlése
if (I2C1->S & 0x01) // Negatív nyugtázás esetén
return ERR_NO_ACK; // Kilépés hibakóddal
}
I2C1->C1 &= ~0x30; // TX és MST törlése, STOP generálás
return ERR_NONE;
}


//---------------------------------------
// Késleltető függvény 48 MHz órajelhez
//---------------------------------------
void delayUs(int n) {
int i, j;
for(i = 0 ; i < n; i++)
for (j = 0; j < 8; j++);
}

Program9_3: Tömbösített beolvasás az I2C buszon

Az előző program fordítottja, amelyben egyetlen tranzakcióban olvassuk ki az RTC első hét regiszterét (a futó időt). Az ehhez definiált I2C1_burstRead() függvény paraméterei:
Az I2C1_burstRead() függvény nem egyszerű tükörképe az előző programban definiált I2C1_burstWrite() függvénynek, mivel az I2C buszon az adatbeolvasás jóval komplikáltabb műveletsor, mint az adatküldés. A bonyodalmat az okozza, hogy az olvasást is adatküldéssel kell kezdeni: ki kell küldeni a slave címét, s pozitív nyugtázás után többnyire meg kell adni azt a címet, ahonnan a kiolvasást kezdeni akarjuk. Ezután adatáramlási irányt kell váltani  - ezt célszerű RESTART feltétel generálásával (vagyis STOP nélkül, egy újabb START feltétellel) véghezvinni, hogy ne veszítsük el az arbitrációt az I2C buszon. Ezután újra ki kell küldeni a slave eszköz címét, de az R/W biten most az olvasásnak megfelelő '1' áll. Ezután következhet az adatbájtok beolvasása, melyeknek beérkezését az utolsó bájt kivételével pozitív nyugtázással jeleznünk kell. Az utolsó adatbájt esetében negatív nyugtázást küldünk, ezzel jelezzük a slave eszköznek, vogy vége a tranzakciónak. Egy több-bájtos adatbeolvasási tranzakció menete az alábbi ábrán látható. A satírozott téglalapokban látható adatokat és nyugtázásokat a master (azaz a mikrovezérlő) küldi, a feltételeket (START, RESTART, STOP) a master generálja. A fehér hátterű négyszögekben látható adatok és nyogtázásokat pedig a slave küldi.


9. ábra: A DS1337 RTC modul regisztereinek tömbösített olvasása

Hardver követelmények:
RTC modul
FRDM-KL25Z
GND
GND
VCC
3,3 V
SDA
PTE0 (I2C1_SDA)
SCL
PTE1 (I2C1_SCL)
Hardver beállítások:
3. lista: A Program9_3/main.c program listája
/* program9_3: I2C tömbösített adatbeolvasás egy DS1337 RTC-ről
*
* A programban egy DS1337 Real-time órával kommunikálunk az I2C buszon.
* A DS1337 továbbfejlesztett változata a közismert DS1307 RTC-nek.
* A tápfeszültség 1.8 - 5.5 V közötti érték lehet,
* az adatátviteli sebesség pedig max. 100 kHz.
*
* A programban az időt és a dátumot (az RTC elsö hét regiszterét)
* tömbösítve, egyetlen tranzakcióval olvassuk be az eszközről.
*
* Hardver követelmények:
* - FRDM-KL25Z kártya
* - DS1337 vagy kompatibilis RTC, az alábbi bekötéssel
*
* FRDM-KL25Z DS1337
* ----------------------------
* 3.3V ----> VCC
* GND ----- GND
* PTE1 ----> SCL
* PTE0 <---> SDA
*
* A program forrása: Mazidi et al., Freescale ARM Cortex-M Embedded Programming
* http://www.microdigitaled.com/ARM/Freescale_ARM/Code/Freescale_ARM_codes.htm
*
* A programon az alábbi apróbb módosításokat hajtottunk végre:
* - A rendszer órajelek beállítása: CPU 48 MHz, Bus 24 MHz
* - A nagyobb buszfrekvencia miatt az I2C bitráta beállításánál 0x1C helyett 0x1F kell
* - Kihagytuk a fölöslóleges count változót
*/

#include "MKL25Z4.h"

#define SLAVE_ADDR 0x68 // 1101 000
#define ERR_NONE 0 // Hibátlan, nyugtázott átvitel
#define ERR_NO_ACK 0x01 // Negatív nyugtázás
#define ERR_ARB_LOST 0x02 // Arbitráció vesztés
#define ERR_BUS_BUSY 0x03 // Busz foglaltság

void I2C1_init(void);
int I2C1_burstRead(unsigned char slaveAddr, unsigned char memAddr, int byteCount,
unsigned char* data);
void delayUs(int n);

int main(void) {
unsigned char timeDateReadBack[7];
int rv;

I2C1_init();
rv = I2C1_burstRead(SLAVE_ADDR,0,7,timeDateReadBack);
if(rv != ERR_NONE) {
for(;;); // Hibakezelés jöhet ide!
}
for (;;) {
}
}

//---------------------------------------
// I2C1 és a kivezetések inicializálása
//---------------------------------------
void I2C1_init(void) {
SIM->SCGC4 |= 0x80; // I2C1 engedélyezése
SIM->SCGC5 |= 0x2000; // PORT E engedélyezése
PORTE->PCR[1] = 0x0600; // PTE1 legyen I2C1 SCL kivezetés
PORTE->PCR[0] = 0x0600; // PTE0 legyen I2C1 SDA kivezetés

I2C1->C1 = 0; // I2C1 letiltása konfiguráláshoz
I2C1->S = 2; // IICIF törlése
I2C1->F = 0x1F; // 100 kHz bitráta választása
I2C1->C1 = 0x80; // I2C1 engedélyezése
}

//---------------------------------------
// Tömbösített adatcsomag beolvasása a slave eszközről
// Tranzakció: S-(scím+w)-ACK-rcím-ACK-R-(scím+r)-ACK-adat-ack-...-adat-nak-P
//---------------------------------------
int I2C1_burstRead(unsigned char slaveAddr, unsigned char memAddr, int byteCount,
unsigned char* data) {
int retry = 1000;
volatile unsigned char dummy;
while (I2C1->S & 0x20) { // Várunk, míg a busz szabaddá válik
if (--retry <= 0) // Legfeljebb 1000-szer próbálkozunk
return ERR_BUS_BUSY;
delayUs(100);
}

I2C1->C1 |= 0x10; // TX beírása
I2C1->C1 |= 0x20; // MST beírása, START generálása
I2C1->D = slaveAddr << 1; // Slave cím és W bit küldése
while(!(I2C1->S & 0x02)); // Átvitel végére várunk
I2C1->S |= 0x02; // IICIF törlése
if (I2C1->S & 0x10) { // Arbitráció elvesztése esetén
I2C1->S |= 0x10; // ARBL bit törlése
return ERR_ARB_LOST; // Kilépés hibakóddal
}
if (I2C1->S & 0x01) // Negatív nyugtázás esetén
return ERR_NO_ACK; // Kilépés hibakóddal

I2C1->D = memAddr; // Regisztercím küldése
while(!(I2C1->S & 0x02)); // Átvitel végére várunk
I2C1->S |= 0x02; // IICIF törlése
if (I2C1->S & 0x01) // Negatív nyugtázás esetén
return ERR_NO_ACK; // Kilépés hibakóddal

I2C1->C1 |= 0x04; // RESTART feltétel generálás
I2C1->D = (slaveAddr<<1) | 1; // Slave cím és R bit küldése
while(!(I2C1->S & 0x02)); // Átvitel végére várunk
I2C1->S |= 0x02; // IICIF törlése
if (I2C1->S & 0x01) // Negatív nyugtázás esetén
return ERR_NO_ACK; // Kilépés hibakóddal

I2C1->C1 &= ~0x18; // TX bit törlése, ACK küldés előkészítése
if (byteCount == 1)
I2C1->C1 |= 0x08; // NAK küldés előkészítése
dummy = I2C1->D; // kamu olvasás, és új ciklus indítása

while(byteCount > 0) {
if (byteCount == 1)
I2C1->C1 |= 0x08; // NAK küldés előkészítése
while(!(I2C1->S & 0x02)); // Átvitel végére várunk
I2C1->S |= 0x02; // IICIF törlése
if (byteCount == 1) {
I2C1->C1 &= ~0x20; // STOP feltétel generálása
}
*data++ = I2C1->D; // Vett adat kiolvasása
byteCount--;
}
return ERR_NONE;
}


//---------------------------------------
// Késleltető függvény 48 MHz órajelhez
//---------------------------------------
void delayUs(int n) {
int i, j;
for(i = 0 ; i < n; i++)
for (j = 0; j < 8; j++);
}
Megjegyzések:
  1. A kártyára szerelt DS1337 (vagy vele kompatibilis) modulok már tartalmazzák az I2C busz vonalainak felhúzó ellenállását. Ha mi fejlesztünk I2C eszközt, akkor ne feledkezzünk meg a felhúzásokról!
  2. Ez a program sem tartalmaz kiíratást, így csak kiegészítő eszközökkel (pl. Bus Pirate, Arduino ArduPirate firmware-rel, esetleg a program9_3 mintapélda futtatása Keil MDK5 környezetben, nyomkövető módban) tudjuk ellenőrizni a programfutás eredményét.

10. ábra: Program9_3 futási eredménye (Keil MDK5 nyomkövetési módban)

Program9_4: Real time óra kiolvasása és az idő kiíratása

A korábbiaknál egy picivel "hasznosabb" programot mutatunk be, amelyben az előző programban bemutatott I2C1_burstRead() függvény segítségével kiolvassuk egy DS1337 (vagy vele kompatibilis) Real Time órát, s az Aszinkron soros kommunikáció (UART) fejezetben bemutatott stdio átirányítással az UART0 porton formázottan kiíratjuk az aktuális idő és dátumot. A programban feltételezzük, hogy az RTC-t előzőleg már egy másik programmal beállítottuk.

A kiíratásokhoz a kiolvasott adatokat binárisan kódolt decimális (BCD) ábrázolásból vissza kell alakítanunk "normális" számmá. Ehhez a program elején definiáltunk egy bcdToDec() függvényt.

Hardver követelmények:
RTC modul
FRDM-KL25Z
GND
GND
VCC
3,3 V
SDA
PTE0 (I2C1_SDA)
SCL
PTE1 (I2C1_SCL)
Hardver beállítások:
Megjegyzések:

4. lista: A Program9_4/main.c program listája
/* program9_4: DS1337 RTC kiolvasás és formázott kiíratás
*
* Egy DS1337 vagy kompatibils Real Time órát rendszeresen kiolvasunk
* és a futó időt formázottan kiíratjuk év-hó-nap óra:perc:másodperc formában.
*
* A programban az időt és a dátumot (az RTC elsö hét regiszterét)
* tömbösítve, egyetlen tranzakcióval olvassuk be az eszközről.
*
* Hardver követelmények:
* - FRDM-KL25Z kártya
* - DS1337 vagy kompatibilis RTC, az alábbi bekötéssel
*
* FRDM-KL25Z DS1337
* ----------------------------
* 3.3V ----> VCC
* GND ----- GND
* PTE1 ----> SCL
* PTE0 <---> SDA
*
* Hardver beállítások:
* --------------------
* Rendszer órajelek: CPU 48 MHz, busz 24 MHz
* UART0 beállítások: 9600 bps, 8bit, nincs paritás, 1 stop
* I2C1 buszfrekvencia: 100 kHz
*
*/


#include <MKL25Z4.H>
#include <stdio.h>
#include "uart.h"


#define SLAVE_ADDR 0x68 // 1101 000
#define ERR_NONE 0 // Hibátlan, nyugtázott átvitel
#define ERR_NO_ACK 0x01 // Negatív nyugtázás
#define ERR_ARB_LOST 0x02 // Arbitráció vesztés
#define ERR_BUS_BUSY 0x03 // Busz foglaltság


void I2C1_init(void);
int I2C1_burstRead(uint8_t slaveAddr, uint8_t memAddr, int byteCount, uint8_t* data);
int I2C1_burstWrite(uint8_t slaveAddr, uint8_t memAddr, int byteCount, uint8_t* data);
void processSerialCommand(void);
void delayUs(int n);

//--- Convert decimal numbers to BCD -----------------------
uint8_t decToBcd(uint8_t val) {
return( (val/10*16) + (val%10) );
}

//--- Convert BCD numbers to decimal ----------------------
int bcdToDec(uint8_t val) {
return (int)((val/16*10) + (val%16));
}

int main(void) {
int hours=0,minutes=0,seconds=0;
int year=0, month=0, dayOfMonth=0;
// int dayOfWeek=1;
uint8_t tbuf[7];
int rv;

SystemCoreClockUpdate();
UART_config(9600);
__enable_irq(); // Megszakítások globális engedélyezése
printf("\r\nProgram9_4 a FRDM-KL25Z kártyán\r\n");
I2C1_init();
while(1){
if(UART_available()) processSerialCommand();
rv = I2C1_burstRead(SLAVE_ADDR,0,7,tbuf);
if(rv != ERR_NONE) {
for(;;); // Hibakezelés jöhet ide!
}
seconds = bcdToDec(tbuf[0]) &0x7f;
minutes = bcdToDec(tbuf[1]);
hours = bcdToDec(tbuf[2]) & 0x3f;
// dayOfWeek = bcdToDec(tbuf[3]);
dayOfMonth = bcdToDec(tbuf[4]);
month = bcdToDec(tbuf[5]);
year = bcdToDec(tbuf[6]);
printf("%d-%02d-%02d ",year+2000,month,dayOfMonth);
printf(" %02d:%02d:%02d\n",hours,minutes,seconds);
delayUs(1000000);
}
}

//-----------------------------------------------
// I2C1 és a kivezetések inicializálása
//-----------------------------------------------
void I2C1_init(void) {
SIM->SCGC4 |= 0x80; // I2C1 engedélyezése
SIM->SCGC5 |= 0x2000; // PORT E engedélyezése
PORTE->PCR[1] = 0x0600; // PTE1 legyen I2C1 SCL kivezetés
PORTE->PCR[0] = 0x0600; // PTE0 legyen I2C1 SDA kivezetés

I2C1->C1 = 0; // I2C1 letiltása konfiguráláshoz
I2C1->S = 2; // IICIF törlése
I2C1->F = 0x1F; // 100 kHz bitráta választása
I2C1->C1 = 0x80; // I2C1 engedélyezése
}

//-----------------------------------------------
// Tömbösített adatcsomag beolvasása a slave eszközrol
// Tranzakció: S-(scím+w)-ACK-rcím-ACK-R-(scím+r)-ACK-adat-ack-...-adat-nak-P
//-----------------------------------------------
int I2C1_burstRead(uint8_t slaveAddr, uint8_t memAddr, int byteCount, uint8_t* data) {
int retry = 1000;
volatile uint8_t dummy;
while (I2C1->S & 0x20) { // Várunk, míg a busz szabaddá válik
if (--retry <= 0) // Legfeljebb 1000-szer próbálkozunk
return ERR_BUS_BUSY;
delayUs(100);
}

I2C1->C1 |= 0x10; // TX beírása
I2C1->C1 |= 0x20; // MST beírása, START generálása
I2C1->D = slaveAddr << 1; // Slave cím és W bit küldése
while(!(I2C1->S & 0x02)); // Átvitel végére várunk
I2C1->S |= 0x02; // IICIF törlése
if (I2C1->S & 0x10) { // Arbitráció elvesztése esetén
I2C1->S |= 0x10; // ARBL bit törlése
return ERR_ARB_LOST; // Kilépés hibakóddal
}
if (I2C1->S & 0x01) // Negatív nyugtázás esetén
return ERR_NO_ACK; // Kilépés hibakóddal

I2C1->D = memAddr; // Regisztercím küldése
while(!(I2C1->S & 0x02)); // Átvitel végére várunk
I2C1->S |= 0x02; // IICIF törlése
if (I2C1->S & 0x01) // Negatív nyugtázás esetén
return ERR_NO_ACK; // Kilépés hibakóddal

I2C1->C1 |= 0x04; // RESTART feltétel generálás
I2C1->D = (slaveAddr<<1) | 1; // Slave cím és R bit küldése
while(!(I2C1->S & 0x02)); // Átvitel végére várunk
I2C1->S |= 0x02; // IICIF törlése
if (I2C1->S & 0x01) // Negatív nyugtázás esetén
return ERR_NO_ACK; // Kilépés hibakóddal

I2C1->C1 &= ~0x18; // TX bit törlése, ACK küldés elokészítése
if (byteCount == 1)
I2C1->C1 |= 0x08; // NAK küldés előkészítése
dummy = I2C1->D; // kamu olvasás, és új ciklus indítása

while(byteCount > 0) {
if (byteCount == 1)
I2C1->C1 |= 0x08; // NAK küldés előkészítése
while(!(I2C1->S & 0x02)); // Átvitel végére várunk
I2C1->S |= 0x02; // IICIF törlése
if (byteCount == 1) {
I2C1->C1 &= ~0x20; // STOP feltétel generálása
}
*data++ = I2C1->D; // Vett adat kiolvasása
byteCount--;
}
return ERR_NONE;
}

//-----------------------------------------------
// Tömbösített adatcsomag kiküldése a slave eszköznek
// Tranzakció: S-(scím+w)-ACK-rcím-ACK-adat-ACK-...-adat-ACK-P
//-----------------------------------------------
int I2C1_burstWrite(uint8_t slaveAddr, uint8_t memAddr, int byteCount, uint8_t* data) {
int retry = 1000;
while (I2C1->S & 0x20) { // Várunk, míg a busz szabaddá válik
if (--retry <= 0) // Legfeljebb 1000-szer próbálkozunk
return ERR_BUS_BUSY;
delayUs(100);
}

I2C1->C1 |= 0x10; // TX beírása
I2C1->C1 |= 0x20; // MST beírása, START generálása
I2C1->D = slaveAddr << 1; // Slave cím és W bit küldése
while(!(I2C1->S & 0x02)); // Átvitel végére várunk
I2C1->S |= 0x02; // IICIF törlése
if (I2C1->S & 0x10) { // Arbitráció elvesztése esetén
I2C1->S |= 0x10; // ARBL bit törlése
return ERR_ARB_LOST; // Kilépés hibakóddal
}
if (I2C1->S & 0x01) // Negatív nyugtázás esetén
return ERR_NO_ACK; // Kilépés hibakóddal
I2C1->D = memAddr; // Regisztercím küldése
while(!(I2C1->S & 0x02)); // Átvitel végére várunk
I2C1->S |= 0x02; // IICIF törlése
if (I2C1->S & 0x01) // Negatív nyugtázás esetén
return ERR_NO_ACK; // Kilépés hibakóddal
while(byteCount-- > 0) {
I2C1->D = *data++; // Adat küldése
while(!(I2C1->S & 0x02)); // Átvitel végére várunk
I2C1->S |= 0x02; // IICIF törlése
if (I2C1->S & 0x01) // Negatív nyugtázás esetén
return ERR_NO_ACK; // Kilépés hibakóddal
}
I2C1->C1 &= ~0x30; // TX és MST törlése, STOP generálás
return ERR_NONE;
}

//-----------------------------------------------
// Késleltető függvény 48 MHz órajelhez
//-----------------------------------------------
void delayUs(int n) {
int i, j;
for(i = 0 ; i < n; i++)
for (j = 0; j < 8; j++);
}

//-----------------------------------------------
// Soros porti parancsok feldolgozása
// RTC beállítás parancs formátuma: TYYMMDDHHMMSS
// Example: 2012 Oct 21 1:23pm is T121021132300
//-----------------------------------------------
void processSerialCommand() {
uint8_t y, m, d, hh, mm, ss, rv;
uint8_t sbuf[7];
char c = UART_getc();
switch(c){
case 'T':
delayUs(100000); // Wait for all data to arrive
if( UART_available() >= 12 ){ // process only if all expected data is available
// Parse incomming 12 ASCII charaters into y,m,d,hh,mm,ss
// no error checking for numeric values in YYMDDHHMMSS fields, so be carefull!
c = UART_getc(); y = c - '0';
c = UART_getc(); y = 10*y; y += c-'0';
c = UART_getc(); m = c - '0';
c = UART_getc(); m = 10*m; m += c-'0';
c = UART_getc(); d = c - '0';
c = UART_getc(); d = 10*d; d += c-'0';
c = UART_getc(); hh = c - '0';
c = UART_getc(); hh = 10*hh; hh += c-'0';
c = UART_getc(); mm = c - '0';
c = UART_getc(); mm = 10*mm; mm += c-'0';
c = UART_getc(); ss = c - '0';
c = UART_getc(); ss = 10*ss; ss += c-'0';
printf("\nRTC Set to: ");
sbuf[0] = decToBcd(ss);
sbuf[1] = decToBcd(mm);
sbuf[2] = decToBcd(hh);
sbuf[3] = 0;
sbuf[4] = decToBcd(d);
sbuf[5] = decToBcd(m);
sbuf[6] = decToBcd(y);
rv = I2C1_burstWrite(SLAVE_ADDR,0,7,sbuf);
if(rv != ERR_NONE) {
for(;;); // Hibakezelés jöhet ide!
}
while(UART_available()) UART_getc(); // clear serial buffer
}
break;
}
}

A program futási eredménye az alábbi ábrán látható. A kiíratások közötti idő valamivel rövidebbre sikerült, mint 1 másodperc, emiatt időnként egy-egy időérték kétszer is kiírásra kerül.

11. ábra: Program9_4 futási eredménye


Program9_5: A kártyára épített gyorsulásmérő kiolvasása

Ebben a programban a FRDM-KL25Z kártyára épített MMA8451Q háromtengelyű  gyorsulásmérőt olvassuk ki, s a kiolvasott adatokat  kiíratjuk az UART0 porton.


12. ábra: Az MMA8451Q gyorsulásmérő bekötése a FRDM-Kl25Z kártyán

Ahogy a fenti kapcsolási vázlaton is látható, az R22 ellenállás gyárilag nincs beültetve (DNP jelentése: do not populate), így az eszköz címének legalsó bitjel (SA0) magas szintre van húzva. Az eszköcm így 0011101, azaz 0x1D. Az MMA8451Q gyorsulásmérő IC számtalan regisztere közül mi csak az alábbiakkal foglalkozunk:

#define REG_WHO_AM_I      0x0D   // Eszköz azonosító, gyárilag 0x1A-ra állítva
#define REG_CTRL_REG_1 0x2A // Vezérlő regiszter. Legalsó bitjét 1-re kell állítanunk
#define REG_OUT_X_MSB 0x01 // A mért gyorsulás X komponense (legfelső 8 bit)
#define REG_OUT_Y_MSB 0x03 // A mért gyorsulás Y komponense (legfelső 8 bit)
#define REG_OUT_Z_MSB 0x05 // A mért gyorsulás Z komponense (legfelső 8 bit)
Hardver követelmények:
Hardver beállítások:
5. lista: A Program9_5/main.c program listája
/* Program9_5: A MMA8451Q gyorsulásmérő kiolvasása
*
* A FRDM-KL25Z kártyára épített MMA8451Q háromtengelyű
* gyorsulásmérőt olvassuk ki, s a kiolvasott adatokat
* kiíratjuk az UART0 porton.
*
* Az MMA8451Q IC az I2C0 modulhoz kapcsolódik a PTE24 (SCL)
* és a PTE25 (SDA) kivezetéseken keresztül. Ebben a programban
* a 14-bites X, Y, Z adatokból csak a legfeleső 8 bitet olvassuk
* ki és használjuk fel.
*
* Az UART0 soros portot a Lab04/uart_retarget példaprogramban
* bemutatott módon használjuk.
*
* Hardver beállítások:
* --------------------
* Rendszer órajelek: CPU 48 MHz, busz 24 MHz
* UART0 beállítások: 9600 bps, 8bit, nincs paritás, 1 stop
* I2C0 buszfrekvencia: 100 kHz
*/


#include <MKL25Z4.H>
#include <stdio.h>
#include "uart.h"
// System runs at 48MHz
// Baud rate 9600, 8 bit data, no parity, 1 stop bit

#define SLAVE_ADDR 0x1D // 0011101
#define ERR_NONE 0 // Hibátlan, nyugtázott átvitel
#define ERR_NO_ACK 0x01 // Negatív nyugtázás
#define ERR_ARB_LOST 0x02 // Arbitráció vesztés
#define ERR_BUS_BUSY 0x03 // Busz foglaltság

#define REG_WHO_AM_I 0x0D
#define REG_CTRL_REG_1 0x2A
#define REG_OUT_X_MSB 0x01
#define REG_OUT_Y_MSB 0x03
#define REG_OUT_Z_MSB 0x05

void acc_init(void);
int acc_write(uint8_t regAddr, uint8_t data);
int acc_read(uint8_t regAddr, uint8_t* data);
void delayUs(int n);


int main(void) {
int rv,xx,yy,zz;
uint8_t id,x,y,z;
SystemCoreClockUpdate();
UART_config(9600);
acc_init();
rv = acc_read(REG_WHO_AM_I, &id);
printf("Device ID: %02x\n",id);
printf("\r\nWelcome to FRDM-KL25Z board!\r\n");
while(1){
rv = acc_read(REG_OUT_X_MSB, &x);
rv += acc_read(REG_OUT_Y_MSB, &y);
rv += acc_read(REG_OUT_Z_MSB, &z);
if(rv) {
printf("*** Hiba az I2C0 buszon!\n");
}
else {
xx = (x>127)? x - 256 : x;
yy = (y>127)? y - 256 : y;
zz = (z>127)? z - 256 : z;
printf("position = %4d %4d %4d\n",xx,yy,zz);
}
delayUs(2000000);
}
}

//---------------------------------------------------
// I2C0, PTE24, PTE25 és az MMA8451 inicializálása
//---------------------------------------------------
void acc_init(void) {
SIM->SCGC4 |= 0x40; // I2C0 engedélyezése
SIM->SCGC5 |= 0x2000; // PORT E engedélyezése
PORTE->PCR[24] = 0x0500; // PTE1 legyen I2C0 SCL kivezetés
PORTE->PCR[25] = 0x0500; // PTE0 legyen I2C0 SDA kivezetés

I2C0->C1 = 0; // I2C0 letiltása konfiguráláshoz
I2C0->S = 2; // IICIF törlése
I2C0->F = 0x1F; // 100 kHz bitráta választása
I2C0->C1 = 0x80; // I2C0 engedélyezése
acc_write(REG_CTRL_REG_1,0x01); // MMA aktív módba kapcsolása
}


//-----------------------------------------------
// Egy adatbájt kiküldése a slave eszközre
// Tranzakció: S-(scím+w)-ACK-rcím-ACK-adat-ACK-P
//-----------------------------------------------
int acc_write(uint8_t regAddr, uint8_t data) {
int retry = 1000;
volatile uint8_t dummy;
while (I2C0->S & 0x20) { // Várunk, míg a busz szabaddá válik
if (--retry <= 0) // Legfeljebb 1000-szer próbálkozunk
return ERR_BUS_BUSY;
delayUs(100);
}
I2C0->C1 |= 0x10; // TX beírása
I2C0->C1 |= 0x20; // MST beírása, START generálása
I2C0->D = SLAVE_ADDR << 1; // Slave cím és W bit küldése
while(!(I2C0->S & 0x02)); // Átvitel végére várunk
I2C0->S |= 0x02; // IICIF törlése
if (I2C0->S & 0x10) { // Arbitráció elvesztése esetén
I2C0->S |= 0x10; // ARBL bit törlése
return ERR_ARB_LOST; // Kilépés hibakóddal
}
if (I2C0->S & 0x01) // Negatív nyugtázás esetén
return ERR_NO_ACK; // Kilépés hibakóddal

I2C0->D = regAddr; // Regisztercím küldése
while(!(I2C0->S & 0x02)); // Átvitel végére várunk
I2C0->S |= 0x02; // IICIF törlése
if (I2C0->S & 0x01) // Negatív nyugtázás esetén
return ERR_NO_ACK; // Kilépés hibakóddal

I2C0->D = data; // Adatbájt küldése
while(!(I2C0->S & 0x02)); // Átvitel végére várunk
I2C0->S |= 0x02; // IICIF törlése
if (I2C0->S & 0x01) // Negatív nyugtázás esetén
return ERR_NO_ACK; // Kilépés hibakóddal
I2C0->C1 &= ~0x30; // TX és MST törlése, STOP generálás
return ERR_NONE;
}


//-----------------------------------------------------------
// Egy adatbájt beolvasása a slave eszközröl
// Tranzakció: S-(scím+w)-ACK-rcím-ACK-R-(scím+r)-adat-nak-P
//-----------------------------------------------------------
int acc_read(uint8_t regAddr, uint8_t* data) {
int retry = 1000;
volatile uint8_t dummy;
while (I2C0->S & 0x20) { // Várunk, míg a busz szabaddá válik
if (--retry <= 0) // Legfeljebb 1000-szer próbálkozunk
return ERR_BUS_BUSY;
delayUs(100);
}

I2C0->C1 |= 0x10; // TX beírása
I2C0->C1 |= 0x20; // MST beírása, START generálása
I2C0->D = SLAVE_ADDR << 1; // Slave cím és W bit küldése
while(!(I2C0->S & 0x02)); // Átvitel végére várunk
I2C0->S |= 0x02; // IICIF törlése
if (I2C0->S & 0x10) { // Arbitráció elvesztése esetén
I2C0->S |= 0x10; // ARBL bit törlése
return ERR_ARB_LOST; // Kilépés hibakóddal
}
if (I2C0->S & 0x01) // Negatív nyugtázás esetén
return ERR_NO_ACK; // Kilépés hibakóddal

I2C0->D = regAddr; // Regisztercím küldése
while(!(I2C0->S & 0x02)); // Átvitel végére várunk
I2C0->S |= 0x02; // IICIF törlése
if (I2C0->S & 0x01) // Negatív nyugtázás esetén
return ERR_NO_ACK; // Kilépés hibakóddal

I2C0->C1 |= 0x04; // RESTART feltétel generálás
I2C0->D = (SLAVE_ADDR<<1) | 1; // Slave cím és R bit küldése
while(!(I2C0->S & 0x02)); // Átvitel végére várunk
I2C0->S |= 0x02; // IICIF törlése
if (I2C0->S & 0x01) // Negatív nyugtázás esetén
return ERR_NO_ACK; // Kilépés hibakóddal

I2C0->C1 &= ~0x18; // TX bit törlése, ACK küldés elokészítése
I2C0->C1 |= 0x08; // NAK küldés elokészítése
dummy = I2C0->D; // kamu olvasás, és új ciklus indítása
while(!(I2C0->S & 0x02)); // Átvitel végére várunk
I2C0->S |= 0x02; // IICIF törlése
I2C0->C1 &= ~0x20; // STOP feltétel generálása
*data++ = I2C0->D; // Vett adat kiolvasása
return ERR_NONE;
}


//---------------------------------------
// Késlelteto függvény 48 MHz órajelhez
//---------------------------------------
void delayUs(int n) {
int i, j;
for(i = 0 ; i < n; i++)
for (j = 0; j < 8; j++);
}
Megjegyzések:

A program futásának eredménye az alábbi ábrán látható. A kártya (közel) vízszintes felületen feküdt, az X és Y komponensek ennek megfelelően nulla körüli értékek, csak Z irányban van számottevő komponens (1 g).

13. ábra: Program9_5 futásának eredménye