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ó

Mi újság, Hallgatói Gyűlés?

Pszi-kör

Kultúrinterjú Szabó Istvánnal

Cím nélkül

Leonardo: The Absolute Man

MusicMania

ACM programozói verseny

"... egy komoly világverseny előselejtezője volt."

25 éves jubileumi KSZK-s találkozó

Süveges Péter (SPéter), II. Vill.

Támpont Hírek

Doktorandusz konferencia; új egyetemi klub; Prága; ERASMUS pályázat

Hegyen-völgyön kultúra

Életkép egy büféinfós hétköznapjáról, elmondva szabadköltészetben

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.