19 Nisan 2017 Çarşamba

Seri Port API'si

Not : Konuyl ilgili olarak TTY ve Devpts başlıklı yazıya göz atabilirsiniz.

Giriş
Seri portlar konusu programlama diline bağlı olarak çok kolay veya karışık bir hal alabiliyor.

UART
UART yazısına taşıdım.

Posix

Seri portları kullanmak özellikle POSIX C API'si ile karmaşık bir iş. Kafa karıştıracak bir sürü bayrak ve metod var. En basit haliyle şu işleri yapmak gerekiyor.

1. Seri portu açmak için şöyle yaparız.
int fd = open( "/dev/ttyUSB0", O_RDWR | O_NONBLOCK | O_NDELAY | O_NCTTY); 
Şöyle yaparız.
int fd = open( "/dev/ttyUSB0", O_RDWR | O_NDELAY | O_NCTTY); 
2. Seri port parametrelerin okumak için şöyle yaparız.
struct termios options {};
tcgetattr(fd,&termAttr);
3. Giriş çıkış hızını ata
/* Set Baud Rate */
cfsetospeed (&options, (speed_t)B9600);
cfsetispeed (&options, (speed_t)B9600);

4. Diğer parametreleri ata
/* cflag (control flag) settings. Make 8n1 */
tty.c_cflag     &=  ~PARENB;  //Parity disabled
tty.c_cflag     &=  ~CSTOPB;  //1 stop bit
tty.c_cflag     &=  ~CSIZE;   //Bitmask for size setting
tty.c_cflag     |=  CS8;      //8 bit data
Flow Control için
tty.c_cflag     &=  ~CRTSCTS;           // no flow control

//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

5. Parametreleri ata
tcsetattr(fd, TCSAFLUSH, &term_settings)
6. Okuma İşlemini Yap
Önce sinyalin iki kere gelmesini engellmek için sinyali maskeleriz. Şöyle yaparız.
sigemptyset(&saio.sa_mask);
sigaddset(&saio.sa_mask, SIGIO);
Sonra signal handler atamak için şöyle yaparız.
saio.sa_handler = signal_handler_IO;
saio.sa_flags = 0;
saio.sa_restorer = NULL;
sigaction(SIGIO,&saio,NULL);
handlerın imzası şöyledir.
void signal_handler_IO (int status)
{
  ...
}
Okumak için şöyle yaparız.
while(true)
{ 
  size_t bytes_avail; 
  ioctl(fd, FIONREAD, &bytes_avail); 
  if(bytes_avail == 0) 
    break; 
  int n = read (fd, buf, (sizeof buf)-1); 
  if(n<0) 
  { 
    //error handling
    fprintf(stderr, "Error while receiving message: %s\n", strerror(errno)); 
    return; 
  } 
  buf[n] = '\0'; 
  // you can add a check here to see if (length+n) < 1024 
  strcat(msg, buf); 
  length += n; 
} 

Şimdi bu kafa karıştırıcı parametrelere bakalım.

Metodların Gruplanması
Seri Port API'sine ait metodlar termios yapısını yöneten metodlar ve line control functions başlığı altında iki gruba toplanabilir.  Aşağıdaki şekli şekli buradan aldım.

termios yapısını kullanan metodlar en çok işe yarayanlar. Line Control Functions ile Line Discipline değiştirebilme imkanı vardır.




termios metodları

Termios Yapısı
termios yapısı yazısına taşıdım.

Seri Portlar'u Açarken Controlling Terminal Olmak

Controlling Terminal SIGINT sinyali ile bir uygulamayı öldürebilme yeteneğine sahiptir. Seri Port kullanılırken seri port'tan uygulamamızın öldürülmesini çoğunlukla istemeyiz. Bu yüzden seri port TTY'sini "controlling terminal" haline getirmemek gerekir. Peki seri port TTY'si nasıl "controlling terminal" olabiliyor ?

Aslında bu konuyu ben de doğru dürüst anladım diyemem. POSIX standardında 7.1.1.3 bendinde şöyle bir cümle var.

7.1.1.3 The Controlling Terminal

If a session leader without a controlling terminal opens a terminal device file not already associated with a session without specifying the O_NOCTTY option, then this terminal becomes the controlling terminal for the session leader. This is how a controlling terminal is acquired.
Yani seri port açılırken eğer uygulamanın controlling terminali yoksa durup dururken seri port tty'sini controlling terminalimiz yapmayalım diye O_NOCTTY seçeneğini kullanabilirsiniz diyor.

 Aşağıdaki kod parçasını bunu gösteriyor.

