【Ubantu读取串口数据】_ubuntu 读取串口232数据-程序员宅基地

技术标签: 经验分享  学习  c++  ubuntu  

最近在使用ubantu读取串口数据,遇到了很多问题,好在最后也一一解决,实现目标,所以想写一下整个流程,进行一个记录分享吧。我用的是ubantu20.04,部署的ROS,希望从ROS中读取到串口数据,并进行发布订阅。

目录

前期准备

安装CuteCom【可选】

下载

添加权限

下载安装ROS自带的Serial库

编写代码

创建功能包

自定义msg消息

创建文件

修改package.xml

修改Cmakelists.txt 

读取串口数据并发布

首先,添加需要的头文件

其次,初始化ROS

然后,打开串口

 读取串口信息,并定时发送

 最后,关闭串口

发布方的完整代码如下

订阅串口数据

修改Cmakelists.txt文件 

最终结果


前期准备

安装CuteCom【可选】

下载

Ubantu系统也有很多串口助手,使用起来也很方便,和Windows差不多,我下载的是CuteCom,可以自动读取串口号,基本不用设置,就可以很轻松的读取到串口数据。

sudo apt-get install cutecom

 直接输入代码等他安装就OK了。

下载完之后直接从桌面图标进入即可,也可以用命令行

sudo cutecom

和win一样也是需要找到相对应的串口端口,我们在实际使用的时候,大部分都用了USB转串口。也就是ttyUSB*。(一般直接插上去的第一个都是ttyUSB0)

添加权限

点击Open,可能会出现“Could not open /dev/ttyUSB0”,这个是没有权限的意思,给他加个权限

sudo chmod 666 /dev/ttyUSB0

就可以读取了。但是实测可能有点慢,需要多等一会。

但是,这样每次USB插拔之后都需要添加权限,可以直接将当前用户添加进组

 查看串口信息

ls -l /dev/ttyUSB0

查询当前用户Who am i

whoami

 然后将当前用户添加进组

sudo usermod -aG dialout USER

完成之后重启电脑,后面就不需要每次都添加权限了。

下载安装ROS自带的Serial库

这部分比较简单,直接安装即可。我用的是noetic,如果是其他版本记得更改中间的版本名称。

sudo apt install ros-noetic-serial

 查询方法,打开终端输入

roscore

然后再打开一个终端,在另一个终端中输入以下代码即可。

rosversion -d

编写代码

创建功能包

切换到工作空间目录,然后创建功能包

catkin_create_pkg serial_pkg_sc roscpp rospy serial std_msgs

 这就在当下目录创建了一个支持C++(roscpp)、Python(rospy)、串口(serial)、标准信息(std_msgs)的功能包了!

自定义msg消息

我的数据是一个常见字符串,#31.754@这样,#是起始字符,@作为终止字符,中间为有效数据。我也尝试了可以用标准的msg,那个会简单一点。但是本着学习的理念,也了解了一下怎么自定义消息。

创建文件

首先在功能包目录下新建文件夹msg

然后新建一个sc_sensor.msg的.msg文件,注意一定要是.msg类型的文件,然后往里面添加需要的变量

Header header
float64 sc_double
string sc_str

 第一行的Header header表示这是一个标准信息,可以用现成功能。

float64是double类型数据,float32是float类型的,string是字符串等等,这个和C++不太一样,具体可以看官方介绍文档

msg - ROS Wiki

写完之后,记得保存(开个玩笑),然后就是

修改package.xml

 在相应位置添加以下代码,(相应位置在哪?相信我,你能找到它的。)

<build_depend>message_generation</build_depend> 
<exec_depend>message_runtime</exec_depend>

修改Cmakelists.txt 

...
find_package(catkin REQUIRED COMPONENTS 
    roscpp
    rospy
    std_msgs
    message_generation
)
...
add_message_files(
    DIRECTORY msg 
    FILES 
    sc_sensor.msg 
) 
...
generate_messages( 
    DEPENDENCIES
    std_msgs
)
...
catkin_package( 
    CATKIN_DEPENDS 
    roscpp 
    rospy 
    std_msgs 
    message_runtime 
)

