【ROS、STM32】ROS与STM32通信

4 篇文章 3 订阅


本章节主要解决的是如何在ROS与STM32之间建立串口通信,达到相互发送和接收对方数据的目的,从而为我们自己以ros操作系统开发硬件提供相应的便利。

一、相关原理及准备工作

完成ROS与STM32之间的串口通信,需要准备的硬件有STM32串口TTL转USB模块Linux硬件设备,博主使用的STM32是正点原子的STM32精英板,Linux硬件设备下搭载的rROS环境是ros-kinetic,其工作原理如下图所示
在这里插入图片描述
在开始ROS与STM32的通信之前,需要注意以下几点
(1)确保stm32端和ros端波特率一定一致
(2)确保是stm32串口,TTL转USB模块和Linux设备之间是按照上图所示连接,TX与RX相连,RX与TX相连,同时,也要测试连接线的好坏
(3)确保linux设备系统中有CH340/CH341驱动,一般情况下都会有,如果没有CH340/CH341驱动,需要到网上查找相关资料下载驱动
(4)确保自己的串口在Linux系统中具有超级用户权限(一般都默认不具有该权限),一般插上TTL转USB模块,Linux系统中就可以检测到ttyUSB0的串口设备
(5)如果插上TTL转USB模块后,Linux系统中查询的串口设备不是ttyUSB0的话,在编写相应的程序时,要注意串口设备名称的一致性

二、STM32与ROS工程文件下载

所需要的的STM32与ROS工程文件百度云链接如下
链接: https://pan.baidu.com/s/1oOcfHVRdzKeyQKiBKLHsrg.
提取码:abcd

STM32与ROS工程文件所包含的工程文件
(1)STM32端:STM32与ROS串口通信测试工程文件
(2)ROS端:ROS与STM32串口通信测试功能包文件

1、STM32测试工程文件结构

主要包括:
(1)主函数main.c
(2)文件mbotLinuxUsart.c
在这里插入图片描述

STM32与ROS串口通信测试工程文件程序详解

main.c文件

#include "delay.h"
#include "sys.h"
#include "usart.h"
#include "led.h"
#include "mbotLinuxUsart.h"
//测试发送变量
short testSend1   =5000;
short testSend2   =2000;
short testSend3   =1000;
unsigned char testSend4 = 0x05;
//测试接收变量
int testRece1     =0;
int testRece2     =0;
unsigned char testRece3 = 0x00;

int main(void)
{	
	delay_init();	    	                      
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	uart_init(115200);	                        
	while(1)
	{
	    usartSendData(testSend1,testSend2,testSend3,testSend4);
		delay_ms(13);
	} 
}
//串口中断服务函数,接收数据
void USART1_IRQHandler()
{
	if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
 	 {
		 USART_ClearITPendingBit(USART1,USART_IT_RXNE);
		 //从ROS接收数据,并把他们存放到以下三个变量当中
		 usartReceiveOneData(&testRece1,&testRece2,&testRece3);
	 }
}

mbotLinuxUsart.c文件

#include "mbotLinuxUsart.h"
#include "usart.h"       
//数据接收暂存区
unsigned char  receiveBuff[16] = {0};   
//通信协议常量
const unsigned char header[2]  = {0x55, 0xaa};
const unsigned char ender[2]   = {0x0d, 0x0a};
//发送数据共用体
union sendData
{
	short d;
	unsigned char data[2];
}leftVelNow,rightVelNow,angleNow;
//接收数据共用体
union receiveData
{
	short d;
	unsigned char data[2];
}leftVelSet,rightVelSet;

