0

I have a raspberry pi 2, and a momentry switch, I am not using a resistor, and I don't know if that is the issue...

Here is my python code -

import RPi.GPIO as GPIO
import time
import os

GPIO.setmode(GPIO.BCM)
GPIO.setup(18, GPIO.IN,pull_up_down=GPIO.PUD_UP) # Button


state = 0

while True:
button = GPIO.input(18)

if state==0:
    print "State 0"
    os.system('/home/pi/PiTableLights/Start.sh')
    if button:
        state=1

elif state==1:
    print "State 1"
    os.system('/home/pi/PiTableLights/Red-On.sh')
    if not(button):
        state=2

elif state==2:
    print "State 2"
    os.system('/home/pi/PiTableLights/Blue-On.sh')
    if button:
        state=3

elif state==3:
    print "State 3"
    os.system('/home/pi/PiTableLights/Green-On.sh')
    if not(button):
        state=4

elif state==4:
    print "State 4"
    os.system('/home/pi/PiTableLights/Off.sh')
    if button:
        state=1

time.sleep(0.1)

GPIO.cleanup()

The issue I have is a press and a release is being treated as a press by my script, so when it runs its at state 0, I press the button and it goes to state 1 and I release and it goes to state 2, I only want it to change state on a press, not a release.

Is this something someone can help me fix?

Thanks

4
  • As posted your script will run once and stop. I assume this is a formatting problem. It is difficult to work out what this clumsy code is actually trying to do. Your underlying problem is probably contact bounce.
    – Milliways
    Jul 29, 2016 at 10:37
  • Basicly, after each button press, I want the sh file to run, so this will turn my lights red, then blue, then green, then off...and this should be able to loop..
    – Karl
    Jul 29, 2016 at 10:40
  • 1
    You're using if not statements on states 1 and 3, rather than if statements, i.e. if state == 1 and the button's not pushed then it sets state to 2 and continues. Doesn't look right to me?
    – goobering
    Jul 29, 2016 at 10:57
  • That seems to fix it, it seems a little intermittent to the button presses, but it seems to have got it working only on presses rather than releases..thank you
    – Karl
    Jul 29, 2016 at 11:14

2 Answers 2

1

I think you just need to nudge your if statements about a wee bit:

import RPi.GPIO as GPIO
import time
import os

GPIO.setmode(GPIO.BCM)
GPIO.setup(18, GPIO.IN,pull_up_down=GPIO.PUD_UP) # Button

state = 0
button = GPIO.input(18)

while True:

    # If the button's pushed:
    if button:
        # Check the current state, act accordingly:
        if state == 0:
            print "State 0"
            os.system('/home/pi/PiTableLights/Start.sh')
            # Increment state
            state += 1
        elif state == 1:
            print "State 1"
            os.system('/home/pi/PiTableLights/Red-On.sh')
            # Increment state
            state += 1
        elif state == 2:
            print "State 2"
            os.system('/home/pi/PiTableLights/Blue-On.sh')
            # Increment state
            state += 1
        elif state == 3:
            print "State 3"
            os.system('/home/pi/PiTableLights/Green-On.sh')
            # Increment state
            state += 1
        elif state == 4:
            print "State 4"
            os.system('/home/pi/PiTableLights/Off.sh')
            # Reset state
            state = 1

    # Wait 0.1 secs before next loop iteration
    time.sleep(0.1)

GPIO.cleanup()
5
  • Nothing seems to work if I change my code to do this, pressing the button doesn't do anything?...
    – Karl
    Jul 29, 2016 at 10:41
  • 2 secs, I'll just copy/paste the top section in...
    – goobering
    Jul 29, 2016 at 10:47
  • That still doesn't work, pressing my button causes no action in the terminal...my orignal code works, it just seems to take press and release as a press rather than just press :(
    – Karl
    Jul 29, 2016 at 10:53
  • 1
    I think the problem here is this loop will then register a state change every 100 ms while the button is held down. You need respond only to changes in value, not value itself ;)
    – goldilocks
    Jul 29, 2016 at 12:59
  • @goldilocks Good shout. I'll hold my hands up to half-assing this one. :P
    – goobering
    Jul 29, 2016 at 13:00
