Saiyato / volumio-rotary-encoder-plugin

Simple dual rotary encoder plugin for Volumio 2.x
MIT License
19 stars 6 forks source link

Rotation is buggy #2

Open mundodisco8 opened 6 years ago

mundodisco8 commented 6 years ago

Hi,

I managed to get the plugin working, but the behaviour is very erratic. Sometimes it raises the volume, sometimes it lowers it (when rotating in the same direction, that is) and most of the time, nothing happens. I think that the problem is either that my rotary encoder has more detents than yours and you are not sampling fast enough, but I . Where can I see the output of the debug log?

I copy the info from my setup from my previous message. The rotary encoder is being pulled up, but that shouldn't change its behaviour. My encoder uses gray encoding, but changing the encoding in the options doesn't improve the performance. The switch works nicely as a play/pause toggle. I browsed your code but I'm not that good at python

I'm using a bourns encoder (http://www.bourns.com/docs/Product-Datasheets/PEC11R.pdf?sfvrsn=bb617cbf_5) and the schematic for my circuit is the following:

2018-01-26_1522

I'm using a raspberry pi 2 with a HifiBerry Amp+, and GPIO 22 for A and 17 for B and 4 for the switch. I've managed to use the rotary on raspbian, when I did some tests, and it worked there.

Thanks for your work!

Saiyato commented 6 years ago

Hi,

Good point, I've updated the plugin to be able to choose 'no action' which will only log the direction. The erratic behaviour might be caused by the libraries used to capture the direction, but I'm not entirely sure. I've had some misfires as well, but only in one direction.

I've also HW debounced my encoder, just like you did, with a 1uF capacitor. But I'm not sure how to further harden the signal, this is way beyond me. I know some basic electronic stuff, but nothing fancy. I just know how to google and combine work from others. ;)

I will update the repo in a few minutes with the new version (untested), maybe that'll help you.

Also I've ordered some bourns encoders, just to try them out. Do you have a picture of your setup? Just curious what you have made. I can read simple diagrams and yours is showing resistors; is that necessary, or are those the resistors in the encoder? Just trying to figure out how stuff works :)

mundodisco8 commented 6 years ago

I'll check it this Friday. I want to check my encoder under the oscilloscope as it is doing some stuff I wasn't expecting (but I assumed it was fine because I played a bit with it and it was working with a silly script I did).

mundodisco8 commented 6 years ago

Hey!

I checked my circuit with an oscilloscope and I think I found the issue. I had a slip with the caps, and placed 1μF instead of the intended 100nF, so it takes too long for the signal to rise and fall, so there is no way that that's going to work. I will fix it and probably replace the pull up resistor too, as how it is right now, it will take twice the time to rise from 0 to 3V than it will take to fall. Also, I have an issue that I just can't explain, where I don't get the full 3.3V at the GPIOs, I'm loosing half a volt in the pull up resistors, and I just don't get it 🤷‍♂️. I 'll carry on working on it, but it seems that the problem is on my side.

My rotary encoder sends 24 pulses for rotation, and is rated to work at 60rpm (so that's 1 rotation per second), with that, each pulse is 1/48s wide (~20ms). With the 10kΩ/100nF filter i get from 0 to 3V in around 3ms, so that leaves plenty of time to check for the state of the other pin.

mundodisco8 commented 6 years ago

Yeah, I'm pretty sure that the error is in my setup, so I'll close this issue. I attach a trace: the yellow line is A and the blue is B. You can see a single pulse. It is reversed because I'm pulling up the output. The pulse width is 45ms, and B follows A after around 30ms, so you have around 25ms to detect the 01 code. Then the A signal returns back to normal, and B does the strangest thing, by following after only 3ms instead of 30. Taking into account the time it takes A to rise above a voltage that the raspberry pi can interpret as high (around 2.5V, solid vertical cursor), B will only remain low for around 1.5ms (dashed vertical cursor), what would be the 10 code. That's really odd, I would expect it to be 30ms too...

The raspberry pi should be able to do check the value of B fast enough as to detect that before the 1.5ms, but your code is probably not the problem

img_20180209_134002

mundodisco8 commented 6 years ago

Hi again :D

I've been toying around with the GPIO lib and my awful python skills, and got this simple sketch working (although just on the console, not plugged to volumio's functions):

#!/usr/bin/env python
import time

try:
    import RPi.GPIO as GPIO
except RuntimeError:
    print("error importing RPi.GPIO!")

