C의 직렬 포트에서 열고 읽고 쓰는 방법?


139

직렬 포트를 읽고 쓰는 것에 대해 약간 혼란 스럽습니다. Linux에는 FTDI USB 직렬 장치 변환기 드라이버를 사용하는 USB 장치가 있습니다. 플러그인하면 / dev / ttyUSB1이 생성됩니다.

나는 C에서 열고 쓰는 것이 간단하다고 생각했습니다. 보드 속도와 패리티 정보를 알고 있지만 이것에 대한 표준이없는 것 같습니다.

뭔가 빠졌습니까, 아니면 누군가 올바른 방향으로 나를 가리킬 수 있습니까?


18
Serial Programming HOWTO를 보았습니까 ?
ribram

1
편집 : ribram의 링크를 볼 것입니다. 직렬 장치는 파일로 표현하면서 그러나, 점 유적, 장치는 종종 시스템 호출 등을 통해 구현 구체적인 인터페이스를 가지고 있음 ioctlfcntl.
Mr. Shickadance

8
POSIX 운영 체제 용 직렬 프로그래밍 안내서 링크가 업데이트되었습니다 .
svec

1
UNIX 용어 이해 VMIN 및 VTIME 은 직렬 포트에서 read ()의 차단 특성을 처리하는 데 사용되는 VTIME 및 VMIN을 이해하는 데 유용한 리소스입니다.
flak37

Frerking의 "Serial Programming HOWTO"의 코드를 첫 번째 주석에서 언급 한대로 사용하지 마십시오. POSIX 호환 코드로 작성되지 않았으므로 코드 예제는 이식성이 없으며 안정적으로 작동하지 않을 수 있습니다.
톱밥

답변:


247

나는 이것을 1985-1992 년부터 몇 년 동안 약간 수정 하여 썼으며 각 프로젝트에 필요한 비트를 복사하여 붙여 넣습니다.

cfmakeraw에서 tty얻은 제품을 호출해야합니다 tcgetattr. 당신은하지 제로 아웃 할 수있는 struct termios, 구성이 다음을 설정 tty하여 tcsetattr. zero-out 방법을 사용하는 경우, 특히 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()은 포트의 a 가 지정된 문자 수가 도착할 때까지 대기 할지 여부를 설정합니다 . 차단을 설정 하지 않으면read() 리턴은 버퍼 한계까지 더 이상 기다리지 않고 많은 문자를 사용할 수 있음을 의미합니다 .


추가:

CMSPAR마크와 공간 패리티를 선택할 때만 필요합니다. 대부분의 응용 프로그램에서는 생략 할 수 있습니다. 내 헤더 파일 /usr/include/bits/termios.hCMSPAR전 처리기 기호 __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"입니다. 그러나 PC에는 많은 USB 포트가 있습니다. 어떻게 구성합니까?
Bas

3
@Bas : Linux 인 경우 명령 lsusb을 사용하여 모든 USB 장치를 봅니다. 시스템에 사용자 정의 udev규칙 이있는 경우 이름이 다르게 지정할 수 있습니다 . /etc/udev/rules.d/ 어쩌면 거기에서 찾고있는 포트를 선택할 수 있습니다를 참조하십시오 . 확실하게 포트를 나열한 후 플러그를 뽑아서 차이를 식별 할 수 있습니다.
wallyk

1
@ wallyk 공간 패리티 (PARENB | CMSPRAR)를 사용하여 출력 (쓰기 할 수 없음)을 얻을 수 없습니다. 그러나 나는 마크 패리티와 통신 할 수 있습니다. 어떤 아이디어로 해결할 수 있습니까?
Bas

5
이 코드에 대한 비판을 참조 stackoverflow.com/questions/25996171/...
톱밥

2
에서처럼 ttyUSB0 장치로 데이터를 보냈고 실제로 사용하고있는 tty 장치에서 나왔습니다. 이 코드를 사용하여 말 그대로 터미널을 스팸으로 전송했습니다. 톱밥에서 아래 답변은 더 안전한 구현입니다.
Owl

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

수신 된 데이터가 2 진 데이터가 아닌 ASCII 텍스트이고 개행 문자로 끝나는 행으로 읽으려면 샘플 프로그램에 대한 이 답변 을 참조하십시오 .


1
그것의 많은 바로 바뀔 수 cfmakeraw있습니까?
CMCDragonkai

내가 본 다른 예제도 O_NDELAY또는로 포트를 엽니 다 O_NONBLOCK. cmrr.umn.edu/~strupp/serial.html는 당신이 그 플래그와 함께 파일 기술자를 열 경우, 다음이 있음을 언급 VTIME무시됩니다. 그렇다면 O_NONBLOCK파일 디스크립터로 실행하는 것과 파일 디스크립터로 실행하는 것의 차이점은 무엇 VTIME입니까?
CMCDragonkai

@CMCDragonkai-당신이 쓴 것보다 훨씬 더 복잡합니다. 참조 stackoverflow.com/questions/25996171/... 이 질문에 대한 허용 대답을 참조합니다. 비 차단 모드에서 터미널을 열더라도 BTW는 fcntl ()을
sawdust

초보자 질문에 대해 죄송하지만 메인에서 do while 루프를 종료하거나 영원히 반복합니까?
bakalolo

1
@bakalolo-영원히 수신하고 표시하는 간단한 데모 코드입니다. 의도는 다른 코드와 달리 컴파일 (오류 없음)하고 안정적으로 작동하는 이식 가능한 코드입니다. 메시지의 끝을 결정하는 테스트가 추가 될 수 있습니다. 원시 데이터의 경우 메시지 패킷의 정의는 프로토콜에 따라 다릅니다. 또는이 답변에 설명 된 것처럼 다른 스레드가 처리 할 수 ​​있도록 수신 된 데이터를 원형 버퍼에 저장하기 위해이 코드를 수정할 수 있습니다 .
톱밥
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.