Categories
AVR-ek programozása

AVR IO-portok hozzáférése

Az előző részben megismerkedtünk még több regiszterrel, ezek közül is a 16-bitesekkel. Most viszont megnézzük végre, hogyan használhatók az AVR portjai.

Minden AVR különböző mennyiségű GPIO-Regisztert kezel (GPIO – General Purpose Input/Output). Ezek a regiszterek azért szükségesek mert:

  • Meghatározzák a kontroller melyik kivezetése legyen kimenet, melyik legyen bemenet.
  • Megadják a kimenetek állapotát.
  • Megállapítják a benetek állapotát

A GPIO-ba tehát digitális állapotok íródnak be, illetve olvasunk abból. Például egy kimeneten feszültség jelenik meg, vagy a bemenet jelei tárolódnak benne. Az adatlap Electrical Charateristics/DC Characteristics részében találhatóak a feszültség értékek (V_OL, V_OH a kimeneteké, V_IL, V_IH a bemeneteké).

Az analóg bemeneti értékek feldolgozását, és analóg jelek előállítását egy későbbi fejezetben vesézzük ki. Az AVR minden portját regiszterek vezérlik. Minden portnak 3 regisztere van:

DDRx:
A PORTx adatirány regisztere. Az x helyére a port betűjele kerül: A,B,C,D stb ( az alkalmazott kontroller portjainak számától függően).

PINx:
A PORTx bemeneti címe. A PINx –ben lévő bitek írják le a porton bementként definiált lábak pillanatnyi állapotát. Ha a bit értéke 1, magas, ha 0, akkor alacsony szinten van a bemenet.

PORTx:
A PORTx adatregisztere. Ez a regiszter a port kimeneteinek vezérléséhez szükséges. A DDRx –ben bemenetként beállított lábakhoz a PORTx a belső felhúzóellenállást aktiválja, vagy kikapcsolja azt (1 = aktív).

Adatirány meghatározása

Először az alkalmazott lábak adatirányát kell meghatározni. Ahhoz hogy ezt megtegyük, a port adatirányregiszterét kell leírnunk. A porton kimenetként használni kívánt lábakat úgy állíthatjuk be, hogy a hozzájuk tartozó helyiértékű bitnek értéket adunk (1). Ha egy lábat bemenetként kívánunk beállítani, a hozzátartozó helyiértékű bit értékét törölni kell (0).

+---+---+---+---+---+---+---+---+
| 0 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | BIT ÉRTÉKE
+---+---+---+---+---+---+---+---+
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | BIT HELYIÉRTÉKE
+---+---+---+---+---+---+---+---+

Tegyük fel, hogy a B porton a 0-tól 4-ig számozott lábakat kimenetkét, a megmaradt 5-től 7-ig számozott lábakat bemenetként akarjuk beállítani. Ehhez a B port adatirányregiszteréhez a következő bitkonfigurációt kell hozzárendelni:

#include <avr/io.h>

int main()
 {
     // A 0,1,2,3 és 4-ik biteknek értéket adunk
     // Binárisan 00011111 = Hexadecimálisan 1F
     DDRB = 0x1F; /* közvetlen hozzárendelés - átláthatatlan */

     // Átláthatóbb alternatíva – Bináris írásmód
     DDRB = 0b00011111; /* közvetlen hozzárendelés - átlátható */

     /* Részletes írásmód: azonosítható funkció, többet kell gépelni,
     de átláthatóbb */
     DDRB = (1 << DDB0) | (1 << DDB1) | (1 << DDB2) | (1 << DDB3) | (1<< DDB4);
 }

Az 5-től 7ig számozott lábak bemenetként működnek. További példák:

// A PORTB minden lába kimenet:
 DDRB = 0xff;

// A nulladik láb újra bemenet, a többi változatlan marad:
 DDRB &= ~(1<<DDB0);


// a harmadik, és negyedik láb bemenet lesz, a többi változatlan marad:
 DDRB &= ~((1<<DDB3) | (1<<DDB4));


// a nulladik és a harmadik láb ismét kimenet lesz, a többi változatlan marad:
 DDRB |= (1<<DDB0) | (1<<DDB3);


// Minden láb bemenet:
 DDRB = 0x00;

 

Az I/O regiszter számára előre definiált Bitek

