Linux'ta Seri Port Denemeleri

Başlatan Tagli, 14 Haziran 2011, 12:31:47

Tagli

Bilgisayarda seri port erişimi her zaman bir sorun olmuştur benim için. İlk denemelerimi Windows'ta C# ile yapmıştım ve başarılı olmuştum. Çok da kolay olduğunu söyleyebilirim. Ancak ideolojik sebeplerden dolayı, Windows ve ürünlerinden mümkün olduğunca kurtulmak istiyorum. Bu sebeple Linux'ta bu işin nasıl yapılacağını öğrenmeye çalışıyorum.

İlk aşama seri port erişimi için termios.h'ın nasıl kullanılacağını öğrenmek. Gerçi sanırım bu iş için başka yollar da var ama en kolayı bu galiba. Süreç genel olarak basit, ancak termios.h içinde insanın midesini bulandırabilecek kadar çok flag var. Neyse ki bunların büyük çoğunluğu ayrıntı şeyler, ve temel erişim için sadece ufak bir kısmı ile oynamak yeterli oluyor. İnternette bulduğum örneklerden ve rehberlerden yararlanarak bir uygulama yapmayı başardım (yazının sonunda kodu ekleyeceğim).

Ama bir C uygulamasında seri porta erişmek tek başına işe yaramayacaktır. Muhtemelen seri port erişimi programın sadece bir kısmı olacaktır. Bu sebeple programın multi thread olması kaçınılmaz. Bu bağlamda, pthreads kullanmayı da bir miktar bilmek gerekiyor.

Programın son aşaması ise buna bir arayüz eklemek olmalı. Şu anda GTK+ öğrenmeye çalışıyorum ama bu yazının konusu o değil.

Asıl merak ettiğim şey iki ayrı thread'in seri port dosyasına (örneğin /dev/ttyUSB0 , dönüştürücü kullandığım için) erişiminin nasıl olacağı. Yaptığım uygulamada bir PIC18F2550'yi 19200 boudrate ile gelen 1 byte veriyi PORTB'deki LED'lerde gösterecek, ve yaklaşık olarak 0.7 saniye aralıkla da (timer0 kullanarak) ASCII karakterleri gönderecek şekilde programladım. Bilgisayardaki program da hem PIC'e veri gönderiyor (ve bu LED'lerde görülüyor), hem de PIC'ten gelenleri konsola yazıyor. Şimdilik işler yolunda gibi. Ama mutex falan kullanmadım, zaten ne şekilde kullanacağımı da bilmiyorum. (mutex nedir, nasıl kullanılır biliyorum, ama seri port dosyası ile nasıl kullanılır onu çözemedim). Verileri daha sık gönderirsem, bu iki thread'in /dev/ttyUSB0'a erişimlerinde sıkıntı olur mu acaba? Veya olursa bu sıkıntı kendini belli eder mi kolaylıkla? Bu konuda yardıma ihtiyacım var. Nasıl bir yöntem izlemeliyim?

Deneme amaçlı yazdığım kodları veriyorum. Bu işe girişecek arkadaşlar için iyi bir başlangıç noktası olabilir düşüncesindeyim. Tabi oldukça temel durumda olan bu programda hatalar olabilir, ama dediğim gibi ilk denemeler başarılı.

Eclipse'de yazdığım C kodunu rahat okunabilsin diye iki dosyaya böldüm. Alışkanlıklarım sebebiyle dosyalar İngilizce. Çünkü kütüphane fonksiyon isimleri de İngilizce olduğundan öbür türlü kafam karışıyor.

SerialTest.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <pthread.h>

//Function Prototypes
int configureSerialPort(const char* device);
void* getSerialInput(void* device);

const char *device = "/dev/ttyUSB0";

void* getSerialInput(void* device){
	int inCounter = 0, i;
	int fd = (int)(device);
	char buffer[10];

	while(1){
		inCounter = read(fd, buffer, 10);
		for (i = 0; i < inCounter; ++i){
			printf("Received Byte: %c\n", buffer[i]);
		}
	}
}

int main(void) {

	unsigned char gidici;
	int fd;
	pthread_t receiver;
	int rc;
	//void* (*fncPtr)(void*) = &getSerialInput;

	fd = configureSerialPort(device);

	rc = pthread_create(&receiver, NULL, getSerialInput, (void*)fd);

	while(1){
		gidici++;
		if (write(fd, &gidici, 1) < 0){
			perror("Write");
		}
		usleep(100000);
	}
	return EXIT_SUCCESS;
}


