2 * @file motion-control.cc
3 * @author Michal Sojka <sojkam1@fel.cvut.cz>, Petr Beneš
4 * @date Fri Mar 20 10:36:59 2009
11 //#define MOTION_DEBUG
14 #define DBG(format, ...) printf(format, ##__VA_ARGS__)
15 #define DBGflush() fflush(stdout)
17 #define DBG(format, ...)
28 #include <path_planner.h>
30 #include <movehelper.h>
34 #include "robot_config.h"
36 #include <sys/syscall.h>
38 #define MOTION_CONTROL
39 #include "motion-control.h"
46 #ifdef CONFIG_TIMING_ANALYSIS
50 /* ULoPoS constants (-%-TEMPERATURE-%- dependent!) */
51 #define SOUND_VELOCITY (331.3+0.606*20)
52 #define XCORR2METER (SOUND_VELOCITY*(127.0/508.0)/3000.0)
53 #define D_MAX (XCORR2METER*508.0)
55 /*******************************************************************************
56 * Controller thread and helper functions for that thread
57 *******************************************************************************/
60 * If the distance of robot's estimated position from robot's
61 * requested position if above this value, the robot lost and we try
62 * to reset localization.
64 #define MAX_POS_ERROR_M 0.25
67 * If trajectory end is reached and robot's estimated position is
68 * closer than this distance, the movement is considered as "done".
70 #define CLOSE_TO_TARGET_M 0.1
73 const struct balet_params k = {
74 p_tangent: 3, // dx gain
75 p_angle: 2, // dphi gain
76 p_perpen: 5 // dy gain
77 // p_tangent: 0.2, // dx gain
78 // p_angle: 0.15, // dphi gain
79 // p_perpen: 1 // dy gain
82 #define MOTION_PERIOD_NS (50/*ms*/*1000*1000)
83 #define MEASURE_TIMEOUT_NS (100/*ms*/*1000*1000)
85 #define SIG_DO_CONTROL_NOW (SIGRTMIN+1)
88 static pthread_t thr_trajectory_follower;
89 static struct timeval tv_start; /**< Absolute time, when trajectory started. */
91 /** Stores the actually followed trajectory object */
92 static Trajectory *actual_trajectory;
93 static pthread_mutex_t actual_trajectory_lock;
95 // Trajectory recalculation
96 sem_t recalculation_not_running;
97 sem_t measurement_received;
100 * Determines way of thread_trajectory_follower() operation:
101 * - 0 measurement doesn't work, controller invocation based on time (formerly CONFIG_OPEN_LOOP)
102 * - 2 measurement works, controller invocation based on sem_post
103 * - 1 measurement doesn't work and stop() was called
105 int measurement_ok = 0;
109 static void delete_actual_trajectory()
112 pthread_mutex_lock(&actual_trajectory_lock);
113 old = actual_trajectory;
114 actual_trajectory = NULL;
115 pthread_mutex_unlock(&actual_trajectory_lock);
116 robot_send_speed(0,0);
117 if (old) delete(actual_trajectory);
120 /** Sends events from follower thread to FSM. */
121 static void notify_fsm(bool done, double error)
123 static bool done_sent;
124 static bool lost_sent = false;
126 if (error > MAX_POS_ERROR_M) {
129 FSM_SIGNAL(MOTION, EV_TRAJECTORY_LOST, NULL);
134 if (error < CLOSE_TO_TARGET_M) {
135 FSM_SIGNAL(MOTION, EV_TRAJECTORY_DONE_AND_CLOSE, NULL);
136 } else if (!done_sent) {
138 FSM_SIGNAL(MOTION, EV_TRAJECTORY_DONE, NULL);
146 static void check_for_collision_in_future(Trajectory *traj, double current_time)
149 struct map *map = robot.map;
154 // const double times[] = { 0.5, 0.3, 0.1 }; // seconds
155 const double times[] = { 0.3, 0.4, 0.5, 0.7, 0.9, 1.1 }; // seconds
158 for (i=0; i < sizeof(times)/sizeof(times[0]); i++) {
159 traj->getRefPos(current_time+times[i], future_pos);
161 /* Ignore obstacles when turning */
162 if (fabs(future_pos.v) < 0.01)
165 x = future_pos.x + cos(future_pos.phi)*ROBOT_AXIS_TO_BRUSH_M;
166 y = future_pos.y + sin(future_pos.phi)*ROBOT_AXIS_TO_BRUSH_M;
168 ShmapPoint2Cell(x, y, &xcell, &ycell, &valid);
171 if (map->cells[ycell][xcell].detected_obstacle > 0) {
172 if (sem_trywait(&recalculation_not_running) == 0) {
173 FSM_SIGNAL(MOTION, EV_OBSTACLE, NULL);
180 static void do_estimation()
182 static bool virgo = true;
189 static real_t beacon_xy[3][2] = {
197 static real_t beacon_xy[3][2] = {
205 static uint32_t odo0[2];
206 static real_t y[5] = {0.0, 0.0, 0.0, 0.0, 0.0};
207 static int missing_odo_count = 0;
208 uint32_t t[3], odo[2];
210 int i, odo_received, err[5];
212 #ifdef CONFIG_TIMING_ANALYSIS
213 #ifdef CONFIG_TIMING_MOTION_ESTIMATION
214 timing_ipoint(IPOINT_MOTION_DO_ESTIMATION_IN);
219 robot.team_color = RED;
221 robot.team_color = GREEN;
224 /* locks should not be necessary, however... */
225 ROBOT_LOCK(corr_distances);
226 t[0] = robot.corr_distances.t1;
227 t[1] = robot.corr_distances.t2;
228 t[2] = robot.corr_distances.t3;
229 ROBOT_UNLOCK(corr_distances);
230 ROBOT_LOCK(motion_irc);
231 odo[0] = robot.motion_irc.left;
232 odo[1] = robot.motion_irc.right;
233 odo_received = robot.motion_irc_received;
234 robot.motion_irc_received = 0;
235 ROBOT_UNLOCK(motion_irc);
237 for (i = 0; i < 3; i++)
238 y[i] = (XCORR2METER/32.0)*t[i];
240 /* missing odometry workaround :-( */
242 real_t c = 1.0/(real_t)(1 + missing_odo_count);
243 y[3] = c*ODO_C*(real_t)((int32_t)(odo0[0] - odo[0]));
244 y[4] = c*ODO_C*(real_t)((int32_t)(odo[1] - odo0[1]));
245 missing_odo_count = 0;
250 odo0[0] = odo[0]; odo0[1] = odo[1];
251 DBG("UZV+ODO: %f %f %f %f %f\n", y[0], y[1], y[2], y[3], y[4]);
252 //DBG("ODO: %f %f %u %u\n", y[3], y[4], odo[0], odo[1]);
254 if (virgo || init_ekf_flag) {
255 /*FIXME:reflect init pos & beacon coords accord.to our color */
256 ROBOT_LOCK(est_pos_uzv);
257 real_t xy0[] = {robot.est_pos_uzv.x, robot.est_pos_uzv.y};
259 ekf8_init(&ekf8, (real_t*)beacon_xy, D_MAX, 30.0, xy0, y);
260 ekf8.ekf.x[6] = robot.est_pos_uzv.phi;
261 init_ekf_flag = false;
263 ROBOT_UNLOCK(est_pos_uzv);
266 ekf8_step(&ekf8, x, P, err, y);
268 DBG("EKF: x=%f y=%f phi=%8.4f\n", x[0], x[1], x[6]*(180.0/M_PI));
270 ROBOT_LOCK(est_pos_uzv);
271 robot.est_pos_uzv.x = x[0] - ODO_D*cos(x[6]);
272 robot.est_pos_uzv.y = x[1] - ODO_D*sin(x[6]);
273 robot.est_pos_uzv.phi = x[6];
274 ROBOT_UNLOCK(est_pos_uzv);
275 #ifdef CONFIG_TIMING_ANALYSIS
276 #ifdef CONFIG_TIMING_MOTION_ESTIMATION
277 timing_ipoint(IPOINT_MOTION_DO_ESTIMATION_OUT);
282 static void do_control()
284 double speedl, speedr;
289 // Calculate reference position
290 /***FIXME:should not rely on system clock, the period is fixed***/
291 #ifdef CONFIG_TIMING_ANALYSIS
292 #ifdef CONFIG_TIMING_MOTION_CONTROL
293 timing_ipoint(IPOINT_MOTION_DO_CONTROL_IN);
296 gettimeofday(&tv, NULL);
297 t = (double)(tv.tv_usec - tv_start.tv_usec) / 1000000.0;
298 t += (tv.tv_sec - tv_start.tv_sec);
300 // check for new trajectory to switch
301 // only if the trajectory is already prepared
302 if (switch_to_trajectory != NULL && t >= switch_time) {
303 pthread_mutex_lock(&switch_to_trajectory_lock);
305 DBG("SWITCHING to new trajectory\n");
307 go(switch_to_trajectory);
308 // nothing prepared now
309 switch_to_trajectory = NULL;
310 pthread_mutex_unlock(&switch_to_trajectory_lock);
313 pthread_mutex_lock(&actual_trajectory_lock);
314 Trajectory *w = actual_trajectory;
316 Pos ref_pos, est_pos, balet_out;
319 // Calculate reference position
320 gettimeofday(&tv, NULL);
321 t = (double)(tv.tv_usec - tv_start.tv_usec) / 1000000.0;
322 t += (tv.tv_sec - tv_start.tv_sec);
324 // if switch_to_trajectory is being prepared, it can not stop calculation
325 // and start to count again, it could evoke overloading
326 if (robot.obstacle_avoidance_enabled)
327 check_for_collision_in_future(w, t);
330 done = w->getRefPos(t, ref_pos);
332 if (ref_pos.omega > actual_trajectory->constr.maxomega)
333 DBG("Omega constraint problem %lf, max %lf -------------------- \n", ref_pos.omega, actual_trajectory->constr.maxomega);
336 robot.ref_pos.x = ref_pos.x;
337 robot.ref_pos.y = ref_pos.y;
338 robot.ref_pos.phi = ref_pos.phi;
339 ROBOT_UNLOCK(ref_pos);
341 ROBOT_LOCK(est_pos_uzv);
342 est_pos.x = robot.est_pos_odo.x;
343 est_pos.y = robot.est_pos_odo.y;
344 est_pos.phi = robot.est_pos_odo.phi;
345 ROBOT_UNLOCK(est_pos_uzv);
347 #ifdef MOTION_PRINT_REF
348 static double last_t;
349 if (t < last_t) last_t = t; // Switched to a new trajectory
350 printf("rx=%5.02f ry=%5.02f, rphi=%4.0f v=%-4.02f omega=%-4.02f, time=%lf dt=%lf \n", ref_pos.x, ref_pos.y, ref_pos.phi/M_PI*180, ref_pos.v, ref_pos.omega, t, t-last_t);
355 // Call the controller
357 error = balet(ref_pos, est_pos, k, balet_out);
358 speedl = balet_out.v - ROBOT_ROTATION_RADIUS_M*balet_out.omega;
359 speedr = balet_out.v + ROBOT_ROTATION_RADIUS_M*balet_out.omega;
360 notify_fsm(done, error);
367 // Apply controller output
368 robot_send_speed(speedl, speedr);
369 pthread_mutex_unlock(&actual_trajectory_lock);
371 #ifdef CONFIG_TIMING_ANALYSIS
372 #ifdef CONFIG_TIMING_MOTION_CONTROL
373 timing_ipoint(IPOINT_MOTION_DO_CONTROL_OUT);
378 void dummy_handler(int)
382 static inline void next_period(struct timespec *next, long long interval_ns)
384 next->tv_nsec += interval_ns;
385 if (next->tv_nsec >= 1000000000) {
387 next->tv_nsec -= 1000000000;
392 * A thread running the controller.
394 * This (high priority) thread executes the motion control
395 * algorithm. It calculates repference position based on actual
396 * trajectory and current time. Then it calls "balet" controller to
403 void *thread_trajectory_follower(void *arg)
405 struct timespec next;
408 #ifdef CONFIG_TIMING_ANALYSIS
409 timing_set_pid(syscall(SYS_gettid));
411 clock_gettime(CLOCK_REALTIME, &next);
414 ret = sem_timedwait(&measurement_received, &next);
416 if (ret == -1 && errno == ETIMEDOUT) {
417 next_period(&next, MOTION_PERIOD_NS);
418 if (measurement_ok) {
419 if (measurement_ok == 2) {
420 fprintf(stderr, "problem: measurement timeout!!!!!!!!!!!");
427 next_period(&next, MEASURE_TIMEOUT_NS);
428 if (measurement_ok < 2) {
433 robot.localization_works = (measurement_ok == 2);
434 if (measurement_ok == 2) {
443 * Tells trajctory_follower to start moving along trajectory @c t.
445 * @param t Trajectory to follow.
446 * @param append_time Relative time from the beginning of the @c actual_trajectory
447 * when to append the new one
449 void go(Trajectory *t, double append_time)
451 pthread_mutex_lock(&actual_trajectory_lock);
453 if (actual_trajectory && append_time != 0) {
454 // trajectory only connects a new one in some specific time
455 if(!actual_trajectory->appendTrajectory(*t, append_time))
456 DBG("Can not append trajectory\n");
458 // trajectory starts from zero time
459 old = actual_trajectory;
460 gettimeofday(&tv_start, NULL);
461 actual_trajectory = t;
463 t->logTraj(tv_start.tv_sec + 1e-6*tv_start.tv_usec);
468 pthread_mutex_unlock(&actual_trajectory_lock);
472 * switches to newly calculated trajectory to go on it at specific time
474 /*void switch_trajectory_at(Trajectory *t, double time)
476 pthread_mutex_lock(&switch_to_trajectory_lock);
477 switch_to_trajectory = t;
479 pthread_mutex_unlock(&switch_to_trajectory_lock);
482 gettimeofday(&tv, NULL);
483 double tm = (double)(tv.tv_usec - tv_start.tv_usec) / 1000000.0;
484 tm += (tv.tv_sec - tv_start.tv_sec);
485 if (switch_time <= tm)
486 DBG("//// BAD SWITCH ////");
491 delete_actual_trajectory();
493 // Interrupt sem_timedwait() in thread_trajectory_follower(),
494 // so we stop immediately.
495 sem_post(&measurement_received);
499 * Initializes motion controller.
502 * @return Zero on success, non-zero otherwise.
504 int motion_control_init()
506 pthread_attr_t tattr;
508 pthread_mutexattr_t mattr;
511 actual_trajectory = NULL;
512 //switch_to_trajectory = NULL;
515 ret = pthread_mutexattr_init(&mattr);
516 #ifdef HAVE_PRIO_INHERIT
517 ret = pthread_mutexattr_setprotocol(&mattr, PTHREAD_PRIO_INHERIT);
519 pthread_mutex_init(&actual_trajectory_lock, &mattr);
521 sem_init(&recalculation_not_running, 0, 1);
523 // Trajectory follower thread
524 sem_init(&measurement_received, 0, 0);
525 pthread_attr_init (&tattr);
526 pthread_attr_getschedparam (&tattr, ¶m);
527 pthread_attr_setschedpolicy(&tattr, SCHED_FIFO);
528 param.sched_priority = THREAD_PRIO_TRAJ_FOLLOWER;
529 ret = pthread_attr_setschedparam (&tattr, ¶m);
531 perror("move_init: pthread_attr_setschedparam(follower)");
534 ret = pthread_create(&thr_trajectory_follower, &tattr, thread_trajectory_follower, NULL);
536 perror("move_init: pthread_create");
545 void motion_control_done()
547 pthread_cancel(thr_trajectory_follower);
548 pthread_join(thr_trajectory_follower, NULL);
550 robot.orte.motion_speed.right = 0;
551 robot.orte.motion_speed.left = 0;
552 ORTEPublicationSend(robot.orte.publication_motion_speed);
556 void get_future_pos(double rel_time_sec, Pos &pos, double &switch_time)
560 gettimeofday(&tv, NULL);
561 switch_time = (double)(tv.tv_usec - tv_start.tv_usec) / 1000000.0;
562 switch_time += (tv.tv_sec - tv_start.tv_sec);
563 switch_time += rel_time_sec;
565 pthread_mutex_lock(&actual_trajectory_lock);
566 if (actual_trajectory) {
567 actual_trajectory->getRefPos(switch_time, pos);
568 pthread_mutex_unlock(&actual_trajectory_lock);
570 // Robot doesn't move, so return current position
571 pthread_mutex_unlock(&actual_trajectory_lock);
573 ROBOT_LOCK(est_pos_uzv);
574 pos.x = robot.est_pos_uzv.x;
575 pos.y = robot.est_pos_uzv.y;
576 pos.phi = robot.est_pos_uzv.phi;
579 ROBOT_UNLOCK(est_pos_uzv);