7. Using the DC motor driver interface (Pololu SMC G2)

The DC motors for the main drive wheel of the robot are driven by a Pololu Simple Motor Controller (SMC) G2 <https://www.pololu.com/product/1367>. A C++ driver for interfacing with this board over the I2C interface is included in this repository at the relative path:

catkin_ws/src/asclinic_pkg/src/drivers/src/pololu_smc_g2/

The i2c_motors_and_servos.cpp is setup to use this Pololu SMC G2 driver, and the following steps detail how you can add this functionality to your own ROS C++ I2C node.

Note

The “controller” in the name “Simple Motor Controller” should be understood correctly.

What it is: The Pololu SMC G2 controls the duty cycle of the pulse width modulation (PWM) signal that power the motor based on the duty cycle that is requested via an I2C command. Other interfaces are possible for requesting the duty cycle (USB, TTL serial, analog voltage (potentiometer), and hobby radio control (RC)) but are not described in this wiki.

What it is NOT: The Pololu SMC G2 does NOT control the rotational speed of the motor. The board has NO options for receiving feedback of the motors velocity (e.g., via an encoder) and controlling the rotational velocity based on the feedback.

Step 1. Include the Pololu SMC G2 header in your I2C node by adding the following:

#include "pololu_smc_g2/pololu_smc_g2.h"

Step 2. Instantiate two Pololu_SMC_G2 objects as member variables for your node so that it can be accessed from all functions within the node; one is for the left wheel and the other for the right wheel. To do this, locate the lines of code where you instantiate the I2C_Driver object, which should something like this:

// MEMBER VARIABLES FOR THIS NODE:
// > For the I2C driver
const char * m_i2c_device_name = "/dev/i2c-1";
I2C_Driver m_i2c_driver (m_i2c_device_name);

and add the following lines of code just after:

// > For the Pololu Simple Motor Controller (SMC) driver
const uint8_t m_pololu_smc_address_left = 13;
Pololu_SMC_G2 m_pololu_smc_left (&m_i2c_driver, m_pololu_smc_address_left);

const uint8_t m_pololu_smc_address_right = 14;
Pololu_SMC_G2 m_pololu_smc_right (&m_i2c_driver, m_pololu_smc_address_right);

Where the m_i2c_driver variable is the variable of type I2C_Driver that should already be in your code.

Important

The default I2C address of the Pololu SMC G2 board is 13 (i.e., 0x0d). Hence to use two Pololu SMC G2 on the same I2C bus, you need to make the addresses different.

The I2C address of the Pololu SMC G2 is software selectable in the range 1 to 127 by using the windows-based software provided by Pololu.

The code snippet in this step suggests using address 13 for the left wheel and address 14 for the right wheel.

Note

As stated on this page of the user guide: I2C is disabled by default, so you must enable it by checking the “Enable I2C” checkbox in the “Input settings” tab of the Simple Motor Control Center G2.

Step 3. Initialise the Pololu SMC G2 board and set limits on:

  • The maximum current allowed

  • The maximum duty cycle allowed

  • The maximum change in duty cycle per update period increment of the board (referred to in teh documentation as acceleration for increase in duty cycle and deceleration for decreases in duty cycle).

by adding the following code to the main function of your I2C node.

// SET THE CONFIGURATION OF EACH MOTOR CONTROLLER

// Specify the various limits
int new_current_limit_in_milliamps = 5000;
int new_max_duty_cycle_limit = 2560;
int new_max_accel_limit = 1;
int new_max_decel_limit = 5;

// Initialise a pointer for iterating through
// the Pololu SMC objects
Pololu_SMC_G2 * pololu_smc_pointer;

// Initialise each of the Pololu SMC objects
// with the limits specified above.

