C seri bağlantı noktasından nasıl açılır, okunur ve yazılır?


139

Bir seri porta okuma ve yazma konusunda biraz kafam karıştı. Linux'ta FTDI USB seri aygıt dönüştürücü sürücüsünü kullanan bir USB aygıtım var. Fişi taktığımda oluşturur: / dev / ttyUSB1.

C de açmak ve okumak / yazmak için basit olacağını düşündüm. Baud hızı ve parite bilgilerini biliyorum, ama bunun için bir standart yok gibi görünüyor?

Bir şey mi kaçırıyorum yoksa biri beni doğru yöne yönlendirebilir mi?


18
Eğer bir göz uyguladınız mı Seri Programlama NASIL ?
ribram

1
EDIT: Ribram'ın bağlantısına bakardım. Bununla birlikte, nokta, bir seri cihaz bir dosya olarak temsil edilirken, cihazların genellikle ioctlve gibi sistem çağrıları yoluyla uygulanan daha spesifik arayüzlere sahip olduğu anlamına gelir fcntl.
Bay Shickadance


1
UNIX terminallerini anlamak VMIN ve VTIME , bir seri bağlantı noktasındaki bir read () öğesinin engelleme özelliklerini işlemek için kullanılan VTIME ve VMIN'i anlamak için harika bir kaynaktır.
flak37

İlk yorumda belirtildiği gibi Frerking'in "Seri Programlama NASIL" kodunu kullanmayın. POSIX uyumlu oldukları için yazılmamıştır, bu nedenle kod örnekleri taşınabilir değildir ve sizin için güvenilir bir şekilde çalışmayabilir.
talaş

Yanıtlar:


247

Bunu uzun zaman önce yazdım ( 1985-1992 yılları arasında, o zamandan beri sadece birkaç değişiklikle ) ve sadece gereken bitleri her projeye kopyalayıp yapıştırdım.

Sen çağırmalıdır cfmakerawbir üzerinde ttyelde edilen tcgetattr. Sen sıfır dışarı can struct termios, yapılandırmak o ve sonra set ttyile tcsetattr. Sıfır çıkış yöntemini kullanırsanız, özellikle BSD'lerde ve OS X'te açıklanamayan aralıklı hatalarla karşılaşırsınız. "Açıklanamayan aralıklı hatalar" takmayı içerir read(3).

#include <errno.h>
#include <fcntl.h> 
#include <string.h>
#include <termios.h>
#include <unistd.h>

int
set_interface_attribs (int fd, int speed, int parity)
{
        struct termios tty;
        if (tcgetattr (fd, &tty) != 0)
        {
                error_message ("error %d from tcgetattr", errno);
                return -1;
        }

        cfsetospeed (&tty, speed);
        cfsetispeed (&tty, speed);

        tty.c_cflag = (tty.c_cflag & ~CSIZE) | CS8;     // 8-bit chars
        // disable IGNBRK for mismatched speed tests; otherwise receive break
        // as \000 chars
        tty.c_iflag &= ~IGNBRK;         // disable break processing
        tty.c_lflag = 0;                // no signaling chars, no echo,
                                        // no canonical processing
        tty.c_oflag = 0;                // no remapping, no delays
        tty.c_cc[VMIN]  = 0;            // read doesn't block
        tty.c_cc[VTIME] = 5;            // 0.5 seconds read timeout

        tty.c_iflag &= ~(IXON | IXOFF | IXANY); // shut off xon/xoff ctrl

        tty.c_cflag |= (CLOCAL | CREAD);// ignore modem controls,
                                        // enable reading
        tty.c_cflag &= ~(PARENB | PARODD);      // shut off parity
        tty.c_cflag |= parity;
        tty.c_cflag &= ~CSTOPB;
        tty.c_cflag &= ~CRTSCTS;

        if (tcsetattr (fd, TCSANOW, &tty) != 0)
        {
                error_message ("error %d from tcsetattr", errno);
                return -1;
        }
        return 0;
}

void
set_blocking (int fd, int should_block)
{
        struct termios tty;
        memset (&tty, 0, sizeof tty);
        if (tcgetattr (fd, &tty) != 0)
        {
                error_message ("error %d from tggetattr", errno);
                return;
        }

        tty.c_cc[VMIN]  = should_block ? 1 : 0;
        tty.c_cc[VTIME] = 5;            // 0.5 seconds read timeout

        if (tcsetattr (fd, TCSANOW, &tty) != 0)
                error_message ("error %d setting term attributes", errno);
}


...
char *portname = "/dev/ttyUSB1"
 ...
int fd = open (portname, O_RDWR | O_NOCTTY | O_SYNC);
if (fd < 0)
{
        error_message ("error %d opening %s: %s", errno, portname, strerror (errno));
        return;
}

set_interface_attribs (fd, B115200, 0);  // set speed to 115,200 bps, 8n1 (no parity)
set_blocking (fd, 0);                // set no blocking

