{"id":900,"date":"2017-02-18T16:55:00","date_gmt":"2017-02-18T15:55:00","guid":{"rendered":"http:\/\/heppg.de\/ikg\/wordpress\/?p=900"},"modified":"2017-02-19T21:27:49","modified_gmt":"2017-02-19T20:27:49","slug":"rotary-encoder-performance","status":"publish","type":"post","link":"https:\/\/heppg.de\/ikg\/wordpress\/?p=900","title":{"rendered":"rotary encoder performance"},"content":{"rendered":"<p>Rotary encoders produce two waveforms, shifted by 90 degrees, which allow to decode position and direction of movements.<\/p>\n<p>The two waveforms look as given in the chart. Only one direction is shown.<\/p>\n<p><a href=\"http:\/\/heppg.de\/ikg\/wordpress\/wp-content\/uploads\/2017\/02\/encoder.png\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-901\" src=\"http:\/\/heppg.de\/ikg\/wordpress\/wp-content\/uploads\/2017\/02\/encoder.png\" alt=\"encoder\" width=\"493\" height=\"140\" \/><\/a><\/p>\n<p>In raspberry pi forum, someone asked for the performance of a python program to decode these signals.<\/p>\n<p>The code used to validate python program is<\/p>\n<pre>import RPi.GPIO as GPIO\r\nimport time\r\n\r\nGPIO.setmode (GPIO.BCM)\r\nGPIO.setwarnings(False)\r\npin_A = 22\u00a0 \u00a0\r\npin_B = 27\r\n\r\nEncoder_Count = 0\u00a0\u00a0\u00a0\u00a0\u00a0 # Encoder Count variable\r\n\u00a0 \u00a0\r\ndef do_Encoder(channel):\r\n\u00a0\u00a0\u00a0 global Encoder_Count\r\n\u00a0\u00a0\u00a0 if GPIO.input(pin_B) == 1:\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Encoder_Count += 1\r\n\u00a0\u00a0\u00a0 else:\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Encoder_Count -= 1\r\n\r\nGPIO.setup (pin_A, GPIO.IN, pull_up_down=GPIO.PUD_UP)\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 # pin input pullup\r\nGPIO.setup (pin_B, GPIO.IN, pull_up_down=GPIO.PUD_UP)\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 # pin input pullup\r\n\r\nGPIO.add_event_detect (pin_A, GPIO.FALLING, callback=do_Encoder)\u00a0\u00a0 # Enable interrupt\r\n\r\nlastCount = 0\r\nt0 = time.time()\r\nwhile(1):\r\n\u00a0\u00a0\u00a0 t0 += 20\r\n\u00a0\u00a0\u00a0 time.sleep(t0 - time.time())\r\n\u00a0\u00a0\u00a0 print (\"{e:d} diff={d:d}\".format(e=Encoder_Count, d = Encoder_Count-lastCount))\r\n\u00a0\u00a0\u00a0 lastCount = Encoder_Count\r\n\r\n<\/pre>\n<p>As a driver for these signals I use an arduino DUE.<\/p>\n<p>The results for the python code on a Raspberry Pi 3 are<\/p>\n<pre><code>freq\u00a0 \u00a0 \u00a0 \u00a0exp\u00a0 \u00a0 \u00a0measure\r\n\u00a0 \u00a010Hz\u00a0 \u00a0 200\u00a0 \u00a0 \u00a0 \u00a0 \u00a0200\r\n\u00a0 100Hz\u00a0 \u00a02000\u00a0 \u00a0 \u00a0 \u00a0 2000\r\n\u00a0 500Hz\u00a0 10000\u00a0 \u00a0 \u00a0 \u00a010000\u00a0 +-2\r\n\u00a01000Hz\u00a0 20000\u00a0 \u00a0 \u00a0 \u00a020000 +-25\r\n\u00a02000Hz\u00a0 40000\u00a0 \u00a0 \u00a0 \u00a040000 -40\r\n\u00a03000Hz\u00a0 60000\u00a0 \u00a0 \u00a0 \u00a060000 -660\r\n\u00a05000Hz 100000\u00a0 \u00a0 \u00a0-100000 +-20, but received negative values, so python too slow to get matching level<\/code><\/pre>\n<p>Up to 3.000 pulses, the results are quite reliable. For a 1.000RPR encoder, these are 3 rotations per second.<\/p>\n<p>Usually I recommend to use an atmel328-processor as a slave processor to handle time sensitive operations. With an arduino UNO, 16MHz, the results are precise up to 15.000Hz.<br \/>\nThere is no complete arduino needed. A bare atmel328, clocked from GPIO4, using level shifters for clock and serial line, would be a cost effective solution.<br \/>\nThe sketch uses interrupts for the event inputs:<\/p>\n<pre>const byte ip2 = 2;\r\nconst byte ip3 = 3;\r\nvolatile long counter = 0;\r\n\r\nvoid ip2Int() {\r\n\u00a0 byte iip2 = digitalRead(ip2);\r\n\u00a0 byte iip3 = digitalRead(ip3);\r\n\u00a0 if ( iip2 == 1 ) {\r\n\u00a0\u00a0\u00a0 if ( iip3 == 1 ) {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 counter --;\r\n\u00a0\u00a0\u00a0 }\r\n\u00a0\u00a0\u00a0 else\r\n\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 counter ++;\r\n\u00a0\u00a0\u00a0 }\r\n\u00a0 }\r\n\u00a0 else \/\/ ip2 == 0\r\n\u00a0 {\r\n\u00a0\u00a0\u00a0 if ( iip3 == 1 ) {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 counter ++;\r\n\u00a0\u00a0\u00a0 }\r\n\u00a0\u00a0\u00a0 else\r\n\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 counter --;\r\n\u00a0\u00a0\u00a0 }\r\n\u00a0 }\r\n}\r\n\r\nvoid ip3Int() {\r\n\u00a0 byte iip2 = digitalRead(ip2);\r\n\u00a0 byte iip3 = digitalRead(ip3);\r\n\u00a0 if ( iip2 == 1 ) {\r\n\u00a0\u00a0\u00a0 if ( iip3 == 1 ) {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 counter ++;\r\n\u00a0\u00a0\u00a0 }\r\n\u00a0\u00a0\u00a0 else\r\n\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 counter --;\r\n\u00a0\u00a0\u00a0 }\r\n\u00a0 }\r\n\u00a0 else \/\/ ip2 == 0\r\n\u00a0 {\r\n\u00a0\u00a0\u00a0 if ( iip3 == 1 ) {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 counter --;\r\n\u00a0\u00a0\u00a0 }\r\n\u00a0\u00a0\u00a0 else\r\n\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 counter ++;\r\n\u00a0\u00a0\u00a0 }\r\n\u00a0 }\r\n}\r\n\r\nvoid setup() {\r\n\u00a0 Serial.begin(115200);\r\n\u00a0 Serial.println(\"encoder\");\r\n\r\n\u00a0 pinMode(ip2, INPUT);\r\n\u00a0 pinMode(ip3, INPUT);\r\n\r\n\u00a0 pinMode( 13, OUTPUT);\r\n\u00a0 pinMode(pin_sync, INPUT );\r\n\u00a0 \r\n\u00a0 attachInterrupt(digitalPinToInterrupt(ip2), ip2Int, CHANGE);\r\n\u00a0 attachInterrupt(digitalPinToInterrupt(ip3), ip3Int, CHANGE);\r\n}\r\n\r\nvoid loop() {\r\n\u00a0 long c0 = 0;\r\n\u00a0 noInterrupts();\r\n\u00a0 c0 = counter;\r\n  interrupts();\r\n\r\n  Serial.println(c0);\r\n  delay(10000);\r\n}<\/pre>\n<p>This code runs up to 15.000Hz, at 20.000 Hz it fails.<\/p>\n<p>An obvious optimization is to change the interrupt routine and avoid the digitalRead-function for the atmel328-processor.<\/p>\n<pre>void ip2Int() {\r\n\u00a0 uint8_t pd = 0b00001100 &amp; PORTD;  \/\/ pins 2,3 are PD2, PD3\r\n\u00a0 if ( pd == 0b00001000 || pd == 0b00000100 ) {\r\n\u00a0\u00a0\u00a0 counter ++;\r\n\u00a0 }\r\n\u00a0 else {\r\n\u00a0\u00a0\u00a0 counter --;\r\n\u00a0 }\r\n}\r\n\r\nvoid ip3Int() {\r\n\u00a0 uint8_t pd = 0b00001100 &amp; PORTD;\r\n\u00a0 if ( pd == 0b00001100 || pd == 0b00000000 ) {\r\n\u00a0\u00a0\u00a0 counter --;\r\n\u00a0 }\r\n\u00a0 else {\r\n\u00a0\u00a0\u00a0 counter ++;\r\n\u00a0 }\r\n}<\/pre>\n<p>This code is much faster and update rates till 50.000Hz work perfect.<\/p>\n<p>With handcrafted assembler code for the interrupt-routine I would expect to extend performance even more.<\/p>\n<p>Last option investigated was an arduino feather M0 board running at 48MHz.<br \/>\nThis produces good results up to 30.000Hz, at 40.000Hz there are failures. With the results from optimized atmel328-code, there is quite a lot of optimization expected.<\/p>\n<p>For the fast running signals, timing accuracy is crucial. To work around this problem, a sync pattern was used: an additional sync signal indicated start and end of measurement from generator to measurement device. Pulse sequence was 10 sec in each case.<\/p>\n<p><a href=\"http:\/\/heppg.de\/ikg\/wordpress\/wp-content\/uploads\/2017\/02\/encoder_sync-1.png\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-903\" src=\"http:\/\/heppg.de\/ikg\/wordpress\/wp-content\/uploads\/2017\/02\/encoder_sync-1.png\" alt=\"encoder_sync\" width=\"614\" height=\"214\" \/><\/a><\/p>\n<p>For faster signals, either optimized assembler code needs to be used, or a hardware solution with a FPGA.<\/p>\n<p>Updated 2017-02-19: optimized atmel code.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Rotary encoders produce two waveforms, shifted by 90 degrees, which allow to decode position and direction of movements. The two waveforms look as given in the chart. Only one direction is shown. In raspberry pi forum, someone asked for the performance of a python program to decode these signals. The code used to validate python [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[16,32,41],"class_list":["post-900","post","type-post","status-publish","format-standard","hentry","category-uncategorized","tag-atmel","tag-performance","tag-rotary-encoder"],"_links":{"self":[{"href":"https:\/\/heppg.de\/ikg\/wordpress\/index.php?rest_route=\/wp\/v2\/posts\/900","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/heppg.de\/ikg\/wordpress\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/heppg.de\/ikg\/wordpress\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/heppg.de\/ikg\/wordpress\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/heppg.de\/ikg\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=900"}],"version-history":[{"count":3,"href":"https:\/\/heppg.de\/ikg\/wordpress\/index.php?rest_route=\/wp\/v2\/posts\/900\/revisions"}],"predecessor-version":[{"id":906,"href":"https:\/\/heppg.de\/ikg\/wordpress\/index.php?rest_route=\/wp\/v2\/posts\/900\/revisions\/906"}],"wp:attachment":[{"href":"https:\/\/heppg.de\/ikg\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=900"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/heppg.de\/ikg\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=900"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/heppg.de\/ikg\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=900"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}