O_NOCTTY : Kullanılırsa seri port TTY uygulamanın "controlling terminal"'i haline gelmez.
O_NDELAY : Kullanılırsa seri port non-blocking olarak açılır.
O_NONBLOCK: O_NDELAY ile aynı işe yarar. Bu sabit POSIX standardında tanımlıdır.

ps -j komutu ile uygulamanın "controlling terminal"i olup olmadığını görmek mümkün.

Why CTRL+C won't work when using RS-232 on Linux? başlıklı soru da ilginç.


termios yapısı c_flag alanları

c_flag alanı ile receiver'ı etkinleştirmek
Tam olarak ne olduğunu anlamadım ancak buradaki açıklamaya göre CLOCAL ve CREAD aşağıdaki örnekteki gibi her zaman lazım.

CLOCAL  : local connection, no modem contol
CREAD   : enable receiving characters

c_cflag alanı ile stop bit atamak
c_cflag alanı ile donanıma aşağıdaki gibi stop bit atamak mümkün.
c_cflag alanı ile karakter büyüklüğü atamak

options.c_cflags &= ~CSIZE; // Mask the character size bit
options.c_cflags |= CS8;    // Select 8 data bits
c_cflag alanı ile parity atamak
PARENB bayrağı ile parity kullanıp kullanmayacağımızı belirtiriz. Eğer parity kullanılacaksa PARODD bayrağı atanmışsa odd parity kullanılır, atanmamışsa even parity kullanılır.
//No parity (8N1):
options.c_cflags &= ~PARENB //No parity
options.c_cflags &= ~CSTOPB //1 stop bit
options.c_cflags &= ~CSIZE; // Mask the character size bit
options.c_cflags |= CS8; //8 data bits

c_flag alanı ile hardware flow control yapmak
Bu seçenek sadece donanım gerekli kabloları ihtiva ediyorsa kullanılabilir.
options.c_cflag &= ~CRTSCTS; // Disable hardware flow control

CRTSCTS : output hardware flow control (only used if the cable has all necessary lines. See sect. 7 of Serial-HOWTO)

struct termios options;
options.c_cflag |= CRTSCTS;//enable hardware flow control
tcsetattr(fd, TCSANOW, &options)
options.c_cflag &= ~CRTSCTS; //disable hardware flow control
Eğer bu kabloları hardware flow control için kullanmıyorsak bile seri portu açtıktan sonra da kontrol etmek istiyorsak aşağıdaki gibi yapabiliriz. TIOCM_RTS ve TIOCMSET kullanılıyor.


c_iflag alanları

c_iflag alanı ile software flow control yapmak

struct termios options;
options.c_ciflag |= (IXON | IXOFF | IXANY);//enable software flow control
tcsetattr(fd, TCSANOW, &options)
struct termios options;
options.c_ciflag &= ~(IXON | IXOFF | IXANY);//disable software flow control
tcsetattr(fd, TCSANOW, &options)

c_iflag alanı ile parity hatası içeren byteları süzmek
Örnekte parity hatası içeren byte'lar programa input olarak verilmez.
options.c_iflag |= IGNPAR;//ignore parity errors
c_oflag alanları
c_oflag alanı ile raw output
port_settings.c_oflag &= ~OPOST; //enable raw output 
cfmakeraw yazısına da bakalıbilir.

c_lflag alanı - local flags
c_lflag alanı ile non canonical input yapmak
ICANON seçeneği kullanılır. Herhangi bir cihaz ile seri iletişim için genellikle non-canonical mode kullanılır. non-canonical en basit haliyle non-buffered I/O olarak düşünülebilir. Örnek olarak ACE kütüphanesi ile gelen TTY_IO.cpp dosyasındaki
     // Enable noncanonical input processing mode 
     options.c_lflag &= ~ICANON; 
satırına bakılabilir.

Buradaki soruda getch() metodunda kullanıcı Enter tuşuna basmadan, tuşladığı karakter hemen alınabilsin diye 0 nolu file descriptor olan stdin non-canonical hale getiriliyor.

Basılan her tuş bir byte ile temsil edilmek zorunda değildir. non-canonical modda basılan tuşu anlamak için read işleminin döndürdüğü byte sayısına bakmak gerekir. Örnek:

