1

I've got 4 LEDs, a buzzer and a button. There's an add_event_detect to switch the LEDs and buzzer on and off. The callback methods works find when the outputs are just on continuously until another press turns them off. But I want them to sleep for 0.3 seconds and turn on again.

This works kind of with the Keypad. You have to press key 2 quite hard but it works. I can't figure out how to get out of the loop in the btnState function.

A button press turns them on but they stay on.

Wiring

button = Gpio and ground. leds = gpio and ground buzzer = 5v, NPN 8050 transistor to gpio and ground

Code

import RPi.GPIO as GPIO
import time
import Keypad

Leds = [37, 35, 33, 31]
buzz = 13
button = 29
ROWS = 4        # number of rows of the Keypad
COLS = 4        #number of columns of the Keypad
keys =     ['1','2','3','A',    #key code
            '4','5','6','B',
            '7','8','9','C',
            '*','0','#','D' ]
rowsPins = [12, 22, 24, 26]        #connect to the 
row pinouts of the keypad
colsPins = [40, 38, 36, 32]        #connect to the 
column pinouts of the keypad

btnState = 1

alarm_end = time.time() + 60 * 3  # alarm_ends 
after 3 mintues

def setup():
    GPIO.setmode(GPIO.BOARD)
    GPIO.setup(Leds, GPIO.OUT)
    GPIO.setup(buzz, GPIO.OUT)
    GPIO.setup(button, GPIO.IN, 
pull_up_down=GPIO.PUD_UP)
    GPIO.setwarnings(False)

def swState(ev=None):
global btnState
if btnState == 1:
    btnState = 0
    GPIO.output(Leds, btnState)
    GPIO.output(buzz, btnState)
    while (time.time() < alarm_end or btnState == 0):
       print ('Sound the Alarm!!...')
       GPIO.output(Leds, True)
       GPIO.output(buzz, True)
       time.sleep(0.3)
       GPIO.output(Leds, False)
       GPIO.output(buzz, False)
       time.sleep(0.3)


else:
    btnState = 1
    alarmOff()

def keyPadInput():
   keypad = Keypad.Keypad(keys,rowsPins,colsPins,ROWS,COLS)    
    key = keypad.getKey()     
   if key == '1':
        print ('Sound the Alarm!!...')
        while (key != '2'):
            GPIO.output(Leds, True)
            GPIO.output(buzz, True)
            time.sleep(0.3)
            GPIO.output(Leds, False)
            GPIO.output(buzz, False)
            time.sleep(0.3)
            key = keypad.getKey()
            if (key == '2'):
                alarmOff()          
    elif key == '2':
        alarmOff()

def alarmOff():
    print ('Turning off system...')
    GPIO.output(Leds, False)  
    GPIO.output(buzz, False)

def main():
    setup()
    print ("Program is starting ... ")
    try: 
        loop()
    except KeyboardInterrupt:  #When 'Ctrl+C' is 
pressed, exit the program. 
         destroy()


def loop():
    GPIO.add_event_detect(button, GPIO.FALLING, 
callback=swState, bouncetime=200) # wait for 
falling
    while True:
        keyPadInput()

def destroy():
    GPIO.output(Leds, False)
    GPIO.output(buzz, False)
    GPIO.cleanup()

if __name__ == '__main__':     #Program start from 
here
    main()

TIA!

  • Not sure I quite follow what you're trying to do but shouldn't the `while (time.time() < alarm_end or btnState == 0):` line be using `and` instead? i.e. blink the light whilst the alarm's not expired AND `btnState` hasn't changed. Personally I'd also wrap the two conditions in parethases to visually separate them: `while ( ( time.time() < alarm_end ) and ( btnState == 0 ) ):` – Roger Jones May 08 '19 at 11:46
  • A further thought (that might well be wrong, I'm no Python guru). You're using the `event_detect` interrupt to toggle the `btnState` variable but I'm not sure you can have two concurrent interrupts on the same pin? When you first press the button the event triggers and you enter your loop but the second press can't trigger the second event until you exit the loop for the first one so `btnState` never gets set to `1` whilst you're in the loop. – Roger Jones May 08 '19 at 11:59
  • @RogerJones: I'm not quite sure I follow what I'm trying to do. Basically I have an alarm system. **Outputs** - 4 LEDS, a buzzer, a OLED display, a servo motor. **Inputs** a button, a motion sensor, a keypad. The idea, If the motion sensor, button or keypad take input, then turn on the outputs. The button and keypad should also be able to turn the outputs off and the alarm should only sound for 3 minutes beofre shutting itself off. any suggestions? :) –  May 08 '19 at 12:07
  • @DaviebPrime How interesting, so your hardware list includes a servo motor. What is it used for? Can I have a full picture? – tlfong01 May 09 '19 at 09:03

