ROS与STM32串口通信详解与实践

前言:

最近在学习ROS想通过ROS控制小车运动,因为想要降低成本,所以想在PC端运行ROS然后通过无线模块与单片机进行通信,一开始想要用的是WiFi透传使用TCP协议,但发现一开始建立连接太麻烦了,然后换成UDP协议就好很多,但后来因为办公室路由器一直有问题,导致我虚拟机一直上不了网,索性就直接使用无线模块通过虚拟串口与stm32通信了.

1.先来看看UDP协议传输

使用UDP其实挺方便的,数据也没有什么延迟,但是因为需要WiFi内网透传,学校校园网不能透传,然后只能自己开热点,然后又需要设置静态IP,搞得我虚拟机一直用不了网,所以才换成无线串口模块.

我这里是通过订阅一个速度话题/cmd_vel,然后通过WiFi内网发到单片机

#include <ros/ros.h>
#include <geometry_msgs/Twist.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstring>  // for memset
#include <string>
#include <unistd.h> // for close()

// 全局变量声明
int udp_socket;
struct sockaddr_in dest_addr;

void v_hui(const geometry_msgs::Twist::ConstPtr& msg) {
    // 打印接收到的速度指令
    ROS_INFO("Received velocity: linear.x=%f, angular.z=%f", 
             msg->linear.x, msg->angular.z);

    // 格式化为字符串
    std::string msg_str = "linear.x=" + std::to_string(msg->linear.x) +
                            "linear.y=" + std::to_string(msg->linear.y)+
                          ", angular.z=" + std::to_string(msg->angular.z);

    // 发送UDP数据包
    ssize_t bytes_sent = sendto(udp_socket, msg_str.c_str(), msg_str.size(), 0,
                               (struct sockaddr*)&dest_addr, sizeof(dest_addr));
    if (bytes_sent < 0) {
        ROS_ERROR("Failed to send UDP packet: %s", strerror(errno));
    }
}

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

    // 初始化 UDP 套接字
    udp_socket = socket(AF_INET, SOCK_DGRAM, 0);
    if (udp_socket < 0) {
        ROS_ERROR("Failed to create UDP socket");
        return -1;
    }

    // 设置目标地址
    memset(&dest_addr, 0, sizeof(dest_addr));
    dest_addr.sin_family = AF_INET;
    dest_addr.sin_port = htons(8080);  // 目标端口
    inet_pton(AF_INET, "192.168.188.195", &dest_addr.sin_addr);  // 本地地址

    // 订阅 /cmd_vel 话题
    ros::Subscriber sub = nh.subscribe("/cmd_vel", 10, v_hui);

    ROS_INFO("UDP publisher node started, sending data to 127.0.0.1:8080");

    // 使用 ros::spin() 替代 while 循环
    ros::spin();

    // 关闭套接字
    close(udp_socket);

    return 0;
}

现象:

使用UDP进行数据透传,数据还是很丝滑的,也没有出现丢包的现象

测试

2.通过虚拟串口

通过loar模块,因为我这里只有loar模块,就用loar模块测试了,实际换成蓝牙,2.4G,星闪都可以

<1>首先进行串口初始化

这里我通过std::stringstream ss;
    ss.precision(2);
    ss << "X" << x << "Y" << y << "Z" << z << "\n";

将数据构造成与stm32端接收数据相对应,实际情况可修改.

#include <ros/ros.h>
#include <serial/serial.h>
#include <sstream>
#include <stdexcept>
#include <geometry_msgs/Twist.h>

// 串口配置参数
    //std::string port = "/dev/tzx"; // 根据实际设备修改
    std::string port = "/dev/ttyACM0"; // 根据实际设备修改
    unsigned long baud_rate = 115200;    // 根据设备要求修改

// 初始化串口对象
    serial::Serial ser;
class serial_usb{
public:
    serial_usb(std::string port,unsigned long baud_rate);
    ~serial_usb(){
    // 关闭串口
    ser.close();
    }

    void serial_usb_write(double _x,double _y,double _z);

private:
    double x,y,z;

};

void v_black(const geometry_msgs::Twist::ConstPtr &msg);

