Impulzus

 
A Budapesti Műszaki és Gazdaságtudományi Egyetem Villamosmérnöki és Informatikai Kar Hallgatói Képviseletének lapja
Random cikkajánló

Tavaszi Zsongás

Bolt++

"A "Hogyan legyünk gazdagok?", a "Sikeres élet titkai" és a "Hogyan érveljünk gyorsan és hatásosan?" című munkák bestsellerek lettek, íróik pedig egycsapásra meggazdagodtak. Az Impulzus sem maradhat ki ebből az üzletből, így egy tanulmányt készítettünk..."

Kereskényi Balázs

Sportrendezvények 2000 tavaszán

Interjú Dr. Pongor György tanár úrral

Felező

Csapdában

"Szabad volt, habár nem is tudta igazából, mit jelent a szabadság."

Nyomdatörténeti barangolások

Ma már többnyire csak a régi idők emlékét őrzik a betűszekrények a nyomdákban. Nem szabad viszont elfelejtenünk, hogy azok az apró kis ólomdarabok és a többi fa és fém nyomdai eszköz segített bennünket, hogy eljussunk oda, ahol ma tartunk.

McQpa

Minőség a kezdetektől

Életkép

Soros port kezelése Linux alól

avagy hogyan fűzzünk RS-232/485-ös buszt a kedvenc operációs rendszerünket futtató számítógépre?

Előbb-utóbb legtöbbünk találkozik azzal a problémával már az egyetem alatt is, hogy a PC soros portján kell kommunikálni valamilyen eszközzel, legyen az valami régebbi device, mikrokontroller vagy egy "terepi" buszrendszer. Ezeknél az alkalmazásoknál általában magára hagyott rendszerekről van szó, ahol nincs állandó emberi felügyelet a számítógépünk mellett, esetleg a PC mintegy "beágyazott" számítógépként működik valami nagyobb eszközben. Nagyon jól jön az ilyen problémák megoldásánál a Linux közmondásos megbízhatósága. Pár tízezer forintból összerakhatunk egy olyan PC-t, amiben egyetlen mozgó alkatrész sincsen (például ha flash-memóriát használunk merevlemez helyett), és az így összerakott rendszert nyugodtabb szívvel hagyhatjuk magára.

A soros port programozása nem valami nehéz dolog Linux alól, de mégis sokat lehet vele dolgozni, mire működőképes rendszert alakíthatunk ki, mivel nem valami jól dokumentált a probléma. Elsődleges forrásunk legyen a Serial HowTo, és olvassuk el a (kissé hiányos) Serial Programming HowTo-t is, aminek ez a rövid cikk egyfajta kiegészítése lenne.

A soros port nem valami nagy teljesítményű, modern interface, azonban a mai napig elég elterjedt. (Egyik tanárunk szavaival élve: az iparban "a soros port körül forog a világ".) Ha a világ nem is forog talán körülötte, néha azért célravezető lehet a használata; például ha több mikrokontrollert szeretnénk mondjuk egy épületen belüli hálózatba kötni, akkor megfontolandó, hogy esetleg RS485-ös buszrendszert használjunk. És a PC soros portja egy – a sarki boltban megvásárolható – interface-szel RS485-re kapcsolható.

Az (EIA) RS485 aszinkron busz half-duplex változata egy csavart érpáron "fut". Az érpáron a feszültség szimmetrikus, ezáltal kevésbé érzékeny a zavarjelekre. Az átviteli sebesség a kábelhossz függvénye, de maximum 10Mbit/sec a szabvány szerint (bár megfelelő chipsettel elérhető akár 25 Mbit/sec is). Egy (maximálisan 1 km hosszú) kábelre legfeljebb 32 eszköz fűzhető fel. Ahhoz, hogy PC-ről erre a buszra csatlakozzunk, rögtön adódik, hogy a soros porton keresztül tegyük ezt.

Fontos megjegyezni, hogy az RS485 nem tartalmaz semmi előírást sem a protokollra. Tehát elvileg akármilyen szabványt alkalmazhatnánk az adatok forgalmazására.

