最近在使用ubantu读取串口数据,遇到了很多问题,好在最后也一一解决,实现目标,所以想写一下整个流程,进行一个记录分享吧。我用的是ubantu20.04,部署的ROS,希望从ROS中读取到串口数据,并进行发布订阅。
目录
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
完成之后重启电脑,后面就不需要每次都添加权限了。
这部分比较简单,直接安装即可。我用的是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)的功能包了!
我的数据是一个常见字符串,#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++不太一样,具体可以看官方介绍文档
写完之后,记得保存(开个玩笑),然后就是
在相应位置添加以下代码,(相应位置在哪?相信我,你能找到它的。)
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
...
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
)
这四部分建议先找到,然后解除注释,再进行修改,因为顺序不对的话可能会出问题。
这样我们就自定义了一个新的消息类型,修改完可以切换回工作空间目录,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::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();
}
这部分参考以下文章,我只是做了微不足道的更改,十分感谢作者的分享。
// 关闭串口
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;
}
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
总的来说,效果还是很不错的,虽然中间也踩了很多坑,但是能自己发现问题解决问题,还是很棒的,最终的结果让我很是欣慰,总算对了!!!
文章浏览阅读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_振动信号时域分析
文章浏览阅读1.4k次。敲《Python机器学习及实践》上的code的时候,对于数据预处理中涉及到的fit_transform()函数和transform()函数之间的区别很模糊,查阅了很多资料,这里整理一下:涉及到这两个函数的代码如下:# 从sklearn.preprocessing导入StandardScalerfrom sklearn.preprocessing import StandardScale..._fit_transform函数参数
文章浏览阅读1.2k次。图文并茂走迷宫_一起talk c栗子吧
文章浏览阅读666次,点赞29次,收藏20次。JAVA:Java是一门面向对象编程语言,不仅吸收了C++语言的各种优点,还摒弃了C++里难以理解的多继承、指针等概念,因此Java语言具有功能强大和简单易用两个特征。Java语言作为静态面向对象编程语言的代表,极好地实现了面向对象理论,允许程序员以优雅的思维方式进行复杂的编程。Vue:Vue (发音为 /vjuː/,类似 view) 是一款用于构建用户界面的JavaScript框架。
文章浏览阅读1.3k次,点赞72次,收藏27次。字节序——是以字节为单位,来讨论存储顺序的其实超过一个字节的数据在内存中存储的时候,就有存储顺序的问题,按照不同的存储顺序,我们分为大端字节序存储和小端字节序存储,下面是具体的概念:大端(存储)模式:是指数据的低位字节内容保存在内存的高地址处,而数据的高位字节内容,保存在内存的低地址处。小端(存储)模式:是指数据的低位字节内容保存在内存的低地址处,而数据的高位字节内容,保存在内存的高地址处。
文章浏览阅读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
文章浏览阅读1.2w次,点赞5次,收藏18次。USB数据线的调试方式,大家应该比较常用,今天就专门说说怎么在不使用USB数据线的方式下进行安卓的开发调试。_不使用usb调试连接电脑
文章浏览阅读2.7k次,点赞2次,收藏2次。但在 SQLyog 上查看时却是不存在的。这是因为 MySQL 异常停止后,导致某些文件丢失或损害引起的,具体为什么会到至 MySQL 异常停止,只能进一步查看。本地 MySQL 异常停止后,手动启动服务,热庵后执行 sql 脚本时,发现又报错信息,于是手动创建表,发现还是无法创建,报了。表空间已存在,在导入数据之前需要将表空间释放掉。_please discard the tablespace before import.
文章浏览阅读2.7k次,点赞11次,收藏28次。四足机器人|机器狗|仿生机器人|多足机器人|PPT|汇报|科研汇报PPT|技术汇报_四足机器人关键技术ppt
文章浏览阅读1k次。org.springframework.data.redis.serializer.SerializationException: Could not read JSON将数据存储到redis中报错,由于对象(实体)中缺少json的某个字段属性引起解决办法。@JsonIgnoreProperties(ignoreUnknown = true) _org.springframework.data.redis.serializer.serializationexception: could not
文章浏览阅读375次。在本节中,将设置商品提醒组件,当用户点击‘Notify Me’的时候,像商品列表组件发出事件。1、打开 product-alerts.component.ts, 从 @angular/core 中导入 Output 和 EventEmitter。2、在组件类中,用 @Output 装饰器和一个事件发射器(EventEmitter)实例定义一个名为 notify 的属性。这可以让商品提醒组件在 ..._angular notifychange$
文章浏览阅读661次。一周时间快速阅读了400页的《javascript基础教程》,理解了主要概念。解决了一个很久之前的疑问。我的网站是使用flask框架搭建的,介绍flask web的一本著名的书(之前提到过)作者搭建个人博客时,向读者推荐了flask-bootstrap,flask_moment这两个库,前者能快速的解决前端样式问题,后者提供了时间戳功能。但在某种情况下,比如网络延迟或者运营商的问题,访问网..._bootstrap 速度慢 maxcdn