话不多说,先看视频。没有声音的视频,大家可以来盲猜一下这是啥~

怎么说呢,一直在玩ROS和Arduino搭档的开发,rosserial、ros_arduino_bridge、StandardFirmata、Modbus,这些都玩了,但是ROS的无线方案确实是少。

之前考虑使用Arduino Nano+OLED屏幕的方式来做一个ROS周边的显示模块,然后碰到了两个内存大户——rosserial和OLED的库文件,结果成功的炸掉的我的想法。

其实有替代方案可以用,但是我不想~

使用rosserial的话,我们只需要在ROS系统那里启动下rosserial_python的serial_node.py就可以了,还没有过多的程序。最主要的是将Arduino当作ROS节点来进行开发,这个确实是舒服~

有想对rosserial和Arduino开发进行学习的伙伴们,大家可以来看下这个链接rosserial和Arduino

后续我用Arduino Mega实现的效果,当时用一块Arduino Mega或者是Atmega2560的芯片就来驱动个屏幕,实属有些败家。当时的代码是这样的。

#include <ros.h>	//引用ros头文件
#include <std_msgs/String.h>	//引用std_msgs的String类型的数据
	
#include <Wire.h>	//引用IIC协议库
#include <Adafruit_GFX.h>	//引用字体取模库
#include <Adafruit_SSD1306.h>	//引用SH1106驱动库,1.3寸OLED驱动时SH1106

#define OLED_RESET 4	//定义OLED为4针引脚模式

Adafruit_SSD1306 display(OLED_RESET);	//实例化OLED显示功能

ros::NodeHandle nh;	//声明ros句柄

/************************************************************
*函数名称:oled_callback									*
*函数类型:void												*
*函数功能:当订阅到oled的话题,在OLED屏幕显示订阅到的msg	*
*输入参数:msg,订阅到的数据								*
*输出参数:无												*
************************************************************/
void oled_callback(const std_msgs::String& msg)
{
  display.clearDisplay();	//清空当前屏幕内容
  display.setTextSize(1);	//设置显示字体的大小
  display.setTextColor(WHITE);	//设置显示字体的样式
  display.setCursor(0,0);	//设置显示字体的坐标
  display.println(msg.data);	//设置显示字体的内容
  display.display();	//显示
}

//实例化订阅,订阅的话题名为oled,话题消息类型为std_msgs::String,回调函数为oled_callback
ros::Subscriber<std_msgs::String> sub("oled", &oled_callback );	

/****************************
*函数名称:setup			*
*函数类型:void				*
*函数功能:初始化相关硬件	*
*输入参数:无				*
*输出参数:无				*
****************************/
void setup()
{
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);	//初始化OLED屏幕,IIC默认地址0x3C
  
  display.display();	//显示
  delay(2000);	//延时2000ms
  display.clearDisplay();	//清空当前屏幕内容

  display.setTextSize(1);	//设置显示字体的大小
  display.setTextColor(WHITE);	//设置显示字体的样式
  display.setCursor(0,0);	//设置显示字体的坐标
  display.println("Hello, ROS!");	//设置显示字体的内容
  display.display();	//显示
  delay(2000);	//延时2000ms
  display.clearDisplay();	//清空当前屏幕内容
  
  nh.initNode();	//初始化节点
  nh.subscribe(sub);	//订阅
}

/************************************
*函数名称:loop						*
*函数类型:void						*
*函数功能:程序执行内容,循环执行	*
*输入参数:无						*
*输出参数:无						*
************************************/
void loop()
{
  nh.spinOnce();	//在Arduino的loop函数,调用nh.spinOnce(),这样所有的ROS回调函数就会被处理
  delay(1);	//延时1ms
}

大家可以找个Arduino Mega+OLED屏幕来测试以下,这段代码的功能是在开机的时候在平面显示一个“Hello ROS!”,然后就是订阅一个叫做oled的话题,这个话题是std_msgs/String类型的,其实就是一串字符串,将这个字符串的内容在OLED屏幕进行显示。

而我们在ROS下需要执行的只有三个步骤:

roscore
#启动ROS Master

rosrun rosserial_python serial_node.py
#启动rosserial

rostopic pub  /oled std_msgs/String "Hello GYHome!"
#发送一个叫做oled的话题,类型是std_msgs/String,内容是"Hello GYHome!"

其实也没啥东西,和咱们C++写ROS节点订阅的过程一样,甚至还比那玩意简单!就是用块Arduino Mega只是实现这个功能,实属败家~

前两天看到瑞雷老师的一篇博文《ESP8266和ROS调试一些问题汇总》,通过WiFi局域网实现rosserial节点通讯,我刚刚的那个视频就是干这个的!

这个代码是rosserial_arduino里面的示例代码——Esp8266HelloWorld,大家可以自行下载尝试以下,操作步骤请看视频。代码内容如下:

/*
 * rosserial Publisher Example
 * Prints "hello world!"
 * This intend to connect to a Wifi Access Point
 * and a rosserial socket server.
 * You can launch the rosserial socket server with
 * roslaunch rosserial_server socket.launch
 * The default port is 11411
 *
 */
#include <ESP8266WiFi.h>
#include <ros.h>
#include <std_msgs/String.h>

//连接到指定WiFi
const char* ssid     = "Rugels.Dix";
const char* password = "Qing990127";
//设置ROS Master所在电脑的IP地址
IPAddress server(192,168,43,51);
// rosserial tcp使用11411端口
const uint16_t serverPort = 11411;

