如何从Linux上的picocom等串行端口读取数据? [英] How to read from serial port like picocom on Linux?

查看:270
本文介绍了如何从Linux上的picocom等串行端口读取数据?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个gps模块,每隔1秒钟将数据(NMEA语句)发送到串行端口.我一直在尝试从C ++程序中读取它.

I have a gps module that sends data (NMEA sentence) every 1 seconds to the serial port. I've been trying to read it from a c++ program.

使用picocom读取串行端口时,数据以干净的方式显示,每行都有NMEA语句).

When reading the serial port with picocom, data is displayed in a clean way, each line has a NMEA sentence).

我的程序的结果很接近,但有时混合在一起.

The result of my program is close but lines are sometimes mixed.

这是我的代码:

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <fcntl.h> 
#include <errno.h> 
#include <termios.h> 
#include <unistd.h> 

int main(){

    struct termios tty;
    memset(&tty, 0, sizeof tty);

    int serial_port = open("/dev/ttyUSB0", O_RDWR);

    // Check for errors
    if (serial_port < 0) {
        printf("Error %i from open: %s\n", errno, strerror(errno));
    }

        // Read in existing settings, and handle any error
    if(tcgetattr(serial_port, &tty) != 0) {
        printf("Error %i from tcgetattr: %s\n", errno, strerror(errno));
    }

    tty.c_cflag &= ~PARENB; // Clear parity bit, disabling parity (most common)
    tty.c_cflag &= ~CSTOPB; // Clear stop field, only one stop bit used in communication (most common)
    tty.c_cflag |= CS8; // 8 bits per byte (most common)
    tty.c_cflag &= ~CRTSCTS; // Disable RTS/CTS hardware flow control (most common)
    tty.c_cflag |= CREAD | CLOCAL; // Turn on READ & ignore ctrl lines (CLOCAL = 1)
    tty.c_lflag &= ~ICANON;
    tty.c_lflag &= ~ECHO; // Disable echo
    tty.c_lflag &= ~ECHOE; // Disable erasure
    tty.c_lflag &= ~ECHONL; // Disable new-line echo
    tty.c_lflag &= ~ISIG; // Disable interpretation of INTR, QUIT and SUSP
    tty.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL); // Disable any special handling of received bytes
    tty.c_oflag &= ~OPOST; // Prevent special interpretation of output bytes (e.g. newline chars)
    tty.c_oflag &= ~ONLCR; // Prevent conversion of newline to carriage return/line feed
    tty.c_cc[VTIME] = 10;   
    tty.c_cc[VMIN] = 0;
    // Set in/out baud rate to be 9600
    cfsetispeed(&tty, B9600);
    cfsetospeed(&tty, B9600);

    // Save tty settings, also checking for error
    if (tcsetattr(serial_port, TCSANOW, &tty) != 0) {
        printf("Error %i from tcsetattr: %s\n", errno, strerror(errno));
    }

    // Allocate memory for read buffer, set size according to your needs
    char read_buf [24];
    memset(&read_buf, '\0', sizeof(read_buf));

    while(1){
        int n = read(serial_port, &read_buf, sizeof(read_buf));
        std::cout << read_buf ;
    }

    return 0;
}

picocom如何设法正确显示数据?是由于我的缓冲区大小还是VTIMEVMIN标志?

How does picocom manage to display data correctly? Is is due to my buffer size or maybe VTIME and VMIN flags ?

推荐答案

picocom如何设法正确显示数据?

How does picocom manage to display data correctly?

正确性"所显示的输出的仅仅是"人类倾向于感知或归因于秩序". (和/或模式)自然发生的事件.

The "correctness" of the displayed output is merely the human tendency to perceive or attribute "order" (and/or a pattern) to naturally occurring events.

Picocom 只是一个"最小的哑终端仿真程序"就像其他终端仿真程序一样,仅显示接收到的内容.
您可以调整行终止行为,例如,在收到换行符时追加回车符(以便Unix/Linux文本文件正确显示).
但是,否则,您看到的就是收到的内容. picocom 没有应用任何处理或格式.

Picocom is just a "minimal dumb-terminal emulation program" that, like other terminal emulation programs, simply displays what is received.
You can tweak the line-termination behavior, for example append a carriage return when a line feed is received (so that Unix/Linux text files display properly).
But otherwise, what you see displayed is what was received. There is no processing or formatting applied by picocom.

基于您发布的输出,GPS模块显然正在输出以换行符和回车符结尾的ASCII文本行.
无论(终端仿真器)程序如何读取此文本,即一次读取一个字节,还是每次读取某个随机字节数,只要每个接收到的字节以与接收到的相同的顺序显示,则显示将有序显示,清晰易读.

Based on the outputs you have posted, the GPS module clearly is outputting lines of ASCII text terminated with line feed and carriage return.
Regardless of how this text is read by a (terminal emulator) program, i.e. a byte at a time or some random number of bytes each time, so long as each received byte is displayed in the same order as received, the display will appear orderly, legible and correct.

是由于我的缓冲区大小还是VTIME和VMIN标志?

Is is due to my buffer size or maybe VTIME and VMIN flags ?

VTIME和VMIN值不是最佳值,但是真正的问题是您的程序存在一个错误,该错误会导致某些接收到的数据显示不止一次.

