ATmega8 - Soros vonal (USART) szelidíté

2013-02-02

 

A soros vonal régi perifériája/tartozéka a számítógépeknek. A nyolcvanas években még dolgoztam olyan számítógéppel, amelyikben a soros portot megvalósító modul csaknem éjjeliszekrény méretű volt. Persze már akkor is általános volt az integráltabb kivitel, a Zilog Z80 SIO, vagy az Intel i8251. A soros vonalat megvalósító áramkör (USART) megtalálható a legtöbb AVR mikrovezérlőben is. Nagy túlélő, mondhatnánk erre. De valójában arról van szó, hogy ez egy olyan jól bevált megoldás, ami lehetővé tette a kommunikációt anno és ma is a számítógéppel. Mről is van szó tulajdonképpen?

Egy számítógének nem természetes tartozéka a képernyő, a video kártya, ellentétben mondjuk a mai PC-kkel, tabletekkel, vagy akár mobil telefonokkal. A számítógépek a perifériáik segítségével tudnak kommunikálni a környezetükkel. Fejlesztéseinkben általában mi is követjük az evolúciót. Előszőr voltak a lámpácskák (nem LED-ek:) és a kapcsolók. Ennél jobb eredményt adtak a nyomtatók, az adatokat meg lyukkártyák, meg lyukszallag formájában lehetett a gépbe tölteni. Persze az interaktívabb munkához sokkal megfelelőbbek voltak a terminálok. Egy terminál állt egy display-ből, meg egy billentyűzetből. A terminál soros vonallal volt a számítógéphez kötve. A bebillentyűzött kódolt jeleket (általában ASCII), a soros vonalon keresztül megkapta a számítógép. Ha a számítógépen futott a megfelelő program (mondjuk operációs rendszer), ami fogadta a küldött kódokat, általában a soros vonalon keresztül visszaküldte a terminálnak/displaynek, amin megjelenek azok. Ha a számítógép mondjuk nem volt bekapcsolva, vagy nem klappolt minden, akkor hiába nyomkodtuk a billentyűzetet, a képernyőn nem jelent meg semmi. Ha sikerült olyan dolgokat beírnunk, amit a számítógép értelmezni is tudott, akkor még egyébb dolgokat is kiírt jutalmul. A soros vonal minimál megvalósítása 2 vezetéket igényel, egy föld és egy adat eret. Ez az úgynevezett half duplex megoldás. Ilyenkor az adat vezetéken mindkét irányban mennek adatok, kezelni kell, hogy a számítógép vagy a terminál a saját adását ne vegye figyelembe, illetve ütközések is felléphetnek, mint az életben, ha a vonal mindkét végén egyszerre akarnak beszélni. Általánosabb megoldás a ful duplex, amikor 3 vezetéket alkalmaznak, a föld vezetéken kívül külön adat vazaték van a számítógéptől a terminál felé és a terminál felől a számítógép felé. Egy teljes kiépítésű soros interface tartalmaz még több handshake vezetéket is, vagyis olyan jeleket, amelyekkel a két beszélgető fél jelezni tud egymásnak, hogy adás kész meg hasonlók. Ezeket nem részletezném, ne bonyolítsuk a dolgot, ha kevesebb vezetékkel is meg tudjuk oldani. Annyit érdemes megjegyezni, hogy a plusz jelek segítségével lehet megvalósítani a szinkron kommunikációt, ezek hiányában pedig asszinkron tudunk kommunikálni.

Tehát ma is talán a legegyszerűbb lehetőség, hogy kommunikáljunk a számítógépünkkel/mikro vezérlőnkkel a soros vonal. Manapság általában az ember rendelkezésére áll egy PC, így nincs szükségünk terminálra, csak egy terminál emulátor programra. Annak idején világviszonylatban vezető cég volt a Digital Equipment Corporation (versenyben az IBM-mel). A DEC nevét olyan számítógép endszerek fémjelezték mint a PDP, VAX, Alpha, és olyan teminálok mint a VT52, VT100, VT220... régi idők dicsősége. Csak azért említettem meg, mert az ember könnyen belefuthat, hogy milyen terminál emulációt szeretne, általában a VT100 szerintem kielégít minden igényt. Azért vannak különböző típusok, mert a mérnökök egyre újabb vezérlő kódok kezelését építettek be a terminálokba (ugrás sor elejére, képernyő törlés...).