int usartReceiveOneData(int *p_leftSpeedSet,int *p_rightSpeedSet,unsigned char *p_crtlFlag)
{
	unsigned char USART_Receiver              = 0;        
	static unsigned char checkSum             = 0;
	static unsigned char USARTBufferIndex     = 0;
	static short j=0,k=0;
	static unsigned char USARTReceiverFront   = 0;
	static unsigned char Start_Flag           = START;    
	static short dataLength                   = 0;

	USART_Receiver = USART_ReceiveData(USART1);   

	if(Start_Flag == START)
	{
		if(USART_Receiver == 0xaa)                           
		{  
			if(USARTReceiverFront == 0x55)       
			{
				Start_Flag = !START;             
				printf("header ok\n");
				receiveBuff[0]=header[0];       
				receiveBuff[1]=header[1];  
				USARTBufferIndex = 0;           
				checkSum = 0x00;				
			}
		}
		else 
		{
			USARTReceiverFront = USART_Receiver;  
		}
	}
	else
    { 
		switch(USARTBufferIndex)
		{
			case 0:
				receiveBuff[2] = USART_Receiver;
				dataLength     = receiveBuff[2];            //buf[2]
				USARTBufferIndex++;
				break;
			case 1:
				receiveBuff[j + 3] = USART_Receiver;        //buf[3] - buf[7]					
				j++;
				if(j >= dataLength)                         
				{
					j = 0;
					USARTBufferIndex++;
				}
				break;
			case 2:
				receiveBuff[3 + dataLength] = USART_Receiver;
				checkSum = getCrc8(receiveBuff, 3 + dataLength);
				if (checkSum != receiveBuff[3 + dataLength]) //buf[8]
				{
					printf("Received data check sum error!");
					return 0;
				}
				USARTBufferIndex++;
				break;
			case 3:
				if(k==0)
				{
					k++;
				}
				else if (k==1)
				{			
					 for(k = 0; k < 2; k++)
					{
						leftVelSet.data[k]  = receiveBuff[k + 3]; //buf[3]  buf[4]
						rightVelSet.data[k] = receiveBuff[k + 5]; //buf[5]  buf[6]
					}				
					*p_leftSpeedSet  = (int)leftVelSet.d;
					*p_rightSpeedSet = (int)rightVelSet.d;
					//ctrlFlag
					*p_crtlFlag = receiveBuff[7];                //buf[7]
					USARTBufferIndex   = 0;
					USARTReceiverFront = 0;
					Start_Flag         = START;
					checkSum           = 0;
					dataLength         = 0;
					j = 0;
					k = 0;
				}
				break;
			 default:break;
		}		
	}
	return 0;
}

void usartSendData(short leftVel, short rightVel,short angle,unsigned char ctrlFlag)
{
	unsigned char buf[13] = {0};
	int i, length = 0;
	leftVelNow.d  = leftVel;
	rightVelNow.d = rightVel;
	angleNow.d    = angle;
	for(i = 0; i < 2; i++)
		buf[i] = header[i];                      // buf[0] buf[1] 
	length = 7;
	buf[2] = length;                             // buf[2]
	for(i = 0; i < 2; i++)
	{
		buf[i + 3] = leftVelNow.data[i];         // buf[3] buf[4]
		buf[i + 5] = rightVelNow.data[i];        // buf[5] buf[6]
		buf[i + 7] = angleNow.data[i];           // buf[7] buf[8]
	}
	buf[3 + length - 1] = ctrlFlag;              // buf[9]
	buf[3 + length] = getCrc8(buf, 3 + length);  // buf[10]
	buf[3 + length + 1] = ender[0];              // buf[11]
	buf[3 + length + 2] = ender[1];              // buf[12]
	USART_Send_String(buf,sizeof(buf));
}
void USART_Send_String(u8 *p,u16 sendSize)
{ 
	static int length =0;
	while(length<sendSize)
	{   
		while( !(USART1->SR&(0x01<<7)) );
		USART1->DR=*p;                   
		p++;
		length++;
	}
	length =0;
}
unsigned char getCrc8(unsigned char *ptr, unsigned short len)
{
	unsigned char crc;
	unsigned char i;
	crc = 0;
	while(len--)
	{
		crc ^= *ptr++;
		for(i = 0; i < 8; i++)
		{
			if(crc&0x01)
                crc=(crc>>1)^0x8C;
			else 
                crc >>= 1;
		}
	}
	return crc;
}

