Controlling the motors

Keywords: #ros2 #rp6 #quail

The Motor Driver Node

Getting an output from the PI

If we’re going to drive the motors, we’re going to need some method of interfacing with the GPIO output pins on the Pi. After doing some reading and researching, I think that pigpio (Probably pronounced pi-gpio, and not pig-pio like my mind keeps reading it as) perfectly fits the bill for what we are looking for.

There is a small potential caveat about using pigpio, which is that under normal operation, it requires sudo access. Thankfully, we can get around this through utilising the pigpio daemon, which only needs to be ran once on each startup. The libraries for interfacing with the daemon are within the pigpiod_if2 library. Install instructions, as well as how to utilise the pigpio daemon can be found here: Download pigpio

Using this in ROS2

Once you have the pigpio library installed, importing it into our project is relatively trivial, through modifying our CMakelists to contain:

target_link_libraries(motor_driver_node
  ${rclcpp_lifecycle_LIBRARIES}
  ${std_msgs_LIBRARIES}
  pigpiod_if2
)

And inside of our header:

#include <pigpiod_if2.h>

This ensures that the linker knows to look for the pigpiod_if2 library when linking everything together.

Enabling the GPIO
pi_  = pigpio_start(NULL, NULL);

Once we have called start, we can enable each pin we want to access with:

set_mode(pi_, PIN_NUMBER, PI_OUTPUT); 
Activating the GPIO

Sending a command to start the PWM signal on the output is as simple as:

int hardware_PWM(int pi, unsigned gpio, unsigned PWMfreq, uint32_t PWMduty);

The current node structure

,-------------------------------.                         
|quail_base                     |                         
|-------------------------------|                         
|-------------------------------|                         
|Main()                         |                         
|--ControllerNode.on_configure()|                         
|--ControllerNode.on_activate() |                         
|spin()                         |                         
`-------------------------------'                         
                |                                         
                |                                         
,-------------------------------.                         
|ControllerNode : LifeCycleNode |                         
|-------------------------------|  ,---------------------.
|-------------------------------|  |MotorDriver          |
|on_configure()                 |  |---------------------|
|--MotorDriver.configure()      |  |---------------------|
|on_activate()                  |--|configure()          |
|on_deactivate()                |  |drive_motors_direct()|
|on_cleanup()                   |  |shutdown_driver()    |
|on_shutdown()                  |  `---------------------'
|--MotorDriver.shutdown_driver()|                         
`-------------------------------'  

At this point, our system consists of three primary components: quail_base, ControllerNode, and the MotorDriver. quail_base is responsible for configuring and spinning up our ControllerNode, while The MotorDriver is what interfaces directly to the pigpio library. Most of the interesting stuff (In my opinion) is happening in the ControllerNode. So it gets its own little section.

More on the ControllerNode

At the heart of our control loop sits the ControllerNode. This node will manage all aspects of quail, from driving the motors, to reading the IR sensors. This isn’t a software design that shall scale well if we were building an complex system, but given how limited our scope of capabilities is for quail, I’m content to not over-complicate things.

The ControllerNode has been designed to be something called a ‘managed node’, more specifically, a LifeCycleNode. These are a new node type introduced in ROS2. You can find a high level overview of what this means Here, and a usage details Here.

Essentially, the LifeCyleNodeType allows us to sequentially configure, activate, and shutdown the node when desired from the base executable. This is especially useful for us, as it allows the system to configure our outputs, and ensure the configuration is successful prior to activating the control loop, and sending outputs to the GPIO.

What’s next?

Next will be the encoders. I’ll be using the same pigpio library to read the outputs from the encoders, and from there we can start getting some closed loop control going! How exciting.