Como ler um dado binário sobre o terminal serial no programa C?

Eu li os links seguidos e outras fontes, mas não encontrei resposta para a minha pergunta.

Dados binários sobre o terminal serial

Os dados são corrompidos durante a transmissão pela porta serial

Eu me comunico com meu dispositivo incorporado por meio de uma porta serial. Por padrão, o Linux incorporado usa essa porta como um terminal. Mas eu quero transferir também dados binários (pacotes de serviço) através da porta. Meu arquivo / etc / inittab tem uma chamada "getty":console :: respawn: / sbin / getty 115200 ttyS0

Eu também tenho o arquivo / etc / passwd com string onde o usuário "admin" ativa meu aplicativo "cli" depois do login:admin: 8Mt / Jtxcyg8AY: 1000: 0: admin: / tmp: / tmp / cli

Minhas configurações padrão do ttyS0 antes de executar o programa são:

~ # stty -a
speed 115200 baud;stty: standard input
 line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = ^J;
eol2 = <undef>; swtch = <undef>; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R;
werase = ^W; lnext = ^V; flush = ^O; min = 1; time = 0;
-parenb -parodd cs8 hupcl -cstopb cread -clocal -crtscts
-ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon ixoff
-iuclc -ixany -imaxbel -iutf8
opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
isig icanon -iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt
-echoctl echoke
~ #

Então, no meu programa cli eu faço o seguinte:

