Hello, I'm trying to synchronise two moving shafts and have been trying to understand the possibility of using an Arduino to control the drives.
The issue I have is that the shafts run in one direction for long periods of time so I'm worried that eventually the int values will overflow.
I understand how to read and interpret code but my knowledge is reaching it's limit on whether this code actually keeps counting up, or just compares the two counts and thus maintains low int values.
Any help would be amazing.
Thanks
/*
* Simple arduino PID motor position control
* Fine tune for better results
* Implemented on ASlONG DC Motor with a magnetic encoder (JGB3-520B-12v-178RPM)
*/
#include <PIDController.h>
PIDController pid;
volatile int degree = 0;
int encoderSignalAPin = 2; // Signal A from 1st Hall sensor
int encoderSignalBPin = 3; // Signal B from 2nd Hall sensor
int motorOP1Pin = 9; // Motor output1 (OP1) connect to the IN1 of L293D motor driver shield
int motorOP2Pin = 10; // Motor output2 (OP2) connect to the IN2 of L293D motor driver shield
int motorDirection = 0; // Motor direction
int encoderReversePulseCount = 0;
int encoderForwardPulseCount = 0;
void setup()
{
Serial.begin(115200); // Begin serial communication
pinMode(encoderSignalAPin, INPUT);
pinMode(encoderSignalBPin, INPUT);
pinMode(motorOP1Pin, OUTPUT);
pinMode(motorOP2Pin, OUTPUT);
attachInterrupt(digitalPinToInterrupt(encoderSignalAPin), detectReverseISR, RISING);
pid.begin(); // Initialize the PID instance
pid.tune(15, 3, 3000); // Tune the PID, arguments: kP, kI, kD (Fine tune for better responses)
pid.limit(-255, 255); // Limit the PID output between 0 and 255, this is important to get rid of integral windup!
}
void loop()
{
init:
if (Serial.available())
{
int setTargetPositon = Serial.parseInt();
if (setTargetPositon <= 360 && setTargetPositon >= -360 )
{
Serial.print("Target position: ");
Serial.println(setTargetPositon);
pid.setpoint(setTargetPositon); // Set the "goal" the PID controller tries to "reach"
Serial.println("Position reached");
}
else
{
Serial.println("Out of range value:");
goto init;
}
}
degree = encoderReversePulseCount / 1.72222; // Approximate pulses convertion to degrees
int outputSignal = pid.compute(degree); // PID compute the value and returns the optimal output
if (outputSignal > 0)
{
RotateCounterRotateClockwise(outputSignal); // Rotate the motor to counter clockwise
}
else
{
RotateClockwise(abs(outputSignal)); // Rotate the motor to clockwise
}
delay(10); // delay is important to tune
}
/*
* ISR to detect reverse direction when the encoder is moved
*/
void detectReverseISR()
{
motorDirection = digitalRead(encoderSignalBPin);
if (!motorDirection)
{
encoderReversePulseCount += 1;
if (degree >= 360)
{
encoderReversePulseCount = 0;
}
}
else {
encoderReversePulseCount += -1;
if (degree <= -360)
{
encoderReversePulseCount = 0;
}
}
attachInterrupt(digitalPinToInterrupt(encoderSignalAPin), detectForwardISR, FALLING);
}
/*
* ISR to detect forward direction when the encoder is moved
*/
void detectForwardISR()
{
motorDirection = digitalRead(encoderSignalBPin);
if (motorDirection)
{
encoderForwardPulseCount += 1;
}
else
{
encoderForwardPulseCount += -1;
}
attachInterrupt(digitalPinToInterrupt(encoderSignalAPin), detectReverseISR, RISING);
}
/*
* To rotate the motor to clockwise direction
*/
void RotateClockwise(int power)
{
if (power > 95)
{
analogWrite(motorOP1Pin, power);
digitalWrite(motorOP2Pin, LOW);
}
else
{
digitalWrite(motorOP1Pin, LOW);
digitalWrite(motorOP2Pin, LOW);
}
}
/*
* To rotate the motor to counter clockwise direction
*/
void RotateCounterRotateClockwise(int power)
{
if (power > 95)
{
analogWrite(motorOP2Pin, power);
digitalWrite(motorOP1Pin, LOW);
}
else
{
digitalWrite(motorOP1Pin, LOW);
digitalWrite(motorOP2Pin, LOW);
}
}
quote="kolaha, post:2, topic:1140523"]
place the code in proper "code" format
[/quote]
click the <code> icon and paste you code in the highlighted area
i'm not sure what you code is doing. are you driving one motor and trying to have a 2nd motor track it?
only need to "attach" an interrupt once in setup()
don't see a need to convert the encoder position to degrees, nor reset when it exceeds 360 degree.
why are there 2 separate ISRs affecting the enocderReversePulseCount
when detecting that the one (A) encoder output goes high, the state of the other encoder output (B) indicates the direction and can be used to either inc/decrement position. PID can output a value opposite the current motor polarity (direction) to slow a motor.
why not control the motor with a single routine that sets the polarity based on the sign of the value and the speed on the abs() of the value
not sure if you can get away with tracking the position with an unsigned value
I wouldn't go as far as saying there's always a "better" way without goto. Sometimes it's better to keep "exceptional" code out of the main flow of code to make it easier to see what the "non-exceptional" code is doing:
bool with_goto()
{
bool success = false;
a = get_a();
if (!a) goto out;
b = get_b();
if (!b) goto release_a;
c = get_c();
if (!c) goto release_b;
d = get_d();
if (!d) goto release_c;
// do something with a, b, c, and d and keep their allocations
success = true;
goto out;
release_c:
release_c(c);
release_b:
release_b(b);
release_a:
release_a(a);
out:
return success;
}
bool without_goto()
{
bool success = false;
if (a) {
b = get_b();
if (b) {
c = get_c();
if (c) {
d = get_d();
if (d) {
// do something with a, b, c, and d and keep their allocations
success = true;
} else {
release_c(c);
release_b(b);
release_a(a);
}
} else {
release_b(b);
release_a(a);
}
} else {
release_a(a);
}
}
return success;
}
In without_goto the non-exceptional code is nested four levels deep in ifs, and there are redundant release_a/b/c() calls, but in with_goto the non-exceptional code is not nested at all and there are no redundant resource release calls. The non-exceptional case is clearer to see.
Avoiding goto in this sort of code for no other reason than to follow the "goto is bad" dogma gives you the "arrow" anti-pattern, not to mention violating the DRY principle. That's why you'll find goto used for this purpose (i.e., exceptions) in large successful code bases like the Linux kernel, for example.
(Of course, if you have and use C++ exceptions then this resource unwinding pattern is not as necessary, but those aren't generally available in Arduino land.)
I guess my purpose was to point out that "never" and "always" statements are almost never true. Also, programming is a pedantic field. A computer is a fast idiot so you have to be precise in what you tell it to do.
Nope. If it followed the resource allocation/deallocation pattern, then yes I probably would.
there are times when a goto is appropriate. otherwise, why would c/c++ continue to support it. but most often, especially on this forum, it is incorrectly used.
my freshman programming class professor said he'd fail anyone using goto
but i've seen is used appropriately in unix application code to exit more cleanly when a problem occurs
Understood, thanks for the correction!
I'm still trying to understand a way to get around an overflow, the encoders I'm using have a PPR that mean the system will only run in one direction for a short period of time before results get unreliable.
Short of porting the whole thing to a 32bit MC I'm not sure what to do?
I have a projector that runs on a VFD and AC motor.
I want to rotate a platter that takes up the film coming from the projector, it must run at the same speed as the projector but it is powered by a DC motor.
The idea is to put an encoder on both motors and the projector is operated as normal with the slave DC motor following the projector.
The Arduino will just count and increase and decrease power to the DC drive based on encoder count.
The issue is that after time the int overflows and results are unpredictable.
Be careful, if you are winding film onto a reel the motor speed will need to vary as the radius of the reel of film increases. Might be easier to use a tension arm to detect how much slack is in the film and vary the motor speed based on that.
if you are measuring rotational speed, then wouldn't you want to use the number of pulses over a specific time interval, instead of the total number of pulses?
You do not seem to have declared the pulse count variables as 'volatile', and are not guarding against an interrupt occurring while accessing those variables.
Do you have any idea of the maximum number of pulses that will occur between subsequent calculations?