A kommunikáció tehát három ér segítségével valósítható meg. Az RS232 szabvány +/- 12V-os feszültség szintek alkalmazását írja elő, ezért néhol V24-nek is nevezik. Ennek az volt az oka, hogy a soros vonalat zajos környezetben is lehetett használni (gépterem), nagyobb távolság (20-30m) áthidalására. Az mikrovezérlőnk általában 5V, vagy alacsonyabb tápfeszültségről működik, ezért kiegészítő illesztő áramkörrel csatlakozhatunk a PC szabványos soros portjára. A ténylegesen megvalósított illesztő áramkörök (pl. SN75154) általában 3V-os szintnél komparálnak, ezt használtam ki az APH2 fejben lévő szintilesztő kapcsolásnál is. Korrektebb megoldás a MAXIM MAX232-es szintillesztő használata. Ez az IC az 5V tápfeszből, kondenzátorok segítségével +/-10V körüli feszültséget csinál, és ezekre a szintekre konvertálja a jelet. Hátránya, hogy alkalmazása 20-30mA többletfogyasztással jár, előnye, hogy nem kell +/-12V tápfeszt biztosítanunk. Ipari környezetben soros kommunikációt az RS485 interface, vagy optikailag leválasztott áramhurkos interface segítségével valósítják meg (ezek áramkörileg térnek el az RS232-től, de ugyanaz a kódolásuk), amelyek alkalmasak a jel több száz méteres távolságra való továbbítására is. Ha már a szintillesztésről írok, meg kell említeni, hogy léteznek USB/soros átalakító áramkörök (pl. FT232RL), vagy Bluetooth/soros modulok is, amivel vezeték nélküli kapcsolat is megvalósítható.

Kiollóztam az ATmega8 doksijából ezt az ábrát, amely a soros adatátvitelt mutatja. Tehát alapban 1-ben van az adatvezeték. Az adatátvitel úgy kezdődik, hogy az adó egység 0-ba lehúzza, ez a start bit. Na most egy kicsit részletezzük, hogy mennyi is ez az egy bit? Mivel az órajel nem kerül az adatjellel párhuzamosan átvitelre, ezért fontos, hogy mind az adó, mind a vevő egyforma sebességgel dolgozzon. Elég régre nyúlik vissza a szabványos sebességek kialakulása. Utóbb nagyon tréfás, de minden lépcsőnél azt állították, hogy ez a vége, egy telefon vonalon képtelenség nagyobb átviteli sebességet megvalósítani. Az én internet emlékeimben előszőr nagyon örültem a 14,4k-s modemnek, azután kijött a 33.6k-s és végül az 56k-s. A telexeket nem igazán ismerem, de volt amikor úgy gondolták, hogy 300 bit/sec átvitele az egy szép teljesítmény. És tényleg, ha gépelni kellene ilyen tempóban... Ezt nevezzük 300 Baud-nak. A szabványos értékek ennek a többszöresei: 600, 1200, 2400, 4800, 9600, 19200, 38400... Az átvitt adatmennyiség valójában kicsit kevesebb ennél, ezt mindjárt látni fogjuk a fenti diagram további elemzéséből. Tehát a start bit után az adat egyes bitjeinek megfelelően állítja az adó a vezetéket 1-be vagy 0-ba. Valaha használtak különböző hosszúságú adatokat, de gyakorlatilag már csak a 8 bites adathossz van használatban. Az soros adatunkat kiegészíthetjük paritás bit-tel, de el is hagyhatjuk. A paritás bitet egy ellenörző összeg, amit az áramkör az adatbitek összeadásából képez. Ebből a vevő oldal meg tudja állapítani, ha az átvitt adatban 1 bitnyi hiba van. Ilyenkor a vevő kérheti az adót az adat megismétlésére. Klassz ez a dolog, lehet, hogy valakinek szüksége lesz rá, de a gyakorlatban nem szoktuk használni, hanem olyan összeköttetést csinálunk amelyiken nem jellemzőek az ilyen hibák. Ezután következik a stop bit, amikor a vezeték 1-be kerül. Választhatunk két stop bitet is. Ez egy elválasztó jel, nyilván ha pont vessző nélkül nyomnánk az adatokat, nem tudná a vevő, hogy hol végződik az egyik adat, és mikor következik már a másik. Én jellemzően 9600 Baud, no parity, 1 stop bit beállítást használok. Röviden 9600N1-nek szoktam jelezni. Szép emlékeimben ezen a sebességen már egészen real-time-ban (valós időben) lehetett szöveget szerkeszteni VT100 terminálon, sőt jól futott PDP architektúrán a Tetris nevü orosz játék is!