struct termios term_original, term_current;
tcgetattr(STDIN_FILENO, &term_original);
term_current = term_original;
term_current.c_lflag &= ~(ICANON | ISIG | IEXTEN | ECHO);
term_current.c_iflag &= ~(BRKINT | ICRNL | IGNBRK | IGNCR | INLCR | INPCK | ISTRIP | IXON | PARMRK);
term_current.c_oflag &= ~(OPOST);
term_current.c_cc[VMIN]  = 1;
term_current.c_cc[VTIME] = 0;
tcsetattr(STDIN_FILENO, TCSADRAIN, &term_current);



c_lflag alanı ile input echo
Girilen karakterlerin echolanarak ekranda gösterilmesi veya gösterilmemesini kontrol eder.
options.c_clflag &= ~ECHO; // Disable echoing of input characters
options.c_clflag &= ~ECHOE;
 c_lflag alanı ile sinyaller
ISIG kullanılır.
options.c_lflag &= ~( ISIG | // disable SIGxxxx signals
                            IEXTEN // disable extended functions
clflag ile raw input
Yukarıdaki tüm bayraklar bir araya gelince raw input yapmak için aşağıdaki gibi yapmak lazım.
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // make raw
clflag ile terminal yazmayı engelleme
Arka planda çalışan bir uygulamanın terminale yazmasını engellemek için TOSTOP kullanılır. Örnek:


c_cc alanı
Şöyle kodlanır
options.c_cc[VMIN]   =  1; //read doesn't block. Min. number of chars to read
options.c_cc[VTIME]  =  5; // 0.5 seconds read timeout
Bu satırların anlamı ise buradan aldığım aşağıdaki şekilde anlatılıyor.
select() gibi kullanmak için şöyle yaparız.

VMIN = 0 and VTIME > 0
This is a pure timed read. If data are available in the input queue, it's transferred to the caller's buffer up to a maximum of nbytes, and returned immediately to the caller. Otherwise the driver blocks until data arrives, or when VTIME tenths expire from the start of the call. If the timer expires without data, zero is returned. A single byte is sufficient to satisfy this read call, but if more is available in the input queue, it's returned to the caller. Note that this is an overall timer, not an intercharacter one.

tcgetattr metodu
tcgetattr metodu yazısına taşıdım.

cfsetispeed ve cfsetospeed ile iletişim hızı (Baud Rate)
baud nedir yazısına bakabilirsiniz.

İletişim hızını ayarlamak için cfsetispeed (input) ve cfsetospeed (output) metodları kullanılır.Aslında bu metodlar struct termios yapısının c_cflag alanını değiştiriyor. Ancak niyeyse ayrı metodlar halinde gelmişler.

Bazı platformlar cfsetspeed() extension metodunu da tanımlıyorlar, ancak sanırım bu metod portable değil.

Baud Rate sabitlerini buradan görmek mümkün. Her platformda bulunmasa da özellikle USB cihazları için B2000000 sabitini de görmek mümkün.


Line Control Metodları

tcflush metodu
Bu metod gönderilmemiş tamponda bekleyen veriyi gönderir.
tcflush(fd, TCIFLUSH );
Örnek:
tcflush(fd,TCIOFLUSH)

C#
C# ile seri portu kullanmak için SerialPort sınıfı kullanılabilir. SerialPort nesnesi isimle açılır. Port'un ismi DeviceManager ile öğrenilebilir.

Açma Örneği
Örnek'te SerialPort sınıfına PortName, BaudRate,DataBits,Parity,StopBits atanıyor.

SerialPort myPort = new SerialPort();
myPort.DataReceived += MyPortDataReceived;
myPort.PortName = "COM45";
myPort.BaudRate = 4800;
myPort.DataBits = 8;
myPort.Parity = Parity.None;
myPort.StopBits = StopBits.One;

GetNames Metodu
Port isimlerini alır. Örnek:
// Get all open ports
string[] ports = SerialPort.GetPortNames();

ReadLine Metodu
// Create a new SerialPort object with default settings.
SerialPort s = new SerialPort();

// Set the read/write timeouts
s.ReadTimeout = 500;
s.WriteTimeout = 500;

s.Open();
string message = s.ReadLine();
string message = s.ReadExisting();
s.WriteLine("message");

WriteLine Metodu
Verilen string'i port'a yazar.

Hiç yorum yok:

Yorum Gönder