Як відкривати, читати та записувати з послідовного порту на C?


139

Я трохи заплутаний у читанні та записі до послідовного порту. У мене в Linux є USB-пристрій, який використовує драйвер перетворювача послідовних пристроїв FTDI USB. Коли я підключаю його, він створює: / dev / ttyUSB1.

Я думав, що і т. Д. Буде просто відкривати та читати / писати з нього в C. Я знаю інформацію про швидкість передачі даних та інформацію про паритет, але здається, що для цього немає стандарту?

Я щось пропускаю, чи хтось може вказати мені в правильному напрямку?


18
Ви подивилися на серійне програмування HOWTO ?
ribram

1
EDIT: Я перегляну посилання на ribram. Однак справа в тому, що хоча послідовний пристрій представлений у вигляді файлів, пристрої часто мають більш конкретні інтерфейси, реалізовані за допомогою системних викликів типу ioctlі та fcntl.
Містер Шикаданс


1
Розуміння термінів UNIX VMIN і VTIME - чудовий ресурс для розуміння VTIME та VMIN, які використовуються для обробки блокуючих характеристик read () на послідовному порту.
flak37

Не використовуйте код із "Серійного програмування HOWTO" Frerking, як згадується у першому коментарі. Вони не написані як сумісні з POSIX, тому приклади коду не є портативними та можуть не працювати надійно для вас.
тирса

Відповіді:


247

Я писав це дуже давно ( з 1985-1992 рр., З тих пір лише декілька налаштувань ) і просто копіював і вставляв потрібні біти в кожен проект.

Ви повинні зателефонувати cfmakerawна ttyотримане від tcgetattr. Ви не можете обнулення в struct termios, налаштувати його, а потім встановити ttyз tcsetattr. Якщо ви використовуєте метод нульового виходу, тоді у вас виникнуть незрозумілі переривчасті збої, особливо на BSD і OS X. "Непояснені переривчасті збої" включають зависання 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

Значення швидкості є B115200, B230400, B9600, B19200, B38400, B57600, B1200, B2400, B4800і т.д. Значення парності НЕ 0(тобто без перевірки парності), PARENB|PARODD(включити парність і використовувати непарний), PARENB(включити парність і використовувати навіть), PARENB|PARODD|CMSPAR(парність знака), і PARENB|CMSPAR( космічний паритет).

"Блокування" встановлює, чи read()очікує, що на порту надходить вказана кількість символів. Якщо не блокувати, це означає, що read()повернення, однак багато символів доступно, не чекаючи більше, до межі буфера.


Додаток:

CMSPARпотрібен лише для вибору позначки та паритету простору, що є рідкістю. У більшості застосунків його можна опустити. Мій файл заголовка /usr/include/bits/termios.hдозволяє визначити CMSPARлише те, якщо визначено символ препроцесора __USE_MISC. Це визначення відбувається (в features.h) з

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

Вступний коментар <features.h>говорить:

/* 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: У моєму комп’ютері немає файлів з назвою ttyUSB, єдині файли з іменем USB - це "usbmon". Але на ПК є багато портів USB. Отже, як їх налаштувати?
Бас

3
@Bas: Якщо це Linux, скористайтеся командою, lsusbщоб переглянути всі USB-пристрої. Їх можна назвати по-різному, якщо у вашій системі є власні udevправила; див. /etc/udev/rules.d/ Можливо, звідти ви можете вибрати потрібний порт. Звичайно, перерахувавши, а потім відключивши / підключивши порт, ви можете визначити різницю.
wallyk

1
@ wallyk Я не в змозі отримати жодного виводу (не в змозі писати) за допомогою простору парності (PARENB | CMSPRAR). Але я вмію спілкуватися з позначкою Parity. Будь-які ідеї, як це вирішити?
Бас

5
Критику цього коду див. Stackoverflow.com/questions/25996171/…
тирса

2
Як і в я надсилав дані на пристрій ttyUSB0, і це вийшло з мого пристрою tty, яким я фактично користувався. Я буквально спамував власний термінал, використовуючи цей код. Відповідь нижче з тирси - це більш безпечне виконання.
Сова

50

Для демо-коду, який відповідає стандарту POSIX, як описано в Правильному налаштуванні термінальних режимів та Посібнику з послідовного програмування для операційних систем POSIX , пропонується наступне.
Це по суті випливає з іншої відповіді, але неточні та оманливі коментарі були виправлені.

#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 = "/dev/ttyUSB0";
    int fd;
    int wlen;

    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, "Hello!\n", 7);
    if (wlen != 7) {
        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);
}

Щоб програма сприйняла отримані дані як ASCII-коди, складіть програму з символом DISPLAY_STRING, наприклад

 cc -DDISPLAY_STRING demo.c

Якщо отримані дані - це текст ASCII (а не двійкові дані), і ви хочете прочитати їх у вигляді рядків, що закінчуються символом нового рядка, то див. Цю відповідь для прикладної програми.


1
Чи багато цього можна було б замінити просто cfmakerawтак?
CMCDragonkai

Інші приклади, які я бачив, також відкривають порт за допомогою O_NDELAYабо O_NONBLOCK. Cmrr.umn.edu/~strupp/serial.html згадує , що якщо відкрити дескриптор файлу з цими прапорами, то VTIMEігнорується. Тоді в чому різниця між запуском O_NONBLOCKдескриптора файлу та його виконанням VTIME?
CMCDragonkai

@CMCDragonkai - Це набагато складніше, ніж те, що ви написали. Див. Stackoverflow.com/questions/25996171/…, де йдеться про прийняту відповідь на це запитання. До речі, навіть якщо ви відкриєте термінал у неблокувальному режимі, ви все одно можете повернутися до режиму блокування за допомогою fcntl ()
тирси

Вибачте за запитання про новачків, але де ви виходите з циклу do, поки основний цикл чи він циклічний назавжди?
bakalolo

1
@bakalolo - Це просто простий демо-код, який можна отримувати та відображати назавжди. Намір - це портативний код, який буде компілювати (без помилок) і надійно працювати (на відміну від іншої відповіді). Тест для визначення кінця повідомлення може бути доданий; із необробленими даними визначення пакета повідомлень залежить від протоколу. Або цей код може бути змінений, щоб просто зберігати отримані дані у круговому буфері для іншого потоку для обробки, наприклад, описаного в цій відповіді .
тирса
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.