这一节就有些乱了……
机器人如何走出一个正方形的轨迹呢?
这里,采用了官方例程中:
- //draw_square
-
- #include
- #include
- #include
- #include
- #include
-
- turtlesim::PoseConstPtr g_pose;
- turtlesim::Pose g_goal;
-
- enum State
- {
- FORWARD,
- STOP_FORWARD,
- TURN,
- STOP_TURN,
- };
-
- State g_state = FORWARD;
- State g_last_state = FORWARD;
- bool g_first_goal_set = false;
-
- #define PI 3.141592
-
- void poseCallback(const turtlesim::PoseConstPtr& pose)
- {
- g_pose = pose;
- }
-
- bool hasReachedGoal()
- {
- return fabsf(g_pose->x - g_goal.x) < 0.1 && fabsf(g_pose->y - g_goal.y) < 0.1 && fabsf(g_pose->theta - g_goal.theta) < 0.01;
- }
-
- bool hasStopped()
- {
- return g_pose->angular_velocity < 0.0001 && g_pose->linear_velocity < 0.0001;
- }
-
- void printGoal()
- {
- ROS_INFO("New goal [%f %f, %f]", g_goal.x, g_goal.y, g_goal.theta);
- }
-
- void commandTurtle(ros::Publisher twist_pub, float linear, float angular)
- {
- geometry_msgs::Twist twist;
- twist.linear.x = linear;
- twist.angular.z = angular;
- twist_pub.publish(twist);
- }
-
- void stopForward(ros::Publisher twist_pub)
- {
- if (hasStopped())
- {
- ROS_INFO("Reached goal");
- g_state = TURN;
- g_goal.x = g_pose->x;
- g_goal.y = g_pose->y;
- g_goal.theta = fmod(g_pose->theta + PI/2.0, 2*PI);
- printGoal();
- }
- else
- {
- commandTurtle(twist_pub, 0, 0);
- }
- }
-
- void stopTurn(ros::Publisher twist_pub)
- {
- if (hasStopped())
- {
- ROS_INFO("Reached goal");
- g_state = FORWARD;
- g_goal.x = cos(g_pose->theta) * 2 + g_pose->x;
- g_goal.y = sin(g_pose->theta) * 2 + g_pose->y;
- g_goal.theta = g_pose->theta;
- printGoal();
- }
- else
- {
- commandTurtle(twist_pub, 0, 0);
- }
- }
-
-
- void forward(ros::Publisher twist_pub)
- {
- if (hasReachedGoal())
- {
- g_state = STOP_FORWARD;
- commandTurtle(twist_pub, 0, 0);
- }
- else
- {
- commandTurtle(twist_pub, 1.0, 0.0);
- }
- }
-
- void turn(ros::Publisher twist_pub)
- {
- if (hasReachedGoal())
- {
- g_state = STOP_TURN;
- commandTurtle(twist_pub, 0, 0);
- }
- else
- {
- commandTurtle(twist_pub, 0.0, 0.4);
- }
- }
-
- void timerCallback(const ros::TimerEvent&, ros::Publisher twist_pub)
- {
- if (!g_pose)
- {
- return;
- }
-
- if (!g_first_goal_set)
- {
- g_first_goal_set = true;
- g_state = FORWARD;
- g_goal.x = cos(g_pose->theta) * 2 + g_pose->x;
- g_goal.y = sin(g_pose->theta) * 2 + g_pose->y;
- g_goal.theta = g_pose->theta;
- printGoal();
- }
-
- if (g_state == FORWARD)
- {
- forward(twist_pub);
- }
- else if (g_state == STOP_FORWARD)
- {
- stopForward(twist_pub);
- }
- else if (g_state == TURN)
- {
- turn(twist_pub);
- }
- else if (g_state == STOP_TURN)
- {
- stopTurn(twist_pub);
- }
- }
-
- int main(int argc, char** argv)
- {
- ros::init(argc, argv, "draw_square");
- ros::NodeHandle nh;
- ros::Subscriber pose_sub = nh.subscribe("turtle1/pose", 1, poseCallback);
- ros::Publisher twist_pub = nh.advertise
("turtle1/cmd_vel", 1); - ros::ServiceClient reset = nh.serviceClient
("reset"); - ros::Timer timer = nh.createTimer(ros::Duration(0.016), boost::bind(timerCallback, _1, twist_pub));
-
- std_srvs::Empty empty;
- reset.call(empty);
-
- ros::spin();
- }
由于原来代码中,对于到达目标的误差设定较大:
bool hasReachedGoal()
{
return fabsf(g_pose->x - g_goal.x) < 0.1 && fabsf(g_pose->y - g_goal.y) < 0.1 && fabsf(g_pose->theta - g_goal.theta) < 0.01;
}
如果单纯提升精度,会由于控制速度是阶跃信号(控制要么为零要么是一个固定值),导致控制失效。
commandTurtle(twist_pub, 1.0, 0.0);
这种类似bang-bang控制。
enum State
{
FORWARD,
STOP_FORWARD,
TURN,
STOP_TURN,
};State g_state = FORWARD;
State g_last_state = FORWARD;
bool g_first_goal_set = false;
为何定义四种状态?
为何控制不连续?