3 Answers3

1

If I've understood your question the alarm condition triggers correctly but pressing the button again to cancel it early does not work and you have to wait for your 3 minutes timeout?

I think part of the problem is the logic on the while... : line in the swState function. You want the code to stay in the loop "while" two conditions are true:

  • The timer has not reached the 3 minute time-out ( conditon A )
  • The btnState variable does not change ( condition B )

As written, using while( A or B ):, the loop will continue while one or the other is still true. In other words if A is true then it makes no difference if B is true or not. For your intended behaviour you need the loop to continue whilst both are true so you need to be using while( A and B ):. This will exit the loop as soon as either condition becomes false.

Another possible problem (that I'm not 100% certain of so I'm happy to be corrected by someone with more knowledge of Python interrupts) is that you can't have more than one swState running at once. That is to say if you press the button swState gets called and enters your loop but if you then press the button again then swState can't be run until the first one has exited. If this is true then pressing the button a second time to cancel the alarm will not work because the function is not called and the btnState is not changed. Coming from a C and embedded background you always keep your interrupts as short as possible and definitely avoid loops.

To overcome this you could try moving the whole alarm sounding part into the loop function and use the swState function to set your alarm timer and btnState variable. Something like this (totally untested so beware)...


alarm_end = time.time() - 1
btnState = 1
alarmWarbleState = 1

def swState(ev=None):
  if btnState == 1:
    # Trigger the alarm and set the time-out
    alarm_end = time.time() + ( 60 * 3 )
    btnState = 0
  else:
    # Cancel the alarm.
    btnState = 1

:
:
:

def processAlarm():
    # Is alarm triggered? 
    if( ( time.time() < alarm_end ) and ( btnState == 0 ) ):
       print ('Sound the Alarm!!...')

       if( alarmWarbleState > 0 ):
         alarmWarbleState = 0
       else
         alarmWarbleState = 1

       GPIO.output(Leds, alarmWarbleState )
       GPIO.output(buzz, alarmWarbleState )
       time.sleep(0.3)
    else:
      # Alarm is either not running; cancled or has timed-out. Re-arm btnState.
      alarmOff()
      btnState = 1;


:
:
:

def loop():
  GPIO.add_event_detect(button, GPIO.FALLING, callback=swState, bouncetime=200)
  while True:
    keyPadInput()
    processAlarm()


Roger Jones
  • 1,479
  • 5
  • 14
  • Hey Roger, thanks for taking the time. I implimented what you suggested and concosle prints Led Off. So the else: btnState != 1 is executed. Yet btnState does = 1!? –  May 08 '19 at 14:29
0

I think the problem is you are trying to use the variable btnState as an integer sometimes and as a boolean (True/False) sometimes.

btnState = 1 sets it to an integer 1.

btnState = not btnState sets it to a boolean False.

The line if btnState == 1: will then always fail as True or False are not the same as 1.

It's probably simplest to use

if btnState == 1: btnState = 0 else: btnState = 1

instead of btnState = not btnState

joan
  • 67,803
  • 5
  • 67
  • 102
  • I tried somethings with your suggestion but still cannot get the button to turn off once the while loop starts. I edited the code above if you can take a look? Thanks! –  May 07 '19 at 21:07
  • I would change all your code to use 1 for on and 0 for off. It gets confusing when sometimes you use True, sometimes False, sometimes 1, sometimes 0. Just choose one method and stick with it. I wonder if the problem is you have a variable btnState as well as a function btnState? – joan May 07 '19 at 21:16
  • Ok, I've done that. Still no joy. am I right in thinking a continuous on/off with LEDs and buzzer will need to be in a while loop with some condition? So how would I break from the loop when the btnState changes again? –  May 07 '19 at 21:49
  • @DaviebPrime How much works? Has the button ever worked? Have the LEDs ever switched on and off? If not I would start from scratch. Get rid of the code, get rid of the wiring. Then get one thing working at a time and don't add a new feature until the old one is working. When you get a new feature working check that all the old ones still work. There seem to be multiple errors at the moment. One is fixed and others appear. – joan May 08 '19 at 07:06
0

Question

  1. Have to press key 2 quite hard but it works.
  2. A button press turns them on but they stay on.

Answer

What do you mean by "quite" hard"? Do you mean "quite long" (even quite soft)? Perhaps the hardware bouncing is worst than 200mS.

Or better use level trigger if edge trigger is being used.