2、ROS测试功能包文件结构

主要包括:
(1)通信协议以及串口配置程序文件mbot_linux_serial.cpp
(2)系统结构测试主程序文件publish_node.cpp
(3)一个基础ROS功能包具备的其他程序文件
在这里插入图片描述

ROS与STM32串口通信测试功能包程序详解

publish_node.cpp文件

//包含ros库下的ros.h头文件
#include "ros/ros.h"
//包含std_msgs库下的String.h头文件
#include "std_msgs/String.h" 
//包含mbot_linux_serial.h头文件
#include "mbot_linux_serial.h"

//测试发送数据两
double testSend1=5555.0;
double testSend2=2222.0;
unsigned char testSend3=0x07;
//测试接受数据变量
double testRece1=0.0;
double testRece2=0.0;
double testRece3=0.0;
unsigned char testRece4=0x00;

int main(int agrc,char **argv)
{
    //创建一个ros节点,节点名称为public_node
    ros::init(agrc,argv,"public_node");
    //创建句柄,用于管理节点信息
    ros::NodeHandle nh;
    //设置频率,10HZ
    ros::Rate loop_rate(10);
    //串口初始化,相关定义在mbot_linux_serial.cpp有描述
    serialInit();
    /*
    ros::ok()在不进行任何操作时,就相当于返回True,只有在以下几种情况下会变成返回False
    (1)运行终端时,按下Ctrl-C时
    (2)我们被一个同名同姓的节点从网络中踢出。
    (3)ros::shutdown()被应用程序的另一部分调用。
    (4)所有的ros::NodeHandles都被销毁了。
    */
    while(ros::ok())
    {
        /*
        ros::spinOnce()和ros::spin()是ros消息回调处理函数
        ros消息回调处理函数原理:如果你的程序写了相关的消息订阅函数,那么程序在执行过程中,除了主程序以外,ROS还会自动在后台按照你规定的格式,接受订阅的消息,但是所接到的消息并不是立刻就被处理,而是必须要等到ros::spin()或ros::spinOnce()执行的时候才被调用
        他们的区别在于ros::spinOnce调用后会继续执行其后面的语句,而ros::spin()则在调用后不会继续执行其后面的语句
        */
        ros::spinOnce();
        //向STM32端发送数据,前两个为double类型,最后一个为unsigned char类型,其函数相关定义在mbot_linux_serial.cpp有描述
	    writeSpeed(testSend1,testSend2,testSend3);
        //从STM32接收数据,输入参数依次转化为小车的线速度、角速度、航向角(角度)、预留控制位,其函数相关定义在mbot_linux_serial.cpp有描述
	    readSpeed(testRece1,testRece2,testRece3,testRece4);
        //打印数据
	    ROS_INFO("%f,%f,%f,%d\n",testRece1,testRece2,testRece3,testRece4);
	    //等待100ms的时间
        loop_rate.sleep();
    }
    return 0;
}

mbot_linux_serial.cpp文件

