Szinkron soros perifériák: I2C és SPI 

[Szerzői jogok]

A fejezet tartalma:
A PIC24 mikrovezérlők fejlett kommunikációs képességekkel rendelkeznek. Az univerzális aszinkron soros kommunikáción (USART) kívül (amivel egy szintillesztőn keresztül akár a számítógép RS-232 portjára is csatlakozhatunk) megtalálhatók a szinkron soros kommunikációs (SPI vagy I2C) alegységek, s több mikrovezérlő típus, így pl. a PIC24HJ128GP502 mikrovezérlő is) CAN vagy ECAN alegységgel is rendelkezik. Ebben a fejezetben csak az I2C és SPI szinkron soros perifériákkal foglalkozunk, amelyeknél az adat biteket egy, a kommunikációs buszon megjelenő órajel szinkronizálja. 

Az I2C kommunikációs csatorna

Az I2C (Inter-Integrated Circuit = integrált áramkörök közötti) kétvezetékes soros kommunikációs sínt a Philips fejlesztette ki. Az 1990-es évek közepétől számos versenytárs is fejlesztett ki I2C kompatibilis eszközöket. A PIC24H mikrovezérlők 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 néhány jellemzője:

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.
 
1. ábra: az I2C soros busz

A PIC-kwik projektben használt mikrovezérlőknél az I2C egység nem tartozik az áthelyezhető perifériák közé, kivezetései kötöttek. Ahogy az alábbi ábra mutatja, az SCL1 órajel a 17. lábon, az SDA1 adatvonal pedig a 18. lábon van kivezetve. Mindkét kivezetés tolerálja az 5 V-os jelszintet, ami egyszerű lehetőséget kínál az 5 V-os eszközökkel történő kommunikációra: ha a felhúzó ellenállásokat +5 V-ra kötjük, akkor  az I2C buszon szintkonverter nélkül összeköthetjük a 3,3 V-os tápfeszültségű mikrovezérlőnket egy 5 V-os I2C eszközzel.

Az I2C vonalak más lábakon történő kivezetésre egy alternatívánk van csupán: az FPOR konfigurációs regiszter ALTI2C bitjének törlésével átirányíthatjuk a kimeneteket az ASCL1, ASDA1 kivezetésekre (15. és 14. lábak). Ezt a lehetőséget nem fogjuk használni.

2. ábra:  A PIC24HJ128GP502 mikrovezérlő lábkiosztása
(az 5V-os jelszintet toleráló lábakat színezés jelzi)

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


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

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

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

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

Az I2C egység felépítése és működése

Az I2C alegység felépítését, működését, s az egyes üzemmódokban az adatvonalon zajló kommunikációt a PIC24H Family Reference Manual” részletesen ismerteti. Az I2C alegység vázlatos felépítését és regisztereit az alábbi ábrán láthatjuk. A regiszterek elnevezésében az 'x' az I2C egység sorszáma. A PIC-kwik projektben használt PIC24HJ128GP502 és dsPIC33FJ128GP802 mikrovezérlők csak egyetlen I2C egységet tartalmaznak, ezért 'x' helyére mindenütt '1'-et kell érteni és írni. Az Explorer 16 demó kártya mikrovezérlői (PIC24FJ128GA010 vagy dsPIC33FJ256GP710) viszont két-két I2C egységet tartalmaznak, ott tehát 'x' helyén '1' vagy '2' is állhat.


4. ábra: Az I2C egység blokkvázlata

Az I2C egység hét olyan regiszterrel rendelkezik, amihez a felhasználó hozzáfér (a fenti ábrán fehér téglalapokkal jelöltük a regisztereket). Ezek a regiszterek a következők:
Ahogy a 3. ábrán is látható, van egy nyolcadik regiszter is (I2CxRSR), ebbe érkezik a vett adat. Ez a regiszter azonban nem érhető el (a regiszter nem kapcsolódik az adatbuszhoz). Amikor egy teljes bájtnyi adat megérkezett, akkor az I2CxRSR regiszter tartalma átmásolódik az I2CxRCV regiszterbe, s az adat onnan olvasható ki. Végeredményben az I2CxRSR regiszter és az I2CxRCV regiszter kettős bufferelésű vevőt alkotnak, tehát a következő bájt vétele az előző bájt kiolvasásával párhuzamosan folyhat. Ha a következő bájt vétele is befejeződik, mielőtt az előző bájtot kiolvasnánk, túlcsordulás történik, ami '1'-be állítja az I2CxSTAT állapotjelző regiszter I2COV bitjét. Túlcsordulás esetén az I2CxRSR regiszter tartalma elvész, s a vétel csak a következő START/RESTART vagy START/STOP feltétel detektálása után folytatódik. Ha az I2COV állapotjelzőt töröljük, a vétel normálisan folytatódik. Ha viszont nem töröljük az I2COV jelzőbitet, az I2C egység NAK választ küld.

Az I2C egység a felsorolt regisztereken kívül címdetektáló áramkört, START és STOP feltételt detektáló áramkört,  vezérlő áramkört, valamint ütemjel (baud rate) generátort tartalmaz.

Az I2CxCON regiszter

Az I2CxCON regiszter vezérli az I2C egység működését. Az egyes bitek szerepét az alábbiakban foglalhatjuk össze:
bit15 bit14  bit13 bit12 bit11 bit10 bit9 bit8 
I2CEN -- I2CSIDL    SCLREL    IPMIEN     A10M  DISSLW  SMEN 
bit7  bit6  bit5 bit4 bit3 bit2 bit1  bit0 
GCEN STREN ACKDT    ACKEN    RCEN     PEN   RSEN SEN 
I2CEN    - I2Cx engedélyezés (1: engedélyezi az I2Cx modult,  0: Letiltja az I2Cx modult)

I2CSIDL
 - a CPU tétlen módjában (1: az I2C modul leáll,  0: az I2C modul tovább működik)

SCLREL
 - Órajel vonal elengedése (1: Elengedi az SCLx vonalat, 0: Lehúzza az SCLx vonalat)

IPMIEN  - Minden cím elfogadása slave módban  (1: engedélyezve, 0: letiltva)   

A10M    - címzésmód kiválasztása slave módban (1: 10 bites címzés, 0: 7 bites címzés)

DISSLW - Lassú jel felfutás (Slew rate) tiltás (1: lassú felfutás tiltása, 0: lassú felfutás engedélyezése)

SMEN    - SMBUS kompatibilis jelszintek engedélyezése (1: engedélyez, 0: tilt)

GCEN    - általános hívás figyelésének engedélyezése (1: GC interrupt engedélyezve, 0: letiltva)

STREN   - SCLx órajel nyújtásának engedélyezése slave módban (1: engedélyezve, 0: letiltva)
 
ACKDT  - Nyugtázó adatbit (Acknowledge Data). Ha az I2C modul master módban működik, akkor ez a bit vételkor érvényesül. Itt lehet megadni, hogy milyen választ küldjön a modul a slave eszköznek. (1: NACK küldés, 0: ACK küldés)

ACKEN - Nyugtázó szekvencia engedélyezés Ha az I2C modul master módban működik, akkor ez a bit vételkor érvényesül. (1: ACK szekvenciát indít az SDAx és SCLx lábakon és kiküldi az ACKDT bitet. Hardveresen törlődik, amikor a szekvencia befejeződik. 0: Nyugtázó szekvencia nincs folyamatban)

RCEN   - Vétel engedélyezés (Receive Enable) bit, ha a modul I2C master (1: Engedélyezi a vételt. Hardveresen törlődik, amikor a 8 bites vétel befejeződik. 0: Vétel nincs folyamatban)

PEN    - STOP feltétel engedélyezés, ha a modul I2C master (STOP szekvenciát indít az SDAx és SCLx lábakon. Hardveresen törlődik, amikor a szekvencia befejeződik. 0: STOP szekvencia nincs folyamatban)

RSEN  - RESTART feltétel engedélyezés, ha a modul I2C master (RESTART szekvenciát indít az SDAx és SCLx lábakon. Hardveresen törlődik, amikor a szekvencia befejeződik. 0: RESTART szekvencia nincs folyamatban)

SEN   - START feltétel engedélyezés, ha a modul I2C master (START szekvenciát indít az SDAx és SCLx lábakon. Hardveresen törlődik, amikor a szekvencia befejeződik. 0: START szekvencia nincs folyamatban)

Az I2CxSTAT státuszregiszter

Az I2CxSTAT regiszter állapotjelző biteket tartalmaz amelyek az I2C egység állapotáról értesítenek bennünket a működés során. Az egyes bitek jelentését az alábbiakban foglalhatjuk össze:
bit15 bit14  bit13 bit12 bit11 bit10 bit9 bit8 
ACKSTAT TRSTAT --    --    --  BCL  GCSTAT  ADD10 
bit7  bit6  bit5 bit4 bit3 bit2 bit1  bit0 
IWCOL I2COV D_A   P      S       R_W RBF TBF 
ACKSTAT - Nyugtázás státuszbit (1:  NACK érkezett a slave eszköztől, 0: ACK érkezett a slave eszköztől)

TRSTAT
- Adatküldés státusza master módban (1: adatküldés folyamatban,  0: nincs adatküldés)

BCL
 - Multi-master ütközés detektálás (1: ütközés történt master adatküldés közben, 0: nincs ütközés)

GCSTAT
- Általános hívás (General Call) detektálás (1: Általános hívás érkezett, 0: Nincs általános hívás) Hardveresen állítódik be, ha a címdetektáló áramkör általános hívást észlel. STOP feltétel detektálásakor hardveresen törlésre kerül.

ADD10
- 10 bites cím egyezés státusza. Hardveresen állítódik be, ha a címdetektáló áramkör cím egyezést talál a második címbájt érkezésekor. STOP feltétel detektálásakor automatikusan törlődik.(1: 10-bites cím egyezés történt, 0: Nincs 10-bites cím egyezés)

IWCOL
- Ütközés detektálása írásnál (Write Collision Detect). A hardver állítja be, ha az I2C foglalt állapotában próbálunk az I2CxTRN regiszterbe írni. Ezt a bitet szoftveresen kell törölni. (1: Ütközés történt, 0: Nincs ütközés)

I2COV
- Vevőoldali túlcsordulás. A hardver állítja be, ha az I2CxRCV még nem olvastuk ki, de már a következő bájt is megérkezett. Ezt a bitet szoftveresen kell törölni. (1: Túlcsordulás történt, 0: Nincs túlcsordulás)

D_A
- Data/Address (adat/cím) bit (ha I2C slave módban vagyunk). Hardveresen állítódik, csak olvasható bit. (1: Az utoljára vett bájt adat volt, 0: az utoljára vett bájt cím volt)

P
- Stop bit. Akkor állítódik be, ha STOP  feltételt detektált, s akkor törlődik, ha START vagy RESTART feltételt detektált a hardver.  (1: utoljára STOP feltétel volt, 0: utoljára nem STOP feltétel volt)

S
- Start bit. Akkor állítódik be, ha START vagy RESTART feltételt detektált a hardver, s akkor törlődik, ha STOP feltételt detektált.  (1: utoljára START vagy RESTART feltétel volt, 0: utoljára nem START feltétel volt)

R_W
- Írás/Olvasás jelzőbit, amikor slave módban van az I2C modul. Hardveresen állítódik a vett parancsnak megfelelően. (1: Olvasás, 0: Írás)

RBF
- Vevőoldali buffer megtelt (Receive Buffer Full). Hardveresen állítódik be, ha beérkezett egy bájt. Az I2CxRCV regiszter olvasásakor ez a bit automatikusan törlődik (1: Beérkezett egy adatbájt, 0: Nincs beérkezett adat, az I2CxRCV regiszter üres)

TBF
- A küldő buffer foglalt (Transmit Buffer Full).  Automatikusan '1'-be áll, ha az I2CxTRN regiszterbe írunk. Automatikusan törlődik, ha az adatküldés befejeződött. (1: az I2CxTRN regiszter foglalt, 0: az adatküldés befejeződött)

I2C támogatói függvények

A PIC-kwik projekt szoftver segédletében található PIC24 támogatói programkönyvtár természetesen az I2C periféria használatához is segítséget nyújt. Az I2C támogatói függvények két csoportba sorolhatók: az elemi műveletek és az összetett műveletek (tranzakciók).

Az elemi I2C műveletek támogatói függvényei

Az alábbi táblázatban összefoglaltuk az elemi I2C műveletek támogatói függvényeit. Mivel a PIC-kwik projektben használt mikrovezérlők csak egy I2C modult tartalmaznak, az I2C modullal kapcsolatos függvények és regiszterek nevében mindenhol I2C1 név áll.

1. táblázat: Az elemi I2C műveletek támogatói függvényei
I2C támogatói függvény Rövid leírás
void configI2C1(uint16 u16_FkHZ); Inicializálja az I2C modult az u16_FkHZ paraméterrel kHz-ben megadott frekvencián
void startI2C1(void); START feltételt generál az I2C buszon
void rstartI2C1(void); RESTART feltételt generál az I2C buszon
void stopI2C1(void); STOP feltételt generál az I2C buszon
void putI2C1(uint8 u8_val);
Kiküldi az u8_val paraméterrel megadott adatot; szoftveres reset történik, ha NACK válasz érkezett
uint8 putNoAckCheckI2C1(uint8 u8_val);
Kiküldi az u8_val paraméterrel megadott adatot és a válaszul kapott nyugtázó bit értéke lesz a függvény visszatérési érték
uint8 getI2C1(uint8 u8_ack2Send); Egy adatbájt fogadása és nyugtázó bit küldése az u8_ack2Send paraméter megadott értéke szerint (0/1).
Az I2C támogatói függvényeket a PIC24_i2c. h állomány deklarálja (itt definiáljuk a kapcsolódó makrókat is). Az I2C támogatói függvények definíciója pedig a PIC24_i2c.c állományban található.

A configI2C1() függvényben az adatsebesség beállítása az adatlapban található

I2CBRG = FCY/FSCL - FCY/10 000 000  -1

képlet alapján történik, ahol FCY a mikrovezérlő órajelének frekvenciája, FSCL pedig a kívánt adatátviteli frekvencia (általában 100 kHz vagy 400 kHz). A configI2C1() függvény u16_FkHz paramétere tulajdonképpen FSCL értékét adja meg, de kHz egységekben, ezért kell a programban FCY értékét külön osztani 1000-rel. A programban az L azt jelzi, hogy long típusú (32 bites) számértékekről van szó. Az (uint32) pedig típuskonverziót ír elő. A függvényben I2CBRG beállításán kívül csak az I2C1 modul engedélyezése történik.

void configI2C1(uint16 u16_FkHZ) {
  uint32 u32_temp;

  u32_temp = (FCY/1000L)/((uint32) u16_FkHZ);
  u32_temp = u32_temp - FCY/10000000L - 1;
  I2C1BRG = u32_temp;
  I2C1CONbits.I2CEN = 1;
}

