├── README.md ├── roboteq_diff_driver ├── CMakeLists.txt ├── include │ └── roboteq_diff_driver │ │ └── driver.h ├── launch │ └── driver.launch ├── package.xml └── src │ └── driver.cpp └── roboteq_diff_msgs ├── CMakeLists.txt ├── msg ├── Duplex.msg ├── OdometryCovariances.msg ├── Point.msg ├── Pose.msg ├── Quaternion.msg ├── Twist.msg └── Vector3.msg ├── package.xml └── srv ├── RequestOdometryCovariances.srv └── RequestParam.srv /README.md: -------------------------------------------------------------------------------- 1 | # roboteq_diff_driver 2 | 3 | ROS driver for the Roboteq SDC21xx family of motor controllers in a differential-drive configuration. 4 | 5 | Subscribes to cmd_vel, publishes to odom, and broadcasts odom tf. 6 | 7 | Also publishes sensor data to some ancillary topics including roboteq/voltage, roboteq/current, roboteq/energy, and roboteq/temperature. 8 | 9 | Does not require any MicroBasic script to operate. 10 | 11 | ## Usage 12 | 13 | Clone to src directory of catkin workspace, then `catkin_make`. 14 | 15 | Requires ROS serial package. If not already installed: 16 | ``` 17 | sudo apt-get install ros--serial 18 | ``` 19 | Sample launch files in roboteq_diff_driver/launch. 20 | 21 | ## Motor Power Connections 22 | 23 | This driver expects that the right motor is connected to channel 1 of the motor controller (M1), and the left motor is connected to channel 2 (M2). 24 | 25 | It also expects that when it issues positive speed/power commands to the motor controller, this will translate into forward motion at each motor. In a typical differential drive robot, for forward motion the right motor will need to rotate clockwise and the left motor will need to rotate counter-clockwise. This will generally mean that the right motor will need to be connected as expected with positive motor lead on M1+ and negative motor lead on M1-, and the left motor will need to be connected in reverse with the positive motor lead on M2- and the negative motor lead on M2+. If either side of the robot is moving in reverese when instructed to move forward, usually just swapping the positive and negative motor leads at the motor controller for that side will correct it. 26 | 27 | ## TODO 28 | 29 | - [x] Make topic names and frames configuration parameters configurable at runtime. 30 | - [x] Make robot configuration parameters configurable at runtime. 31 | - [x] Make motor controller device configuration parameters configurable at runtime. 32 | - [ ] Make miscellaneous motor controller configuration parameters configurable at runtime. 33 | - [ ] Implement dynamically enabled forced-run mode to verify correct motor power connections. 34 | - [ ] Implement dynamically enabled self-test mode to verify correct motor power and encoder connections and configuration. 35 | 36 | ## Authors 37 | 38 | * **Chad Attermann** - *Initial work* - [Ecos Technologies](https://github.com/ecostech) 39 | 40 | ## License 41 | 42 | This project is licensed under the BSD License. 43 | 44 | -------------------------------------------------------------------------------- /roboteq_diff_driver/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8.3) 2 | project(roboteq_diff_driver) 3 | 4 | find_package(catkin REQUIRED COMPONENTS roscpp roboteq_diff_msgs std_msgs nav_msgs sensor_msgs topic_tools tf serial) 5 | find_package(Boost REQUIRED COMPONENTS system thread) 6 | 7 | catkin_package( 8 | # INCLUDE_DIRS include 9 | CATKIN_DEPENDS roscpp roboteq_diff_msgs std_msgs nav_msgs sensor_msgs topic_tools tf serial 10 | ) 11 | 12 | include_directories( 13 | include 14 | ${Boost_INCLUDE_DIRS} 15 | ${catkin_INCLUDE_DIRS} 16 | ) 17 | 18 | add_executable(${PROJECT_NAME}_driver src/driver.cpp) 19 | target_link_libraries(${PROJECT_NAME}_driver ${catkin_LIBRARIES}) 20 | set_target_properties(${PROJECT_NAME}_driver PROPERTIES OUTPUT_NAME driver PREFIX "") 21 | #add_dependencies(${PROJECT_NAME}_driver roboteq_diff_msgs_gencpp) 22 | 23 | install( 24 | TARGETS ${PROJECT_NAME}_driver 25 | RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} 26 | ) 27 | 28 | install( 29 | DIRECTORY launch 30 | DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION} 31 | ) 32 | 33 | install( 34 | DIRECTORY include/${PROJECT_NAME}/ 35 | DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION} 36 | ) 37 | -------------------------------------------------------------------------------- /roboteq_diff_driver/include/roboteq_diff_driver/driver.h: -------------------------------------------------------------------------------- 1 | 2 | // Placeholder for future driver class header file 3 | 4 | -------------------------------------------------------------------------------- /roboteq_diff_driver/launch/driver.launch: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /roboteq_diff_driver/package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | roboteq_diff_driver 4 | 0.1.0 5 | The roboteq_diff_driver package 6 | Chad Attermann 7 | Chad Attermann 8 | BSD 9 | https://github.com/ecostech/roboteq_diff_driver.git 10 | 11 | catkin 12 | roboteq_diff_msgs 13 | std_msgs 14 | nav_msgs 15 | sensor_msgs 16 | roscpp 17 | topic_tools 18 | tf 19 | serial 20 | roboteq_diff_msgs 21 | std_msgs 22 | nav_msgs 23 | sensor_msgs 24 | roscpp 25 | topic_tools 26 | tf 27 | serial 28 | 29 | -------------------------------------------------------------------------------- /roboteq_diff_driver/src/driver.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | 9 | #define DELTAT(_nowtime,_thentime) ((_thentime>_nowtime)?((0xffffffff-_thentime)+_nowtime):(_nowtime-_thentime)) 10 | 11 | 12 | // 13 | // cmd_vel subscriber 14 | // 15 | 16 | // Define following to enable cmdvel debug output 17 | #define _CMDVEL_DEBUG 18 | 19 | // Define following to enable motor test mode 20 | // Runs both motors in forward direction at 10% of configured maximum (rpms in close_loop mode, power in open_loop mode) 21 | // If configured correctly robot should translate forward in a straight line 22 | //#define _CMDVEL_FORCE_RUN 23 | 24 | #include 25 | #include 26 | #include 27 | 28 | 29 | // 30 | // odom publisher 31 | // 32 | 33 | // Define following to enable odom debug output 34 | #define _ODOM_DEBUG 35 | 36 | // Define following to publish additional sensor information 37 | #define _ODOM_SENSORS 38 | 39 | // Define following to enable service for returning covariance 40 | //#define _ODOM_COVAR_SERVER 41 | 42 | #define NORMALIZE(_z) atan2(sin(_z), cos(_z)) 43 | 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #ifdef _ODOM_SENSORS 50 | #include 51 | #include 52 | #endif 53 | #ifdef _ODOM_COVAR_SERVER 54 | #include "roboteq_diff_msgs/OdometryCovariances.h" 55 | #include "rogoteq_diff_msgs/RequestOdometryCovariances.h" 56 | #endif 57 | 58 | 59 | 60 | void mySigintHandler(int sig) 61 | { 62 | ROS_INFO("Received SIGINT signal, shutting down..."); 63 | ros::shutdown(); 64 | } 65 | 66 | 67 | uint32_t millis() 68 | { 69 | ros::WallTime walltime = ros::WallTime::now(); 70 | // return (uint32_t)((walltime._sec*1000 + walltime.nsec/1000000.0) + 0.5); 71 | // return (uint32_t)(walltime.toNSec()/1000000.0+0.5); 72 | return (uint32_t)(walltime.toNSec()/1000000); 73 | } 74 | 75 | class MainNode 76 | { 77 | 78 | public: 79 | MainNode(); 80 | 81 | public: 82 | 83 | // 84 | // cmd_vel subscriber 85 | // 86 | void cmdvel_callback( const geometry_msgs::Twist& twist_msg); 87 | void cmdvel_setup(); 88 | void cmdvel_loop(); 89 | void cmdvel_run(); 90 | 91 | // 92 | // odom publisher 93 | // 94 | void odom_setup(); 95 | void odom_stream(); 96 | void odom_loop(); 97 | //void odom_hs_run(); 98 | void odom_ms_run(); 99 | void odom_ls_run(); 100 | void odom_publish(); 101 | #ifdef _ODOM_COVAR_SERVER 102 | void odom_covar_callback(const roboteq_diff_msgs::RequestOdometryCovariancesRequest& req, roboteq_diff_msgs::RequestOdometryCovariancesResponse& res); 103 | #endif 104 | 105 | int run(); 106 | 107 | protected: 108 | ros::NodeHandle nh; 109 | 110 | serial::Serial controller; 111 | 112 | uint32_t starttime; 113 | uint32_t hstimer; 114 | uint32_t mstimer; 115 | uint32_t lstimer; 116 | 117 | // 118 | // cmd_vel subscriber 119 | // 120 | ros::Subscriber cmdvel_sub; 121 | 122 | // 123 | // odom publisher 124 | // 125 | geometry_msgs::TransformStamped tf_msg; 126 | tf::TransformBroadcaster odom_broadcaster; 127 | nav_msgs::Odometry odom_msg; 128 | ros::Publisher odom_pub; 129 | 130 | #ifdef _ODOM_SENSORS 131 | std_msgs::Float32 voltage_msg; 132 | ros::Publisher voltage_pub; 133 | roboteq_diff_msgs::Duplex current_msg; 134 | ros::Publisher current_pub; 135 | std_msgs::Float32 energy_msg; 136 | ros::Publisher energy_pub; 137 | std_msgs::Float32 temperature_msg; 138 | ros::Publisher temperature_pub; 139 | #endif 140 | 141 | // buffer for reading encoder counts 142 | int odom_idx; 143 | char odom_buf[24]; 144 | 145 | // toss out initial encoder readings 146 | char odom_encoder_toss; 147 | 148 | int32_t odom_encoder_left; 149 | int32_t odom_encoder_right; 150 | 151 | float odom_x; 152 | float odom_y; 153 | float odom_yaw; 154 | float odom_last_x; 155 | float odom_last_y; 156 | float odom_last_yaw; 157 | 158 | uint32_t odom_last_time; 159 | 160 | #ifdef _ODOM_SENSORS 161 | float voltage; 162 | float current_right; 163 | float current_left; 164 | float energy; 165 | float temperature; 166 | uint32_t current_last_time; 167 | #endif 168 | 169 | // settings 170 | bool pub_odom_tf; 171 | std::string odom_frame; 172 | std::string base_frame; 173 | std::string cmdvel_topic; 174 | std::string odom_topic; 175 | std::string port; 176 | int baud; 177 | bool open_loop; 178 | double wheel_circumference; 179 | double track_width; 180 | int encoder_ppr; 181 | int encoder_cpr; 182 | double max_amps; 183 | int max_rpm; 184 | 185 | }; 186 | 187 | MainNode::MainNode() : 188 | starttime(0), 189 | hstimer(0), 190 | mstimer(0), 191 | odom_idx(0), 192 | odom_encoder_toss(5), 193 | odom_encoder_left(0), 194 | odom_encoder_right(0), 195 | odom_x(0.0), 196 | odom_y(0.0), 197 | odom_yaw(0.0), 198 | odom_last_x(0.0), 199 | odom_last_y(0.0), 200 | odom_last_yaw(0.0), 201 | odom_last_time(0), 202 | #ifdef _ODOM_SENSORS 203 | voltage(0.0), 204 | current_right(0.0), 205 | current_left(0.0), 206 | energy(0.0), 207 | temperature(0.0), 208 | current_last_time(0), 209 | #endif 210 | pub_odom_tf(true), 211 | open_loop(false), 212 | baud(115200), 213 | wheel_circumference(0), 214 | track_width(0), 215 | encoder_ppr(0), 216 | encoder_cpr(0), 217 | max_amps(0.0), 218 | max_rpm(0) 219 | { 220 | 221 | 222 | // CBA Read local params (from launch file) 223 | ros::NodeHandle nhLocal("~"); 224 | nhLocal.param("pub_odom_tf", pub_odom_tf, true); 225 | ROS_INFO_STREAM("pub_odom_tf: " << pub_odom_tf); 226 | nhLocal.param("odom_frame", odom_frame, "odom"); 227 | ROS_INFO_STREAM("odom_frame: " << odom_frame); 228 | nhLocal.param("base_frame", base_frame, "base_link"); 229 | ROS_INFO_STREAM("base_frame: " << base_frame); 230 | nhLocal.param("cmdvel_topic", cmdvel_topic, "cmd_vel"); 231 | ROS_INFO_STREAM("cmdvel_topic: " << cmdvel_topic); 232 | nhLocal.param("odom_topic", odom_topic, "odom"); 233 | ROS_INFO_STREAM("odom_topic: " << odom_topic); 234 | nhLocal.param("port", port, "/dev/ttyACM0"); 235 | ROS_INFO_STREAM("port: " << port); 236 | nhLocal.param("baud", baud, 115200); 237 | ROS_INFO_STREAM("baud: " << baud); 238 | nhLocal.param("open_loop", open_loop, false); 239 | ROS_INFO_STREAM("open_loop: " << open_loop); 240 | nhLocal.param("wheel_circumference", wheel_circumference, 0.3192); 241 | ROS_INFO_STREAM("wheel_circumference: " << wheel_circumference); 242 | nhLocal.param("track_width", track_width, 0.4318); 243 | ROS_INFO_STREAM("track_width: " << track_width); 244 | nhLocal.param("encoder_ppr", encoder_ppr, 900); 245 | ROS_INFO_STREAM("encoder_ppr: " << encoder_ppr); 246 | nhLocal.param("encoder_cpr", encoder_cpr, 3600); 247 | ROS_INFO_STREAM("encoder_cpr: " << encoder_cpr); 248 | nhLocal.param("max_amps", max_amps, 5.0); 249 | ROS_INFO_STREAM("max_amps: " << max_amps); 250 | nhLocal.param("max_rpm", max_rpm, 100); 251 | ROS_INFO_STREAM("max_rpm: " << max_rpm); 252 | 253 | } 254 | 255 | 256 | // 257 | // cmd_vel subscriber 258 | // 259 | 260 | void MainNode::cmdvel_callback( const geometry_msgs::Twist& twist_msg) 261 | { 262 | 263 | // wheel speed (m/s) 264 | float right_speed = twist_msg.linear.x + track_width * twist_msg.angular.z / 2.0; 265 | float left_speed = twist_msg.linear.x - track_width * twist_msg.angular.z / 2.0; 266 | #ifdef _CMDVEL_DEBUG 267 | ROS_DEBUG_STREAM("cmdvel speed right: " << right_speed << " left: " << left_speed); 268 | #endif 269 | 270 | std::stringstream right_cmd; 271 | std::stringstream left_cmd; 272 | 273 | if (open_loop) 274 | { 275 | // motor power (scale 0-1000) 276 | int32_t right_power = right_speed / wheel_circumference * 60.0 / max_rpm * 1000.0; 277 | int32_t left_power = left_speed / wheel_circumference * 60.0 / max_rpm * 1000.0; 278 | #ifdef _CMDVEL_DEBUG 279 | ROS_DEBUG_STREAM("cmdvel power right: " << right_power << " left: " << left_power); 280 | #endif 281 | right_cmd << "!G 1 " << right_power << "\r"; 282 | left_cmd << "!G 2 " << left_power << "\r"; 283 | } 284 | else 285 | { 286 | // motor speed (rpm) 287 | int32_t right_rpm = right_speed / wheel_circumference * 60.0; 288 | int32_t left_rpm = left_speed / wheel_circumference * 60.0; 289 | #ifdef _CMDVEL_DEBUG 290 | ROS_DEBUG_STREAM("cmdvel rpm right: " << right_rpm << " left: " << left_rpm); 291 | #endif 292 | right_cmd << "!S 1 " << right_rpm << "\r"; 293 | left_cmd << "!S 2 " << left_rpm << "\r"; 294 | } 295 | 296 | 297 | #ifndef _CMDVEL_FORCE_RUN 298 | controller.write(right_cmd.str()); 299 | controller.write(left_cmd.str()); 300 | controller.flush(); 301 | #endif 302 | } 303 | 304 | void MainNode::cmdvel_setup() 305 | { 306 | 307 | // stop motors 308 | controller.write("!G 1 0\r"); 309 | controller.write("!G 2 0\r"); 310 | controller.write("!S 1 0\r"); 311 | controller.write("!S 2 0\r"); 312 | controller.flush(); 313 | 314 | // disable echo 315 | controller.write("^ECHOF 1\r"); 316 | controller.flush(); 317 | 318 | // enable watchdog timer (1000 ms) 319 | controller.write("^RWD 1000\r"); 320 | 321 | // set motor operating mode (1 for closed-loop speed) 322 | if (open_loop) 323 | { 324 | // open-loop speed mode 325 | controller.write("^MMOD 1 0\r"); 326 | controller.write("^MMOD 2 0\r"); 327 | } 328 | else 329 | { 330 | // closed-loop speed mode 331 | controller.write("^MMOD 1 1\r"); 332 | controller.write("^MMOD 2 1\r"); 333 | } 334 | 335 | // set motor amps limit (A * 10) 336 | std::stringstream right_ampcmd; 337 | std::stringstream left_ampcmd; 338 | right_ampcmd << "^ALIM 1 " << (int)(max_amps * 10) << "\r"; 339 | left_ampcmd << "^ALIM 2 " << (int)(max_amps * 10) << "\r"; 340 | controller.write(right_ampcmd.str()); 341 | controller.write(left_ampcmd.str()); 342 | 343 | // set max speed (rpm) for relative speed commands 344 | std::stringstream right_rpmcmd; 345 | std::stringstream left_rpmcmd; 346 | right_rpmcmd << "^MXRPM 1 " << max_rpm << "\r"; 347 | left_rpmcmd << "^MXRPM 2 " << max_rpm << "\r"; 348 | controller.write(right_rpmcmd.str()); 349 | controller.write(left_rpmcmd.str()); 350 | 351 | // set max acceleration rate (2000 rpm/s * 10) 352 | controller.write("^MAC 1 20000\r"); 353 | controller.write("^MAC 2 20000\r"); 354 | 355 | // set max deceleration rate (2000 rpm/s * 10) 356 | controller.write("^MDEC 1 20000\r"); 357 | controller.write("^MDEC 2 20000\r"); 358 | 359 | // set PID parameters (gain * 10) 360 | controller.write("^KP 1 10\r"); 361 | controller.write("^KP 2 10\r"); 362 | controller.write("^KI 1 80\r"); 363 | controller.write("^KI 2 80\r"); 364 | controller.write("^KD 1 0\r"); 365 | controller.write("^KD 2 0\r"); 366 | 367 | // set encoder mode (18 for feedback on motor1, 34 for feedback on motor2) 368 | controller.write("^EMOD 1 18\r"); 369 | controller.write("^EMOD 2 34\r"); 370 | 371 | // set encoder counts (ppr) 372 | std::stringstream right_enccmd; 373 | std::stringstream left_enccmd; 374 | right_enccmd << "^EPPR 1 " << encoder_ppr << "\r"; 375 | left_enccmd << "^EPPR 2 " << encoder_ppr << "\r"; 376 | controller.write(right_enccmd.str()); 377 | controller.write(left_enccmd.str()); 378 | 379 | controller.flush(); 380 | 381 | ROS_INFO_STREAM("Subscribing to topic " << cmdvel_topic); 382 | cmdvel_sub = nh.subscribe(cmdvel_topic, 1000, &MainNode::cmdvel_callback, this); 383 | 384 | } 385 | 386 | void MainNode::cmdvel_loop() 387 | { 388 | } 389 | 390 | void MainNode::cmdvel_run() 391 | { 392 | #ifdef _CMDVEL_FORCE_RUN 393 | if (open_loop) 394 | { 395 | controller.write("!G 1 100\r"); 396 | controller.write("!G 2 100\r"); 397 | } 398 | else 399 | { 400 | std::stringstream right_cmd; 401 | std::stringstream left_cmd; 402 | right_cmd << "!S 1 " << (int)(max_rpm * 0.1) << "\r"; 403 | left_cmd << "!S 2 " << (int)(max_rpm * 0.1) << "\r"; 404 | controller.write(right_cmd.str()); 405 | controller.write(left_cmd.str()); 406 | } 407 | controller.flush(); 408 | #endif 409 | } 410 | 411 | 412 | // 413 | // odom publisher 414 | // 415 | 416 | #ifdef _ODOM_COVAR_SERVER 417 | void MainNode::odom_covar_callback(const roboteq_diff_msgs::RequestOdometryCovariancesRequest& req, roboteq_diff_msgs::RequestOdometryCovariancesResponse& res) 418 | { 419 | res.odometry_covariances.pose.pose.covariance[0] = 0.001; 420 | res.odometry_covariances.pose.pose.covariance[7] = 0.001; 421 | res.odometry_covariances.pose.pose.covariance[14] = 1000000; 422 | res.odometry_covariances.pose.pose.covariance[21] = 1000000; 423 | res.odometry_covariances.pose.pose.covariance[28] = 1000000; 424 | res.odometry_covariances.pose.pose.covariance[35] = 1000; 425 | 426 | res.odometry_covariances.twist.twist.covariance[0] = 0.001; 427 | res.odometry_covariances.twist.twist.covariance[7] = 0.001; 428 | res.odometry_covariances.twist.twist.covariance[14] = 1000000; 429 | res.odometry_covariances.twist.twist.covariance[21] = 1000000; 430 | res.odometry_covariances.twist.twist.covariance[28] = 1000000; 431 | res.odometry_covariances.twist.twist.covariance[35] = 1000; 432 | } 433 | #endif 434 | 435 | /* 436 | position.pose.covariance = boost::assign::list_of(1e-3) (0) (0) (0) (0) (0) 437 | (0) (1e-3) (0) (0) (0) (0) 438 | (0) (0) (1e6) (0) (0) (0) 439 | (0) (0) (0) (1e6) (0) (0) 440 | (0) (0) (0) (0) (1e6) (0) 441 | (0) (0) (0) (0) (0) (1e3) ; 442 | 443 | position.twist.covariance = boost::assign::list_of(1e-3) (0) (0) (0) (0) (0) 444 | (0) (1e-3) (0) (0) (0) (0) 445 | (0) (0) (1e6) (0) (0) (0) 446 | (0) (0) (0) (1e6) (0) (0) 447 | (0) (0) (0) (0) (1e6) (0) 448 | (0) (0) (0) (0) (0) (1e3) ; 449 | 450 | To estimate velocity covariance you should know TICKSMM (128) and SIPCYCLE (100) parameters of your robot (written in your robots flash memory and not accessible with Aria). First parameter tells you how many encoder impulses (count) gets generated by your robot's forward movement of 1 mm. Second parameter tells you number of milliseconds between two consecutive Server Information Packets from your robot. The values in the parentheses are for P3-DX (ARCOS). 451 | 452 | So an error in determining velocity could come from missing an encoder impulse in a cycle. This would result in 1/TICKSMM/SIPCYCLE velocity error (mm/ms or m/s) for one wheel. For P3-DX parameters above, this value is 7.8125e-05. Note that you would err by the same absolute amount of velocity in the next cycle. Gearbox also plays a role in velocity error, but you would need to measure to find the exact amount. As a rule of thumb, I would at least double the previous amount in order to include gearbox error. 453 | 454 | Now that we have determined maximum error of a single wheel's (transversal) velocity, i.e. we expect 99.7% of errors to be less than this number, we can determine sigma = max_err/3 and C = sigma^2. Translational and rotational velocities are determined from left and right wheel velocities like this: 455 | 456 | v = (v_R + v_L)/2 457 | 458 | w = (v_R - v_L)/(2d) 459 | 460 | So the covariance for transversal velocity would be (1/2)^2 2C and the covariance for rotational velocity would be (1/(2d))^2 2C. The d parameter is 1/DiffConvFactor and is accessible from Aria (ArRobot::getDiffConvFactor()). 461 | 462 | */ 463 | 464 | void MainNode::odom_setup() 465 | { 466 | 467 | if ( pub_odom_tf ) 468 | { 469 | ROS_INFO("Broadcasting odom tf"); 470 | // odom_broadcaster.init(nh); // ??? 471 | } 472 | 473 | ROS_INFO_STREAM("Publishing to topic " << odom_topic); 474 | odom_pub = nh.advertise(odom_topic, 1000); 475 | 476 | #ifdef _ODOM_COVAR_SERVER 477 | ROS_INFO("Advertising service on roboteq/odom_covar_srv"); 478 | odom_covar_server = nh.advertiseService("roboteq/odom_covar_srv", &MainNode::odom_covar_callback, this); 479 | #endif 480 | 481 | #ifdef _ODOM_SENSORS 482 | ROS_INFO("Publishing to topic roboteq/voltage"); 483 | voltage_pub = nh.advertise("roboteq/voltage", 1000); 484 | ROS_INFO("Publishing to topic roboteq/current"); 485 | current_pub = nh.advertise("roboteq/current", 1000); 486 | ROS_INFO("Publishing to topic roboteq/energy"); 487 | energy_pub = nh.advertise("roboteq/energy", 1000); 488 | ROS_INFO("Publishing to topic roboteq/temperature"); 489 | temperature_pub = nh.advertise("roboteq/temperature", 1000); 490 | #endif 491 | 492 | tf_msg.header.seq = 0; 493 | tf_msg.header.frame_id = odom_frame; 494 | tf_msg.child_frame_id = base_frame; 495 | 496 | odom_msg.header.seq = 0; 497 | odom_msg.header.frame_id = odom_frame; 498 | odom_msg.child_frame_id = base_frame; 499 | 500 | odom_msg.pose.covariance.assign(0); 501 | odom_msg.pose.covariance[0] = 0.001; 502 | odom_msg.pose.covariance[7] = 0.001; 503 | odom_msg.pose.covariance[14] = 1000000; 504 | odom_msg.pose.covariance[21] = 1000000; 505 | odom_msg.pose.covariance[28] = 1000000; 506 | odom_msg.pose.covariance[35] = 1000; 507 | 508 | odom_msg.twist.covariance.assign(0); 509 | odom_msg.twist.covariance[0] = 0.001; 510 | odom_msg.twist.covariance[7] = 0.001; 511 | odom_msg.twist.covariance[14] = 1000000; 512 | odom_msg.twist.covariance[21] = 1000000; 513 | odom_msg.twist.covariance[28] = 1000000; 514 | odom_msg.twist.covariance[35] = 1000; 515 | 516 | #ifdef _ODOM_SENSORS 517 | // voltage_msg.header.seq = 0; 518 | // voltage_msg.header.frame_id = 0; 519 | // current_msg.header.seq = 0; 520 | // current_msg.header.frame_id = 0; 521 | // energy_msg.header.seq = 0; 522 | // energy_msg.header.frame_id = 0; 523 | // temperature_msg.header.seq = 0; 524 | // temperature_msg.header.frame_id = 0; 525 | #endif 526 | 527 | // start encoder streaming 528 | odom_stream(); 529 | 530 | odom_last_time = millis(); 531 | #ifdef _ODOM_SENSORS 532 | current_last_time = millis(); 533 | #endif 534 | } 535 | 536 | void MainNode::odom_stream() 537 | { 538 | 539 | #ifdef _ODOM_SENSORS 540 | // start encoder and current output (30 hz) 541 | // doubling frequency since one value is output at each cycle 542 | // controller.write("# C_?CR_?BA_# 17\r"); 543 | // start encoder, current and voltage output (30 hz) 544 | // tripling frequency since one value is output at each cycle 545 | controller.write("# C_?CR_?BA_?V_# 11\r"); 546 | #else 547 | // // start encoder output (10 hz) 548 | // controller.write("# C_?CR_# 100\r"); 549 | // start encoder output (30 hz) 550 | controller.write("# C_?CR_# 33\r"); 551 | // // start encoder output (50 hz) 552 | // controller.write("# C_?CR_# 20\r"); 553 | #endif 554 | controller.flush(); 555 | 556 | } 557 | 558 | void MainNode::odom_loop() 559 | { 560 | 561 | uint32_t nowtime = millis(); 562 | 563 | // if we haven't received encoder counts in some time then restart streaming 564 | if( DELTAT(nowtime,odom_last_time) >= 1000 ) 565 | { 566 | odom_stream(); 567 | odom_last_time = nowtime; 568 | } 569 | 570 | // read sensor data stream from motor controller 571 | if (controller.available()) 572 | { 573 | char ch = 0; 574 | if ( controller.read((uint8_t*)&ch, 1) == 0 ) 575 | return; 576 | if (ch == '\r') 577 | { 578 | odom_buf[odom_idx] = 0; 579 | #ifdef _ODOM_DEBUG 580 | //ROS_DEBUG_STREAM( "line: " << odom_buf ); 581 | #endif 582 | // CR= is encoder counts 583 | if ( odom_buf[0] == 'C' && odom_buf[1] == 'R' && odom_buf[2] == '=' ) 584 | { 585 | int delim; 586 | for ( delim = 3; delim < odom_idx; delim++ ) 587 | { 588 | if ( odom_encoder_toss > 0 ) 589 | { 590 | --odom_encoder_toss; 591 | break; 592 | } 593 | if (odom_buf[delim] == ':') 594 | { 595 | odom_buf[delim] = 0; 596 | odom_encoder_right = (int32_t)strtol(odom_buf+3, NULL, 10); 597 | odom_encoder_left = (int32_t)strtol(odom_buf+delim+1, NULL, 10); 598 | #ifdef _ODOM_DEBUG 599 | ROS_DEBUG_STREAM("encoder right: " << odom_encoder_right << " left: " << odom_encoder_left); 600 | #endif 601 | odom_publish(); 602 | break; 603 | } 604 | } 605 | } 606 | #ifdef _ODOM_SENSORS 607 | // V= is voltages 608 | else if ( odom_buf[0] == 'V' && odom_buf[1] == '=' ) 609 | { 610 | int count = 0; 611 | int start = 2; 612 | for ( int delim = 2; delim <= odom_idx; delim++ ) 613 | { 614 | if (odom_buf[delim] == ':' || odom_buf[delim] == 0) 615 | { 616 | odom_buf[delim] = 0; 617 | /* 618 | switch (count) 619 | { 620 | case 0: 621 | // odom_internal_voltage = (float)strtol(odom_buf+start, NULL, 10) / 10.0; 622 | break; 623 | case 1: 624 | voltage = (float)strtol(odom_buf+start, NULL, 10) / 10.0; 625 | break; 626 | case 2: 627 | // odom_aux_voltage = (float)strtol(odom_buf+start, NULL, 1000.0); 628 | break; 629 | } 630 | */ 631 | if ( count == 1 ) 632 | { 633 | voltage = (float)strtol(odom_buf+start, NULL, 10) / 10.0; 634 | #ifdef _ODOM_DEBUG 635 | //ROS_DEBUG_STREAM("voltage: " << voltage); 636 | #endif 637 | break; 638 | } 639 | start = delim + 1; 640 | count++; 641 | } 642 | } 643 | } 644 | // BA= is motor currents 645 | else if ( odom_buf[0] == 'B' && odom_buf[1] == 'A' && odom_buf[2] == '=' ) 646 | { 647 | int delim; 648 | for ( delim = 3; delim < odom_idx; delim++ ) 649 | { 650 | if (odom_buf[delim] == ':') 651 | { 652 | odom_buf[delim] = 0; 653 | current_right = (float)strtol(odom_buf+3, NULL, 10) / 10.0; 654 | current_left = (float)strtol(odom_buf+delim+1, NULL, 10) / 10.0; 655 | #ifdef _ODOM_DEBUG 656 | //ROS_DEBUG_STREAM("current right: " << current_right << " left: " << current_left); 657 | #endif 658 | 659 | // determine delta time in seconds 660 | float dt = (float)DELTAT(nowtime,current_last_time) / 1000.0; 661 | current_last_time = nowtime; 662 | energy += (current_right + current_left) * dt / 3600.0; 663 | break; 664 | } 665 | } 666 | } 667 | #endif 668 | odom_idx = 0; 669 | } 670 | else if ( odom_idx < (sizeof(odom_buf)-1) ) 671 | { 672 | odom_buf[odom_idx++] = ch; 673 | } 674 | } 675 | } 676 | 677 | //void MainNode::odom_hs_run() 678 | //{ 679 | //} 680 | 681 | void MainNode::odom_ms_run() 682 | { 683 | 684 | #ifdef _ODOM_SENSORS 685 | // current_msg.header.seq++; 686 | // current_msg.header.stamp = ros::Time::now(); 687 | current_msg.a = current_right; 688 | current_msg.b = current_left; 689 | current_pub.publish(current_msg); 690 | #endif 691 | 692 | } 693 | 694 | void MainNode::odom_ls_run() 695 | { 696 | 697 | #ifdef _ODOM_SENSORS 698 | // voltage_msg.header.seq++; 699 | // voltage_msg.header.stamp = ros::Time::now(); 700 | voltage_msg.data = voltage; 701 | voltage_pub.publish(voltage_msg); 702 | // energy_msg.header.seq++; 703 | // energy_msg.header.stamp = ros::Time::now(); 704 | energy_msg.data = energy; 705 | energy_pub.publish(energy_msg); 706 | // temperature_msg.header.seq++; 707 | // temperature_msg.header.stamp = ros::Time::now(); 708 | temperature_msg.data = temperature; 709 | temperature_pub.publish(temperature_msg); 710 | #endif 711 | 712 | } 713 | 714 | void MainNode::odom_publish() 715 | { 716 | 717 | // determine delta time in seconds 718 | uint32_t nowtime = millis(); 719 | float dt = (float)DELTAT(nowtime,odom_last_time) / 1000.0; 720 | odom_last_time = nowtime; 721 | 722 | #ifdef _ODOM_DEBUG 723 | /* 724 | ROS_DEBUG("right: "); 725 | ROS_DEBUG(odom_encoder_right); 726 | ROS_DEBUG(" left: "); 727 | ROS_DEBUG(odom_encoder_left); 728 | ROS_DEBUG(" dt: "); 729 | ROS_DEBUG(dt); 730 | ROS_DEBUG(""); 731 | */ 732 | #endif 733 | 734 | // determine deltas of distance and angle 735 | float linear = ((float)odom_encoder_right / (float)encoder_cpr * wheel_circumference + (float)odom_encoder_left / (float)encoder_cpr * wheel_circumference) / 2.0; 736 | // float angular = ((float)odom_encoder_right / (float)encoder_cpr * wheel_circumference - (float)odom_encoder_left / (float)encoder_cpr * wheel_circumference) / track_width * -1.0; 737 | float angular = ((float)odom_encoder_right / (float)encoder_cpr * wheel_circumference - (float)odom_encoder_left / (float)encoder_cpr * wheel_circumference) / track_width; 738 | #ifdef _ODOM_DEBUG 739 | /* 740 | ROS_DEBUG("linear: "); 741 | ROS_DEBUG(linear); 742 | ROS_DEBUG(" angular: "); 743 | ROS_DEBUG(angular); 744 | ROS_DEBUG(""); 745 | */ 746 | #endif 747 | 748 | // Update odometry 749 | odom_x += linear * cos(odom_yaw); // m 750 | odom_y += linear * sin(odom_yaw); // m 751 | odom_yaw = NORMALIZE(odom_yaw + angular); // rad 752 | #ifdef _ODOM_DEBUG 753 | //ROS_DEBUG_STREAM( "odom x: " << odom_x << " y: " << odom_y << " yaw: " << odom_yaw ); 754 | #endif 755 | 756 | // Calculate velocities 757 | float vx = (odom_x - odom_last_x) / dt; 758 | float vy = (odom_y - odom_last_y) / dt; 759 | float vyaw = (odom_yaw - odom_last_yaw) / dt; 760 | #ifdef _ODOM_DEBUG 761 | //ROS_DEBUG_STREAM( "velocity vx: " << odom_x << " vy: " << odom_y << " vyaw: " << odom_yaw ); 762 | #endif 763 | odom_last_x = odom_x; 764 | odom_last_y = odom_y; 765 | odom_last_yaw = odom_yaw; 766 | #ifdef _ODOM_DEBUG 767 | /* 768 | ROS_DEBUG("vx: "); 769 | ROS_DEBUG(vx); 770 | ROS_DEBUG(" vy: "); 771 | ROS_DEBUG(vy); 772 | ROS_DEBUG(" vyaw: "); 773 | ROS_DEBUG(vyaw); 774 | ROS_DEBUG(""); 775 | */ 776 | #endif 777 | 778 | geometry_msgs::Quaternion quat = tf::createQuaternionMsgFromYaw(odom_yaw); 779 | 780 | if ( pub_odom_tf ) 781 | { 782 | tf_msg.header.seq++; 783 | tf_msg.header.stamp = ros::Time::now(); 784 | tf_msg.transform.translation.x = odom_x; 785 | tf_msg.transform.translation.y = odom_y; 786 | tf_msg.transform.translation.z = 0.0; 787 | tf_msg.transform.rotation = quat; 788 | odom_broadcaster.sendTransform(tf_msg); 789 | } 790 | 791 | odom_msg.header.seq++; 792 | odom_msg.header.stamp = ros::Time::now(); 793 | odom_msg.pose.pose.position.x = odom_x; 794 | odom_msg.pose.pose.position.y = odom_y; 795 | odom_msg.pose.pose.position.z = 0.0; 796 | odom_msg.pose.pose.orientation = quat; 797 | odom_msg.twist.twist.linear.x = vx; 798 | odom_msg.twist.twist.linear.y = vy; 799 | odom_msg.twist.twist.linear.z = 0.0; 800 | odom_msg.twist.twist.angular.x = 0.0; 801 | odom_msg.twist.twist.angular.y = 0.0; 802 | odom_msg.twist.twist.angular.z = vyaw; 803 | odom_pub.publish(odom_msg); 804 | 805 | } 806 | 807 | 808 | int MainNode::run() 809 | { 810 | 811 | ROS_INFO("Beginning setup..."); 812 | 813 | serial::Timeout timeout = serial::Timeout::simpleTimeout(1000); 814 | controller.setPort(port); 815 | controller.setBaudrate(baud); 816 | controller.setTimeout(timeout); 817 | 818 | // TODO: support automatic re-opening of port after disconnection 819 | while ( ros::ok() ) 820 | { 821 | ROS_INFO_STREAM("Opening serial port on " << port << " at " << baud << "..." ); 822 | try 823 | { 824 | controller.open(); 825 | if ( controller.isOpen() ) 826 | { 827 | ROS_INFO("Successfully opened serial port"); 828 | break; 829 | } 830 | } 831 | catch (serial::IOException e) 832 | { 833 | ROS_WARN_STREAM("serial::IOException: " << e.what()); 834 | } 835 | ROS_WARN("Failed to open serial port"); 836 | sleep( 5 ); 837 | } 838 | 839 | cmdvel_setup(); 840 | odom_setup(); 841 | 842 | starttime = millis(); 843 | hstimer = starttime; 844 | mstimer = starttime; 845 | lstimer = starttime; 846 | 847 | // ros::Rate loop_rate(10); 848 | 849 | ROS_INFO("Beginning looping..."); 850 | 851 | while (ros::ok()) 852 | { 853 | 854 | cmdvel_loop(); 855 | odom_loop(); 856 | 857 | uint32_t nowtime = millis(); 858 | //ROS_INFO_STREAM("loop nowtime: " << nowtime << " lstimer: " << lstimer << " delta: " << DELTAT(nowtime,lstimer) << " / " << (nowtime-lstimer)); 859 | //uint32_t delta = DELTAT(nowtime,lstimer); 860 | //ROS_INFO_STREAM("loop nowtime: " << nowtime << " lstimer: " << lstimer << " delta: " << delta << " / " << (nowtime-lstimer)); 861 | 862 | // // Handle 50 Hz publishing 863 | // if (DELTAT(nowtime,hstimer) >= 20) 864 | // Handle 30 Hz publishing 865 | if (DELTAT(nowtime,hstimer) >= 33) 866 | { 867 | hstimer = nowtime; 868 | // odom_hs_run(); 869 | } 870 | 871 | // Handle 10 Hz publishing 872 | if (DELTAT(nowtime,mstimer) >= 100) 873 | { 874 | mstimer = nowtime; 875 | cmdvel_run(); 876 | odom_ms_run(); 877 | } 878 | 879 | // Handle 1 Hz publishing 880 | if (DELTAT(nowtime,lstimer) >= 1000) 881 | { 882 | lstimer = nowtime; 883 | odom_ls_run(); 884 | } 885 | 886 | ros::spinOnce(); 887 | 888 | // loop_rate.sleep(); 889 | } 890 | 891 | if ( controller.isOpen() ) 892 | controller.close(); 893 | 894 | ROS_INFO("Exiting"); 895 | 896 | return 0; 897 | } 898 | 899 | int main(int argc, char **argv) 900 | { 901 | 902 | ros::init(argc, argv, "main_node"); 903 | 904 | MainNode node; 905 | 906 | // Override the default ros sigint handler. 907 | // This must be set after the first NodeHandle is created. 908 | signal(SIGINT, mySigintHandler); 909 | 910 | return node.run(); 911 | } 912 | 913 | -------------------------------------------------------------------------------- /roboteq_diff_msgs/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8.3) 2 | project(roboteq_diff_msgs) 3 | 4 | find_package(catkin REQUIRED COMPONENTS message_generation std_msgs) 5 | 6 | add_message_files(FILES 7 | Duplex.msg 8 | OdometryCovariances.msg 9 | Point.msg 10 | Vector3.msg 11 | Quaternion.msg 12 | Pose.msg 13 | Twist.msg 14 | ) 15 | 16 | add_service_files(FILES 17 | RequestParam.srv 18 | RequestOdometryCovariances.srv 19 | ) 20 | 21 | generate_messages(DEPENDENCIES std_msgs) 22 | catkin_package(CATKIN_DEPENDS message_runtime std_msgs) 23 | -------------------------------------------------------------------------------- /roboteq_diff_msgs/msg/Duplex.msg: -------------------------------------------------------------------------------- 1 | float32 a 2 | float32 b 3 | -------------------------------------------------------------------------------- /roboteq_diff_msgs/msg/OdometryCovariances.msg: -------------------------------------------------------------------------------- 1 | float64[36] pose_covariance 2 | float64[36] twist_covariance 3 | -------------------------------------------------------------------------------- /roboteq_diff_msgs/msg/Point.msg: -------------------------------------------------------------------------------- 1 | float32 x 2 | float32 y 3 | float32 z 4 | -------------------------------------------------------------------------------- /roboteq_diff_msgs/msg/Pose.msg: -------------------------------------------------------------------------------- 1 | Point position 2 | Quaternion orientation 3 | -------------------------------------------------------------------------------- /roboteq_diff_msgs/msg/Quaternion.msg: -------------------------------------------------------------------------------- 1 | float32 x 2 | float32 y 3 | float32 z 4 | float32 w 5 | -------------------------------------------------------------------------------- /roboteq_diff_msgs/msg/Twist.msg: -------------------------------------------------------------------------------- 1 | Vector3 linear 2 | Vector3 angular 3 | -------------------------------------------------------------------------------- /roboteq_diff_msgs/msg/Vector3.msg: -------------------------------------------------------------------------------- 1 | float32 x 2 | float32 y 3 | float32 z 4 | -------------------------------------------------------------------------------- /roboteq_diff_msgs/package.xml: -------------------------------------------------------------------------------- 1 | 2 | roboteq_diff_msgs 3 | 0.1.0 4 | The roboteq_diff_msgs package 5 | Chad Attermann 6 | Chad Attermann 7 | BSD 8 | https://github.com/ecostech/roboteq_diff_driver.git 9 | 10 | catkin 11 | 12 | message_generation 13 | std_msgs 14 | 15 | message_runtime 16 | std_msgs 17 | 18 | -------------------------------------------------------------------------------- /roboteq_diff_msgs/srv/RequestOdometryCovariances.srv: -------------------------------------------------------------------------------- 1 | std_msgs/Empty empty 2 | --- 3 | OdometryCovariances odometry_covariances 4 | -------------------------------------------------------------------------------- /roboteq_diff_msgs/srv/RequestParam.srv: -------------------------------------------------------------------------------- 1 | string name 2 | 3 | --- 4 | 5 | int32[] ints 6 | float32[] floats 7 | string[] strings 8 | --------------------------------------------------------------------------------