write (fd, "hello!\n", 7);           // send 7 character greeting

usleep ((7 + 25) * 100);             // sleep enough to transmit the 7 plus
                                     // receive 25:  approx 100 uS per char transmit
char buf [100];
int n = read (fd, buf, sizeof buf);  // read up to 100 characters if ready to read

Hız için değerlerdir B115200, B230400, B9600, B19200, B38400, B57600, B1200, B2400, B4800, vb parite değerleri vardır 0, (hayır paritesi anlamına gelir) PARENB|PARODD, (paritesine etkinleştirmek ve garip kullanın) PARENB(paritesine etkinleştirmek ve hatta kullanın), PARENB|PARODD|CMSPAR(işareti paritesi) ve PARENB|CMSPAR( boşluk paritesi).

"Engelleme" read(), bağlantı noktasındaki a'nın belirtilen sayıda karakterin gelmesini bekleyip beklemeyeceğini ayarlar . Ayar hiçbir engelleme bir o aracı read()döner ancak birçok karakter tampon sınırına kadar, daha beklemeden mevcuttur.


Zeyilname:

CMSPARsadece nadir bulunan işaret ve boşluk paritesini seçmek için gereklidir. Çoğu uygulama için atlanabilir. Başlık /usr/include/bits/termios.hdosyam, CMSPARyalnızca önişlemci sembolü __USE_MISCtanımlanmışsa tanımlamayı etkinleştirir . Bu tanım, (oluşur features.h) ile

#if defined _BSD_SOURCE || defined _SVID_SOURCE
 #define __USE_MISC     1
#endif

Tanıtım yorumları <features.h>şunları söylüyor:

/* These are defined by the user (or the compiler)
   to specify the desired environment:

...
   _BSD_SOURCE          ISO C, POSIX, and 4.3BSD things.
   _SVID_SOURCE         ISO C, POSIX, and SVID things.
...
 */

1
@wallyk: Bilgisayarımda ttyUSB adlı dosya yok, USB adlı tek dosya "usbmon". Ancak pc'nin çok fazla USB portu var. Peki onları nasıl yapılandırabilirim?
Bas

3
@Bas: Linux ise, lsusbtüm USB aygıtlarını görmek için komutu kullanın. Sisteminizde özel udevkurallar varsa bunlar farklı şekilde adlandırılabilir ; bkz. /etc/udev/rules.d/ Belki oradan aradığınız limanı seçebilirsiniz. Kesinlikle bağlantı noktasını listeleyip ardından / fişini çekerek farkı tanımlayabilirsiniz.
wallyk

1
@ wallyk Boşluk paritesi (PARENB | CMSPRAR) kullanarak herhangi bir çıktı (yazılamıyor) alamıyorum. Ama mark Parite ile iletişim kurabiliyorum. Nasıl çözüleceğine dair bir fikrin var mı?
Bas

5
Bu kodun bir eleştirisi için bkz. Stackoverflow.com/questions/25996171/…
talaş

2
Ben de bir ttyUSB0 cihaza veri gönderdi ve ben aslında kullanmakta olduğum tty cihazımdan çıktı. Kelimenin tam anlamıyla bu kodu kullanarak kendi terminal spam. Talaşın aşağıdaki cevabı daha güvenli bir uygulamadır.
Baykuş

50

POSIX İşletim Sistemleri için Terminal Modlarını Düzgün Ayarlama ve Seri Programlama Kılavuzu'nda açıklandığı gibi POSIX standardına uyan demo kodu için aşağıdakiler sunulmaktadır.
Bu kod, x86 üzerinde Linux ve ARM (hatta CRIS) işlemcileri kullanarak doğru şekilde çalışmalıdır.
Esasen diğer cevaptan türetilmiştir, ancak yanlış ve yanıltıcı yorumlar düzeltilmiştir.

Bu demo programı, mümkün olduğunca taşınabilir olan standart olmayan mod için 115200 baud'da bir seri terminal açar ve başlatır.
Program sabit bir metin dizesini diğer terminale iletir ve çıkış yapılırken gecikir.
Program daha sonra seri terminalden veri almak ve görüntülemek için sonsuz bir döngüye girer.
Varsayılan olarak, alınan veriler onaltılık bayt değerleri olarak görüntülenir.

Programın alınan verileri ASCII kodları olarak işlemesini sağlamak için programı DISPLAY_STRING sembolü ile derleyin, örn.

 cc -DDISPLAY_STRING demo.c

Alınan veriler ASCII metni (ikili veri yerine) ise ve bunu yeni satır karakteriyle sonlandırılmış satırlar olarak okumak istiyorsanız, örnek bir program için bu cevaba bakın .


#define TERMINAL    "/dev/ttyUSB0"

#include <errno.h>
#include <fcntl.h> 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>

