身處機器人行業,不想一直只做低端的單片機控制,老是待在舒適區,所以一直都想學一下ROS系統,但看了幾個月資料后,感覺還是雲里霧里,似懂非懂,感念似乎都很清楚,但要實際去做,卻又感覺無從下手。
當然之前看的資料也沒白看,系統安裝、基本概念、基本操作都得心里有底,因為Linux基礎並不是很好,所以還是比較畏懼敲命令的方式的,不管在Linux系統上做什么,我都還是很樂意去找相應IDE,ROS也不例外,官網上教程全是命令行,痛苦了一陣子之后,實在覺得沒必要回到原始人狀態,寫代碼就不用說了,光是那一堆依賴都能讓人崩潰,於是網上一頓搜,發現ROS開發也是有IDE的,那個欣喜之情啊,簡直溢於言表;馬上下載安裝上了,用了一段時間,發現這工具有很多bug,有時候莫名其妙的錯誤,但無妨,至少不用再自己去手動添加依賴了,我心足以!
#include <ros/ros.h>
#include <serial/serial.h> //ROS已經內置了的串口包
#include <std_msgs/String.h>
#include <std_msgs/Empty.h>
#include <stm32f407/tdata.h>
#include "std_msgs/UInt8.h"
#include "std_msgs/UInt8MultiArray.h"
serial::Serial ser; //聲明串口對象
//回調函數
void datasend_callback(const stm32f407::tdata::ConstPtr &frm)
{
int len = frm->data.size();
printf("Send:%02d 0x", len);
for (int i = 0; i < len; i++)
{
// printf("%02X ", frm->data[i]);
printf("%02X ", frm->data.at(i));
}
printf("\r\n");
ser.write(frm->data); //發送串口數據
}
int main (int argc, char** argv)
{
//初始化節點
ros::init(argc, argv, "serial_readwrite_node");
//聲明節點句柄
ros::NodeHandle nh;
static int len;
stm32f407::tdata RecvData;
//訂閱主題,並配置回調函數
ros::Subscriber send_sub = nh.subscribe("datasend", 1000, datasend_callback);
//發布主題
ros::Publisher read_pub = nh.advertise<stm32f407::tdata>("datarecv", 1000);
try
{
//設置串口屬性,並打開串口
ser.setPort("/dev/ttyUSB1");
ser.setBaudrate(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 initialized");
}
else
{
return -1;
}
//指定循環的頻率
ros::Rate loop_rate(10);
while(ros::ok())
{
len = ser.available();
if(len>0)
{
std_msgs::UInt8MultiArray serial_data;
// len = ser.available();
ser.read(serial_data.data,len) ;
printf("Recv:%02d 0x", len);
RecvData.data.clear();//清除上一次的數據
for (size_t i = 0; i < len; i++)
{
// ROS_INFO("%02X", serial_data.data[i]);
printf("%02X ", serial_data.data[i]);
RecvData.data.push_back(serial_data.data[i]);
}
printf("\r\n");
read_pub.publish(RecvData);
}
//處理ROS的信息,比如訂閱消息,並調用回調函數
ros::spinOnce();
loop_rate.sleep();
}
}
其中操作非常簡單,定義了一個訂閱句柄,用來接收其他節點發布的數據,然后在回調當中,用串口直接發送出去,另外定義一個發布句柄,在主循環不斷掃描串口,讀取數據,
如果有讀到數據,則發布出去,其他節點訂閱后既可查看單片機上發的數據;
其中無論訂閱還是發布都使用到了一個自定義消息,用來規定要傳輸數據的格式,添加Msg文件夾后,再添加msg文件,內容如下:

這便是兩個話題對應消息格式;
上面這個節點主要用作直接對硬件串口進行收發,它只管收發,並不理會收發的數據是什么,具體流程為,如果接收其他節點發布的消息,則將之原樣發送到串口,如何
掃描到串口有數據,則將讀到的數據發布到話題上;
所以如果我們要發特定數據並對接收到的做數據處理,則需添加另外一個節點,名稱定為datapro,內容如下:
#include "ros/ros.h"
#include "std_msgs/String.h"
#include <sstream>
#include <stm32f407/tdata.h>
#include "CRC.h"
//回調函數
void datarecv_callback(const stm32f407::tdata::ConstPtr &frm)
{
int len = frm->data.size();
// std::cout << "frm->data.size=" << cnt << std::endl;
// ROS_INFO_STREAM("Writing to serial port" << msg->data);
// ROS_INFO_STREAM("Send: " << frm->data);
// printf("Recv:%ld 0x", cnt);
printf("Recv:%02d 0x", len);
for (int i = 0; i < len; i++)
{
// printf("%02X ", frm->data[i]);
printf("%02X ", frm->data.at(i));
}
printf("\r\n");
}
int main(int argc, char **argv)
{
ros::init(argc, argv, "datapro");
ros::NodeHandle nh;
//訂閱主題,並配置回調函數
ros::Subscriber recv_sub = nh.subscribe("datarecv", 1000, datarecv_callback);
ros::Publisher send_pub = nh.advertise<stm32f407::tdata>("datasend", 1000);
ros::Rate loop_rate(1);//10hz
int len=0;
stm32f407::tdata SendData;
CRC cc_crc;
//復位 A6 B7 01 02 04 00 B7 1B 02 A6 B7 01 02 04 05 B7 E4 F7
uint8_t Buffer[9] ={0xA6, 0xB7, 0x01, 0x02, 0x04, 0x05, 0xB7, 0xE4, 0xF7};
uint16_t CrcSum=0;
CrcSum = cc_crc.CRC16_ccitt(Buffer,0,7);
// printf("0x%04X\r\n",CrcSum);
Buffer[7] = (uint8_t)(CrcSum>>8);
Buffer[8] = (uint8_t)CrcSum;
SendData.data.clear();
for (size_t i = 0; i < 9; i++)
{
SendData.data.push_back(Buffer[i]);
}
// SendData.data.push_back(0xA6);
// SendData.data.push_back(0xB7);
// SendData.data.push_back(0x01);
// SendData.data.push_back(0x02);
// SendData.data.push_back(0x04);
// SendData.data.push_back(0x05);
// SendData.data.push_back(0xB7);
// SendData.data.push_back(0xE4);
// SendData.data.push_back(0xF7);
// frame_pub.publish(SendData);
while (ros::ok())
{
len = SendData.data.size();
printf("Send:%02d 0x", len);
for (int i = 0; i < len; i++)
{
// printf("%02X ", SendData.data[i]);
printf("%02X ", SendData.data.at(i));
}
printf("\r\n");
send_pub.publish(SendData);
ros::spinOnce();
loop_rate.sleep();
}
return 0;
}
該節點的作用就是發布特定格式的數據到發送話題上,以及接收上一個節點發布的話題消息;邏輯很簡單,其中需要注意的是在添加CRC校驗時,添加:C++類,輸入類名稱后
會有幾個選項,如下:
之前一直沒搞懂他們的區別,都是選的“加入到新的可執行文件中”,如何選這一項,則改類的代碼會單獨形成一個可執行文件,在運行時需要單獨開啟,所以如果兩個
功能相對獨立,則可以選一項,但是如果代碼是調用關系,比如上面的CRC類實際上是被datapro調用的,所以這里我們選擇第二項會更好,運行時我們只需要驅動datapro就
可以使用CRC類功能;
另外Studio工具有一個bug是,添加C++類時,會報錯,提示找不到頭文件,這問題折騰了很久,后來發現需要把自動生成的頭文件,提到上一級目標才可編譯通過,即將
頭文件直接到include文件夾下。
另一個小提醒就是,每次添加新文件或新類時,最好點一下刷新按鈕,有時候看不到添加的類文件,需要手動刷新一下才會出來;