serial.c
#include <termios.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>

int configureSerialPort(const char* device){
	struct termios options;
	int fd;

	//Opening the serial port file
	fd = open(device, O_RDWR | O_NOCTTY | O_NDELAY);
	if (fd < 0){
		perror("open");
		return -1;
	}

	printf("Is a tty?: %d\n", isatty(fd));

	//Serial Port Configuration
	if (tcgetattr(fd, &options)){
		perror("tcgetattr");
		return -1;
	}

	//Speed Settings
	if (cfsetispeed(&options, B19200) || cfsetospeed(&options, B19200)){
		perror("Speed Settings");
		return -1;
	}

	//c_cflag Settings
	options.c_cflag &= ~PARENB; //Parity disabled
	options.c_cflag &= ~CSTOPB; //1 stop bit
	options.c_cflag &= ~CSIZE;	//Bitmask for size setting
	options.c_cflag |= CS8; //8 bit data

	//c_lflag Settings
	options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);

	//c_iflag Settings
	options.c_iflag &= ~(IXON | IXOFF | IXANY); //Software flow control disabled

	//c_oflag Settings
	options.c_oflag &= ~OPOST; //Raw output

	if (tcsetattr(fd, TCSADRAIN, &options)){
		perror("tcsetattr");
		return -1;
	}

	return fd;
}


PIC18F2550 için yazılan C18 kodu serialLEDs.c:
#include <p18f2550.h>
#include <delays.h>
#include <usart.h>
#include <timers.h>

#define MIPS 6ULL
#define LED LATBbits.LATB7

#pragma config CPUDIV = OSC1_PLL2
#pragma config FOSC = HS
#pragma config FCMEN = OFF
#pragma config IESO = OFF
#pragma config PWRT = ON
#pragma config BOR = OFF
#pragma config VREGEN = OFF
#pragma config WDT = OFF
#pragma config CCP2MX = OFF
#pragma config PBADEN = OFF
#pragma config LPT1OSC = OFF
#pragma config MCLRE = OFF
#pragma config STVREN = OFF
#pragma config LVP = OFF
#pragma config XINST = OFF
#pragma config DEBUG = OFF

//Function Prototypes
void delayMs(unsigned int ms);
void high_isr(void);

#pragma idata
unsigned char counter = 32;

#pragma code highVector = 0x08
void high_int(void){
    _asm GOTO high_isr _endasm
}

#pragma code

#pragma interrupt high_isr
void high_isr(void){
    if (INTCONbits.TMR0IF == 1){
        while(BusyUSART());
        WriteUSART(counter++);
        if (counter == 127) counter = 32;
        INTCONbits.TMR0IF = 0;
    }
}

void delayMs(unsigned int ms){
    while (ms > 0){
        Delay1KTCYx(MIPS);
        ms--;
    }
    return;
}

void main(void) {
    TRISB = 0x00;
    PORTB = 0x00;

    OpenUSART(USART_TX_INT_OFF &
        USART_RX_INT_OFF &
        USART_ASYNCH_MODE &
        USART_EIGHT_BIT &
        USART_CONT_RX &
        USART_BRGH_HIGH,
        77); //19200 bps

    OpenTimer0(TIMER_INT_ON &
        T0_16BIT &
        T0_SOURCE_INT &
        T0_PS_1_64);

    INTCON = 0b11100000; //Interrupts enabled

    while(1){
        while(!DataRdyUSART());
        LATB = RCREG;
    }
    return;
}




Gökçe Tağlıoğlu

Tagli

Yine uzun yazınca herhalde millet okumaya üşenmiş, veya soru arada kaynamış. Özetleyerek tekrar sorayım:

Aynı dosyaya 2 thread'in birinin yazmak diğerinin okumak için erişmesi sorun olur mu? Ancak bu normal bir dosya değil, bir seri port. Normal bir dosyada iki işlemi aynı anda yapmak tehlikeli, ancak seri port aynı anda gönderme ve alma yapabiliyor olmalı.

Koruma koymak istersem programdaki read() ve write() komutlarını mı aynı mutex ile sarmalıyım? Ama read() eğer gelmiş veri yoksa dönmüyor, bu durumda yazma da engellenir. Gerçi bu dönmeme durumu ayarlardan değiştirilebiliyor.
Gökçe Tağlıoğlu

CoşkuN

