本文webots仓库:gitee仓库
仿真平台:Webots R2021a
硬件平台:stm32F767
- webots是一款开源易用的机器人仿真软件,支持Windows\Linux\Mac三平台,所以webots项目能具备很高的通用性,只需要具备PC机,就能去进行仿真。
- 硬件在环仿真(又名:硬件在回路仿真、HIL),是一种半实物的仿真流程,其主要特点是通过实体的处理单元来控制虚拟的被控对象,和数字孪生的概念有一定的重合。
笔者希望通过webots来进行HIL,选中实体处理单元是STM32F767的板子,而虚拟的被控对象便是window11平台在webots 2021a中创建的模型,其中webots和STM32F767之间,使用虚拟串口通信(USB-OTG)。
由于笔者也是刚接触webots,对webots了解也不多,目前只是通过webots中内置的控制器,实现了最简单的串口通信,stm32上的虚拟串口是没有实现的。
代码主要参考了windows c编写串口通信,这位博主还提供了代码及所生成exe文件的下载链接,很感谢这位博主的分享。
值得一提的是,笔者曾在B站通过关键词
webots STM32
搜索到一位UP主的视频,里面出现了ta使用stm32的按钮来遥控webots内部机器人运动方向的视频,通信方式正是USB串口。而笔者通过QQ联系到这位UP主询问是否方便分享实现源码,UP主告诉笔者,假如同是校友可以免费分享,假如不是的话则收费,985学校有优惠,985优惠价是120元,由于笔者囊中羞涩,所以没办法承担此费用。好在后来通过视频逐帧分析,笔者发现UP主也是基于上面链接中的代码移植而得到的,所以再次感谢这位博主Genven_Liang
的分享。
webot控制器有源文件和头文件组成,笔者在其中创建了一个四轮机器人,控制器名为auv_ctrl
- 头文件
auv_ctrl.hpp
代码如下
#ifndef __AUV_CTRL__
#define __AUV_CTRL__
#include <stdio.h>
#include <windows.h>
#include <string.h>
#if 1 //开启DEBUG打印
#define LOGD(...) printf(__VA_ARGS__)
#else //关闭DEBUG打印
#define LOGD(...)
#endif
#if 1 //开启ERROR打印
#define LOGE(...) printf(__VA_ARGS__)
#else //关闭ERROR打印
#define LOGE(...)
#endif
//缓冲区大小
#define BUF_SIZE 2048
#define EXIT_STR "exit"
#define I_EXIT "I exit.\r\n"
#define I_RECEIVE "I receive.\r\n"
HANDLE OpenSerial(const char ****,int baud,int byteSize,int parity,int stopBits);
#endif
- 源文件
auv_ctrl.cpp
代码如下
#include <webots/DistanceSensor.hpp>
#include <webots/Motor.hpp>
#include <webots/Robot.hpp>
#include "auv_ctrl.hpp"
#define TIME_STEP 64
using namespace webots;
int main(int argc, char **argv) {
BOOL b = FALSE;
DWORD wRLen = 0;
DWORD wWLen = 0;
char buf[BUF_SIZE] = {0};
HANDLE ***Handle = INVALID_HANDLE_VALUE;//串口句柄
//打开串口
const char **** = "***2";
***Handle = OpenSerial(***, CBR_9600, 8, NOPARITY, ONESTOPBIT);
if (INVALID_HANDLE_VALUE == ***Handle) {
LOGE("OpenSerial ***2 fail!\r\n");
return -1;
}
else{
LOGD("[auv_ctrl]Open ***2 Su***essfully!\r\n");
}
Robot *robot = new Robot();
DistanceSensor *ds[2];
char dsNames[2][10] = {"ds_right", "ds_left"};
for (int i = 0; i < 2; i++) {
ds[i] = robot->getDistanceSensor(dsNames[i]);
ds[i]->enable(TIME_STEP);
}
Motor *wheels[4];
char wheels_names[4][14] = {"wheel_motor_1", "wheel_motor_2", "wheel_motor_3", "wheel_motor_4"};
for (int i = 0; i < 4; i++) {
wheels[i] = robot->getMotor(wheels_names[i]);
wheels[i]->setPosition(INFINITY);
wheels[i]->setVelocity(0.0);
}
int avoidObstacleCounter = 0;
while (robot->step(TIME_STEP) != -1) {
double leftSpeed = -1.0;
double rightSpeed = -1.0;
if (avoidObstacleCounter > 0) {
avoidObstacleCounter--;
leftSpeed = -1.0;
rightSpeed = 1.0;
} else { // read sensors
for (int i = 0; i < 2; i++) {
if (ds[i]->getValue() < 300.0)
avoidObstacleCounter = 100;
}
}
wheels[0]->setVelocity(leftSpeed);
wheels[1]->setVelocity(rightSpeed);
wheels[2]->setVelocity(leftSpeed);
wheels[3]->setVelocity(rightSpeed);
/* 循环接收消息,收到消息后将消息内容打印并回复I_RECEIVE, 如果收到EXIT_STR就回复EXIT_STR并退出循环 */
wRLen = 0;
//读串口消息
b = ReadFile(***Handle, buf, sizeof(buf)-1, &wRLen, NULL);
if (b && 0 < wRLen) {//读成功并且数据大小大于0
buf[wRLen] = '\0';
LOGD("[RECV]%s\r\n", buf);//打印收到的数据
if (0 == strncmp(buf, EXIT_STR, strlen(EXIT_STR))) {
//回复
b = WriteFile(***Handle, TEXT(I_EXIT), strlen(I_EXIT), &wWLen, NULL);
if (!b) {
LOGE("WriteFile fail\r\n");
}
break;
}
//回复
b = WriteFile(***Handle, TEXT(I_RECEIVE), strlen(I_RECEIVE), &wWLen, NULL);
if (!b) {
LOGE("WriteFile fail\r\n");
}
}
}
/*关闭串口*/
b = CloseHandle(***Handle);
if (!b) {
LOGE("CloseHandle fail\r\n");
}
LOGD("Program Exit.\r\n");
wheels[0]->setVelocity(0);
wheels[1]->setVelocity(0);
wheels[2]->setVelocity(0);
wheels[3]->setVelocity(0);
delete robot;
return 0; // EXIT_SU***ESS
}
//打开串口
HANDLE OpenSerial(const char ****, //串口名称,如***1,***2
int baud, //波特率:常用取值:CBR_9600、CBR_19200、CBR_38400、CBR_115200、CBR_230400、CBR_460800
int byteSize, //数位大小:可取值7、8;
int parity, //校验方式:可取值NOPARITY、ODDPARITY、EVENPARITY、MARKPARITY、SPACEPARITY
int stopBits) //停止位:ONESTOPBIT、ONE5STOPBITS、TWOSTOPBITS;
{
DCB dcb;
BOOL b = FALSE;
***MTIMEOUTS ***mTimeouts;
HANDLE ***Handle = INVALID_HANDLE_VALUE;
//打开串口
***Handle = CreateFile(***, //串口名称
GENERIC_READ | GENERIC_WRITE, //可读、可写
0, // No Sharing
NULL, // No Security
OPEN_EXISTING,// Open existing port only
FILE_ATTRIBUTE_NORMAL, // Non Overlapped I/O
NULL); // Null for ***m Devices
if (INVALID_HANDLE_VALUE == ***Handle) {
LOGE("CreateFile fail\r\n");
return ***Handle;
}
// 设置读写缓存大小
b = Setup***m(***Handle, BUF_SIZE, BUF_SIZE);
if (!b) {
LOGE("Setup***m fail\r\n");
}
//设定读写超时
***mTimeouts.ReadIntervalTimeout = MAXDWORD;//读间隔超时
***mTimeouts.ReadTotalTimeoutMultiplier = 0;//读时间系数
***mTimeouts.ReadTotalTimeoutConstant = 0;//读时间常量
***mTimeouts.WriteTotalTimeoutMultiplier = 1;//写时间系数
***mTimeouts.WriteTotalTimeoutConstant = 1;//写时间常量
b = Set***mTimeouts(***Handle, &***mTimeouts); //设置超时
if (!b) {
LOGE("Set***mTimeouts fail\r\n");
}
//设置串口状态属性
Get***mState(***Handle, &dcb);//获取当前
dcb.BaudRate = baud; //波特率
dcb.ByteSize = byteSize; //每个字节有位数
dcb.Parity = parity; //无奇偶校验位
dcb.StopBits = stopBits; //一个停止位
b = Set***mState(***Handle, &dcb);//设置
if (!b) {
LOGE("Set***mState fail\r\n");
}
return ***Handle;
}
最后使用Configure Virtual Serial Port Driver V6.9
这个软件进行调试,这个软件可以生成虚拟端口,方便进行***口之间的通信测试。
测试效果:
从图片可以看到,虚拟的外部***口连接上webots所打开的***口,webots这边能收到外部发来的信息,也能给外部端口发去应答信息,由此实现了HIL的通信基础。
总结与展望
- 实现了HIL环节中的通信基础
- 后续会对串口代码进行进一步的封装,将参考以下链接:
- windows c编写串口通信
- ChatGPT win下C语言串口通信
- 后续会在STM32上部署好虚拟串口,并验证通信速率是否能满足webots的控制条件。
网上资料提到,虚拟串口可以跑到921600波特率,假如使用8N1的串口数据帧格式,那么速率相当于92.16kByte/s,照这个速率应该是能满足需求的。
- 3D模型的导入和使用
- 参考链接: 【开源-教程】Webots运动学仿真,webots模型导入最好方法,SolidWorks导出wrl【四足机器人足端运动学仿真】
- Win11+VScode+MinGW+Cmake+Eigen环境搭建
- 参考链接: Windows10下配置VSCode、Mingw、Cmake、Eigen、OpenCV环境