Continue to Site

Welcome to our site!

Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

  • Welcome to our site! Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

Rotory Encoder Woes

Status
Not open for further replies.

MrAl

Well-Known Member
Most Helpful Member
Hi,

Anyone else here using the two bit rotory encoders ?
These are the type with five terminals, one for positive, one for ground, one for the push switch, and two for the two bits. I dont need the push switch right now, so am just using the two bits and plus and minus supplies.

The internal working is such that if terminal 1 goes to ground before terminal 2 (the two bits) then the program detects rotation in one direction, and if 2 before 1 then the other direction, and there is also logic for sensing which one has been released first. Supposedly this can be used to increment say a large number counter that counts from say zero to over 65,000 but it can really go up to whatever you want it to go up to such as type long long or even single digits grouped any way you wish.

So the typical use is to set a setting for another device, such as a frequency generator. For example, say you want to set it to 1Hz, then you turn the encoder one click clockwise.
Then for 2Hz, another click to the right.
Now say you really wanted 32Hz. After you do the above, you push the button in and the 'counter' display moves to the next digit to the left (the 10's digit), and then you turn the encoder one more click and it goes up to 12, then if another click clockwise it goes to 22, then 32, etc.
Now say you want 432Hz, you push again then turn the rotory encoder four clicks clockwise, and the counter display goes to 432. Seems simple enough.

However, sometimes the rotory encoder does not respond as well as we would like, so it misses some counts. It may take 7 clicks clockwise to get to 4 instead of just 4.

The library i am using is simple enough, and it detects all possible state transitions and increments or decrements the counter as it should. It just doesnt seem fast enough even though it uses two interrupts.

It may be that the I2C display routine does not like to be interrupted. If i allow it to be interrupted it gets lost and does not display anymore, just the numbers that had shown up before turning the encoder. If i disable interrupts when displaying a number, then the display works fine but the encoder does not catch all the turns because it is not allowed to interrupt the display routine.

I can probably use a 4 bit parallel display instead, but i would like it to work with the I2C display too.

Any ideas, or how you do it in your code?
Maybe it would be better to modify the library, but it still wont like being interrupted while it is sending bits to the display.

When i uses three switches: one for up, one for down, and one for digit select, it works perfectly because the switches are polled, not using interrupts.
 
You don't mention which uC you're using but most have hardware I2C that should work whether interrupted or not. Even Software I2C should work fine as it's not a time critical protocol. One problem I have encountered with I2C is if the processor is reset during transmission, the slave can hang the bus when it restarts. However, this doesn't sound like your problem. More details please.

Mike.
 
Hello again,

Ok, maybe it is the ADC chip then, which has a minimum I2C frequency requirement.
I can retry this without the ADC chip next.

But in any case, have you gotten the rotory encoders to work well, and if so, do they seem responsive enough?
 
When i uses three switches: one for up, one for down, and one for digit select, it works perfectly because the switches are polled, not using interrupts.
You can poll the rotary encoder also, no problem. That way you wont miss any changes because you missed an interrupt (if you poll often enough). Usually rotary encoders work much faster than switches, that might be an issue.
And, for rotary encoders to work reliably with interrupts, you need to detect rising-edge and falling-edge interrupts (pin change interrupt) on both sensor lines.

This is how I would do it with interrupts: Lets say you have two datalines, A and B. Both lines have dedicated interrupt routines to detect rising and falling edges.

Pin change interrupt for A:
A ≠ B -> RIGHT
A = B -> LEFT

Pin change interrupt for B:
A = B -> RIGHT
A ≠ B -> LEFT

For polling, you must save the last state the datalines A and B were, and then detect changes and with simple logic determine the direction. First, find out which data line changed, and then use the above logic.
 
Last edited:
Mr Al!! I use these encoders quite a bit..... I have written my own Quadrature encoder... I can't afford to lose counts so I have written the encoder to never miss a beat....

I took a pic12f1822... clocked it to 32Mhz and wrote a small state machine... RA0 and RA1 are connected directly to the rotary encoder... I have tested it to 2khz ( faster than the specification of the rotary encoder ) . The output is a pulse on RA5 for count and a logic level on RA4 for direction..

I measure payout ( very accurately ) and have no issues at all...
 
Hi,

Thanks for the ideas/data etc.

Here is the code i was using for now.

Code:
  delayMicroseconds(10000);
  int MSB = digitalRead(CLK); //MSB = most significant bit
  int LSB = digitalRead(DAT); //LSB = least significant bit



  int encoded = (MSB << 1) |LSB;
  int sum  = (lastEncoded << 2) | encoded;


  if(sum == 0b1101 || sum == 0b0100 || sum == 0b0010 || sum == 0b1011) dir=1;
  if(sum == 0b1110 || sum == 0b0111 || sum == 0b0001 || sum == 0b1000) dir=-1;
  count=count+dir;


  lastEncoded = encoded;
 
Hello again,

See far below also.

Ian:
I thought we needed some debounce. I could change that i guess.
The encoder is a pair of switches and switches bounce, so i thought we needed something. With no debounce it seems it would create too many interrupts.

Mosaic:
I'll check out your code.

Far Below:
I decided to look into this a little more, because when going over the code i already had i realized i wasnt entirely sure how the encoder switches were set up, as to when they came on and turned off.
The model here is two switches and one 'wiper'. The two switches are spaced a tiny fraction of an inch apart, and the wiper is wide enough to contact both switches at the same time, but because it is rotating it hits one contact first, then the other contract shortly after that. By connecting two LED's to the two contact pins, i can see the pattern the switches create when the arm is turned like a potentiometer.

The switching action surprised me, and here is why. The encoder also has tiny click stops, so when the arm is turned it clicks into the next position. That seems nice. However, for each click position, either both LED's are ON or both OFF, there appears to be no in between. So click it once, both LED's on, click it again and both LED's off.

To see the sequence of switch action (one before the other due to the phase difference) you have to turn it VERY slowly, being careful NOT to allow it to 'click' into the next position right away, but rather find that in between spot where it is in between click detents. THAT is the only way to see only one LED come on at a time, and that is how we can detect rotation direction.

The problem is, during normal operation that is going to happen pretty fast even for a slow rotation unless we turn it for partial clicks as above. So the controller has to have free control over the detection of these states, which is where the interrupt comes in. If there is anything that turns the interrupts off for any length of time, then the encoder will not be detected every time and it wont be reliable at all.

See, i expected to see a slightly different operation. I expected to see ONE led turn on when it is clicked say one click clockwise, then the other LED turn on when it is clicked another click clockwise, then the first LED go out on the next click, then the second LED go out on the next click, for a total of 4 clicks to get all these states. The way it works now is it only takes TWO clicks to get all 4 states, which means it happens fast.

Since this is the only encoder i have at the moment i cant be sure this is the way they all are or not. I have to wonder why there is no data on the web.

I tried that late yesterday, but i'll be trying some other stuff out today hopefully too.
If you care to test your encoder maybe we can compare the basic switching action of the encoders themselves.
 
I thought we needed some debounce. I could change that i guess.
The encoder is a pair of switches and switches bounce, so i thought we needed something. With no debounce it seems it would create too many interrupts.
That's why I use an external decoder.... As its a state machine it can bounce all it likes.... It still processes it correctly!!
 
Hi Ian,

What do you mean by 'external decoder', you mean you have a separate chip that decodes and sends the data to the uC chip? That sounds very interesting.

I got the routine to work pretty well now but it is counting by 2's instead of by 1's (har har). It works nearly perfect, but instead of counting 0,1,2,3,4,... up to 9, it counts 0,2,4,6,8 then back to 0, and down it goes 8,6,4,2,0 then back to 8.
I know this is because it uses 2 interrupts and both internal switches are activated for every 'click' detent on the encoder, but i havent gotten as far as getting it to count by 1's yet. When i try to make it ignore every other interrupt, the count does not work well at all, missing some, skipping some, etc. So i have to look into this a little more.
I would be interested to see that external decoder too though.

My intended use (for one) is to be able to set the parameters for other devices, such as the frequency generator being discussed in the other thread. A button to change digits, then turn the encoder to get the desired digit. So for 9000Hz i would push the button 3 times, then turn the encoder and the digit shown as '9' above would go from 0 to 9 one step at a time, or one step down from 9 to 0 turning it counter clockwise.
Right now it would go from 0 to 8 by 2's but then skip 9 and go right to zero.
Little more work on it i guess might do it.

I suppose there are better encoders out there too, this is one of those 1 dollar ones. For that price if we can get one to work that's a good deal :)

Oh BTW, i had to increase the 'delay' to 50ms to get reasonable operation, so it required 50ms debounce. I dont see any way around this because it would count too fast before. Turning the thing one click it would go from 1 to 9 or something like that, which tells me it was generating a whole bunch of interrupts one right after the other. A little delay prevents that.
 
Hi,

Oh yes, i remember now.

Since you mentioned this i also looked on the web and found some chips that do it too. I'll have to look into them also. Not sure what the cost would be yet though.

I did get it to work now, but it's strange the way it 'forces' a count of 2 increments instead of just 1. I've never seen anything so illogical before. Anything i do to make it count only 1 does not work, it becomes erratic.
What i had to do was allow it to increment by 2, then divide the digit counts by 2 (shift to the right once). So instead of the count going from 0 to 9 it goes from 0 to 18, then back to zero, but it displays half of that which is 0 to 9. Very strange.
I dont like doing it that way because i cant explain yet why it insists on counting by 2 for stability, but it's very reliable now though so i might just leave it alone for now until i look over the hardware decoders.
 
Your code!!!!

C:
  delayMicroseconds(10000);
  int MSB = digitalRead(CLK); //MSB = most significant bit
  int LSB = digitalRead(DAT); //LSB = least significant bit



  int encoded = (MSB << 1) |LSB;
  int sum  = (lastEncoded << 2) | encoded;


  if(sum == 0b1101 || sum == 0b0100 || sum == 0b0010 || sum == 0b1011) dir=1;
  if(sum == 0b1110 || sum == 0b0111 || sum == 0b0001 || sum == 0b1000) dir=-1;
  count=count+dir;


  lastEncoded = encoded;

Lets assume the sum is non of the above.. ie not moved 1010 or 0101 or 1111 or 0000 then dir will still hold 1 or -1 so you should revise this code so ONLY WHEN sum is one of the above count += dir... Or indeed! place dir = 0; before the if's...
 
I found this article on software switch debouncing by the debounce expert Gnassle.
https://www.embedded.com/electronics-blogs/break-points/4024981/My-favorite-software-debouncers
I dunno, just thought I would share the link, may be old hat to you, but I have no way of knowing that?

Hi,

Yes that looks interesting too. I had similar problems a while back with another project that used a PIC chip. All it had to do was detect the switch closure using an interrupt, then time out a little then go on with the program. What happened though is when i turned a fan on or off, it would generate an interrupt! The chip thought that someone pressed the button :)
So i had to make the debounce routine more sophisticated to ignore some interrupts. If i did not have that fan though i would have never known this can happen, even with that small box fan which does not have a huge motor. The fan was 120vac though.
 