The VTIME and VMIN values are not optimal, but the real issue is that your program has a bug that causes some of the received data to be displayed more than once.

while(1){
    int n = read(serial_port, &read_buf, sizeof(read_buf));
    std::cout << read_buf ;
}

read()系统调用仅返回一个字节的数字(或错误指示,即-1),并且不返回字符串.
您的程序使用该字节数不执行任何操作,仅显示该缓冲区中的任何内容(以及所有内容).
每当最新的 read()返回的字节数不足以覆盖缓冲区中已有的内容时,旧的字节将再次显示.

The read() syscall simply returns a number a bytes (or an error indication, i.e. -1), and does not return a string.
Your program does nothing with that number of bytes, and simply displays whatever (and everything) that is in that buffer.
Whenever the latest read() does not return sufficient bytes to overwrite what is already in the buffer, then old bytes will be displayed again.

您可以通过将原始程序的输出与以下调整进行比较来确认此错误:

You can confirm this bug by comparing output from your original program with the following tweak:

unsigned char read_buf[80];

while (1) {
    memset(read_buf, '\0', sizeof(read_buf));  // clean out buffer
    int n = read(serial_port, read_buf, sizeof(read_buf) - 1);
    std::cout << read_buf ;
}

请注意,传递给 read()的缓冲区大小必须比实际缓冲区大小小1,以便为字符串终止符保留至少一个字节的位置.

Note that the buffer size passed to the read() needs to be one less that the actual buffer size in order to preserve at least one byte location for a string terminator.

您的代码还有另一个问题.无法测试 read()的返回代码是否存在错误情况.
因此,以下代码是对您的改进:

Failure to test the return code from read() for an error condition is another problem with your code.
So the following code is an improvement over yours:

unsigned char read_buf[80];

while (1) {
    int n = read(serial_port, read_buf, sizeof(read_buf) - 1);
    if (n < 0) {
        /* handle errno condition */
        return -1;
    }
    read_buf[n] = '\0';
    std::cout << read_buf ;
}


您不确定是要模拟 picocom ,还是程序的另一个版本在从GPS模块读取数据时出现问题,因此您决定发布此XY问题. br/> 如果您打算阅读和处理程序中文本的 lines 行,那么您就不想模仿 picocom 并使用非规范的阅读.
相反,您可以并且应该使用规范的I/O,以便 read()将在缓冲区中返回完整的行(假设缓冲区足够大).


You are not clear as to whether you are just trying to emulate picocom, or another version of your program was having issues reading data from your GPS module and you decided to post this XY problem.
If you intend to read and process the lines of text in your program, then you do not want to emulate picocom and use noncanonical reads.
Instead you can and should use canonical I/O so that read() will return a complete line in your buffer (assuming that the buffer is large enough).

您的程序不是从串行端口读取,而是从串行终端读取.当接收到的数据是行终止文本时,当(代替)终端设备(和行规)可以为您解析接收到的数据并检测行终止字符时,没有理由读取原始字节.
不用执行其他答案中建议的所有额外编码,而是利用操作系统中已内置的功能.

Your program is not reading from a serial port, but from a serial terminal. When the received data is line-terminated text, there is no reason to read raw bytes when (instead) the terminal device (and line discipline) can parse the received data for you and detect the line termination characters.
Instead of doing all the extra coding suggested in another answer, utilize the capabilities already built into the operating system.

有关阅读内容,请参见串行通信规范模式无阻塞NL检测规范模式的Linux串行端口,用于简单而完整的C程序.

For reading lines see Serial Communication Canonical Mode Non-Blocking NL Detection and Working with linux serial port in C, Not able to get full data, as well as Canonical Mode Linux Serial Port for a simple and complete C program.

添加

我很难理解"相反,您可以并且应该使用规范的I/O,以便read()将在缓冲区中返回完整的行".

我不知道该怎么写.

您阅读过termios man 页面吗?

Have you read the termios man page?

规范模式下:

  • 逐行提供输入.输入行是 当键入行定界符之一(NL,EOL,EOL2;或 开始时的EOF 行).除EOF以外,行分隔符包括在 read (2)返回的缓冲区中.
  • Input is made available line by line. An input line is available when one of the line delimiters is typed (NL, EOL, EOL2; or EOF at the start of line). Except in the case of EOF, the line delimiter is included in the buffer returned by read(2).


我应该期望对read()的每次调用都将返回带有$ ...的整行吗?还是应该实现一些逻辑以读取并以一整行ASCII文本填充缓冲区?

Should i expect that each call to read() will return a full line with $... or should i implement some logic to read and fill the buffer with a full line of ASCII text?

您是否想知道我的完整" 含义与您使用的完整" 有区别吗?

Are you wondering if there is a difference between my meaning of "complete" versus your use of "full"?

您是否已在我已经写过"的地方阅读了注释?",如果您按照我的建议编写程序,那么[c]> $应该是缓冲区中的第一个字符" ?
是的, 您应该期望",每次对read()的调用都会返回带有$ ...的整行..

Did you read the comment where I already wrote "If you write your program as I suggest, [then] that $ should be the first char in the buffer"?
So yes, you should expect "that each call to read() will return a full line with $..." .

您需要研究我已经写过的内容以及所提供的链接.

You need to study what I already wrote as well as the links provided.

这篇关于如何从Linux上的picocom等串行端口读取数据?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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