如何从 Linux 上的 picocom 等串口读取数据?

本文介绍了如何从 Linux 上的 picocom 等串口读取数据?的处理方法,对大家解决问题具有一定的参考价值

问题描述

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

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

我的程序的结果很接近,但有时会混淆.

这是我的代码:

#include <iostream>#include <stdio.h>#include <string.h>#include <fcntl.h>#include <errno.h>#include <termios.h>#include <unistd.h>诠释主要(){结构 termios tty;memset(&tty, 0, sizeof tty);int serial_port = open("/dev/ttyUSB0", O_RDWR);//检查错误if (serial_port < 0) {printf("打开错误 %i: %s
", errno, strerror(errno));}//读入现有设置,并处理任何错误if(tcgetattr(serial_port, &tty) != 0) {printf("来自 tcgetattr 的错误 %i: %s
", errno, strerror(errno));}tty.c_cflag &= ~PARENB;//清除奇偶校验位,禁用奇偶校验(最常见)tty.c_cflag &= ~CSTOPB;//清除停止字段,通信中只使用一个停止位(最常见)tty.c_cflag |= CS8;//每字节 8 位(最常见)tty.c_cflag &= ~CRTSCTS;//禁用 RTS/CTS 硬件流控(最常见)tty.c_cflag |= CREAD |本地;//开启 READ &忽略 ctrl 行(CLOCAL = 1)tty.c_lflag &= ~ICANON;tty.c_lflag &= ~ECHO;//禁用回声tty.c_lflag &= ~ECHOE;//禁用擦除tty.c_lflag &= ~ECHONL;//禁用换行回显tty.c_lflag &= ~ISIG;//禁用对 INTR、QUIT 和 SUSP 的解释tty.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL);//禁用对接收字节的任何特殊处理tty.c_oflag &= ~OPOST;//防止输出字节的特殊解释(例如换行符)tty.c_oflag &= ~ONLCR;//防止换行转换为回车/换行tty.c_cc[VTIME] = 10;tty.c_cc[VMIN] = 0;//设置输入/输出波特率为 9600cfsetispeed(&tty, B9600);cfsetospeed(&tty, B9600);//保存 tty 设置,同时检查错误if (tcsetattr(serial_port, TCSANOW, &tty) != 0) {printf("来自 tcsetattr 的错误 %i: %s
", errno, strerror(errno));}//为读取缓冲区分配内存,根据需要设置大小字符 read_buf [24];memset(&read_buf, '', sizeof(read_buf));而(1){int n = read(serial_port, &read_buf, sizeof(read_buf));std::cout <<read_buf ;}返回0;}

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

解决方案

picocom 如何正确显示数据?

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

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

根据您发布的输出,GPS 模块显然正在输出以换行和回车结尾的 ASCII 文本行.
无论(终端仿真器)程序如何读取此文本,即一次一个字节或每次一些随机字节数,只要每个接收到的字节以与接收到的顺序相同的顺序显示,显示就会出现有序,清晰可辨.


<块引用>

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

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

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

read() 系统调用仅返回一个字节数(或错误指示,即 -1),而不返回字符串.
你的程序对这个字节数什么都不做,只是显示缓冲区中的任何内容(和所有内容).
每当最新的 read() 没有返回足够的字节来覆盖缓冲区中已经存在的内容时,就会再次显示旧字节.

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

unsigned char read_buf[80];而(1){memset(read_buf, '', sizeof(read_buf));//清理缓冲区int n = read(serial_port, read_buf, sizeof(read_buf) - 1);std::cout <<read_buf ;}

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

未能测试来自 read() 的返回代码是否存在错误条件是您的代码的另一个问题.
所以下面的代码是对你的改进:

unsigned char read_buf[80];而(1){int n = read(serial_port, read_buf, sizeof(read_buf) - 1);如果(n <0){/* 处理 errno 条件 */返回-1;}read_buf[n] = '';std::cout <<read_buf ;}


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

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

有关阅读行,请参阅 串行通信规范模式非阻塞NL 检测在 C 中使用 linux 串口,无法获取完整数据,以及 Canonical Mode Linux Serial Port 用于一个简单而完整的 C 程序.


附录

<块引用>

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

我不知道怎么写才能更清楚.

您是否阅读过 termios ma​​n 页面?

<块引用>

规范模式下:

  • 输入是逐行提供的.输入线是当键入行分隔符之一时可用(NL、EOL、EOL2;或开头的EOF线).除了 EOF 的情况外,行分隔符包含在 read(2) 返回的缓冲区中.


<块引用>

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

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

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

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

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.

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.

This is my code:

#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
", errno, strerror(errno));
    }

        // Read in existing settings, and handle any error
    if(tcgetattr(serial_port, &tty) != 0) {
        printf("Error %i from tcgetattr: %s
", 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
", errno, strerror(errno));
    }

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

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

    return 0;
}

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

解决方案

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

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.


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

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

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, '', sizeof(read_buf));  // clean out buffer
    int n = read(serial_port, read_buf, sizeof(read_buf) - 1);
    std::cout << read_buf ;
}

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.

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] = '';
    std::cout << read_buf ;
}


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 Linux 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/processing suggested in another answer, utilize the capabilities already built into the operating system.

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.


ADDENDUM

