Greetings Arduino community. 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 Arduino Uno.
Hardware: Pololu 12V motor Pololu - 75:1 Metal Gearmotor 25Dx66L mm HP 12V with 48 CPR Encoder (No End Cap)
Hardware: Pololu MC33926 Motor Shield Pololu Dual MC33926 Motor Driver Shield for Arduino
MotorShield Library: GitHub - pololu/dual-mc33926-motor-shield: Arduino library for the Pololu Dual MC33926 Motor Driver 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. My secondary goal is to receive advice on how better to implement this code and interface with the difference aspects of motor-sensor hardware, so if you have any additional suggestions those are welcome. I have listed the code and outputs at the end of the post. To save time, I will note the following items up front 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 in this forum 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 from Pololu. 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 seen on the Pololu site).
- 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.
I will now give the specifics of my code and outputs.
/*************************************************************
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);
}
}
}
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:
Dual MC33926 Motor Shield
56
998
61
783
78
641
However, if I comment out either of the RPM loops (identical code to above 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
If there is any other helpful information which I can provide, please let me know. USA.
Your problem is attempting to use something from Instructables. Ditch that unworkable code and start over. Instructables are written for the most part by people who have no idea what they are doing, and there is no quality control whatsoever. But you aren't the first to be fooled, so don't worry about it.
Use the Pololu encoder library to read the encoders, not nonsense like this:
//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) { //Ummm, what about using "val3", the variable actually read?
A couple of things:
- Small correction: val3 wouldn't be used in that loop because it's reading the second motor encoder. val2 is a zero int we used for the count loop instead of going the ++ route because we're not experienced in arduino sketches yet. If this makes a non-trivial difference, let me know, and I will put time into better learning that structure.
- I listed the Dual MC33926 Motor Driver Shield library at the top of the post. To my knowledge, it does not have this function. Are you saying there's a separate library for these specific DC motor encoders? Because I cannot find one which explicitly targets this type of encoder, only literal wheel position encoders for line following robot wheels and such. For example, this library mentions a 3mm resolution as it is intended for rubber wheels, not encoder wheels: libpololu-avr/PololuWheelEncoders.h at master · pololu/libpololu-avr · GitHub
- This Pololu page is their documentation where I suspected such a library would be found: Pololu AVR C/C++ Library User’s Guide However, it is not super user friendly and I was under the impression that the Orangutan controllers were something different entirely.
Following your advice, we're going to begin testing and trying to integrate some different libraries and sketches such as Teensy. I am also assuming that reading this thoroughly would be a good use of time: Arduino Playground - RotaryEncoders. If you have any insight into whether one path is better than the other, that would be helpful. I will also list some potentially helpful links for those encountering similar problems.
-https://forum.pololu.com/t/motor-encoder-problem-with-arduino-uno/5539
-https://forum.pololu.com/t/motor-driver-controller-for-brewing-system-pump/6517/4
It appears a Pololu rep recommended their library for a 64 CPR motor in the help forums, so that's the direction I'll go. I guess my reading of their description lost something in translation. Will update if I find a solution.
Edit: Yup, here's the help page: Pololu - 3.l. Pololu Wheel Encoder Functions
I am also assuming that reading this thoroughly would be a good use of time: Arduino Playground - RotaryEncoders
Any quadrature encoder library will work better than what you have now (this one is best), and just about any reading you do on the topic will be very helpful.
Just wanted to give an update and bring the solution back for any future searchers. This code is working quite well at the low to medium speed range of our motors. We cannot seem to get the current draw above 2.5A for the higher speeds and are stuck around 60 RPM under run conditions (can get to 120 RPM in free spin). However, we can probably troubleshoot from here.
Here's the code which worked:
//*************************************************************/
#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);
}