1

In addition to the initial correction of not using not with every other transition, to smooth things out you need to:

  1. Maintain state (already down and already up) for the button independently, so that the loop doesn't count holding the button down as multiple presses.

  2. Include a bit of "debounce time", also maintained as an aspect of the button -- which points to the conclusion that button should be a simple class with several characteristics, one tracking its state and one tracking the debounce time. More on this below. In this case, you don't have to create a class, but if you want to use multiple buttons in the same program, you should.

I have an example of this in perl here, but since the focus is not really implementing a button (although it is used as an example), and it is more callback style event based, it may not be easy to follow.

Here are some constraints that your example demonstrates:

  • You want the loop to be running continuously, because you could push the button at any point, but you should avoid "busy looping" which will consume all of one processor. This means you need a brief sleep in the loop, as per goobering's example. Since no one is likely to be able to be able to push a button up and down in less than 1/20th of a second, you could use 50 ms. However, to start use 250 ms, and keep that in mind as you get it working; push a button down, and hold it at least half a second. This should ensure the loop catches the event.

  • When a button is pushed down and held for an arbitrary length of time, that should trigger one state change, not one for every iteration of the loop while the button is held down. I don't think goobering's code accounts for this. The 100 ms sleep time will cover debouncing and prevent busy looping, but not this issue.

To deal with the latter problem, you need to track the current state of the button with a simple boolean variable (as per #1 above); when the state of the pin indicates the button is down, you set this variable, and discard subsequent such readings until the state of the pin changes back to up, at which point you change the variable tracking the button state. Only when there is a contrast between the GPIO state and your tracking of the button state do you trigger an event/transition from one color to the next. This means the button can be held down for an arbitrary length of time, but it only counts as one push.

There is a further minor complication though.

Debouncing

As per the wikipedia blurb, contact buttons have a tendency, when first pushed down, to create an oscillating response over a very short time period (milliseconds). If you are using a 100+ ms sleep in your loop, that should cover it. However, in the general case this may add a noticeable bit of latency in response time and make it possible, if you try hard and have the right button, to click it quickly during the sleep, meaning that will be ignored. 50 ms makes this harder, but ideally you want to use event callbacks here anyway instead of a loop, since a GPIO can generate genuine interrupts for the operating system. Responding to those is what will give you the most optimal responsiveness and means you don't need to use any sleep at all. This is what is going on in that other answer with the perl example.

Again, in this case, with a single button, you can use the loop with the brief sleep. As you get into more complicated things, however, keep this debouncing issue in mind. Unless you are going to use a per button thread (which is probably silly), you don't want to use a sleep to debounce. You want to maintain a second variable per button, in addition to the one tracking its state: when the state changed last. You then ignore any changes which take place within a short time which may vary from button to button. I have seen people using 100+ ms bounce times; I think this is way too much and indicates there is something else wrong or the button is very awkward.1 In the perl example it looks like I used 10 ms, and I remember testing that one pretty thoroughly. In the oscilliscope image from wikipedia, the bounce is < 5ms (but we don't know what it is from).

50 ms may be a good compromise. I believe I started with 100 and worked my way down; I think 100 is too much because it complicates getting a clean response when clicking the button quickly -- it must not overlap the point the button is released, because then the variable tracking the button state will be out of phase.

But for now, with your loop and a single button, using a sleep in the loop, starting at 250 ms and working down experimentally, remembering to press and hold the button for at least 1/2 second, should deal with it. The main issue is tracking the button state so the loop does not count one push as a push for every iteration of the loop.

I don't code in python so I can't give you a concrete example, but again, since you are using a loop and one variable, it could simply be another global state variable. Then in each loop iteration:

  • If the GPIO state says the button is up, set the variable to indicate this.

  • If the GPIO state says the button is down, and the variable says the button is up, set the variable to indicate this and trigger an event/state transition for the LED. Otherwise just ignore it. Then when the button is released the variable tracking it will be changed, ready for the next press.


1. I've seen people use much more than that, 0.5 - 1 second plus. This is a case where "debounce" is being confused or conflated with the issue of the button being held down and released. That's fine in some contexts but in the general case it is a mistake.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.