Bildiğim kadarıyla Linux'ta bir donanım kaynağına yazmakla, dosyaya yazmak arasında fark yok. Daha doğrusu donanımlar da sistem içerisinde birer dosya gibi görünüyorlar. Dolayısıyla iki ayrı thread aynı donanıma yazma yaparken mutex kullanılması gerekir.

Tagli

İki ayrı yazıcı yok ama. Bir yazıcı bir okuyucu thread var. Ve işin ilginci, her ne kadar dosya aynı da olsa, okunan veri ile yazılan veri birbirinden bağımsız.
Gökçe Tağlıoğlu

lojikmemo1

Güzel bir çalışma fakat ülkemizde linux ile çalışan insan sayısı az olduğu için başlık yetim kalmış.
İnsanlara Akılları Ölçüsünde Söz Söyleyiniz.

controller

Sorunuzun cevabı, aygıtı açarken open fonksiyonunda kullandığınız parametrelere bağlı.

Yazma ve okumadaki mutex işlemleri kullandığınız aygıtın, aygıt sürücüsü kodunda mevcuttur.

Aygıtı açarken open fonksiyonunda O_NONBLOCK olarak açarsanız, okuma ve yazma işlemlerinin birisi aygıtı blokladıysa, yazma veya okuma işlemi başarısız olarak döner. Yani sizin kodunuz bloke olmaz.

Aygıtı O_NONBLOCK olarak açmaz iseniz, yazma ve okuma işlemlerinin birisi aygıtı blokladıysa, diğer fonksiyon bloklama kalkana kadar bekler. Bloklama kalkınca kendi işlemini yapıp geri döner. Bu işlemde aygıt bloklu olduğu sürece kodunuz o satırda bekler.
Hesabım OG tarafından haksız bir şekilde pasif yapılmıştır.

Tagli

Az önce bir deneme yaptım, dosya O_NONBLOCK ile açılmadığı halde read() işlemi write() işlemini engellemiyor. Bunu görebilmek için PIC kodunda timer0 prescaler'i 256 yaptım (daha önce 64 idi), yani yaklaşık 2.8 saniyede bir veri yolluyor. Bilgisayar ise PIC'e 50 ms aralıkla veri yolluyor. LED'lerin bu hızda yanıp söndüklerini görebiliyorum. Eğer read() işlemi write() işlemini engelleseydi LED'ler de 2.8 saniyede bir değişmeliydi.
Gökçe Tağlıoğlu

felibol

Merhaba,

boost library sini kullanarak denemek istersen ornek olarak yazilmis ve guzelce aciklanmis bir link vereyim. buradaki orneklerde probleminin cozumunu bulabilirsin.
http://www.webalice.it/fede.tft/serial_port/serial_port.html

kolay gelsin.

Tagli

Konu ile ilgili bir soru daha kafama takıldı:

Bilgisayardaki kullanılabilir seri portları nasıl görebilirim? Bilindiği üzere, seri port terminal programlarında kullanıcının istediği seri portu seçmesi için bir menü olur genelde. Bu menüde mevcut portlar gözükür, istediğinizi seçersiniz. Bu tür bir şeyi nasıl yapabilirim?

Gerçi şimdi iki örneğe baktım, GTKTerm ve minicom, bunlarda seçim işi kullanıcının metin yazması ile oluyor, /dev/ttyUSB0 gibi. Yoksa Linux'te böyle bir şey mümkün değil mi? .net böyle bir destek veriyordu diye hatırlıyorum.

Aklıma gelen bir yöntem isatty() fonksiyonunu kullanmak. /dev içindeki tüm dosyaları açıp bu fonksiyonla kontrol etmek bir çözüm olabilir mi?
Gökçe Tağlıoğlu

mufitsozen

Alıntı yapılan: Tagli - 23 Haziran 2011, 15:18:28

"Ancak ideolojik sebeplerden dolayı, Windows ve ürünlerinden mümkün olduğunca kurtulmak istiyorum"


"Konu ile ilgili bir soru daha kafama takıldı:

Bilgisayardaki kullanılabilir seri portları nasıl görebilirim? Bilindiği üzere, seri port terminal programlarında kullanıcının istediği seri portu seçmesi için bir menü olur genelde. Bu menüde mevcut portlar gözükür, istediğinizi seçersiniz. Bu tür bir şeyi nasıl yapabilirim?

..... Yoksa Linux'te böyle bir şey mümkün değil mi? .net böyle bir destek veriyordu diye hatırlıyorum.
"

