Problem reading two Pololu 12V motor encoders with Uno and Pololu MC33926

I have read the FAQ on asking questions and done my own research to solve this problem but come up short. I now ask for your help. Please let me know if I break etiquette or have missed something obvious. We are using an Arduino Uno along with the following:

Hardware: Pololu 12V motor https://www.pololu.com/product/3218
Hardware: Pololu MC33926 Motor Shield https://www.pololu.com/product/2503
MotorShield Library: https://github.com/pololu/dual-mc33926-motor-shield

My target goal is to read the encoders on both of these motors simultaneously to determine RPM for each of them as quickly as I can in real time. That RPM will then be used to enact rudimentary feedback control in the future. We are encountering the following issue: each motor encoder can determine RPM fine when the other is commented out of the code. However, when used together, the outputs are erroneous.

When I run the code as is, I receive a strange error of one RPM integer count starting low and one starting high, which then proceeds to increase or decrease respectively. Serial monitor output:

Dual MC33926 Motor Shield
56
998
61
783
78
641

However, if I comment out either of the RPM loops (identical code except with either loop 1 or loop 2 commented away), the remaining encoder can successfully determine the RPM of the motor. Motor 1:

Dual MC33926 Motor Shield
56
56
56
56
56

Motor 2:

Dual MC33926 Motor Shield
54
54
54
54

Here is the code:

/*************************************************************
  Motor Shield 2-Channel DC Motor Demo
  by Randy Sarafan

  For more information see:
  https://www.instructables.com/id/Arduino-Motor-Shield-Tutorial/

*************************************************************/
#include <Wire.h>
#include <SPI.h>
#include <Adafruit_LSM9DS1.h>
#include <Adafruit_Sensor.h>  // not used in this demo but required!
#include <DualMC33926MotorShield.h>

DualMC33926MotorShield md;
void stopIfFault()
{
  if (md.getFault())
  {
    Serial.println("fault");
    while (1);
  }
}

int inPin = 5;   // pushbutton connected to digital pin 5
int inPin2 = 6;   // pushbutton connected to digital pin 6
int val1 = 0;         // variable to store the read value
int val2 = 0;         // variable to store the read value
int val3 = 0;         // variable to store the read value
int val4 = 0;         // variable to store the read value

//Max K=400 mapped to 255
int k = 200;
int k1 = k;
int k2 = k;
float minute = 0;
int t1 = 0;
int t2 = 0;
int dt = 0;
int rpm = 0;
int count = 0;
int count2 = 0;




void setup() {
  //Set up Motor Controller
  Serial.begin(9600);
  Serial.println("Dual MC33926 Motor Shield");
  md.init();
  pinMode(inPin, INPUT);      // sets the digital pin 5 as input
  pinMode(inPin2, INPUT);      // sets the digital pin 6 as input
  t1 = millis();

}



//Andrew: We can consider using the F() function to utilize flash instead of SRAM for string-only println messages.
//See here: https://www.arduino.cc/reference/en/language/variables/utilities/progmem/
void loop() {

  //Motor A forward
  md.setM1Speed(k1);
  stopIfFault();
  Serial.print(F("M1 current: "));
  Serial.println(md.getM1CurrentMilliamps());

  //Motor B backward
  md.setM2Speed(-k2);
  stopIfFault();
  Serial.print(F("M2 current: "));
  Serial.println(md.getM2CurrentMilliamps());


  //Encoders
  val1 = digitalRead(inPin);   // read the input pin
  val3 = digitalRead(inPin2);   // read the input pin
  Serial.println(val1);
  Serial.println(val2);

  //Calculates RPM (corrected indentation)
  //Full count is 1800 for one geared revolution
  //Loop1

  if (val1 != val2) {
    count = count + 1;
    val2 = val1;
    //Serial.print("Count: ");
    //Serial.println(count);
    if (count == 1800) {
      t2 = millis();
      dt = t2 - t1;
      t1 = t2;
      rpm = 60000 / dt;
      count = 0;
      Serial.print(F("Motor 1 RPM: "));
      Serial.println(rpm);
    }
  }

  //Loop2
  if (val3 != val4) {
    count2 = count2 + 1;
    val4 = val3;
    //Serial.print("Count2: "); Serial.println(count);
    //Full count is 1800 for one geared revolution
    if (count2 == 1800) {
      t2 = millis();
      dt = t2 - t1;
      t1 = t2;
      rpm = 60000 / dt;
      count2 = 0;
      Serial.print(F("Motor 2 RPM: ")); Serial.println(rpm);
    }
  }
}