counter = 0
volume = 50
switchStatus = False
areWeIncreasingVolume = 0  #1 if increasing and -1 if decreasing. Provides hysteresis
rotationDirection = 1            #1 if CW and -1 if CW
maxHysteresis = 6                #max level of the hysteresis filter

#This callback simulates a play/pause toggle
def switchReleased(self):
    time.sleep(.015) #i forgot to HW debounce the switch :S

    global switchStatus
    switchStatus = not switchStatus
    if switchStatus:
        print "Play"
    else:
        print "Pause"

#This callback checks the rotary encoder and changes the volume accordingly
def checkDirection(self):

    # This function uses an integrator filter to debounce. Each time the encoder
    # goes CW, it increments a counter and vice versa. The action (increase or
    # decrease the volume, is only triggered if the counter is at its max level
    # (6 in this example). This provides some hysteresis that protects against
    # artifacts that rise the volume one level and then decrese it and stuff like that
    global counter
    global volume
    global areWeIncreasingVolume
    global maxHysteresis
    global rotationDirection

    # determine rotation direction
    if GPIO.input(rotaryA):
        # It was a rising edge
        if GPIO.input(rotaryB):
            # Code 11 and rising: CCW
            rotationDirection = -1
        else:
            # Code 10 and rising: CW

    else:
        # It was a falling edge
        if GPIO.input(rotaryB):
            # Code 01 and falling: CW
            rotationDirection = +1
        else:
            # Code00 and falling: CCW
            rotationDirection = -1

    if (rotationDirection is 1):
        counter += 1 # increase the counter

        if counter > maxHysteresis:
            # if we reached max counter, don't increment it more, and
            # change the direction of the volume increase
            counter = maxHysteresis
            areWeIncreasingVolume = 1

        if areWeIncreasingVolume is 1:
            #if we are increasing the volume, do it now
            volume += 1
            print "CW! volume is ", volume

    if (rotationDirection is -1):
        counter -= 1 # increase the counter

        if counter < -maxHysteresis:
            counter = -maxHysteresis
            areWeIncreasingVolume = -1

        if areWeIncreasingVolume is -1:
            volume -= 1
            print "CCW! volume is ", volume

GPIO.setmode(GPIO.BCM)

rotaryA = 17
rotaryB = 27
switch = 4

#GPIO.setup(rotaryA, GPIO.IN, pull_up_down=GPIO.PUD_UP)
#GPIO.setup(rotaryB, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(rotaryA, GPIO.IN)
GPIO.setup(rotaryB, GPIO.IN)
GPIO.setup(switch, GPIO.IN)

GPIO.add_event_detect(switch, GPIO.RISING, callback=switchReleased)
GPIO.add_event_detect(rotaryA, GPIO.BOTH, callback=checkDirection)

try:
    while True:
        time.sleep(0.25)
finally:
    GPIO.cleanup()

My point is that even with my crappy signal, I get a very stable response from the raspberry pi, without bounces or weird things going on. Where could I check the code that you use for the rotary encoder in your repo? maybe I could check it on the console, with console logging, and find out why it doesn't work on my setup?

elodur commented 5 years ago

Did you get a solution? I have got a similar problem. I use an ALPS rotary encoder, type STEC12E08. I added an external pullup to 3.3V with 10k resistors and hardware debouncing with 10k resistors and 100 nF capacitors, but it only works when turning the encoder very slow. When turning faster it does what ever it likes. Do you use 10k / 1 µF? Did you get it working? I already used this combination with an arduino nano and it works perfectly there.

mundodisco8 commented 5 years ago

Hi! I got mine working, but not with this plugin. I offered my help to the maintainer, but he never replied, so I guess he got what he wanted and worked for him and life found him and he's probably busy with other stuff (don't want to sound snarky). 10k 1μ is very damping, so I would use something smaller for the cap (whith your encoder, having 24 detents, it creates 24 pulses per rotation. Rotating at 60rpm, 1 per second, you have a pulse every ~40ms, and your tau with 1μ is 23ms). I ended up using 10k 100n. With the python sketch I pasted I can detect presses easily in both directions and even when I'm rotating the shaft quite fast. Sadly, I'm not that good at programming as to be able to create a merge request, but in my particular case (and if I recall correctly), I think the problem is in the way the events are handled. In my code, I only check for events in the channel A, and then check what's the state of A (to know if it was a rising edge or falling edge) and then check the status of B. That's all you need to know if you rotated CW or CCW.

PS: I tried the latest version of the plugin, but I can't get it active. Clicking on On does nothing. I tried with the volumio GPIO plugin and I have the same issue, so I think it's an issue on my side. I will investigate further and come back to you