A fenti kapcsolást építettem meg a dugdosós panelemen. Én azt tapasztaltam, hogy soros port használata esetén célszerűbb kvarcot, vagy kerámi szűrőt használni, mert a kalibrált belső R-C oszcillátornál pontosabb órajelre van szükség. A szintillesztést az APH2 fej végzi.

Az első program másodpercenként egy 'U' betüt küld. Ez arra jó, hogy az adó oldalt ellenőrizni tudjuk. Az 'U' betű nsem véletlen. Ha megnézzük a kódját, ez binárisan 01010101, amelyik legalkalmasabb az adatviteli sebesség tesztelésére. A programban megtalálható néhány makró, amelyekkel átekinthetően tudjuk az USART-ot konfigurálni. Két álmatlan éjszakát okozott az ATmega8 tökkelütött regiszter kiosztása. Az UBRRH és az UCSRC regiszter ugyanazon a címen találhatók, és a legfelső bittől függ, hogy éppen melyikbe írunk. Ilyet én még nem pipáltam. A programban megtalálhatók az adat adását és vételét kezelő függvények is. Adásnál meg kell néznünk, hogy az előző adat adásával már elkészült-e az USART, ha igen, egyszerűen csak bele kell írnunk az adatregiszterbe az új adatot. A vétel egy pici megszakítás kezeléssel bonyolultabb csak. Ha az USART vett egy adatot, akkor megszakítást (IT) kezdeményez. A megszakítást kezelő függvényben csupán annyi a dolgunk, hogy kiolvassuk az USART adatregiszteréből a vett adatot. Én itt most szimplán átmásolom az usart_input változóba. Ezt később lehet saját szájíz szerint cizellálni, buffer kezelés, miegymás



/*******************************************************************************
*   Author       -  Kiraly Tibor
*                   http://www.tkiraaly.hu
*   Date         -  2013.01.31.
*   Chip         -  ATmega8, APH2
*   Compiler     -  avr-gcc ( WinAVR)
*   
*   A program 9600 Baud 8N1 mellett folyamatosan "U" betuket kuld.
*   Adas ellenorzesehez. 
*
********************************************************************************
*
*   CKSEL3, CKSEL2, CKSEL1, CKSEL0
*   P       P       P       P       0000  External Clock
*   
*   P       P       P       -       0001  Calibrated Internal RC Oscillator 1MHz
*   P       P       -       P       0010  Calibrated Internal RC Oscillator 2MHz
*   P       P       -       -       0011  Calibrated Internal RC Oscillator 4MHz
*   P       -       P       P       0100  Calibrated Internal RC Oscillator 8MHz
*
*   P       -       P       -       0101  External RC Oscillator 0.1-0.9MHz
*   P       -       -       P       0110  External RC Oscillator 0.9-3MHz
*   P       -       -       -       0111  External RC Oscillator 3-8MHz
*   -       P       P       P       1000  External RC Oscillator 8MHz-
*
*   -       P       P       -       1001  External Low-frequency Crystal
*
*   -       P       -       P       1010  Ceramic resonator 0.4-0.9MHz
*   -       P       -       -       1011  Crystal Oscillator 0.4-0.9MHz
*   -       -       P       P       1100  Ceramic resonator 0.9-3MHz
*   -       -       P       -       1101  Crystal Oscillator 0.9-3MHz
*   -       -       -       P       1110  Ceramic resonator 3-8MHz
*   -       -       -       P       1110  Ceramic resonator 8MHz-, +CKOPT P
*   -       -       -       -       1111  Crystal Oscillator 3-8MHz
*   -       -       -       -       1111  Crystal Oscillator 8MHz-, +CKOPT P
*
*   Calibrated Internal RC Oscillator
*   SUT1  SUT0
*   P     P         BOD enabled
*   P     -         Fast rising power
*   -     P         Slowly rising power
*
********************************************************************************
*
*   PB0 (14) - LED - 470R
*   TXD (2)
*   RXD (3)
*
*******************************************************************************/

