2012-09-15 57 views
12

Tôi đã đọc các liên kết được theo dõi và các nguồn khác, nhưng không tìm thấy câu trả lời cho câu hỏi của tôi.Làm thế nào để đọc dữ liệu nhị phân trên thiết bị đầu cuối nối tiếp trong chương trình C?

Binary data over serial terminal

Data gets corrupted during transmission over the serial port

tôi giao tiếp với thiết bị nhúng của mình thông qua một cổng nối tiếp. Theo mặc định, Linux nhúng sử dụng cổng này làm thiết bị đầu cuối. Nhưng tôi cũng muốn truyền dữ liệu nhị phân (các gói dịch vụ) thông qua cổng. File/etc/inittab của tôi có một "getty" gọi: console :: respawn:/sbin/Getty 115200 ttyS0

Tôi cũng có file/etc/passwd với chuỗi nơi "admin" user khởi động của tôi "cli "ứng dụng sau khi đăng nhập: admin: 8Mt/Jtxcyg8AY: 1000: 0: admin:/tmp:/tmp/cli

thiết lập ttyS0 mặc định của tôi trước khi chạy chương trình là:

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

Vì vậy, , trong chương trình cli của tôi, tôi làm như sau:

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

Tôi đã thử hàm read() (với bộ đệm char chưa ký) để nhận dữ liệu nhị phân nhưng không nhận được dữ liệu chính xác. Tôi cũng mở sơ bộ/dev/ttyS0 lần nữa để lấy file_descriptor & sử dụng read() func.

Chương trình của tôi gửi 3 byte: 0xAA, 0x02, 0xFE. Nhưng trong syslog tôi luôn thấy rằng thiết bị nhận được các ký hiệu không đúng: 0x98, 0xE6, 0x18.

Vấn đề là gì? Làm thế nào để có được dữ liệu nhị phân chính xác?

Toàn bộ mã mà tôi đang thử nghiệm tại thời điểm này.

#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; 
} 
+0

Cổng nối tiếp của bạn được thiết lập cho đầu vào chuẩn, tức là dòng văn bản ASCII. Hoặc chuyển sang * chế độ thô * hoặc chuyển đổi dữ liệu nhị phân ở cả hai đầu. Xem 'uuencode' và 'uudecode' để truyền dữ liệu nhị phân qua môi trường ASCII. Đó là cách truyền thống để gửi dữ liệu nhị phân trong các e-mail chỉ bằng văn bản và USENET. Một phương pháp khác để gửi các ký tự nhị phân hoặc "đặc biệt" là sử dụng ký tự "thoát" vào tiền tố "đặc biệt" và chuẩn hóa byte thành một byte ASCII hợp lệ. BTW '-parenb -parodd cs8' sẽ tạo ra các khung ký tự 11 bit. "Không chẵn lẻ" là điển hình khi sử dụng các ký tự dữ liệu 8 bit. – sawdust

+0

Cảm ơn bạn đã thấy mùn cưa! Bạn nói đúng, biến thể thứ hai là một phương pháp thuận tiện hơn để sử dụng. Nhưng tôi có một phần mềm cũ giao tiếp với các thiết bị nhúng trước đó và không hỗ trợ nó. Có khả năng chúng ta sẽ phải thay đổi nó để hỗ trợ một cái mới. Mặc dù vậy, tại sao tôi không thể có được một char đặc biệt khi đi vào một chế độ thô (vui lòng xem mã đã đề cập) bằng cách sử dụng một hàm read() func? – Bakir

Trả lời

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

Đây sẽ là không đủ mã (và thực hành mã hóa kém mỗi ước POSIX) để đặt cổng nối tiếp vào chế độ thô hoặc không kinh điển.
Phương pháp dễ nhất trong C và Linux là sử dụng hàm cfmakeraw() có sẵn trong cả thư viện GNU libc và uClibc. Sử dụng trang người dùng để nhận chi tiết về các thành viên cấu trúc terma được sửa đổi bởi cfmakeraw().
Hãy coi chừng rằng cfmakeraw() sẽ thiết lập cổng nối tiếp ở chế độ thô cho độ dài dữ liệu 8 bit và không chẵn lẻ, cho tổng số ký tự là 10 bit (giả sử một bit dừng).

Phương pháp ưu tiên là giữ bản sao của cấu trúc termo (để khôi phục khi thoát chương trình) và chỉ sửa đổi các bit cờ bắt buộc (thay vì viết thành viên cấu trúc hoàn chỉnh).

RÀ SOÁT

Mã hoạt động trên SoC ARM của tôi là:

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

Chương trình biên soạn được nạp & thực hiện trên bảng mục tiêu.

Từ phía chủ nhà của liên kết comm nối tiếp, các tập tin seq.bin với những nội dung sau đây được gửi:

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

Sau đó, "ABC" được đánh máy trên máy chủ (mà đang chạy chương trình giả lập minicom terminal) , tiếp theo là một trở về vận chuyển. chương trình Các chấm dứt vào mục tiêu, và sau đó là syslog được kiểm tra:

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

Các dữ liệu nhị phân đã được nhận được nguyên vẹn. Lưu ý rằng vì đây là chế độ thô và các ký tự đã nhập được nhập tương đối chậm, dữ liệu read() trả về dữ liệu "một phần" và chương trình người dùng sẽ chịu trách nhiệm đệm/lắp ráp dữ liệu thành "thông điệp" hoàn chỉnh. Nếu các tin nhắn có độ dài cố định, thì c_cc[VMIN] thành viên có thể được đặt ở độ dài tin nhắn. Nhưng hãy cẩn thận với các vấn đề về khung tin nhắn và các biến chứng khi đồng bộ khung bị mất!

+0

Tôi đã sử dụng chức năng này và nhận được kết quả tương tự :) – Bakir

+0

Nhưng tôi sẽ thử lại một lần nữa. – Bakir

+0

Vì vậy, tôi đã kiểm tra mã của bạn. Thật không may tôi có cùng một hình ảnh. – Bakir

Các vấn đề liên quan