yukaridaki iki mesajdan linux altinda .net ile yaptiklarinizi yapmak istediginiz gibi bir anlam cikardim. developer forumlarindan birindeki bir soruya ve cevaba dikkatinizi cekmek isterim:

soru: Is there any way to run the .NET exe (of a winform app) in Linux without building the code in linux? In fact I don't have the code for some of the utilities I developed earlier and would like to run them in linux.

Cevap(lardan birisi): yes, we build in visual studio and deploy on linux and run under mono

cunki mono "is an implementation of the CLR. assemblies built against the .net framework on windows can be run on mono without recompiling"

http://www.mediafire.com/?r3o065qephd43i7

http://en.wikipedia.org/wiki/Mono_%28software%29
Aptalca bir soru yoktur ve hiç kimse soru sormayı bırakana kadar aptal olmaz.

Tagli

".net ile yaptıklarımız" ifadesi doğru, ama kesinlikle .net ile istemiyorum. Amacım sadece GNU C kullanarak Linux ortamında, .net ile Windows'ta görmeye alıştığımız şeyleri yapabilmek. Platform bağımsızlığı şartı da aramıyorum şimdilik. Eğer ileride böyle bir hedefim olursa, yapacağım şey .net'i Linux'ta çalıştırmak değil, glib'i Windows'ta çalıştırmak olur.

isatty() fonksiyonunu kullanarak yoklama yapmayı denedim ama arada tty olduğu halde gerçekte fiziksel bir seri port olmayan bir dolu dosya da çıkıyor. Biraz daha kurcalayıp buraya örnek bir kod ekleyeceğim olduğu kadarıyla.

Not: Aslında tty'nin tam olarak ne olduğunu da hatırlamıyorum. Bilgilerimi bir tazelemem gerekecek.
Gökçe Tağlıoğlu

SpeedyX

Alıntı yapılan: Tagli - 23 Haziran 2011, 15:18:28Bilgisayardaki kullanılabilir seri portları nasıl görebilirim?
dmesg | grep tty

yazarak sistem mesajlarından, önceden tanımlı gerçek tty ları görebilirsin.
setserial -g

komutu ile de fiziksel seri portları listeletebilirsin.

Ayrıca stty, getty, agetty komutları da konuyla ilgili.

Tagli

#12
Araştırmalarım sırasında bu komutlara rastlamıştım ama sanırım tam olarak istediğimi yapmıyorlar. Ayrıca C kodunda bunları kullanmak biraz daha zor olacak, fork, exec falan işleri, insan üşeniyor ister istemez. Ayrıca, verdikleri çıktılar da tam olarak kullanabileceğim nitelikte değil gibi.

Araştırmalarım sonucunda burada yazılanları uygulamaya karar verdim. Ancak sorun şu ki bu işlem gerçekte var olmayan bazı portları da gösteriyor.

Yazdığım deneme kodu bu:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <dirent.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/serial.h>

int main(){
	struct dirent *dp;
	DIR *dir;
	struct serial_struct serinfo;
	int fp;

	dir = opendir("/dev");
	if (dir == NULL){
		perror("opendir");
		return -1;
	}
	
	while ((dp = readdir(dir)) != NULL){
		char temp[256];
		//printf("%s\n", dp->d_name);
		sprintf(temp, "%s%s", "/dev/", dp->d_name);
		fp = open(temp, O_RDONLY | O_NONBLOCK);
		if (fp == -1){
			//perror("open");
			continue;
		}
		else if (ioctl(fp, TIOCGSERIAL, &serinfo) >= 0){
			printf("%s bir seri port! :)\n", dp->d_name);
		}
		else{
			printf("%s bir seri port değil! :(\n", dp->d_name);
		}
		close(fp);
	}
	
	printf("...Tamam!\n");

	return 0;
}


Gerçek seri portu olmayan, ve bir USB > RS232 dönüştürücü takılı olan bilgisayarda şöyle bir çıktı veriyor:

tagli@tagli-u1104:~/Desktop/dirTest$ ./dirTest2
. bir seri port değil! :(
.. bir seri port değil! :(
serial bir seri port değil! :(
ttyUSB0 bir seri port! :)
dri bir seri port değil! :(
v4l bir seri port değil! :(
video0 bir seri port değil! :(
snd bir seri port değil! :(
cpu bir seri port değil! :(
shm bir seri port değil! :(
disk bir seri port değil! :(
bsg bir seri port değil! :(
char bir seri port değil! :(
block bir seri port değil! :(
.udev bir seri port değil! :(
stderr bir seri port değil! :(
stdout bir seri port değil! :(
stdin bir seri port değil! :(
fd bir seri port değil! :(
.initramfs bir seri port değil! :(
.initramfs-tools bir seri port değil! :(
pts bir seri port değil! :(
mapper bir seri port değil! :(
input bir seri port değil! :(
bus bir seri port değil! :(
net bir seri port değil! :(
pktcdvd bir seri port değil! :(
ttyS31 bir seri port! :)
ttyS30 bir seri port! :)
ttyS29 bir seri port! :)
ttyS28 bir seri port! :)
ttyS27 bir seri port! :)
ttyS26 bir seri port! :)
ttyS25 bir seri port! :)
ttyS24 bir seri port! :)
ttyS23 bir seri port! :)
ttyS22 bir seri port! :)
ttyS21 bir seri port! :)
ttyS20 bir seri port! :)
ttyS19 bir seri port! :)
ttyS18 bir seri port! :)
ttyS17 bir seri port! :)
ttyS16 bir seri port! :)
ttyS15 bir seri port! :)
ttyS14 bir seri port! :)
ttyS13 bir seri port! :)
ttyS12 bir seri port! :)
ttyS11 bir seri port! :)
ttyS10 bir seri port! :)
ttyS9 bir seri port! :)
ttyS8 bir seri port! :)
ttyS7 bir seri port! :)
ttyS6 bir seri port! :)
ttyS5 bir seri port! :)
ttyS4 bir seri port! :)
ttyS3 bir seri port! :)
ttyS2 bir seri port! :)
ttyS1 bir seri port! :)
ttyS0 bir seri port! :)
ptmx bir seri port değil! :(
fuse bir seri port değil! :(
tty bir seri port değil! :(
urandom bir seri port değil! :(
random bir seri port değil! :(
full bir seri port değil! :(
zero bir seri port değil! :(
null bir seri port değil! :(
rfkill bir seri port değil! :(
...Tamam!


Koddan da anlaşılacağı üzere program sadece açma izni olan dosyalar hakkında yorum yapıyor, açamadıklarını es geçiyor. Sorun, ttySx şeklindeki portların hiçbiri gerçekte yok, ama onları da neşeyle gösteriyor...

Ekleme: Garip bir şekilde, "D.E.G.I.L" olarak yazdığım tüm yazılar "değil" olarak değiştiriliyor otomatik olarak.
Gökçe Tağlıoğlu

Tagli

Geri gönen serial_struct'u inceledim. Bunun içindeki baud_base ve flags değişkenleri (int) gerçek olmayanlarda 0 çıktı. ttyUSB0'da ise sırasıyla 24000000 ve 8192 değerlerini alıyorlar. Diğer ifadelerin hemen hepsi 0 gibi (bir kısmı yazmaya üşendiğim için göremiyorum). Bundan yola çıkarak gerçek aygıtları var olmayanlardan ayırmak mümkün olabilir belki.

Ancak maalesef ttyS0, ttyS1, ttyS2 ve ttyS3 de gerçekte olmadıkları halde var gibi gözüküyorlar. Bunlar belki de bilgisayarın içindeki dahili cihazlara bağlıdırlar.

Sizden ricam aşağıda vermiş olduğum kodu kendi bilgisayarınızda deneyerek sonuçları paylaşmanız.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <dirent.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/serial.h>

int main(){
	struct dirent *dp;
	DIR *dir;
	struct serial_struct serinfo;
	int fp;

	dir = opendir("/dev");
	if (dir == NULL){
		perror("opendir");
		return -1;
	}
	
	while ((dp = readdir(dir)) != NULL){
		char temp[256];
		//printf("%s\n", dp->d_name);
		sprintf(temp, "%s%s", "/dev/", dp->d_name);
		fp = open(temp, O_RDONLY | O_NONBLOCK);
		if (fp == -1){
			//perror("open");
			continue;
		}
		else if (ioctl(fp, TIOCGSERIAL, &serinfo) >= 0){
			if (serinfo.flags != 0)
				printf("%s GERCEK bir seri port! :)\n", dp->d_name);
			else
				printf("%s YALANCI bir seri port! :(\n", dp->d_name);
		}
		else{
			printf("%s bir seri port DEGIL! :(\n", dp->d_name);
		}
		close(fp);
	}
	
	printf("...Tamam!\n");

	return 0;
}
Gökçe Tağlıoğlu