Az I2C kommunikációs csatorna használata
A fejezet tartalma:- Az I2C kommunikációs csatorna
- A FRDM-KL25Z kártya I2C perifériái
- Az I2C modulokhoz rendelhető kivezetések konfigurálása
- Az I2C modulok engedélyezése
- Az I2C modulok regiszterkészlete
- I2Cx_F - az I2C adatsebesség beállítása
- Az I2Cx_C1 vezérlő regiszter
- Az I2Cx_A1 címregiszter
- Az I2Cx_D adatregiszter
- Az I2Cx_S állapotjelző regiszter
- Az adatküldés folyamata
- Az I2C modul konfigurálásának lépései
- Mintaprogramok
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:
- Csak két vonalat használ: az SCL vonal a szinkronjel (órajel), az SDA vonal pedig az adat jel.
- Kommunikáció csak akkor indítható, ha a busz nem foglalt, hanem tétlen (idle). A tétlen állapot jellemzője, hogy egy STOP feltételt követően mind az SDA, mind az SCL vonal magas szinten van (nincs aktív kimenet).
- Soros, 8-bit-es, kétirányú adatforgalom zajlik, melynek maximális sebessége normál üzemmódban (standard mode) 100 kbit/s, gyors üzemmódban (fast mode) pedig 400 kbit/s. Az újabb eszközök között van olyan, amelyik ún. nagy sebességű (high-speed mode) módban is képes működni, maximum 3,4 mbit/s sebességgel. A high-speed üzemmódnál annyi a különbség, hogy nincs arbitráció (a multi-master módú arbitrációt a nagysebességű mód használata előtt gyors üzemmódban kell lejátszani). A másik különbség: a nagysebességű master eszköz aktív, áramgenerátoros felhúzást használ, hogy a jelek felfutása gyorsabb legyen, mint a hagyományos passzív felhúzásnál.
- Mindegyik csatlakoztatott eszköz címezhető egy egyedi címmel.
- Az egy buszra csatlakoztatható eszközök számát csak a busz kapacitása korlátozza, ami maximum 400 pF lehet.
- A kommunikáló eszközök között egyszerű master/slave kapcsolat áll fenn. Mindig a master kezdeményezi és vezérli a kommunikációt (a master szolgáltatja az órajelet) és a master képes adóként és vevőként is üzemelni.
- Bonyolultabb esetekben több master is csatlakozhat a buszra. Az MKL25Z128VLK4 mikrovezérlő I2C alrendszere ütközés detektálással és arbitrációval rendelkezik az adatvesztés elhárítására, ha két vagy több master kezdene egyidejűleg küldeni.
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:
- Az adatküldés kezdetét a START feltétel jelzi (magas órajelszint mellett az adatvonal magas állapotból alacsonyra vált).
- 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.
- 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:- Kompatibilisel az I2C busz specifikációjával
- Multimaster környezetben is használhatók
- Az adatsebesség szoftveresen beállítható (64 fokozat választható)
- Szoftveresen választható nyugtázó bit küldés
- Programmegszakításos adatküldés/fogadás
- Arbitráció elvesztése esetén programmegszakítás és automatikus átváltás master módból slave módba
- Hívási cím egyezésekor programmegszakítás
- START és STOP feltétel generálás, illetve felismerés
- Ismételt START feltétel generálás, illetve felismerés
- Nyugtázó jel küldése, illetve felismerése
- Busz foglaltságának felismerése
- Általános hívás felismerése
- 10-bites kiterjesztett címzés lehetősége
- Kompatibilis a System Management Bus (SMBus) v2 specifikációjával
- Programozható bemeneti zavarszűrés
- Kisfogyasztású módból ébresztés slave címegyezés detektálásakor
- Slave címtartomány korlátozás támogatása
- DMA adatátvitel támogatása
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) |
- 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.
- 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!
- 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 |
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:- Engedélyezzük az I2C modul órajelét a SIM_SCGC4 regiszter tartalmának módosításával!
- Tiltsuk le az I2C modult a konfigurálás tartamára az I2Cx_C1 regiszter nullázásával!
- Állítsuk be a kívánt adatátviteli sebességhez tartozó osztási arányt az I2Cx_F regiszterben!
- Engedélyezzük az I2C modult az I2Cx_C1 regiszter IICEN bitjének '1'-be állításával!
- Az I2Cx_S regiszter BUSY bitjének lekérdezésével várakozzunk, amíg az I2C busz szabaddá válik!
- Az I2Cx_C1 regiszter TX bitjét állítsuk '1'-be az adatküldéshez!
- 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!
- Írjuk a slave 7-bites címét és az adatküldést jelző 0 bitet az I2Cx_D adatregiszterbe!
- Az I2Cx_S regiszter IICIF bitjének lekérdezésével várakozzunk, amíg az átvitel le nem zajlik.
- Töröljük az IICIF bitet '1' beírásával!
- 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!
- 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!
- Írjuk a kiküldeni kívánt adatot az I2Cx_D adatregiszterbe!
- Az I2Cx_S regiszter IICIF bitjének lekérdezésével várakozzunk, amíg az átvitel le nem zajlik.
- Töröljük az IICIF bitet '1' beírásával!
- 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!
- 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:
- FRDM-KL25Z kártya
- DS1337 vagy kompatibilis RTC, az alábbi bekötés szerint csatlakoztatva
RTC modul |
FRDM-KL25Z |
---|---|
GND |
GND |
VCC |
3,3 V |
SDA |
PTE0 (I2C1_SDA) |
SCL |
PTE1 (I2C1_SCL) |
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:
- Az olvasó fantázíájára bízzuk, hogy a főprogram jelzett helyén kiíratással, vagy egy LED kigyújtásával jelezzük a felhasználónak az átviteli hibát.
- A programban most a szokásosnál rövidebb időzítéseket használunk, ezér a késleltető függvény belső for ciklusában lecsökkentettük a ciklusok számát.
- Az I2C busz sebességét 100 kHz-re konfiguráltuk.
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:- slaveAddr - a slave eszköz címe (0x68)
- memAddr - a legelső adathoz tartozó regiszter sorszáma (esetünkben 0)
- byteCount - a kiküldendő adatok darabszáma (esetünkben 7)
- data - mutató a programban tárolt adattáblázathoz (esetünkben a timeDateToSet[] tömb kezdőcíme)
- FRDM-KL25Z kártya
- DS1337 vagy kompatibilis RTC, az alábbi bekötés szerint csatlakoztatva
RTC modul |
FRDM-KL25Z |
---|---|
GND |
GND |
VCC |
3,3 V |
SDA |
PTE0 (I2C1_SDA) |
SCL |
PTE1 (I2C1_SCL) |
- Rendszer órajelek: CPU órajel 48 MHz, buszfrekvencia 24 MHz
- I2C busz órajel frekvencia: 100 kHz
- 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!
- 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.
/* 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:- slaveAddr - a slave eszköz címe (0x68)
- memAddr - a legelső adathoz tartozó regiszter sorszáma (esetünkben 0)
- byteCount - a beolvasandó adatok darabszáma (esetünkben 7)
- data - mutató az adatok eltárolására kijelölt adattáblázathoz (esetünkben a timeDateToSet[] tömb kezdőcíme)
9. ábra: A DS1337 RTC modul regisztereinek tömbösített olvasása
Hardver követelmények:
- FRDM-KL25Z kártya
- DS1337 vagy kompatibilis RTC, az alábbi bekötés szerint csatlakoztatva
RTC modul |
FRDM-KL25Z |
---|---|
GND |
GND |
VCC |
3,3 V |
SDA |
PTE0 (I2C1_SDA) |
SCL |
PTE1 (I2C1_SCL) |
- Rendszer órajelek: CPU órajel 48 MHz, buszfrekvencia 24 MHz
- I2C busz órajel frekvencia: 100 kHz
/* 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: - 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!
- 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:
- FRDM-KL25Z kártya
- DS1337 vagy kompatibilis RTC, az alábbi bekötés szerint csatlakoztatva
RTC modul |
FRDM-KL25Z |
---|---|
GND |
GND |
VCC |
3,3 V |
SDA |
PTE0 (I2C1_SDA) |
SCL |
PTE1 (I2C1_SCL) |
- Rendszer órajelek: CPU órajel 48 MHz, buszfrekvencia 24 MHz
- I2C busz órajel frekvencia: 100 kHz
- UART0 beállítások: 9600 bps, 8 bit, nincs paritás, 1 stop bit, nincs adatfolyam-vezérlés
- A programban az Aszinkron soros kommunikáció (UART) című fejezetben bemutatott módon irányítottuk át a printf() kimenetét a standard kimenetre (UART0). A soros portot megszakításos módban és 64 bájtos FIFO bufferrel kiegészítve használjuk.
- Az UART_config() meghívásakor paraméterként meg kell adni az adatsebességet. Például: UART_config(9600);
- A programba egy egyszerű órabeállítási lehetőséget is beépítettünk: a soros porton érkező RTC beállítási parancs formátuma TYYMMDDHHMMSS alakú, a parancsokat a processSerialCommand() függvény segítségével értelmezzük. Például T170309170000 a 2017. március 9. 17:00 időpontot állítja be.
- Ebben a programban a Day of Week (hét napja) adatot nem állítjuk be és nem használjuk fel.
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.- Az MMA8451Q gyorsulásmérő az I2C0 modulhoz kapcsolódik a PTE24 (SCL) és a PTE25 (SDA) kivezetéseken keresztül. Mindkét kivezetésnél a megfelelő portvezérlő regiszter MUX bitcsoportjában az Alt5 módot kell választani az I2C0 funkció eléréséhez.
- Ebben a programban a 14-bites X, Y, Z adatokból csak a legfelső 8 bitet olvassuk ki és használjuk fel.
- Az UART0 soros portot az Aszinkron soros kommunikáció (UART) fejezetben bemutatott stdio átirányítással használjuk.
- Az MMA8451Q gyorsulásmérő bonyolult működésének és felépítésének részleteire itt nem kívánunk kitérni, ehelyett az MMA8451Q adatlap önálló tanulmányozására buzdítjuk az olvasót.
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)
- A "Who am I" regiszter kiolvasásával az eszköz jelenlétét és típusát ellenőrizhetjük (0x1A olvasható ki)
- A vezérlő regiszter legalső bitjét 1-be kell állítani a működéshez (felébresztés standby módból).
- Az Out_X_MSB, Out_X_MSB és Out_Z_MSB regiszterekből a gyorsulás három dimenziós vektorának komponenseit (pontosabban azok legfelső 8 bitjét) olvashatjuk ki, előjeles formában.
- FRDM-KL25Z kártya a ráépített gyorsulásmérővel
- Rendszer órajelek: CPU órajel 48 MHz, buszfrekvencia 24 MHz
- I2C busz órajel frekvencia: 100 kHz
- UART0 beállítások: 9600 bps, 8 bit, nincs paritás, 1 stop bit, nincs adatfolyam-vezérlés
/* 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: - Ebben a programban is az Aszinkron soros kommunikáció (UART) című fejezetben bemutatott módon irányítottuk át a printf() kimenetét a standard kimenetre. A soros portot megszakításos módban és 64 bájtos FIFO bufferrel kiegészítve használjuk.
- Az UART_config() meghívásakor paraméterként meg kell adni az adatsebességet. Például: UART_config(9600);
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