3

I am trying to control and LED on an Raspberry Pi.

I want the LED to light up when I push a button, and maintain that state until I push the button again.

I have implemented the code below and it works quite fine. However, I am getting problems when I am not fast enough in pushing the button or hold the button.

import RPi.GPIO as GPIO
from time import sleep

inpin = 16
outpin = 20

GPIO.setmode(GPIO.BCM)
counter = 0
GPIO.setup(outpin, GPIO.OUT)
GPIO.setup(inpin, GPIO.IN, pull_up_down=GPIO.PUD_UP)

try:
        while True:

                if GPIO.input(inpin):
                        if counter == 0:
                                print "port is low"
                                GPIO.output(outpin, 0)
                                counter = 0

                        else:
                                print "port is high"
                                GPIO.output(outpin, 1)
                                counter = 1
                else:
                        if counter == 1:
                                print "port is low"
                                GPIO.output(outpin, 0)
                                counter = 0
                        else:
                                print "port is high"
                                GPIO.output(outpin, 1)
                                counter = 1
                sleep(0.1)

finally:
        GPIO.cleanup()

Implementing it the way "TessellatingHeckler" suggested works perfect. Even with multiple inputs and outputs it works perfect. Important things are the "elif" loops to guarantee fast changing states. Here's the working code:

import RPi.GPIO as GPIO
from time import sleep