这四部分建议先找到,然后解除注释,再进行修改,因为顺序不对的话可能会出问题。

  • find_package主要是多了message_generation
  • add_message_files里 DIRECTORY msg 是之前定义放消息的文件夹,sc_sensor.msg 是消息文件的名称,不要搞错了。
  • generate_messages解除注释就好。
  • catkin_package多了message_runtime 

这样我们就自定义了一个新的消息类型,修改完可以切换回工作空间目录,catkin_make一下试试,记得要刷新。

读取串口数据并发布

ROS中src文件夹一般存放cpp文件,可以在src文件夹下创建一个sc_talker.cpp,这个程序主要实现串口数据的读取并发布,同时将字符串切割保存为浮点数。由于单片机与电脑的读取写入速率不同,所以还需要进行定时发送,否则就会出现一次读取没有数,一次读取一堆数。

具体功能一个一个实现

首先,添加需要的头文件

#include <string>
#include <sstream>
#include <iostream>
#include "ros/ros.h"
#include "std_msgs/String.h"
#include "serial/serial.h"
#include "serial_pkg_sc/sc_sensor.h" //    新建功能包名称/消息文件名称

serial::Serial ser;    //定义对象
using namespace std;    //使用std命名空间     

其次,初始化ROS

    ros::init(argc, argv, "sc_serial_talker"); //定义节点sc_serial_talker
	ros::NodeHandle nh;

	ros::Rate loop_rate(10); //刷新速率,即串口数据发送速率,单片机数据的采样率。
	ros::Publisher sc_pub = nh.advertise<serial_pkg_sc::sc_sensor>("sc_sensor", 1);                    //发布sc_sensor的话题,每次只处理最新的1条消息
	    

然后,打开串口

// 打开串口
	try
	{
		ser.setPort("/dev/ttyUSB0");  // 这个端口号就是之前用cutecom看到的端口名称
		ser.setBaudrate(115200);
		serial::Timeout to = serial::Timeout::simpleTimeout(1000);
		ser.setTimeout(to);
		ser.open();
	}
	catch(serial::IOException &e)
	{
		ROS_INFO_STREAM("Failed to open port ");
		return -1;
	}
	ROS_INFO_STREAM("Succeed to open port" );    //ROS_INFO_STREAM相当于cout

	serial_pkg_sc::sc_sensor sc_data;    //定义一个对象
	string sc_sensors;    //储存数据的字符串

	    

 读取串口信息,并定时发送

while(ros::ok())
{
		if (ser.available())    //串口有数据时ser.available()为真
        {
            //1.读取串口信息:
            // ROS_INFO_STREAM("Reading from serial port\n");
            //通过ROS串口对象读取串口信息
            //std::cout << ser.available();
            //std::cout << ser.read(ser.available());
            sc_sensors += ser.read(ser.available());
            //std::cout <<"strRece:" << strRece << "\n" ;
            //strRece = "#31.984@";

            //2.截取数据、解析数据:
            //SC起始标志
            string gstart = "#";
            //SC终止标志
            string gend = "@";
            int i = 0, start = -1, end = -1;
            while ( i < sc_sensors.length() )
            {
                //找起始标志

                start = sc_sensors.find(gstart);
                if ( start != -1)
                //如果找到了起始标志,开始找终止标志
                {
                    //找终止标志
                    end = sc_sensors.find(gend);
                    //如果没找到,把起始标志开始的数据留下,前面的数据丢弃,然后跳出循环
                    if (end == -1)
                    {
                        if (end != 0)
                        sc_sensors = sc_sensors.substr(start);
                        break;
                    }
                    //如果找到了终止标志,把这段有效的数据剪切给解析的函数,剩下的继续开始寻找
                    else
                    {
                        i = end;

                        //把有效的数据给解析的函数以获取data
                        double sc_temp;
                        sc_temp=stod(sc_sensors.substr(start+1,end-start-1));
                        cout << setiosflags(ios::fixed)<<setprecision(4)<< "Talker:            SC_DATA is :" << sc_temp << "\n";
                        //发布消息到话题

                        sc_data.sc_double=sc_temp;
                        sc_data.sc_str=sc_sensors.substr(start+1,end-start-1);
                        sc_pub.publish(sc_data);
                        //如果剩下的字符大于等于4,则继续循环寻找有效数据,如果所剩字符小于等于3则跳出循环
                        if ( i+5 < sc_sensors.length())
                            sc_sensors = sc_sensors.substr(end+2);
                        else
                        {   sc_sensors = sc_sensors.substr(end+2);
                            break;
                        }
                    }
                }
            }
        }
    ros::spinOnce();
    loop_rate.sleep();
}

 这部分参考以下文章,我只是做了微不足道的更改,十分感谢作者的分享。