A Bitek ( Például: PCx, PINCx, DDCx ) az avr-libc io*.h fáljában vannak definiálva, ezek csupán a jobb olvashatóságot szolgálják. Ezeket a definíciókat nem kötelező használni, lehet egyszerűen PAx, PBx, PCx, stb. Is alkalmazni, akkor is ha a DDRx vagy a PINx regiszterekhez akarunk hozzáférni. A fordító számára a (1<<PC7), (1<<DDC7), (1<<PINC7) kifejezések az (1<<7) kifejezéssel azonosak. (Pontosabban az előfeldolgozó a (1<<PC7) kifejezést kicseréli (1<<7) –el )

Itt látható egy részlet az ATmega32 C portjáról, az iom32.h fájlból:

/* PORTC */
#define PC7 7
#define PC6 6
#define PC5 5
#define PC4 4
#define PC3 3
#define PC2 2
#define PC1 1
#define PC0 0

/* DDRC */
#define DDC7 7
#define DDC6 6
#define DDC5 5
#define DDC4 4
#define DDC3 3
#define DDC2 2
#define DDC1 1
#define DDC0 0

/* PINC */
#define PINC7 7
#define PINC6 6
#define PINC5 5
#define PINC4 4
#define PINC3 3
#define PINC2 2
#define PINC1 1
#define PINC0 0

Kimenetek

Ha egy kimenetként definiált lábat logikai 1 szintre akarunk állítani, a portregiszterben hozzátartozó helyiértékű bitnek adunk értéket.

#include <avr/io.h>

...
PORTB = 0x04; /* vagy PORTB = (1 << PB2) */

// Áttekinthetőbb alternatívírásmód
PORTB = 0b0000100; /* Közvetlen hozzárendelés – átlátható */

A fenti kódrészletben található utasításokkal a PB2 kimenetnek adunk értéket. ( Figyeljünk arra, hogy a Bitek helyiértékei 0-tól kezdőnek, és nem 1-től ) A hozzárendelő operátor = használatával minden egyes bit a porton egyszerre kap értéket. Szükség lehet olykor olyan műveletre is, amikor csak egy vagy néhány láb állapotát szeretnénk megváltoztatni. Ilyenkor a változtatni kívánt láb portjának minden bit-jét először be kell olvasni – tehát meg kell állapítani hogy van e értéke vagy sem – és aztán csak a kívánt biteket megváltoztatni. Tehát ha csak például a B port 3. lábát ( 2. Helyiértékű bit) akarjuk HIGH szintre állítani, és a többit változatlanul hagyni, akkor az alábbi formát alkalmazzuk:

#include <avr/io.h>

...
PORTB = PORTB | 0x04; /* szebben: PORTB = PORTB | ( 1<

/* egyszerűbben a |= operátor használatával: */
PORTB |= (1<<PB2);

/* Egyszerre töbet is lehet: */
PORTB |= (1<<PB4) | (1<<PB5); /* PB4 és PB5 "HIGH" */

A lábakat kikapcsolni, tehát a kimeneteket LOW szintre állítani a következőképpen lehet:

#include <avr/io.h>
...
PORTB &= ~(1<<PB2); /* B Port 2. Bit-jét törli, így PB2 lábon LOW szint lesz */
PORTB &= ~( (1<<PB4) | (1<<PB5) ); /* PB4, és PB5 LOW szintre állítva*/

Azokban a forráskódokban amik az avr-gcc régebbi verzióihoz íródtak, az sbi és a cbi függvénnykkel oldották meg a bitenkénti értékadást, vagy törlést. Ezek a függvények a mostani verziókban már nem elérhetőek, használatuk nem lehetséges.

Abban az esetben, ha a kimenetek kezdő állapota kritikus, figyelni kell az adatirány és a kimeneti értékek beállításának sorrendjére: A kimeneti lábakat, amik kezdőértékként HIGH állapotba kell inicializálni:

Először a PORTx regiszter bitjeinek kell értéket adni.,
végül az adatirányt kimenetre állítani.

Ebből ered a következő sorrend egy eddig bemenetként, lekapcsolt felhúzó ellenállással konfigurált láb esetén:

PORTx beállítása: Belső felhúzó ellenállás aktív
DDRx beállítása: Kimenet (”HIGH”) 

