有参考价值的博客:
http://stevenshi.me/2017/07/18/control-mobile-base-by-serial/
http://stevenshi.me/2017/05/17/linux-serial/
http://stevenshi.me/2017/05/17/ros-serial/
https://blog.csdn.net/m0_37598942/article/details/80713512
https://blog.csdn.net/Tansir94/article/details/81357612
https://github.com/wjwwood/serial
https://blog.csdn.net/weixin_43795921/article/details/85219249
https://blog.csdn.net/m0_37598942/article/details/80713512

https://blog.csdn.net/u012247418/article/details/82960889 libusb

详解stm32串口到底如何与ROS实现信息交互
设计思路:
有的朋友使用全向移动底座,有的使用两轮差动或四轮驱动实现移动底座;为了保持代码通用性,运动学解析这一部分没有特别的说明,移动底座只接收 x 与 y 两个方向的线速度以及一个绕 z 轴的角速度;针对不同的移动底座,还需要设计不同的运动学解析函数,以便于将线速度与角速度转变成电机运动指令,从而控制电机运动。ROS 部分实现一个节点,该节点订阅 cmd_vel 话题,并将该话题转变成 x y 两个方向的线速度以及一个绕 z 轴的角速度,通过串口发送到移动底座,即给STM32;另外该节点还需要发布导航需要的 odom 消息,这个消息需要移动底座提供,通过STM32的串口发送机器人的位置、速度、偏航角等信息,经过特殊的变换之后再发布。

STM32

STM32使用最多的便是STM32F103,为了便于调试,选择STM32的串口1作为调试串口,打印一些调试信息;使用STM32的串口3作为与 ROS 通信的端口;调试串口波特率设置为9600,8位数据1个停止位,偶校验;串口3设置为115200波特率,8位数据1个停止位无校验模式;当然这个波特率可以自行设置。数据的发送和接收可以是固定长度或不固定长度,为了适应不同的长度字节,建议使用DMA模式,并开启串口的空闲中断,否则很难判断一帧数据包的完全接收。当然也可以开启一个定时器,在一定时间内判断是否超时来间接的判断一帧数据是否完成。最好的方式就是开启串口空闲中断,将数据的发送和接收都由DMA负责,这样不占用CPU资源。
调试端口:
PA9—>USART1_TX—->DMA1_Channel4
PA10—>USART1_RX—>DMA1_Channel5
与ROS通信端口:
PB10—>USART3_TX—->DMA1_Channel2
PB11—->USART3_RX—>DMA1_Channel3

数据接收
使用DMA接收数据,配置如下:

DMA_Cmd(USARTz_Rx_DMA_Channe,DISABLE);//配置之前先停止
DMA_DeInit(USARTz_Rx_DMA_Channe);//恢复缺省配置
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)USARTz_DR_Base;// 设置串口接收数据寄存器
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)USARTzRxBuffer;//接收缓存地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//设置外设为数据源
DMA_InitStructure.DMA_BufferSize = USARTzTxBufferSize;//需要接收的字节数
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址不做增加调整
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; // 内存缓冲地址增加调整
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; // 外设数据宽度一个字节
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; // 内存数据宽度一个字节
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //单次传输模式
DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh; // 高优先级
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //关闭内存到内存的DMA模式
DMA_Init(USARTz_Rx_DMA_Channe, &DMA_InitStructure);// 写入配置
DMA_ClearFlag(USARTz_Rx_DMA_FLAG); //清除DMA所有标志
DMA_Cmd(USARTz_Rx_DMA_Channe, ENABLE); // 开启DMA接收通道
开启串口的空闲中断,并在空闲中断中读取串口数据:

/* Enable USART3 Receive interrupts */
USART_ITConfig(USART3, USART_IT_IDLE, ENABLE);
void USART3_IRQHandler(void)//串口中断
{

if(USART_GetITStatus(USART3, USART_IT_IDLE) != RESET)  
{
    com_x_usart_dma_read();
	USART_ReceiveData( USART3 );
}

}
这样才能接收变长的数据帧。

数据发送
数据发送时只要将数据发送的首地址以及数据宽度给 DMA 即可,因为一开始固定了发送缓存地址,所以只需要指定发送缓存长度即可:

void com_x_usart_dma_start_tx(uint8_t size)
{
USARTz_Tx_DMA_Channe->CNDTR = (uint16_t)size; //重新赋值,指定发送缓存长度
DMA_Cmd(USARTz_Tx_DMA_Channe, ENABLE); // 开启DMA发送
}
调试功能
关于 stm32 的 printf 功能实现可以参考站内文章 STM32如何在DMA模式下实现printf
这里建议使用 DMA 方式实现 printf,因为底座还有很多任务要处理包括运动学解析以及电机控制等。

在这里插入图片描述
在这里插入图片描述
//将偏航角转换成四元数才能发布
odom_quat = tf::createQuaternionMsgFromYaw(yaw.fvalue);
//发布坐标变换父子坐标系
odom_trans.header.frame_id = “odom”;
odom_trans.child_frame_id = “base_link”;

//发布tf坐标变换
odom_broadcaster.sendTransform(odom_trans);
//获取当前时间
current_time = ros::Time::now();
//载入里程计时间戳
odom.header.stamp = current_time;
//里程计位置数据
odom.pose.pose.position.x = posx.fvalue;
//载入线速度和角速度
odom.twist.twist.linear.x = vx.fvalue;
odom.twist.twist.angular.z = va.fvalue;
//发布里程计消息
read_pub.publish(odom);
ROS_INFO(“publish odometry”);

××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××
××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××
问题解决:
浮点数与16进制的相互转换

强制转换法
浮点转换成4字节16进制:

void float2bytes(float p,unsigned char *bytes)
{
 unsigned char *pchar = (unsigned char*)&p;
 for(int i=0;i < sizeof(float);i++)
 {
   *bytes = *pchar;
   pchar++;
   bytes++;
 }
}

16进制4字节转换成浮点:

float bytes2float(unsigned char *bytes)
{
 return *((float*)bytes);//强制转换
}

联合体法

typedef union{
unsigned char cvalue[4];
float fvalue;
}float_union;
定义联合体变量:
 

float_union trans_data;
此时如果给变量trans_data赋值一个浮点数:
 

trans_data.fvalue = 10.05;
那么直接调用cvalue即可获取浮点数10.05的4字节16进制数据:
 

for(int i=0;i<4;i++)
printf(" 0x%02x",trans_data.cvalue[i]);
#include <stdio.h>  
  
/* 
要点提示: 
1. float和unsigned long具有相同的数据结构长度 
2. union据类型里的数据存放在相同的物理空间 
*/  
typedef union  
{  
    float fdata;  
    unsigned long ldata;  
}FloatLongType;  
  
/* 
将浮点数f转化为4个字节数据存放在byte[4]中 
*/  
void Float_to_Byte(float f,unsigned char byte[])  
{  
    FloatLongType fl;  
    fl.fdata=f;  
    byte[0]=(unsigned char)fl.ldata;  
    byte[1]=(unsigned char)(fl.ldata>>8);  
    byte[2]=(unsigned char)(fl.ldata>>16);  
    byte[3]=(unsigned char)(fl.ldata>>24);  
}  
/* 
将4个字节数据byte[4]转化为浮点数存放在*f中 
*/  
void Byte_to_Float(float *f,unsigned char byte[])  
{  
    FloatLongType fl;  
    fl.ldata=0;  
    fl.ldata=byte[3];  
    fl.ldata=(fl.ldata<<8)|byte[2];  
    fl.ldata=(fl.ldata<<8)|byte[1];  
    fl.ldata=(fl.ldata<<8)|byte[0];  
    *f=fl.fdata;  
}  
  
/* 
测试函数 
*/  
int main()  
{  
    float f=123456.789f;  
    unsigned char byte[4]={0};  
  
    printf("float data=%f\n",f);  
    Float_to_Byte(f,byte);  
    f=789.123456f;  
    printf("changed float data=%f\n",f);  
    Byte_to_Float(&f,byte);  
    printf("float data=%f\n",f);  
  
    return 0;  
} 

仅做参考:

首先,这里要引入一个名称为serial的包,这个包的安装命令为:

$ sudo apt-get install ros-<版本号>-serial

serial包的介绍:http://wiki.ros.org/serial

接下来,创建一个自己的包,借助serial这个包来编写串口通信的代码。

1、创建一个包,依赖roscpp和serial两个包

$ catkin_create_pkg serial_port roscpp serial

