User Tools

Site Tools


arduino:rotary-encoder

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
arduino:rotary-encoder [2020/02/11 21:05]
Ilias Iliopoulos
arduino:rotary-encoder [2024/02/02 21:48] (current)
Ilias Iliopoulos
Line 1: Line 1:
-====== Rotary Encoder ======+====== Rotary Encoder ​in real life ======
  
 ===== Introduction ===== ===== Introduction =====
Line 7: Line 7:
 The first thing in an engineers'​ mind when solving a problem is to find an existing solution. I have tried several different implementations of algorithms designed for Arduino, which are supposed to read a Rotary Encoder in a manner that is resistant to the contact bouncing problem. Most of the algorithms which provide a reliable solution are based on polling the current state of the switches at regular intervals and implement an elaborate state-machine to deduce status and rotational direction. Other algorithms based on hardware interrupts, employ a small time period to wait during the bounce (debouncing delay) and read again the state when the contacts have calmed down. They can also receive multiple readings at intervals and realize that the state has reached stability when the readings do not change. ​   The first thing in an engineers'​ mind when solving a problem is to find an existing solution. I have tried several different implementations of algorithms designed for Arduino, which are supposed to read a Rotary Encoder in a manner that is resistant to the contact bouncing problem. Most of the algorithms which provide a reliable solution are based on polling the current state of the switches at regular intervals and implement an elaborate state-machine to deduce status and rotational direction. Other algorithms based on hardware interrupts, employ a small time period to wait during the bounce (debouncing delay) and read again the state when the contacts have calmed down. They can also receive multiple readings at intervals and realize that the state has reached stability when the readings do not change. ​  
  