Az RS-232 (szabványos nevén EIA-RS-232) nagyon ősi protokoll. Két végpont összekötésére használhatjuk alacsony sebességgel, rövid kábellel. Bár full-duplex kapcsolatot is lehetővé lesz, mi most a half duplex kapcsolatot fogjuk használni, azaz egy időben csak egy irányban kommunikálhatunk. Minden jelet külön vezeték továbbít, és az adatot kódoló feszültséget a földhöz viszonyítjuk. Aszinkron buszról van szó itt is, tehát nincs órajel.

A főbb továbbított jelek a következők:

GND A föld.

TXD Transmitted Data, vagyis a leadott adat, feszültségtől függően 0 vagy 1 az értéke.

RXD Received Data, vagyis a vett adat.

DCD A Data Carrier Detect azt jelöli, hogy össze van-e kötve a két végpont. Nem mindig használják.

DTR A Data Terminal Ready értéke 1, ha a számítógép kész a kommunikációra, tehát általában a soros port megnyitásától igaz.

CTS Clear To Send, a kábel másik oldalán lévő számítógép ezzel jelzi, hogy készen áll arra, hogy mi elkezdjünk adni.

RTS Request To Send, ezzel jelzi a számítógépünk, hogy készen állunk az adásra.

Fontos megjegyezni, hogy az RTS és a CTS , az un. "flow control" jelzőbitek a legtöbb esetben mindig nullára vannak állítva. Azonban ha RS-485-höz akarunk hozzáférni, akkor muszáj használnunk ezeket a jeleket is, mert több RS232/485 konverter is a CTS/RTS változását figyelve működik.

Nézzük meg, hogy milyen módon megy át egy byte a soros porton!


| Start Bit | Adatbitek | Paritásbit | Stopbit |


A startbit feladata az aszinkron kommunikáció időzítése. Értéke mindig 1, a stopbit pedig 0. Paritásbit helyett használhatunk két stopbitet is. Az adat lehet 7 vagy 8 bites. Mivel ez egy nagyon alacsonyszintű protokoll, mind a fogadó, mind a küldő félnek tudnia kell, hogy hány adatbitet küld, páros vagy páratlan paritást használ-e, esetleg két stopbitet küld.

Szerencsére ezeket a feladatokat elvégzi helyettünk a számítógépünk UART chipje, és a kommunikáció alatt nekünk csak az adatbyte-ok küldésével és fogadásával kell foglalkoznunk. Az UART felprogramozása nem valami nehéz feladat egy egységsugarú mikrokontroller esetében, azonban Linux alatt az egész meglehetősen el van bonyolítva. Azért nehezítették meg ennyire a dolgunkat, mert a POSIX-os srácok alapvetően arra készülhettek, hogy mi a soros porton egy terminált akarunk majd a számítógéphez kapcsolni, és nem pedig, teszem azt, mikrokontrollereket.

Szóval soros kommunikáció Linux alatt. Ugye UNIX alatt majdnem minden file. Linux esetében a soros port a /dev/ttySx néven alatt írható vagy olvasható, ahol x egy tetszőleges természetes szám a COM portjaink számától függően. Ahhoz, hogy látszódjon a ttySx a /dev/ alatt, be kell hogy töltsük a serial.o kernel modult például a modprobe serial paranccsal. A /dev/ttySx az első írás vagy olvasás alkalmával jön létre. Alapbeállításban csak a root férhet hozzá, tehát gondoskodnunk kell róla, hogy minden rendszerboot után megfelelő jogosultságokat kapjon, vagy pedig rendszergazda jogokkal kell futtatnunk a soros-portot használó programunkat.

Vágjunk a dolgok közepébe, és nyissuk meg a soros portot egy egyszerű C program segítségével!


#include <stdio.h>

#include <stdlib.h>

#include <termios.h>

#include <fcntl.h>

#include <unistd.h>

#include <sys/ioctl.h>


[ ... ]