Az először DDRx, és aztán PORTx sorrend egy rövid ”low-puls”-t okozhat, ami külső felhúzó ellenállásokat is ”felülbírálja”. A (kedvezőtlen) sorrend: Bemenet -> DDRx beállítása: kimenet (LOW szinten, így a PORTx- t lenullázza (reset) -> PORTx beállítása: Kimenetet magasra állítani. Lásd még az adatlap Configuring the PIN részében.

Bemenetek:

Jelcsatolás

A legegyszerűbb eset, amikor a jeleket közvetlenül egy másik digitális kapcsolásból visszük át. sőt, ha ez a kapcsolás TTL szintű, akár közvetlenül is összeköthetjük a kimenetet a kontrollerünk egyik bemeneti lábával.

Amennyiben a kapcsolás nem TTL szintű jeleket bocsát ki, úgy a szintet illeszteni (opto-csatoló, feszültségosztó, ”level-shifter” /szintillesztő/ ) kell.

A kapcsolások test pontjait magától értetődően össze kell egymással kötni. A szoftver, ami a kontrolleren fut, végső soron teljesen mindegy miként dolgozza fel a jelet, mi úgy is kizárólag a jelszinteket tudjuk vizsgálni egy mérőműszerrel a kontrollerünk lábain, hogy azokon Logikai 1, (feszültség nagyobb mint kb. 0,7 V x Vcc ), vagy Logikai 0 (feszültség kisebb mint kb. 0,2 V x Vcc ) szint van jelen. Részletes információt arról hogy egy bemeneten mekkora feszültség milyen logikai szintnek felel meg, a felhasznált kontroller adatlapjának DC Characteristics részében találhatunk.

A Port lábak állapotainak lekérdezése közvetlenül a regiszter neveiken keresztül történik.

Fontos, hogy a bemenetek lekérdezéséhez ne a PORTx port-regisztert alkalmazzuk, hanem a PINx bemeneti regisztert, különben nem a bemenetek állapotát olvassuk be, hanem a belső felhúzó ellenállások állapotát. A lábállapotok PORTx –en keresztültörténő lekérdezése PINx helyett gyakori probléma az AVR-ekel való ismerkedéskor.

Tehát ha a D port aktuális jelállapotait akarjuk lekérdezni, és egy bPortD nevű változóba elmenteni, azt a következő módon tehetjük meg:

#include <avr/io.h>
#include

...
uint8_t bPortD;
...
bPortD = PIND;
...

A C- Bitműveletekkel le lehet kérdezni a bit-ek állapotát:

#include <avr/io.h>
...
/* Lefut az utasítás ha a Bit1-nek a PINC-ben (1) értéke van */
if ( PINC & (1<<PINC1) )
{
/* itt csinálunk valamit */
}

/* Lefut az utasítás, ha Bit2-nek a PINB-ben (0) nincs értéke*/
if (!(PINB & (1<<PINB2)))
{
    /* do something.. */
}
...

Belső felhúzóellenállások

Egy AVR-ben a ki-és bemeneti Portok lábaihoz (GPIO) rendelkezésre állnak hozzákapcsolható belső felhúzó ellenállások (többnyire több 10 kΩ, ATmega16 esetében például ez az érték 20-50 kΩ). Ezek sok esetben használhatóak külső felhúzóellenállások helyett.

A belső felhúzóellenállások a tápfeszültségről (Vcc) a PORTx regiszteren keresztül kapcsolhatóak a Port lábakhoz, ha a lábak Bemenetként vannak konfigurálva.

Ha a Port egyik lábának értéke 1, akkor a felhúzó ellenállás aktív, 0 értéknél inaktív. Vagy a külső, vagy a belső felhúzó ellenállást használjuk, de sohasem a kettőt együtt.

A példában a D port minden lába bemenetként van beállítva, és minden Pull-Up aktív. A továbbiakban a PC7 láb bemenetként beállított, és annak a felhúzó ellenállása aktiválva lett, mindezt a port többi lábainak változtatása nélkül.(PC0-PC6)

#include <avr/io.h>

...
DDRD = 0x00; /* A D port minden lába bemenet */
PORTD = 0xff; /* A Port minden lábán a belső felhúzóellenállások aktívak */
...
DDRC &= ~(1<<DDC7); /* PC7 láb bemenet */
PORTC |= (1<<PC7); /* Belső felhúzóellenállás bekapcsolása a PC7 láb számára */

Nyomógombok, kapcsolók

Két módszert különböztetünk meg a mechanikus érintkezők mikrokontrollerhez való kapcsolására: Active Low, és Active High. Ez így jó magyarosan hangzik, de lássuk mik is ezek a kifejezések.

Active Low:
Ennél a módszernél az érintkező a kontroller bemeneti lába, és a test közé van kapcsolva, így nyitott kapcsoknál kizárjuk a kontroller számára nem kívánatos jeleket. A tápfeszültség, és a bemeneti kapocs közé úgynevezett Pull-Up (felhúzó) ellenállás kapcsolódik. Ez arra szolgál, hogy nyitott kapcsoknál a bemenetet logika 1 szinten tartsa.

 

Active High:
Itt az érintkező a tápfeszültség, és a bemeneti láb közé van kapcsolva. Nyitott állapotban nem lesz nem kívánatos jel a bemeneten, mivel a bemeneti láb, és a test közé van az ellenállás kapcsolva. Ez a bemenet logikai 0 szinten tartásáért felel.

 

 

A Pull-Up és Pull-Down ellenállások értékei nem kritikusak, mindenesetre ha túl magas értéket választunk, nem érjük el vele a kívánt hatást. A gyakorlatban ez az érték általában néhány 10 kΩ körül van. Az AVR-ek lábai legtöbbször rendelkeznek belső Pull-Up ellenállással, amik mint a példánkban, nyomógomboknál, mechanikus kontaktusoknál nagyszerűen alkalmazható külső Pull-Up helyett. Belső Pull-Down ellenállás viszont sajnos nincs, azt külső ellenállás formájában illeszthetjük az áramkörünkhöz.

Prell mentesítés
Minden mechanikus érintkezőnek – legyen az kapcsoló, vagy akár relé – megvan az a kellemetlen tulajdonsága, hogy prelleg. Ez azt jelenti, hogy az érintkezők záródásakor nem egyből a végleges, tökéletes kapcsolat, hanem közben a másodperc tört része többszöri ki- és bekapcsolás szerű impulzusok jönnek létre.

Ha egy mikrokontrollerrel meg kéne számolni, hogy hányszor nyomtunk meg egy gombot, ki kell küszöbölnünk a prellegést, különben a kapcsolás közbeni impulzusok is egy – egy gombnyomásnak számítanának. Ezt a jelenséget a program tervezésekor okvetlen számításba kell venni.

A következő egyszerű példában az AVR egy gombnyomás estében 200 ms-ot vár. Az időzítésre kényes alkalmazásokban másik megoldást kell használni ( Például a nyomógombállapotok lekérdezése egy Timer-Interrupt-Service-Routine- ban ).

#include <avr/io.h>
#include 
<inttypes.h>

#ifndef F_CPU
#warning "F_CPU még nem definiált, definiálva 3686400 értékkel"
#define F_CPU 3686400UL /* kvarc 3.6864 Mhz */
#endif


#include <util/delay.h> /* régi avr-libc: #include <avr/delay.h> */

/* Egyszerű függvény prellmentesítéshez */

inline uint8_t debounce(volatile uint8_t *port, uint8_t pin)
{
    if ( !(*port & (1 << pin)) )
    {
        /* a láb testre van húzva, 100ms-ot vár */
        _delay_ms(100);

        if ( *port & (1 << pin) )
        {
            /* idő a gomb elengedéséhez */
            _delay_ms(50);
            _delay_ms(50);
            return 1;
        }
    }

    return 0;
}

int main()
{
    DDRB &= ~(1 << PB0); // PB0 bemenet
    PORTB |= (1<< PB0); // Pull UP bekapcsolása

    ...

    if (debounce(&PINB, PB0))
    {
        // ha a PB0-án levő gomb le van nyomva, Ledet a PB7-en ki vagy be kapcsolni
        PORTD = PORTD ^ (1 << PD7);
    }
}

A fenti rutinnak sajnos van több hátránya is:

Csak az elengedést detektálja
A main függvényt benyomott gombnál mindig 100 ms –al késlelteti
Elveszti a gombnyomásokat, minél több teendője van a main függvénynek.

Végül csak a cikk végére értünk. Legközelebb megnézzük, hogyan lehet analóg feszültségértékeket átalakítani digitális értékekké.

Vélemény, hozzászólás?

Az email címet nem tesszük közzé. A kötelező mezőket * karakterrel jelöljük.