To save time, I will note the following items that I have researched and explored as potential culprits and solutions:

  • Baud. We have experimented with changing the baud rates without seeing difference.
  • Printing Strings. This was my initial suspicion as I did research and found that printing strings could be an issue if included at high rates. I removed traditional strings from the code and replaced with F() outputs.
  • Printing any statements other than RPM integer. You will note in my output code that there are only the RPM numbers listed as loop output statements. I attempted to isolate prints as the issue without success.
  • Faulty Motor hardware. Both motors were ordered brand new. Both display the correct RPM in the serial monitor window when evaluated individually with one of the loops commented out. RPM for output shaft was confirmed visually by inspection of rotation for 30 seconds, multiple times. While each motor is slightly different physically, as expected, both conform closely to target RPM.
  • External Library Shenanigans. I looked through the c++ code for the motor shield library and did not see anything which would cause me to believe it was the cause.

Potential Solutions:

  • Interrupt Service Routines. I have read a little bit on ISR’s here but I am still uncertain how to structure and proceed with designing this into a dual encoder code.
  • Separate Encoder wiring. It was only towards the end of troubleshooting yesterday that I realized my colleague had attempted to save pinspace by splicing both power and ground to the encoders. To be clear, there is one male pin going into the 5V pinspace on the shied which is spliced into two male pins which each connect to the encoder power (blue wire).
  • Different Motor Shield. This was chosen specifically because it was designed to run dual Pololu motors. However, if there is another shield with easy layout/mapping to use with Arduino then our lab can probably buy it.

If there is any other helpful information which I can provide, please let me know.

Hello.

It would not be surprising to get errors using two encoders without interrupts. I suggest looking at the Reading Rotary Encoders page on Arduino’s website. On the page, you can find several helpful examples, so you might refer to them to get started. It also has libraries with functions that take care of handling the interrupts for you.

- Amanda

Followup question: Could we successfully use the encoders only reading channel A instead of both A and B?

Hello I would like to modify the Pololu Motor Controller library found here: https://www.pololu.com/docs/0J20/3.l

I would like to use 2 12V 25D motors with 48 CPR encoders. Because I am using an arduino uno and pololu motor shield, I only have two interrupt pins available. Instead of using channel a and b on the motors, I would like to just use channel A on both.

Have you encountered anything like this before? And will this library work for Arduino and motor shield? I am using motor shield found here: https://forum.pololu.com/t/pololu-dual-mc33926-motor-driver-shield-for-arduino/5469

Greetings,

I have this motor: https://www.pololu.com/product/3218
I have this pololu motor shield: https://www.pololu.com/product/2503

We attempted to write our own code to interpret the encoder but were advised it was incredibly inefficient. Can you please point me to the right library?

We merged most of your recent posts across our forum into this thread since they are about the same issue. Please keep discussions on the same topic to a single thread, and please don’t post the same thing in multiple places.

Yes; the distance and speed can be measured using one channel of an encoder, but you will have less resolution and you need to use both channels (A and B) if you want to determine direction.

The WheelEncoders library from our AVR Library you linked was specifically written for our 3pi robot and Orangutan robot controllers. You could make our encoder library work for your setup, but I think it would be easier for you to use one of the interrupt supported libraries on the “Reading Rotary Encoders” page on Arduino’s website that I linked to in my previous post. The encoder on your motors is a standard quadrature encoder, so any quadrature encoder code can work. I suggest reading this Wikipedia page on rotary encoders to better understand how quadrature encoders work.

- Amanda

Just wanted to give a followup on the code that ended up working. We’re also having trouble getting our motors to draw more than 2.5A combined, and our understanding was that we could draw 3A each through the motor controller, but this is a different problem. Currently the code works modestly well until above 60 rpm.

//*************************************************************/
#include <Wire.h>
#include <SPI.h>
#include <DualMC33926MotorShield.h>

#define encodPinA1      2                       // encoder A pin motor 1
#define encodPinB1      6                       // encoder B pin motor 1
#define encodPinA2      3                       // encoder A pin motor 2
#define encodPinB2      5                       // encoder A pin motor 2

//Max K=400
//float Kp =  0.4;                             // PID proportional control Gain
//float Kd =  1.5;                                // PID Derivitave control gain

float Kp =  0.8;                                // PID proportional control Gain
float Kd =  1.5;                                // PID Derivitave control gain

int speed_req = 120;
int e_speed_sum;
float pidTerm = 0;
int error = 0;
int last_error = 0;
int k = 200;
int k_new = 0;
int k1 = k;
int k2 = k;
int t1 = 0;
int t2 = 0;
volatile long count1 = 0;                        // rev counter
volatile long count2 = 0;
int LOOPTIME = 50;
int speed_act1 = 0;                              // speed (actual value)
int speed_act2 = 0;
int current = 0;                                // in mA
int dt = 0;
///About 76 RPM for 1 minute at 300///18
///About 49 ROM for 1 minute at 200///13