int serialFd;

struct flock serialFlock;


if((serialFd = open("/dev/ttyS0", O_RDWR | O_NOCTTY)) < 0)

return ERROR;


serialFlock.l_type = F_WRLCK;

serialFlock.l_whence = SEEK_SET;

serialFlock.l_start = 0;

serialFlock.l_len = 0;

if (fcntl(serialFd, F_SETLK, &serialFlock) < 0)

return ERROR;


Az open()-nel megnyitjuk a portot, read-write módban. "Fel kell készülni mindenre" alapon az O_NOCTTY flag biztosítja, hogy ezen a device-on nem futhat a processzt kontrolláló terminál.

Az fcntl() függvénnyel megadunk néhány attribútumot, pontosabban a processzünk egy write lock-ot rak a device-ra. (Akit bővebben érdekel a lockolás kérdése, az tanulmányozza át a man fcntl oldalt.) Innentől a soros portot a serialFd nevű file-leiírón keresztül érhetjük el programunkban.


termios t;

int serialStatus;

if (tcgetattr(serialFd, &t))

return ERROR;


ioctl(serialFd, TIOCMGET, &serialStatus);


A tcgetattr() függvénnyel lekérdezzük az eredeti állapotát a soros port tulajdonságait meghatározó rekordnak, hogy csak a fontos részeket módosítsuk a továbbiakban.


t.c_iflag &= ~(ISTRIP | IXON | IXOFF | INLCR | ICRNL);

t.c_iflag |= IGNBRK | CRTSCTS ;

t.c_oflag &= ~OPOST;

t.c_cflag &= ~(CSTOPB | CSIZE | PARODD); //EVEN paritas

t.c_cflag |= (CLOCAL | CREAD | CS8 | PARENB);

t.c_lflag &= ~(ICANON | ECHO | ISIG);

t.c_cc[VMIN] = 0 ;

t.c_cc[VTIME] = 1 ;

cfsetispeed(&t, B57600);

tcsetattr(serialFd, TCSAFLUSH, &newt);


A termios flagek beállításánál lehet nagyon elszállni a programozásban. A termios rekord egy kifejezetten soros port tulajdonságainak felprogramozására létrehozott adattípus. Ajánlott a man cfsetispeed beható tanulmányozása. Azonban ez egy igen unalmas manpage; így lusták kedvéért a következő két táblázatban itt le van írva, hogy miket érdemes feltétlenül beállítani. (Ez a flagbeállítás eddig minden PC-n működött, amivel próbáltam.)

Érdekes dolog a beállítandó dolgok közül a termios::c_cc tömbje. Itt két fontos dolgot lehet elrontani: a VMIN és a VTIME értékét. A VMIN meghatározza, hogy minimum hány karaktert olvassunk be. Ha 0-ra rakjuk, akkor a VTIME egy timeout-ot állít be – állítólag – tizedmásodpercben. Ezzel a két értékkel ízlés szerint nagyon sokat lehet trükközni, de ebben a cikkben ez sajnos nincs leírva. A Serial Programming HowTo és a megfelelő man oldalak részletesen leírják a működésüket, azonban nekem ez a beállítás volt a legpraktikusabb.

A cfsetispeed() beállítja a sebességet, itt 5.76Kbaudra, a tcsetattr() pedig alkalmazza a beállításainkat.

A kikapcsolt flagek jelentése a következő:

ISTRIP 8. bit figyelmen kívül hagyása.

IXON,IXOFF XON/XOFF típusú flow control engedélyezése a kimeneten/bemeneten.

INCLR Bemenetnél NL karakter helyett küldjünk CR-t.

ICRNL Bemenetnél a CR karaktert kicseréli NL-re.

OPOST Implementációfüggő kimenet-feldolgozás engedélyezése*.

CSTOPB Két stopbit használata.

CSIZE Karakterméret maszkot fogunk használni.

PARODD Páratlan paritás vizsgálata.

ICANON Kanonikus mód engedélyezése. Ez akkor hasznos, ha terminalt valósítunk meg a soros porton.