main ()
{
    ...
    system("stty erase ^H);
    system("stty -F /dev/ttyS0 -icrnl -ixon -ixoff -opost -isig -icanon -echo");    // enter in non-canonical (raw) mode

    // What function do I need to use here to retrieve binary data (also symbols that > 0x7F) from /dev/ttyS0?

    system("stty -F /dev/ttyS0 icrnl ixon ixoff opost isig icanon echo");   // go back to canonical mode

    ...

    exit(0);
}

Eu tentei read () função (com buffer de dados não assinados) para obter dados binários, mas não conseguiu receber os dados corretos. Eu também preliminarmente abro o / dev / ttyS0 novamente para obter o file_descriptor & use read () func.

Meu programa envia 3 bytes: 0xAA, 0x02, 0xFE. Mas no syslog eu sempre vejo que o dispositivo recebe símbolos incorretos: 0x98, 0xE6, 0x18.

Qual é o problema? Como obter dados binários corretos?

Um código inteiro que estou testando no momento.

#include "cli.h"
#include "glb_vars.h"

/******************************************
 *** Definitions
 ******************************************/
#define APPLICATION_NAME    "cli"
#define SERIALPORT_IS_CONSOLE

/******************************************
 *** Constants
 ******************************************/
const char dev_name[] = DEV_NAME;
const char lineminstr[] = "\t--------------------------------------------------------\n";

/******************************************
 *** Internal Function Declarations
 ******************************************/
CLI_RETVAL cliInit(void);
CLI_RETVAL cliClose(void);
void cliWorkLoop(Term_callback_t **term);

/******************************************
 *** External Function Declarations
 ******************************************/
extern void Vectors_init(Term_callback_t **vec);
extern char** Menu_completion(const char * text, int start, int end);


/****************************************************************************/
int file_descr, max_fd;
struct termios tty, orig_tty;
fd_set work_set;

/****************************************************************************/
/*!
 *  \brief  Init cli
 *
 *  \return  success or failure
 *  \retval  CLI_SUCCESS, CLI_FAILURE
 *
 *  \ingroup CLI
 */
/****************************************************************************/
CLI_RETVAL cliInit(void)
{
    long spd;

    signal(SIGINT, SIG_IGN);
    signal(SIGHUP, SIG_IGN);
    signal(SIGTERM, SIG_IGN);
    signal(SIGABRT, SIG_IGN);
    signal(SIGQUIT, SIG_IGN);
    signal(SIGILL, SIG_IGN);

//  system("stty -F /dev/ttyS0 -icrnl -ixon -ixoff -opost -isig -icanon -echo");    // enter in non-canonical mode
//  system("stty -a");
//  sleep(1);

#ifdef SERIALPORT_IS_CONSOLE
    file_descr = STDIN_FILENO;
    SYS_LOG_DEBUG("SERIALPORT IS CONSOLE");
#else
    SYS_LOG_DEBUG("SERIALPORT IS NOT CONSOLE");
    file_descr = open("/dev/ttyS0", O_RDWR | O_ASYNC | O_NDELAY);
    if (file_descr == -1) {
        // Could not open the port
        perror("unable to open /dev/ttyS0");
        exit(1);
    }
#endif

    if(tcgetattr(file_descr, &tty) < 0)
    {
        perror("unable to get tty attributes");
        exit(1);
    }
    // backup tty, make it raw and apply changes
    orig_tty = tty;

    spd = B115200;
    cfsetospeed(&tty, (speed_t)spd);
    cfsetispeed(&tty, (speed_t)spd);

    cfmakeraw(&tty);

    tty.c_cc[VMIN] = 1;
    tty.c_cc[VTIME] = 10;

    tty.c_cflag &= ~CSTOPB;
    tty.c_cflag &= ~CRTSCTS;    /* no HW flow control? */
    tty.c_cflag |= CLOCAL | CREAD;
    tcsetattr(file_descr, TCSANOW, &tty);

//  // update local mode flags
//  tty.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
////    // renew control mode flags
////    tty.c_cflag &= ~(CBAUD | CSIZE | CSTOPB | PARENB | PARODD);
////    tty.c_cflag |= (BAUD | DATABITS | STOPBITS | PARITYON | PARITY);
//  // select 'raw' output mode
//  tty.c_oflag &= ~OPOST;
//  // disable mapping for input mode
//  tty.c_iflag &= ~(INLCR | ICRNL);
//
//
//  if(tcsetattr(file_descr, TCSAFLUSH, &tty) < 0)
//  {
//      perror("unable to set tty attributes");
//      exit(1);
//  }
//
    // Setup fd_set
    FD_ZERO(&work_set);
    FD_SET(file_descr, &work_set);
    max_fd = file_descr + 1;

    /* Readline lib init */
    // Define application name for readline library
    rl_readline_name = APPLICATION_NAME;
    // Update Pointer to alternative function to create matches.
    rl_attempted_completion_function = Menu_completion;
    // Start readline with reading /etc/inputrc file
    using_history();
    stifle_history(CLI_MAX_HISTORY_SIZE);


    // Some other initialization code
    // ...
    // ...

    return CLI_SUCCESS;
}

/****************************************************************************/
/*!
 *  \brief  Close cli
 *
 *  \return  success or failure
 *  \retval  CLI_SUCCESS, CLI_FAILURE
 *
 *  \ingroup CLI
 */
/****************************************************************************/
CLI_RETVAL cliClose(void)
{

//  system("stty -F /dev/ttyS0 icrnl ixon ixoff opost isig icanon echo");   // enter in canonical mode

    tcsetattr(file_descr, TCSANOW, &orig_tty);
//  if(tcsetattr(file_descr, TCSAFLUSH, &orig_tty) < 0)
//  {
//      perror("unable to set orig_tty attributes");
//      exit(1);
//  }
    close(file_descr);

    return CLI_SUCCESS;
}


/****************************************************************************/
/*!
 *  \brief  Main cli processing loop
 *
 *  \no return
 *
 *  \ingroup CLI
 */
/****************************************************************************/
void cliWorkLoop(Term_callback_t **term)
{
    Term_callback_t *cur_term;
    int8 *commandString;
    uint8 ret = CLI_REFRESH, no_prompt;

    char prompt_str[20];

    while (1) {

        cur_term = *term;
        global_cmd_compl_pointer = cur_term->cmd_list;

        commandString = NULL;
        sprintf(prompt_str, "%s:~> ", dev_name);

        if(ret == CLI_REFRESH) {
            CLEAR_SCR();
            if(cur_term->out != NULL) {
                cur_term->out(term, commandString, &ret);
                no_prompt = ret;
            }
            CURSOR_DOWN();
        }

        int n;
        struct timeval timeout;
        uint8 tmpBuf[32];

        while (1)
        {
            // Setup Timeout
            timeout.tv_sec = 60;
            timeout.tv_usec = 0;
            // Wait for new connections
            n = select(max_fd, &work_set, NULL, NULL, &timeout);
            if (n < 0)
            {
                perror("select #2 failed");
                break;
            }
            if (n > 0)
            {
                /* У нас есть ввод */
                if (FD_ISSET(file_descr, &work_set))
                {
                    if (read(file_descr, tmpBuf, 10) < 0) {
                        perror("cannot read");
                        exit(1);
                    }
                    else
                    {
                        SYS_LOG_DEBUG("READ first 4 chars: 0x%X,0x%X,0x%X,0x%X", tmpBuf[0], tmpBuf[1], tmpBuf[2], tmpBuf[3]);
                    }
                }
                break;
            }
        }
//
//
//      n = read(file_descr, tmpBuf, 5);
//      if (n > 0) {
//          unsigned char   *p = tmpBuf;
//
//          while (n-- > 0)
//              printf(" 0x%x", *p++);
//          printf("\r\n");
//      } else {
//          printf("failed to read: %d\r\n", n);
//      }
//
//
        exit(0);
    }

    CLEAR_SCR();
    return;
}


/****************************************************************************/
/*!
 *  \brief Main cli function
 *
 *  \param[in]      argc - argument number.
 *  \param[in,out]  argv - argument values entered by user.
 *
 *  \return  success or failure
 *  \retval  EXIT_SUCCESS, EXIT_FAILURE
 *
 *
 *  \ingroup CLI
 */
/****************************************************************************/
int main(int argc, char *argv[])
{
    Term_callback_t *term;
    char logname[16];
    FILE *fp;


    /* Set mask for file operation */
    umask(0);

    system("stty erase ^H");
    openlog("cli", LOG_CONS, LOG_USER);

    /* Write cli start log */
    syslog(LOG_NOTICE, "Console startup. Software version: %s", VERSION);
    /* Find login name */
    strcpy(logname, "noname");
    if ((fp = popen( "whoami", "r" )) == NULL)
    {
        SYS_LOG_ERR("Can't open process for \"whoami\" command.");
    } else
    {
        fgets(logname, 16, fp);
        pclose(fp);
    }
    SYS_LOG_INFO("Console is entered by \"%s\".", logname); //getenv("USER")

    /* Console initialization */
    if (cliInit() != CLI_SUCCESS) {
        SYS_LOG_CRIT("CLI init failed");
        return EXIT_FAILURE;
    }

    Vectors_init(&term);

    /* Console work loop */
    cliWorkLoop(&term);
    cliClose();

    /* Exiting from cli */
    SYS_LOG_INFO("\"%s\" exited from console.", logname);

    return EXIT_SUCCESS;
}
 sawdust16 de set de 2012 00:30
Sua porta serial é configurada para entrada canônica, ou seja, linhas de texto ASCII. Ou mude paramodo cru ou transformar os dados binários em ambas as extremidades. Olhe para auuencode euudecode para transmissão de dados binários em um meio ASCII. É o modo tradicional de enviar dados binários em mensagens de e-mail somente texto e USENET. Outro método de envio de caracteres binários ou "especiais" é usar um caractere "escape" para prefixar o caractere "especial" e normalizar o byte para um caractere ASCII válido. BTW-parenb -parodd cs8 irá produzir quadros de caracteres de 11 bits. "Sem paridade" é típico ao usar caracteres de dados de 8 bits.
 Bakir17 de set de 2012 07:19
Obrigado serragem! Você está certo, a segunda variante é um método mais conveniente de usar. Mas eu tenho um software antigo que se comunica com dispositivos embarcados anteriores e não o suporta. É provável que tenhamos que mudá-lo para dar suporte a um novo. Mesmo assim, por que não consigo obter um caracter especial quando estou em modo raw (por favor, veja o código mencionado) usando uma função read ()?

questionAnswers(1)

QuestionSolution
system("stty erase ^H);
system("stty -F /dev/ttyS0 -icrnl -ixon -ixoff -opost -isig -icanon -echo");    // enter into non-canonical (raw) mode

Isso seria código insuficiente (e prática de codificação pobre por convenções POSIX) para colocar a porta serial em modo bruto ou não canônico.
O método mais fácil em C e Linux é usar a funçãocfmakeraw() que está disponível nas bibliotecas GNU libc e uClibc. Use opágina man para obter os detalhes sobre otermios membros da estrutura que são modificados porcfmakeraw().
Cuidado com issocfmakeraw() irá configurar a porta serial no modo raw para um comprimento de dados de 8 bits enão paridade, para um quadro de caracteres total de 10 bits (assumindo um bit de parada).

O método preferido é preservar uma cópia dotermios estrutura (para restauração na saída do programa) e somente modificando os bits de sinalização requeridos (ao invés de escrever os membros completos da estrutura).

REVISÃO

O código que funciona no meu SoC da ARM é:

#include <stdio.h>
#include <stdlib.h>
#include <sys/un.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/syslog.h>
#include <termios.h>

#define SERIALPORT_IS_CONSOLE

main()
{
    struct termios  tty;
    struct termios  savetty;
    speed_t     spd;
    unsigned int    sfd;
    unsigned char   buf[80];
    int     reqlen = 79;
    int     rc;
    int     rdlen;
    int     pau = 0;

#ifdef SERIALPORT_IS_CONSOLE
    sfd = STDIN_FILENO;
#else
    sfd = open("/dev/ttyS1", O_RDWR | O_NOCTTY);
#endif
    if (sfd < 0) {
        syslog(LOG_DEBUG, "failed to open: %d, %s", sfd, strerror(errno));
        exit (-1);
    }
    syslog(LOG_DEBUG, "opened sfd=%d for reading", sfd);

    rc = tcgetattr(sfd, &tty);
    if (rc < 0) {
        syslog(LOG_DEBUG, "failed to get attr: %d, %s", rc, strerror(errno));
        exit (-2);
    }
    savetty = tty;    /* preserve original settings for restoration */

    spd = B115200;
    cfsetospeed(&tty, (speed_t)spd);
    cfsetispeed(&tty, (speed_t)spd);

    cfmakeraw(&tty);

    tty.c_cc[VMIN] = 1;
    tty.c_cc[VTIME] = 10;

    tty.c_cflag &= ~CSTOPB;
    tty.c_cflag &= ~CRTSCTS;    /* no HW flow control? */
    tty.c_cflag |= CLOCAL | CREAD;
    rc = tcsetattr(sfd, TCSANOW, &tty);
    if (rc < 0) {
        syslog(LOG_DEBUG, "failed to set attr: %d, %s", rc, strerror(errno));
        exit (-3);
    }

    do {
        unsigned char   *p = buf;

        rdlen = read(sfd, buf, reqlen);
        if (rdlen > 0) {
            if (*p == '\r')
                pau = 1;
            syslog(LOG_DEBUG, "read: %d, 0x%x 0x%x 0x%x", \
                     rdlen, *p, *(p + 1), *(p + 2));
        } else {
            syslog(LOG_DEBUG, "failed to read: %d, %s", rdlen, strerror(errno));
        }
    } while (!pau);

    tcsetattr(sfd, TCSANOW, &savetty);
    close(sfd);
    exit (0);
}

O programa compilado é carregado e executado na placa alvo.

Do lado do host do link de comunicação serial, o arquivoseq.bin com o seguinte conteúdo é enviado:

$ od -t x1 seq.bin
0000000 aa 02 fe
0000003

Então "ABC" é digitado no host (que está executando ominicom programa emulador de terminal), seguido por um retorno de carro. O programa termina no destino e o syslog é então examinado:

# tail /var/log/messages                                                        
Sep xx xx:xx:42 atmel_soc user.info kernel: EXT3 FS on nvsram, internal journal 
Sep xx xx:xx:42 atmel_soc user.info kernel: EXT3-fs: mounted filesystem with or.
Sep xx xx:xx:42 atmel_soc user.info kernel: kjournald starting.  Commit intervas
Sep xx xx:xx:18 atmel_soc auth.info login[431]: root login on 'ttyS0'           
Sep xx xx:xx:04 atmel_soc user.debug syslog: opened sfd=0 for reading           
Sep xx xx:xx:14 atmel_soc user.debug syslog: read: 3, 0xaa 0x2 0xfe             
Sep xx xx:xx:50 atmel_soc user.debug syslog: read: 1, 0x41 0x2 0xfe              
Sep xx xx:xx:51 atmel_soc user.debug syslog: read: 1, 0x42 0x2 0xfe              
Sep xx xx:xx:51 atmel_soc user.debug syslog: read: 1, 0x43 0x2 0xfe              
Sep xx xx:xx:52 atmel_soc user.debug syslog: read: 1, 0xd 0x2 0xfe              
# 

Os dados binários foram recebidos intactos.

Note que como este é o modo raw e os caracteres digitados foram inseridos de forma relativamente lenta,read() retorna dados "parciais" e o programa do usuário seria responsável por armazenar / reunir os dados em "mensagens" completas. Se as mensagens forem de tamanho fixo,c_cc[VMIN]membropoderia ser definido para o tamanho da mensagem. Mas cuidado com os problemas e complicações do enquadramento de mensagens quando a sincronização de quadros é perdida!

 Bakir18 de set de 2012 06:31
Eu usei essa função e obtive o mesmo resultado :)
 Bakir18 de set de 2012 06:33
Mas vou tentar mais uma vez.
 sawdust21 de set de 2012 21:04
Algum código real provavelmente seria mais compreensível que sua sinopse textual. FYI "SERIALPORT_IS_CONSOLE" é um símbolo de compilação condicional confeccionado; você poderia simplesmente codificar osfd = STDIN_FILENOou instale#define SERIALPORT_IS_CONSOLE no começo, ou adicione-DSERIALPORT_IS_CONSOLE como um switch de linha de compilação gcc. Você fez algum teste de sanidade, como enviar algo diferente como "ABC" (por exemplo, 0x41 0x42 0x43) para os 3 bytes? O que é esse "syslog" que está relatando os "dados incorretos"?
 Bakir21 de set de 2012 10:22
Eu abro explicitamente o console para obter um arquivo des. Vou tentar isso.
 Bakir18 de set de 2012 07:24
Então, verifiquei seu código. Infelizmente eu tenho a mesma foto.
 Bakir21 de set de 2012 12:24
Então, tentei novamente. Mas não consegui sucesso (. Eu inseri declaração condicional e diz por alguma razão que SERIALPORT NÃO É CONSOLE. E, simples read () não funciona no meu código, é por isso que usei fd set com read () func in loop para obter Eu obtive o mesmo: 0x98, 0xE6, 0x18, muito obrigado !!!
 Bakir22 de set de 2012 11:30
Inseri a definição "SERIALPORT_IS_CONSOLE", mas isso não afetou o recebimento dos símbolos. E fiz testes de sanidade, por exemplo, enviei caracteres regulares e obtive os símbolos corretos no lado do recebimento. Eu uso um log de sistema através de / var / log / messages. Eu adicionei um código inteiro na minha pergunta. Por favor, olhe acima. Cumprimentos.
 sawdust24 de set de 2012 02:33
Minha resposta foi revisada para incluir um exemplo de trabalho completo para receber dados binários em uma porta serial. Existem (pelo menos) dois itens que você deve corrigir em seu código. (1) Como você está tendo dificuldades, você deve verificar os valores de retorno para todas as chamadas libc / system. (2) O caso degenerativo deler() Retornar 0 bytes (ou qualquer leitura curta para esse assunto) não é detectado e o log relatará valores de lixo. Boa sorte.
 Bakir18 de set de 2012 07:39
Eu acho que não completei totalmente a preparação do tty config. Anteriormente escrevi o programa RS232toETH e usei todas essas ferramentas e elas funcionaram. Mas nesse caso eu comentei o lançamento do console na inicialização do linux. Havia apenas meu aplicativo que usava / dev / ttyS0. Muito obrigado serragem! :) Você me deu uma ideia. Eu acho que vou tentar o método de solução alternativa (uuencode e uudecode / escape chars). Obrigado pelo seu tempo.
 Bakir26 de set de 2012 07:02
Funciona!!! Eu tenho vergonha de mim. A causa de tudo isso é minha desatenção! :( O programa que testa o meu com-app foi configurado para usar a taxa de 57600 bits quando eu precisava de 115200. Ambos os códigos estão corretos. Eu sinto muito muito por isso. Muito obrigado serragem! :)Jan 1 00:03:42 FM2 user.debug cli: read: 3, 0xaa 0x2 0xfe Jan 1 00:05:44 FM2 user.debug cli: read: 1, 0x41 0x2 0xfe Jan 1 00:05:45 FM2 user.debug cli: read: 1, 0x42 0x2 0xfe Jan 1 00:05:46 FM2 user.debug cli: read: 1, 0x43 0x2 0xfe Jan 1 00:05:50 FM2 user.debug cli: read: 1, 0xd 0x2 0xfe
 sawdust19 de set de 2012 01:29
Você está explicitamente abrindo novamente o console para obter um descritor de arquivo,sdf, ou você tentou simplesmente usarsfd = STDIN_FILENO no lugar doopen()? Por favor, revise as alterações feitas no exemplo de código acima.

yourAnswerToTheQuestion