Jak otwierać, odczytywać i zapisywać z portu szeregowego w C?

Jestem trochę zdezorientowany czytaniem i zapisem do portu szeregowego. Mam Urządzenie USB w Linuksie, które używa sterownika konwertera szeregowego USB FTDI. Kiedy go podłączam, tworzy: / dev / ttyUSB1.

Myślałem, że itd jest proste do otwarcia i odczytu/zapisu z niego w C. znam szybkość transmisji i informacje o parzystości, ale wygląda na to, że nie ma na to standardu?

Coś mi umyka, czy ktoś może wskazać mi właściwy kierunek?

Author: jww, 2011-08-04

2 answers

Napisałem to dawno temu (z lat 1985-1992, z kilkoma poprawkami od tego czasu ), i po prostu skopiuj i wklej potrzebne bity do każdego projektu.

Musisz zadzwonić cfmakeraw na tty uzyskany z tcgetattr. Nie można wyzerować struct termios, skonfigurować go, a następnie ustawić tty za pomocą tcsetattr. Jeśli użyjesz metody zero-out, doświadczysz niewyjaśnionych przerywanych awarii, zwłaszcza na BSD i OS X. "niewyjaśnione przerywane awarie" obejmują zawieszanie się w 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

Wartości prędkości to B115200, B230400, B9600, B19200, B38400, B57600, B1200, B2400, B4800, itd. Wartości dla parzystości to 0 (bez parzystości), PARENB|PARODD (włącz parzystość i użyj parzystości), PARENB (włącz parzystość i użyj parzystości), PARENB|PARODD|CMSPAR (Oznacz parzystość) i PARENB|CMSPAR (parzystość przestrzeni).

"Blocking" ustawia, czy read() na porcie czeka na nadejście określonej liczby znaków. Ustawienie brak blokowania oznacza, że read() zwraca jednak wiele znaki są dostępne bez czekania na więcej, aż do limitu bufora.


Dodatek:

CMSPAR jest potrzebny tylko do wyboru znaku i parytetu przestrzeni, co jest rzadkością. W większości zastosowań można go pominąć. Mój plik nagłówkowy /usr/include/bits/termios.h umożliwia zdefiniowanie CMSPAR tylko wtedy, gdy zdefiniowany jest symbol preprocesora __USE_MISC. Definicja ta występuje (w features.h) z

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

Wstępny komentarz <features.h> mówi:

/* 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.
...
 */
 255
Author: wallyk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-11-25 04:46:11

Dla kodu demonstracyjnego zgodnego ze standardem POSIX opisanym w odpowiednie Ustawianie trybów terminala i Serial Programming Guide for POSIX Operating Systems , Poniżej przedstawiono
Kod ten powinien być poprawnie wykonany przy użyciu Linuksa na procesorach x86 oraz arm (a nawet Cris).
Wynika to zasadniczo z drugiej odpowiedzi, ale niedokładne i wprowadzające w błąd komentarze zostały poprawione.

Ten program demonstracyjny otwiera i inicjalizuje terminal szeregowy w 115200 baud dla trybu niekanonicznego, który jest jak najbardziej przenośny.
Program przesyła zakodowany na twardo ciąg tekstowy do drugiego terminala i opóźnia wykonanie wyjścia.
Następnie program wchodzi w nieskończoną pętlę, aby odbierać i wyświetlać dane z terminala szeregowego.
Domyślnie odebrane dane są wyświetlane jako szesnastkowe wartości bajtów.

Aby program traktował otrzymane dane jako kody ASCII, skompiluj program za pomocą symbolu DISPLAY_STRING, np.

 cc -DDISPLAY_STRING demo.c

Jeśli odebrane dane są tekstem ASCII (a nie danymi binarnymi) i chcesz je odczytać jako linie zakończone znakiem nowej linii, zobacz ta odpowiedź dla przykładowego programu.


#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);
}

Przykład efektywnego programu, który zapewnia buforowanie odebranych danych, ale pozwala na przekazanie danych wejściowych bajt po bajcie, zobacz ta odpowiedź.


 54
Author: sawdust,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2021-01-14 22:40:08