Your code!!!!

C:
  delayMicroseconds(10000);
  int MSB = digitalRead(CLK); //MSB = most significant bit
  int LSB = digitalRead(DAT); //LSB = least significant bit



  int encoded = (MSB << 1) |LSB;
  int sum  = (lastEncoded << 2) | encoded;


  if(sum == 0b1101 || sum == 0b0100 || sum == 0b0010 || sum == 0b1011) dir=1;
  if(sum == 0b1110 || sum == 0b0111 || sum == 0b0001 || sum == 0b1000) dir=-1;
  count=count+dir;


  lastEncoded = encoded;

Lets assume the sum is non of the above.. ie not moved 1010 or 0101 or 1111 or 0000 then dir will still hold 1 or -1 so you should revise this code so ONLY WHEN sum is one of the above count += dir... Or indeed! place dir = 0; before the if's...

Hi,

I changed the code to:

if(sum == 0b1101 || sum == 0b0100 || sum == 0b0010 || sum == 0b1011)
counter+=1;
if(sum == 0b1110 || sum == 0b0111 || sum == 0b0001 || sum == 0b1000)
counter-=1;

Amazingly, it still did the same thing.
I also added a 'pass' variable to detect two interrupts and only count on one:

byte pass=1;

if (pass==1)
{
DoNormalSumCountIncrementTestAbove;
pass=0;
}
else
pass=1;