ECHO Echozza a bejövő karaktereket.

ISIG Bizonyos karakterek érkezésekor bizonyos szignált generál.

A bekapcsolt flagek jelentése:

IGNBRK Break feltétel mellőzése a bemeneten.

CRTSCTS Engedélyezzük a CTS/RTS (flow control) vezetéket. A legtöbb RS485 konverter használata esetén kötelező.

CLOCAL Mellőzzük a modemvezérlő sorokat.

CREAD Engedélyezzük a vételt.

CS8 8 bites adatbyte-okat használunk.

PARENB Engedélyezzük a paritás generálást/ellenőrzést

Most, hogy kész az inicializálás, túl is vagyunk a nehezén, kezdhetünk végre programozni.


serialFile = fdopen(serial_fd,"rw");

if (serialFile==NULL) {

printf("Nem tudom megnyitni a serial portot. n");

return ERROR;

}


int serialStatusRead, serialStatusWrite;

serialStatusRead = serialStatusWrite = serialStatus;

serialStatusWrite &= ~TIOCM_DTR;

serialStatusWrite |= TIOCM_RTS;

serialStatusRead &= ~TIOCM_RTS;

serialStatusRead |= TIOCM_DTR;


unsigned char buf[8];

unsigned char be[8];


//

// Erteket adunk buf[8]-nak.

// [ .. ]

//


// Iras

ioctl(serialFd, TIOCMSET, &serialStatusWrite); //FONTOS

tcflush(serialFd, TCOFLUSH);

write(serialFd,buf,8);

usleep(3); //FONTOS


// Olvasas

ioctl(serialFd, TIOCMSET, &serialStatusRead); //FONTOS

tcflush(serialFd, TCIFLUSH);

res = fread(&be,1,8,serialFile);


if (res>0) {

//Kiertekeljuk a beolvasott byte-okat...

}


A vicc kedvéért először nyissuk meg a portot file-ként is. Utána definiálunk pár státuszváltozót, majd kiírjuk a buf[] nyolc tetszőleges byte-ját a soros portra. Utána pedig beolvassuk, hogy mit írt valaki válaszul, bárki is legyen a vonal végén.

Nézzük gyorsan végig, hogy mi az írás folyamata! Az ioctl() segítségével felkészítjük az eszközt az írásra, a megfelelő flag beállításával. A tcflush() parancs "flussol", vagyis kiírja/beolvassa a bufferben várakozó, ki nem írt/be nem olvasott adatokat. A write parancs működéséhez nem lehet túl sokat hozzáfűzni.

Az olvasás nagyon hasonlóan történik. A példánkban maximum 8 byte-ot olvasunk be. A fread a beolvasott byte-ok számával tér vissza, tehát a timeout lejárása után mindenképpen visszakapjuk – az esetleges sikertelen olvasás esetén változatlan tartalmú – be[] paramétert.

Érzékeny jószág a soros port, ezért nagyon fontos, hogy hagyjunk időt az írásra a device-t kezelő chipnek. Ha hamarabb kezdünk olvasni, minthogy az eszköz befejezte volna az írást, akkor megzavarhatjuk portunk nőiesen érzékeny lelkivilágát, és kommunikációs kísérletünk eredménytelenül zárul. Erre való az usleep() függvényhívás a kódban. Természetesen változó hosszúságú bufferek írásakor a várakozás időtartamára vigyázni kell. Ez a várakozás egyébként annyira lényeges, hogy nélküle a kis programrészlet egyáltalán nem működik!

Remélem, hogy ez a rövid cikk segítségére lesz azoknak, akik esetleg abba a problémába ütköznek, hogy Linux alatt kell sorosan kommunikálniuk, és az itt közölt kis forráskód sok bosszúságtól kíméli majd meg őket.

csaba@impulzus.com


U.i.: Köszönet a linux++@mlf.linux.org levelezőlista tagjainak, akik segítettek a bajban, amikor a flagek igencsak összezavarodtak.