elodur commented 5 years ago

Ok. I think I'll try 10k / 10 nF first. Then I try to understand the code - it's quite complex compared to my arduino sketch where I just checked on pin A as an interrupt, and when low, checked for pin B. If this was low as well it detects clockwise turning, if high it's counterclockwise. Oh, and I just had a look, I measure the time between status-changes for software debounce and acceleration. Duration of less than 10 ms is interpetred as bouncing and the faster I turn the more the value is increased / decreased.

elodur commented 5 years ago

10 nF caps didn't help. How did you get it working?

Saiyato commented 5 years ago

Hi there,

I'm quite busy in the personal sphere, which does mean I won't be able respond promptly, mea culpa. I'm afraid this will not change in the upcoming months, but I'll try to assist where I can.

As mentioned earlier, the technical part on resistance and capacitance is a bit beyond me at this point. I do like to understand, but I trust trying to explain it to me will only slow things down (however making things easier to understand helps me to reproduce and is appreciated ;) ). I can read the wiki pages, but lack the understanding to effectively apply the laws and formulae.

I do understand how the code seems to work however, so I'll try to explain best I can. The logic for the rotary is not in this repo, but here: https://github.com/Saiyato/onoff-rotary I forked from another repo: https://github.com/pichfl/onoff-rotary

Volumio plugin

This onoff-rotary repo is where the ticks and clicks are detected and propagated, the plugin only listens to the events sent off by the onoff-rotary library/module/package (whatever you want to call it). The plugin only acts on the event of rotation +1 or -1 and the click event.

The onoff-rotary library

A tick there is a change from one detent to another, this can be either CW (clockwise) or CCW (counter-clockwise), that's how it works simply put. The logic applied is gray code; which has the following sequence (one bit offset on each detent/tick):

00 01 11 10 00 01 11 etc.

One of the two bits is sent by CLK (in my code MSB = left bit) the other by DT (in my code LSB = right bit) if I'm not mistaken. So in order to get 01, we need a zero from either one and a one from the other. It doesn't really matter, I think, which one is what, as long as you're always using the same logic.

The previous value is kept (in memory) and the newly received bits are added to the right of the previous value. So the script shifts the previous bits two to the right and adds the newly measured bits to the left.

E.g. if the previous value was 00 and the new one 01, the comparison value is 0001, this is then interpreted as CCW because 01 follow 00 when turning the encoder counter-clockwise.

I used this page as a reference: https://www.allaboutcircuits.com/projects/how-to-use-a-rotary-encoder-in-a-mcu-based-project/

Note: I never used the resistors... should I? Also the reference I used advises 27k resistors, not 10k. I know the higher the resistance, the less current is sent through, does 17k really make a noticeable difference?

elodur commented 5 years ago

You use resistors to 1) limit the current and 2) to get defined states on your input. 1: The tiny switches in your encoder can only handle very little current, the alps STEC12E08 I use for example is designed for a current of 0,5 mA (3.3V / 0.0005 A = 6600 ohms minimum). 2: "low" signal means 0 V, i.e. a connection to GND. If the encoder/switch is "open" you want to have "high" signal on your input, which has to be ~3 V. If you just open the connection to GND your input is floating anywhere, but not only at 3 V. So you use a pullup -either internal activated via software or external- to be sure, that an open switch is definetly 3.3 V at the input. Because the resistor is at least 10 k the closed switch overrides it easily and does not see to much current.

The capacitor is used to limit bouncing. Like a ball you drop, the switch does not simply close and stay there, it bounces for a short time. If your input is fast enough the switch does not only change from open to closed, but bounces from open to close before it stays closed. Your input will consequently send many signals instead of the one you wanted to send. You can debounce the switch via software, if you ignore state changes that are shorter than a certain timeperiod, e.g. 10 ms, or you can do it in hardware. The capacitor stores some energy, which it has to unload or load again on a state change of your switch. The bigger the energy it stores or the greater the resistor, which slows down loading or unloading, the slower the input will follow the switch. If it is just slow enough, you won't see the bounce, but it still gets every wanted state change of the switch.

Conclusion: You should use the resistors, 10 k vs 27 k makes a difference of 0,33 mA vs 0,12 mA, both is fine for your encoder. You can use a capacitor to debounce the encoder/switch, but using when using bigger resistors you need a smaller capacitance. If you don't use the capacitor you should add a debounce-timeout to the reading in software, which ignores state changes of the switch if they are to short.

I am not to good at writing software, but as I got it working on an arduino, I want to try it with python on Volumio, using the RPI.GPIO package. https://sourceforge.net/p/raspberry-gpio-python/wiki/Inputs/ I will let you know if I am succesful.

