In this post, we report the experience and lesson learned when commanding motors on TurtleBot3.
System Configuration
- Hardware: Turtlebot3 Burger
- Raspberry Pi Model: 3B+
- OS: Ubuntu 20.04
- ROS: Noetic
Task Description
The task is to use ROS to send commands to control the motors on Turtlebot3.
Recall the basic setup: Two motors of the robot are controlled by the OpenCR board, which is connected to Raspberry Pi via USB.

To control the motors, we need to
- Install the motor driver on OpenCR.
- Launch
roscore
on Raspberry Pi. - Make
roscore
communicate with the serial node. - Publish motor control command.
Motor Driver
To install the driver, we can follow the official instruction Turtlebot3 - OpenCR Setup. Note that there is a sanity check at the end of the tutorial. We should be able to push the buttons mentioned in the tutorial to trigger motor movement. If the sanity check fails, please follow the instruction in the section "Click here to expand more details about the firmware upload using Arduino IDE".

Launch roscore on Raspberry Pi
Here we deviate from the official TurtleBot3 tutorial. In the official tutorial, the roscore
is launched on the remote PC and then we "bring up" TurtleBot3 on Raspberry Pi. See more details at TurtleBot3 - Quick Start Guid - Bringup.
Because we only want to test the motor control part and we don't want to launch other things prepared by the TurtleBot3 automatically, we should not use the turtlebot3_bringup package. Instead, we can build the turtlebot packages manually and then launch roscore on Raspberry Pi directly.
First, we need to checkout turtlebot packages. The turtlebot3 packages can be found at ROBOTIS-GIT/turtlebot3. This repository is the ros workspace we need. Here is the project structure of my ros workspace:
ros_workspace └── src ├── CMakeLists.txt -> /opt/ros/noetic/share/catkin/cmake/toplevel.cmake ├── ISSUE_TEMPLATE.md ├── LICENSE ├── README.md ├── turtlebot3 ├── turtlebot3_bringup ├── turtlebot3_description ├── turtlebot3_example ├── turtlebot3_navigation ├── turtlebot3_slam └── turtlebot3_teleop
If we build the workspace, it will fail due to missing dependencies. The problem is that the turtlebot3_bringup package depends on turtlebot3_msgs and we don't have turtlebot3_msgs in our workspace. This dependency requirement can be found in the turtlebot3_bringup/package.xml file.
The fix is simple, we just need to check out the turtlebo3_msgs package at ROBOTIS-GIT/turtlebot3_msgs.
Note: Most turtlebot packages list other related resources. For example, see Open Source related to TurtleBot3.
Now we can build the workspace by executing the following commands:
cd ~/catkin_ws catkin_make source devel/setup.bash
If the catkin_make
command does not exist, make sure the following command is executed
source /opt/ros/noetic/setup.bash
After the workspace is built, we can now launch roscore
by executing the command on Raspberry Pi:
roscore
Communication between Serial Node and Ros Core
Now we need to "bridge" the OpenCR and Raspberry Pi. We've uploaded code to OpenCR when we set up the OpenCR board. The code enables OpenCR to behave like a ROS node. Therefore, this "bridge" step is similar to a node joining a cluster.
The "bridge" operation is handled by rosserial
and we need to install it on Raspberry Pi.
We can install the rosserial package by using the command
sudo apt-get install ros-noetic-rosserial-arduino
Because OpenCR is connected to Raspberry Pi via USB, we need to tell ros the serial port to listen to.
Here is the interesting part, I still cannot find any formal way to detect the serial devices. But I found one solution online:
# Save the list of devices to the file ls -1 /dev > /home/ubuntu/tmp/before.txt # Plug the usb ls -1 /dev > /home/ubuntu/tmp/after.txt # Compare the differnece diff -u /home/ubuntu/tmp/before.txt /home/ubuntu/tmp/after.txt
The output will look like the following:
--- /home/ubuntu/tmp/before.txt 2022-06-11 00:10:32.897681353 +0000 +++ /home/ubuntu/tmp/after.txt 2022-06-11 00:11:10.681737940 +0000 @@ -307,6 +307,7 @@ ptyzf random rfkill +serial shm snapshot snd @@ -380,6 +381,7 @@ tty7 tty8 tty9 +ttyACM0 ttyAMA0 ttyS0 ttya0
In our case, we know the port is ttyACM0
.
It's time to establish the communication between the serial node and ros core on Raspberry Pi. We can execute the commands below:
cd ~/ros_workspace source devel/setup.bash rosrun rosserial_python serial_node.py _port:=/dev/ttyACM0 _baud:=115200
Note: Each time we launch a new terminal, we need to execute the source ~/ros_workspace/devel/setup.sh
command. It's better to add it to the ~/.bashrc
file.
Publish Motor Control Commands
The goal is to trigger motor movement. However, we don't need to go to the low level and communicate with the motor directly because ultimately what we want is to control the robot movement. The robot movement can be controlled by setting the linear and angular velocities. The API provided in ROS for this purpose is geometry_msgs/Twist message. The unit in the message is m/s
and rad/s
.
Note:
- For more information about the units used in ROS, please refer to Standard Units of Measure and Coordinate Conventions.
- For more details about how the robot velocity is related to motor state, please check Mobile Robot Kinematics - From Motor State to Robot State.
To publish a command, we can use the following command:
rostopic pub -r 10 /cmd_vel geometry_msgs/Twist '{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.5}}'
How does the /cmd_vel
topic work? We notice that if we stop publishing to the /cmd_vel
topic, the wheels stop moving. It suggests that the system requires a constant stream of commands to keep moving. This behavior is also mentioned in Understanding ROS using Turtlesim. It seems that the system is modeled as a state machine and we need to provide a stream of ticks. This is a reasonable approach because in this way we can no longer tell the difference between a simulated system and a real physical system. The simulation is usually trigged by ticks.
But what is the rate of the tick? There must be something in the code we upload to the OpenCR because the code defines the behavior of the serial node. The code we upload can be found at ROBOTIS-GIT/OpenCR/tree/master/arduino/opencr_arduino/opencr/libraries/turtlebot3/examples/turtlebot3_burger/turtlebot3_core.
If we check the turtlebot3_core_config.h
file, we can see the line below:
#define CONTROL_MOTOR_SPEED_FREQUENCY 30 //hz
#define CONTROL_MOTOR_TIMEOUT 500 //ms
One of the usages of these two variables is in the turtlebot3_core.ino
file: (See code)
if ((t-tTime[0]) >= (1000 / CONTROL_MOTOR_SPEED_FREQUENCY))
{
updateGoalVelocity();
if ((t-tTime[6]) > CONTROL_MOTOR_TIMEOUT)
{
motor_driver.controlMotor(WHEEL_RADIUS, WHEEL_SEPARATION, zero_velocity);
}
else {
motor_driver.controlMotor(WHEEL_RADIUS, WHEEL_SEPARATION, goal_velocity);
}
tTime[0] = t;
}
As we can see in the code, the velocy is set up zero when there is a timeout event.
----- END -----
©2019 - 2024 all rights reserved