如何在C程序中通过串行terminal读取二进制数据?
我阅读其次链接和其他来源,但没有find我的问题的答案。
二进制数据通过串行terminal
数据在通过串口传输时被损坏
我通过串口与我的embedded式设备通信。 默认情况下,embedded式Linux使用此端口作为terminal。 但我也想通过端口传输二进制数据(服务包)。 我的/ etc / inittab文件有一个“getty”调用: console :: respawn:/ sbin / getty 115200 ttyS0
我也有/ etc / passwd文件,其中“admin”用户login后启动我的“cli”应用程序: admin:8Mt / Jtxcyg8AY:1000:0:admin:/ tmp:/ tmp / cli
运行程序之前,我的默认ttyS0设置是:
~ # 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程序中,我做了以下几点:
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); }
我试图读取()函数(无符号字符缓冲区)来获取二进制数据,但未能收到正确的数据。 我也初步打开/ dev / ttyS0来获得file_descriptor&使用read()函数。
我的程序发送3个字节:0xAA,0x02,0xFE。 但在系统日志中,我总是看到该设备接收不正确的符号:0x98,0xE6,0x18。
有什么事? 如何获得正确的二进制数据?
我正在testing的整个代码。
#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; }
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位, 无奇偶校验,总共为10位字符(假设为一个停止位)。
首选方法是保留termios结构的副本(用于程序退出时的恢复),只修改所需的标志位(而不是写入完整的结构成员)。
修订
适用于我的ARM SoC的代码是:
#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); }
编译好的程序在目标板上加载并执行。
从串行通信链路的主机侧,发送具有以下内容的文件seq.bin
:
$ od -t x1 seq.bin 0000000 aa 02 fe 0000003
然后在主机(运行minicom
terminal仿真器程序)上键入“ABC”,然后回车。 程序终止在目标上,然后检查系统日志:
# 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 #
二进制数据已被完整接收。
请注意,由于这是原始模式并且input的字符input相对较慢,所以read()
返回“部分”数据,用户程序将负责将数据caching/组装成完整的“消息”。 如果消息的长度是固定的,则可以将c_cc[VMIN]
成员设置为消息长度。 但是,当帧同步丢失时,要小心消息帧问题和复杂性。