进入下载的软件包的位置

roscd serial
1
若是安装成功会看到:

$:/opt/ros/kinetic/share/serial
1
新建工作空间级程序包:

cd
mkdir -p serialPort/src
cd serialPort
catkin_make
source devel/setup.bash
cd src
catkin_create_pkg serialPort std_msgs roscpp serial
cd serialPort/src


#include <ros/ros.h>
#include <serial/serial.h>
#include <iostream>
 
int main(int argc, char** argv)
{
    ros::init(argc, argv, "serial_port");
    //创建句柄(虽然后面没用到这个句柄,但如果不创建,运行时进程会出错)
    ros::NodeHandle n;
    
    //创建一个serial类
    serial::Serial sp;
    //创建timeout
    serial::Timeout to = serial::Timeout::simpleTimeout(100);
    //设置要打开的串口名称
    sp.setPort("/dev/ttyUSB0");
    //设置串口通信的波特率
    sp.setBaudrate(115200);
    //串口设置timeout
    sp.setTimeout(to);
 
    try
    {
        //打开串口
        sp.open();
    }
    catch(serial::IOException& e)
    {
        ROS_ERROR_STREAM("Unable to open port.");
        return -1;
    }
    
    //判断串口是否打开成功
    if(sp.isOpen())
    {
        ROS_INFO_STREAM("/dev/ttyUSB0 is opened.");
    }
    else
    {
        return -1;
    }
    
    ros::Rate loop_rate(500);
    while(ros::ok())
    {
        //获取缓冲区内的字节数
        size_t n = sp.available();
        if(n!=0)
        {
            uint8_t buffer[1024];
            //读出数据
            n = sp.read(buffer, n);
            
            for(int i=0; i<n; i++)
            {
                //16进制的方式打印到屏幕
                std::cout << std::hex << (buffer[i] & 0xff) << " ";
            }
            std::cout << std::endl;
            //把数据发送回去
            sp.write(buffer, n);
        }
        loop_rate.sleep();
    }
    
    //关闭串口
    sp.close();
 
    return 0;

代码中&0xFF的目的:
byte类型的数字要&0xff再赋值给int类型,其本质原因就是想保持二进制补码的一致性。
当byte要转化为int的时候,高的24位必然会补1,这样,其二进制补码其实已经不一致了,&0xff可以将高的24位置为0,低8位保持原样。这样做的目的就是为了保证二进制数据的一致性。

实现过程中的问题:
如果出现如下错误,则是因为权限不够引起的

terminate called after throwing an instance of 'boost::exception_detail::clone_impl<boost::exception_detail::error_info_injector<boost::system::system_error> >'
  what():  open: Permission denied

首先查看电脑链接的串口信息(名称),据此更改程序:

dmesg | grep ttyS*

打开/dev/ttyUSB0 权限不够解决办法:
非永久性:

sudo chmod 666 /dev/ttyUSB0

永久性解决
1、将你的用户名加入dialout用户组,因为默认情况下只有root用户才有权操作tty设备

sudo adduser USER_NAME dialout

2、登出系统,再重新登入,就可以了
3、可以通过下面的指令查看你的用户名是否已经加入dialout用户组

cat /etc/group

错误:
terminate called after throwing an instance of ‘boost::exception_detail::clone_impl<boost::exception_detail::error_info_injectorboost::lock_error >’
what(): boost: mutex lock failed in pthread_mutex_lock: Invalid argument
解决办法:
sudo apt-get dist-upgrade

ubuntu串口助手:

下载:sudo apt-get install cutecom 
打开:sudo cutecom 

程序写完后CmakeLists文件的修改:

find_package(catkin REQUIRED COMPONENTS
  roscpp
  cv_bridge
  std_msgs
  serial
)

find_package(Boost REQUIRED COMPONENTS system)

add_executable(serialtest
  src/serialtest.cpp)
  下面的可不添加
  add_dependencies(${PROJECT_NAME}_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
target_link_libraries(${PROJECT_NAME}_node
   ${catkin_LIBRARIES}

示例代码:

#include <stdio.h>
#include <string.h>
#include <ros/ros.h>
#include <cv_bridge/cv_bridge.h>
#include <ros/package.h>

#include <geometry_msgs/PoseStamped.h>


#include <nav_msgs/Odometry.h>	//数据格式
#include <serial/serial.h>		//ros串口

#include <std_msgs/String.h> 
#include <std_msgs/Empty.h> 
#include <fstream>
#include <iostream>
using namespace std;

serial::Serial ser;			//定义一个串口对象
ros::Subscriber position_sub;		//订阅位姿节点

typedef union{				//定义一个联合体,用于double数据与16进制的转换
unsigned char cvalue[4];
double fvalue;
}double_union;




static uint8_t s_buffer[15];	分配静态存储空间

void position_world_callback(const nav_msgs::Odometry& odom_msg)
{
	geometry_msgs::PoseStamped pose_msg;
	pose_msg.pose = odom_msg.pose.pose;
	cout<<"positon->  x: "<<pose_msg.pose.position.x<<"  y :  "<<pose_msg.pose.position.y<<"  z :  "<<pose_msg.pose.position.z<<endl;;
	ROS_INFO_STREAM("----------------------------------------------");

	double_union position_x,position_y,position_z;
	memset(s_buffer,0,sizeof(s_buffer));		//内存空间初始化为0
	
	position_x.fvalue=pose_msg.pose.position.x;
	position_y.fvalue=pose_msg.pose.position.y;
	position_z.fvalue=pose_msg.pose.position.z;
	
	//数据打包
	s_buffer[0] = 0xff;		//数据的帧头
	s_buffer[1] = 0xfe;
	
	s_buffer[2] = position_x.cvalue[0];
	s_buffer[3] = position_x.cvalue[1];
	s_buffer[4] = position_x.cvalue[2];
	s_buffer[5] = position_x.cvalue[3];
	
	s_buffer[6] = position_y.cvalue[0];
	s_buffer[7] = position_y.cvalue[1];
	s_buffer[8] = position_y.cvalue[2];
	s_buffer[9] = position_y.cvalue[3];
	
	s_buffer[10] = position_z.cvalue[0];
	s_buffer[11] = position_z.cvalue[1];
	s_buffer[12] = position_z.cvalue[2];
	s_buffer[13] = position_z.cvalue[3];
	
	//CRC		校验和从有效数据开始取异或即可
	s_buffer[14] = s_buffer[2]^s_buffer[3]^s_buffer[4]^s_buffer[5]^s_buffer[6]^s_buffer[7]^\
					s_buffer[8]^s_buffer[9]^s_buffer[10]^s_buffer[11]^s_buffer[12]^s_buffer[13];
	ser.write(s_buffer,15);
	
	ROS_INFO("data send successful");
	
}





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

	
	ros::init(argc, argv, "serial_node");		//ROS串口节点名称
	ros::NodeHandle my_node;
	
	position_sub = my_node.subscribe("/camera/odom/sample", 1, position_world_callback);
	
	//读取串口名称
	string filename = "/home/lmf/WORK_SPACE/catkin_ws/src/realsense-ros/realsense2_camera/parameter.txt";
	ifstream fin( filename.c_str() );
	if (!fin)
        {
            cout<<"parameter file does not exist."<<endl;
            return 0;
        }
	string serialPort;
	getline( fin, serialPort );
	fin.close();
	cout<<serialPort<<endl;
	try
	{
	//设置串口属性,并打开串口
		ser.setPort(serialPort);	//设置对应串口的名称
		ser.setBaudrate(115200);		//波特率115200
		serial::Timeout to = serial::Timeout::simpleTimeout(1000);
        ser.setTimeout(to);
        ser.open();
	}
	catch (serial::IOException& e)
    {
        ROS_ERROR_STREAM("Unable to open port ");
        return -1;
    }
	
	
    if(ser.isOpen())
    {
        ROS_INFO_STREAM("Serial Port opened");
    }
    else
    {
        return -1;
    }
	
	
	//指定循环的速率 
	ros::Rate loop_rate(50);
	while (ros::ok())
	{
		

		ros::spinOnce();
		
		loop_rate.sleep();
	}
    ser.close();
	return 0;
}


GitHub 加速计划 / li / linux-dash
10.39 K
1.2 K
下载
A beautiful web dashboard for Linux
最近提交(Master分支:2 个月前 )
186a802e added ecosystem file for PM2 4 年前
5def40a3 Add host customization support for the NodeJS version 4 年前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