void serial_usb_write_(double _x,double _y,double _z){
    // 构造发送数据
    double x = _x, y = _y, z = _z;
    std::stringstream ss;
    ss.precision(2);
    ss << "X" << x << "Y" << y << "Z" << z << "\n";
    std::string send_data = ss.str();
    // 发送数据
    try {
        ser.write(send_data);
        ROS_INFO_STREAM("发送数据: " << send_data);
    } catch (serial::IOException& e) {
        ROS_ERROR_STREAM("发送失败: " << e.what());
    }
}
serial_usb::serial_usb(std::string port,unsigned long baud_rate){
         try {
        ser.setPort(port);
        ser.setBaudrate(baud_rate);
        serial::Timeout to = serial::Timeout::simpleTimeout(2000);
        ser.setTimeout(to);
        ser.open();
    } catch (serial::IOException& e) {
        ROS_ERROR_STREAM("无法打开串口: " << e.what());
        
    }

    if (ser.isOpen()) {
        ROS_INFO_STREAM("串口初始化成功");
    } else {
        ROS_ERROR_STREAM("串口初始化失败");
    }

    // // 构造发送数据
    // double x = 1.22, y = 2.11, z = 3.88;
    std::stringstream ss;
    ss.precision(2);
    ss << "hello ros" << "\n";
    std::string send_data = ss.str();

    // 发送数据
    try {
        ser.write(send_data);
        ROS_INFO_STREAM("发送数据: " << send_data);
    } catch (serial::IOException& e) {
        ROS_ERROR_STREAM("发送失败: " << e.what());
    }
    }

void serial_usb:: serial_usb_write(double _x,double _y,double _z){
    // 构造发送数据
    double x = _x, y = _y, z = _z;
    std::stringstream ss;
    ss.precision(2);
    ss << "X" << x << "Y" << y << "Z" << z << "\n";
    std::string send_data = ss.str();
    // 发送数据
    try {
        ser.write(send_data);
        ROS_INFO_STREAM("发送数据: " << send_data);
    } catch (serial::IOException& e) {
        ROS_ERROR_STREAM("发送失败: " << e.what());
    }
}


void v_black(const geometry_msgs::Twist::ConstPtr &msg){
    
    // 打印接收到的速度指令
    ROS_INFO("Received velocity: linear.x=%f, linear.x=%f,angular.z=%f", 
             msg->linear.x, msg->linear.y,msg->angular.z);
    //ptr.serial_usb_write(msg->linear.x, msg->linear.y,msg->angular.z);
    serial_usb_write_(msg->linear.x, msg->linear.y,msg->angular.z);

   
}

<2>初始化ros节点

跟上面一样通过订阅速度话题/cmd_vel,然后通过串口发送到下位机

这里遇到一个问题:

就是当时我使用ros::spin();时,stm32就接收不到数据,但是不加ros::spin();就能正常接收数据,后来再网上查找资料也没有找到问题,最后是通过换了一个UBS转TTL模块,然后就正常了

int main(int argc, char** argv) {
    //显示中文
    setlocale(LC_ALL,"");

    ros::init(argc, argv, "vle_usb_node");
    ros::NodeHandle nh;

    ros::Subscriber sub = nh.subscribe("/cmd_vel",1000,v_black);


    serial_usb serial_stm32(port,baud_rate);
   



// ros::Rate rate(50);  // 提高循环频率
// while (ros::ok()) {
//     ros::spinOnce();  // 处理回调函数(如串口接收)
//     rate.sleep();     // 控制循环频率
// }
while(ros::ok()){
     ros::spin();
}
   
    // 关闭串口
    ser.close();

    return 0;
}

3.stm32下位机端

这里是通过串口空闲中断,然后通过sscanf进行串口数据解析,然后再将收到速度传送给电机

#define USART1_RX_BUFFER_SIZE 1024
uint16_t usart1_data_len;
uint8_t usart1_rx_buf[USART1_RX_BUFFER_SIZE];
float X = 0.0f, Y = 0.0f,Z = 0.0f;

void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size) {
	
    if (huart == &huart1) {
        // 获取帧长
        usart1_data_len = Size;
        sscanf((char*)usart1_rx_buf,"X%fY%fZ%f",&X,&Y,&Z);
		//HAL_UART_Transmit(&huart1,usart1_rx_buf,usart1_data_len,100);
		HAL_UART_Transmit(&huart4,usart1_rx_buf,usart1_data_len,100);
		USART1_DMA_printf("%.2f,%.2f,%.2f\r\n",X,Y,Z);
		set_motion_state((int16_t)X*10,(int16_t)Y*10,(int16_t)Z*10);
		//使dma从头存数据
		huart->hdmarx->Instance -> NDTR =  1024;
		//重新打开dma接收,ILDE中断
		HAL_UARTEx_ReceiveToIdle_DMA(&huart1,usart1_rx_buf,1024);

    }
}

现象的话,stm32端可以正常解析到数据,但是loar模块的延迟太高,明显可以感受到,ros下发数据,小车运动都会有延迟

