Reading a rotary encoder with analogRead()

My thoughts of how to read an encoder wheel

I use this little fellow:


It's a TCRT50000. It consists of an IR LED and an IR phototransistor. It's the central element in sensors used in line following robot cars, like the KY-033 sensor. In my case I wanted to use just the tiny TCRT50000. What I have is this:

A planetary gear, where the ring gear is fixed to a bottom plate, the planet gears hold to a machine rotating, and the sun gear is on a motor axis, the motor being fixed on the rotating machine. Everything works nicely. When I read the phototransistor with analogRead(A6), I get nice readings:

This is a very rough curve, where I have a long delay between the readings. With shorter delays and maybe with some filtering, I get a smoother curve. The top of the curve occurs, when the TCRT50000 is above a white sector. The bottom relates to the black sector.
To count steps, I need to count the waves. I need an algo, where I can clearly distinguish these waves from each other and not mix them with maybe smaller waves, which are caused by ambient light or noise.

I want to count all white and all black sectors, which gives me 16 counts per revolution. The whole planetary system will rotate some 9 degrees per sector.
So I need to count the tops and the bottoms of the wave. The average value of the wave in the image is somewhere about 450. If we think of this value as the X axis, we have a sign change when the curve passes 450 in either direction. I want to use this sign change as the significant step to count, but I want to use the tops and the bottoms as the actual position to stop at. The sign change should be easy to detect, with proper filtering one can't miss it, or there won't be multiple sign changes in a narrow interval, due to the large rate of change. But at the tops and bottoms, the rate of change goes to zero and will have its own sign change. Moreover, there might be several sign changes of the rate of change at the tops and bottoms, even after heavy filtering.
The machine will rotate back and forth a number of step. If at a top, one step in either direction takes it to a bottom. Each step is about 9 degrees. A 90 degree rotation means 10 steps.
The algo for one step goes like this:

  1. Start rotation
  2. Wait for a pass of the 450 value ("sign change")
  3. Start monitoring the increase or decrease rate
  4. Wait for the rate to reach 0 and make a sign change
  5. One step is completed. Stop the rotation. Or jump to 2, if more steps are needed.

The exact mean value (450 in the example) is not very important, as long as it's a clear value, where the rate of change is about the largest.
Likewise, the exact point where the rate of change changes the sign is not important, as long as it clearly happens between two points passing 450. Despite using filtering, I could still have some ripples causing several sign changes of the rate of change at a top or at a bottom. I only care for detecting the first sign change. It might be a few degrees off the midpoint of the sector, but it's more important for me to count the sectors passing by, than to stop in the middle of one sector. If I want the system to rotate 90 degrees, I count 10 sectors and stop. I can go back and forth 10 sectors how many times I want and never be more off than a few degrees.

I have built everything already. But I haven't implemented this logic yet. I hope to get feedback on the idea itself. Did I once again reinvent the wheel? (I'm the founder of WRIS, Wheel Re-Inventers' Society. Sorry, but you can't join the Society. Everyone has to found their own WRIS.)

You need two thresholds and a state machine.

  • above 500 = white
  • below 300 = black

You're right on both! I thought that only one "sign change" at 450 would be enough. But if I still have noise left, I might get a sudden "sign change" immediately after passing 450, which would be registered as the top. If such error happens near 450, I'm on unsure territory. But if it happens at 500, I'm safely way above 300. Because 300 would be the next threshold I'd look for, when doing the next rotation. Just focusing on 450 could take me to a situation, where I get readings like 400, 412, 427, 452, 446. At 452, I'd be looking for the change of direction, which happens at the top. But due to noise, I have it at 446. I'd stop there. But then I'd likely be above 450 at the start of next step, when my algo assumes I'd be way higher, around 600.

If you cannot define an hysteresis area the algorithm becomes more complicated but not necessarily impossible.

You need to look for a local maximum / minimum and adjust your thresholds constantly.

ThresholdHIGH = minimum + (maximum - minimum) * 0.70;
ThresholdLOW = minimum + (maximum - minimum) * 0.30;

weights can be adjusted to make the hysteresis area smaller / larger.

I've never done this but ...

Many AVR chips have a built-in analog comparator which can call an interrupt routine when an analog input voltage crosses a threshold.

Rather than constantly reading the ADC, maybe you could use this comparator. When the input voltage from the sensor rises above the threshold, an interrupt routine could update a count and then set a lower threshold ready to catch the falling voltage with another interrupt, and so on.

The hysteresis model seems to be a good approach. A comparator triggered interrupt, or any interrupt solution would be terrific. But I failed to find any info about interrupts based on analog pins.

Read datasheets.

I am curious.

Why are the analog input pins used instead of digital input pins?

Because a transistor is scary.

Right now I'm testing with an MKR1010. I find the following info:


A1 and A2 looks like I could use them for interrupts. Just need more info about how to set the thresholds.

The hysteresis would need two digital pins. One interrupt for going from 0 to 1 and another for going from 1 to 0. And the transistors and opamps and whatnot would trim the transitions to happen at the two thresholds. I like if I could instead use one analog pin for the interrupt and switch the threshold at each interrupt. No opamps or trimpots.

There's already some level of hysteresis built into the digital pins of AVR type Arduinos like a Uno or Mega.

How do you have the phototransistor connected to A6? Are you using a resistor/trimpot now? If so, what is it's value?

Schematic? More details on your setup?


No trimpots. I get nice values at A6. The IR LED draws some 40 mA, which is high compared to a standard LED, but it should be ok.
I could get higher difference between high and low by adjusting the placement of the TCRT5000. Right now it's too close to the wheel.

Cool. In the first plot, the high values are 600-850, so that's 1.9-2.7 volts at A0, a voltage drop of 1.3 to 0.55V across the photo transistor and also current of 190 to 270 uAmps through the resistor and the phototransistor. And when dark, 200/1023 gives about 0.65V, or 2.65V across the PT and 0.65/10K=65uA through the resistor & PT.

If you used a bigger resistor, (20K maybe?), it might push the highs up into the reliably digital range.

An interrupt based system would be very nice in my case, because I need my machine to do other things, too. So, if my MCU doesn't support interrupts with built in analog comparators (haven't found info on either MKR1010 or Giga), I might build one with one or two opamps or comparator ICs, and trimpots.

Here's my 3D printed encoder gear:


I might get a better signal, if I'd cut the encoder pattern in some adhesive paper. I might even be able to add a pair of sectors.