如何在 C 程序中通过串行终端读取二进制数据? [英] How to read a binary data over serial terminal in C program?

查看:29
本文介绍了如何在 C 程序中通过串行终端读取二进制数据?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我阅读了关注的链接和其他来源,但没有找到我的问题的答案.

I read followed links and other sources, but didn't find answer for my question.

串行终端上的二进制数据

数据在通过串行端口传输时损坏

我通过串行端口与我的嵌入式设备通信.默认情况下,嵌入式 Linux 使用此端口作为终端.但我也想通过端口传输二进制数据(服务包).我的/etc/inittab 文件有一个getty"调用:console::respawn:/sbin/getty 115200 ttyS0

I communicate with my embedded device through a serial port. By default, embedded Linux uses this port as a terminal. But I want to transfer also binary data (service packets) through the port. My /etc/inittab file has a "getty" call: console::respawn:/sbin/getty 115200 ttyS0

我也有/etc/passwd 文件,其中admin"用户在登录后启动我的cli"应用程序:admin:8Mt/Jtxcyg8AY:1000:0:admin:/tmp:/tmp/cli

I also have /etc/passwd file with string where "admin" user launch my "cli" application after log in: admin:8Mt/Jtxcyg8AY:1000:0:admin:/tmp:/tmp/cli

我在运行程序之前的默认ttyS0设置是:

My default ttyS0 settings before running the program is:

~ # 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
~ #

因此,在我的 cli 程序中,我执行以下操作:

So, in my cli program I do the following:

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

我已尝试使用 read() 函数(使用 unsigned char 缓冲区)来获取二进制数据,但未能接收到正确的数据.我也初步再次打开/dev/ttyS0获取file_descriptor &使用 read() 函数.

I have attempted read() function (with unsigned char buffer) to get binary data, but failed to receive correct data. I also preliminarily open /dev/ttyS0 again to get file_descriptor & use read() func.

我的程序发送 3 个字节:0xAA、0x02、0xFE.但在 syslog 中,我总是看到设备收到不正确的符号:0x98、0xE6、0x18.

My program sends 3 bytes: 0xAA, 0x02, 0xFE. But in syslog I always see that device receives incorrect symbols: 0x98, 0xE6, 0x18.

这是怎么回事?如何获取正确的二进制数据?

What is the matter? How to get correct binary data?

我目前正在测试的完整代码.

#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[] = "	--------------------------------------------------------
";

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

/****************************************************************************/
/*!
 *  rief  Init cli
 *
 *  
eturn  success or failure
 *  
etval  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;
}

/****************************************************************************/
/*!
 *  rief  Close cli
 *
 *  
eturn  success or failure
 *  
etval  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;
}


/****************************************************************************/
/*!
 *  rief  Main cli processing loop
 *
 *  
o 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("
");
//      } else {
//          printf("failed to read: %d
", n);
//      }
//
//
        exit(0);
    }

    CLEAR_SCR();
    return;
}


/****************************************************************************/
/*!
 *  rief Main cli function
 *
 *  param[in]      argc - argument number.
 *  param[in,out]  argv - argument values entered by user.
 *
 *  
eturn  success or failure
 *  
etval  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;
}

推荐答案

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

这将是不足以将串行端口置于原始或非规范模式的代码(以及根据 POSIX 约定的不良编码实践).
在 C 和 Linux 中最简单的方法是使用函数 cfmakeraw(),它在 GNU libc 和 uClibc 库中都可用.使用手册页获取cfmakeraw()修改的termios结构成员的详细信息.
请注意,cfmakeraw() 会将串行端口设置为原始模式,数据长度为 8 位,no 奇偶校验,总字符帧为 10 位(假设停止位).

This would be insufficient code (and poor coding practice per POSIX conventions) to put the serial port into raw or non-canonical mode.
The easiest method in C and Linux is to use the function cfmakeraw() which is available in both the GNU libc and uClibc libraries. Use the man page to obtain the details on the termios structure members that are modified by cfmakeraw().
Beware that cfmakeraw() will setup the serial port in raw mode for a data length of 8 bits and no parity, for a total character frame of 10 bits (assuming one stop bit).

首选方法是保留 termios 结构的副本(用于在程序退出时恢复)并仅修改所需的标志位(而不是编写完整的结构成员).

The preferred method is preserving a copy of the termios stucture (for restoration on program exit) and only modifying the required flag bits (rather than writing the complete structure members).

适用于我的 ARM SoC 的代码是:

Code that works on my ARM SoC is:

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

编译好的程序被加载 &在目标板上执行.

The compiled program is loaded & executed on the target board.

从串口通信链接的主机端,发送文件seq.bin,内容如下:

From the host side of the serial comm link, the file seq.bin with the following contents is sent:

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

然后是ABC"在主机(运行 minicom 终端仿真程序)上键入,然后是回车.程序在目标上终止,然后检查系统日志:

Then "ABC" is typed on the host (which is running the minicom terminal emulator program), followed by a carriage return. The program terminates on the target, and the syslog is then examined:

# 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              
# 

二进制数据已完整接收.

The binary data has been received intact.

请注意,由于这是原始模式并且输入的字符输入速度相对较慢,因此 read() 返回部分".数据和用户程序将负责将数据缓冲/组装成完整的消息".如果消息是固定长度的,则 c_cc[VMIN]member 可以 设置为消息长度.但请注意帧同步丢失时的消息帧问题和并发症!

Note that since this is raw mode and the typed chars were entered relatively slowly, the read() returns "partial" data and the user program would be responsible for buffering/assembling the data into complete "messages". If the messages are of fixed length, then c_cc[VMIN]member could be set to the message length. But beware of message framing issues and complications when frame sync is lost!

这篇关于如何在 C 程序中通过串行终端读取二进制数据?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