int set_interface_attribs(int fd, int speed)
{
    struct termios tty;

    if (tcgetattr(fd, &tty) < 0) {
        printf("Error from tcgetattr: %s\n", strerror(errno));
        return -1;
    }

    cfsetospeed(&tty, (speed_t)speed);
    cfsetispeed(&tty, (speed_t)speed);

    tty.c_cflag |= (CLOCAL | CREAD);    /* ignore modem controls */
    tty.c_cflag &= ~CSIZE;
    tty.c_cflag |= CS8;         /* 8-bit characters */
    tty.c_cflag &= ~PARENB;     /* no parity bit */
    tty.c_cflag &= ~CSTOPB;     /* only need 1 stop bit */
    tty.c_cflag &= ~CRTSCTS;    /* no hardware flowcontrol */

    /* setup for non-canonical mode */
    tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
    tty.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
    tty.c_oflag &= ~OPOST;

    /* fetch bytes as they become available */
    tty.c_cc[VMIN] = 1;
    tty.c_cc[VTIME] = 1;

    if (tcsetattr(fd, TCSANOW, &tty) != 0) {
        printf("Error from tcsetattr: %s\n", strerror(errno));
        return -1;
    }
    return 0;
}

void set_mincount(int fd, int mcount)
{
    struct termios tty;

    if (tcgetattr(fd, &tty) < 0) {
        printf("Error tcgetattr: %s\n", strerror(errno));
        return;
    }

    tty.c_cc[VMIN] = mcount ? 1 : 0;
    tty.c_cc[VTIME] = 5;        /* half second timer */

    if (tcsetattr(fd, TCSANOW, &tty) < 0)
        printf("Error tcsetattr: %s\n", strerror(errno));
}


int main()
{
    char *portname = TERMINAL;
    int fd;
    int wlen;
    char *xstr = "Hello!\n";
    int xlen = strlen(xstr);

    fd = open(portname, O_RDWR | O_NOCTTY | O_SYNC);
    if (fd < 0) {
        printf("Error opening %s: %s\n", portname, strerror(errno));
        return -1;
    }
    /*baudrate 115200, 8 bits, no parity, 1 stop bit */
    set_interface_attribs(fd, B115200);
    //set_mincount(fd, 0);                /* set to pure timed read */

    /* simple output */
    wlen = write(fd, xstr, xlen);
    if (wlen != xlen) {
        printf("Error from write: %d, %d\n", wlen, errno);
    }
    tcdrain(fd);    /* delay for output */


    /* simple noncanonical input */
    do {
        unsigned char buf[80];
        int rdlen;

        rdlen = read(fd, buf, sizeof(buf) - 1);
        if (rdlen > 0) {
#ifdef DISPLAY_STRING
            buf[rdlen] = 0;
            printf("Read %d: \"%s\"\n", rdlen, buf);
#else /* display hex */
            unsigned char   *p;
            printf("Read %d:", rdlen);
            for (p = buf; rdlen-- > 0; p++)
                printf(" 0x%x", *p);
            printf("\n");
#endif
        } else if (rdlen < 0) {
            printf("Error from read: %d: %s\n", rdlen, strerror(errno));
        } else {  /* rdlen == 0 */
            printf("Timeout from read\n");
        }               
        /* repeat read to get full message */
    } while (1);
}

1
Bunların birçoğu cfmakerawdoğru ile değiştirilebilir mi?
CMCDragonkai

Gördüğüm diğer örnekler de limanı O_NDELAYveya ile açıyor O_NONBLOCK. Cmrr.umn.edu/~strupp/serial.html o bayraklarla dosya tanıtıcı açarsanız, o zaman bahseder VTIMEdikkate alınmaz. O zaman O_NONBLOCKdosya tanımlayıcı ile çalışmak ve onunla yapmak arasındaki fark VTIMEnedir?
CMCDragonkai

@CMCDragonkai - Yazdıklarınızdan çok daha karmaşık. Bu soruya kabul edilen cevaba gönderme yapan stackoverflow.com/questions/25996171/… adresine bakın . BTW, terminali engellemesiz modda açsanız bile, yine de bir fcntl ()
talaş

Acemi soru için özür dilerim ama while döngüsünde anadan nerede çıkıyorsunuz veya sonsuza kadar dönüyor mu?
bakalolo

1
@bakalolo - Sonsuza kadar almak ve görüntülemek için sadece basit bir demo kodu. Niyet, (diğer cevapların aksine) derlenecek (w / o hataları) ve güvenilir bir şekilde çalışacak taşınabilir koddur. Mesajın sonunu belirlemek için bir test eklenebilir; ham verilerle bir mesaj paketinin tanımı protokole bağlıdır. Veya bu kod, alınan cevapları , bu cevapta tarif edildiği gibi, başka bir iş parçacığının işlenmesi için sadece dairesel bir arabellekte saklayacak şekilde değiştirilebilir .
talaş
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.