Szoftveres adatküldő UART rutin
Az aszinkron soros kommunikáció az információt egymás után, bitről-bitre továbbítja. Az AVR hardveres UART-ja automatikusan küldi es fogadja a kimenő és bejövő adatokat, beállítja a mintavételezést az Rx lábon, az időzítést pedig a TX lábon és magától visszajelzést küld a folyamatok állapotáról.
Mint azt már korábban említettem, az ATMega8 hardveres UART-ját a robot és a PC közötti kommunikációra akarom majd használni, ezért a mikrovezérlő és az LCD panel közötti soros kommunikációt egy szoftveres UART rutinnal oldottam meg. Ennek használata nehézkesebb mint a hardveres UART-é. A soros kommunikáció lépéseit nekünk kell szoftveresen időzíteni és végrehajtani a programunkban. Nagyon oda kell figyelni a programszervezésre, hogy a szoftveres UART stabil maradjon. Az esetleges megszakítások nehogy felborítsák az adatküldés időzítését.
Szerencsére az LCD és az AVR között csak egyirányú a kommunikáció (csak a kiírandó adatot küldjük az LCD-re), ez nagymértékben leegyszerűsíti a feladatot, mert nem kell megírni a szoftveres UART adatfogadó algoritmusait. (akinek kétirányú szoftveres UART rutin kell, az nézzen körül a neten)
Nézzük meg a soros adatküldés lépéseit a soros LCD vezérlőpanel által használt 9600bps kommunikációs sebesség esetén (a szoftveres UART programunk is pontosan ezt fogja tenni):
- Logikai 1 jelet adunk a kiválasztott I/O lábra (Stop bit, várakozó állapot)
- Ha adatot akarunk küldeni, akkor Logikai 0-t kapcsolunk a kiválasztott I/O lábra (Start bit)
- A Timer0 használatával beállítjuk, hogy pontosan 104us-ig legyen a lábon a Start bit.
- Megadunk egy ciklust, ami először az adatbit legkisebb helyiértékű bitjet adja 104us-ig az I/O lábra, majd egymás után az egyre magasabb helyiértékű biteket is az I/O lábra küldi, pontosan 104us-os időintervallumokban.
- Az utolsó adatbit után az adatküldést egy Stop bit-tel fejezzük be (Logikai 1 jelet adunk a kiválasztott I/O lábra, várakozó állapot)
A Timer0 8 bites számlálója 0-tól kezdve számol felfelé egyesével folyamatosan, amíg el nem éri a 255-öt, 255 után a számláló túlcsordul és lenullázódik, majd 0-tól ismét újrakezdi a számolást. A periódusidő az előosztás értékétől függ. Ezzel a Timerrel kell majd egy 104 us-os késleltető rutint előállítani.
Mivel a Timert időzítésre akarom használni (pontosabban csak egy adott ideig akarok csak késleltetni), és nem egy adott időközönként folyamatosan ismétlődő periodikus jelet akarok előállítani, ezért nem kell használni a Timer0 túlcsordulásához tartozó megszakítást. Elég lesz a túlcsordulást jelző TOV0 bitet figyelni (mivel a megszakítás ninncs bekapcsolva, ezért ezt a jelzőbitet nekünk kell majd a programunkonn belül törölni).
A soros LCD panel 9600 bps sebességgel kommunikál, ezért egy bit 104us-ig tart. Ha a Timer előosztása 1, akkor ez alatt az idő alatt a számláló 768-ig számolna el ( 7.372800/9600 = 768 órajelütem/bit ). Ez túl sok, mert a Timer0 számlálója 0-tól csak MAX 255-ig tud számolni. De szerencsére a 768-at az előosztással csökkenteni tudjuk.
A Timer0 esetén a következő lehetséges előosztás érték a 8. Ekkor a Timer0 96 ütemet számlál 104us alatt (7.372800/(8*9600) = 96 órajelütem) Ez már jó lesz, mert kisebb mint 255.
Mivel a Timer0 csak felfelé tud számolni, és a hozzá tartozó MAX érték nem változtatható, fixen 255, ezért nem tununk 0-tól 96-ig elszámolni. A TCNT0 regiszterrel a számláló kezdeti értéke viszont szabadon változtatható. Ezért 255-96=159-től 255-ig éppen 96 ütemet számol el a Timer, pontosan 104 us alatt , utána túlcsordul és újrakezdi a számolást, de előtte átállítja a TOV0 jelzőbit értékét a TIFR regiszterben. Ezért a számlálót 159-től indítva, és a TOV0 jelzőbit értékét figyelve megadhatunk egy 104us-ig tartó szoftveres késleltető rutint.
Az alábbi kóddal létrehozok egy függvényt, ami meghívása esetén a paraméterként megadott értékre állítja a TCNT0 regiszter értékét, bekapcsolja a Timer0-t, majd a számláló túlcsordulása után nullázza a TOV0 jelzőbitet és kikapcsolja a Timert.
void kesleltetes(unsigned int _tick) // Szoftveres UART beallitasa
{
TCNT0 = _tick; // a szamlalo beallitasa a fuggvenyben szereplo ertekre
TCCR0 = (1<<CS01); // Start Timer0, CLK/8
while (!(TIFR & (1<<TOV0))); // varakozas amig a szamlalo eleri maximalis erteket
TIFR |= (1<<TOV0); // Tulcsordulas jelzobit torlese
TCCR0 = 0; // Stop Timer0
}
A szoftveres adatküldő UART rutinnal bármelyik I/O lábat használhatjuk kimenetnek. Ezért érdemes az egyes portok kezelését leegyszerűsítő függvényeket használni. Az alábbi ki_c(x,y) függvénnyel pl. PORTC lábainak állapotát tudjuk nagyon egyszerűen változtatni. Az utasítás a PORTC x.-ik lábára 0V-ot vagy 5V-ot ad, attól függően hogy y értéke 0 vagy 1. Először a lábat kimenetnek adjuk meg, majd ha y = 1, akkor a lábra 5V-ot adunk. Ha y = 0, akkor pedig 0V-ot adunk. Például a ki_c(5,1) utasítás a PORTC 5. lábára 5V-ot ad, a ki_c(0,0) utasítás pedig a PORTC 0. lábára 0V-ot.
void ki_c(char _bit,char _dat) /* ki_c(x,y) fuggveny definialasa PORTC-re. PORTC x. labara 0V-ot vagy 5V-ot adunk, attol fuggoen hogy y erteke 0 vagy 1 */
{
DDRC |= _BV(_bit); // PORTC x. lab kimenet
if(_dat)
PORTC |= _BV(_bit); // ha y=1, az x. labra 5V-ot ad
else
PORTC &= ~_BV(_bit); // ha y=0, az x. labra 5V-ot ad
}
A fenti függvények felhasználásával létrehozhatjuk magát a szoftveres adatküldő UART függvényt, ami először a Start bitet, majd egy 8-szor ismétlődő cikluson belül a küldendő adatbájt bitjeit egymás után, végül pedig a Stop bitjeit kapcsolja egyenként 104 us-ig a megadott I/O lábra.
void SzoftUARTKuld(char tx, unsigned char adat) //
{
int i;
ki_c(tx,0); // Start bit
kesleltetes(159); // kesleltetes 104us-ig
for (i=0;i<8;i++) // 8 szor ismetlodo ciklus
{
ki_c(tx,adat & 0x01); // adatbit kuldese
kesleltetes(159); // kesleltetes 104us-ig
adat = adat>>1; // eltolas a kovetkezo bitre
}
ki_c(tx,1); // Stop bit
kesleltetes(159); // kesleltetes 104us-ig
}
Ezzel elméletben már el is készült a szoftveres adatküldő UART rutin de még módosítanunk kell rajta egy kicsit hogy működőképes legyen. Ugyanis a programban használt ki_c(a,b) függvény végrehajtása is időbe telik (kb. 20us), ezért a Timer0-t egy változtatható mértékű offszet érték közbeiktatásával úgy állítjuk be, hogy ne pontosan 104us után csorduljon túl, hanem kb. 20 us-al korábban (ne 159-től kezdje a Timer a számlálást hanem 177-től). Ennek az offszetnek az értékét kísérletezgetéssel állapítottam meg.
Az adatküldő függvényre alapozva létrehozunk egy SzoftUARTKuldSzoveg() függvényt is, amivel már nemcsak karaktereket, hanem szöveget is küldhetünk.
A szoftveres adatküldő UART rutinunk végső formájában így néz ki:
#define offset 18
unsigned int idoalap;
void KonfigSzoftUART(char tx) // Szoftveres UART beallitasa
{
ki_c(tx,1); // kezdetkent egy Stop bit-et adunk meg
TCCR0 = 0; // Stop Timer0
TIFR |= (1<<TOV0); // Tulcsordulas jelzobit torlese
idoalap = 255-(96 - offset);
}
void kesleltetes(unsigned int _tick) // Szoftveres UART beallitasa
{
TCNT0 = _tick; //
TCCR0 = (1<<CS01); // Start Timer0, CLK/8
while (!(TIFR & (1<<TOV0))); // varakozas amig a szamlalo eleri a kivant erteket
TIFR |= (1<<TOV0); // Tulcsordulas jelzobit torlese
TCCR0 = 0; // Stop Timer0
}
void SzoftUARTKuld(char tx, unsigned char adat) //
{
int i;
ki_c(tx,0); // Start bit
kesleltetes(idoalap); // kesleltetes a megadott idoalap-ig
for (i=0;i<8;i++)
{
ki_c(tx,adat & 0x01); // adatbit kuldese
kesleltetes(idoalap); //kesleltetes a megadott idoalap-ig
adat = adat>>1; // eltolas a kovetkezo bitre
}
ki_c(tx,1); // Stop bit
kesleltetes(idoalap); // kesleltetes a megadott idoalap-ig
}
void SzoftUARTKuldSzoveg(unsigned char tx, char *p) // Szovegkuldo fuggveny definialasa
{
while(*p)
{
SzoftUARTKuld(tx, *p++);
}
}
Már csak az LCD-t kell vezérelni a megfelelő parancsokkal.
A cikk még nem ért véget, lapozz!
Értékeléshez bejelentkezés szükséges!