A few years down the line from this discussion, how has the state-of-art evolved in this topic? I have very little ROS experience and would like some feedback on design. I have a straightforward skid-drive robot with an ESP32 microcontroller running micro-ros.
Right now, I have the micro-ros code doing like this:
Subscribe to cmd_vel, do the reverse kinematics, control the motor driver
Read the encoders, do the pid, publish /joint_states
also from the encoders do the direct kinematics and publish /odom
Publish other onboard stuff, like /imu, /temperature, do basic safety checks, etc.
On the ROS2 side:
Have the robot_state_publisher do the tf generation from /joint_states
Have an odom_to_tf node to generate odom frame from /odom.
At the moment, I’m at a crossroads. To move forward, I see two options:
move all the control to the micro-ros, i.e. generate the tf for the odom on the micro-ros side.
Drop the controller code from the micro-ros and rely on ros2_control + joint control messages from micro-ros. (I have only a nebulous idea of how the hardware interface would look; any pointer to relevant documentation is welcome)
On one hand, the robot is very simple, and thus, the control is doable on the micro-ros side. On the other hand, I’d like to have as “canonical” a design as possible. Also, in the future, I might want to integrate with Gazebo.
If you want smooth integration with Gazebo, then you should keep most of the code in ROS2 and let the microcontroller be a dumb driver data reader/sender. That way you can test your odometry, kinematics and other stuff using the same code for the real and simulated robots.
However, you have to draw the line somewhere. I would, for example, keep the feedback loop handling velocity control in the microcontroller. I’m not even sure ROS 2 would be able to process the data fast enough for smooth control.
The “as much in the microcontroller as possible” approach appeals to me because I like having a self-contained system. You just power it on, and it immediately works. There’s no booting time; nothing can get misconfigured or broken because of some weird version conflict. When you give it to someone else, there’s nothing to install, explain, or fiddle with.
Cons:
Programming for microcontrollers is annoying.
Reusing code is difficult in microcontrollers. To alleviate that, I’m writing micro_rosso.
While formative, rewriting a differential controller and odometry for the n-th time is annoying, especially when there already exist standard implementations in ROS2.
The microcontroller’s code is not very flexible and is hard to configure. For example, coefficients to tune your odometry, button layouts for joysticks, and so on. Micro-ROS provides a mechanism for configuration parameters, but it’s pretty cumbersome to use.
While the microcontroller boots up immediately, you have to wait for the ROS to boot to do anything of interest.
But in the end, I just want to do it the most standard way so I can hand over the project to other people with minimal fuss.
That makes lot of sense. That would be similar to how you use smart servos like Dynamixels: your ROS controller would request a rotation speed, and a microcontroller directly connected to the motor driver would maintain that speed. The only difference is that a Dynamixel has a propietary protocol, while my robot would use standard topics. (I’m assuming it’s possible for ros2_control to output commands as topics).
I think I’ll finish the system in the direction I was going (/cmd_vel and /odom on the mictrocontroller), and then dive into ros2_controller to create an alterantive implementation. As reference, the robot I’m working is a Lynxmotion A4WD3 chassis, the tracked version. My code for the microcontorller is here and the associated ROS code is here.