(12条消息) ROS实现串口GPS数据的解析与通信_何伯特的博客-程序员宅基地

 最后,关闭串口

	// 关闭串口
	ser.close();
	return 0;

发布方的完整代码如下

#include <string>
#include <sstream>
#include <iostream>
#include "ros/ros.h"
#include "std_msgs/String.h"
#include "serial/serial.h"
#include "serial_pkg_sc/sc_sensor.h"

serial::Serial ser;
using namespace std;

int main(int argc, char** argv)
{
	ros::init(argc, argv, "sc_serial_talker");
	ros::NodeHandle nh;

	ros::Rate loop_rate(10);
	ros::Publisher sc_pub = nh.advertise<serial_pkg_sc::sc_sensor>("sc_sensor", 1);
	// 打开串口
	try
	{
		ser.setPort("/dev/ttyUSB0");  // 这个端口号就是之前用cutecom看到的端口名称
		ser.setBaudrate(115200);
		serial::Timeout to = serial::Timeout::simpleTimeout(1000);
		ser.setTimeout(to);
		ser.open();
	}
	catch(serial::IOException &e)
	{
		ROS_INFO_STREAM("Failed to open port ");
		return -1;
	}
	ROS_INFO_STREAM("Succeed to open port" );

	serial_pkg_sc::sc_sensor sc_data;
	string sc_sensors;

	while(ros::ok())
	{
		if (ser.available())
        {
            //1.读取串口信息:
            // ROS_INFO_STREAM("Reading from serial port\n");
            //通过ROS串口对象读取串口信息
            //std::cout << ser.available();
            //std::cout << ser.read(ser.available());
            sc_sensors += ser.read(ser.available());
            //std::cout <<"strRece:" << strRece << "\n" ;
            //strRece = "#31.984@";

            //2.截取数据、解析数据:
            //SC起始标志
            string gstart = "#";
            //SC终止标志
            string gend = "@";
            int i = 0, start = -1, end = -1;
            while ( i < sc_sensors.length() )
            {
                //找起始标志

                start = sc_sensors.find(gstart);
                //如果没找到,丢弃这部分数据,但要留下最后2位,避免遗漏掉起始标志
                if ( start == -1)
                {
                    if (sc_sensors.length() > 2)   
                        sc_sensors = sc_sensors.substr(sc_sensors.length()-3);
                        break;

                }
                //如果找到了起始标志,开始找终止标志
                else
                {
                    //找终止标志
                    end = sc_sensors.find(gend);
                    //如果没找到,把起始标志开始的数据留下,前面的数据丢弃,然后跳出循环
                    if (end == -1)
                    {
                        if (end != 0)
                        sc_sensors = sc_sensors.substr(start);
                        break;
                    }
                    //如果找到了终止标志,把这段有效的数据剪切给解析的函数,剩下的继续开始寻找
                    else
                    {
                        i = end;

                        //把有效的数据给解析的函数以获取data
                        double sc_temp;
                        sc_temp=stod(sc_sensors.substr(start+1,end-start-1));
                        cout << setiosflags(ios::fixed)<<setprecision(4)<< "Talker: SC_DATA is :" << sc_temp << "\n";
                        //发布消息到话题

                        sc_data.sc_double=sc_temp;
                        sc_data.sc_str=sc_sensors.substr(start+1,end-start-1);
                        sc_pub.publish(sc_data);
                        //如果剩下的字符大于等于4,则继续循环寻找有效数据,如果所剩字符小于等于3则跳出循环
                        if ( i+5 < sc_sensors.length())
                            sc_sensors = sc_sensors.substr(end+2);
                        else
                        {   sc_sensors = sc_sensors.substr(end+2);
                            break;
                        }
                    }
                }
            }
        }
    ros::spinOnce();
    loop_rate.sleep();
    }

	// 关闭串口
	ser.close();
	return 0;
}