ROS与stm32测试

完整代码:

#include <ros/ros.h>
#include <serial/serial.h>
#include <sstream>
#include <stdexcept>
#include <geometry_msgs/Twist.h>

// 串口配置参数
    //std::string port = "/dev/tzx"; // 根据实际设备修改
    std::string port = "/dev/ttyACM0"; // 根据实际设备修改
    unsigned long baud_rate = 115200;    // 根据设备要求修改

// 初始化串口对象
    serial::Serial ser;
class serial_usb{
public:
    serial_usb(std::string port,unsigned long baud_rate);
    ~serial_usb(){
    // 关闭串口
    ser.close();
    }

    void serial_usb_write(double _x,double _y,double _z);

private:
    double x,y,z;

};

void v_black(const geometry_msgs::Twist::ConstPtr &msg);

void serial_usb_write_(double _x,double _y,double _z){
    // 构造发送数据
    double x = _x, y = _y, z = _z;
    std::stringstream ss;
    ss.precision(2);
    ss << "X" << x << "Y" << y << "Z" << z << "\n";
    std::string send_data = ss.str();
    // 发送数据
    try {
        ser.write(send_data);
        ROS_INFO_STREAM("发送数据: " << send_data);
    } catch (serial::IOException& e) {
        ROS_ERROR_STREAM("发送失败: " << e.what());
    }
}

int main(int argc, char** argv) {
    //显示中文
    setlocale(LC_ALL,"");

    ros::init(argc, argv, "vle_usb_node");
    ros::NodeHandle nh;

    ros::Subscriber sub = nh.subscribe("/cmd_vel",1000,v_black);

    serial_usb serial_stm32(port,baud_rate);
   

// ros::Rate rate(50);  // 提高循环频率
// while (ros::ok()) {
//     ros::spinOnce();  // 处理回调函数(如串口接收)
//     rate.sleep();     // 控制循环频率
// }
while(ros::ok()){
     ros::spin();
}
   
    // 关闭串口
    ser.close();

    return 0;
}

serial_usb::serial_usb(std::string port,unsigned long baud_rate){
         try {
        ser.setPort(port);
        ser.setBaudrate(baud_rate);
        serial::Timeout to = serial::Timeout::simpleTimeout(2000);
        ser.setTimeout(to);
        ser.open();
    } catch (serial::IOException& e) {
        ROS_ERROR_STREAM("无法打开串口: " << e.what());
        
    }

    if (ser.isOpen()) {
        ROS_INFO_STREAM("串口初始化成功");
    } else {
        ROS_ERROR_STREAM("串口初始化失败");
    }

    // // 构造发送数据
    // double x = 1.22, y = 2.11, z = 3.88;
    std::stringstream ss;
    ss.precision(2);
    ss << "hello ros" << "\n";
    std::string send_data = ss.str();

    // 发送数据
    try {
        ser.write(send_data);
        ROS_INFO_STREAM("发送数据: " << send_data);
    } catch (serial::IOException& e) {
        ROS_ERROR_STREAM("发送失败: " << e.what());
    }
    }

void serial_usb:: serial_usb_write(double _x,double _y,double _z){
    // 构造发送数据
    double x = _x, y = _y, z = _z;
    std::stringstream ss;
    ss.precision(2);
    ss << "X" << x << "Y" << y << "Z" << z << "\n";
    std::string send_data = ss.str();
    // 发送数据
    try {
        ser.write(send_data);
        ROS_INFO_STREAM("发送数据: " << send_data);
    } catch (serial::IOException& e) {
        ROS_ERROR_STREAM("发送失败: " << e.what());
    }
}

void v_black(const geometry_msgs::Twist::ConstPtr &msg){
    
    // 打印接收到的速度指令
    ROS_INFO("Received velocity: linear.x=%f, linear.x=%f,angular.z=%f", 
             msg->linear.x, msg->linear.y,msg->angular.z);
    //ptr.serial_usb_write(msg->linear.x, msg->linear.y,msg->angular.z);
    serial_usb_write_(msg->linear.x, msg->linear.y,msg->angular.z);

   
}

最后:

使用串口前一定要给串口添加权限:

sudo chmod 666 /dev/ttyUSB0

参考资料:

极度优雅的用stm32串口接收并分析不定长数据的方法(可用于发送和接收浮点数)_stm32单片机如何解析数据-CSDN博客

作者:@苏瑾

物联沃分享整理
物联沃-IOTWORD物联网 » ROS与STM32串口通信详解与实践

发表回复