Come aprire, leggere e scrivere dalla porta seriale in C?


139

Sono un po 'confuso riguardo la lettura e la scrittura su una porta seriale. Ho un dispositivo USB in Linux che utilizza il driver del convertitore di dispositivo seriale USB FTDI. Quando lo collego, crea: / dev / ttyUSB1.

Ho pensato che sarebbe stato semplice aprirlo e leggerlo / scriverlo in C. Conosco il baud rate e le informazioni sulla parità, ma sembra che non ci siano standard per questo?

Mi sto perdendo qualcosa o qualcuno può indicarmi la giusta direzione?


18
Hai dato un'occhiata al Serial Programming HOWTO ?
ribram,

1
EDIT: darei un'occhiata al link di Ribram. Tuttavia, resta il punto che mentre un dispositivo seriale è rappresentato come un file, i dispositivi spesso hanno interfacce più specifiche implementate tramite chiamate di sistema come ioctle fcntl.
Mr. Shickadance,

8
Collegamento aggiornato alla Guida alla programmazione seriale per i sistemi operativi POSIX .
svec,

1
Comprensione dei termios UNIX VMIN e VTIME è una grande risorsa per comprendere VTIME e VMIN che vengono utilizzati per gestire le caratteristiche di blocco di un read () su una porta seriale.
flak37,

Non usare il codice del "Serial Programming HOWTO" di Frerking come menzionato nel primo commento. Non sono scritti per essere conformi a POSIX, quindi gli esempi di codice non sono portatili e potrebbero non funzionare in modo affidabile.
segatura

Risposte:


247

L'ho scritto molto tempo fa ( dagli anni 1985-1992, con solo alcune modifiche da allora ), e ho semplicemente copiato e incollato i bit necessari in ogni progetto.

È necessario chiamare cfmakerawun ttyottenuto da tcgetattr. Non è possibile azzerare un struct termios, configurarlo e quindi impostare il ttycon tcsetattr. Se si utilizza il metodo zero-out, si verificheranno errori intermittenti inspiegabili, in particolare su BSD e OS X. Gli "errori intermittenti inspiegabili" includono l'aggancio 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

I valori per la velocità sono B115200, B230400, B9600, B19200, B38400, B57600, B1200, B2400, B4800, ecc I valori di parità sono 0(che significa nessuna parità), PARENB|PARODD(consentire la parità e utilizzare dispari), PARENB(consentire parità e usare anche), PARENB|PARODD|CMSPAR(parità marchio), e PARENB|CMSPAR( parità di spazio).

"Blocco" imposta se a read()sulla porta attende l'arrivo del numero specificato di caratteri. L'impostazione di nessun blocco significa che un read()ritorni comunque sono disponibili molti caratteri senza aspettare di più, fino al limite del buffer.


Addendum:

CMSPARè necessario solo per scegliere la parità del segno e dello spazio, il che è insolito. Per la maggior parte delle applicazioni, può essere omesso. Il mio file di intestazione /usr/include/bits/termios.habilita la definizione CMSPARsolo se __USE_MISCè definito il simbolo del preprocessore . Tale definizione si verifica (in features.h) con

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

I commenti introduttivi di <features.h>dice:

/* 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: Nel mio computer non ci sono file di nome ttyUSB, gli unici file di nome USB sono "usbmon". Ma il PC ha molte porte USB. Quindi, come li configuro?
Bas

3
@Bas: se è Linux, usa il comando lsusbper vedere tutti i dispositivi USB. Potrebbero essere nominati diversamente se il tuo sistema ha udevregole personalizzate ; vedi /etc/udev/rules.d/ Forse da lì puoi scegliere la porta che stai cercando. Certamente elencando e quindi scollegando / disconnettendo la porta è possibile identificare la differenza.
Wallyk,

1
@ wallyk Non riesco a ottenere alcun output (impossibile scrivere) usando la parità di spazio (PARENB | CMSPRAR). Ma sono in grado di comunicare con Mark Parity. Qualche idea su come risolverlo?
Bas

5
Per una critica a questo codice vedi stackoverflow.com/questions/25996171/…
segatura

2
Come in ho inviato i dati a un dispositivo ttyUSB0 ed è uscito dal mio dispositivo tty che stavo effettivamente utilizzando. Stavo letteralmente inviando spam al mio terminale usando questo codice. La risposta che segue della segatura è un'implementazione più sicura.
Gufo

50

Per il codice demo conforme allo standard POSIX come descritto in Impostazione corretta delle modalità terminali e Guida alla programmazione seriale per sistemi operativi POSIX , viene offerto quanto segue.
Questo codice dovrebbe essere eseguito correttamente utilizzando Linux su processori x86 e ARM (o persino CRIS).
Deriva essenzialmente dall'altra risposta, ma i commenti inaccurati e fuorvianti sono stati corretti.

Questo programma demo apre e inizializza un terminale seriale a 115200 baud per una modalità non canonica il più portatile possibile.
Il programma trasmette una stringa di testo hardcoded all'altro terminale e ritarda durante l'esecuzione dell'output.
Il programma entra quindi in un ciclo infinito per ricevere e visualizzare i dati dal terminale seriale.
Per impostazione predefinita, i dati ricevuti vengono visualizzati come valori di byte esadecimali.

Per fare in modo che il programma tratti i dati ricevuti come codici ASCII, compilare il programma con il simbolo DISPLAY_STRING, ad es.

 cc -DDISPLAY_STRING demo.c

Se i dati ricevuti sono testo ASCII (anziché dati binari) e si desidera leggerlo come righe terminate dal carattere di nuova riga, vedere questa risposta per un programma di esempio.


#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
Molto potrebbe essere sostituito con cfmakerawgiusto?
CMCDragonkai,

Altri esempi che ho visto anche aprire la porta con O_NDELAYo O_NONBLOCK. Il cmrr.umn.edu/~strupp/serial.html menziona che se si apre il descrittore di file con tali flag, VTIMEviene ignorato. Quindi qual è la differenza tra l'esecuzione con O_NONBLOCKil descrittore di file e il farlo con VTIME?
CMCDragonkai,

@CMCDragonkai - È molto più complicato di quello che hai scritto. Vedi stackoverflow.com/questions/25996171/… che fa riferimento alla risposta accettata a questa domanda. A proposito anche se si apre il terminale in modalità non bloccante, è ancora possibile tornare alla modalità di blocco con un fcntl ()
segatura

Ci scusiamo per la domanda per principianti, ma dove esci dal ciclo do while in main o continua?
Bakalolo,

1
@bakalolo - È solo un semplice codice demo da ricevere e visualizzare per sempre. L'intento è un codice portatile che verrà compilato (senza errori) e funzionerà in modo affidabile (a differenza dell'altra risposta). È possibile aggiungere un test per determinare la fine del messaggio; con dati non elaborati la definizione di un pacchetto di messaggi dipende dal protocollo. Oppure questo codice può essere modificato per archiviare i dati ricevuti in un buffer circolare per l'elaborazione di un altro thread, come descritto in questa risposta .
segatura
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.