// Iterate over the pololu objects
for (int i_smc=0;i_smc<2;i_smc++)
{
  // Point to the appropriate motor controller
  if (i_smc==0)
    pololu_smc_pointer = &m_pololu_smc_left;
  else
    pololu_smc_pointer = &m_pololu_smc_right;

  // Display the object about to be initialised
  ROS_INFO_STREAM("Now initialising SMC with I2C address " << static_cast<int>(pololu_smc_pointer->get_i2c_address()) );

  // Call the Pololu SMC initialisation function
  bool verbose_display_for_SMC_init = false;
  bool result_smc_init = pololu_smc_pointer->initialise_with_limits(new_current_limit_in_milliamps,new_max_duty_cycle_limit,new_max_accel_limit,new_max_decel_limit,verbose_display_for_SMC_init);

  // Display if an error occurred
  if (!result_smc_init)
  {
    ROS_INFO_STREAM("FAILED - while initialising SMC with I2C address " << static_cast<int>(pololu_smc_pointer->get_i2c_address()) );
  }
}

Note

The meaning of the limits and update period are described on the Motor Setting page of the Pololu SMC G2 documentation.

The default update period of the board is 1 millisecond, hence 1000 Hz update rate.

The base units that the board uses for duty cycle are a linear scale from 0 (for 0% duty cycle) to 3200 (for 100% duty cycle). These are the units used for the acceleration and deceleration limits of the board, hence, an acceleration limit of 1 means the duty cycle can increase by 1/32 of a percent per the update period (1ms by default).