//包含对应头文件
#include "mbot_linux_serial.h"
using namespace std;//设定工作空间的名称
using namespace boost::asio;//设定工作空间的名称
//串口相关对象
//创建一个 io_service实例
boost::asio::io_service iosev;
//构造一个串口,将"/dev/ttySUB0"转移给实例iosev
boost::asio::serial_port sp(iosev, "/dev/ttyUSB0");
boost::system::error_code err;
/********************************************************
            串口发送接收相关常量、变量、共用体对象
********************************************************/
const unsigned char ender[2] = {0x0d, 0x0a};//数据尾
const unsigned char header[2] = {0x55, 0xaa};//数据头
//发送左右轮速控制速度共用体
union sendData
{
	short d;
	unsigned char data[2];
}leftVelSet,rightVelSet;
//接收数据(左轮速、右轮速、角度)共用体(-32767 - +32768)
union receiveData
{
	short d;
	unsigned char data[2];
}leftVelNow,rightVelNow,angleNow;
/********************************************************
函数功能:串口参数初始化
入口参数:无
出口参数:
********************************************************/
void serialInit()
{
    sp.set_option(serial_port::baud_rate(115200));//设置波特率
    sp.set_option(serial_port::flow_control(serial_port::flow_control::none));//流量控制
    sp.set_option(serial_port::parity(serial_port::parity::none));//奇偶校验
    sp.set_option(serial_port::stop_bits(serial_port::stop_bits::one));//停止位
    sp.set_option(serial_port::character_size(8)); //字符大小
}
/********************************************************
函数功能:将对机器人的左右轮子控制速度,打包发送给下位机
入口参数:机器人线速度、角速度
出口参数:
********************************************************/
void writeSpeed(double Left_v, double Right_v,unsigned char ctrlFlag)
{
    unsigned char buf[11] = {0};//
    int i, length = 0;

    leftVelSet.d  = Left_v;//mm/s
    rightVelSet.d = Right_v;

    // 设置消息头
    for(i = 0; i < 2; i++)
        buf[i] = header[i];             //buf[0]  buf[1]
    
    // 设置机器人左右轮速度
    length = 5;
    buf[2] = length;                    //buf[2]
    for(i = 0; i < 2; i++)
    {
        buf[i + 3] = leftVelSet.data[i];  //buf[3] buf[4]
        buf[i + 5] = rightVelSet.data[i]; //buf[5] buf[6]
    }
    // 预留控制指令
    buf[3 + length - 1] = ctrlFlag;       //buf[7]

    // 设置校验值、消息尾
    buf[3 + length] = getCrc8(buf, 3 + length);//buf[8]
    buf[3 + length + 1] = ender[0];     //buf[9]
    buf[3 + length + 2] = ender[1];     //buf[10]

    // 通过串口下发数据
    boost::asio::write(sp, boost::asio::buffer(buf));
}
/********************************************************
函数功能:从下位机读取数据
入口参数:机器人左轮轮速、右轮轮速、角度,预留控制位
出口参数:bool
********************************************************/
bool readSpeed(double &Left_v,double &Right_v,double &Angle,unsigned char &ctrlFlag)
{
    char i, length = 0;
    unsigned char checkSum;
    unsigned char buf[150]={0};
    //=========================================================
    //此段代码可以读数据的结尾,进而来进行读取数据的头部
    try
    {
        boost::asio::streambuf response; 
        boost::asio::read_until(sp, response, "\r\n",err);   
        copy(istream_iterator<unsigned char>(istream(&response)>>noskipws),
        istream_iterator<unsigned char>(),
        buf); 
    }  
    catch(boost::system::system_error &err)
    {
        ROS_INFO("read_until error");
    } 
    //=========================================================        

    // 检查信息头
    if (buf[0]!= header[0] || buf[1] != header[1])   //buf[0] buf[1]
    {
        ROS_ERROR("Received message header error!");
        return false;
    }
    // 数据长度
    length = buf[2];                                 //buf[2]

    // 检查信息校验值
    checkSum = getCrc8(buf, 3 + length);             //buf[10] 计算得出
    if (checkSum != buf[3 + length])                 //buf[10] 串口接收
    {
        ROS_ERROR("Received data check sum error!");
        return false;
    }    

    // 读取速度值
    for(i = 0; i < 2; i++)
    {
        leftVelNow.data[i]  = buf[i + 3]; //buf[3] buf[4]
        rightVelNow.data[i] = buf[i + 5]; //buf[5] buf[6]
        angleNow.data[i]    = buf[i + 7]; //buf[7] buf[8]
    }

    // 读取控制标志位
    ctrlFlag = buf[9];
    
    Left_v  =leftVelNow.d;
    Right_v =rightVelNow.d;
    Angle   =angleNow.d;
    return true;
}
/********************************************************
函数功能:获得8位循环冗余校验值
入口参数:数组地址、长度
出口参数:校验值
********************************************************/
unsigned char getCrc8(unsigned char *ptr, unsigned short len)
{
    unsigned char crc;
    unsigned char i;
    crc = 0;
    while(len--)
    {
        crc ^= *ptr++;
        for(i = 0; i < 8; i++)
        {
            if(crc&0x01)
                crc=(crc>>1)^0x8C;
            else 
                crc >>= 1;
        }
    }
    return crc;
}

