Do more with less
Following my previous post about using an Arduino for controlling a DC motor with an encoder in a closed-loop fashion some people have asked me if one Arduino per motor was not too expensive. Well, if you use an Arduino Pro Mini it can be less than $2 each.
However, I do feel too that maybe we can do better.
The main reason of my initial approach was that I had several signals that need to be handled fast-enough. On one hand, the quadrature encoder of the motor provides two pulsing signals that are used to know the current location of the motor shaft. This is accomplished by counting pulses, if not done fast enough, missed pulses will translate into position errors (and we are going closed-loop instead of using steppers to avoid exactly this same problem).
On the other hand, the motor controller has to be able to receive pulses from the main controller (an Arduino Mega running Marlin) that control the motion of the motor. These pulses can be quite short and frequent (up to 40Khz signal) so motor controller needs to be fast enough not to miss them either.
The first obvious solution was to use the two external interrupts available on the Arduino Pro Mini (or the UNO I was using for development that is equipped with a 328 too). One of the external interrupts will handle one of the encoder signals and the other will handle the "pulse" signal from the motion controller. This way we can be confident that all no pulses of each type will be lost.
However that left me with no choice but to have to use one Arduino per motor, as there were no other interrupts I could use.
Todays test shed some light about an alternative way of doing things. There is a feature on ATMega processors that can trigger an interrupt on pin change for any of the I/O pins selected. My first test was not entirely successful because I forgot to use fast I/O: digitalRead() is not a very fast way of doing things. While not a problem most of the time, it makes interrupt routines to use quite a long time and for fast pulses that means that a second pulse could arrive before we are still reading the first one, leading to missed pulses. Once I fixed this, I can move the carriage very fast without missing pulses.
You can the code I am using below. As usual, I have collected some smart ideas over the 'Net. (Please note the picture above does not represent the system I used the following code with but my initial design posted on youmagine).
#include <PinChangeInt.h>
#include <PinChangeIntConfig.h>
#define encoder0PinA 6
#define encoder0PinB 7
long int encoder0Pos = 0;
void setup(){
pinMode(encoder0PinA, INPUT);
pinMode(encoder0PinB, INPUT);
PCintPort::attachInterrupt(encoder0PinA, doEncoderMotor0,CHANGE);
PCintPort::attachInterrupt(encoder0PinB, doEncoderMotor0,CHANGE);
Serial.begin(115200);
}
void loop(){
Serial.println(encoder0Pos);
delay(200);
}
const int QEM[16] = {0,-1,1,2,1,0,2,-1,-1,2,0,1,2,1,-1,0};
volatile unsigned char New, Old;
void doEncoderMotor0(){
Old = New;
New = ((PIND & 128) >>6) + ((PIND & 64)>>6);
encoder0Pos+= QEM [Old * 4 + New];
}
Update: I have not had much luck with two motors per Arduino. I can make it work for low speeds but eventually the interrupt service routines use up all the processor time and the system becomes unstable. I guess it can be done if motors have lower resolution encoders or have a lower RPMs.
However, I have learned that Maple Mini has an ARM processor that includes four timers that can handle one encoder each. This may be a better platform, specially if you can get it for $4 on Aliexpress.
However, I do feel too that maybe we can do better.
The main reason of my initial approach was that I had several signals that need to be handled fast-enough. On one hand, the quadrature encoder of the motor provides two pulsing signals that are used to know the current location of the motor shaft. This is accomplished by counting pulses, if not done fast enough, missed pulses will translate into position errors (and we are going closed-loop instead of using steppers to avoid exactly this same problem).
On the other hand, the motor controller has to be able to receive pulses from the main controller (an Arduino Mega running Marlin) that control the motion of the motor. These pulses can be quite short and frequent (up to 40Khz signal) so motor controller needs to be fast enough not to miss them either.
The first obvious solution was to use the two external interrupts available on the Arduino Pro Mini (or the UNO I was using for development that is equipped with a 328 too). One of the external interrupts will handle one of the encoder signals and the other will handle the "pulse" signal from the motion controller. This way we can be confident that all no pulses of each type will be lost.
However that left me with no choice but to have to use one Arduino per motor, as there were no other interrupts I could use.
Todays test shed some light about an alternative way of doing things. There is a feature on ATMega processors that can trigger an interrupt on pin change for any of the I/O pins selected. My first test was not entirely successful because I forgot to use fast I/O: digitalRead() is not a very fast way of doing things. While not a problem most of the time, it makes interrupt routines to use quite a long time and for fast pulses that means that a second pulse could arrive before we are still reading the first one, leading to missed pulses. Once I fixed this, I can move the carriage very fast without missing pulses.
You can the code I am using below. As usual, I have collected some smart ideas over the 'Net. (Please note the picture above does not represent the system I used the following code with but my initial design posted on youmagine).
#include <PinChangeInt.h>
#include <PinChangeIntConfig.h>
#define encoder0PinA 6
#define encoder0PinB 7
long int encoder0Pos = 0;
void setup(){
pinMode(encoder0PinA, INPUT);
pinMode(encoder0PinB, INPUT);
PCintPort::attachInterrupt(encoder0PinA, doEncoderMotor0,CHANGE);
PCintPort::attachInterrupt(encoder0PinB, doEncoderMotor0,CHANGE);
Serial.begin(115200);
}
void loop(){
Serial.println(encoder0Pos);
delay(200);
}
const int QEM[16] = {0,-1,1,2,1,0,2,-1,-1,2,0,1,2,1,-1,0};
volatile unsigned char New, Old;
void doEncoderMotor0(){
Old = New;
New = ((PIND & 128) >>6) + ((PIND & 64)>>6);
encoder0Pos+= QEM [Old * 4 + New];
}
Update: I have not had much luck with two motors per Arduino. I can make it work for low speeds but eventually the interrupt service routines use up all the processor time and the system becomes unstable. I guess it can be done if motors have lower resolution encoders or have a lower RPMs.
However, I have learned that Maple Mini has an ARM processor that includes four timers that can handle one encoder each. This may be a better platform, specially if you can get it for $4 on Aliexpress.
Comments
Today, the wife is out, and I'm wiring up an ATtiny85 raw to an L293 H bridge. Minimalist approach. I'll flip you the Fritz in a bit, but basically two input pins (I2c or step/dir) two encoder pins, and two motor out connections.
The breadboard is bigger than the Pololu stepper driver, but once on PCB, there's no reason at all that we cannot fit that footprint.
You can always go SMD and there is a To263 version of this other h.bridge http://uk.farnell.com/infineon/tle5206-2s/ic-h-bridge-driver-5a-to220-7/dp/2215549
I like your idea but I do not know if you get at least two interrupt pins to deal with encoder and step signals. If you can get two interrupt pins for the encoder you can get 4x resolution!!
I like your idea as these carrier boards could be made cheaply and replacement will not require much fuss for a RAMPS-based setup.
Gain adjustment could be done recompiling and re-uploading the content (though a variable resistor could be set on one output pin and only read after reset before setting the pin as PWM output).