{"id":4307,"date":"2019-08-21T20:00:48","date_gmt":"2019-08-21T18:00:48","guid":{"rendered":"https:\/\/msalamon.pl\/?p=4307"},"modified":"2025-12-27T19:25:04","modified_gmt":"2025-12-27T18:25:04","slug":"ridiculously-simple-hardware-encoder-handling-on-stm32","status":"publish","type":"post","link":"https:\/\/msalamon.pl\/en\/ridiculously-simple-hardware-encoder-handling-on-stm32\/","title":{"rendered":"Ridiculously Simple Hardware Encoder Handling on STM32"},"content":{"rendered":"\n<p>An encoder is a great device. You surely know some equipment where a knob would rotate endlessly while adjusting some parameter (e.g., an audio amplifier). A very nice idea to replace a classic potentiometer. Encoders are also very often used to measure the rotation angle of motors. In automation and robotics they are <\/p>\n\n\n\n<!--more-->\n\n\n\n<p>indispensable devices. Come on, I\u2019ll show you how easy it is to implement handling such an encoder on STM32.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">How an incremental encoder works<\/h1>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter\"><a href=\"https:\/\/sklep.msalamon.pl\/produkt\/enkoder-impulsator-obrotowy\/?utm_source=blog&amp;utm_medium=banner&amp;utm_campaign=encoder&amp;utm_content=encoder\"><img loading=\"lazy\" decoding=\"async\" width=\"1200\" height=\"400\" src=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/Enkoder_baner.jpg\" alt=\"\" class=\"wp-image-1118\" srcset=\"https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/Enkoder_baner.jpg 1200w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/Enkoder_baner-300x100.jpg 300w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/Enkoder_baner-1024x341.jpg 1024w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/Enkoder_baner-768x256.jpg 768w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/Enkoder_baner-24x8.jpg 24w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/Enkoder_baner-36x12.jpg 36w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/Enkoder_baner-160x53.jpg 160w\" sizes=\"auto, (max-width: 1200px) 100vw, 1200px\" \/><\/a><\/figure>\n<\/div>\n\n\n<p><\/p>\n\n\n\n<p>A rotary encoder, also called an incrementer, typically has two outputs labeled <strong>A<\/strong> and <strong>B<\/strong>. As the knob rotates, square-wave signals appear on these outputs. Both waveforms form a quadrature signal. It is characterized by the two waveforms being 90 degrees out of phase. <strong>You can see such a signal in the graphic below.<\/strong><\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter\"><a href=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/encoder_quadrature_routine.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"444\" src=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/encoder_quadrature_routine-1024x444.png\" alt=\"\" class=\"wp-image-1116\" srcset=\"https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/encoder_quadrature_routine-1024x444.png 1024w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/encoder_quadrature_routine-300x130.png 300w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/encoder_quadrature_routine-768x333.png 768w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/encoder_quadrature_routine-1536x666.png 1536w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/encoder_quadrature_routine-24x10.png 24w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/encoder_quadrature_routine-36x16.png 36w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/encoder_quadrature_routine-160x69.png 160w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/encoder_quadrature_routine.png 1920w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n<\/div>\n\n\n<p><\/p>\n\n\n\n<p>I drew two waveforms \u2014 CW and CCW. They simply denote the waveform for rotation clockwise (CW) and counterclockwise (CCW).<\/p>\n\n\n\n<p>In green I marked the start and end of the changes that occur during one encoder detent. At rest both lines A and B are high. While the encoder is being turned, square-wave signals form at the outputs. Depending on the rotation direction, the signal begins with a falling edge on pin A for CW or on pin B for CCW. On the right I pasted how this looks in a live example from a logic analyzer.<\/p>\n\n\n\n<p>You can notice that I marked every edge. It is on them that it\u2019s best to detect the direction in which the lever or a motor shaft with a magnet is turning. How to determine from an edge which way the encoder is turning? You can notice a certain relationship, which I presented in the table below.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter\"><a href=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/encoder_stepping_table.jpg\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"447\" src=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/encoder_stepping_table-1024x447.jpg\" alt=\"\" class=\"wp-image-1117\" srcset=\"https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/encoder_stepping_table-1024x447.jpg 1024w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/encoder_stepping_table-300x131.jpg 300w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/encoder_stepping_table-768x335.jpg 768w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/encoder_stepping_table-24x10.jpg 24w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/encoder_stepping_table-36x16.jpg 36w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/encoder_stepping_table-160x70.jpg 160w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/encoder_stepping_table.jpg 1122w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n<\/div>\n\n\n<p><\/p>\n\n\n\n<p>Moving from left to right in the table gives you the direction in which the encoder rotates. As you can see, you can set interrupts, for example, on the falling edge of A and check the state of line B at that moment. If it\u2019s low, the rotation was \u201cleft\u201d. If B was high, it rotated \u201cright\u201d. Simple, isn\u2019t it?<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Handling the encoder<\/h2>\n\n\n\n<p>We should handle the received encoder signal on the microcontroller. With the above table you can already write the interrupt handling. In theory it looks nice, but reality is different. A rotary encoder like this is built with mechanical switches. Do you know what that means? Yes, contact bounce\u2026 With interrupts set on a single edge it may turn out that during a single detent many more edges appear. The microcontroller is so fast that the interrupt service will be executed multiple times, yielding an unintended effect.&nbsp;<\/p>\n\n\n\n<p>What can be done? You can use some debouncing method. However, such software elimination of contact bounce on an encoder is not the most convenient solution. I have a much better way out of this tough situation for you.&nbsp;<\/p>\n\n\n\n<p>At hand we have, of course, the <strong>STM32. Its timers<\/strong> <strong>have hardware encoder support<\/strong>! Isn\u2019t that beautiful? Sure it is \ud83d\ude42<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Hardware encoder handling on a timer<\/h2>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter\"><a href=\"https:\/\/sklep.msalamon.pl\/kategoria-produktu\/dev-boardy\/stm32-nucleo\/?utm_source=blog&amp;utm_medium=banner&amp;utm_campaign=encoder&amp;utm_content=nucleo\"><img loading=\"lazy\" decoding=\"async\" width=\"1200\" height=\"400\" src=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2020\/07\/Nucleo-64-baner.jpg\" alt=\"\" class=\"wp-image-1593\" srcset=\"https:\/\/msalamon.pl\/wp-content\/uploads\/2020\/07\/Nucleo-64-baner.jpg 1200w, https:\/\/msalamon.pl\/wp-content\/uploads\/2020\/07\/Nucleo-64-baner-300x100.jpg 300w, https:\/\/msalamon.pl\/wp-content\/uploads\/2020\/07\/Nucleo-64-baner-1024x341.jpg 1024w, https:\/\/msalamon.pl\/wp-content\/uploads\/2020\/07\/Nucleo-64-baner-768x256.jpg 768w\" sizes=\"auto, (max-width: 1200px) 100vw, 1200px\" \/><\/a><\/figure>\n<\/div>\n\n\n<p><\/p>\n\n\n\n<p>Today I\u2019ll be working on a <a href=\"https:\/\/sklep.msalamon.pl\/produkt\/nucleo-f401re\/?utm_source=blog&amp;utm_medium=article&amp;utm_campaign=neo6&amp;utm_content=Text\">Nucleo with STM32F401RE<\/a>. Let\u2019s take a look at the Reference Manual of the chip. We will surely find something interesting there.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter\"><a href=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/encoder_mode_rm.jpg\"><img loading=\"lazy\" decoding=\"async\" width=\"364\" height=\"520\" src=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/encoder_mode_rm.jpg\" alt=\"\" class=\"wp-image-1115\" srcset=\"https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/encoder_mode_rm.jpg 364w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/encoder_mode_rm-210x300.jpg 210w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/encoder_mode_rm-17x24.jpg 17w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/encoder_mode_rm-25x36.jpg 25w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/encoder_mode_rm-56x80.jpg 56w\" sizes=\"auto, (max-width: 364px) 100vw, 364px\" \/><\/a><\/figure>\n<\/div>\n\n\n<p><\/p>\n\n\n\n<p>As you can see there is a timer feature called <em>Encoder interface mode<\/em>. The description takes roughly two pages, so it\u2019s not extensive. It\u2019s worth getting familiar with it. On the other hand, it doesn\u2019t have to be extensive, because it\u2019s quite a simple timer function. In the RM you\u2019ll find which registers to configure for encoder handling to work. Fortunately, we have STM32CubeIDE with the built-in Cube, which will do it all for us. Let\u2019s go to it.<\/p>\n\n\n\n<p>I will configure TIM1 as the one that will read the A and B signals from the encoder. In this counter\u2019s settings, choose the <strong>Combined Channels<\/strong> option and the <strong>Encoder Mode<\/strong> function. Most of the remaining functions of the counter will be grayed out.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter\"><a href=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/encoder_mode_combined_channels.jpg\"><img loading=\"lazy\" decoding=\"async\" width=\"746\" height=\"441\" src=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/encoder_mode_combined_channels.jpg\" alt=\"\" class=\"wp-image-1113\" srcset=\"https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/encoder_mode_combined_channels.jpg 746w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/encoder_mode_combined_channels-300x177.jpg 300w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/encoder_mode_combined_channels-24x14.jpg 24w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/encoder_mode_combined_channels-36x21.jpg 36w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/encoder_mode_combined_channels-135x80.jpg 135w\" sizes=\"auto, (max-width: 746px) 100vw, 746px\" \/><\/a><\/figure>\n<\/div>\n\n\n<p><\/p>\n\n\n\n<p>On the MCU model, two pins \u2014 <strong>CH1<\/strong> and <strong>CH2<\/strong> \u2014 will appear configured for Timer 1. You can name them according to the connection. Some modules will have the signals marked as A and B, and some will have analogously <strong>CLK<\/strong> and <strong>DT<\/strong>.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter\"><a href=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/encoder_mode_channels.jpg\"><img loading=\"lazy\" decoding=\"async\" width=\"548\" height=\"494\" src=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/encoder_mode_channels.jpg\" alt=\"\" class=\"wp-image-1112\" srcset=\"https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/encoder_mode_channels.jpg 548w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/encoder_mode_channels-300x270.jpg 300w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/encoder_mode_channels-24x22.jpg 24w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/encoder_mode_channels-36x32.jpg 36w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/encoder_mode_channels-89x80.jpg 89w\" sizes=\"auto, (max-width: 548px) 100vw, 548px\" \/><\/a><\/figure>\n<\/div>\n\n\n<p><\/p>\n\n\n\n<p>Connect the encoder appropriately to the Nucleo pins. <strong>PA8<\/strong> <strong>corresponds to D7<\/strong> on the Arduino header, and <strong>PA9 \u2014 to D8<\/strong>.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter\"><a href=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/STM32-Nucleo-F401RE-Pinout.png\"><img loading=\"lazy\" decoding=\"async\" width=\"601\" height=\"523\" src=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/STM32-Nucleo-F401RE-Pinout.png\" alt=\"\" class=\"wp-image-1110\" srcset=\"https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/STM32-Nucleo-F401RE-Pinout.png 601w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/STM32-Nucleo-F401RE-Pinout-300x261.png 300w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/STM32-Nucleo-F401RE-Pinout-24x21.png 24w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/STM32-Nucleo-F401RE-Pinout-36x31.png 36w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/STM32-Nucleo-F401RE-Pinout-92x80.png 92w\" sizes=\"auto, (max-width: 601px) 100vw, 601px\" \/><\/a><\/figure>\n<\/div>\n\n\n<p><\/p>\n\n\n\n<p>Once you have encoder mode enabled and have sorted out the connection to the <a href=\"https:\/\/sklep.msalamon.pl\/produkt\/nucleo-f401re\/?utm_source=blog&amp;utm_medium=article&amp;utm_campaign=encoder&amp;utm_content=Text\">Nucleo<\/a>, it\u2019s time to configure how this Timer will work. In the <strong>Configuration<\/strong> window you\u2019ll find plenty of things. Fortunately, many of them won\u2019t matter for the counter working with the encoder. I marked the settings that should interest you.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter\"><a href=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/encoder_mode_configuration.jpg\"><img loading=\"lazy\" decoding=\"async\" width=\"550\" height=\"638\" src=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/encoder_mode_configuration.jpg\" alt=\"\" class=\"wp-image-1114\" srcset=\"https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/encoder_mode_configuration.jpg 550w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/encoder_mode_configuration-259x300.jpg 259w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/encoder_mode_configuration-21x24.jpg 21w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/encoder_mode_configuration-31x36.jpg 31w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/encoder_mode_configuration-69x80.jpg 69w\" sizes=\"auto, (max-width: 550px) 100vw, 550px\" \/><\/a><\/figure>\n<\/div>\n\n\n<p><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Counter Mode<\/strong> \u2013 decides whether the counter will increase with CW motion or decrease.<\/li>\n\n\n\n<li><strong>Counter Period<\/strong> \u2013 up to what value the counter will count. After exceeding it, it will roll over. You can limit this in specific applications, but I like the full 16-bit range because of the magic of binary arithmetic, as you\u2019ll see in a moment \ud83d\ude42<\/li>\n\n\n\n<li><strong>Encoder Mode<\/strong> \u2013 Decides on which channel the pulse counting will take place. If you set only TI1 or TI2, the counter will react to edges of only one of the signals. I will set the combined mode so that it reacts to all.<\/li>\n\n\n\n<li><strong>Polarity<\/strong> \u2013 I didn\u2019t notice any difference for different edges. Leave it on rising.<\/li>\n\n\n\n<li><strong>Input Filter<\/strong> \u2013 A very important parameter. It determines the length of input sampling before the counter changes. With such a hand-operated encoder, it\u2019s worth setting it to the maximum value. This effectively eliminates the contact bounce effect.<\/li>\n<\/ul>\n\n\n\n<p>I additionally configured UART2, which is routed to the ST-Link.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Encoder software<\/h2>\n\n\n\n<p>The code for <a href=\"https:\/\/sklep.msalamon.pl\/produkt\/nucleo-f401re\/?utm_source=blog&amp;utm_medium=article&amp;utm_campaign=encoder&amp;utm_content=Text\">STM32F401RE<\/a> was generated using STM32CubeIDE 1.0.2 with the HAL F4 library version 1.24.1.<\/p>\n\n\n\n<p>Configured according to the above description, Cube will generate code almost ready for action. You only need to start the timer along with all channels.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"c\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">HAL_TIM_Encoder_Start(&amp;amp;htim1, TIM_CHANNEL_ALL);<\/pre>\n\n\n\n<p>Now every movement of the encoder will be counted and the current number of counted pulses for TIM1 is located in the CNT register of this counter.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"c\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">htim1.Instance-&amp;gt;CNT<\/pre>\n\n\n\n<p>In the main loop I put printing of this register to the terminal and\u2026 the counter increments by 4 pulses.&nbsp;<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter\"><a href=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/encoder_counter_step.jpg\"><img loading=\"lazy\" decoding=\"async\" width=\"482\" height=\"296\" src=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/encoder_counter_step.jpg\" alt=\"\" class=\"wp-image-1111\" srcset=\"https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/encoder_counter_step.jpg 482w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/encoder_counter_step-300x184.jpg 300w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/encoder_counter_step-24x15.jpg 24w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/encoder_counter_step-36x22.jpg 36w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/08\/encoder_counter_step-130x80.jpg 130w\" sizes=\"auto, (max-width: 482px) 100vw, 482px\" \/><\/a><\/figure>\n<\/div>\n\n\n<p><\/p>\n\n\n\n<p>Is that a bug? Not at all! Recall the encoder mode configuration. We set it so that it reacts to edges of both signals A and B. For one encoder detent these signals will generate two edges each, so in the end the counter will count four of them. That\u2019s where this value comes from. If you set <strong>Encoder Mode<\/strong> to T1 or T2, the counter will change by a value equal to 2 with each encoder click.<\/p>\n\n\n\n<p>By rotating the encoder slowly and holding it between stable positions you can observe intermediate values on the counter \u2014 this proves that each edge is treated separately.&nbsp;<\/p>\n\n\n\n<p>It\u2019s easy to compute how many detents occurred on the encoder. Just divide by 4 and you\u2019re done!<\/p>\n\n\n\n<p>As you can see, handling an encoder is simple \ud83d\ude42<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Alright, but what do I do with that value?<\/h3>\n\n\n\n<p>Hello hello! What if I\u2019m using an encoder as a knob to increase volume in the 0\u201330 range, say?! I can\u2019t use the variable htim1.Instance-&gt;CNT as the volume value, because even if I limit counting to 30, it will wrap around! Besides, after an MCU reset this value will always be zero! How to live?<\/p>\n\n\n\n<p>I\u2019ll show you a trick and why I almost always recommend using the full range of the counter.<\/p>\n\n\n\n<p>I will create a variable to store the current volume level.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"c\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">int8_t AudioVolume;<\/pre>\n\n\n\n<p>You may ask why <em>int8_t<\/em>, and not the \u2018classic\u2019 <em>uint8_t<\/em>. Well, if you want to keep the 0\u201330 range, it will be easier to check whether the variable went below zero than whether it wrapped back to 255.<\/p>\n\n\n\n<p>Now the whole magic. I separated a dedicated function for code readability.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"c\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">void UpdateAudioVolume(void)\n{\n\tstatic uint16_t LastTimerCounter = 0;\n\tint TimerDif = htim1.Instance-&amp;gt;CNT - LastTimerCounter;\n\tif(TimerDif &amp;gt;= 4 || TimerDif &amp;lt;= -4)\n\t{\n\t\tTimerDif \/= 4;\n\t\tAudioVolume += (int8_t)TimerDif;\n\t\tif(AudioVolume &amp;gt; 30) AudioVolume = 30;\n\t\tif(AudioVolume &amp;lt; 0) AudioVolume = 0;\n\t\tLastTimerCounter = htim1.Instance-&amp;gt;CNT;\n\t}\n}<\/pre>\n\n\n\n<p>Let me explain step by step how I solved it and where the magic is.<\/p>\n\n\n\n<p>First, I created a static variable that remembers the last state of the counter at the moment of changing the volume.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"c\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">static uint16_t LastTimerCounter = 0;<\/pre>\n\n\n\n<p>On each pass I check the difference between the previous counter state at the volume change and the current one<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"c\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">int TimerDif = htim1.Instance-&amp;gt;CNT - LastTimerCounter;<\/pre>\n\n\n\n<p>Condition to enter the volume change. If the state changed by more than 4 (in the increasing direction) or less than -4 (in the decreasing direction), I go further.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"c\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">if(TimerDif &amp;gt;= 4 || TimerDif &amp;lt;= -4)<\/pre>\n\n\n\n<p>I divide, of course, to obtain the number of individual encoder detents.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"c\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">TimerDif \/= 4;<\/pre>\n\n\n\n<p>And I increase\/decrease the volume.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"c\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">AudioVolume += (int8_t)TimerDif;<\/pre>\n\n\n\n<p>Finally, I check whether the volume fell outside the limits and I write down the current counter state.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"c\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">if(AudioVolume &amp;gt; 30) AudioVolume = 30;\n\nif(AudioVolume &amp;lt; 0) AudioVolume = 0;\n\nLastTimerCounter = htim1.Instance-&amp;gt;CNT;<\/pre>\n\n\n\n<p>Alright, but where is the magic? I\u2019ll give you a hint. It\u2019s about two special transitions of the counter <em>htim1.Instance-&gt;CNT.<\/em> These are exactly the transitions 0-&gt;65535 and in the other direction.<\/p>\n\n\n\n<p>Why? I have zero on the counter and the last known value is also zero. I turn the encoder to the left (backwards) and what happens?<\/p>\n\n\n\n<p>For uint16_t 0 \u2013 4 = 65532. Now subtracting the previous value (0) from the current one (65532) we obtain for the variable <em>TimerDif<\/em> declared as int the value 65532. Now, according to the algorithm, you divide by 4 obtaining 16383 which, according to the algorithm, you add to the volume variable.<\/p>\n\n\n\n<p>You can see that something is wrong, because you turned in the direction that should decrease the volume, and finally it is supposed to increase it by 16383! A disaster, right?&nbsp;<\/p>\n\n\n\n<p>But you can notice one small detail. I cast the value of <em>TimerDif<\/em> to <em>int8_t<\/em>. For small values (from -129 to 127) it won\u2019t matter much. BUT for a value like 16383 it already matters a lot. Do you know what the result of this cast will be? It will be\u2026 attention <strong>-1<\/strong>! Yes, by the magic of the counter roll-over, you will get the value you expected. Great, isn\u2019t it?<\/p>\n\n\n\n<p>The same will happen when rolling over in the other direction. If you don\u2019t believe it, check on some online compiler what the result will be for casting to <em>int8_t<\/em> a variable with the value 16383.&nbsp;<\/p>\n\n\n\n<p>After such a procedure it doesn\u2019t matter from what initial value you start with the encoder, the <em>AudioVolume<\/em> variable will always behave as required. It will never go above 30 or below 0.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Summary<\/h2>\n\n\n\n<p>I showed you that handling an encoder on STM32 is trivially simple. With a simple trick you can also use the encoder for controlled modification of variables.<\/p>\n\n\n\n<p>On the market you can find other types of encoders. Another popular type are those for counting motor rotations. These are, for example, encoders operating on a rotating magnetic field. You can handle such ones with a timer in encoder mode as well.<\/p>\n\n\n\n<p><strong>And what\u2019s your opinion about encoders? Share in the comments.<\/strong><\/p>\n\n\n\n<p>You will find the full project along with the library as usual on my GitHub: <a href=\"https:\/\/github.com\/lamik\/Encoder_HW_STM32_HAL\" target=\"_blank\" rel=\"noopener\">link<\/a><\/p>\n\n\n\n<p><span>If you noticed any error, disagree with something, would like to add something important or simply think you\u2019d like to discuss the topic, write a comment. Remember that the discussion should be polite and in accordance with the rules of the Polish language.<\/span><\/p>\n\n\n<div class=\"kk-star-ratings kksr-auto kksr-align-left kksr-valign-bottom\"\n    data-payload='{&quot;align&quot;:&quot;left&quot;,&quot;id&quot;:&quot;4307&quot;,&quot;slug&quot;:&quot;default&quot;,&quot;valign&quot;:&quot;bottom&quot;,&quot;ignore&quot;:&quot;&quot;,&quot;reference&quot;:&quot;auto&quot;,&quot;class&quot;:&quot;&quot;,&quot;count&quot;:&quot;0&quot;,&quot;legendonly&quot;:&quot;&quot;,&quot;readonly&quot;:&quot;&quot;,&quot;score&quot;:&quot;0&quot;,&quot;starsonly&quot;:&quot;&quot;,&quot;best&quot;:&quot;5&quot;,&quot;gap&quot;:&quot;0&quot;,&quot;greet&quot;:&quot;&quot;,&quot;legend&quot;:&quot;0\\\/5 - (0 votes)&quot;,&quot;size&quot;:&quot;24&quot;,&quot;title&quot;:&quot;Ridiculously Simple Hardware Encoder Handling on STM32&quot;,&quot;width&quot;:&quot;0&quot;,&quot;_legend&quot;:&quot;{score}\\\/{best} - ({count} {votes})&quot;,&quot;font_factor&quot;:&quot;1.25&quot;}'>\n            \n<div class=\"kksr-stars\">\n    \n<div class=\"kksr-stars-inactive\">\n            <div class=\"kksr-star\" data-star=\"1\" style=\"padding-right: 0px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 24px; height: 24px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"2\" style=\"padding-right: 0px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 24px; height: 24px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"3\" style=\"padding-right: 0px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 24px; height: 24px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"4\" style=\"padding-right: 0px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 24px; height: 24px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"5\" style=\"padding-right: 0px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 24px; height: 24px;\"><\/div>\n        <\/div>\n    <\/div>\n    \n<div class=\"kksr-stars-active\" style=\"width: 0px;\">\n            <div class=\"kksr-star\" style=\"padding-right: 0px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 24px; height: 24px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 0px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 24px; height: 24px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 0px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 24px; height: 24px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 0px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 24px; height: 24px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 0px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 24px; height: 24px;\"><\/div>\n        <\/div>\n    <\/div>\n<\/div>\n                \n\n<div class=\"kksr-legend\" style=\"font-size: 19.2px;\">\n            <span class=\"kksr-muted\"><\/span>\n    <\/div>\n    <\/div>\n","protected":false},"excerpt":{"rendered":"<p>An encoder is a great device. You surely know some equipment where a knob would rotate endlessly while adjusting some parameter (e.g., an audio amplifier). A very nice idea to replace a classic potentiometer. Encoders are also very often used to measure the rotation angle of motors. In automation and [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":3262,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":"","_links_to":"","_links_to_target":""},"categories":[160],"tags":[175,178,176,174,177],"class_list":["post-4307","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-stm32","tag-electronics","tag-kursstm32","tag-programming","tag-stm32","tag-stm32cubemx"],"_links":{"self":[{"href":"https:\/\/msalamon.pl\/en\/wp-json\/wp\/v2\/posts\/4307","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/msalamon.pl\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/msalamon.pl\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/msalamon.pl\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/msalamon.pl\/en\/wp-json\/wp\/v2\/comments?post=4307"}],"version-history":[{"count":3,"href":"https:\/\/msalamon.pl\/en\/wp-json\/wp\/v2\/posts\/4307\/revisions"}],"predecessor-version":[{"id":4414,"href":"https:\/\/msalamon.pl\/en\/wp-json\/wp\/v2\/posts\/4307\/revisions\/4414"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/msalamon.pl\/en\/wp-json\/wp\/v2\/media\/3262"}],"wp:attachment":[{"href":"https:\/\/msalamon.pl\/en\/wp-json\/wp\/v2\/media?parent=4307"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/msalamon.pl\/en\/wp-json\/wp\/v2\/categories?post=4307"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/msalamon.pl\/en\/wp-json\/wp\/v2\/tags?post=4307"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}