I made a rotary encoder using a IR sensor and a wheel with printed black and white spokes. Everything seems to work but I'm wondering if there is a better way to code this. The IR signal looks ideally like a square wave and I have it looking for the rising edge of each wave. Then calculate the time between rising edges to calculate the wheel's RPM.
int wheelPin = 11; // I/O pin for encoder
int count = 0; // number of encoder ticks
bool rising = false; // bool for rising edge of encoder signal
double dt; // time between rising edges
long dtStore[2]; // FIFO array holding current and previous rising edge times
float rpm;
void setup(){
Serial.begin(57600);
}
void loop() {
int wheelVal = analogRead(wheelPin); // get encoder val
/* check to see if we are at a low spot in the wave. Set rising (bool) so we know to expect a rising edge */
if (wheelVal < 200) {
rising = true;
}
/* Check for a rising edge. Rising is true and a wheelVal above 200 (determined experimentally)*/
if (rising && wheelVal > 200) {
dtStore[0]=dtStore[1]; // move the current time to the previous time spot
count++;
dtStore[1] = millis(); // record the current time of the rising edge
dt = dtStore[1]-dtStore[0]; // calculate dt between the last and current rising edge time
rpm = (0.0625)/dt*1000*60; // RPM calculation
rising = false; // reset flag
}
Serial.println(rpm);
}
-
\$\begingroup\$ Can the wheel only turn in one direction? Without oscillation about an edge? Often two encoders offset 90 degrees are used with the resulting signals in quadrature interpreted to handle those issues. \$\endgroup\$Chris Stratton– Chris Stratton2012年09月04日 13:54:48 +00:00Commented Sep 4, 2012 at 13:54
-
\$\begingroup\$ Yes, only one direction. \$\endgroup\$JDD– JDD2012年09月04日 16:04:14 +00:00Commented Sep 4, 2012 at 16:04
-
\$\begingroup\$ As well as hysteresis for analogue signal level transitions, you might want to use a de-bounce algorithm in time, provided you maximum speed is much less than the sampling frequency. \$\endgroup\$shuckc– shuckc2012年09月04日 17:10:06 +00:00Commented Sep 4, 2012 at 17:10
4 Answers 4
There are two things you should do.
Firstly, implement a little bit of hysterisis. Currently, it look like you're using 200 as the threshold in both the rising and falling direction. What you'll always get with analogue sensors is a bit of noise. This means that, even during a rising edge, you might actually see a tiny falling edge sometimes. You might even see it cross your threshold up, then down, then up again!
Encoder Hysterisis
- The green line shows what you hope your sensor is producing.
- The red line shows what it's probably producing. Actually, it's probably much worse than this.
- The purple line is what you want
- The black line is what you could be getting
So, when the signal is rising, use 200 as your threshold. And when the signal is falling, use something like 150 as a threshold.
Secondly, you could use both the rising and falling edges to calculate your speed. That way you can have twice the update rate.
-
\$\begingroup\$ Thanks! I like the plots and explanation. I looked at my raw sensor data and it does look a lot more like the red line. \$\endgroup\$JDD– JDD2012年09月04日 16:14:09 +00:00Commented Sep 4, 2012 at 16:14
Regarding a "better way to code this" you could use Analog Comparator that is in Atmega chip. It works by comparing voltages that are present on two specific pins of microcontroller - AIN0 and AIN1. Add a potentiometer that sets a threshold for transition on AIN1, then connect your sensor to AIN0. When voltage from sensor is above voltage set by potentiometer, Analog comparator can be configured to rise an interrupt - you add your logic to servicing routine for this interrupt.
Another way to code this could be by using external interrupt functionality on Atmega - you get an interrupt on rising or falling edge on some specific pin of microcontroller, but you would need to do a signal conditioning using a separate opamp to be able to set threshold and hysteresis.
You would have to resort to programming an "AVR level" C to do all of this rather than Arduino lingo.
Edit You still can do that from Arduino environment, this is a sniplet on using analog comparator in arduino, it just uses defines from avr-libc directly inside arduino code:
void setup(){
pinMode(7,INPUT);
Serial.begin(9600);
ACSR = B01011010; // comparator interrupt enabled and tripped on falling edge.
}
void loop(){
}
ISR(ANALOG_COMP_vect) {
Serial.println("Interrupt Executed!")
}
-
\$\begingroup\$ I was thinking that an external interrupt might be the way to go but I'm not too familiar with that method. I like this idea though. \$\endgroup\$JDD– JDD2012年09月04日 22:08:45 +00:00Commented Sep 4, 2012 at 22:08
-
\$\begingroup\$ this is just an opportunity to move from arduino level to deeper microcontroller stuff \$\endgroup\$miceuz– miceuz2012年09月05日 05:53:39 +00:00Commented Sep 5, 2012 at 5:53
-
\$\begingroup\$ I agree. I'm starting to hit the limit of what arduino level stuff can do. This sensor is eventually going in another project that uses a PIC32. I'm just working out how to use the sensor with the arduino. \$\endgroup\$JDD– JDD2012年09月05日 10:48:34 +00:00Commented Sep 5, 2012 at 10:48
-
\$\begingroup\$ Finally got around to trying this out. It works very well. Thanks! \$\endgroup\$JDD– JDD2012年09月07日 01:00:01 +00:00Commented Sep 7, 2012 at 1:00
I'm doing something rather similar at the moment. But with two differencs:
- I've a schmitt-trigger (74HCT14) between the signal source and the controller
- I do not measure the time between two edges but I count the edges for a certain amount of time and compute the RPM from the division of number of edges and time
Seems to me, that generating a constant frequency interrupt (like 1Hz) and count pulses is easier and more robust than measuring a time in ms range.
But maybe this approach does not apply to your design.
-
\$\begingroup\$ You're very much right! If a fast measurement is not necessary, then it's a lot better to measure the number of pulses over a long time instead of inverting a very small and inaccurate time measurement. \$\endgroup\$Christoph B– Christoph B2012年09月07日 15:06:04 +00:00Commented Sep 7, 2012 at 15:06
As well as hysteresis for analogue signal level transitions, you might want to use a de-bounce algorithm once digitised. You set de-bounce timers to be a small value relative to the expected switching rate at maximum rotation speed.
E.g. For 4 spokes, maximum 120 rpm you expect an 8 Hz square wave output. With a period of 125ms, you can afford debounce timers of say 10ms on each transition. The idea is to keep 10ms "small" relative to your 62.5ms hold times. E.g. a L-H transition might force a 1
output for 10ms, a H-L transition might force a 0
output for 10ms etc.
-
1\$\begingroup\$ Mechanical encodes bounce. The encoder in the O.P is optical. Why would it bounce? \$\endgroup\$Nick Alexeev– Nick Alexeev2012年09月05日 01:31:01 +00:00Commented Sep 5, 2012 at 1:31