I have been trying to implement this for the last few months, and haven't really got anywhere. I am trying to use an 2 channel hall effect encoder to control the speed of a DC motor through the latest Arduino Motor shield (purchased off this website).
The Arduino board I am using is the Mega 2560.
I think the best way to implement this for me is to use P or PD control, with the error being the difference between the rpm, and the required rpm, which are found using interrupts with the encoder.
In all honesty, I am a total novice at programming, and haven't really got anywhere with this, after a lot of hours. It is frustrating, as I had hoped this would be the simple part of my project. However, I am determined not to just throw in the towel!
Is there anybody out there who is willing to share a relevant code example, or hold my hand through this..? I have looked at the PID and interrupt library ect, but I am still nowhere near being able to implement this.
Thank you so much for your time, I realise it is probably frustrating to have to deal with total noobs, but I can't tell you how grateful I would be for any help!
but I am still nowhere near being able to implement this.
Why not? Break the problem down into small enough pieces. Are you able to read the speed of the motor? Are you able to control the speed of the motor? Do you have any code that attempts to implement PID?
//Motor
const int MOTOR_DIR = 13; // Non PWM pin for direction control
const int MOTOR_PWM = 11; // PWM controlled pin
const int MOTOR_BRAKE = 8; // Motor brake
const int MOTOR_CURRENT = A1; // Motor Current
void setup()
{
Serial.begin (9600);
}
void loop()
{
//Engage Motor
digitalWrite(MOTOR_DIR, HIGH); //Establishes forward direction of Channel A
digitalWrite(MOTOR_BRAKE, LOW); //Disengage the Brake for Channel A
analogWrite(MOTOR_PWM, 123); //Spins the motor on Channel A at half speed
/*delay(500); // hold the motor at full speed for 5 seconds
Serial.print("current consumption at full speed: ");
Serial.println(analogRead(MOTOR_CURRENT));*/
}
Reading the encoder (using the high performance library)
#include <Encoder.h>
Encoder myEnc(2, 18); //(digital 2, analog 4)
void setup() {
Serial.begin(9600);
Serial.println("Basic Encoder Test:");
}
long oldPosition = -999;
void loop() {
long newPosition = myEnc.read();
if (newPosition != oldPosition) {
oldPosition = newPosition;
Serial.println(newPosition);
}
}
I was hoping to use the PID library as well. My biggest difficulties at the moment are;
I am not sure how to find the rpm from the interrupts (I imagine I can either find the time between each interrupt, or find the number of interrupts over a given time. I am not sure what is the best route to go down, (or how to achieve this)!. I believe this involves using the millis function, but I don't really understand how to implement this.
Implementing different codes in the same script (do I just integrate the motor and encoder loops).
I am not sure how to find the rpm from the interrupts (I imagine I can either find the time between each interrupt, or find the number of interrupts over a given time. I am not sure what is the best route to go down, (or how to achieve this)!.
Yes. Which way to go depends on whether you are looking for an instantaneous RPM reading (you want to know as soon as RMP changes) or for an average reading (for display, like a tachometer).
I believe this involves using the millis function, but I don't really understand how to implement this.
It does, but first you need to decide what RPM reading you are looking for.
Implementing different codes in the same script (do I just integrate the motor and encoder loops).
Yes.
As for PID control, you have some INPUT (RPM), some SETPOINT (the RPM you want to maintain), and some OUTPUT (this is a time-based value that you need to convert to a useable value to influence the analogWrite() value). Since it's a feedback-based system, exact interpretation of the OUTPUT value isn't critical. The OUTPUT value will go up (in magnitude) as the difference between the INPUT value and the SETPOINT value increases and will go down as the difference decreases. The sign of the value will be positive if the difference is negative and will be negative if the difference is positive.
I would prefer the instantaneous RPM reading, as this will increase the response time of the PID (I think!). However presumably the signal would be noisier? For reference the encoder I am using is this one:
Ah I see the difference! Thank you very much. I will have a go at this today!
Would the millis() or stopwatch class be better in this situation? (I realise the stopwatch class is based on millis()). It seems like this could be simpler to implement. Sorry if this is idiotic!
#include <Encoder.h>
//Timer
unsigned long oldTime = 0;
unsigned long newTime;
unsigned long deltaT;
//Encoder
long oldPosition =-999;
long newPosition;
Encoder myEnc(2, 3);
//Motor
const int MOTOR_DIR = 13; // Non PWM pin for direction control
const int MOTOR_PWM = 11; // PWM controlled pin
const int MOTOR_BRAKE = 8; // Motor brake
const int MOTOR_CURRENT = A1; // Motor Current
void setup() {
Serial.begin(9600);
Serial.println("Basic Encoder Test:");
}
void loop() {
//Engage Motor
digitalWrite(MOTOR_DIR, HIGH); //Establishes forward direction of Channel A
digitalWrite(MOTOR_BRAKE, LOW); //Disengage the Brake for Channel A
analogWrite(MOTOR_PWM, 123); //Spins the motor on Channel A at half speed
newPosition = myEnc.read();
if (newPosition != oldPosition) {
oldPosition = newPosition;
newTime = micros();
deltaT = (newTime - oldTime); //time interval (in micro seconds)
oldTime = newTime;
Serial.println(deltaT);
}
}
It outputs about 6240 +/- 12 no matter how fast the motor is going. I have clearly made a novice error, any advice on what I have done wrong?
Many thanks as usual!
Alex
It outputs about 6240 +/- 12 no matter how fast the motor is going.
Why does this surprise you? You are printing how long it took to recognize a change in encoder values. Regardless of the speed of the motor, that time is going to be relatively constant. You need to divide the time by the amount of change (that is the part that is going to change) to get RPM. Keep in mind that the time you are measuring is in microseconds, not minutes.
#include <Encoder.h>
//Timer
unsigned long oldTime = 0;
unsigned long newTime;
unsigned long deltaT;
unsigned long deltaX;
unsigned long deltaV;
//Encoder
long oldPosition = 0;
long newPosition;
int encRes = 360; // Encoder resolution
Encoder myEnc(2, 3);
// avoid using pins with LEDs attached
const int MOTOR_DIR = 13; // Non PWM pin for direction control
const int MOTOR_PWM = 11; // PWM controlled pin
const int MOTOR_BRAKE = 8; // Motor brake
const int MOTOR_CURRENT = A1; // Motor Current
void setup() {
Serial.begin(9600);
Serial.println("Basic Encoder Test:");
}
void loop() {
digitalWrite(MOTOR_DIR, HIGH); // Establishes forward direction of Channel A
digitalWrite(MOTOR_BRAKE, LOW); // Disengage the Brake for Channel A
analogWrite(MOTOR_PWM, 255); // Spins the motor on Channel A at half speed
newPosition = myEnc.read();
if (newPosition != oldPosition) {
newTime = millis();
deltaT = (newTime - oldTime);
deltaX = (newPosition - oldPosition);
deltaV = ((deltaX*1000/deltaT)/encRes); //RPS
Serial.println("speed = ");
Serial.println(deltaV);
oldTime = newTime;
oldPosition = newPosition;
delay(250);
}
}
I now have an output that correlates to speed, but it seems to be far too high. At the lowest speeds the motor appears to be going around 1 rps, whereas the serial read out it about 60/70. Any advice. Thank you in advance!
void loop() {
digitalWrite(MOTOR_DIR, HIGH); // Establishes forward direction of Channel A
digitalWrite(MOTOR_BRAKE, LOW); // Disengage the Brake for Channel A
analogWrite(MOTOR_PWM, 255); // Spins the motor on Channel A at half speed
newPosition = myEnc.read();
if (newPosition != oldPosition) {
newTime = millis();
Serial.println("before =");
Serial.println(oldTime);
Serial.println(oldPosition);
deltaT = (newTime - oldTime);
deltaX = (newPosition - oldPosition);
deltaV = ((deltaX*1000/deltaT)/encRes); //RPS
oldTime = newTime;
oldPosition = newPosition;
Serial.println("after = ");
Serial.println(newTime);
Serial.println(newPosition);
delay(250);
}
I get this kind of serial output:
before =
8151
136314
after =
8402
142399
before =
8402
142399
after =
8654
148454
before =
8654
148454
after =
8906
154540
before =
8906
154540
after =
9158
160614
before =
9158
160614
after =
9411
161886
At this point, the motor is turning at about 2rps,
The deltaX is around 70, and the deltaT is 250ms.
Thanks for your time as ever PaulS. Definitely owe you!
So, deltaX is 6200. Multiplying that by 1000 gives 6,200,000. Dividing that by delta T gives 24,800. Dividing that by 360 gives 68.888. So, given the numbers you are seeing, the output of 70 is correct.
Therefore, there is something wrong with your formula.
the motor is internally geared 10:1, which means there are 3600 counts per output shaft revolution. This puts it in a similar range. Sorry for not realising this sooner.
does the fact that there are A/B pins mean that there are double the number of interrupts if that makes sense? Could be way off base here!
does the fact that there are A/B pins mean that there are double the number of interrupts
No. It means that you can tell which way the encoder is moving. In some cases it matters. In a geared motor setup, it is unlikely that you would not know which way it is rotating.