Categories
AVR-ek programozása

AVR-ek regiszterei 2. rész

Az előző cikkben megismerkedhettünk a 8 bites regiszterekkel. Nem is olyan nehéz mint gondoltuk volna az elején ugye? Jó formán úgy kezeljük őket, mintha változók lennének. Most többek között a 16-bites regiszterekről lesz szó.

A 16-Bites Regiszterek (ADC, ICR1, OCR1x, TCNT1, UBRR)

Az AVR-ek portregiszterei között akad bizony néhány 16-Bites is. Az adatlapokban ezeket a regisztereket általában az “L“ (Low-Byte) és “H“ (High-byte) utótaggal találhatjuk meg. Az avr-libc ezeket a változókat az említett utótag nélkül definiálja így ezekhez a regiszterekhez közvetlenül hozzáférhetünk, mint az ADC, vagy a TCNT1 regiszter esetében is.

#include <avr/io.h>
 uint16_t valtozo;

// A változó értéke a legutolsó átalakítás értékét veszi fel
 valtozo = ADC;

Más regiszterek esetében (mint például a BAUDRATE regiszter) A High, és Low része nem közvetlen egymás mellett vannak az SFR területén, úgyhogy a 16 bites hozzáférés nem lehetséges, és úgy kell össze tákolni a hozzáférést:

#include <avr/io.h>

#ifndef F_CPU
#define F_CPU 3686400 #endif
#define UART_BAUD_RATE 9600
...
uint16_t baud = FCPU / (UART_BAUD_RATE * 16L) -1;

UBRRH = (uint8_t) (baud >> 8);
UBRRL = (uint8_t) baud;
...

Néhány AVR-ben (pl Atmega8, vagy Atmega16) az UBRRH és az UCSRC regiszterek ugyanazon a memóriacímen osztoznak. Azt, hogy az AVR mégis eltudja dönteni hogy a két regiszter közül melyikhez férjen hozzá, a Bit7 (URSEL) értéke határozza meg. A 1000 0011 (0x83) ezért az UCSRC-t címezi meg, és ebbe értékként 3-at ad neki. Ha 0000 0011 (0x03) az URSEL értéke, akkor UBRRH-t címezi, aminek szintén 3-at ad értékként. Minden adatregiszter – különösen a 16-Bites időzítők, és az ADC-k esetében is – hozzáférésekor szükséges, hogy ezek az adatok szinkronizálva legyenek. Ha például kiolvastuk egy 16-Bites időzítő számláló regiszterének a High-byte-ját, és ezt megelőzően a Low-byte olvasásakor a Low-byte túlcsordulása történt, egy teljesen értelmetlen értéket kapunk eredményül. A komparátor regisztert is szinkronban kell írni, mert az különben nem kívánatos eseményekhez vezethet.

A probléma az ADC-nél abban rejlik, hogy a két regiszter közötti hozzáféréskor befejeződhet egy átalakítási folyamat, és az ADC egy új eredményt ír ADCL-be, és ADCH-ba, holott a High-byte, és a Low-byte már nem tartozik össze.

Az efféle adatszemét keletkezésének megakadályozására mindkét esetben szinkronizáció történik, ami a Low-byte hozzáférésén keresztül aktiválódik:

Az időzítő regisztereknél (ez minden 16-Bites TCNT-, OCR-, és ICR időzítő regiszterre vonatkozik) a Low-byte minden olvasási hozzáférésekor a High-byte tartalma automatikusan egy átmeneti regiszterben tárolódik, ami egyébként kívülről nem látható. A High-byte hozzáférésekor ennek az ideiglenes regiszternek az értéke lesz kiolvasva.

A fent említett regiszterek egyikének írásakor a High-byte az említett átmeneti regiszterben lesz gyorsítótárazva, és csak a Low-byte írása után veszi át a célregiszter az értéket.

Ez a következő sorrendet jelenti:

Olvasáskor: Először Low-byte, majd High-byte

Íráskor: Először High-byte majd Low-byte

Arra figyeljünk oda, hogy az összes 16-Bites regiszter számára csak egyetlen egy átmeneti regiszter létezik, tehát ha egy megszakítás közbeavatkozásakor ennek az átmeneti regiszternek a tartalma módosul, a félbeszakadt hozzáférés eredménye nem lesz helyes, és megint csak adatszemetet kapunk. Ha interruptokkal dolgozunk, szükség lehet egy ilyen 16-Bites regiszter hozzáférése előtt a megszalítás feldolgozást kikapcsolni.