机器人轨迹和数学之间的联系。
数学或物理上位移和速度的关系就类似为机器人运行轨迹和机器人速度控制量之间的关系。
定理:如果函数 f 在 x = a 中可导,则 f 在 x = a 中也是连续的。
每个直角∟点,显然不连续,机器人轨迹是分段的,控制自然也分段了。

改了如上的代码,实现了高精度的曲线绘制,但是依然存在误差,更不能忍受的是,一直画下去,根本停不下来啊……
那怎么办?
需要用topic/service/action
用服务或者行动更为合适。
参考如下程序:
- #include
- #include
- #include
- #include
- #include
- #include
-
- #include
- #include
-
- // This class computes the command_velocities of the turtle to draw regular polygons
- class ShapeAction
- {
- public:
- ShapeAction(std::string name) :
- as_(nh_, name, false),
- action_name_(name)
- {
- //register the goal and feeback callbacks
- as_.registerGoalCallback(boost::bind(&ShapeAction::goalCB, this));
- as_.registerPreemptCallback(boost::bind(&ShapeAction::preemptCB, this));
-
- //subscribe to the data topic of interest
- sub_ = nh_.subscribe("/turtle1/pose", 1, &ShapeAction::controlCB, this);
- pub_ = nh_.advertise
("/turtle1/cmd_vel", 1); -
- as_.start();
- }
-
- ~ShapeAction(void)
- {
- }
-
- void goalCB()
- {
- // accept the new goal
- turtle_actionlib::ShapeGoal goal = *as_.acceptNewGoal();
- //save the goal as private variables
- edges_ = goal.edges;
- radius_ = goal.radius;
-
- // reset helper variables
- interior_angle_ = ((edges_-2)*M_PI)/edges_;
- apothem_ = radius_*cos(M_PI/edges_);
- //compute the side length of the polygon
- side_len_ = apothem_ * 2* tan( M_PI/edges_);
- //store the result values
- result_.apothem = apothem_;
- result_.interior_angle = interior_angle_;
- edge_progress_ =0;
- start_edge_ = true;
- }
-
- void preemptCB()
- {
- ROS_INFO("%s: Preempted", action_name_.c_str());
- // set the action state to preempted
- as_.setPreempted();
- }
-
- void controlCB(const turtlesim::Pose::ConstPtr& msg)
- {
- // make sure that the action hasn't been canceled
- if (!as_.isActive())
- return;
-
- if (edge_progress_ < edges_)
- {
- // scalar values for drive the turtle faster and straighter
- double l_scale = 6.0;
- double a_scale = 6.0;
- double error_tol = 0.00001;
-
- if (start_edge_)
- {
- start_x_ = msg->x;
- start_y_ = msg->y;
- start_theta_ = msg->theta;
- start_edge_ = false;
- }
-
- // compute the distance and theta error for the shape
- dis_error_ = side_len_ - fabs(sqrt((start_x_- msg->x)*(start_x_-msg->x) + (start_y_-msg->y)*(start_y_-msg->y)));
- theta_error_ = angles::normalize_angle_positive(M_PI - interior_angle_ - (msg->theta - start_theta_));
-
- if (dis_error_ > error_tol)
- {
- command_.linear.x = l_scale*dis_error_;
- command_.angular.z = 0;
- }
- else if (dis_error_ < error_tol && fabs(theta_error_)> error_tol)
- {
- command_.linear.x = 0;
- command_.angular.z = a_scale*theta_error_;
- }
- else if (dis_error_ < error_tol && fabs(theta_error_)< error_tol)
- {
- command_.linear.x = 0;
- command_.angular.z = 0;
- start_edge_ = true;
- edge_progress_++;
- }
- else
- {
- command_.linear.x = l_scale*dis_error_;
- command_.angular.z = a_scale*theta_error_;
- }
- // publish the velocity command
- pub_.publish(command_);
-
- }
- else
- {
- ROS_INFO("%s: Succeeded", action_name_.c_str());
- // set the action state to succeeded
- as_.setSucceeded(result_);
- }
- }
-
- protected:
- ros::NodeHandle nh_;
- actionlib::SimpleActionServer
as_; - std::string action_name_;
- double radius_, apothem_, interior_angle_, side_len_;
- double start_x_, start_y_, start_theta_;
- double dis_error_, theta_error_;
- int edges_ , edge_progress_;
- bool start_edge_;
- geometry_msgs::Twist command_;
- turtle_actionlib::ShapeResult result_;
- ros::Subscriber sub_;
- ros::Publisher pub_;
- };
-
- int main(int argc, char** argv)
- {
- ros::init(argc, argv, "turtle_shape");
-
- ShapeAction shape(ros::this_node::getName());
- ros::spin();
-
- return 0;
- }
这里面,对于精度设置如下:
error_tol = 0.00001;
用这种方式实现,还有一个有点就是可以设置任意边。
- #include
- #include
- #include
- #include
-
- int main (int argc, char **argv)
- {
- ros::init(argc, argv, "test_shape");
-
- // create the action client
- // true causes the client to spin it's own thread
- actionlib::SimpleActionClient
ac("turtle_shape", true) ; -
- ROS_INFO("Waiting for action server to start.");
- // wait for the action server to start
- ac.waitForServer(); //will wait for infinite time
-
- ROS_INFO("Action server started, sending goal.");
- // send a goal to the action
- turtle_actionlib::ShapeGoal goal;
- goal.edges = 5;
- goal.radius = 1.3;
- ac.sendGoal(goal);
-
- //wait for the action to return
- bool finished_before_timeout = ac.waitForResult(ros::Duration(40.0));
-
- if (finished_before_timeout)
- {
- actionlib::SimpleClientGoalState state = ac.getState();
- ROS_INFO("Action finished: %s",state.toString().c_str());
- }
- else
- ROS_INFO("Action did not finish before the time out.");
-
- //exit
- return 0;
- }
在如下程序加入一些程序段:
int edges_input;
float radius_input;
ROS_INFO("Please input edges:");
std::cin>>edges_input;
ROS_INFO("Please input radius:");
std::cin>>radius_input;
if(edges_input>2)
goal.edges = edges_input;
if(radius_input>0)
goal.radius = radius_input;
全部代码如下:
- #include
- #include
- #include
- #include
-
- int main (int argc, char **argv)
- {
- ros::init(argc, argv, "test_shape");
-
- // create the action client
- // true causes the client to spin it's own thread
- actionlib::SimpleActionClient
ac("turtle_shape", true) ; -
- ROS_INFO("Waiting for action server to start.");
- // wait for the action server to start
- ac.waitForServer(); //will wait for infinite time
-
- ROS_INFO("Action server started, sending goal.");
- // send a goal to the action
- turtle_actionlib::ShapeGoal goal;
- goal.edges = 5;
- goal.radius = 1.3;
-
- int edges_input;
- float radius_input;
- ROS_INFO("Please input edges:");
- std::cin>>edges_input;
- ROS_INFO("Please input radius:");
- std::cin>>radius_input;
- if(edges_input>2)
- goal.edges = edges_input;
- if(radius_input>0)
- goal.radius = radius_input;
-
-
- ac.sendGoal(goal);
-
- //wait for the action to return
- bool finished_before_timeout = ac.waitForResult(ros::Duration(40.0));
-
- if (finished_before_timeout)
- {
- actionlib::SimpleClientGoalState state = ac.getState();
- ROS_INFO("Action finished: %s",state.toString().c_str());
- }
- else
- ROS_INFO("Action did not finish before the time out.");
-
- //exit
- return 0;
- }
三角形:
九边形:

二十七边形:

相关博文,供参考:
玫瑰线轨迹如何规划?(desmos+ROS2+turtlesim+……)