ros::NodeHandle nh;
// Make a chatter publisher
std_msgs::String str_msg;
ros::Publisher chatter("chatter", &str_msg);

// Be polite and say hello
char hello[13] = "hello world!";

void setup()
{
  // Use ESP8266 serial to monitor the process
  Serial.begin(115200);
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  // Connect the ESP8266 the the wifi AP
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());

  // Set the connection to rosserial socket server
  nh.getHardware()->setConnection(server, serverPort);
  nh.initNode();

  // Another way to get IP
  Serial.print("IP = ");
  Serial.println(nh.getHardware()->getLocalIP());

  // Start to be polite
  nh.advertise(chatter);
}

void loop()
{

  if (nh.connected()) {
    Serial.println("Connected");
    // Say hello
    str_msg.data = hello;
    chatter.publish( &str_msg );
  } else {
    Serial.println("Not Connected");
  }
  nh.spinOnce();
  // Loop exproximativly at 1Hz
  delay(1000);
}

大家需要修改的地方就算WiFi名称及密码,你运行ROS电脑的IP,端口三11411,这个无需修改。

我的WiFi名称是Rugels.Dix,密码是Qing990127,电脑所处的IP地址是192.168.43.51。

然后就是运行,运行的步骤如下。

roscore
#启动ROS Master

rosrun rosserial_python serial_node.py tcp
#启动rosserial_python功能包里面的serial_node节点,后缀参数tcp,说明是tcp的rosserial

这两行执行成功的话,它会是这样的。

然后大家就可以rostopic加rqt一块来造作这些数据就可以了的。

官方提供的Demo比较简单,只是发布一个名字是chatter的话题,类型是std_msgs/String,内容是"Hello World!"。这里不做详细描述,大家自行解读代码和在ROS下操作一波,很轻松就可以看到数据。

OK!我们继续,做了这俄国验证之后,我发现使用ESP8266来操作这个还是有希望的。所以我又萌生的一个想法~

ESP8266开发板+OLED屏幕+PCA9685舵机驱动板+旋转编码器中断+按键操作+5V 2A的UPS,最终等于一个超级版本的Double_Arm,而且还是自带示教和按键操作的那种。

然后事实证明我想多了!!!

ESP8266不兼容SH1106的OLED驱动,github找的API研究了一下午还是没有搞懂,走u8g2库的话~别给我提这个库!

ESP8266不兼容PCA9685,github找到支持ESP8266的,代码好神奇(所以拜拜了您勒)~

旋转编码器中断和按键,嗯,这俩没啥问题~

然后就是5V 2A的UPS,这俩也没啥问题!!

所以,继续干吧~

经历了无数次的写(抄)代码之后,终于有了一个版本~

ESP8266不支持SH1106但是支持SSD1306,所以我搞了一块0.96存的OLED屏幕(心里还是想用1.3寸的,主要是大!QAQ)

来贴个下午的代码吧,编译通过了,手里没0.96的OLED屏幕,所以只能等快递到了再继续调试吧!就先这样吧~

手里有ESP8266+0.96寸OLED屏幕的伙伴可以先来试一把~

#include <ESP8266WiFi.h>

#include <ros.h>
#include <std_msgs/String.h>

#include <Wire.h>  //引用IIC协议库
#include <Adafruit_GFX.h> //引用字体取模库
#include <Adafruit_SSD1306.h> //引用SH1106驱动库,1.3寸OLED驱动时SH1106

#define OLED_RESET 4  //定义OLED为4针引脚模式

const char* ssid     = "Rugels.Dix";
const char* password = "Qing990127";
IPAddress server(192,168,43,51);
const uint16_t serverPort = 11411;

ros::NodeHandle nh;

Adafruit_SSD1306 display(OLED_RESET); //实例化OLED显示功能

void oled_callback(const std_msgs::String& msg)
{
  display.clearDisplay();  //清空当前屏幕内容
  display.setTextSize(1); //设置显示字体的大小
  display.setTextColor(WHITE);  //设置显示字体的样式
  display.setCursor(0,0); //设置显示字体的坐标
  display.println(msg.data);  //设置显示字体的内容
  display.display();  //显示
}

ros::Subscriber<std_msgs::String> sub("oled", &oled_callback );

void setup()
{
  Serial.begin(115200);
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());

  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);  //初始化OLED屏幕,IIC默认地址0x3C
  
  display.display();  //显示
  delay(2000);  //延时2000ms
  display.clearDisplay(); //清空当前屏幕内容

  display.setTextSize(1); //设置显示字体的大小
  display.setTextColor(WHITE);  //设置显示字体的样式
  display.setCursor(0,0); //设置显示字体的坐标
  display.println("Hello, ROS!"); //设置显示字体的内容
  display.display();  //显示
  delay(2000);  //延时2000ms
  display.clearDisplay(); //清空当前屏幕内容

  nh.getHardware()->setConnection(server, serverPort);
  nh.initNode();

  Serial.print("IP = ");
  Serial.println(nh.getHardware()->getLocalIP());

  nh.subscribe(sub);  //订阅
}

void loop()
{

  if (nh.connected()) {
    Serial.println("Connected");
  } else {
    Serial.println("Not Connected");
  }
  nh.spinOnce();
  delay(1000);
}