A startI2C1() és stopI2C1() függvényeknél a watchdog timer engedélyezésével korlátot szabunk a várakozásnak. Ha nem fejeződik be záros időn belül az esemény, akkor szoftveres RESET történik. A hibát okozó esemény nevét előtte eltároljuk a sz_lastTimeoutError nevű változóban, így újrainduláskor a hiba oka is kiíratható.
void startI2C1(void) {
  uint8 u8_wdtState;

  sz_lastTimeoutError = "I2C1 Start";
  u8_wdtState = _SWDTEN;       //WDT állapotát elmentjük
  _SWDTEN = 1;                 //WDT engedélyezése
  I2C1CONbits.SEN = 1;         //START feltétel generálás
  while (I2C1CONbits.SEN);     //várunk,amíg lezajlik
  _SWDTEN = u8_wdtState;       //WDT visszaállítása eredeti állapotába
  sz_lastTimeoutError = NULL;  //töröljük a hiba ok nevet
}

void stopI2C1(void) {
  uint8 u8_wdtState;
  sz_lastTimeoutError = "I2C1 Stop";
  u8_wdtState = _SWDTEN;       //WDT állapotát elmentjük
  _SWDTEN = 1;                 //WDT engedélyezése
  I2C1CONbits.PEN=1;           //STOP feltétel generálás
  while (I2C1CONbits.PEN);     //várunk,amíg lezajlik
  _SWDTEN = u8_wdtState;       //WDT visszaállítása eredeti állapotába
  sz_lastTimeoutError = NULL;  //töröljük a hiba ok nevet
}

A putI2C1() és getI2C1() függvényeknél is a Watchdog Timer ügyel arra, hogy ha kifutunk az időből, akkor szakítsa félbe a programot. A küldésnél további hibalehetőség az, ha negatív nyugtázás érkezik (pontosabban ha elmarad a pozitív nyugtázás). A putI2C1() függvényben tehát figyelni kell a 9. bitet (ACK bit) is. Negatív nyugtázásnál a PIC24 támogatói programkönyvtár reportError() függvényével jelezzük a hibát. 

A getcI2C1() függvény esetében más a helyzet, hiszen itt mi állítjuk be az ACK bitet. Ennél a függvénynél viszont a szokásosnál több várakozás van: várunk a busz tétlen állapotára, az adatbájt beérkezésére (8 bit), újabb tétlen állapotra (csak ezután küldhetjük az ACK bitet), majd az ACK bit küldésének befejezésére (az ACKEN állapotjelző bit hardveres törlésére)
//-- Egy bájt kiküldése
void putI2C1(uint8 u8_val) {
  uint8 u8_wdtState;
  sz_lastTimeoutError = "I2C1 Put";
  u8_wdtState = _SWDTEN;       //WDT állapotát elmentjük
  _SWDTEN = 1;                 //WDT engedélyezése
  I2C1TRN = u8_val;            //adatbájt kiküldése
  while (I2C1STATbits.TRSTAT); //vár, amíg az adatforgalom (8 bit+ACK) lezajlik
  _SWDTEN = u8_wdtState;       //WDT visszaállítása eredeti állapotába
  sz_lastTimeoutError = NULL;
  if (I2C1STATbits.ACKSTAT != I2C_ACK) {
    reportError("I2CPUT1, NAK returned."); //NAK (negatív nyugtázás)
  }
}

