GNU/Linux >> Znalost Linux >  >> Linux

Jak otevřít, číst a zapisovat ze sériového portu v C?

Pro ukázkový kód, který odpovídá standardu POSIX, jak je popsáno v Nastavení režimů terminálu správně a v příručce sériového programování pro operační systémy POSIX, je nabízeno následující.
Tento kód by se měl správně spustit pomocí Linuxu na x86 i procesorech ARM (nebo dokonce CRIS).
Je to v podstatě odvozeno z druhé odpovědi, ale nepřesné a zavádějící komentáře byly opraveny.

Tento ukázkový program otevře a inicializuje sériový terminál na 115200 baudů pro nekanonický režim, který je maximálně přenosný.
Program přenese napevno zakódovaný textový řetězec do druhého terminálu a zpozdí se při provádění výstupu.
Program poté vstoupí do nekonečné smyčky pro příjem a zobrazení dat ze sériového terminálu.
Ve výchozím nastavení jsou přijatá data zobrazena jako hexadecimální bajtové hodnoty.

Aby program zacházel s přijatými daty jako s kódy ASCII, zkompilujte program se symbolem DISPLAY_STRING, např.

 cc -DDISPLAY_STRING demo.c

Pokud jsou přijatá data text ASCII (spíše než binární data) a chcete je číst jako řádky ukončené znakem nového řádku, podívejte se na tuto odpověď pro ukázkový program.

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

Příklad efektivního programu, který poskytuje ukládání přijatých dat do vyrovnávací paměti, ale zároveň umožňuje předávání vstupu po bytech, naleznete v této odpovědi.


Napsal jsem to už dávno (z let 1985-1992, od té doby jen s několika úpravami) a stačí zkopírovat a vložit potřebné kousky do každého projektu.

Musíte zavolat cfmakeraw na tty získané z tcgetattr . Nemůžete vynulovat struct termios , nakonfigurujte jej a poté nastavte tty s tcsetattr . Pokud použijete metodu zero-out, zaznamenáte nevysvětlitelné občasné poruchy, zejména na BSD a OS X. Mezi „nevysvětlené občasné poruchy“ patří zablokování v 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

Hodnoty rychlosti jsou B115200 , B230400 , B9600 , B19200 , B38400 , B57600 , B1200 , B2400 , B4800 , atd. Hodnoty parity jsou 0 (to znamená bez parity), PARENB|PARODD (povolit paritu a použít liché), PARENB (povolit paritu a použít sudé), PARENB|PARODD|CMSPAR (označit paritu) a PARENB|CMSPAR (parita prostoru).

"Blocking" nastavuje, zda read() na portu čeká na příchod zadaného počtu znaků. Nastavení žádné blokování znamená, že read() vrátí, nicméně mnoho znaků je dostupných bez čekání na další, až do limitu vyrovnávací paměti.

Dodatek:

CMSPAR je potřeba pouze pro volbu parity značky a prostoru, což je neobvyklé. U většiny aplikací jej lze vynechat. Můj soubor záhlaví /usr/include/bits/termios.h umožňuje definici CMSPAR pouze pokud je symbol preprocesoru __USE_MISC je definováno. Tato definice se vyskytuje (v features.h ) s

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

Úvodní komentáře <features.h> říká:

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

Linux
  1. Jak číst a opravovat zprávy o odmítnutí SELinuxu

  2. Jak číst a zapisovat na jednotky Windows NTFS jako každý uživatel

  3. Jak zjistit, který sériový port se používá?

  1. Jak otevřít sériový port v linuxu bez změny pinu?

  2. Jak otevřít port v Linuxu

  3. Jak číst/zapisovat všechna nastavení BIOSu z Linux CLI?

  1. Jak otevřít port 80 a 443 ve FirewallD

  2. Jak se připojit a odeslat data do sériového portu Bluetooth v systému Linux?

  3. Čtení/zápis na sériový port bez root?