订阅串口数据

订阅方代码就比较简单了,其实发布订阅可以在同一个代码里实现,我主要考虑到后期可能会在其他功能包里使用这个包,所以准备分开发布和订阅,到时候直接用就可以了。

在src文件夹里创建sc_listener.cpp

代码如下:

#include <string>
#include <sstream>
#include <iostream>
#include "ros/ros.h"
#include "std_msgs/String.h"
#include "serial/serial.h"
#include "serial_pkg_sc/sc_sensor.h"

serial::Serial ser;
using namespace std;

void SC_SENSORcallback(const serial_pkg_sc::sc_sensor::ConstPtr& msg) 
{ 
    ROS_INFO_STREAM("Listener: SC_DATA is :" <<msg->sc_str); 
    // ser.write(msg->sc_str);   //发送串口数据 
	// ROS_INFO_STREAM("Write_serial is OK"); 
} 

int main(int argc, char **argv)
{

  ros::init(argc, argv, "sc_serial_listener");

  ros::NodeHandle n;

  ros::Subscriber sc_sub = n.subscribe("sc_sensor", 1, SC_SENSORcallback);

  ros::spin();

  return 0;
}

修改Cmakelists.txt文件 

include_directories(
# include
  ${catkin_INCLUDE_DIRS}
)


add_executable(sc_talker src/sc_talker.cpp)
target_link_libraries(sc_talker serial_pkg_sc_generate_messages_cpp)
add_dependencies(sc_talker ${catkin_LIBRARIES})

add_executable(sc_listener src/sc_listener.cpp)
target_link_libraries(sc_listener serial_pkg_sc_generate_messages_cpp)
add_dependencies(sc_listener ${catkin_LIBRARIES})

最后,切换回工作空间,编译一下,大功告成。

最终结果

最终一个终端运行roscore,然后打开两个终端,运行talker和listener,即可实现下面的效果。

roscore //终端1
rosrun serial_pjg_sc sc_talker //终端2
rosrun serial_pjg_sc sc_listener //终端3

总的来说,效果还是很不错的,虽然中间也踩了很多坑,但是能自己发现问题解决问题,还是很棒的,最终的结果让我很是欣慰,总算对了!!!

 

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_44934219/article/details/131858756

智能推荐

振动信号常用的时域和频域指标_振动信号时域分析-程序员宅基地

文章浏览阅读6.5k次,点赞6次,收藏68次。通常振动信号为一时间序列,衡量振动信号的指标包括时域指标和频域指标,网上分享计算公式和源程序的比较少,本文给出了公式定义以及matlab/python源码。常见时域指标:对应的Matlab程序%%%%matlab程序close allclearclcst = 0.01;data = sin(0:st:10);Xr = mean(sqrt(abs(data)))*mean(sqrt(abs(data)));Xmean = mean(abs(data));Xrms = rm_振动信号时域分析

Python: sklearn库中数据预处理函数fit_transform()和transform()的区别_fit_transform函数参数-程序员宅基地

文章浏览阅读1.4k次。敲《Python机器学习及实践》上的code的时候,对于数据预处理中涉及到的fit_transform()函数和transform()函数之间的区别很模糊,查阅了很多资料,这里整理一下:涉及到这两个函数的代码如下:# 从sklearn.preprocessing导入StandardScalerfrom sklearn.preprocessing import StandardScale..._fit_transform函数参数

一起talk C栗子吧(第四十七回:C语言实例--走迷宫一)-程序员宅基地

文章浏览阅读1.2k次。图文并茂走迷宫_一起talk c栗子吧

基于SpringBoot+微信小程序的失物招领小程序(前后端分离)-程序员宅基地

文章浏览阅读666次,点赞29次,收藏20次。JAVA:Java是一门面向对象编程语言,不仅吸收了C++语言的各种优点,还摒弃了C++里难以理解的多继承、指针等概念,因此Java语言具有功能强大和简单易用两个特征。Java语言作为静态面向对象编程语言的代表,极好地实现了面向对象理论,允许程序员以优雅的思维方式进行复杂的编程。Vue:Vue (发音为 /vjuː/,类似 view) 是一款用于构建用户界面的JavaScript框架。