#define F_CPU          4000000                   // orajel 4MHz
#define BAUDRATE       9600
#define MYUBRR         F_CPU/ 16UL/ BAUDRATE - 1




#include "tkiraaly_atmega8.h"
#include 
#include 




#define LED                  0
#define LED_ENABLE           BS( DDRB, LED)
#define LED_BE               BC( PORTB, LED)
#define LED_KI               BS( PORTB, LED)




// UCSRA beallitasai
#define DOUBLE_SPEED         0B00000010          // ketszeres sebesseg

// UCSRB beallitasai
#define IT_RX_ENABLE         0B10000000          // vetel kesz megszakitas engedelyezese
#define IT_TX_ENABLE         0B01000000          // adas kesz megszakitas engedelyezese
#define IT_UDR_EMPTY_ENABLE  0B00100000          // adat buffer ures megszakitas engedelyezese
#define RX_ENABLE            0B00010000          // vetel engedelyezese
#define TX_ENABLE            0B00001000          // adas engedelyezese

// UCSRC beallitasai
#define SET_UCSRC            0B10000000          // UCSRC kivalasztasa
#define SET_UBRRH            0                   // UBRRB kivalasztasa
#define ASYNCRON             0                   // asszinkron mukodes
#define SYNCRON              0B01000000          // szinkron mukodes
#define PARITY_NO            0                   // nincs paritas bit
#define PARITY_EVEN          0B00100000          // paros paritas
#define PARITY_ODD           0B00110000          // paratlan paritas
#define STOPBIT_1            0                   // 1 stopbit
#define STOPBIT_2            0B00001000          // 2 stopbit
#define BIT_5                0                   // 5 bit-es karakterhossz
#define BIT_6                0B00000010          // 6 bit-es karakterhossz
#define BIT_7                0B00000100          // 7 bit-es karakterhossz
#define BIT_8                0B00000110          // 8 bit-es karakterhossz




UC volatile usart_input= 0;                      // soros portrol bejovo betu




void usart_putc( UC);                            // USART egy karakter kuldese



 
ISR( USART_RXC_vect)                             // USART megszakitas kezelese
{
   usart_input= UDR;
}




int main( void)
{
   UBRRL= MYUBRR;                            // USART inicializalasa
   UBRRH= ( MYUBRR)>> 8;
   UCSRC= SET_UCSRC+ ASYNCRON+ BIT_8+ PARITY_NO+ STOPBIT_1;
   UCSRB= RX_ENABLE+ TX_ENABLE+ IT_RX_ENABLE;
   IT_ENABLE;
  
   LED_ENABLE;
   LED_BE;
   
   for(;;)
   {
      usart_putc( 'U');                          // 0B01010101
      _delay_ms( 1000);
   }
}




void usart_putc( UC c)                           // USART egy karakter kuldese
{
    while( BTC( UCSRA, UDRE));                   // 1, ha kesz az adat fogadasara
    UDR= c;
}

Ez a második program echo-za a bejövő karaktereket, vagyis visszaküldi a terminálnak. Itt következik a második programból a main(), amiben a kettő program különbözik.