GPIO.setmode(GPIO.BCM)
GPIO.setup(16, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(20, GPIO.OUT)
GPIO.setup(17, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(27, GPIO.OUT)


btn1_button = 'up'
btn1_light = 'off'

btn2_button = 'up'
btn2_button = 'off'



def LED1(output):
    GPIO.output(20, output)

def LED2(output):
    GPIO.output(27, output)

while True:


######################## BUTTON 1 ########################
    if (btn1_button == 'up' and btn1_light == 'off'):
            if not  GPIO.input(16):
            print "LED1 ON"
            LED1(1)         
            btn1_button = 'down'
            btn1_light = 'on'


    elif (btn1_button == 'down' and btn1_light == 'on'):
            if GPIO.input(16):
            btn1_button = 'up'


    elif (btn1_button == 'up' and btn1_light == 'on'):
            if not GPIO.input(16):
            print "LED1 OFF"
            LED1(0)
            btn1_button = 'down'
            btn1_light = 'off'


    elif (btn1_button == 'down' and btn1_light == 'off'):
            if GPIO.input(16):
            btn1_button = 'up'

###########################################################

####################### BUTTON 2 ##########################
    if (btn2_button == 'up' and btn2_light == 'off'):
                if not  GPIO.input(17):
                        print "LED2 ON"
                        LED2(1)
                        btn2_button = 'down'
                        btn2_light = 'on'

    elif (btn2_button == 'down' and btn2_light == 'on'):
                if GPIO.input(17):
                        btn2_button = 'up'


    elif (btn2_button == 'up' and btn2_light == 'on'):
                if not GPIO.input(17):
                        print "LED2 OFF"
                        LED2(0)
                        btn2_button = 'down'
                        btn2_light = 'off'


    elif (btn2_button == 'down' and btn2_light == 'off'):
                if GPIO.input(17):
                        btn2_button = 'up'
    sleep(0.1)
###########################################################

GPIO.cleanup()
OYPS
  • 81
  • 1
  • 2
  • 13
  • Your description only talks about pushing the button. You're forgetting about tracking releasing the button. Don't do any more LED changes until the button has been released and re-pressed. – TessellatingHeckler Sep 24 '15 at 18:35
  • Are you talking about something like this ? : import RPi.GPIO as GPIO GPIO.setmode(GPIO.BCM) GPIO.setup(4, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) GPIO.setup(25, GPIO.OUT, initial=GPIO.LOW) GPIO.add_event_detect(4, GPIO.BOTH) def my_callback(): GPIO.output(25, GPIO.input(4)) GPIO.add_event_callback(4, my_callback) – OYPS Sep 24 '15 at 20:29
  • If your edit is an accurate copy of your file, the indentation is mixed up for btn2; the last three "if" tests for btn2 are indented too far, they are all *inside* the first one. – TessellatingHeckler Sep 28 '15 at 22:48
  • It is not because of the indentation. I edited the post – OYPS Sep 29 '15 at 07:39
  • It was just a huge typo, everything works fine. Thanks for the help again – OYPS Sep 29 '15 at 11:37

2 Answers2

3

You wrote this plan in words:

  • Button pressed, light goes on
  • Button pressed, light goes off

But what you've written in code is more like:

  • 10x per second,
    • If the button is up, do nothing
    • If the button is pressed, toggle the light

Which is quite different. Hold the button for more than 1/10th of a second and it starts to go weird. What I meant by my comment is that your code goes through the loop and every time it tries to update everything; you're tracking what happened the last time through the loop, which means you can't hold anything more than one loop duration. Instead you need to separate the loop from the state tracking, so the state can stay the same over and over, and only let the dog eat the biscuit when the button changes.

It's the button which drives the system state, not time passing. The system can be in 4 possible states, like this:

(Button=Up, Light=Off) <----------------
        |                               |
        | Button pushed down            |
        \/                              |
(Button=Down, Light=/On/)               |
        |                               |
        | Button released               |
        \/                              |
(Button=Up, Light=On)                   |
        |                               |
        | Button pushed down            |
        \/                              |
(Button=Down, Light=/Off/)              |
        |                              / \
        | Button released               |
        |                               |
         -------------------------------

If you explicitly code those states and follow the sequence, let the button be the only thing allowing you to go from one to the next ... you can't possibly get any weird behaviour from holding the button too long. I hope. Your current code jumps from (Button=Down, Light=On) to (Button=Down, Light=Off) and back again.

My code here is untested, and I'm not completely sure which way the GPIO.input() goes when the button is pressed and released. I assume it's 0/False most of the time and 1/True when the button is pressed.

import RPi.GPIO as GPIO
from time import sleep

inpin = 16
outpin = 20

GPIO.setmode(GPIO.BCM)
GPIO.setup(outpin, GPIO.OUT)
GPIO.setup(inpin, GPIO.IN, pull_up_down=GPIO.PUD_UP)

button='up'
light='off'

while True:

    if (button=='up'   and   light=='off'):
        # wait for button press before changing anything
        if not GPIO.input(inpin):
            GPIO.output(outpin, 1)
            button='down'; 
            light='on'

    elif (button=='down' and   light=='on'):
        # stay in this state until button released
        if GPIO.input(inpin):
            button='up'

    elif (button=='up'   and   light=='on'):
        if not GPIO.input(inpin):
            GPIO.output(outpin, 0)
            button='down'
            light='off'

    elif (button=='down' and   light=='off'):
        if GPIO.input(inpin):
            button='up'
    sleep(0.1)

So, button and light track the state of the system. Every time through the loop, only one of the if blocks will match, and it will mostly do nothing until it gets the button change that makes the state change to the next one.

First time through, the first block matches, it checks for a button press. It keeps doing that.

You press the button, now the first block lights the LED and updates the state.

Now, every time through the loop, (button=='down' and light=='on') matches. It's in state 2 and it stays in that state for as long as you press the button. Every time through the loop it looks for the button release, and that's the only thing that can trigger any state change.

etc.

TessellatingHeckler
  • 27,511
  • 4
  • 48
  • 87
  • Thanks for the perfect input! I am implementing it right now with this ideas in mind and will post the results afterwards – OYPS Sep 27 '15 at 17:21
  • I have implemented it and tested it over and over again. There are two problems: the first one is that right after the program starts the LED is on and i haven't pressed anything at that time. Second problem is, that the LED doesn't stay in it's state. While i press the button the LED is off, after releasing it, it comes back on. – OYPS Sep 28 '15 at 09:51
  • I think that means I read the button incorrectly - the opposite of what it actually is. Try changing the two `if GPIO.input(inpin):` so they say `if not GPIO.input(inpin):` and remove `not` from the other two. (I've updated my post with that change as well). – TessellatingHeckler Sep 28 '15 at 15:04
  • yeah i thought so, so i changed it afterwards but it is still not working perfect. It is way better as before. The only problem i having now is that the states are not always changed right. I will test it again and show the results – OYPS Sep 28 '15 at 18:55
  • After some testing i am coming to the conclusion that it works just fine. it fits MY needs, the only situation it is not useful is when you want to use the button very often, because toggling between the states fast leads to unintended results! But thanks for the solution. It is perfect for me – OYPS Sep 28 '15 at 19:06
  • That's cool. I wonder why? If you change the last three `if` lines inside the loop to `elif`, so instead of `if / if / if / if ` through the loop, it goes `if / elif / elif / elif `, I think that would force it to only change state once per loop - and with the sleep(0.1) line it would add some fast change limit. What kind of button do you have? Is it built in? If not, maybe you're seeing button bounce sending more than one signal change for every fast press of the button: http://raspberrypihobbyist.blogspot.co.uk/2014/11/debouncing-gpio-input.html Anyway, if that's good enough, that's good.. – TessellatingHeckler Sep 28 '15 at 19:16
  • now it works like a charm. Perfect! for my project i will need this to work for 5 buttons, so i will test that now. – OYPS Sep 28 '15 at 20:43
  • I have updated my code up in my initial post to show how i was trying to implement multiple buttons, the second one doesn't work at all – OYPS Sep 28 '15 at 21:16
1

Try the following, This eliminates "led-on" problem at start:

import RPi.GPIO as GPIO
from time import sleep

inpin = 11
outpin = 7

GPIO.setmode(GPIO.BOARD)
GPIO.setup(outpin, GPIO.OUT)
GPIO.setup(inpin, GPIO.IN)

x=0
y=0

try:

   while True:

     if (x==0   and   y==0):
           # wait for button press before changing anything
          if GPIO.input(inpin) == 0:
                 GPIO.output(outpin, 0)
                 x=0 
                 y=1
                 print("led off")

     elif (x==0  and   y==1):
           # stay in this state until button released
          if GPIO.input(inpin) == 1:
             x = 1

     elif (x==1   and   y==1):
          if GPIO.input(inpin) == 0:
                 GPIO.output(outpin, 1)
                 x=1
                 y=0
                 print("led on")

     elif (x==1 and   y==0):
          if GPIO.input(inpin) == 1:
             x=0
     sleep(0.1)


except KeyboardInterrupt:
          GPIO.output(outpin, 0)
          GPIO.cleanup()    
Sankumarsingh
  • 9,889
  • 11
  • 50
  • 74
sas
  • 21
  • 3