Driven events are hard to troubleshoot. I would suggest a simple loop of just checking out one button one led and use a scope to make sure bouncing and glitch patterns etc.

Of course you can add more generous software debouncing (on top of Rpi GPIO's built in software debouncing) to eliminate bouncing related risks.

I also suggest to remove the NPN BJT driven buzzer circuit (also see Note 1 below).

The Vcc = 5V NPN BJT driven buzzer's switching off back EMF can be as high as +20V at collector and -10V at base, which of course is risky at least to that part of the GPIO circuit, latching it up, killing it instantly, or in a short time of some 30 minutes, or shorten its life.

See the scope waveform below. Or use optocoupler/logical level shifter such as TLP521-4 or TLP281-4 module, to isolate back EMFs from inductive loads such as buzzer, relay, solenoids. Using optocouplers is almost a standard practive in industrial environments. My place is near a huge motor driving a heavy elevator for a 28 story building. So I also need optocouplers to isolate machinery noises.

Your momentarily button/switch behaves like a latch/self locking switch. I guess after fixing the first problem, second problem would disappear.

Note 1

And if you are using an Arduino compatible active piezo buzzer, you might have the "Rpi-High-Not-high-enough" problem with the following symptoms:

  1. Relay always on, cannot switch off, LED dim or blinking.

  2. Buzzer always on, still buzzing even should be off.

  3. other devices, ...

See old post below for a quick and dirty, but still latching up risky get around.

References

NPN BJT driven Buzzer Back EMF waveform

Buzzer back EMF

Buzzer Back EMF Experiments

NPN BJT Driven Buzzer Video

TLP521-4/TLP281-4 Octocoupler/Logical Level Shifter

tlp5214_2814_optocoupler

TLP521-4 and TLP281-4 are old, new replacements are EL817C and EL354

Alternative Optocouplers to replace TLP521/281/4

active buzzer always buzzing problem

Active Buzzer makes sound on both LOW and HIGH

Why active buzzer is always on.

The root cause is using an Arduino compatible active buzzer for Rpi. For this Arduino compatible buzzer, it is designed to be low active, ie, when input signal lower than about 1.0V buzzer will be on. And if input signal is higher than about 3.5V, buzzer will be off. Now Arduino has no problem, because its High is about 4.2V, well above 3.5V.

Now Rpi is in big trouble, because its High is only about 3V, no hope reaching the required 3.5V to switch off.

Get around

Easy - insert a 4k7 resistor between Rpi GPIO pin and input of active buzzer.

Quick and dirty explanation

The buzzer circuit input front end is very likely a PNP BJT. It is biased such that when input signal to base, through a biasing resistor, is 3.5V or higher , the transistor is cut off (Arduino High is 4.2V, therefore a clean cut off), no base current flows, therefore not enough collector current to activate the piezo buzzer.

Now Rpi's High is only 3V, therefore not high enough to have a clean cut off, resulting some base current, and therefore some collector current to partially/weakly activate the piezo, therefore the smaller buzzing sound.

The get around of inserting a 4k7 between Rpi GPIO and input is not to allow even small base current to flow, to get clean cut off, so no sound.

Now for the activating/on case, both Arduino and Rpi have Low level lower than 1V, therefore both have no problems switching on.

Actually Rpi has the same problem with a couple of other Arduino only devices, including 5V low level trigger relay. Similarly Rpi can only switch on, but not switch off. The same trick of adding a 4k7 resistor is the quick cure. Another get around it is the following:

To switch off relay, instead of set GPIO High,

set GPIO to input mode

I think node.js can use the same trick, that is

instead of buzzer.write.0 to switch off buzzer, use buzzer.unexport()

tlfong01
  • 4,384
  • 3
  • 9
  • 23
  • Thanks for this though, I'am a beginner and theres a lot to take in. How do I use 4k7? I have 220, 1k and 10k. –  May 08 '19 at 09:30
  • Just a quick reply. Two 10k resistor in parallel make 5k, which is good enough. 4k7 is only a rough value. I guess 3k3 to 10k will also work, Because the low side of 0.8V to switch on is generously forgiving. But don't try 220 or 1k, which might be a little bit risky. I am going out to eat. Perhaps a diagram late evening or tomorrow. – tlfong01 May 08 '19 at 09:34
  • I am drawing the pictures on the old active buzzer using node.js post. I am not sure if my new suggestion works for you, because I don't know if you are using the active buzzer similar to the two I am discussing (they used different biasing resistors, 4k7 instead of 1k/10k). You may like to give the link of your buzzer, so that I can include options for you model. – tlfong01 May 09 '19 at 09:19