-In my project, due to the limited available resources ​ and the nature of the system, it was not acceptable for the software neither to waste time waiting for anything, nor to add additional hardware, such as a monostable nor to use a more expensive encoder such as a debounced optical encoder. I wanted a purely software, interrupt-based solution that would deduce the state of the rotary encoder directly upon invocation. After lots of searching and trying, I came across the article [[http://​www.technoblogy.com/​show?​1YHJ | "​Bounce-Free Rotary Encoder"​]] ​ by David Johnson-Davies of http://​www.technoblogy.com,​ which presented what I consider as the most elegant and reliable solution on-line. ​+Please note that this article refers to a type of rotary encoders known as **incremental**. The other type is the **absolute** rotary encoders which operate under a totally different principle. The absolute rotary encoders are far more expensive than the incremental ones and this cost is justified for specific applications only, where it is required to read the actual position of the encoder at any point in time. Consider for example an instrument that shows the direction of the wind. The instrument must report the actual direction of the wind when it starts up and therefore an absolute rotary encoder should be selected in this design. Incremental rotary encoders are less expensive but even they may require additional resources to operate properly in a particular application. Consider ​ a simple case of a volume control button. One would expect that when the apparatus is turned on, the volume would be set at the position it had when it was turned off. Meaning that the current volume setting should be stored in a non-volatile memory. Only if we decided that the volume would be set at a specific (e.g. 1 of 10) setting any time the apparatus would be turned on, then the memory would not be required. ​   
 + 
 +Back to the incremental encoders then. In my project, due to the limited available resources ​ and the nature of the system, it was not acceptable for the software neither to waste time waiting for anything, nor to add additional hardware, such as a monostable nor to use a more expensive encoder such as a debounced optical encoder. I wanted a purely software, interrupt-based solution that would deduce the state of the rotary encoder directly upon invocation. After lots of searching and trying, I came across the article [[http://​www.technoblogy.com/​show?​1YHJ | "​Bounce-Free Rotary Encoder"​]] ​ by David Johnson-Davies of http://​www.technoblogy.com,​ which presented what I consider as the most elegant and reliable solution on-line. ​
  
 I have spent some time getting acquainted with David'​s solution and I have identified a few intricacies that are inherent to the design, that I considered worthwhile to document. In addition, the diagrams that I have created may be useful to those who want to dive into the internals of the Rotary Encoders. I have spent some time getting acquainted with David'​s solution and I have identified a few intricacies that are inherent to the design, that I considered worthwhile to document. In addition, the diagrams that I have created may be useful to those who want to dive into the internals of the Rotary Encoders.
  
-One of the reasons for writing this article is because I find it extremely remarkable how such a mundane thing, like **"​reading two input pins"​** requires so much effort and knowledge to deal with; and still is not solved 100%. At times when sensors are fully blown computers and interfacing with them is performed by just dropping a library and type a few commands, reading two contacts still bring us back to the basics! Perhaps, ​young engineers can bring it as an example to their managers ​who ask them to built a Mars Rover "by tomorrow"​ :-).+One of the reasons for writing this article is because I find it extremely remarkable how such a mundane thing, like **"​reading two input pins"​** requires so much effort and knowledge to deal with; **and still is not solved 100%**. At times when sensors are full-blown computers and interfacing with them is performed by just dropping a library ​into the code and typing ​a few commands, reading two contacts still bring us back to the basics, requiring expertise in electricity,​ electronics and algorithms! Perhaps, ​junior ​engineers can bring the case as an example to their managers, when such managers demand ​them to built a Mars Rover "by tomorrow"​ :-).
  
 If you are familiar with how Rotary Encoders work and you are are only interested in the debouncing method, you can go directly to [[arduino:​rotary-encoder#​Explanation of the de-bouncing method| Explanation of the de-bouncing method]]. If you are familiar with how Rotary Encoders work and you are are only interested in the debouncing method, you can go directly to [[arduino:​rotary-encoder#​Explanation of the de-bouncing method| Explanation of the de-bouncing method]].
 ===== Theory of operation ===== ===== Theory of operation =====
  
-Rotary Encoders are based on the principle of utilizing several signals that are encoded in a binary form, so that any transition causes the change of just on single bit. For an explanation of the basics, you can consult the following Wikipedia articles:+Rotary Encoders are based on the principle of utilizing several signals that are encoded in a binary form, so that any transition causes the change of **just one single bit**. For an explanation of the basics, you can consult the following Wikipedia articles:
  
   * [[https://​en.wikipedia.org/​wiki/​Rotary_encoder | Rotary encoder]]   * [[https://​en.wikipedia.org/​wiki/​Rotary_encoder | Rotary encoder]]
   * [[https://​en.wikipedia.org/​wiki/​Gray_code | Gray code]]  ​   * [[https://​en.wikipedia.org/​wiki/​Gray_code | Gray code]]  ​
  
-In this article, we will explore a simple form of Rotary Encoder ​having ​two signals, A and B. Most encoders ​procured from the Chinese market, are based on this principle. ​   ​+In this article, we will explore a simple form of Rotary Encoder ​which provides as outputs ​two signals, A and B. Most encoders ​labeled as EC11 or KY-040, are based on this principle. Please note that not all rotary encoders behave exactly the same, especially during transition from one state to another, depending on their construction. I will try to implement a universal solution, applicable to most rotary encoders variants. It is understood that this approach may have implications which may or may not affect a specific implementation and should be considered thoroughly at the design phase of each project.    ​
  
 In theory, we learn that signals A and B __"are in quadrature"​__ or __"are 90 degrees out of phase"​__. In theory, we learn that signals A and B __"are in quadrature"​__ or __"are 90 degrees out of phase"​__.
Line 36: Line 38:
 The transition of the signals are shown with **arrows**, so that the reader can focus on what is happening at the transition point, which is the point of interest in an interrupts-base solution. The arrows are placed purposely on the right side of the transition, in order to correspond with the new binary state of the signal which is shown on the table below the diagram. ​ The transition of the signals are shown with **arrows**, so that the reader can focus on what is happening at the transition point, which is the point of interest in an interrupts-base solution. The arrows are placed purposely on the right side of the transition, in order to correspond with the new binary state of the signal which is shown on the table below the diagram. ​
  
-Starting with time instance in column C, in the upper part of the figure (Clockwise) we see that A is ''​LOW''​ and B is ''​HIGH''​. Accepting a notation of ''​LOW = 0''​ and ''​HIGH = 1'',​ we have ''​A = 0'',​ ''​B = 1''​ and consequently ''​AB = 00 (binary) = 0 (decimal)''​. In the following, I will refer to AB only in its decimal form, because it is easier ​ata least for us humans to identify the state transition sequences.+Starting with time instance in column C, in the upper part of the figure (Clockwise) we see that A is ''​LOW''​ and B is ''​HIGH''​. Accepting a notation of ''​LOW = 0''​ and ''​HIGH = 1'',​ we have ''​A = 0'',​ ''​B = 1''​ and consequently ''​AB = 00 (binary)''​ which is same as ''​AB ​= 0 (decimal)''​. In the following, I will refer to AB only in its decimal form, because it is easier, at least for us humansto identify the state transition sequences.
  
 Going back to Diagram 1, at the end of time instance C, signal B goes ''​LOW'',​ while signal A remains ''​LOW''​. Most rotary encoders designed to be operated by humans (in contrast to other encoders which are moved and controlled by machines), employ a mechanism to temporarily "​lock"​ the shaft in this resting position, which is the **"​detent"​**. Turning the shaft right or left of this position, you feel a distinctive "​click"​ when you move to the next stable position. These positions are shown as **"​stable zones"​** in the diagram. Therefore, in the types of encoders that we examine, we will consider the change from one detent to the immediately neighbouring detent, either on the right side or the left side, as one identified change in our position indication. ​     ​ Going back to Diagram 1, at the end of time instance C, signal B goes ''​LOW'',​ while signal A remains ''​LOW''​. Most rotary encoders designed to be operated by humans (in contrast to other encoders which are moved and controlled by machines), employ a mechanism to temporarily "​lock"​ the shaft in this resting position, which is the **"​detent"​**. Turning the shaft right or left of this position, you feel a distinctive "​click"​ when you move to the next stable position. These positions are shown as **"​stable zones"​** in the diagram. Therefore, in the types of encoders that we examine, we will consider the change from one detent to the immediately neighbouring detent, either on the right side or the left side, as one identified change in our position indication. ​     ​
  
-If we consider signal A the most significant bit (MSB) and B the least significant bit (LSB) of a binary number composed of A and B, we can see that when the shaft is not rotating, it rests at state **00**which is decimal **0**, or at state ** binary 11** which is **decimal 3**. +** NOTE: The stable zones had been a source of controversy since I have received several emails regarding implementations of other encoder manufacturers where the signal transition from one step to another and the detents did not follow the above principle. For example, one detent might correspond to a change of a single signal, either A or B, according to the Gray encoding. The reason that I have not covered this case in this section relates: ** 
 + 
 +** a) to the fact that the common EC11, KY-040 variants produce two Gray code transitions per detent. As per the {{arduino:​ky-040-datasheet.pdf|KY-040 datasheet}}:​ ** 
 + 
 +// 
 + A rotary encoder has a fixed number of positions. These positions are easily felt as small clicks when you turn the encoder. The KY-040 module has thirty of these positions....... In each encoder position, both switches are either opened or closed. Each click causes these switches to change states as follows: 
 + 
 +  * If both switches are closed, turning the encoder either clockwise or counterclockwise,​ one position will cause both switches to open. 
 + 
 +  * If both switches are open, turning the encoder either clockwise or counterclockwise,​ one position will cause both switches to close. 
 +//  
 + 
 +**  
 +b) to the principle of the debouncing algorithm [[arduino:​rotary-encoder#​2. A transition is considered valid when both signals (A and B) change value]] presented below. Therefore, I considered that even in the case where one detent corresponds to a change in only one signal, the user should make two shaft movements, or in the generic case, as many movements as necessary so that both signals are changed. This obviously has the effect that the Gray code will move two positions instead of one. This is a side-effect of this debouncing algorithm and the price to pay if we do not debounce with hardware.  
 +** 
 + 
 +If we consider signal A the most significant bit (MSB) and B the least significant bit (LSB) of a binary number composed of A and B, we can see that when the shaft is not rotating, it rests at state **00** ​(which is decimal **0**), or at state ** binary 11** (which is **decimal 3**)
  
 In the **clockwise** direction, moving from stable state **0** (column D) to stable state **3** (column F), the encoder needs to pass through state **2** (column E). Then, to go from stable state **3** (column G) to stable state **0** (column H), the encoder needs to pass through transit state **1** (column G). So, we have a pattern **0-2-3** and **3-1-0** to go from one stable state to the next, rotating clockwise. ​ In the **clockwise** direction, moving from stable state **0** (column D) to stable state **3** (column F), the encoder needs to pass through state **2** (column E). Then, to go from stable state **3** (column G) to stable state **0** (column H), the encoder needs to pass through transit state **1** (column G). So, we have a pattern **0-2-3** and **3-1-0** to go from one stable state to the next, rotating clockwise. ​
Line 55: Line 73:
 ===== The bouncing problem ===== ===== The bouncing problem =====
  
-The problem with electro-mechanical switches is that when a contact between the two wires makes or breaks, the two wires jump momentarily,​ creating a series of unwanted pulses. This is an over-simplification,​ because there are also additional ​caused for the bouncing effect. In addition, the input pin may have a range where the voltage level is not identified clearly as logic ''​HIGH''​ or ''​LOW''​. Stray capacitance or even capacitance from RC filters inserted to fight bounce (!) may keep the voltage at this range for quite some time.  If the input of the MCU does not provide some form of hysteresis, such as a Schmitt Trigger, the results can be unpredictable. Finally, strong electromagnetic interference (EMI) can be a source for unwanted pulses. Arm yourself with some patience, if you want to dig into the matter and read [[https://​my.eng.utah.edu/​~cs5780/​debouncing.pdf|this article by Jack G. Ganssle]].+The problem with electro-mechanical switches is that when a contact between the two wires makes or breaks, the two wires jump momentarily,​ creating a series of unwanted pulses. This is an over-simplification,​ because there are also additional ​issues which cause the bouncing effect. In addition, the input pin may have a range where the voltage level is not identified clearly as logic ''​HIGH''​ or ''​LOW''​. Stray capacitance or even capacitance from RC filters inserted to fight bounce ​in some designs(!) may keep the voltage at this range for quite some time.  If the input of the MCU does not provide some form of hysteresis, such as a Schmitt Trigger, the results can be unpredictable. Finally, strong electromagnetic interference (EMI) can be a source for unwanted pulses. Arm yourself with some patience, if you want to dig into the matter and read [[https://​my.eng.utah.edu/​~cs5780/​debouncing.pdf|this article by Jack G. Ganssle]].
  
 The bouncing phenomenon is shown graphically in the diagram below. When a signal changes from one state to the other, several pulses may be produced before the signal rests to its final state. Some of them may produce the value **AB = 0** or **AB = 3** and be mistakenly identified as stable zone in the detent. ​ The bouncing phenomenon is shown graphically in the diagram below. When a signal changes from one state to the other, several pulses may be produced before the signal rests to its final state. Some of them may produce the value **AB = 0** or **AB = 3** and be mistakenly identified as stable zone in the detent. ​
Line 63: Line 81:
 Most algorithms try to eliminate the bouncing problem by examining whether the rotation creates strictly the known patterns (**0-2-3, 3-1-0, 0-1-3, 3-2-0**). Anything different will be considered illegal and will not be identified as a known rotation. The problem with this method, is that although we have moved the shaft of the encoder, in the case of illegal patterns the system does not respond. If for example we use a knob to change the value at a display, sometimes we may need one "​click"​ to change the value from e.g. number 7 to 8, and other times two or three clicks. This behaviour is certainly not acceptable. We definitely want each and every click of the encoder to produce one response from the system. ​ Most algorithms try to eliminate the bouncing problem by examining whether the rotation creates strictly the known patterns (**0-2-3, 3-1-0, 0-1-3, 3-2-0**). Anything different will be considered illegal and will not be identified as a known rotation. The problem with this method, is that although we have moved the shaft of the encoder, in the case of illegal patterns the system does not respond. If for example we use a knob to change the value at a display, sometimes we may need one "​click"​ to change the value from e.g. number 7 to 8, and other times two or three clicks. This behaviour is certainly not acceptable. We definitely want each and every click of the encoder to produce one response from the system. ​
  
-Yet, //bouncing is a fact of life (and not only of electro-mechanical contacts :-) ) and we must learn to live with it//. Even with more expensive electro-mechanical switches that do not present a problem ​in the current time, when just purchased, they may exhibit bouncing problems after several months or years of use, because of the wear of the metallic parts due to oxidization,​ electrolysis etc, as well as other mechanical issues.  ​+Yet, //bouncing is a fact of life (and not only of electro-mechanical contacts :-) ) and we must learn to live with it//. Even with more expensive electro-mechanical switches that do not present a problem when just purchased, they may exhibit bouncing problems after several months or years of use, because of the wear of the metallic parts due to oxidization,​ electrolysis etc, as well as other mechanical issues.  ​
  
 ===== Explanation of the de-bouncing method ===== ===== Explanation of the de-bouncing method =====
Line 69: Line 87:
 The [[http://​www.technoblogy.com/​show?​1YHJ|debouncing method presented by David Johnson-Davies]] makes use of the following principles: The [[http://​www.technoblogy.com/​show?​1YHJ|debouncing method presented by David Johnson-Davies]] makes use of the following principles:
  
-==== When one signal changes state, the other is stable ====+==== 1. When one signal changes state, the other is stable ====
    
 This is a result of the phase shift between the two signals. We assume that bouncing of one signal has ended before the other signal changes state. This sets a limit to the maximum speed of rotation of the shaft. Considering that bouncing takes place for 20 milliseconds and as shown in Diagram 2, at the end of the bouncing (column I) the state of signal A is at its stable and resting point, the **maximum rotation speed** (if column I is of almost zero duration) is 1/20ms = 50 transitions per second. I have experienced switches with longer bouncing issues, such as 100ms, so I would consider safe a maximum rotational speed of ''​10 transitions per second''​. This value can be within the limits of a person trying to abuse the shaft, but remember that we need to design considering that this type of abuse is part of the ordinary life of an electronic equipment. This is a result of the phase shift between the two signals. We assume that bouncing of one signal has ended before the other signal changes state. This sets a limit to the maximum speed of rotation of the shaft. Considering that bouncing takes place for 20 milliseconds and as shown in Diagram 2, at the end of the bouncing (column I) the state of signal A is at its stable and resting point, the **maximum rotation speed** (if column I is of almost zero duration) is 1/20ms = 50 transitions per second. I have experienced switches with longer bouncing issues, such as 100ms, so I would consider safe a maximum rotational speed of ''​10 transitions per second''​. This value can be within the limits of a person trying to abuse the shaft, but remember that we need to design considering that this type of abuse is part of the ordinary life of an electronic equipment.
  
-==== A transition is considered valid when both signals (A and B) change value ====+==== 2. A transition is considered valid when both signals (A and B) change value ====
  
 This is a result of the detents, having stable zones when the signals AB are in state ''​0''​ or ''​3''​. So, whatever happens at the state of each switch during the progress of time, such as bouncing, is irrelevant ​ if the outcome is not causing a change in both signals. This principle has consequences when the direction of the rotation changes, as we shall examine below. This is a result of the detents, having stable zones when the signals AB are in state ''​0''​ or ''​3''​. So, whatever happens at the state of each switch during the progress of time, such as bouncing, is irrelevant ​ if the outcome is not causing a change in both signals. This principle has consequences when the direction of the rotation changes, as we shall examine below.
Line 84: Line 102:
 {{ :​arduino:​re_diagram_2.png |Diagram 2}} {{ :​arduino:​re_diagram_2.png |Diagram 2}}
  
-It is also worth mentioning that **the rotational movement is identified at the beginning of the bounce**, as indicated by the red arrow. This makes the algorithm more responsive, in contrast to other algorithms based on debounce ​delay, which identify the transition only at the end of the bounce.  ​+It is also worth mentioning that **the rotational movement is identified at the beginning of the bounce**, as indicated by the red arrow. This makes the algorithm more responsive, in contrast to other algorithms based on a debouncing ​delay. Such algorithms should wait until the end of the bounce ​to identify a steady state and subsequently infer the transition.  ​
  
  
Line 197: Line 215:
 These flaws are minor, when dealing with rotary encoders operated by humans, but may be significant when the algorithm is intended to be used in automated environments. ​ These flaws are minor, when dealing with rotary encoders operated by humans, but may be significant when the algorithm is intended to be used in automated environments. ​
  
-Can we fix those flaws? Regarding the missed step at the change of rotational detection, the answer is **no**, because B must have changed from its previous value at the time when A changes value. This is inherent to the debouncing algorithm and we will have to leave with it.     +Can we fix those flaws? Regarding the missed step at the change of rotational detection, the answer is **no**, because B must have changed from its previous value at the time when A changes value. ​**This is inherent to the debouncing algorithm and we will have to live with it**.     
  
-What about the random change at the first click after the program starts? Of course, we cannot control the resting state of the encoder. But what we can do, is enhance the code by reading the current state of B when the program starts and set the value of the previous B, depending on the current state of the switch and the intended or expected direction of the first rotation. ​+What about the random change at the first click after the program starts? Of course, we cannot control the resting state of the encoder. But what we can do, is enhance the code by reading the current state of B when the program starts and set the value of the previous B, depending on the current state of the switch and the intended or the expected direction of the first rotation. ​
  
 For example, consider that our implementation has a counter displaying only positive values (e.g. from 1 to 10) and the value is increased with a clockwise rotation. We can setup the system so that the first "​lost"​ detent will occur at the same clockwise rotation. Starting from the initial point of counter = 1, the first right click will be lost and the second right click will set counter = 2. If the user changes direction and turns left, the first left click will be lost, but the second left click will return the shaft to the starting position of 1.  For example, consider that our implementation has a counter displaying only positive values (e.g. from 1 to 10) and the value is increased with a clockwise rotation. We can setup the system so that the first "​lost"​ detent will occur at the same clockwise rotation. Starting from the initial point of counter = 1, the first right click will be lost and the second right click will set counter = 2. If the user changes direction and turns left, the first left click will be lost, but the second left click will return the shaft to the starting position of 1. 
  
-Another workaround to make the operation of the encoder to be consistent in each and every step, instead of "​fixing"​ the problem, we can "​expand"​ the problem to all transitions. If for example, after each transition, we set the previous state to a wrong value, the encoder will not recognize the direction of the next step. This way, each position change, regardless of the direction, will require two clicks. This feature is implemented in the library.+Another workaround to make the operation of the encoder to be consistent in each and every step, instead of "​fixing"​ the problem, we can "​expand"​ the problem to all transitions. If for example, after each transition, we set the previous state to a wrong value, the encoder will not recognize the direction of the next step. This way, each position change ​will require two clicks, regardless of the direction. This feature is implemented in the library.
  
 Finally, it seems that to deal completely with the bouncing problem, a hardware solution (monostable multivibrator,​ flip-flop, RC filter followed by Schmitt trigger etc.) is the proper thing to do. If no such resource is available, or such a level of accuracy is not mandatory, such as for hobby projects, the next best thing is the algorithm described above.  ​ Finally, it seems that to deal completely with the bouncing problem, a hardware solution (monostable multivibrator,​ flip-flop, RC filter followed by Schmitt trigger etc.) is the proper thing to do. If no such resource is available, or such a level of accuracy is not mandatory, such as for hobby projects, the next best thing is the algorithm described above.  ​
  
-During my tests with an Arduino Uno or Nano, I have noticed that the pins of PortD (PD0 - PD7) seem to have a much better tolerance to bouncing than PortB. But this is just a feeling and perhaps the subject of another research. ​+**Note:​** ​During my tests with an Arduino Uno or Nano, I have noticed that the pins of PortD (PD0 - PD7) seem to have a much better tolerance to bouncing than PortB. But this is just a feeling and perhaps the subject of another research. ​
 ===== The library ===== ===== The library =====
        
Line 214: Line 232:
 In addition to the rotary encoder, the code has provision for a push-button which is integrated in the shaft of the rotary encoder. Being an electro-mechanical switch, this push-button also suffers from bouncing problems, that are coped with by the standard debouncing delay method. ​ In addition to the rotary encoder, the code has provision for a push-button which is integrated in the shaft of the rotary encoder. Being an electro-mechanical switch, this push-button also suffers from bouncing problems, that are coped with by the standard debouncing delay method. ​
  
-Please check [[https://​github.com/​fryktoria/​FR_Rotary_Encoder ​| the Github page]] for a description of the functionality available and to download the library. Several examples which demonstrate the library features are included. ​  +Please check [[https://​github.com/​fryktoria/​FR_RotaryEncoder ​| the Github page]] for a description of the functionality available and to download the library. Several examples which demonstrate the library features are included. ​  
  
 +~~DISQUS~~
arduino/rotary-encoder.1581447913.txt.gz · Last modified: 2020/02/11 21:05 by Ilias Iliopoulos