Saiyato commented 5 years ago

Ok reading it makes sense, I will seek out the formula for capacitance calculation in regards to the needed debounce values for the KY-040. I find this to be interesting indeed. Thanks!

The debouncing makes sense too, the capacitor "eats" the faulty signals, I've found that onoff also provides debouncing in software, I'll see if I can easily incorporate that in the plugin.

When my understanding of debouncing and the signal (strength) increases, I might be able to provide clearer answers.... I'll have a look at the specs of the KY-040 (cheapest encoder on the net I think and therefore used a lot) and see if I can come up with the correct numbers (Ohms and Farads).

elodur commented 5 years ago

Take a capacitor with 10 nF and any resistor you like as long as it is > 10 k. http://www.elektronik-labor.de/OnlineRechner/Grenzfrequenz.html I think the KY-040 will be just any cheep mechanical rotary encoder on a breakout board.

Saiyato commented 5 years ago

It's dawning on me more and more... I've read the KY-040 (badly documented, as stated in my other post) has internal pull-up resistors (10k).

Which means the CLK and DT are automatically pulled up, when the + pin is connected to either +5V or +3.3V; the code implies this, see #11 for SW it's advised to connect a 10k resistor between + and SW, which effectively pulls it up.

So that covers pull-up, any change to the contacts should pull them down and cause a ripple which is read (interrupt) and results in a 'tick' or 'click'. This changing of the states can cause (literal) bouncing of contacts and dirty signals, a capacitor filters this.

Since I'm using HW debouncing and pull-up, erratic behavior can only be bad code or the node platform delaying stuff (insufficient IOPS), am I right? Given that the components used for pulling up and debouncing are correct.

elodur commented 5 years ago

To me it looks like node is too slow - or the onoff-repo is to sensitive, since it reacts to rising and falling signal (as far as I understand it). My approach (on the arduino) was to react only to falling edge on "CLK" and depending of the state of "DT" I decided if it was cw or ccw, which makes one step per click on the encoder.

NeillRG commented 5 years ago

Saiyato and Elodur. Thanks for your work on this guy's I have learnt a lot. Did you ever get any improvement in the SW side of this? The plugin in Volumio is still very patchy when rotating and only really responds when indents are passed at one per second! (Oh and anticlock seems to increase volume weirdly ie the control is reversed) . I'm running it on a pi zero so I was wondering if the sluggish response was just down to the pi's lack of processing speed.

Saiyato commented 5 years ago

Hi @NeillRG ,

I presume you're running v1.0.6, you'd be better off using the version from the repo (v1.2.2). The version from the repo has a lot of additional patches which should fix most of the erratic behaviour.

NeillRG commented 5 years ago

Oh right. Sorry, I'm new to the platform. I'm afraid so far I haven't worked out how to update outside of the plugin lists in Volumio. I read on one thread that you had a "lazy" install procedure. Is that still available?

NeillRG commented 5 years ago

Hello again. Sorry to be a pain! I tried to manually install 1.2.2 from your repo using the volumio guide to unapproved plugin install but there seemed to be error reports generated during the installation process. The plugin now exists in volumino (reporting v1.2.2) but won't enable it also can't be uninstalled via the ui either. Which due to my lack of skills with terminal commands ended in me having to reflash the sd card. Perhaps with the latest version of volumino which is very recent, your v1.2.2 no longer works? Any help greatfully received please! Regards

Saiyato commented 5 years ago

Hi Neill,

I’ve resubmitted my PR, it shouldn’t take long before it’s accepted. Once that’s done, you can download the latest version from the store instead.

I’m not sure what you did exactly, so I can’t really say anything on the matter, I’ve not had any problems installing from scratch, see my PR convo: https://github.com/volumio/volumio-plugins/pull/319 Balbuze did experience some problems, so I’ll await his update to see if I can/need to patch.

Cheers!