Step 4. If it does not already exist, then define a LeftRightFloat32 message type by adding a file named :code:`LeftRightFloat32.msg to the relative path:

catkin_ws/src/asclinic_pkg/msg/

And write the following for the contents of the file:

float32 left
float32 right

Step 5. Include the LeftRightFloat32 message type in your I2C node by adding the following with the other includes of your I2C node:

// Include the asclinic message types
#include "asclinic_pkg/LeftRightFloat32.h"

// Namespacing the package
//using namespace asclinic_pkg;

At this stage, if you try to compile your I2C node with catkin_make, it will likely fail because the headers LeftRightFloat32.h and pololu_smc_g2.h are not found. The CMakeLists.txt needs to be adjusted to give the required compilation directives.

Note

If you uncomment the line using namespace asclinic_pkg;, then you can leave off the asclinic_pkg:: wherever it appears in the following steps.

Step 6. Adjust the CMakeLists.txt to add the LeftRightFloat32.msg to the following part:

add_message_files(
  FILES
  TemplateMessage.msg
  LeftRightFloat32.msg
)

Step 7. Adjust the CMakeLists.txt to add the pololu_smc_g2.cpp as an executable to your I2C node, i.e., in a form similar to the following:

add_executable(i2c_motors_and_servos    src/nodes/i2c_motors_and_servos.cpp
                                        src/drivers/src/i2c_driver/i2c_driver.cpp
                                        src/drivers/src/pololu_smc_g2/pololu_smc_g2.cpp)

Step 8. Compile your I2C node with catkin_make to check that the above steps are correctly implemented.

Note

Ensure that you have the latest version of the Pololu SMC G2 driver from the repository, i.e., ensure the the contents of your repository at the relative path:

catkin_ws/src/asclinic_pkg/src/drivers/src/pololu_smc_g2/

is up to date with the contents of the same directory in the main repository.

In order actually command your main drive wheels, you will need to create a subscriber for sending commands to your I2C node, and then in the subscriber callback send those commands over the I2C interface.

Step 9. Add a subscriber to the main function of your I2C node for responding to requests to command the servo:

// Initialise a subscriber for the duty cycle of the main drive motors
ros::Subscriber set_motor_duty_cycle_subscriber = nodeHandle.subscribe("set_motor_duty_cycle", 1, driveMotorsSubscriberCallback);

Note

Be sure to change the nodeHandle to be appropriate for the namespace within which you want this topic to operate.

Step 10. Add the subscriber callback to your I2C node. The subscriber callback should read the requested duty cycle for the left and right wheel from the LeftRightFloat32 type message received, and send the command to the Pololu SMC G2 over I2C using the function set_motor_target_speed_percent that is provided by the Pololu SMC G2 driver.

void driveMotorsSubscriberCallback(const asclinic_pkg::LeftRightFloat32& msg)
{
  ROS_INFO_STREAM("Message received with left = " << msg.left << ", right = " << msg.right);

  // Clip the data to be in the range [-100.0,100.0]
  // > For the left value
  float pwm_duty_cycle_left = msg.left;
  if (pwm_duty_cycle_left < -100.0f)
    pwm_duty_cycle_left = -100.0f;
  if (pwm_duty_cycle_left > 100.0f)
    pwm_duty_cycle_left = 100.0f;
  // > For the right value
  float pwm_duty_cycle_right = msg.right;
  if (pwm_duty_cycle_right < -100.0f)
    pwm_duty_cycle_right = -100.0f;
  if (pwm_duty_cycle_right > 100.0f)
    pwm_duty_cycle_right = 100.0f;

  // Initialise one boolean variable for the result
  // of all calls to Pololu_SMC_G2 functions
  bool result;

  // SET THE TARGET DUTY CYCLE FOR EACH MOTOR CONTROLLER
  // > NOTE: it may be necessary to negate one or both
  //   of the duty cycles depending on the polarity
  //   with which each motor is plugged in

  // > Set the LEFT motor controller
  result = m_pololu_smc_left.set_motor_target_speed_percent(pwm_duty_cycle_left);
  if (!result)
    ROS_INFO_STREAM("FAILED - Pololu SMC - set motor percent NOT successful for I2C address " << static_cast<int>(m_pololu_smc_left.get_i2c_address()) );

  // > Set the RIGHT motor controller
  result = m_pololu_smc_right.set_motor_target_speed_percent(-pwm_duty_cycle_right);
  if (!result)
    ROS_INFO_STREAM("FAILED - Pololu SMC - set motor percent NOT successful for I2C address " << static_cast<int>(m_pololu_smc_right.get_i2c_address()) );
}

Step 11. Compile your I2C node with catkin_make, launch your I2C node, and send a message to command the duty cycle of the drive wheels. You can send a message from command line as follows:

rostopic pub --once <namespace_of_your_topic>/set_motor_duty_cycle asclinic_pkg/LeftRightFloat32 "{left: 10.1, right: 10.1}"

Where <namespace_of_your_topic> is set appropriate to the nodeHandle you used when subscribing to the topic in Step 9 above. When this message is received by your I2C node, the callback function of Step 10 above will send the I2C command to the Pololu SMC G2 boards to set the duty cylce of the supply to each motor to be 10.1%.

Step 12. As the subscriber callback in Step 10 may change the duty cycle requested, it is beneficial to publish the duty cycle that was actually sent to the Pololu SMC G2 boards so that this information is available to any other node that needs it.

Add a member variable for the publisher (where you instantiate the other member variables):

// > Publisher for the current duty cycle
//   setting of the main drive motors
ros::Publisher m_current_motor_duty_cycle_publisher;

Initialise the publisher in the main function:

// Initialise a publisher for the current duty cycle setting of the main drive motors
m_current_motor_duty_cycle_publisher = nodeHandle.advertise<asclinic_pkg::LeftRightFloat32>("current_motor_duty_cycle", 10, true);

Publish the current motor duty cycle just after they are set, i.e., at the end of the driveMotorsSubscriberCallback function:

// Publish the motor duty cycles
asclinic_pkg::LeftRightFloat32 msg_current_duty_cycle;
msg_current_duty_cycle.left  = pwm_duty_cycle_left;
msg_current_duty_cycle.right = pwm_duty_cycle_right;
m_current_motor_duty_cycle_publisher.publish(msg_current_duty_cycle);

You can view an example of these steps implemented in the i2c_motors_and_servos.cpp file of the main repository.



Creative Commons License
Paul N. Beuchat, 2023