//-- Egy bájt beolvasása
uint8 getI2C1(uint8 u8_ack2Send) {
  uint8 u8_wdtState;
  uint8 u8_inByte;

  sz_lastTimeoutError = "I2C1 Get";
  u8_wdtState = _SWDTEN;            //WDT állapotát elmentjük
  _SWDTEN = 1;                      //WDT engedélyezése                             
  while (I2C1CON & 0x1F);           //a tétlen állapotra várunk
  I2C1CONbits.RCEN = 1;             //vétel engedélyezése
  while (!I2C1STATbits.RBF);        //várunk a bájt beérkezésére
  CLRWDT();                         //Watchdog számláló törlése
  u8_inByte = I2C1RCV;              //az adatpuffer kiolvasása
  //-- megvárjuk a tétlen állapotot mielőtt az ACK bitet kiküldenénk
  while (I2C1CON & 0x1F);           //az alsó 5 bitnek nullának kell lennie
  I2C1CONbits.ACKDT = u8_ack2Send;  //ACK bit beállítása
  I2C1CONbits.ACKEN = 1;            //ACK bit küldésének engedélyezése
  while (I2C1CONbits.ACKEN);        //várunk a befejezésre
  _SWDTEN = u8_wdtState;            //WDT visszaállítása eredeti állapotába
  sz_lastTimeoutError = NULL;
  return(u8_inByte);                //a beolvasott bájttal térünk vissza

Az összetett I2C műveletek (tranzakciók) támogatói függvényei

Az összetett I2C műveletek, vagy más néven I2C tranzakciók az 1. táblázatban felsorolt elemi műveletekből rakhatók össze. Természetesen kényelmetlen volna, ha minden tranzakciót a fenti elemi műveletekkel kellene megfogalmazni, ezért a gyakran használt összetett műveletekre külön függvényeket nyújt a PIC24 támogatói programkönyvtár, amelyeket az alábbi táblázatban foglaltuk össze.

2. táblázat: Az összetett I2C műveletek (tranzakciók) támogatói függvényei
I2C támogatói függvény Rövid leírás
void write1I2C1(uint8 u8_addr,uint8 u8_d1) Egy bájt (u8_d1) kiküldése az u8_addr című eszköznek
void write2I2C1(uint8 u8_addr,uint8 u8_d1, uint8 u8_d2) Két bájt (u8_d1, u8_d2) kiküldése az u8_addr című eszköznek
void writeNI2C1(uint8 u8_addr,uint8* pu8_data, uint16 u16_cnt) Az u16_cnt paraméterrel megadott darabszámú bájt kiküldése a pu8_data tömbből, az u8_addr című eszköznek
void read1I2C1 (uint8 u8_addr,uint8* pu8_d1)
Egy bájtot olvas az u8_addr című eszközről. A beolvasott adat a pu8_d1 mutatóval megcímzett helyre kerül.
void read2I2C1 (uint8 u8_addr,uint8* pu8_d1, uint8* pu8_d2) Két bájtot olvas az u8_addr című eszközről. A beolvasott adatok a pu8_d1 mutatóval megcímzett helytől kezdődően kerülnek elhelyezésre, egymás után.
void readNI2C1 (uint8 u8_addr,uint8* pu8_data, uint16 u16_cnt) Az u16_cnt paraméterrel megadott darabszámú bájtot olvas az u8_addr című eszközről. A beolvasott adat a pu8_d1 mutatóval megcímzett helytől kezdődően kerülnek elhelyezésre, egymás után.
Az elemi és az összetett műveletek összefüggéseinek megértéséhez alábbi ábrákon két tipikus esetet mutatunk be annak szemléltetésére, hogy a 2. táblázat tranzakciói milyen elemi I2C műveletekből tevődnek össze. Mindkét példában egy START feltétel generálásával kezdődik a tranzakció (a startI2C1() függvény hívásával), majd kiküldünk egy címet (a példákban a 7 bites címzési módot használjuk) és a kívánt adatáramlási irányt beállító R/W bitet. A címbájt kiküldését a slave eszköznek nyugtáznia kell (ACK) a tranzakció sikeres folytatásához. A további teendő attól függenek, hogy melyik irányba áramlanak az adatok. Írásnál a putI2C1(), olvasásnál a getI2C1() függvényt használjuk. A tranzakció végén egy STOP feltételt kell generálni a stopI2C1() függvényhívással.

1. példa: két bájt küldése a write2I2C1() függvénnyel


5. ábra: két bájt küldése a slave eszköznek

A fenti ábra a write2I2c1() függvény kifejtését mutatja be, amely két adatbájtot küld ki a slave eszköznek. Írásnál az R/W bit '0' értékű. A címzés nyugtázását követően az adatbájtokat a putI2C1() vagy a putNoAckCheckI2C1() függvénnyel küldhetjük ki, s mindegyik kiküldött bájt után a slave eszköznek nyugtázással kell válaszolnia. A fentiek mintájára történik a tetszés szerinti számú bájt küldése is (lásd: writeNI2c1() függvény).

2. példa: két bájt beolvasása a read2I2C1() függvénnyel


6. ábra: két bájt beolvasása a slave eszközről

A fenti ábra a read2I2c1() függvény kifejtését mutatja be. Beolvasásnál is ugyanúgy START feltétel generálásával és a cím kiküldésével kezdődik a tranzakció, s az R/W bit '1' értéke jelzi, hogy adat beolvasás következik. A címzés nyugtázását követően (ezt a nyugtázást a slave küldi) az adatbájtokat a getI2C1() függvény hívásával olvashatjuk be, melynek bemenő paramétere az I2C_ACK vagy I2C_NAK előre definiált konstansokkal adható meg. Az ACK nyugtázás azt jelzi, hogy még újabb adatbájtot várunk. A NACK nyugtázás pedig azt jelzi, hogy már nem várunk több adatot, vége a tranzakciónak. Adat beolvasáskor tehát a master küldi a nyugtázó bitet, s például N darab bájt olvasásakor az első N-1 esetben  I2C_ACK paraméterrel hívjuk meg a getI2C1() függvényt, az utolsó bájt fogadásánál pedig I2C_NAK paraméterrel.

I2C kommunikáció a TCN75 digitális hőmérővel

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

A TCN75 hőmérő a -55 °C - +125 °C hőmérséklet-tartományban működik, pontossága optimális esetben 0.5 °C. Megjegyezzük, hogy a TCN75 digitális hőmérő mindegyik típusa működőképes a 2,7 - 5.5 V-os tápfeszültség- tartományban, de a TCN75-3.3 típusjelzésű példányok a 3,3 V-os tápfeszültséghez, a TCN75-5.0 típusjelzésűek pedig az 5,0 V-os tápfeszültséghez vannak kalibrálva. A névlegestől eltérő tápfeszültség esetén a tápfeszültség 1 V-os megváltozására a pontosság 1°C-ot is romolhat.
 
A hőmérő és a mikrovezérlő összekötése az adatlapból átvett ábrán látható: az SDA és az SCL lábakat össze kell kötni a mikrovezérlő megfelelő lábaival (esetünkben az SDA1 és SCL1 lábakkal), s mindkét vonalat fel kell húzni egy-egy ellenállással. A 100 kHz-es adatátviteli sebességnél 4,7 k ohm a szokásos érték, nagyobb sebességnél azonban keményebb felhúzásra van szükség (pl. 2 k ohm). Az ALERT láb bekötését most elhagyhatjuk.

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

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

Megjegyzés: A kiegészítő elemek Vdd tápfeszültségének nem kell szükségszerűen megegyeznie a PIC24HJ mikrovezérlő tápfeszültségével. A TCN75-3.3 típusjelzésű IC-t azonban a nagyobb pontosság érdekében célszerű 3,3 V-on járatni.

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

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

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


Írásnál ügyeljünk arra, hogy a címzés után elsőnek kiküldött bájt alsó két bitje a regiszter-mutatót állítja be (a felső hat bit kötelezően nulla!). A CONFIG regiszter írásához tehát a write2I2C1(u8_addr,u8_d1,u8_d2) függvényhívást használhatjuk (lásd 5. ábra), ahol a8_addr a TCN75 címe (0x90, ha minden címvonal a földre van lehúzva), u8_d1 = 1 (a CONFIG regiszter címe), u8_d2 pedig a CONFIG regiszterbe írni kívánt adat. A TSET és THYST regiszterek írásánál értelemszerűen eggyel több adatbájtot kell kiküldeni, mivel ezek 16 bites regiszterek. Ne feledjük el, hogy több-bájtos adatok esetén előbb mindig a magas helyiértékű bájtot küldjük, s utána az alacsonyabb helyiértékűt. A TEMP regiszter csak olvasható.
 
Olvasásnál két lehetőségünk van:
  1. Előbb egybájtos írással (write1I2C1() függvény hívásával) beállítjuk a regiszter-mutatót, majd egy új művelettel (vagy RESTART-tal, ha elemi műveletekből építkezünk) olvasást indítunk, ami az előzőleg beállított regiszter tartalmát olvassa ki. (CONFIG olvasása esetén egybájtos, a többi regiszter esetén egy vagy kétbájtos olvasást kezdeményezünk).
  2. Ha ugyanazt a regisztert olvassuk többször egymás után, akkor elhagyhatjuk a regiszter-mutató beállítását.
A TCN75 hőmérő adatlapjának 5-1. ábrája összefoglalja a lehetséges műveleteket, s megmutatja azok idődiagramját.

A TCN75 digitális hőmérő kezelésén keresztül az I2C támogatói függvények használatát bemutató mintaprogram az alábbi listán látható. A program kb. 2 másodpercenként kiolvassa a hőmérőt, és a kiolvasott értéket hexadecimálisan, valamint Celsius és Fahrenheit fokokra átszámítva is kiírja.

Hardver követelmények: A kísérleti áramkörhöz egy TCN75 digitális hőmérőt csatlakoztatunk, melynek minden címvonalát (A0-A2) földre kötjük. Az SDA és SCL vonalakat egy-egy 2,2 k ohmos ellenállással felhúzzuk Vdd-íre.

Megjegyzés: Az alábbi program nem szerepelt a tankönyv eredeti mintapéldái között, a ds1621_i2c.c programot adaptáltuk a TCN75 hőmérőhöz.

1. lista: A tcn75_i2c.c program listája
#include "pic24_all.h"
#include <stdio.h>
#define TCN75ADDR 0x90       //TCN75 címe, ha minden címvonal földön van
#define ACCESS_CONFIG 0x01   //CONFIG regiszter címe
#define READ_TEMP 0x00       //TEMP regiszter címe

/** A TCN75 digitális hőmérő CONFIG regiszterének beállítása.
 *  A CONFIG regiszter legfelső három bitje kötelezően nulla!
 * \param u8_i a CONFIG regiszterbe írandó adat.
 */
void writeConfigTCN75(uint8 u8_i) {
  write2I2C1(TCN75ADDR, ACCESS_CONFIG, u8_i&0x1F);
}

/** A TCN75 digitális hőmérő TEMP regiszterének kiolvasása.
 *  A visszatérési érték 16 bites, előjeles szám, melyben a
 *  9 bites adat balra igazított.
 *  Kiolvasás előtt beállítjuk a regiszter-mutatót.
 */
int16 readTempTCN75(void) {
  uint8 u8_lo, u8_hi;
  int16 i16_temp;
  write1I2C1(TCN75ADDR, READ_TEMP);
  read2I2C1 (TCN75ADDR, &u8_hi, &u8_lo);
  i16_temp = u8_hi;
  return ((i16_temp<<8)|u8_lo);
}

int main (void) {
  int16 i16_temp;
  float  f_tempC,f_tempF;
  configBasic(HELLO_MSG);
  configI2C1(400);                //az I2C konfigurálása 400 KHz-re
  writeConfigTCN75(0x00);         //normál mód
  while (1) {
    DELAY_MS(2000);
    i16_temp = readTempTCN75();
    f_tempC = i16_temp;           //automatikus konverzió lebegőpontosra
    f_tempC = f_tempC/256;        //fokokra alakít
    f_tempF = f_tempC*9/5 + 32;   //Fahrenheit fokokra konvertál
    printf("Temp is: 0x%0X, %4.4f (C), %4.4f (F)\n", i16_temp,
            (double) f_tempC, (double) f_tempF);
  }
}

A program elején definiált konstansok közül TCN75ADDR=0x90 a TCN75 hőmérő címe (0b10010000) címe, feltéve, hogy minden címvonal földön van (A0=0, A1=0, A2=0) és az R/W bit is nulla. A másik kettő a CONFIG regiszter címe (ACCESS_CONFIG=0x01) és a TEMP regiszter címe  (READ_TEMP=0x00), amelyeket a regiszter-mutató beállításához használunk.

Bár a hőmérő kezelése a 2. táblázatban szereplő függvényekkel sem komplikált, a még kényelmesebb kezelés érdekében külön függvényeket definiáltunk a hőmérő konfigurálásához és kiolvasásához. A writeConfigTCN75() függvény összesen három bájtot küld ki az I2C buszra. Az elsőben a hőmérő címét küldi ki R/W=0 beállítással, ezzel címezzük meg az eszközt és jelezzük, hogy írás művelet következik. A következő bájt a regiszter mutatót állítja be. Itt most a CONFIG regiszter címét kell megadnunk, hiszen abba akarunk írni. A harmadik kiküldött bájt pedig az adat, amit a CONFIG regiszterbe akarunk írni. Az u8_i & 0x1F bitenkénti ÉS művelet biztosítja, hogy a CONFIG regiszter felső három bitjébe mindig nullát írjunk. A readTempTCN75() függvény egy picivel komplikáltabb, mivel itt az adatmozgatás írányt kell, hogy váltson: először megcímezzük a hőmérőt, majd a TEMP regiszter címét küldjük ki a regiszter-mutató beállításához, ez tehát írás műveletet igényel. Ezután egy kétbájtos olvasás művelet következik, amellyel kiolvassuk a 16 bites TEMP regisztert.

A főprogramban a rendszer konfigurálása után engedélyezzük és 400 kbit/s sebességre konfiguráljuk az I2C alrendszert. Ezután beállítjuk a hőmérő konfigurációs regiszterét (folyamatos mérés, komparátor mód). A végtelen ciklusban kb. két másodpercenként kiolvassuk a hőmérőt, majd a kiolvasott értéket hexadecimálisan, valamint Celsius és Fahrenheit fokokra átszámítva is kiíratjuk. A hőmérséklet fokokra történő számításánál lebegőpontos (float) változókat használunk. A kiolvasott értéket 256-tal osztjuk, mivel a képzeletbeli tizedespont a TEMP regiszter két bájtja között van, tehát a 16 bites egészként kiolvasott érték 8 bittel van balra léptetve.

A DS1621 digitális hőmérő használata

A tankönyvi mintapéldák között a Dallas Semiconductor (MAXIM) DS1621 digitális hőmérőhöz is találunk mintapéldát (ds1621_i2c.c). A DS1621 digitális hőmérő hasonló felépítésű, és bekötésű, mint a fentebb ismertetett TCN75 hőmérő. Van azonban néhány különbség a belső szervezésben és a kommunikációban. Erről az adatlap nyújt részletes tájékoztatást, itt csak a mintaprogram számára lényeges különbségeket foglaljuk össze:
Ezektől az eltérésektől eltekintve a program megegyezik az előző mintapéldával.

2. lista: A ds1621_i2c.c program listája 
#include "pic24_all.h"
#include <stdio.h>
#define DS1621ADDR 0x90       //DS1621 címe, ha minden címvonal földön van
#define ACCESS_CONFIG 0xAC    //CONFIG regiszter címe
#define START_CONVERT 0xEE    //Konverziót indító parancs kódja
#define READ_TEMP 0xAA        //TEMP regiszter címe

/** A DS1621 digitális hőmérő CONFIG regiszterének beállítása.
 * \param u8_i a CONFIG regiszterbe írandó adat.
 */
void writeConfigDS1621(uint8 u8_i) {
  write2I2C1(DS1621ADDR, ACCESS_CONFIG, u8_i);
}

/** A DS1621 digitális hőmérő konverziójának indítása.
 * Folyamatos módban csak egyszer kell kiadni, egylövetű
 * módban azonban minden mérésnél.
 */
void startConversionDS1621() {
  write1I2C1(DS1621ADDR, START_CONVERT);
}

/** A DS1621 digitális hőmérő TEMP regiszterének kiolvasása.
 *  A visszatérési érték 16 bites, előjeles szám, melyben a
 *  9 bites adat balra igazított. Kiolvasás előtt be kell
 *  állítani a TEMP regiszter címét.
 */
int16 readTempDS161() {
  uint8 u8_lo, u8_hi;
  int16 i16_temp;
  write1I2C1(DS1621ADDR, READ_TEMP);
  read2I2C1 (DS1621ADDR, &u8_hi, &u8_lo);
  i16_temp = u8_hi;
  return ((i16_temp<<8)|u8_lo);
}

int main (void) {
  int16 i16_temp;
  float  f_tempC,f_tempF;
  configBasic(HELLO_MSG);
  configI2C1(400);                //az I2C konfigurálása 400 KHz-re
  writeConfigDS1621(0x00);        //folyamatos konverzió
  startConversionDS1621();        //a konverzió indítása
  while (1) {
    DELAY_MS(2000);               //2 s várakozás
    i16_temp = readTempDS161();
    f_tempC = i16_temp;           //automatikus konverzió lebegőpontosra
    f_tempC = f_tempC/256;        //fokokra alakít
    f_tempF = f_tempC*9/5 + 32;   //Fahrenheit fokokra konvertál
    printf("Temp is: 0x%0X, %4.4f (C), %4.4f (F)\n", i16_temp,
            (double) f_tempC, (double) f_tempF);
  }
}
A főprogramban folyamatos konverziójú üzemmódot állítunk be, majd a startConversionDS1621() eljárással elindítjuk a konverziót. Ezután minden ugyanúgy zajlik, mint az előző programnál.

A DS1631 digitális hőmérő használata

A fentihez nagyon hasonló a Dallas Semiconductor (MAXIM) DS1631 digitális hőmérőhöz készült tankönyvi mintapélda (ds1631_i2c.c). A DS1631 digitális hőmérő annyiban különbözik az előzőektől, hogy beállítástól függően 9-12 bites felbontással mér. Nagyobb felbontásnál azonban a konverziós idő hosszabb, például 12 bites módban 750 ms a konverziós idő. Ügyelnünk kell arra is, hogy a konverzió indítása ennél az IC-nél 0x51 (eltérően a DS1621 IC-től, ahol ugyanez 0xEE volt). Ezektől az apró különbségektől eltekintve a program megegyezik az előzővel, ezért minden külön megjegyzés nélkül adjuk közre.

Megjegyzés: A DS1631-hez nagyon hasonló tulajdonságokkal rendelkezik a TCN75A típusú hőmérő is (az is választhatóan 9-12 bites felbontású). A kezelése azonban a "sima" TCN75 típuséhoz hasonlít jobban, tehát az első listán bemutatott program módosításával (a konfigurációs regiszter módosításával, és a konfigurációs regiszter maszkolásának megszüntetésével) tudjuk beállítani és használni.

3. lista: A ds1631_i2c.c program listája 
#include "pic24_all.h"
#include <stdio.h>
#define DS1631ADDR 0x90       //DS1631 címe, ha minden címvonal földön van
#define ACCESS_CONFIG 0xAC    //CONFIG regiszter címe
#define START_CONVERT 0x51    //Konverziót indító parancs kódja
#define READ_TEMP 0xAA        //TEMP regiszter címe

/** A DS1631 digitális hőmérő CONFIG regiszterének beállítása.
 * \param u8_i a CONFIG regiszterbe írandó adat.
 */
void writeConfigDS1631(uint8 u8_i) {
  write2I2C1(DS1631ADDR, ACCESS_CONFIG, u8_i);
}

/** A DS1631 digitális hőmérő konverziójának indítása.
 * Folyamatos módban csak egyszer kell kiadni, egylövetű
 * módban azonban minden mérésnél.
 */
void startConversionDS1631() {
  write1I2C1(DS1631ADDR, START_CONVERT);
}

/** A DS1631 digitális hőmérő TEMP regiszterének kiolvasása.
 *  A visszatérési érték 16 bites, előjeles szám, melyben az
 *  üzemmódtól függően 9-12 bites adat balra igazított.
 *  Kiolvasás előtt be kell állítani a TEMP regiszter címét.
 */
int16 readTempDS1631() {
  uint8 u8_lo, u8_hi;
  int16 i16_temp;
  write1I2C1(DS1631ADDR, READ_TEMP);
  read2I2C1 (DS1631ADDR, &u8_hi, &u8_lo);
  i16_temp = u8_hi;
  return ((i16_temp<<8)|u8_lo);
}

int main (void) {
  int16 i16_temp;
  float  f_tempC,f_tempF;
  configBasic(HELLO_MSG);
  configI2C1(400);            //az I2C konfigurálása 400 KHz-re
  writeConfigDS1631(0x0C);    //folyamatos konverzió, 12-bites mód
  startConversionDS1631();    //konverzió indítása
  while (1) {
    DELAY_MS(750);
    i16_temp = readTempDS1631();
    f_tempC = i16_temp;       //lebegőpontossá alakítja
    f_tempC = f_tempC/256;    //fokokra konvertálja
    f_tempF = f_tempC*9/5 + 32;
    printf("Temp is: 0x%0X, %4.4f (C), %4.4f (F)\n",
            i16_temp, (double) f_tempC, (double) f_tempF);
  }
}
 

24LC515 típusú EEPROM írása és olvasása

A Microchip 24LC515 típusú EEPROM memóriája egy 8 lábú IC, amely I2C buszon keresztül vezérelhető, s 64 Kx8 bites szervezésű (512 kbit) memóriát tartalmaz. A 24LC515 típus 2,5 - 5,5 V közötti tápfeszültségen használható, az I2C busz adatsebessége maximálisan 400 kHz lehet. Van azonban egy gyorsabb változata is ennek a memóriának, amelyet a PIC-kwik projekt keretében kipróbáltunk. Ennek típusjele 24FC515, a maximális adatsebessége pedig 1 MHz lehet (egyéb paramétereiben megegyezik az LC típussal).

A 24LC515 (vagy 24FC515) memória kipróbálásához a kísérleti áramkört az alábbi ábrán látható módon kell kiegészíteni. A kiegészítő elemek Vdd tápfeszültségének nem kell szükségszerűen megegyeznie a PIC24HJ mikrovezérlő tápfeszültségével.

9. ábra: A kísérleti áramkör kiegészítése a 24LC515 memória kipróbálásához

A 24LC515 címzése egy kicsit meg van cifrázva. Az A0, A1, A2 bemenetek közül csak az első kettő használható az eszköz címzésére (tehát 4 db. IC fűzhető fel közvetlenül az I2C buszra). Az A2 címvonal hardveres választóként (chip select) működik, s magas szintre kell húzni ahhoz, hogy működjön az IC. A 64 kilobájtos memória fizikailag két blokkra van osztva, s folyamatos olvasásnál nem csoroghatunk rá egyikről a másikra. A blokk-választó bitet a START után elsőnek küldött cím- és parancs-bájtban kell megadni, az 5. bitben, tehát A2 helyén. A parancs-bájtot egy 15 bites cím követi, két bájtban kiküldve, jobbra igazítva. A két bájt közül az első a magasabb helyiértékű biteket, a második pedig az alacsonyabb helyiértékű biteket.


10. ábra: A parancs-bájt formátuma (B0 a blokk-választó bit)

A cím kiküldését követően egy vagy több bájtot írhatunk/olvashatunk, de lehetőség van 64 bájt (egy memórialap) írására is. Írás közben az IC nem fogad el újabb parancsot, tehát a címzés kiküldésével és a nyugtázó jel megérkezésének (vagy hiányának) figyelésével állapíthatja meg a program, hogy mikor küldheti a következő kiírandó blokkot.

  11. ábra: 64 bájt (egy memórialap) írásának idődiagramja

A WP bemenet írásvédelemre (write protect) szolgál. Ha magas szintre húzzuk, vagy szabadon hagyjuk (utóbbi esetben belső felhúzás működik) a WP bemenetet, akkor az IC továbbra is elfogad minden parancsot, de a kapott adatokat nem rögzíti a memóriában. A WP bemenetet alacsony szintre kell húzni az írásvédelem kikapcsolásához.

A 24LC515 eszköz azonosító I2C cím fix első négy bitje 0b1010 (0xA0), így 24LC515 memóriákat és TCN75 (vagy DS16x1) hőmérőket cím ütközés nélkül felfűzhetünk az I2C buszra.

Az alábbi tankönyvi mintaprogramban egy 24LC515 (vagy 24FC515) memóriát írunk és olvasunk 64 bájt egyidejű írása vagy olvasása mellett.

Hardver követelmények: a kísérleti áramkört a 9. ábrán látható módon kell kiegészíteni. A program feltételezi, hogy az A0 és A1 címvonalakat GND-re, az A2 kiválasztó és a WP írásvédelmi bemenetet pedig Vdd-re kötjük. Az SDA és az SCL kivezetéseket a mikrovezérlő SDA1 és SCL1 kivezetéseire kötjük. Mindkét buszvonalat 2,2 k ohm-mal a tápfeszültségre húzzuk.

4. lista: Az mcp24lc515_i2c.c program listája 
#include "pic24_all.h"
#define EEPROM 0xA0    //a 24LC515 címe, földre kötött címvonalakkal
#define BLKSIZE 64     //blokk méret: egy memórialap 64 bájtos

/** Várakozás arra, hogy az EEPROM befejezze az írást. Amíg az írás tart,
 *  addig az EEPROM nem küld nyugtázó jelet a címének kiküldésekor.
 *  A függvény blokkoló típusú, addig nm tér vissza, amíg az EEPROM foglalt.
 *  A WDT gondoskodik róla, hogy ne kerüljünk végtelen várakozási ciklusba
 *  (feltételezzük, hogy WDT időzítése hosszabb, mint az EEPROM írási ideje).
 *  Timeout esetén szoftveres reset történik.
 * \param u8_i2cAddr a vizsgálni kívánt slave eszköz I2C címe.
 */
void waitForWriteCompletion(uint8 u8_i2cAddr) {
  uint8 u8_ack, u8_savedSWDTEN;
  u8_savedSWDTEN = _SWDTEN;
  _SWDTEN = 1;        //WDT engedélyezése, hogy ne ragadjunk végtelen ciklusba!
  u8_i2cAddr = I2C_WADDR(u8_i2cAddr);        //írás művelet, R/W# = 0;
  do {
    startI2C1();
    u8_ack = putNoAckCheckI2C1(u8_i2cAddr);  //ACK bit figyelése
    stopI2C1();
  } while (u8_ack == I2C_NAK);
  _SWDTEN = u8_savedSWDTEN;                  //WDT-t visszaállítása
}

/** Egy memórialap (64 bájt) írása a bemenő adatbufferből, az EEPROM egy
 *  megadott címétől kezdődően.
 *  \param u8_i2cAddr az EEPROM I2C címe
 *  \param u16_MemAddr a memórialap kezdőcíme, ahová írunk
 *  \param *pu8_buf mutató az adatbuffer kezdetéhez
 */
void memWriteLC515(uint8 u8_i2cAddr,  uint16 u16_MemAddr, uint8 *pu8_buf) {
  uint8 u8_AddrLo, u8_AddrHi;
  u8_AddrLo = u16_MemAddr & 0x00FF;
  u8_AddrHi = (u16_MemAddr >> 8);
  pu8_buf[0] = u8_AddrHi;                    //a címet is a bufferbe tesszük
  pu8_buf[1] = u8_AddrLo;

  if (u16_MemAddr & 0x8000) {                // ha MSB='1', beállítjuk a blokk-választó bitet
    u8_i2cAddr = u8_i2cAddr | 0x08;
  }
  waitForWriteCompletion(u8_i2cAddr);        //Várunk, ha az EEPROM elfoglalt
  writeNI2C1(u8_i2cAddr,pu8_buf,BLKSIZE+2);  //Az adatbuffer kiírása (cím+adatok)
}

/** Egy memórialap (64 bájt) olvasása az EEPROM egy megadott címétől kezdődően,
 *  és eltárolása az adatbufferbe.
 *  \param u8_i2cAddr az EEPROM I2C címe
 *  \param u16_MemAddr a beolvasni kívánt memórialap kezdőcíme
 *  \param *pu8_buf mutató az adatbuffer kezdetéhez
 */
void memReadLC515(uint8 u8_i2cAddr,  uint16 u16_MemAddr, uint8 *pu8_buf) {

  uint8 u8_AddrLo, u8_AddrHi;

  u8_AddrLo = u16_MemAddr & 0x00FF;
  u8_AddrHi = (u16_MemAddr >> 8);

  if (u16_MemAddr & 0x8000) {
    u8_i2cAddr = u8_i2cAddr | 0x08;          //a blokk-választó bit beállítása
  }
  waitForWriteCompletion(u8_i2cAddr);
  //set address counter
  write2I2C1(u8_i2cAddr,u8_AddrHi, u8_AddrLo);
  //read data
  readNI2C1(u8_i2cAddr,pu8_buf, BLKSIZE);
}


int main (void) {
  uint8 au8_buf[64+2];                      //2 extra bájt kell a címnek
  uint16 u16_MemAddr;
  uint8 u8_Mode;

  configBasic(HELLO_MSG);
  configI2C1(400);                          //az I2C konfigurálása 400 KHz-re
//-- 24FC515 esetén configI2C1(1000); is használható!
  outString("\nEnter 'w' for write mode, anything else reads: ");
  u8_Mode = inCharEcho();
  outString("\n");
  u16_MemAddr = 0;                          //A memória 0 címétől kezdünk
  while (1) {
    uint8 u8_i;
    if (u8_Mode == 'w') {
      outString("Enter 64 chars.\n");
//-- a buffer első két bájtja a címnek van fenntartva
      for (u8_i = 2; u8_i< 64+2;u8_i++) {
        au8_buf[u8_i] = inCharEcho();
      }
      outString("\nDoing Write\n");
//-- kétszer írjuk ki egymás után, hogy a foglaltság ellenőrzését is kipróbáljuk
      memWriteLC515(EEPROM,u16_MemAddr, au8_buf);   // írás
      u16_MemAddr = u16_MemAddr +64;
      memWriteLC515(EEPROM,u16_MemAddr,au8_buf);    // írás mégegyszer
      u16_MemAddr = u16_MemAddr +64;
    } else {
      memReadLC515(EEPROM,u16_MemAddr,au8_buf);     // olvasás
      for (u8_i = 0;u8_i< 64;u8_i++) outChar(au8_buf[u8_i]);
      outString("\nAny key continues read...\n");
      inChar();
      u16_MemAddr = u16_MemAddr + 64;
    }
  }
}

A program elején definált konstansok az IC I2C címe (0xA0) és a memória lapmérete (64 byte). Utóbbi szimbolikus névvel történő kezelésének sok értelme nincs, mert a főprogramban eleve feltételezzük, hogy 64 byte-os egységekben történik az írás/olvasás...

A programban a külső memória használatához az alábbi függvényeket definiáltuk:

waitForWriteCompletion(): Addig várakozik, amíg az EEPROM el van foglalva az írással.

memWriteLC515():
Egy memórialap (64 bájt) írása a mutatóval megadott adatbufferből, az EEPROM egy megadott címétől kezdődően.

memReadLC515(): Egy memórialap (64 bájt) olvasása az EEPROM egy megadott címétől kezdődően, és eltárolása a mutatóval megadott adatbufferbe. Vegyük észre, hogy olvasás előtt is vizsgálnunk kell az EEPROM esetleges foglaltságát.

A programnak két üzemmódja van: induláskor egy egybetűs parancsot kér, hogy írjon, vagy olvasson. Ha w betűt adunk meg, akkor írás üzemmódban indul, minden más esetben  pedig olvasással. Írás üzemmódban módban 64 karakteres szöveget kell megadnunk, amit a program kétszer egymás után ír ki (a memória címet minden kiíráskor lépteti). Az ismétlés azért kell, hogy a gyors, egymás utáni írással kipróbálhassuk a foglaltság figyelését. Az írás befejeztével újabb szöveget vár a program, hogy azt is kiírja, s mindez addig folytatódik,amí a RESET gombot meg nem nyomjuk, hogy áttérjünk a másik üzemmódra.A program egy futásának eredménye az alábbi ábrán látható.

12. ábra: A 24LC515_i2c.c program futtatása írás üzemmódban

Olvasás üzemmódban (ha 'w'-től különböző parancsot adunk meg) a program a memória elejétől kezdve 64 karakteres egységekben olvassa ki és írja ki a képernyőre a korábban elmentett szöveget. A kiolvasás egy újabb karakter begépelésével folytatódik. Az üres memóriából "kétpontos ipszilon" karaktereket olvashatunk ki. A fenti ábrán bemutatott írás után az olvasás üzemmódban az alábbi ábrán látható eredményt kapjuk.

13. ábra: A 24LC515_i2c.c program futtatása olvasás üzemmódban

Amint az ábrán is látható, minden beírt sort kétszer rögzített a program a memóriában, tehát kiolvasásnál is duplán jelennek meg.

Az MCP23017 portbővító IC használata

A Microchip kínálatában többféle portbővítő IC található, amelyek 8 bites (MCP23008) vagy 16 bites (MCP23017)  általános célú ki/bemenetként használhatók, s amelyek I2C vagy SPI buszon vezérelhetők (az SPI változatot S betű jelzi, például MCP23S17). Ezek a portbővítő IC-k lehetővé teszik, hogy a PIC mikrovezérlő általános célú portjainak számát egyszerűen növelhessük.

Az alábbiakban az MCP23017 portbővítővel ismerkedünk meg, ami I2C buszon keresztül vezérelhető, s 2x8 bites porttal rendelkezik. Az I2C kommunikáció a szokásos 100 kHz és 400 kHz mellett a nagysebességű 1,7 MHz-es buszfrekvencián is történhet. Az IC tápfeszültsége 2,7 V - 5,5 V közötti lehet, a -40 °C - +125 °C-os hőmérséklet-tartományban, tehát 3,3 V-on és 5 V-on egyaránt működtethető. Az IC három címvonallal is rendelkezik, tehát akár nyolc portbővítő IC-t is felfűzhetünk egy I2C buszra. Az IC két portja rugalmasan konfigurálható, s függetlenül (2x8 bites portként), vagy együtt (1x16 bites portként) is használhatók. Az IC blokkvázlata az alábbi ábrán látható.

14. ábra: Az MCP23017 portbővítő IC blokkvázlata

A portok működését 22 regiszter (11 regiszterpár) vezérli, melyek páronként címfolytonosan vagy pedig portonként külön blokkokba szervezve címezhetők meg. A címzési módot az IOCON regiszter BANK bitje határozza meg. Bekapcsoláskor ennek a bitnek az értéke nulla, ami a regiszterpáronkénti címfolytonos címzést álltja be, ami 16 bites módnak is tekinthető. Az ICON.BANK bit értékétől függő regiszter címeket az alábbi táblázatban foglaltuk össze:

3. táblázat: Az MCP23017 portbővítő regisztereinek címei
Regiszter Cím
ICON.BANK=0
Cím
ICON.BANK=1
Funkció
IODIRA 0x00 0x00 Az adatáramlás irányát szabja meg (A)
IODIRB 0x01 0x10 Az adatáramlás irányát szabja meg (B)
IPOLA 0x02 0x01 Az adat bemenetek polaritását szabja meg
IPOLB 0x03 0x11 Az adat bemenetek polaritását szabja meg
GPINTENA 0x04 0x02 A változás jelző megszakítást engedélyezi
GPINTENB 0x05 0x12 A változás jelző megszakítást engedélyezi
DEFVALA 0x06 0x03 Alapértelmezett összehasonlítási érték
DEFVALB 0x07 0x13 Alapértelmezett összehasonlítási érték
INTCONA 0x08 0x04 Összehasonlítási alap választó regiszter
INTCONB 0x09 0x14 Összehasonlítási alap választó regiszter
IOCON 0x0A 0x05 Konfigurációs regiszter (A port)
IOCON 0x0B 0x15 Konfigurációs regiszter (B port)
GPUA 0x0C 0x06 Belső felhúzás engedélyezése (A)
GPUB 0x0D 0x16 Belső felhúzás engedélyezése (B)
INTFA 0x0E 0x07 Megszakítás jelzőbit (A port)
INTFB 0x0F 0x17 Megszakítás jelzőbit (B port)
INTCAPA 0x10 0x08 GPIOA állapota megszakításkor
INTCAPB 0x11 0x18 GPIOB állapota megszakításkor
GPIOA 0x12 0x09 A port regiszter
GPIOB 0x13 0x19 B port regiszter
OLATA 0x14 0x0A A port kimeneti adatregiszter
OLATB 0x15 0x1A B port kimeneti adatregiszter
A regiszterek funkciójának részletes ismertetése az adatlapban található. Bekapcsoláskor vagy hardveres RESET-kor az IODIRA és IODIRB regiszterek 0b1111 1111 állapotba kerülnek, az összes többi regiszter törlődik.

Az MCP23017 portbővítő IC SDIP, SOIC, SSOP és QFN fokozásban is kapható. A dugaszolható próbapanelben történő kipróbáláshoz az SDIP változatot használtuk. A lábak kiosztása a 15. ábrán látható. Az NC jelzésű lábak nincsenek felhasználva. Ezekre a lábakra az SPI változatban (MCP23S17) van szükség, mivel - ahogy majd látni fogjuk - az SPI kommunikáció négy vezetéket használ.

A GPA0..7 és GPB0..7 jelzésű lábak a portok kivezetései. VDD a tápfeszültség (2,7 - 5,5 V), VSS a földpont. Az A0, A1, A2 címválasztó vonalak, valamint az SDA és SCL I2C buszvonalak szerepe már ismerős a korábbiakból. Az INTA és az INTB kimenetek interrupt jelet szolgáltatnak az A, illetve a B port bemeneteinek megváltozásakor.

A RESET vonal lehúzásával a portbővítő IC hardveresen resetelhető.                                                                                                                                                          15. ábra: Az MCP23017 IC láb-elrendezése

Az alábbi mintaprogram az MCP23017 16 bites periféria bővítő egyszerű használatát mutatja be. Ha a 16 portbitre egy-egy áramkorlátozó ellenállással (1 k ohm) sorba kötött LED-et kötünk, akkor vizuálisan nyomon követhetjük a program működését.
   - az A porton minden bitet egyszerre kapcsolgatunk 2 Hz frekvenciával.
   - a B porton ellenütemben váltogatjuk a bitek állapotát 1 Hz frekvenciával.


Hardver követelmények: a kísérleti áramkört egy MCP23017 IC-vel kell kiegészíteni. A program feltételezi, hogy az A0, A1 és A2 címvonalak a földre vannak kötve, a RESET lábat pedig magas szintre húzzuk. Az SDA és az SCL kivezetéseket a mikrovezérlő SDA1 és SCL1 kivezetéseire kötjük. Mindkét buszvonalat 2,2 k ohm-mal a tápfeszültségre húzzuk.

5. lista: Az MCP23017 portbővítőt kezelő program listája (mcp23017.c) 
#include "pic24_all.h"
#define MCP23017ADDR 0x40    //MCP23017 címe, ha mindegyik címvezérlő láb földre van kötve
#define IODIRA_CIME 0x00
#define IODIRB_CIME 0x01
#define OLATA_CIME  0x14
#define OLATB_CIME  0x15

int main (void) {
uint8 a_dat=0,b_dat=0x55;
  configBasic(HELLO_MSG);
  configI2C1(400);            //configure I2C for 400 KHz
  write2I2C1(MCP23017ADDR,IODIRA_CIME,0);
  write2I2C1(MCP23017ADDR,IODIRB_CIME,0);
  write2I2C1(MCP23017ADDR,OLATA_CIME,a_dat);
  write2I2C1(MCP23017ADDR,OLATB_CIME,b_dat);
  while (1) {
    DELAY_MS(250);
    a_dat = ~a_dat;
    b_dat = ~b_dat;
    write2I2C1(MCP23017ADDR,OLATA_CIME,a_dat);
    write2I2C1(MCP23017ADDR,OLATB_CIME,b_dat);
    DELAY_MS(250);   
    a_dat = ~a_dat;
    write2I2C1(MCP23017ADDR,OLATA_CIME,a_dat);
  }
}
A programban a portbővítő IC alapértelmezett konfigurációját használjuk (ekkor az IC 16 bites módban működik, azaz az A és B porthoz tartozó regiszterek közvetlenül egymás melletti címeken helyezkednek el. Így tehát az adatáramlás irányát beállító regiszterek címei: IODIRA = 0x00 és IODIRB = 0x01

Megjegyzés: Az IODIRx regiszterek hasonló funkciót látnak el, mint a mikrovezérlő TRISx regiszterei: az adatáramlás irányát állítják be. Az OLATx regiszterek pedig a portok kimeneti adatregisztereit jelentik, s a mikrovezérlő LATx regisztereihez hasonló szerepet töltenek be.  

A programban nem definiáltunk külön függvényeket az MCP23017 kezeléséhez, csak a PIC24 támogatói programkönyvtár függvényeit használjuk. Emiatt a portbővítő IC 16 bites üzemmódjából fakadó előnyöket nem tudjuk kihasználni, mert ahhoz olyan új függvényt kellene definiálni, ami 16 bites változókat és regisztereket kezel.

A rendszer konfigurálása után az I2C buszt 400 kHz-es buszfrekvenciával konfiguráljuk, majd az IODIRx regiszterekbe nullát írunk, ezzel minden portbitet kimenetre állítunk. A kimenő adatregiszterekbe az a_dat és b_dat előjel nélküli, 8 bites változók tartalmát másoljuk be. A változók kezdőértéke 0, illetve 0x55 (0b01010101, azaz felváltva egyesek és nullák). A végtelen ciklusban egy-egy 250 ms-os késleltetés után az A regiszter tartalmát kétszer, a B regiszter tartalmát egyszer komplementáljuk, így alakul ki a 2 Hz-es és az 1 Hz-es villogási frekvencia. Komplementálásnál az A regiszter 0x00-ból 0xFF-be, majd 0xFF-ből 0x00-ba vált, tehát minden bit egyszerre '0', vagy '1'. A B regiszternél 0x55 a kezdőérték, amiből komplementáláskor 0xAA-ba vált. A következő ciklusban 0xAA-ból ismét 0x55-be vált, a bitek tehát felváltva billennek át.

Az SPI kommunikációs csatorna

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

A PIC-kwik projektben használt PIC24HJ128GP502 és dsPIC33FJ128GP802 mikrovezérlők egyaránt 2-2 db. SPI egységgel rendelkeznek, amelyek - beállítástól függően - master és slave üzemmódban is képesek működni. Ha általában beszélünk az SPI illesztőkről, akkor az SPI1 és SPI2 nevek helyett az SPIx nevet fogjuk használni (a regiszter neveknél is). A PIC-kwik projektben használt mikrovezérlők esetében az SPIx moduloknak nincs rögzített kimenete, mivel áthelyezhető perifériák, programozottan rendelhetünk hozzájuk kivezetéseket.

Az SPI egységek felépítése és működése

Az SPIx egységek blokkvázlata az alábbi ábrán látható. Bár különleges beállításokra is lehetőség van, normális esetekben a master egység szolgáltatja az adatforgalom szinkronizálásához szükséges órajelet,amelyet az FCY órajelből állíthatunk elő az elsődleges és a másodlagos frekvencia osztók segítségével. Az elsődleges osztónál 1:1, 1:4, 1:16 és 1:64 frekvencia osztási arány választható. A másodlagos osztónál 1:1 és 1:8 között osztásarány állítható be. A másodlagos osztó valójában egy 3 bites számláló, s az SPIxCON<4:2> bitek ezen számláló periódus-regiszterét alkotják. A 0b111 érték beírása esetén minden bejövő impulzus túlcsordulást okoz, tehát ez az 1:1 osztás aránynak felel meg. A 0b110 érték beírásakor minden második impulzusnál csordul túl a számláló, ez tehát az 1:2 osztás aránynak felel meg. 

16. ábra: Az SPIx modulok blokkvázlata

Az SPIx modulok lefontosabb eleme a kimenő és bejövő adatvonalakhoz csatlakozó SPIxSR shift regiszter, melynek léptetése az órajellel szinkronban történik. A többi soros perifériához (I2C, UART) hasonlóan az SPI is kettős bufferelésű, az SPIxSR regiszter tehát a felhasználó által nem elérhető. A kiküldeni kívánt adatokat az SPIxBUF regiszterbe kell írni, ami íráskor valójában az SPIxTXB regisztert jelenti. Innen adáskor automatikusan kerül tovább az adat az SPIxSR shift regiszterbe. Olvasáskor pedig, ha az SPIxSR regiszterbe beérkezett nyolc adatbit, akkor automatikusan átmásolódnak az SPIxRXB regiszterbe, ahonnan az SPIxBUF regisztert címezve tudjuk kiolvasni. Az SPIxBUF írásakor tehát az SPIxTXB regisztert, olvasásakor pedig az SPIxRXB regisztert érjük el.

Az SPI kommunikáció duplex módban történik, tehát amikor egy adatbit kilép az SDO vonalon, akkor egy bitet beléptetünk az SDI vonalon is. Így, mire 8 adatbitet kiléptetünk, addigra egy 8 bites adatot be is olvastunk. A ki- vagy beléptetett adat nem feltétlenül értelmes információ, hiszen a kommunikáció kezdetén a slave még nem tudhatja, hogy milyen adatot várunk tőle. A fordítottja is előfordulhat: egy külső memória adatainak beolvasásakor a master eszköznek már "nincs mondanivalója". A kommunikáció azonban megköveteli, hogy ilyenkor is küldjünk ki valamit, például haszontalan nullákat.  
 
A PIC mikrovezérlők SPI egységei maximálisan 10 MHz-es buszfrekvenciával képesek működni. Ha a megcímzett eszköz ennél lassúbb, akkor a neki megfelelő kisebb buszfrekvenciát kell használni. Az alábbi táblázatban feltüntettük az elsődleges és másodlagos osztók beállítási lehetőségeit, FCY=40 MHz-es utasítás frekvenciát feltételezve (Fosc = 80 MHz).

4. táblázat: A beállítható SPI frekvenciák FCY = 40 MHz esetén

Elsődleges
osztó
Másodlagos osztó frekvencia-osztási aránya
1:1 1:2 1:3 1:4 1:5 1:6 1:7 1:8
1:1 Érvénytelen beállítás 10000 8000 6666.7 5714.7 5000
1:4 10000 5000 3333.3 2500 2000 1666.7 1428.7 1250
1:16 2500 1250 833.3 625 500 416.7 357.1 312.5
1:64 625 312.5 208.3 156.3 125 104.2 89.3 78.1

SPI támogatói függvények

A tankönyv szoftver segédletének PIC24 támogatói könyvtára kevés segítséget nyújt az SPI perifériák kezeléséhez. Ennek valószínűleg az az oka, hogy nehéz általános receptet adni a sokféleképpen konfigurálható és használható perifériára. Az include/pic14_spi.h állományban az SPIxSTAT, valamint az SPIxCON1 és SPIxCON2 regiszterek öndokumentáló módon történő konfigurálásához használható makrókat találunk, amelyeket a tankönyv szerzői a Microchip PIC24 periféria könyvtárából vettek át. A header állomány ezen kívül csak két-két függvényt deklarál, melyek definíciója a common/pic24_spi.c állományban található. Ezek közül a checkRxErrorSPIx() nevű függvény a vevőoldali adatregiszter esetleges túlcsordulását figyeli és jelzi ki, az ioMasterSPIx() függvény pedig a master módú kommunikációt támogatja: egy adatot a kimeneti adatregiszterbe ír, majd a válaszként kapott adattal tér vissza. Az alábbi listában csak az SPI1 modul támogatói függvényeit mutatjuk be. Az SPI2 modulhoz is vannak definiálva ugyanilyen függvények.
void checkRxErrorSPI1() {
  if (SPI1STATbits.SPIROV) {
    //clear the error
    SPI1STATbits.SPIROV = 0;
    reportError("SPI1 Receive Overflow\n");
  }
}

uint16 ioMasterSPI1(uint16 u16_c) {
  checkRxErrorSPI1();
  _SPI1IF = 0;      //clear interrupt flag since we are about to write new value
  SPI1BUF = u16_c;
  while (!_SPI1IF) { //wait for operation to complete
    doHeartbeat();
  };
  return(SPI1BUF);
}
 

A DS1722 digitális hőmérő használata (ds1722_spi.c)

Az egyik tankönyvi mintapélda a Dallas Semiconductor (MAXIM) DS1722 digitális hőmérőjének használatát mutatja be. Ez a hőmérő SPI vagy 3-vezetékes módban kommunikál (a SERMODE láb felhúzása választja ki az SPI módot). A hőmérő funkcionális blokkvázlata az alábbi ábrán látható.

17. ábra: A DS1722 digitális hőmérő funkcionális blokkvázlata

Ennek a hőmérőnek nincs termosztát funkciója, ezért a regiszter készlet csupán a CONFIG és a TEMP regiszterekből áll. A digitális és az analóg rész különböző tápfeszültségre is köthető, mi azonban azonos feszültségről (3,3 V-ról) járatjuk. A CONFIG konfigurációs regiszter címe íráskor 0x80, olvasáskor 0x00. A TEMP regiszter csak olvasható, alacsonyabb helyiértékű felének (ami a Celsius fokokban kifejezett hőmérséklet törtrészét tartalmazza) címe 0x01, a magasabb helyiértékű bájtja pedig (mi a Celsius fokokban kifejezett hőmérséklet egészrészét tartalmazza) a 0x02 címen olvasható.

A CONFIG regiszter bitjeinek jelentését az alábbiakban foglalhatjuk össze:
bit7  bit6  bit5 bit4 bit3 bit2 bit1  bit0 
1 1 1SHOT R2       R1 R0 SD
SD   -  "Shutdown" mód (1: a hőmérő befejezi a konverziót és energia takarékos módba lép, 0: folyamatos mód)

1SHOOT
- "Egylövetű" konverzió (1:  konverziót indít, ha SD=1, 0: automatikusan '0'-ba áll, amikor véget ért a konverzió)

R<2:0>
- A kívánt pontosságot állítja be, az alábbiak szerint:
R2 R1 R0 Felbontás Max. konverziós idő
0 0 0 8 bit 0,075 s
0 1 9 bit 0,15 s
0 1 0 10 bit 0,3 s
0 1 1 11 bit 0,6 s
1 X X 12 bit 1,2 s
Hardver követelmények:  A kísérleti áramkörhöz egy DS1722 digitális hőmérőt csatlakoztatunk. A hőmérő bekötését az alábbi ábra mutatja:

18. ábra: A kísérleti áramkör kiegészítése a DS1722 digitális hőmérővel

Mivel az SPI egységeknek nincsenek fixen hozzárendelve egyik lábhoz sem, nekünk kell elvégezni a hozzárendeléseket a PIC24 támogatói programkönyvtár makrói segítségével. Önkényes alapon az RB5, RB6, RB7 lábakat fogjuk felhasználni. A Chip Enable (CE) jelet pedig (szintén önkényesen) az RB8 kimenetre kötjük.

Megjegyzések:

6. lista: A ds1722_spi.c program listája 
#include "pic24_all.h"
#include <stdio.h>

#define CONFIG_SLAVE_ENABLE() CONFIG_RB8_AS_DIG_OUTPUT()
#define SLAVE_ENABLE()     _LATB8 = 1    //Slave kiválasztása
#define SLAVE_DISABLE()    _LATB8 = 0    //Slave eszköz letiltása

/** Az SPI1 modul konfigurálása 5 MHz-es, 8 bites master módba. 
 *  Az órajel polaritása és fázisa: CKP = 0, CKE = 0
 */
void configSPI1(void) {
//-- SPI órajel = 40MHz/2*4 = 40MHz/4 = 5 MHz
  SPI1CON1 = SEC_PRESCAL_2_1 |           //2:1 másodlagos osztó
             PRI_PRESCAL_4_1 |           //4:1 elsődleges
             CLK_POL_ACTIVE_HIGH |       //órajel aktív magas (CKP = 0)
             SPI_CKE_OFF         |       //kimenet inaktív/aktív átmenetkor (CKE=0)
             SPI_MODE8_ON        |       //8-bites mód
             MASTER_ENABLE_ON;           //master mód
//-- A kimenetek konfigurálása: SDO, SCLK, SDI, CE
  CONFIG_SDO1_TO_RP(6);                  //RP6 legyen SDO
  CONFIG_RP6_AS_DIG_PIN();               //analóg funkció letiltása
  CONFIG_SCK1OUT_TO_RP(7);               //RP7 legyen SCLK
  CONFIG_RP7_AS_DIG_PIN();               //analóg funkció letiltása
  CONFIG_SDI1_TO_RP(5);                  //RP5 legyen SDI
  CONFIG_RP5_AS_DIG_PIN();               //analóg funkció letiltása
  CONFIG_SLAVE_ENABLE();                 //Chip Enable kimenet konfigurálása
  SLAVE_DISABLE();                       //Kezdetben ne legyen megszólítva a slave
  SPI1STATbits.SPIEN = 1;                //az SPI modul engedélyezése
}

/** A DS1722 digitális hőmérő CONFIG regiszterének beállítása.
 * \param u8_i a CONFIG regiszterbe írandó adat.
 */
void writeConfigDS1722(uint8 u8_i) {
  SLAVE_ENABLE();                       //kiadjuk a Chip Enable jelet
  ioMasterSPI1(0x80);                   //a konfigurációs regisztert címezzük meg
  ioMasterSPI1(u8_i);                   //beállítjuk a CONFIG regisztert
  SLAVE_DISABLE();                      //megszüntetjük a Chip Enable jelet
}

/** A DS1722 digitális hőmérő TEMP regiszterének kiolvasása.
 *  A visszatérési érték 16 bites, előjeles szám, melyben az
 *  üzemmódtól függően 9-12 bites adat balra igazított.
 *  Kiolvasás előtt be kell állítani a TEMP regiszter címét.
 */
int16 readTempDS1722() {
  uint16 u16_lo, u16_hi;
  SLAVE_ENABLE();                       //kiadjuk a Chip Enable jelet
  ioMasterSPI1(0x01);                   //TEMP LSB címe
  u16_lo = ioMasterSPI1(0x00);          //LSB olvasása
  u16_hi = ioMasterSPI1(0x00);          //MSB olvasása
  SLAVE_DISABLE();                      //megszüntetjük a Chip Enable jelet
  return((u16_hi<<8) | u16_lo);
}

int main (void) {
  int16 i16_temp;
  float  f_tempC,f_tempF;
  configBasic(HELLO_MSG);                //A rendszer konfigurálása
  configSPI1();                          //Az SPI modul konfigurálása
  writeConfigDS1722(0xE8);               //10-bites, folytonos mód
  while (1) {
    DELAY_MS(1500);                      //1,5 s várakozás
    i16_temp = readTempDS1722();
    f_tempC = i16_temp;                  //lebegőpontossá alakítjuk
    f_tempC = f_tempC/256;               //fokokra alakítjuk
    f_tempF = f_tempC*9/5 + 32;          //Celsius -> Fahrenheit konverzió
    printf("Temp is: 0x%0X, %4.4f (C), %4.4f (F)\n", i16_temp, (double) f_tempC,
           (double) f_tempF);
  }
}

A program elején definiált SLAVE_ENABLE() és SLAVE_DISABLE() makrókkal a hőmérő Chip Enable bemenetét vezéreljük. A portlábak konfigurálását CONFIG_SLAVE_ENABLE() makróval és a configSPI1() függvénnyel végezzük. Ez utóbbi az SPI1 modul beállítását is elvégzi, a fejezet korábbi részében már említett makrók felhasználásával. A beállított mód: 8 bites master üzemmód, 5 MHz-es busz frekvencia, CKP=0, CKE=0 órajel polaritás és fázis. A hőmérő konfigurálása, a hőmérséklet kiolvasása és kiíratása az I2C hőmérőknél látottakhoz hasonlóan történik. 

A konfigurációs regiszter beállítása kétbájtos művelet: elsőként a címet küldjük ki (írásnál 0x80 a CONFIG regiszter címe). Második bájtként a CONFIG regiszterbe írni kívánt adatot küldjük ki. Ügyeljünk rá, hogy a legfelső három bitbe nullát kell írnunk. A programban használt 0xE8 érték folyamatos üzemmódot és 10 bites felbontást állít be (0,25 fok felbontás).

A hőmérő TEMP regiszterének kiolvasása 3 bájtos művelet: az első bájtban kiküldjük a TEMP regiszter első felének (LSB) címét, ami 0x01. A válaszul kapott bájtot eldobjuk. A második és harmadik kiküldött bájtnak (0x00) nincs információ tartalma, de valamit muszáj küldeni. A beolvasott bájtok viszont értékes információt tartalmaznak: a TEMP regiszter LSB-je és MSB-je, azaz az alacsony és magas helyiértékű bájtjai.

25LC256 típusú SPI EEPROM használata

Az alábbi mintaprogram nem tartozik az eredeti tankönyvi mintapéldákhoz, hanem a PIC-kwik projekt keretében átírtuk a 24LC515 memóriát kezelő programot úgy, hogy az SPI illesztővel rendelkező EEPROM-okat kezelje. Mi a 25LC256 IC-vel próbáltuk ki.

A Microchip 25LC256 típusú EEPROM memóriája egy 8 lábú IC, amely SPI buszon keresztül vezérelhető, s 256 kbit kapacitású (32Kx8 bites szervezésű) memóriát tartalmaz. A 25LC256 típus 2,5 - 5,5 V közötti tápfeszültségen használható, az SPI busz órajelének frekvenciája maximálisan 10 MHz lehet. A sebesség azonban tápfeszültség függő: az adatlap szerint 2,5 - 4,5 V között 5 MHz, 4,5 - 5,5 V között 10 MHz a maximális órajel-frekvencia. A PIC-kwik projektben használt alacsony (3,3 V-os) tápfeszültségre való tekintettel legfeljebb 5 MHz-es buszfrekvenciát használhatunk (az e fölötti értékeknél a kommunikáció instabillá válik).

Minden tranzakció egy parancsbájt kiküldésével kezdődik. Az elfogadott parancsokat az alábbi táblázatban foglaltuk össze:
Parancs Kód A funkció rövid leírása
READ 0x03 A memória olvasása adott címtől kezdődően
WRITE 0x02 A memória írása adott címtől kezdődően
WRDI 0x04 Az írást engedélyező bit törlése (letiltja az írást)
WREN 0x06 Az írást engedélyező bit beállítása (engedélyezi az írást)
RDSR 0x05 A STATUS regiszter olvasása
WRSR 0x01 A STATUS regiszter írása
A STATUS regiszter állapotjelző biteket tartalmaz amelyek az EEPROM IC állapotáról értesítenek bennünket a működés során. A legalsó két bit csak olvasható, a többi írható/olvasható. Az egyes bitek jelentését az alábbiakban foglalhatjuk össze:
bit7  bit6  bit5 bit4 bit3 bit2 bit1  bit0 
WPEN - - -   BP1     BP0 WEL WIP
WPEN - a hardveres írásvédelem engedélyezése (1:  WP engedélyezve, 0: WP letiltva)

BP1, BP0
- blokk védelem  beállítása (00: nincs tiltás, 01: a memória felső negyede (0x6000 címtől) írásvédett, 10: a felső fele (0x4000 címtől) írásvédett, 11: a teljes memória írásvédett)

WEL - Ez a bit jelzi az írás engedélyezés (Write Enable Latch) állapotát. Csak olvasható, illetve a WRDI/WREN utasításokkal befolyásolható (1:  az írás engedélyezett, 0: írás letiltva)

WIP - Írás folyamatban (Write In Progress) jelzőbit, csak olvasható (1: írás folyamatban, 0: írás befejeződött)

A 25LC256 EEPROM írásvédelmi rendszere meglehetősen bonyolult. A véletlen módosítások ellen az alábbi védelmet implementálta a gyártó:
Az írásvédelmi rendszer működését az alábbi táblázatban foglaltuk össze. Látható, hogy a STATUS regiszter írásával beállítható WPEN bit a hardveres írásvédelem (a WP bemenet) engedélyezésére szolgál. Ahogy korábban említettük, a WEL bitet a WREN parancs kiküldésével minden írás művelet előtt '1'-be kell állítani.
WEL WPEN WP Védett blokkok Nem védett blokkok STATUS regiszter
0 x x írásvédett írásvédett írásvédett
1 0 x írásvédett írható írható
1 1 L írásvédett írható írásvédett
1 1 H írásvédett írható írható

A parancsbájt és a kétbájtos cím kiküldését követően egy vagy több bájtot írhatunk/olvashatunk, de lehetőség van 64 bájt (egy teljes memórialap) írására is (lásd az alábbi ábrán). Írás közben az IC nem fogad el újabb parancsot, s az EEPROM státusz regisztere WIP bitjének '1' állapota jelzi, ha az írás még folyamatban van. A 16 bites cím legelső (legnagyobb helyiértékű) bitjének tartalma közömbös, mivel a 32 K címzése csak 15 bitet használ.

  19. ábra: 64 bájt (egy memórialap) írásának idődiagramja

Programleírás

Az alábbi mintaprogramban egy 25LC256 SPI memóriát írunk és olvasunk 64 bájt egyidejű írása vagy olvasása mellett. A programnak két üzemmódja van: induláskor egy egybetűs parancsot kér, hogy írjon, vagy olvasson. Ha w betűt adunk meg, akkor írás üzemmódban indul, minden más esetben  pedig olvasással. Írás üzemmódban módban 64 karakteres szöveget kell megadnunk, amit a program kétszer egymás után ír ki (a memória címet minden kiíráskor lépteti). Az ismétlés azért kell, hogy a gyors, egymás utáni írással kipróbálhassuk a foglaltság figyelését. Az írás befejeztével újabb szöveget vár a program, hogy azt is kiírja, s mindez addig folytatódik, amíg a RESET gombot meg nem nyomjuk, hogy áttérjünk a másik üzemmódra.

Hardver követelmények:
a kísérleti áramkört a 20. ábrán látható módon kell kiegészíteni.

20. ábra: A kísérleti áramkör kiegészítése a 25LC256 SPI memóriával

A WP bemenet írásvédelemre (write protect) szolgál, magas szintre kell húzni az írásvédelem kikapcsolásához. A HOLD bemenet arra a szolgál, hogy alacsony szintre történő húzásával menet közben fel lehessen  függeszteni a kommunikációt, majd a bemenet magas szintre húzásával folytatni lehessen anélkül, hogy a bitsorozatot újra kellene kezdeni. Az alábbi programban nem használjuk sem az írásvédelmet, sem a felfüggesztést, ezért a WP és HOLD bemeneteket fixen magas szintre kötjük.

Megjegyzések:

7. lista: Az  mcp25lc256_spi_eeprom.c program listája 
#include "pic24_all.h"
#define BLKSIZE   64                    //blokkméret: egy memórialap 64 bájtos
//-- A 25LC256 EEPROM által elfogadott parancsok
#define CMD_READ  0x03                  //Olvasás a megadott címtől kezdődően
#define CMD_WRITE 0x02                  //Írás a megadott címtől kezdődően
#define CMD_WRDI  0x04                  //Letiltja az írást
#define CMD_WREN  0x06                  //Engedélyezi az írást
#define CMD_RDSR  0x05                  //CMD_RDSR
#define CMD_WRSR  0x01                  //Státuszregiszter írása

//-- Slave Select kimenet definiálása
#define CONFIG_SLAVE_ENABLE() CONFIG_RB8_AS_DIG_OUTPUT()
#define SLAVE_ENABLE()     _LATB8 = 0   //Slave kiválasztása
#define SLAVE_DISABLE()    _LATB8 = 1   //Slave eszköz letiltása

/** Az SPI1 modul konfigurálása 5 MHz-es, 8 bites master módba. 
 *  Az órajel polaritásának és fázisának definiálása.
 */
void configSPI1(void) {
//-- SPI órajel = 40MHz/2*4 = 40MHz/8 = 5 MHz
  SPI1CON1 = SEC_PRESCAL_2_1 |          //2:1 másodlagos osztó
             PRI_PRESCAL_4_1 |          //4:1 elsődleges
             CLK_POL_ACTIVE_HIGH |      //órajel aktív magas (CKP = 0)
             SPI_CKE_OFF         |      //kimenet inaktív/aktív átmenetkor (CKE=0)
             SPI_MODE8_ON        |      //8-bites mód
             MASTER_ENABLE_ON;          //master mód
//-- A kimenetek konfigurálása: SDO, SCLK, SDI, CE
  CONFIG_SDO1_TO_RP(6);                 //RP6 legyen SDO
  CONFIG_RP6_AS_DIG_PIN();              //analóg funkció letiltása
  CONFIG_SCK1OUT_TO_RP(7);              //RP7 legyen SCLK
  CONFIG_RP7_AS_DIG_PIN();              //analóg funkció letiltása
  CONFIG_SDI1_TO_RP(5);                 //RP5 legyen SDI
  CONFIG_RP5_AS_DIG_PIN();              //analóg funkció letiltása
  CONFIG_SLAVE_ENABLE();                //Chip Enable kimenet konfigurálása
  SLAVE_DISABLE();                      //Kezdetben ne legyen megszólítva a slave
  SPI1STATbits.SPIEN = 1;               //az SPI modul engedélyezése
}

/** Várakozás arra, hogy az EEPROM befejezze az írást. Amíg az írás tart,
 *  addig az EEPROM STATUS regiszterének legalsó bitje (WIP) '1'-ben áll.
 *  A függvény blokkoló típusú, addig nem tér vissza, amíg az EEPROM foglalt.
 *  A WDT gondoskodik róla, hogy ne kerüljünk végtelen várakozási ciklusba
 *  (feltételezzük, hogy WDT időzítése hosszabb, mint az EEPROM írási ideje).
 *  Időtúllépés esetén szoftveres reset történik.
 */
void waitFor25LC256(void) {
  uint8 u8_flag,u8_savedSWDTEN;
  u8_savedSWDTEN = _SWDTEN;
  _SWDTEN = 1;                        //WDT be, hogy ne kerüljünk végtelen ciklusba!
  do {
    SLAVE_ENABLE();                   //kiadjuk a Chip Enable jelet
    ioMasterSPI1(CMD_RDSR);           //Státuszregiszter olvasása parancs
    u8_flag = ioMasterSPI1(0x00);     //
    SLAVE_DISABLE();                  //megszüntetjük a Chip Enable jelet
  } while (u8_flag & 0x01);
  _SWDTEN = u8_savedSWDTEN;           //WDT-t visszaállítása
}

/** Egy memórialap (64 bájt) írása a bemenő adatbufferből, az EEPROM egy
 *  megadott címétől kezdődően.
 *  u16_MemAddr a memórialap kezdőcíme, ahová írunk
 *  *pu8_buf mutató az adatbuffer kezdetéhez
 */
void memWrite25LC256(uint16 u16_MemAddr, uint8 *pu8_buf) {
  uint8 u8_i,u8_AddrLo, u8_AddrHi;
  u8_AddrLo = u16_MemAddr & 0x00FF;   
  u8_AddrHi = (u16_MemAddr >> 8);
  waitFor25LC256();                  //Várunk, ha az EEPROM elfoglalt
  SLAVE_ENABLE();
  ioMasterSPI1(CMD_WREN);            //Írás újraengedélyezése
  SLAVE_DISABLE();
  Nop();   
  SLAVE_ENABLE();
  ioMasterSPI1(CMD_WRITE);           //Adatblokk írása
  ioMasterSPI1(u8_AddrHi);
  ioMasterSPI1(u8_AddrLo);
  for(u8_i=0; u8_i < BLKSIZE; u8_i++) {
    ioMasterSPI1(*pu8_buf++);        //Az adatbuffer kiírása
  }
  SLAVE_DISABLE();
}

/** Egy memórialap (64 bájt) olvasása az EEPROM egy megadott címétől kezdődően,
 *  és eltárolása az adatbufferbe.
 *  \param u16_MemAddr a beolvasni kívánt memórialap kezdőcíme
 *  \param *pu8_buf mutató az adatbuffer kezdetéhez
 */
void memRead25LC256(uint16 u16_MemAddr, uint8 *pu8_buf) {
  uint8 u8_i, u8_AddrLo, u8_AddrHi;
  u8_AddrLo = u16_MemAddr & 0x00FF;
  u8_AddrHi = (u16_MemAddr >> 8);
  waitFor25LC256();                  //Várunk, ha az EEPROM elfoglalt
  SLAVE_ENABLE();
  ioMasterSPI1(CMD_READ);            //Adatblokk olvasása
  ioMasterSPI1(u8_AddrHi);
  ioMasterSPI1(u8_AddrLo);
  for(u8_i=0; u8_i < BLKSIZE; u8_i++) {
    *pu8_buf++=ioMasterSPI1(0x00);
  }
  SLAVE_DISABLE();
}

int main (void) {
  uint8 au8_buf[BLKSIZE];
  uint16 u16_MemAddr;
  uint8 u8_Mode;
  configBasic(HELLO_MSG);
  configSPI1();                     //az SPI1 csatorna konfigurálása
  outString("\nEnter 'w' for write mode, anything else reads: ");
  u8_Mode = inCharEcho();
  outString("\n");
  u16_MemAddr = 0;                  //A memória 0 címétől kezdünk
  while (1) {
    uint8 u8_i;
    if (u8_Mode == 'w') {
      outString("Enter 64 chars.\n");
      for(u8_i=0; u8_i < BLKSIZE; u8_i++) {
        au8_buf[u8_i] = inCharEcho();
      }
      outString("\nDoing Write\n");
//-- kétszer írjuk ki egymás után, hogy a foglaltság ellenőrzését is kipróbáljuk
      memWrite25LC256(u16_MemAddr, au8_buf);    // írás
      u16_MemAddr = u16_MemAddr +64;
      memWrite25LC256(u16_MemAddr,au8_buf);     // írás mégegyszer
      u16_MemAddr = u16_MemAddr +64;
    } else {
      memRead25LC256(u16_MemAddr,au8_buf);      // olvasás
      for(u8_i=0; u8_i < BLKSIZE; u8_i++) outChar(au8_buf[u8_i]);
      outString("\nAny key continues read...\n");
      inChar();
      u16_MemAddr = u16_MemAddr + 64;
    }
  }
}
A program elején definált konstansok a memória lapmérete (64 byte), valamint az IC által elfogadott parancsok kódjai.

A programban a külső memória használatához az alábbi függvényeket definiáltuk:

waitFor25LC256(): Addig várakozik, amíg az EEPROM el van foglalva az írással.

memWrite25LC256():
Egy memórialap (64 bájt) írása a mutatóval megadott adatbufferből, az EEPROM egy megadott címétől kezdődően. A kezdőcímnek 64-gyel osztható számnak kell lennie, mert íráskor nem léphetjük át a laphatárt!

memRead25LC256(): Egy memórialap (64 bájt) olvasása az EEPROM egy megadott címétől kezdődően, és eltárolása a mutatóval megadott adatbufferbe. Vegyük észre, hogy olvasás előtt is vizsgálnunk kell az EEPROM esetleges foglaltságát! A kezdőcímnek itt is 64-gyel osztható számnak kell lennie, mert olvasáskor sem léphetjük át a laphatárt!

A program futtatásának részleteire nem térünk ki, az teljes mértékben megegyezik a 4. listán bemutatott  mcp24lc515_i2c.c programéval. A futás eredménye az alábbi ábrákon látható.

21. ábra: A mcp25lc256_spi_eeprom.c program futtatása írás üzemmódban

Olvasás üzemmódban (ha 'w'-től különböző parancsot adunk meg) a program a memória elejétől kezdve 64 karakteres egységekben olvassa ki és írja ki a képernyőre a korábban elmentett szöveget. A kiolvasás egy újabb karakter begépelésével folytatódik. A fenti ábrán bemutatott írás után az olvasás üzemmódban az alábbi eredményt kapjuk.

22. ábra: Az mcp25lc256_spi_eeprom.c program futtatása olvasás üzemmódban

SPI master-slave kommunikáció két mikrovezérlővel

A PIC24 mikrovezérlőt SPI slave üzemmódban is használhatjuk, ha  például külső eszközzel, vagy egy másik mikrovezérlővel akarjuk meghajtani SPI porton keresztül. Az alábbiakban egy olyan példát mutatunk be, amelyikben két mikrovezérlő kommunikál egymással SPI porton keresztül. Az egyik a master funkciót látja el, s ugyanez a mikrovezérlő kommunikál a PC-vel is. A másik mikrovezérlő pedig slave módban arra vár, hogy a master-től kapjon egy sor szöveget, amit azután megfordítva visszaküld. A két mikrovezérlő természetesen két külön programot igényel: a master-mikrovezérlőn az spi_master_revstring.c, a slave vezérlőn pedig az spi_slave_revstring.c program fut.

Nincs különösebb jelentősége, de a PIC-kwik projekt honlapján közzétett projekt miatt figyelmeztetjük az olvasót, hogy slave mikrovezérlőnek itt mi nem PIC24HJ128GP502 típusú mikrovezérlőt használtunk, hanem a vele láb kompatibilis dsPIC33FJ128GP802 mikrovezérlőt. Ez két dolgot jelent:
  1. Mostantól kezdve a PIC-kwik projektben közzétett PIC24 támogató programkönyvtár a dsPIC33 mikrovezérlőket is támogatja.
  2. Ha más mikrovezérlőt szeretnénk használni slave-ként, akkor a megnyitott projektben a Configure/Select Device menüben át kell állítani a kiválasztott eszköz típusát. Mivel a slave esetében nem használunk bootloadert, így az spi_slave_revstring.c program projektjében nem adtunk meg linker állományt, így azt nem is kell módosítani az eszköztípus megváltoztatásakor.
A két mikrovezérlő összekapcsolását és az áthelyezhető SPI1 periféria láb hozzárendeléseit az alábbi ábrán láthatjuk.


23. ábra: Az SPI master és slave mikrovezérlők összekapcsolása

Ahogy az alábbi fényképen is láthatjuk, a programok kipróbálásához a Kísérleti Áramkor c. fejezetben ismertetett kapcsolást két példányban építettük meg, s az elsőt kiegészítettük egy PIC18F14K50 mikrovezérlővel kialakított USB-UART átalakítóval. A feszültségstabilizátor most nem kell, hiszen programozáskor a PICkit2, az USB-UART konverter használatakor pedig maga a konverter szolgáltat tápfeszültséget.  


24. ábra: Az SPI master - slave kapcsolat kipróbálására épített kísérleti áramkör

A képen baloldalt a korábban is használt PIC24HJ128GP502 mikrovezérlőt tartalmazó kísérleti áramkör, ami az RB10, RB11 lábakon  keresztül (USB-UART átalakítóval) kommunikál a PC-vel. Ezen a vonalon tölthetjük le a Bully Bootlader program segítségével a lefordított spi_master_revstring.c programot, s kommunikálhatunk
a letöltő program terminál ablakát használva. A kép jobboldalán egy ugyanilyen áramkört építettünk meg, a soros porti kommunikáció nélkül (ebben a példában a slave csak a masterrel kommunikál). Elhagytuk a RESET gombot is, csak Power on Reset van. Mindkét mikrovezérlő a belső órajel generátorról megy, kb. 80 MHz-es frekvencián.

A program működése során az alábbi adatmozgások történnek:
  1. A master kiírja a szokásos üdvözlő szöveget, majd egy szövegsort vár a PC-től
  2. A beérkezett szövegsort SPI buszon átküldi a slave-nek.
  3. A slave a beérkezett szövegsort megfordítja a  reverseString eljárás segítségével.
  4. A slave ezután '1'-be állítja a Slave Output Ready (a szolga kimenete kész) vonalat, ezzel jelzi a master számára, hogy kész az adatátvitelre.
  5. A master visszaolvassa a megfordított szöveget, és továbbküldi kiírásra a PC-nek.
  6. A slave közben '0'-ra állítja a Slave Output Ready kimenetét.
A slave programja az inicializálások és az életjelző LED villogtatásán kívül interrupton fut. Mivel mind a vétel, mind a küldés ugyanazt a megszakítási vektort (_SPI1Interrupt) aktiválja, egy állapotjelző változót (e_mystate) és egy egyszerű állapotgépet használunk a lehetséges állapotok megfelelő kezesére. A két fő állapot a szövegbeolvasás (STATE_WAIT_FOR_STRING) és a szövegküldés (STATE_SEND_REV_STRING). Az utóbbin állapoton belül azonban meg kell különböztetnünk egy speciális esetet: az utolsó karakter küldését (STATE_LAST_REVCHAR_STRING). Ekkor kell elvégeznünk ugyanis azokat a beállításokat (buffermutató alaphelyzetbe állítása, következő állapot megnevezése), amelyek lehetővé teszik, hogy a következő megszakításkor a vétel megfelelő kezdőállapotból kezdődjön. A slave programja az alábbi listán látható.

8. lista: Az  spi_slave_revstring.c program listája 
#include "pic24_all.h"
void reverseString(volatile char *psz_s1, volatile char *psz_s2);

#define CONFIG_SLAVE_ORDY() CONFIG_RB2_AS_DIG_OUTPUT()
#define SLAVE_ORDY _LATB2

//-- A lehetséges állapotok felsorolása
typedef enum  {
  STATE_WAIT_FOR_STRING,
  STATE_SEND_REV_STRING,
  STATE_LAST_REVCHAR_STRING,
} STATE;

volatile STATE e_mystate = STATE_WAIT_FOR_STRING;

#define BUFSIZE 63                    //Bufferméret
volatile char  sz_1[BUFSIZE+1];
volatile char  sz_2[BUFSIZE+1];
volatile uint16 u16_index;

/** SPI1 programmegszakításának kiszolgálása.
 *  Az interrupt kiszolgálásakor három állapotot
 *  különböztetünk meg:
 *  - STATE_WAIT_FOR_STRING karakterfüzér beolvasása
 *  - STATE_SEND_REV_STRING a megfordított szöveg karaktereinek kiküldése
 *  - STATE_LAST_REVCHAR_STRING az utolsó karakter kiküldése
 */

void _ISR _SPI1Interrupt (void) {
  uint16 u16_tmp;
  switch (e_mystate) {
    case STATE_WAIT_FOR_STRING:
      sz_1[u16_index] = SPI1BUF;     //Karakter érkezett, elhelyezzük a bufferben
      u16_index++;
      if (sz_1[u16_index-1] == 0) {
        reverseString(sz_1,sz_2);    //beérkezett a teljes karakterfüzér, fordítsuk meg
        u16_index = 0;
        SPI1BUF = sz_2[u16_index];   //A megfordított szöveg első karaktere SPI1BUF-ba
        u16_index++;
        SLAVE_ORDY = 1;              //jelezzük a MASTER-nek, hogy készen állunk
        e_mystate = STATE_SEND_REV_STRING;
      }
      break;
    case STATE_SEND_REV_STRING:
      u16_tmp = SPI1BUF;             //muszáj kiolvasni SPIBUF tartalmát, de eldobjuk
      //SPI1BUF-ba helyezzük a következő karaktert, hogy a master olvashassa
      SPI1BUF = sz_2[u16_index];
      u16_index++;
      if (sz_2[u16_index-1] == 0) {
        SLAVE_ORDY = 0;              //Ez az utolsó karakter, nincs több adat
        e_mystate = STATE_LAST_REVCHAR_STRING;
      }
      break;
    case STATE_LAST_REVCHAR_STRING:
      u16_index = 0;
      u16_tmp = SPI1BUF;             //muszáj kiolvasni SPIBUF tartalmát, de eldobjuk
      //az imént értünk a szöveg végére, várjuk a következő karakterfüzért
      e_mystate = STATE_WAIT_FOR_STRING;
      break;
    default:
      e_mystate = STATE_WAIT_FOR_STRING;
  }
  _SPI1IF = 0;                       //programmegszakítás jelzőbit törlése
}

/** Megfordítja egy tömbben tárolt szöveg karaktereinek sorrendjét.
 *  Az eredményt egy másik tömbben tároljuk el, a végén nullával lezárva.
 *  \param psz_s1 mutató a bemeneti adatbuffer kezdetéhez
 *  \param psz_s2 mutató a kimeneti adatbuffer kezdetéhez
 */
void reverseString(volatile char *psz_s1, volatile char *psz_s2) {
  volatile char *psz_s1end;
  if (!(*psz_s1)) {
    *psz_s2 = 0;                     //psz_s1-ben nincs több karakter, visszatérés
    return;
  }
  psz_s1end = psz_s1;                //megkeressük a szöveg végét
  while (*psz_s1end) psz_s1end++;
  psz_s1end--;                       //visszalépünk egyet, az első nem nulla bájtra
  while (psz_s1end != psz_s1) {      //szöveg másolás fordított sorrendben
    *psz_s2 = *psz_s1end;
    psz_s1end--;
    psz_s2++;
  }
  *psz_s2 = *psz_s1end;              //Az utolsó bájt másolása
  psz_s2++;
  *psz_s2 = 0;                       //nullával zárjuk a kimenő szöveget
}


/** Az SPI1 modul konfigurálása 8 bites slave módba. 
 *  Az órajel polaritásának és fázisának definiálása.
 *  Az SPI1 slave programmegszakításos üzemmódjának beállítása.
 */
void configSPI1slave(void) {
  //nem kell előszámlálót beállítani, mert az órajelet a master adja
  SPI1CON1 = CLK_POL_ACTIVE_HIGH |   //órajel aktív magas (CKP = 0)
             SPI_CKE_ON          |   //kimenet aktív/inaktív átmenetkor (CKE=1)
             SLAVE_ENABLE_ON     |   //SLAVE mód
             SPI_MODE8_ON        |   //8-bites mód
             MASTER_ENABLE_OFF;      //master mód letiltva
  //-- A ki- és bemenetek konfigurálása: SDO, SCLK, SDI, SS, SLAVE_ORDY
  CONFIG_SDO1_TO_RP(6);              //RP6 legyen SDO
  CONFIG_RP6_AS_DIG_PIN();           //analóg funkció letiltása
  CONFIG_SCK1IN_TO_RP(7);            //RP7 legyen SCLK
  CONFIG_RP7_AS_DIG_PIN();           //analóg funkció letiltása
  CONFIG_SDI1_TO_RP(5);              //RP5 legyen SDI
  CONFIG_RP5_AS_DIG_PIN();           //analóg funkció letiltása
  CONFIG_SS1IN_TO_RP(3);             //Slave Select bemenet konfigurálása
  CONFIG_RP3_AS_DIG_PIN();           //analóg funkció letiltása
  CONFIG_SLAVE_ORDY();               //Slave Data Ready kimenet konfigurálása
  SLAVE_ORDY = 0;                    //kezdetben nincs küldeni való adat,
  u16_index = 0;                     //üres a buffer
  _SPI1IF = 0;                       //SPI1 interrupt jelzőbit törlése
  _SPI1IP = 3;                       //SPI1 interrupt prioritás beállítása
  _SPI1IE = 1;                       //SPI1 interrupt engedélyezése
  SPI1STATbits.SPIROV = 0;           //SPI1 túlcsordulás jelző törlése
  SPI1STATbits.SPIEN = 1;            //az SPI modul engedélyezése
}

int main (void) {
  configClock();                     //a slave esetében nincs UART kapcsolat
  configHeartbeat();
  configSPI1slave();
  while (1) doHeartbeat();
}
Az SPI1 periféria konfigurálásnál a slave eszköz beállítása eltér a korábbiaktól. Az eltérések:
  1. Engedélyezzük, hogy az SPI1 alrendszer megszakítsa a programot.
  2. Az SPI1CON regiszterben a SLAVE ENABLE bitet állítjuk '1'-be, a MASTER ENABLE bitet pedig '0'-ba.
  3. Nem kell foglalkozni a frekvencia osztók beállításával, mert az órajelet a master-től kapjuk.
  4. Az SCK1 órajelet bemenetként definiáljuk:
  CONFIG_SCK1IN_TO_RP(7);            //RP7 legyen SC
A master esetében az SPI1 periféria beállítása a korábbi mintapéldákban bemutatott módon történik. A korábbi példákkal való egyezés érdekében (hogy a dokumentáláshoz használt DOXYGEN program meg ne zavarodjon) most is 5 MHz-es SPI buszfrekvenciát állítottunk be, de a mikrovezérlőink a maximális (10 MHz-es) buszfrekvencián is képesek kommunikálni. A másodlagos osztó átállításával (SEC_PRESCAL_2_1 helyett SEC_PRESCAL_1_1 álljon) konfigurálhatjuk be magunknak a 10 MHz-es buszfrekvenciát.

9. lista: Az  spi_master_revstring.c program listája 
#include "pic24_all.h"
#define CONFIG_SLAVE_ENABLE() CONFIG_RB3_AS_DIG_OUTPUT()
#define SLAVE_ENABLE()      _LATB3 = 0  //alacsony szint az aktív állapot
#define SLAVE_DISABLE()     _LATB3 = 1

#define CONFIG_SLAVE_ORDY() CONFIG_RB2_AS_DIG_INPUT()
#define SLAVE_ORDY _RB2

/** Az SPI1 modul konfigurálása 10 MHz-es, 8 bites master módba. 
 *  Az órajel polaritásának és fázisának definiálása.
 */
void configSPI1(void) {
  //spi clock = 40MHz/2*4 = 40MHz/4 = 5 MHz
  SPI1CON1 = SEC_PRESCAL_2_1 |           //2:1 másodlagos osztó
             PRI_PRESCAL_4_1 |           //4:1 elsődleges
             CLK_POL_ACTIVE_HIGH |       //órajel aktív magas (CKP = 0)
             SPI_CKE_ON          |       //kimenet aktív/inaktív átmenetkor (CKE=1)
             SPI_MODE8_ON        |       //8-bites mód
             MASTER_ENABLE_ON;           //master mód
  //-- A ki- és bemenetek konfigurálása: SDO, SCLK, SDI, SS, SLAVE_ORDY
  CONFIG_SDO1_TO_RP(6);                  //RP6 legyen SDO
  CONFIG_RP6_AS_DIG_PIN();               //analóg funkció letiltása
  CONFIG_SCK1OUT_TO_RP(7);               //RP7 legyen SCLK
  CONFIG_RP7_AS_DIG_PIN();               //analóg funkció letiltása
  CONFIG_SDI1_TO_RP(5);                  //RP5 legyen SDI
  CONFIG_RP5_AS_DIG_PIN();               //analóg funkció letiltása
  CONFIG_SLAVE_ENABLE();                 //Slave Select kimenet konfigurálása
  CONFIG_SLAVE_ORDY();                   //Slave Data Ready bemenet konfigurálása
  SLAVE_DISABLE();                       //Kezdetben ne legyen megszólítva a slave
  SPI1STATbits.SPIEN = 1;                //az SPI modul engedélyezése
}

//-- A lehetséges állapotok felsorolása
typedef enum  {
  STATE_GET_IN_STRING = 0,               //Szöveg beolvasása
  STATE_GET_REV_STRING,                  //A megfordított szöveg vétele
} STATE;

/** A szövegbuffer tartalmának kiküldése az SPI1 csatornán.
 *  \param psz_s1 mutató a buffer kezdetéhez
 */
void sendStringSPI1(char* psz_s1) {
  SLAVE_ENABLE();
  while (*psz_s1) {
    ioMasterSPI1(*psz_s1);
    psz_s1++;
  }
  ioMasterSPI1(*psz_s1);                 //lezáró nulla küldése
  SLAVE_DISABLE();
}

/** Szöveg beolvasása az SPI1 csatornán.
 *  \param psz_s1 mutató az adatbuffer kezdetéhez
 *  \param u16_maxCount az elfogadott karakterek maximális száma
 */
void getStringSPI1(char* psz_s1, uint16 u16_maxCount) {
  uint16 u16_i = 0;
  if (!u16_maxCount) return;
  SLAVE_ENABLE();
  do {
    *psz_s1 = ioMasterSPI1(0);           //üres bájt küldése, hogy olvashassunk
    psz_s1++;
    u16_i++;
  } while (*(psz_s1-1) && (u16_i <u16_maxCount));
  SLAVE_DISABLE();
  psz_s1--;
  *psz_s1 = 0;                          //biztosítja, hogy a szöveget nulla kód zárja
}


#define BUFSIZE 63                     //Bufferméret
char  sz_1[BUFSIZE+1];

int main (void) {
  STATE e_mystate;
  configBasic(HELLO_MSG);
  configSPI1();
  e_mystate = STATE_GET_IN_STRING;
  while (1) {
    switch (e_mystate) {
      case STATE_GET_IN_STRING:
        inStringEcho(sz_1,BUFSIZE);       //szöveg fogadása a terminálról
        if (*sz_1) {
          sendStringSPI1(sz_1);
          e_mystate = STATE_GET_REV_STRING;
        }
        break;
      case STATE_GET_REV_STRING:
        if (SLAVE_ORDY) {
          getStringSPI1(sz_1,BUFSIZE+1);
          outString(sz_1);                //a megfordított szöveg kiírása
          outString("\n");
          e_mystate = STATE_GET_IN_STRING;
        }
        break;
      default:
        e_mystate = STATE_GET_IN_STRING;
    }
    doHeartbeat();
  }
}

Az SPI slave mikrovezérlő programjának beégetése

A slave eszköz programozásához szükségünk lesz egy programozó készülékre (pl. PICkit2). Az alábbi képen látható, hogy hogyan végeztük el a dsPIC33FJ128GP802 mikrovezérlő programjának beégetését.
  

25. ábra: Az SPI slave mikrovezérlő programjának beégetése

A dsPIC33FJ128GP802 mikrovezérlő programozásának menete:
  1. A PICkit2 programozót az USB kábellel a PC-hez csatlakoztatjuk. A PICkit ICSP csatlakozóját hagyjuk szabadon (még ne csatlakoztassuk a mikrovezérlőhöz)! 
  2. Indítsuk el a PICkit2 kezelői programját (még mindig ne csatlakoztassuk a mikrovezérlőt)! A program jelzi, hogy felismerte a PICkit2 programozót, de nem talált felismerhető mikrovezérlőt. Igaza van! 
  3. A Device Family menüben válasszuk ki a dsPIC33 családot!
  4. Most csatlakoztassuk a PICkit2 programozót a kísérleti áramkörhöz 5 vezetékkel, a 25. ábrán látható módon.
  5. A Device Family menüben válasszuk ki újra a dsPIC33 családot! Ha a felismertetés sikeres volt, akkor a 26. ábrán láthatóhoz hasonlóan megjelenik a felismert eszköz típusszáma.
  6. A File/Import Hex menü pontban olvastassuk be a spi_slave_revstring.hex állományt!  
  7. Kattintsunk rá a Write gombra, s várjuk meg a programozás és visszaolvasás végét!
  8. Ha a 27. ábrán láthatóhoz hasonlóan zöld alapon a "Programming Successful" felirat jelenik meg, akkor sikeres volt a program beégetése. Bonthatjuk a kapcsolatot, s leállíthatjuk a PICkit2 kezelő programját!


26. ábra: Az SPI slave mikrovezérlő felismertetése


27. ábra: Képernyőkép a SPI slave mikrovezérlő sikeres programozása után

A master mikrovezérlő spi_master_revstring.hex programját a szokásos módon a Bully Bootloader program segítségével töltjük le, s a kommunikációhoz a letöltő program terminál funkcióját használjuk. A program futásának eredménye az alábbi ábrán látható.

Megjegyzés: A szöveg beírásához az ablak felső részében található beíró sávot használtuk, s a Send&\n gomb lenyomásával küldtük el a beírt szöveget (így egy sortörést is beszúrunk az elküldött szöveg végére).

27. ábra: Az SPI master és slave mikrovezérlők programjának futási eredménye

MCP41xxx digitális potenciométer vezérlése

A Microchip MCP41xxx integrált áramkörei 256 lépésben szabályozható potenciométerek, 10 kΩ, 50 kΩ és 100 kΩ értékű ellenállással (ennek megfelelően a típusszáma MCP41010, MCP41050 vagy MCP41100). Az IC 8 lábú PDIP vagy SOIC tokozású, a lábkiosztás a 28. ábrán látható. A potenciométer csúszkája és valamelyik végpontja közötti ellenállás az adatregiszterbe írt számmal lineárisan változik és SPI felületen, digitálisan vezérelhető. Az ábra jelöléseivel PW0 a csúszka kivezetése, PA0 és PB0 a potméter két végpontja. Amikor az adatregiszterbe '0' értéket írunk, a csúszka a PB0 végponttal van összekötve.                                                                          28. ábra

Bekapcsoláskor a csúszka automatikusan középső állásba kerül (az adatregiszter tartalma 0x80 lesz). Az SPI illesztő CKP=0, CKE=0 vagy CKP=1, CKE=1 módban használható. Az IC tápfeszültsége 2,7 - 5,5 V közötti érték lehet. A differenciális és integrális nonlinearitás (vagyis az egyes lépések hibája, és a lineáris függéstől való legnagyobb eltérés) +/- 1 LSB (ahol LSB a legkisebb lépés, vagyis a maximális érték 1/256-od része).

A digitális potenciométerek sokoldalúan felhasználhatók feszültségosztásra,  digitális erősítésszabályozására, szűrőáramkörök hangolására, szabályozó áramkörökben és sok más alkalmazásnál. Az alábbi példában egyszerű feszültségosztóként használjuk az MCP41010 digitális potenciométert: a VDD tápfeszültséget osztjuk le vele. A kimenő jellel digitálisan vezérelhetjük például egy LCD alfanumerikus kijelző kontraszt feszültségét.
 
29. ábra: A kísérleti áramkör kiegészítése egy digitális potenciométerrel

A PIC-kwik projekt PIC24 kísérleti áramkörét a fenti ábrán látható módon egészítettük ki egy MCP41010 digitális potenciométerrel. Mint minden digitális IC esetében, itt is célszerű a tápfeszültség (VDD) és a közös pont (VSS) közé egy hidegítő kondenzátort kötni. Ha azt akarjuk, hogy az adatregiszterbe írt növekvő számokkal összhangban a kimenő feszültség is növekedjen, akkor a potenciométer RB0 végét kell a földre kötni, a PA0 kivezetést pedig a tápfeszültségre.  

Az adatregiszter írása

Az adatregiszter írása 16 bit kiküldését igényli. Az első bájt a parancsbájt (esetünkben ennek értéke mindig 0x11 legyen, más értéknek nincs értelme), a második bájt pedig az adatregiszterbe írandó 8 bites adat. Az adatbitek bekapuzása az SCK órajel felfutó élénél történik. Az adatregiszter beírása pedig a CS vezérlőjel felfutó élénél megy végbe.


30. ábra: A digitális potenciométer vezérlése (adatbeírás)

A parancsbájt bitjeinél esetünkben csak a C1,C0 = 01; P1,P0=01 bitkombinációnak van értelme (a 0. sorszámú kimenetet vezérlő adatregiszter írása). A többi bitkombináció csak más típusok (pl. MCP42xxx) esetében használható, ahol egy tokban két digitális potenciométer található (tehát az 1. sorszámú kimenet is választható) és SHUTDOWN (lekapcsolás) parancs is kiadható.

10. lista: Az  mcp41xxx_spi_pot.c program listája 
#include "pic24_all.h"
#include <stdio.h>

#define CONFIG_SLAVE_ENABLE() CONFIG_RB8_AS_DIG_OUTPUT()
#define SLAVE_ENABLE()        _LATB8 = 0   //Slave kiválasztása
#define SLAVE_DISABLE()       _LATB8 = 1   //Slave eszköz letiltása

/** Az SPI1 modul konfigurálása 5 MHz-es, 8 bites master módba. 
 *  Az órajel polaritásának és fázisának definiálása.
 *  Csak írásra (write only) használjuk, az SDI vonalra nincs szükségünk
 */
void configSPI1_WO(void) {
//-- SPI órajel = 40MHz/2*4 = 40MHz/8 = 5 MHz
  SPI1CON1 = SEC_PRESCAL_2_1 |             //2:1 másodlagos osztó
             PRI_PRESCAL_4_1 |             //4:1 elsődleges
             CLK_POL_ACTIVE_HIGH |         //órajel aktív magas (CKP = 0)
             SPI_CKE_ON          |         //kimenet aktív/inaktív átmenetkor (CKE=1)
             SPI_MODE8_ON        |         //8-bites mód
             MASTER_ENABLE_ON;             //master mód
//-- A kimenetek konfigurálása: SDO, SCLK, CE
  CONFIG_SDO1_TO_RP(6);                    //RP6 legyen SDO
  CONFIG_RP6_AS_DIG_PIN();                 //analóg funkció letiltása
  CONFIG_SCK1OUT_TO_RP(7);                 //RP7 legyen SCLK
  CONFIG_RP7_AS_DIG_PIN();                 //analóg funkció letiltása
  CONFIG_SLAVE_ENABLE();                   //Chip Enable kimenet konfigurálása
  SLAVE_DISABLE();                         //Kezdetben ne legyen megszólítva a slave
  SPI1STATbits.SPIEN = 1;                  //az SPI modul engedélyezése
}

/** A digitális potenciométer csúszkájának (wiper) beállítása.
 * \param u8_i a WIPER regiszterbe írandó adat.
 */
void setPotWiper(uint8 u8_i) {
  SLAVE_ENABLE();                         //kiadjuk a Chip Enable jelet
  ioMasterSPI1(0x11);                     //a csúszka regiszter címe
  ioMasterSPI1(u8_i);                     //beállítjuk a WIPER regisztert
  SLAVE_DISABLE();                        //megszüntetjük a Chip Enable jelet
}

#define BUFSIZE 15
char  sz_1[BUFSIZE+1];                    //Bemeneti buffer soros kommunikációhoz

int main (void) {
  uint16 u16_pv;
  configBasic(HELLO_MSG);                 //A rendszer konfigurálása
  configSPI1_WO();                        //Az SPI modul konfigurálása
  while (1) {
    outString("Input decimal value (0-255): ");
    inString(sz_1,BUFSIZE);              //soros portról karaktersorozat beolvasása
    sscanf(sz_1,"%d",  (int *)&u16_pv);  //számként értelmezzük a karaktersort
    printf("\nSending %d to pot.\n",u16_pv);
    setPotWiper(u16_pv & 0x00FF);        //a potméter beállítása a megadott értékre
  }
}
A program elején definált makrók a CS bemenet vezérléséhez kellenek. Az SPI alrendszer inicializálása master módban a korábban bemutatott módon történik. Az egyetlen eltérés az, hogy most nincs szükségünk az SDI bemenetre. A potenciométer adat regiszterét (vagyis a potméter csúszkáját) a setPotWiper(d) eljárással állítjuk be, s a 0-255 közötti d paraméter a beírandó érték.

A főprogram az inicializálások után egy 0-255 közötti számot vát a terminálról, majd ezt a számot kiküldi a digitális potenciométer adat regiszterébe.