From: NeillRG notifications@github.com Sent: donderdag 21 maart 2019 13:49 To: Saiyato/volumio-rotary-encoder-plugin volumio-rotary-encoder-plugin@noreply.github.com Cc: State change state_change@noreply.github.com Subject: Re: [Saiyato/volumio-rotary-encoder-plugin] Rotation is buggy (#2)

Hello again. Sorry to be a pain! I tried to manually install 1.2.2 from your repo using the volumio guide to unapproved plugin install but there seemed to be error reports generated during the installation process. The plugin now exists in volumino (reporting v1.2.2) but won't enable it also can't be uninstalled via the ui either. Which due to my lack of skills with terminal commands ended in me having to reflash the sd card. Perhaps with the latest version of volumino which is very recent, your v1.2.2 no longer works? Any help greatfully received please! Regards Neill

Sent from my Samsung device

-------- Original message -------- From: EWRGroeneveld notifications@github.com<mailto:notifications@github.com> Date: 19/03/2019 15:09 (GMT+00:00) To: Saiyato/volumio-rotary-encoder-plugin volumio-rotary-encoder-plugin@noreply.github.com<mailto:volumio-rotary-encoder-plugin@noreply.github.com> Cc: NeillRG neillroy@hotmail.com<mailto:neillroy@hotmail.com>, Mention mention@noreply.github.com<mailto:mention@noreply.github.com> Subject: Re: [Saiyato/volumio-rotary-encoder-plugin] Rotation is buggy (#2)

Hi @NeillRGhttps://github.com/NeillRG ,

I presume you're running v1.0.6, you'd be better off using the version from the repo (v1.2.2). The version from the repo has a lot of additional patches which should fix most of the erratic behaviour.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHubhttps://github.com/Saiyato/volumio-rotary-encoder-plugin/issues/2#issuecomment-474414037, or mute the threadhttps://github.com/notifications/unsubscribe-auth/AudyCsKbz2uXBbSPWIOQSPBMBgjnqy8pks5vYP2ogaJpZM4R6JNk.

— You are receiving this because you modified the open/close state. Reply to this email directly, view it on GitHubhttps://github.com/Saiyato/volumio-rotary-encoder-plugin/issues/2#issuecomment-475215691, or mute the threadhttps://github.com/notifications/unsubscribe-auth/AL5UrK15OfDs60DxfIndsY8WrCwAs2lLks5vY3_RgaJpZM4R6JNk.

balbuze commented 5 years ago

I uploaded the new version 1.2.2 as official plugin. You can now install it through the UI

NeillRG commented 5 years ago

Thank you chaps your support on this has been excellent I now have v1.2.2 enabled successfully. It operates much smoother than the old one with one remaining niggle. Operation is still reversed, anticlockwise movement increases volume and clockwise reduces. Does anyone else experience this or is it just me? I have checked my wiring. I am using gpio 20 for CLK, 16 for DT, and 6 for the switch. Again thanks for spending your valuable spare time on these issues

Saiyato commented 5 years ago

I uploaded the new version 1.2.2 as official plugin. You can now install it through the UI

Awesome, thanks! :)

@NeillRG, you can configure the CLK and DT pins the other way around, shouldn't matter and reverses the counter.

NeillRG commented 5 years ago

Yup reversing the CLK and DT did the trick. I had tried physically reversing the wiring on the old version without any success but I suppose it was so erratic it may have masked the issue. I wonder if my cheap ass KY-040 is labeled wrong. Anyway, the plugin is pretty much perfect now. From my on line searches there are quite a few people attempting to get this going. I hope they spot the upgrade. Thank you so much. Virtual chocolates and wine coming your way. 😊

joel-felcana commented 5 years ago

I can also confirm that the latest update works beautifully with my setup! 👍 Good job!

gif

Saiyato commented 5 years ago

L'awesome, it's music to me ears to hear that :)

ghost commented 5 years ago

I have no problem with the rotary part, but I do have a problem with the switch The switch works, but the "toggle" part (so the software I guess) does not

If you "mute toggle" ; you get mute while you push, but Releasing the switch makes it unmute. That's not the expected behaviour. In my case the "mute" setting does the same as "mute/unmute toggle".

Someone else reported this in justboom's forums - so this might be related to justboom in some way.

Any pointer ? @Saiyato your help would be much appreciated

ghost commented 5 years ago

(I do use two rotary encoders by the way)

Saiyato commented 5 years ago

I have no problem with the rotary part, but I do have a problem with the switch The switch works, but the "toggle" part (so the software I guess) does not

If you "mute toggle" ; you get mute while you push, but Releasing the switch makes it unmute. That's not the expected behaviour. In my case the "mute" setting does the same as "mute/unmute toggle".

Someone else reported this in justboom's forums - so this might be related to justboom in some way.

Any pointer ? @Saiyato your help would be much appreciated

Hi @Cedric-NET ,

Can you create a new issue for that please? Also what is your setup exactly?

I'm reading you are using a Justboom DAC? Not sure if that should relate to anything really, as the signal should be processed by the 'computer', rather than the soundcard.

Can you test without the board as well and post back?

Thanks in advance!