mbot_linux_serial.h文件

//类似stm32程序头文件的编写规则
#ifndef MBOT_LINUX_SERIAL_H
#define MBOT_LINUX_SERIAL_H

#include <ros/ros.h>
#include <ros/time.h>
#include <geometry_msgs/TransformStamped.h>
#include <tf/transform_broadcaster.h>
#include <nav_msgs/Odometry.h>
#include <boost/asio.hpp>
#include <geometry_msgs/Twist.h>

extern void serialInit();
extern void writeSpeed(double Left_v, double Right_v,unsigned char ctrlFlag);
extern bool readSpeed(double &Left_v,double &Right_v,double &Angle,unsigned char &ctrlFlag);
unsigned char getCrc8(unsigned char *ptr, unsigned short len);

#endif

三、ROS与STM32串口通信实验及运行效果

在STM32端,下载测试工程文件后,打开keil5软件,对程序进行编译烧录;在ROS端,打开vscode软件,在/home/ubuntu/fjy_xm路径下创建catkin_serial/src文件,对工作空间进行编译,并在/catkin_serial/src文件加下添加topic_example功能包,再进行工作空间的编译,并设置环境变量source devel/setup.bash
在这里插入图片描述
将TTY转USB模块按照对应的连接关系分别与STM32和ROS端相连,确保设备连接好之后,上电,首先需要查看以下串口设备,显示如下,说明设备之间连接正常

#查看串口设备
ls -l /dev/ttyUSB*

在这里插入图片描述
其次,为串口添加超级用户权限

#添加设备权限
sudo chmod 777 /dev/ttyUSB0

然后,新建一个终端 ,启动rosmaster,即在终端中输入roscore
在原终端下运行publish_node节点

rosrun topic_example publish_node

在这里插入图片描述
在这里插入图片描述
ROS端正常接收到STM32端发送过来的信息,stm32查看ROS端发送过来的信息,可以在串口调试助手上进行查看,这样,我们就完成了一个简单的ROS与STM32之间的串口通信,为我们的ROS操作系统的开发提供了些许帮助

  • 44
    点赞
  • 417
    收藏
    觉得还不错? 一键收藏
  • 14
    评论
ROS(机器人操作系统)是一个用于开发机器人软件的框架,而STM32是一款常见的嵌入式微控制器。它们可以进行通信以实现机器人系统的控制和数据传输。 要实现ROSSTM32通信,通常有两种方式:串口通信ROS网络通信。 一种常见的方式是通过串口连接ROS主机和STM32ROS主机可以使用基于Linux系统的计算机,如Ubuntu等。使用ROS提供的串口通信库,可以在ROS主机上编写节点程序,通过串口与STM32进行数据交换。在ROS主机上,可以将STM32作为一个外设设备接入ROS系统,通过串口读取STM32发送的数据,并将ROS主机的控制指令发送给STM32。 另一种方式是通过ROS网络通信。在STM32上运行一个ROS节点,该节点通过TCP/IP协议与ROS主机上的其他ROS节点进行通信ROS节点可以通过STM32上的网卡或Wi-Fi模块连接到ROS主机所在的局域网。在ROS主机上,可以使用ROS提供的网络通信库与STM32节点进行通信,发送控制指令或接收传感器数据。 不论是串口通信还是网络通信ROSSTM32通信都需要定义消息格式。可以根据具体的需求,定义自己的ROS消息类型,包括控制指令、传感器数据等。在ROS主机上,可以使用ROS消息库来解析和处理这些消息。 总之,通过串口通信ROS网络通信,可以实现ROSSTM32通信,实现机器人系统的控制和数据传输。这种通信方式可以用于各种机器人应用,如无人车、机器人臂等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值