so the first interrupt does the normal sum test and increment, but the second does nothing because pass=0, it just sets pass=1 so the for the third interrupt it would count again, then for the fourth it would not count again, etc. So only count on the odd passes and dont count on the even passes. Doing that made the count erratic, skipping some counts, stopping at some other counts, seems very unpredictable.

If i let it count 2 times, then it counts really nice: 0, 2, 4, 6, 8, etc.
So i had to divide the actual count by 2 in order to display the correct count :)
I have a feeling there might be something in the Arduino environment code causing this because it just doesnt make any sense logically.

Here's a quick snap shot of the test screen. The large number on the bottom will be the frequency that gets turned into code to send to the frequency generator chip. I was finally able to set all the digits with little or no effort, but still dont have a reasonable explanation why it only works when i let it count 2 at a time instead of the required 1 at a time: 0,1,2,3,... etc.
Note the time display is just the run time of the chip. It will be kept track of for each run and accumulated for a total run time log. The single digit number is just for now the digit in the large bottom number that gets set when i turn the encoder.

I am a little happier now that it works at least the way it is, but it would be nice to know what is causing the 2x count too, if possible. Well, i know what is causing it (two interrupts) but it seems impossible to get rid of without "letting" it do it that way and then dividing later. Stupid right?
 

Attachments

  • Display_Counter-1.jpg
    Display_Counter-1.jpg
    40 KB · Views: 260
