Hi all,
I have a rotary encoder connected to my Raspberry Pi running (recently-updated) Node Red (v2.2), nodejs (v14 LTS), Raspberry Pi OS Bullseye, etc.
If you haven't used a rotary encoder before, here's how it works: As the encoder turns around, there are a certain number of discrete "positions", even if there are no palpable "clicks". For example, the "volume" or "tuning" knobs on your car radio are probably rotary encoders (if it turns continuously and doesn't come to a hard stop).
There are two pins which are pulled low or high depending on the rotation of the spindle on the rotary encoder. As the position changes, the status of the two pins changes like so: "00", then "01", then "11", then "10", then back to "00". Or, if turning to opposite way, the pins would go "00", "10", "11", "01", and back to "00". This way, you can determine not only motion, but the direction of motion, clockwise or counterclockwise.
Here's my problem: Sometimes my encoder turns quickly. When that happens, the discrete "positions" advance faster than the minimum "Debounce" in the Raspberry Pi input node, such that I get, for example "00", then "11" (non-adjacent positions that don't indicate direction)..
I tried setting the "Debounce" number to zero in the RPi Input node, but that stops the node - it needs a 1ms minimum debounce. Or am I missing something? It does accept decimals over 1ms (e.g. 1.1ms), but not below 1ms. Is there a higher-speed way to do this reading that could cut it down to maybe .5ms or even .3ms (which would be more than sufficient, I believe)? For example, some sort of more native GPIO interface that could report into Node Red? Would it be faster to read with python and pass values into Node Red every so often?
Thank you! Here's a sample flow demonstrating how the reading works:
[{"id":"0beb6b0848a6cb93","type":"switch","z":"9146dfa0.ce79f","name":"error","property":"error","propertyType":"msg","rules":[{"t":"true"}],"checkall":"true","repair":false,"outputs":1,"x":950,"y":1040,"wires":[["1ec6dce18e3b682c"]]},{"id":"18ab5da0c5bdf9d0","type":"function","z":"9146dfa0.ce79f","name":"encoder counter","func":"var main = global.get(\"rotary.main\");\nvar oldMain = main;\n\nvar Apin = \"\";\nvar Bpin = \"\";\n\n\nif (msg.topic == \"A\") { \n Apin = String(msg.payload);\n Bpin = String(global.get(\"rotary.B.state\"));\n}\n\nif (msg.topic == \"B\") { \n Bpin = String(msg.payload);\n Apin = String(global.get(\"rotary.A.state\"));\n}\n\nvar newPins = Apin + Bpin;\nglobal.set(\"rotary.pins\",newPins);\nvar lastPins = global.get(\"rotary.lastPins\");\n\nif (newPins == lastPins)\n{return null;} // or maybe this should report +4 or -4 depending on the direction of movement..?\n\nif (newPins == \"00\")\n{ if (lastPins == \"10\")\n {main++;\n global.set(\"rotary.ifErrorMovement\",2)}\n else if (lastPins == \"01\") \n {main--;\n global.set(\"rotary.ifErrorMovement\",-2)}\n else {msg.error = true;\n main = main + global.get(\"rotary.ifErrorMovement\");\n }\n}\n \nif (newPins == \"01\")\n{ if (lastPins == \"00\")\n {main++;\n global.set(\"rotary.ifErrorMovement\",2)}\n else if (lastPins == \"11\")\n {main--;\n global.set(\"rotary.ifErrorMovement\",-2)}\n else {msg.error = true;\n main = main + global.get(\"rotary.ifErrorMovement\");\n }\n}\n\nif (newPins == \"11\")\n{ if (lastPins == \"01\")\n {main++;\n global.set(\"rotary.ifErrorMovement\",2)}\n else if (lastPins == \"10\")\n {main--;\n global.set(\"rotary.ifErrorMovement\",-2)}\n else {msg.error = true;\n main = main + global.get(\"rotary.ifErrorMovement\");\n }\n}\n\nif (newPins == \"10\")\n{ if (lastPins == \"11\")\n {main++;\n global.set(\"rotary.ifErrorMovement\",2)}\n else if (lastPins == \"00\")\n {main--;\n global.set(\"rotary.ifErrorMovement\",-2)}\n else {msg.error = true;\n main = main + global.get(\"rotary.ifErrorMovement\");\n }\n}\n\nglobal.set(\"rotary.main\",main);\nglobal.set(\"rotary.lastPins\",newPins);\n\nmsg.change = main - oldMain;\nmsg.payload = main;\nmsg.pins = newPins;\n\nreturn msg;","outputs":1,"noerr":0,"x":740,"y":1080,"wires":[["d54264bc5d48db8a","0beb6b0848a6cb93","afbd717f5bfc578d","5af03fa76b149da3"]]},{"id":"1ec6dce18e3b682c","type":"template","z":"9146dfa0.ce79f","name":"","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"There has been an error with the rotary encoder - a non-adjacent pin combination was received.\n\nValue: {{payload}}","output":"str","x":1100,"y":1040,"wires":[["7d9a8e6a6bc993e4"]]},{"id":"c0bb5128fe89eb64","type":"delay","z":"9146dfa0.ce79f","name":"","pauseType":"rate","timeout":"5","timeoutUnits":"seconds","rate":"2000","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"outputs":1,"x":520,"y":1080,"wires":[["18ab5da0c5bdf9d0"]]},{"id":"d54264bc5d48db8a","type":"trigger","z":"9146dfa0.ce79f","name":"","op1":"","op2":"","op1type":"nul","op2type":"payl","duration":"1","extend":false,"units":"s","reset":"","bytopic":"all","outputs":1,"x":960,"y":1080,"wires":[["4bfd216b55c1e53a"]]},{"id":"afbd717f5bfc578d","type":"debug","z":"9146dfa0.ce79f","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"pins","targetType":"msg","x":770,"y":1120,"wires":[]},{"id":"5af03fa76b149da3","type":"debug","z":"9146dfa0.ce79f","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":770,"y":1040,"wires":[]},{"id":"7d9a8e6a6bc993e4","type":"debug","z":"9146dfa0.ce79f","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1250,"y":1020,"wires":[]},{"id":"ce3f0bf12eef3285","type":"change","z":"9146dfa0.ce79f","name":"A settings","rules":[{"t":"set","p":"rotary.A.state","pt":"global","to":"payload","tot":"msg"},{"t":"set","p":"topic","pt":"msg","to":"A","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":300,"y":1060,"wires":[["c0bb5128fe89eb64"]]},{"id":"f79669d9654793e5","type":"change","z":"9146dfa0.ce79f","name":"B settings","rules":[{"t":"set","p":"rotary.B.state","pt":"global","to":"payload","tot":"msg"},{"t":"set","p":"topic","pt":"msg","to":"B","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":300,"y":1120,"wires":[["c0bb5128fe89eb64"]]},{"id":"4bfd216b55c1e53a","type":"mqtt out","z":"9146dfa0.ce79f","name":"","topic":"rotaryRawCount","qos":"","retain":"","broker":"cbcfb059.a48c1","x":1140,"y":1080,"wires":[]},{"id":"9046c6bd061e9993","type":"rpi-gpio in","z":"9146dfa0.ce79f","name":"","pin":"23","intype":"up","debounce":"1","read":true,"bcm":true,"x":100,"y":1060,"wires":[["ce3f0bf12eef3285"]]},{"id":"26146eb6e3506c63","type":"rpi-gpio in","z":"9146dfa0.ce79f","name":"","pin":"24","intype":"up","debounce":"1","read":true,"bcm":true,"x":100,"y":1120,"wires":[["f79669d9654793e5"]]},{"id":"cbcfb059.a48c1","type":"mqtt-broker","name":"localhost","broker":"localhost","port":"1883","clientid":"","usetls":false,"compatmode":true,"keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","closeTopic":"","closeQos":"0","closePayload":"","willTopic":"","willQos":"0","willPayload":""}]