/*******************************************************************************
*   Author       -  Kiraly Tibor
*                   http://www.tkiraaly.hu
*   Date         -  2013.01.31.
*   Chip         -  ATmega8, APH2
*   Compiler     -  avr-gcc ( WinAVR)
*   
*   A program 9600 Baud 8N1 mellett a berkezo karaktereket echo-zza (visszakuldi).  
*   Adas/vetel ellenorzesehez. 
*
********************************************************************************
*
*   CKSEL3, CKSEL2, CKSEL1, CKSEL0
*   P       P       P       P       0000  External Clock
*   
*   P       P       P       -       0001  Calibrated Internal RC Oscillator 1MHz
*   P       P       -       P       0010  Calibrated Internal RC Oscillator 2MHz
*   P       P       -       -       0011  Calibrated Internal RC Oscillator 4MHz
*   P       -       P       P       0100  Calibrated Internal RC Oscillator 8MHz
*
*   P       -       P       -       0101  External RC Oscillator 0.1-0.9MHz
*   P       -       -       P       0110  External RC Oscillator 0.9-3MHz
*   P       -       -       -       0111  External RC Oscillator 3-8MHz
*   -       P       P       P       1000  External RC Oscillator 8MHz-
*
*   -       P       P       -       1001  External Low-frequency Crystal
*
*   -       P       -       P       1010  Ceramic resonator 0.4-0.9MHz
*   -       P       -       -       1011  Crystal Oscillator 0.4-0.9MHz
*   -       -       P       P       1100  Ceramic resonator 0.9-3MHz
*   -       -       P       -       1101  Crystal Oscillator 0.9-3MHz
*   -       -       -       P       1110  Ceramic resonator 3-8MHz
*   -       -       -       P       1110  Ceramic resonator 8MHz-, +CKOPT P
*   -       -       -       -       1111  Crystal Oscillator 3-8MHz
*   -       -       -       -       1111  Crystal Oscillator 8MHz-, +CKOPT P
*
*   Calibrated Internal RC Oscillator
*   SUT1  SUT0
*   P     P         BOD enabled
*   P     -         Fast rising power
*   -     P         Slowly rising power
*
********************************************************************************
*
*   PB0 (14) - LED - 470R
*   TXD (2)
*   RXD (3)
*
*******************************************************************************/

#define F_CPU          4000000                   // orajel 4MHz
#define BAUDRATE       9600
#define MYUBRR         F_CPU/ 16UL/ BAUDRATE - 1




#include "tkiraaly_atmega8.h"
#include 
#include 




#define LED                  0
#define LED_ENABLE           BS( DDRB, LED)
#define LED_BE               BC( PORTB, LED)
#define LED_KI               BS( PORTB, LED)




// UCSRA beallitasai
#define DOUBLE_SPEED         0B00000010          // ketszeres sebesseg

// UCSRB beallitasai
#define IT_RX_ENABLE         0B10000000          // vetel kesz megszakitas engedelyezese
#define IT_TX_ENABLE         0B01000000          // adas kesz megszakitas engedelyezese
#define IT_UDR_EMPTY_ENABLE  0B00100000          // adat buffer ures megszakitas engedelyezese
#define RX_ENABLE            0B00010000          // vetel engedelyezese
#define TX_ENABLE            0B00001000          // adas engedelyezese

// UCSRC beallitasai
#define SET_UCSRC            0B10000000          // UCSRC kivalasztasa
#define SET_UBRRH            0                   // UBRRB kivalasztasa
#define ASYNCRON             0                   // asszinkron mukodes
#define SYNCRON              0B01000000          // szinkron mukodes
#define PARITY_NO            0                   // nincs paritas bit
#define PARITY_EVEN          0B00100000          // paros paritas
#define PARITY_ODD           0B00110000          // paratlan paritas
#define STOPBIT_1            0                   // 1 stopbit
#define STOPBIT_2            0B00001000          // 2 stopbit
#define BIT_5                0                   // 5 bit-es karakterhossz
#define BIT_6                0B00000010          // 6 bit-es karakterhossz
#define BIT_7                0B00000100          // 7 bit-es karakterhossz
#define BIT_8                0B00000110          // 8 bit-es karakterhossz




UC volatile usart_input= 0;                      // soros portrol bejovo betu




void usart_putc( UC);                            // USART egy karakter kuldese



 
ISR( USART_RXC_vect)                             // USART megszakitas kezelese
{
   usart_input= UDR;
}




int main( void)
{
   UBRRL= MYUBRR;                            // USART inicializalasa
   UBRRH= ( MYUBRR)>> 8;
   UCSRC= SET_UCSRC+ ASYNCRON+ BIT_8+ PARITY_NO+ STOPBIT_1;
   UCSRB= RX_ENABLE+ TX_ENABLE+ IT_RX_ENABLE;
   IT_ENABLE;
  
   LED_ENABLE;
   LED_BE;
   
   for(;;)
   {
      while( usart_input == 0 );
      usart_putc( usart_input);
      usart_input= 0;
   }
}




void usart_putc( UC c)                           // USART egy karakter kuldese
{
    while( BTC( UCSRA, UDRE));                   // 1, ha kesz az adat fogadasara
    UDR= c;
}

Itt a vége, fuss el véle, legytek az én vendégeim, innen letölthetitek a hozzávalókat összecsomagolva.