///200 45RPM
///175
//125-30RPM
//150-37RPM
DualMC33926MotorShield md;

void stopIfFault()
{
  if (md.getFault())
  {
    Serial.println("fault");
    while (1);
  }
}
void initial_k()
{
  if (speed_req == 30)
  { k1 = 160;
    k2 = 160;
  }

  if (speed_req == 45)
  { k1 = 190;
    k2 = 190;
  }

  if (speed_req == 60)
  { k1 = 240;
    k2 = 240;
  }

  if (speed_req == 75)
  { k1 = 300;
    k2 = 300;
  }

  if (speed_req == 90)
  { k1 = 335;
    k2 = 335;
  }

  if (speed_req == 105)
  { k1 = 370;
    k2 = 370;
  }

  if (speed_req == 120)
  { k1 = 399;
    k2 = 399;
  }

}
void setup () {
  initial_k();
  //Set up Motor Controller
  Serial.begin(115200);
  Serial.println("Dual MC33926 Motor Shield");
  md.init();

  t1 = millis();

  //Set up Encoders
  pinMode(encodPinA1, INPUT);
  pinMode(encodPinB1, INPUT);
  digitalWrite(encodPinA1, HIGH);                      // turn on pullup resistor
  digitalWrite(encodPinB1, HIGH);
  attachInterrupt(0, rencoder1, FALLING);

  pinMode(encodPinA2, INPUT);
  pinMode(encodPinB2, INPUT);
  digitalWrite(encodPinA2, HIGH);                      // turn on pullup resistor
  digitalWrite(encodPinB2, HIGH);
  attachInterrupt(1, rencoder2, FALLING);

}

void loop () {
  //Motor A forward
  k1 = constrain(k1, 0, 399);
  k2 = constrain(k2, 0, 399);
  md.setM1Speed(-k1);

  //Motor B backward
  md.setM2Speed(k2);


  t2 = millis() ;
  if (t2 - t1 >= LOOPTIME) {
    getMotorData1();
    getMotorData2();

    k1 = updatePid(k1, speed_req, abs(speed_act1));
    k2 = updatePid(k2, speed_req, abs(speed_act2));

    t1 = t2;

    // Serial.print("k1: ");
    // Serial.println(k1);
  }

}
void rencoder1()  {                                    // pulse and direction, direct port reading to save cycles
  if (digitalRead(encodPinB1) == HIGH)   count1 ++;             // if(digitalRead(encodPinB1)==HIGH)   count ++;
  else                      count1--;                // if (digitalRead(encodPinB1)==LOW)   count --;
}

void rencoder2()  {                                    // pulse and direction, direct port reading to save cycles
  if (digitalRead(encodPinB2) == HIGH)   count2 ++;            // if(digitalRead(encodPinB1)==HIGH)   count ++;
  else                      count2--;                // if (digitalRead(encodPinB1)==LOW)   count --;
}

void getMotorData1()  {                                                        // calculate speed, volts and Amps
  static long countAnt1 = 0;                                                   // last count
  t2 = millis();
  dt = t2 - t1;
  speed_act1 = ((count1 - countAnt1) * (60 * (1000 / dt))) / (48 * 75) * 4.22222; // 48 pulses X 75 gear ratio = 3600 counts per output shaft rev
  current = md.getM1CurrentMilliamps();                                       // motor current
  Serial.print("RPM1: ");
  Serial.println(speed_act1);
  //   Serial.print("k1 ");
  // Serial.println(k1);
  //
  // Serial.print ("M1: ");
  // Serial.println(current);
  count1 = 0;
}
void getMotorData2()  {                                                        // calculate speed, volts and Amps
  static long countAnt2 = 0;                                                   // last count
  t2 = millis();
  dt = t2 - t1;
  speed_act2 = ((count2 - countAnt2) * (60 * (1000 / dt))) / (48 * 75) * 4.22222; // 48 pulses X 75 gear ratio = 3600 counts per output shaft rev
  current = md.getM2CurrentMilliamps();                                       // motor current
  Serial.print("RPM2: ");
  Serial.println(speed_act2);
  //  Serial.print("k2 ");
  // Serial.println(k2);
  //
  // Serial.print ("M2: ");
  // Serial.println(current);
  count2 = 0;
}

int updatePid(int number_k, int targetValue, int currentValue)   {             // compute PWM value
  float pidTerm = 0;                                                            // PID correction
  int error = 0;
  static int last_error = 0;
  error = abs(targetValue) - abs(currentValue);
  pidTerm = (Kp * error) + (Kd * (error - last_error));
  last_error = error;
  return constrain(number_k + int(pidTerm), 0, 399);
}