Como abrir, ler e gravar da porta serial em C?

139

Estou um pouco confuso sobre ler e gravar em uma porta serial. Eu tenho um dispositivo USB no Linux que usa o driver do conversor de dispositivo serial USB da FTDI. Quando eu o conecto, ele cria: / dev / ttyUSB1.

Eu pensei que seria simples abrir e ler / gravar dele em C. Conheço a taxa de transmissão e as informações de paridade, mas parece que não há um padrão para isso?

Estou faltando alguma coisa ou alguém pode me apontar na direção certa?

gnychis
fonte
18
Você deu uma olhada no HOWTO de programação serial ?
Ribram 4/08
1
Edição: Eu olharia para o link do ribram. No entanto, o argumento é que, enquanto um dispositivo serial é representado como um arquivo, os dispositivos geralmente têm interfaces mais específicas implementadas por meio de chamadas do sistema como ioctle fcntl.
Sr. Shickadance
1
Noções básicas sobre os termos do UNIX VMIN e VTIME é um ótimo recurso para entender o VTIME e o VMIN, que são usados ​​para lidar com as características de bloqueio de um read () em uma porta serial.
precisa saber é o seguinte
Não use o código do "Serial Programming HOWTO" da Frerking, como mencionado no primeiro comentário. Eles não foram escritos para serem compatíveis com POSIX, portanto, os exemplos de código não são portáteis e podem não funcionar de maneira confiável para você.
serragem

Respostas:

246

Escrevi isso há muito tempo ( dos anos 1985-1992, com apenas alguns ajustes desde então ), e apenas copio e colo os bits necessários em cada projeto.

Você deve solicitar cfmakerawum ttyobtido de tcgetattr. Você não pode zerar a struct termios, configurá-lo e depois definir ttycom tcsetattr. Se você usar o método zero-out, ocorrerá falhas intermitentes inexplicáveis, especialmente nos BSDs e no OS X. "Falhas intermitentes inexplicáveis" incluem pendurar 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

Os valores para a velocidade são B115200, B230400, B9600, B19200, B38400, B57600, B1200, B2400, B4800, etc. Os valores de paridade são 0(ou seja, sem paridade), PARENB|PARODD(permitir a paridade e utilizá ímpar), PARENB(permitir a paridade e utilizá mesmo), PARENB|PARODD|CMSPAR(marca de paridade), e PARENB|CMSPAR( paridade espacial).

"Bloqueio" define se um read()na porta aguarda o número especificado de caracteres para chegar. Definir nenhum bloqueio significa que um read()retorno, no entanto, muitos caracteres estão disponíveis sem esperar mais, até o limite do buffer.


Termo aditivo:

CMSPARé necessário apenas para escolher a paridade de marca e espaço, o que é incomum. Para a maioria dos aplicativos, ele pode ser omitido. Meu arquivo de cabeçalho /usr/include/bits/termios.hpermite a definição CMSPARsomente se o símbolo do pré-processador __USE_MISCestiver definido. Essa definição ocorre (in features.h) com

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

Os comentários introdutórios de <features.h>dizem:

/* 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.
...
 */
Wallyk
fonte
1
@wallyk: No meu computador, não existem arquivos com o nome ttyUSB, os únicos arquivos com o nome USB são "usbmon". Mas o PC tem muitas portas USB. Então, como eu os configuro?
Bas
3
@Bas: Se for Linux, use o comando lsusbpara ver todos os dispositivos USB. Eles podem ter nomes diferentes se o seu sistema tiver udevregras personalizadas ; veja /etc/udev/rules.d/ Talvez de lá você possa escolher a porta que está procurando. Certamente, listando e desconectando a porta, você pode identificar a diferença.
wallyk
1
@ wallyk Não consigo obter nenhuma saída (não é possível gravar) usando paridade de espaço (PARENB | CMSPRAR). Mas sou capaz de me comunicar com a marca Parity. Alguma idéia de como resolvê-lo?
Bas
5
Para uma crítica desse código, consulte stackoverflow.com/questions/25996171/…
sawdust
2
Como enviei dados para um dispositivo ttyUSB0 e ele saiu do meu dispositivo tty que eu estava realmente usando. Eu estava literalmente enviando spam para o meu próprio terminal usando esse código. A resposta abaixo da serragem é uma implementação mais segura.
Owl #
50

Para código de demonstração que esteja em conformidade com o padrão POSIX, conforme descrito em Configurando os modos de terminal corretamente e no Guia de programação serial para sistemas operacionais POSIX , é oferecido o seguinte.
É essencialmente derivado da outra resposta, mas comentários imprecisos e enganosos foram corrigidos.

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

Para fazer o programa tratar os dados recebidos como códigos ASCII, compile o programa com o símbolo DISPLAY_STRING, por exemplo

 cc -DDISPLAY_STRING demo.c

Se os dados recebidos são texto ASCII (em vez de dados binários) e você deseja lê-los como linhas terminadas pelo caractere de nova linha, consulte esta resposta para um programa de amostra.

serragem
fonte
1
Muito disso poderia ser substituído por apenas cfmakerawcerto?
precisa saber é o seguinte
Outros exemplos que eu já vi também abrem a porta com O_NDELAYou O_NONBLOCK. O cmrr.umn.edu/~strupp/serial.html menciona que, se você abrir o descritor de arquivo com esses sinalizadores, ele VTIMEserá ignorado. Então, qual é a diferença entre executar com O_NONBLOCKo descritor de arquivo e fazê-lo VTIME?
precisa saber é o seguinte
@CMCDragonkai - É muito mais complicado do que você escreveu. Consulte stackoverflow.com/questions/25996171/… que faz referência à resposta aceita para esta pergunta. BTW, mesmo se você abrir o terminal no modo sem bloqueio, você ainda poderá reverter para o modo de bloqueio com um fcntl ()
sawdust
Desculpe a pergunta do novato, mas onde você está saindo do loop do while no main ou o loop para sempre?
21917 bakalolo
1
@bakalolo - É apenas um código de demonstração simples para receber e exibir para sempre. A intenção é o código portátil que será compilado (sem erros) e funcionará de maneira confiável (diferente da outra resposta). Um teste para determinar o final da mensagem pode ser adicionado; com dados brutos, a definição de um pacote de mensagens depende do protocolo. Ou esse código pode ser modificado para armazenar apenas os dados recebidos em um buffer circular para outro thread processar, como descrito nesta resposta .
Serragem