I find that people tend to overcomplicate encoder read routines. I wrote the following PIC interrupt routine to read a quadrature encoder, and update a 16 bit count value, so that the main program never needs to do anything but look at the count value. It is correct that the interrupts must always be enabled though. At work, we have been using this on a recent project to read the 4096 PPR encoder on a piece of industrial equipment.
Code:
; Encoder service routine
; for midrange PIC 12F, 16F...
; Inputs:
;    PortA,0 - Phase A input (position critical) - IOC
;    PortA,1 - Optional Index input
;    PortA,2 - Phase B input (position critical) - IOC
;
; Encoder variables:
;    CountH, CountL - High and Low byte of Encoder count
;    SaveA - Previous state of Phase A input
;
EncdrSvc
    rrf portA,w      ;load, shift and save phase A
    rlf SaveA,f      ;new Phase B and “delayed” Phase A are now aligned
    xorwf SaveA,w    ;XOR to calculate direction
    andlw 0x02       ;mask off all but direction value which is either 0 or 2
    subwf CountL,f   ;subtract 0 or 2 from count depending on direction
    btfsc status,c
    incf CountH,f
    incfsz CountL,f  ;then add 1 to adjust the 0/+2 increment to +/-1
    decf CountH,f

It is based on the fact that once a change of state of either encoder output (A or B) is detected (by means of the IOC—Interrupt On Change setting), then the direction of rotation can be determined by XORing the current state of one output with the previous state of the other output. This fully decodes every state transition, giving the maximum possible resolution. Note also that the PIC inputs are arranged for maximum efficiency. The above code doesn't include the standard ISR preamble and postamble, for saving and restoring the W and Status registers, which is still required .
 
Hi,

Thanks, and what i tried was similar. Since the pins are either the same or different, i used:

if(MSB!=LSB) then
count+=1;
else
count-=1;

This however depends highly on reading the two pins in correctly. If there is bounce on the 'on change' pin it might still read incorrectly. The other pin should not bounce, but i dont know how long it takes to settle vs how fast the encoder is being turned.

I based this on looking at the waveforms for the two encoder pins. When one of them changes the other looks 'stable', but i dont know for sure how stable it is.

I also considered using two interrupts for the same pin. One set to detect high to low, the other for low to high, but then realized that any bounce at all will set them both off, unless maybe the first one cancels the second one. If we know what level one pin changed to, we can read the other pin and know the direction, if we catch the first change that is.
Havent tried anything with this yet though.

I dont know why the MSB!=LSB did not work. It should have worked, but led to very erratic and very unrepeatable operation.
 
Status
Not open for further replies.

New Articles From Microcontroller Tips

Back
Top