I'm having troubles to understand "Instead you can and should use canonical I/O so that read() will return a complete line in your buffer".

I don't know how to write that to be more clear.

Have you read the termios man page?

In canonical mode:

  • 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).


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"?

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 等串口读取数据?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,WP2

admin_action_{$_REQUEST[‘action’]}

do_action( "admin_action_{$_REQUEST[‘action’]}" )动作钩子::在发送“Action”请求变量时激发。Action Hook: Fires when an ‘action’ request variable is sent.目录锚点:#说明#源码说明(Description)钩子名称的动态部分$_REQUEST['action']引用从GET或POST请求派生的操作。源码(Source)更新版本源码位置使用被使用2.6.0 wp-admin/admin.php:...

日期:2020-09-02 17:44:16 浏览:1172

admin_footer-{$GLOBALS[‘hook_suffix’]}

do_action( "admin_footer-{$GLOBALS[‘hook_suffix’]}", string $hook_suffix )操作挂钩:在默认页脚脚本之后打印脚本或数据。Action Hook: Print scripts or data after the default footer scripts.目录锚点:#说明#参数#源码说明(Description)钩子名的动态部分,$GLOBALS['hook_suffix']引用当前页的全局钩子后缀。参数(Parameters)参数类...

日期:2020-09-02 17:44:20 浏览:1071

customize_save_{$this->id_data[‘base’]}

do_action( "customize_save_{$this-&gt;id_data[‘base’]}", WP_Customize_Setting $this )动作钩子::在调用WP_Customize_Setting::save()方法时激发。Action Hook: Fires when the WP_Customize_Setting::save() method is called.目录锚点:#说明#参数#源码说明(Description)钩子名称的动态部分,$this->id_data...

日期:2020-08-15 15:47:24 浏览:808

customize_value_{$this->id_data[‘base’]}

apply_filters( "customize_value_{$this-&gt;id_data[‘base’]}", mixed $default )过滤器::过滤未作为主题模式或选项处理的自定义设置值。Filter Hook: Filter a Customize setting value not handled as a theme_mod or option.目录锚点:#说明#参数#源码说明(Description)钩子名称的动态部分,$this->id_date['base'],指的是设置...

日期:2020-08-15 15:47:24 浏览:900

get_comment_author_url

过滤钩子:过滤评论作者的URL。Filter Hook: Filters the comment author’s URL.目录锚点:#源码源码(Source)更新版本源码位置使用被使用 wp-includes/comment-template.php:32610...

日期:2020-08-10 23:06:14 浏览:930

network_admin_edit_{$_GET[‘action’]}

do_action( "network_admin_edit_{$_GET[‘action’]}" )操作挂钩:启动请求的处理程序操作。Action Hook: Fires the requested handler action.目录锚点:#说明#源码说明(Description)钩子名称的动态部分$u GET['action']引用请求的操作的名称。源码(Source)更新版本源码位置使用被使用3.1.0 wp-admin/network/edit.php:3600...

日期:2020-08-02 09:56:09 浏览:877

network_sites_updated_message_{$_GET[‘updated’]}

apply_filters( "network_sites_updated_message_{$_GET[‘updated’]}", string $msg )筛选器挂钩:在网络管理中筛选特定的非默认站点更新消息。Filter Hook: Filters a specific, non-default site-updated message in the Network admin.目录锚点:#说明#参数#源码说明(Description)钩子名称的动态部分$_GET['updated']引用了非默认的...

日期:2020-08-02 09:56:03 浏览:865

pre_wp_is_site_initialized

过滤器::过滤在访问数据库之前是否初始化站点的检查。Filter Hook: Filters the check for whether a site is initialized before the database is accessed.目录锚点:#源码源码(Source)更新版本源码位置使用被使用 wp-includes/ms-site.php:93910...

日期:2020-07-29 10:15:38 浏览:834

WordPress 的SEO 教学:如何在网站中加入关键字(Meta Keywords)与Meta 描述(Meta Description)?

你想在WordPress 中添加关键字和meta 描述吗?关键字和meta 描述使你能够提高网站的SEO。在本文中,我们将向你展示如何在WordPress 中正确添加关键字和meta 描述。为什么要在WordPress 中添加关键字和Meta 描述?关键字和说明让搜寻引擎更了解您的帖子和页面的内容。关键词是人们寻找您发布的内容时,可能会搜索的重要词语或片语。而Meta Description则是对你的页面和文章的简要描述。如果你想要了解更多关于中继标签的资讯,可以参考Google的说明。Meta 关键字和描...

日期:2020-10-03 21:18:25 浏览:1733

谷歌的SEO是什么

SEO (Search Engine Optimization)中文是搜寻引擎最佳化,意思近于「关键字自然排序」、「网站排名优化」。简言之,SEO是以搜索引擎(如Google、Bing)为曝光媒体的行销手法。例如搜寻「wordpress教学」,会看到本站的「WordPress教学:12个课程…」排行Google第一:关键字:wordpress教学、wordpress课程…若搜寻「网站架设」,则会看到另一个网页排名第1:关键字:网站架设、架站…以上两个网页,每月从搜寻引擎导入自然流量,达2万4千:每月「有机搜...

日期:2020-10-30 17:23:57 浏览:1309