【C语言】数据在内存中的存储-程序员宅基地

文章浏览阅读1.3k次,点赞72次,收藏27次。字节序——是以字节为单位,来讨论存储顺序的其实超过一个字节的数据在内存中存储的时候,就有存储顺序的问题,按照不同的存储顺序,我们分为大端字节序存储和小端字节序存储,下面是具体的概念:大端(存储)模式:是指数据的低位字节内容保存在内存的高地址处,而数据的高位字节内容,保存在内存的低地址处。小端(存储)模式:是指数据的低位字节内容保存在内存的低地址处,而数据的高位字节内容,保存在内存的高地址处。

SimpleDateFormat 24小时和12小时转换_androidstudio导入simpledateformat-程序员宅基地

文章浏览阅读1k次。SimpleDateFormat 24小时和12小时转换HH 代表 24 小时制 , hh 表示 12 小时制public class SimpleDateFormat_12_24_ { public static void main(String[] args) { SimpleDateFormat sdf_24 = new SimpleDateFormat("HH:mm:_androidstudio导入simpledateformat

随便推点

不用USB连接线或没有ADB驱动如何调试安卓_不使用usb调试连接电脑-程序员宅基地

文章浏览阅读1.2w次,点赞5次,收藏18次。USB数据线的调试方式,大家应该比较常用,今天就专门说说怎么在不使用USB数据线的方式下进行安卓的开发调试。_不使用usb调试连接电脑

MySQL 创建表时出现 Tablespace for `xxx`.`xxx` exists._please discard the tablespace before import.-程序员宅基地

文章浏览阅读2.7k次,点赞2次,收藏2次。但在 SQLyog 上查看时却是不存在的。这是因为 MySQL 异常停止后,导致某些文件丢失或损害引起的,具体为什么会到至 MySQL 异常停止,只能进一步查看。本地 MySQL 异常停止后,手动启动服务,热庵后执行 sql 脚本时,发现又报错信息,于是手动创建表,发现还是无法创建,报了。表空间已存在,在导入数据之前需要将表空间释放掉。_please discard the tablespace before import.

四足机器人|机器狗|仿生机器人|多足机器人|PPT|汇报|科研汇报PPT|技术汇报_四足机器人关键技术ppt-程序员宅基地

文章浏览阅读2.7k次,点赞11次,收藏28次。四足机器人|机器狗|仿生机器人|多足机器人|PPT|汇报|科研汇报PPT|技术汇报_四足机器人关键技术ppt

org.springframework.data.redis.serializer.SerializationException: Could not read JSON-程序员宅基地

文章浏览阅读1k次。org.springframework.data.redis.serializer.SerializationException: Could not read JSON将数据存储到redis中报错,由于对象(实体)中缺少json的某个字段属性引起解决办法。@JsonIgnoreProperties(ignoreUnknown = true) _org.springframework.data.redis.serializer.serializationexception: could not

Angular官网学习4:Angular入门,你的第一个应用(4)输出_angular notifychange$-程序员宅基地

文章浏览阅读375次。在本节中,将设置商品提醒组件,当用户点击‘Notify Me’的时候,像商品列表组件发出事件。1、打开 product-alerts.component.ts, 从 @angular/core 中导入 Output 和 EventEmitter。2、在组件类中,用 @Output 装饰器和一个事件发射器(EventEmitter)实例定义一个名为 notify 的属性。这可以让商品提醒组件在 ..._angular notifychange$

[flask 优化] 由flask-bootstrap,flask-moment引起的访问速度慢的原因及解决办法-程序员宅基地

文章浏览阅读661次。一周时间快速阅读了400页的《javascript基础教程》,理解了主要概念。解决了一个很久之前的疑问。我的网站是使用flask框架搭建的,介绍flask web的一本著名的书(之前提到过)作者搭建个人博客时,向读者推荐了flask-bootstrap,flask_moment这两个库,前者能快速的解决前端样式问题,后者提供了时间戳功能。但在某种情况下,比如网络延迟或者运营商的问题,访问网..._bootstrap 速度慢 maxcdn