Hey, guys! I need some guidance on how to measure the RPM of my brushed DC motor using an optical encoder. I will be using the RPM measurements in a PI controller as a feedback to compare to the target RPM value I will be specifying.
First, let me describe my current hardware setup.
- I am currently using a 6V motor that is supposed to rotate at 100 RPM while drawing 120 mA of current at no-load conditions. The motor has a stall torque of 9 kg/cm. (These were the numbers I could find, but I can't verify if these numbers are extremely accurate).
- I am using Sparkfun's GP1A57HRJ00F photo-interrupter with the breakout board they supply for it. I have soldered a 0.1 uF capacitor between the PWR and GND pins of the breakout board to prevent detection error.
- The motor is driving the following gear train:
Thus, the optical encoder should 192 counts per 1 revolution of the output shaft.
Now, I have tried 3 methods for measuring the RPM of the motor:
- Count the number of encoder pulses between each loop of the program. Divide the total count by the time it takes for the program to loop.
- Count the number of encoder pulses over specified period of time (I call this sampling time). Divide the total count by the specified time period.
- Measure the time elapses between encoder pulse. Take the reciprocal of this time interval.
Here, is my code for implementing all three methods:
#include <util/atomic.h>
//===== Encoder Pins =====//
const int encoderPin = 2;
volatile int encoderCount = 0;
int prevCount;
//===== Motor Pins & Variables =====//
const int DIR1 = 4;
const int EN1 = 5;
float motorVelocity;
//===== Timer Variables =====//
unsigned long currT;
unsigned long prevT;
unsigned long samplingT = 1.0e5;
//===== Motor Test Variables =====//
int testIndex = 1;
unsigned long prevIndexTimer;
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
pinMode(encoderPin, INPUT);
attachInterrupt(digitalPinToInterrupt(encoderPin), encoderPulse, RISING);
pinMode(DIR1, OUTPUT);
pinMode(EN1, OUTPUT);
}
void loop() {
// put your main code here, to run repeatedly:
currT = micros();
int localCount;
float localVelocity;
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
localCount = encoderCount;
localVelocity = motorVelocity;
}
// Method 1: Count the number of encoder pulses between each cycle of loop function
/*
float deltaT = ((float) (currT - prevT))/1.0e6;
motorVelocity = (localCount - prevCount)/deltaT;
prevCount = localCount;
prevT = currT;
*/
// Method 2: Count the number of encoder pulses over specified period of time
/*
if (currT - prevT >= samplingT) {
motorVelocity = (localCount - prevCount)/((float) samplingT/1.0e6);
prevT = currT;
prevCount = localCount;
}
*/
motorRPM_Test(100, 10);
Serial.print(currT/1.0e6);
Serial.print(" ");
// Serial.print(motorVelocity); // Output for Method 1 & 2
// Serial.println(localVelocity); // Output for Method 3
Serial.println();
}
void encoderPulse() {
encoderCount += 1;
// Method 3: Measure time elapsed between each encoder trigger
long currT2 = micros();
float deltaT = ((float) currT2 - prevT) / 1.0e6; // Calculate time interval between triggers
motorVelocity = 1 / deltaT;
prevT = currT2;
}
void motorRPM_Test(int PWM_Value, int testTime) {
// This function ramps up the motor speed from a chosen initial PWM value
// to 255 over a specified time interval.
int basePWM = PWM_Value;
int testInterval = testTime; // Time interval in seconds
// testIndex variable is used to make sure the initial PWM value is maintained every time the test cycles
unsigned long currIndexTimer = micros();
if (currIndexTimer - prevIndexTimer >= (testTime*1.0e6)) {
testIndex++;
prevIndexTimer = currIndexTimer;
}
int motorPWM = basePWM * testIndex + ((255 - basePWM) * (micros() / 1.0e6)) / testInterval;
digitalWrite(DIR1, HIGH);
analogWrite(EN1, motorPWM);
}
Then, I tested each method by ramping up the motor speed from an initial PWM value of 100 to 255 over a time period of 10 seconds. The following are graphs of the results I obtained:
- Method 1
- Method 2
- Method 3
As you can see, method 1 seems to be just hot garbage. I'm not sure if its an issue of implementation, but there isn't any real data to be gleamed from that mess.
Method 2 gives pretty good data, but it feels very "coarse" (I know this is due to the nature of the calculation). I have a feeling this might be good enough to achieve an O.K level of speed control, but I don't really know as this is my first time using a PI/PID controller.
Method 3 gives the most detailed data, but it's fatal flaw is that it cannot detect an RPM of 0 due to the way it is implemented. As the calculation is done in the encoderPulse function, which is only carried out if there is an encoder pulse, it's impossible to detect 0 RPM.
I believe I have two main routes of moving forward with this:
- Keep experimenting with the sampling time for method 2 to get as much detailed data as I can.
- Come up with a way to detect 0 RPM for method 3. The only way I could think of is if the encoderCount variable doesn't change for like 0.5 s, the micro-controller sets the value of motorRPM to 0. I'm not sure if this would work though.
What do you guys think would be the best method? If you have a better idea than the three I have shared above, I would love to hear it as well.
Thank you in advance.