Az ADC regiszterek (ADCH/ADCL) szinkronizálása másképp van megvalósítva. Itt a Low- byte olvasásakor (az ADCH/ADCL csak olvasható) az ADC mindkettő regiszteréhez való hozzáférése egészen addig zárolva marad, amíg a High-byte-ot ki nem olvassuk. Ekközben az ADC nem tud új értéket adni az ADCH/ADCL számára, tehát a közbeni konverziók értéke elveszik.

Alapszabály: Ha ADCL értékét kiolvassuk, közvetlenül utána az ADCH értékét is olvassuk ki!

Mindkét esetben, – az időzítőknél, és az ADC-nél is – a C- fordító rendelkezésre bocsát egy 16-bites ál regisztert, (Például: TCNT1H/TCNT1L → TCNT1, ADCH/ADCL → ADC vagy ADCW) aminek az alkalmazásával a fordító automatikusan a helyes hozzáférési sorrendet szabályozza. Alapvetően a C- programokban ezeket a 16-bites regisztereket kellene alkalmazni. Mindazonáltal, amennyiben egy tag regiszter elérése szükséges vegyük figyelembe a fentieket.

Figyelni kell arra, hogy a fordító egy 16-Bites regiszter hozzáférését is kettő 8-Bites hozzáférésre osztja fel, tehát nem olyan elemi a hozzáférés, mint az egyszeri hozzáférések. Itt is érvényes, hogy bizonyos körülmények között az interrupt (megszkítás) feldolgozást zárolni kell, nehogy adatszemét keletkezzen.

Arra az esetre, ha az ADC 8-Bites felbontása is kielégítő számunkra, lehetőség van az eredmény az ADCH/ADCL regiszterben való balra igazítására, úgy hogy a releváns 8 MSB az ADCH-ban álljon. Ilyenkor csak az ADCH-t kell kiolvasni.

Az ADC és az ADCW különböző megnevezések, ugyan arra a regiszterpárra. C-programokban rendszerint ADC-t alkalmazunk. Az ADCW (ADC Word) csak azért létezik, mert a Header-fájlokat assemblerhez is tervezték, viszont assembler-ben már van adc nevű parancs.

IO-Regiszterek mint paraméterek, és változók

Ahhoz, hogy egy függvénynek egy regisztert adhassunk paraméterként, a regisztert egy volatile uint8_t pointernek kell átadni.
Lássunk erre egy kódrészletet: 

#include <avr/io.h>
#include <util/delay.h>

uint8_t keypressed(const volatile uint8_t *inputreg, uint8_t inputbit)
{
    static uint8_t last_state = 0;

    if (last_state == (*inputreg & (1<<inputbit)))
    {
        return 0; /* nincs változás */
    }

    /* ha van, megvárjuk míg a prellegés véget ér: */
    _delay_ms(20);


    /* Állapot megjegyzése a következő függvényhívás számára */
    last_state = (*inputreg & (1<<inputbit));

    /* a prellmentes gombnyomás értékének visszaadása: */
    return (*inputreg & (1<<inputbit));
}

/* egy példa függvényhívásra: */ //...
uint8_t i;
//...
i = key_pressed( &PINB, PB1 );
//...

Egy függvény értékkel való hívása a következőképpen hat: A függvény belépésekor egy másolat készül a port jelenlegi állapotáról, Ami független a port pillanattal későbbi állapotától, így a port nem változtatja meg ezt az értéket, ami miatt a függvény hatástalan lenne. Egy pointer átadása lenne a megoldás, ha a fordító nem optimalizálná a kódot, de akkor a program már nem a bemenetről, hanem a másolatról venne mintát. Az eredmény ugyan az lenne, mint a fent leírtak. A volatile kifejezés azt jelzi a fordítónak, hogy a változó csak más programrutin (megszakítás például) vagy a hardver által módosulhat csak.

Egyébként a volatile-al jelölt változókat konstansként is deklarálhatjuk, így biztosíthatjuk, hogy a változó csak a hardver által módosítható.

 

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.