1

I'm trying to make a toggleable value in my program, during a loop. I can't use time.sleep() in the program, as I can't have the program stopping completely (so that you can't press buttons during this time).

I could just do this:

while True:
    if button.is_pressed():
        # do task

But then, the value will be toggled every frame. This is too fast.

I also tried this:

while True:
    if button.value:
        cur_state_right = button.value
        if cur_state_right != prev_state_right:
            # do task
        prev_state_right = cur_state_right

This makes you press then release the button before the task can be done again. However in practice, the task is only done sometimes when you press the button, like it must be pressed at a specific time. For some reason, the code also stops when you hold the button down. This is bad, as this code runs a digital clock, which shouldn't stop randomly.

Just in case it's the rest of the code that's causing this, here it is:

import board
import busio
from adafruit_ht16k33 import segments
import adafruit_gps
from adafruit_datetime import timedelta, datetime
import time
import digitalio
import microcontroller

btn_right = digitalio.DigitalInOut(board.GP5)
btn_right.switch_to_input(pull=digitalio.Pull.UP)

btn_confirm = digitalio.DigitalInOut(board.GP9)
btn_confirm.switch_to_input(pull=digitalio.Pull.UP)

btn_left = digitalio.DigitalInOut(board.GP13)
btn_left.switch_to_input(pull=digitalio.Pull.UP)

i2c = busio.I2C(board.GP27, board.GP26)
display = segments.Seg7x4(i2c)
gps = adafruit_gps.GPS_GtopI2C(i2c)

display.fill(1)
time.sleep(1)
display.fill(0)

gps.send_command(
    b"PMTK314,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"
)  # start gps data grab
gps.send_command(b"PMTK220,1000")  # set ur to 1hz
gps.send_command(b"PMTK285,2,100") # activate pps if not already on

def load(filename):
    f = open(filename + ".txt", "r")
    output = f.read()
    f.close()
    return output
    
def save(filename, data):
    f = open(filename + ".txt", "w")
    f.write(data)
    f.close()

def button_input():
    prev_state_left = btn_left.value
    prev_state_centre = btn_confirm.value
    prev_state_right = btn_right.value
    while True:
        cur_state_left = btn_left.value
        if cur_state_left != prev_state_left:
            if not cur_state_left:
                return "<"
        prev_state_left = cur_state_left

        cur_state_centre = btn_confirm.value
        if cur_state_centre != prev_state_centre:
            if not cur_state_centre:
                return "!"
        prev_state_centre = cur_state_centre

        cur_state_right = btn_right.value
        if cur_state_right != prev_state_right:
            if not cur_state_right:
                return ">"
        prev_state_right = cur_state_right


def savings_check():
    dst = 0
    while True:
        display.fill(0)
        display.print(dst)
        option = button_input()
        if option == "!":
            break
        while True:
            if option == ">":
                dst += 1
                break
            if option == "<":
                dst -= 1
                break
            else:
                break
    return dst

dst = savings_check()

gps.update()
while True:
    try:
        time.sleep(1)
        gps.update()
        timeset = datetime(
            gps.timestamp_utc.tm_year,
            gps.timestamp_utc.tm_mon,
            gps.timestamp_utc.tm_mday,
            gps.timestamp_utc.tm_hour,
            gps.timestamp_utc.tm_min,
            gps.timestamp_utc.tm_sec,
        )
        break
    except Exception as e:
        display.print("LOAD")
        print(e)
        print("Time load fail. Retrying. Satellites:", gps.satellites)
print("Load success!")

if dst != 0:
    timeset = timeset + timedelta(hours=dst)

timeset = timeset + timedelta(seconds=15)

last_print = time.monotonic()
last_update = time.monotonic()

pps_state = True

prev_state_right = not btn_right.value
while True:
    current = time.monotonic()
    if btn_left.value and btn_confirm.value and btn_right.value:
        if current - last_print >= 1.0:
                last_print = current
                timeset = timeset + timedelta(seconds=1)
                display.print(
                    "{:02d}".format(timeset.hour) + "{:02d}".format(timeset.minute)
                )
                print(timeset, "With", gps.satellites, "Satellites")
                if timeset.second % 2 == 0:
                    display.colon = True
                else:
                    display.colon = False
                    
    elif not btn_left.value:
        display.colon = False
        display.print(
            "{:02d}".format(timeset.day) + "{:02d}".format(timeset.month)
        )
        
    elif not btn_confirm.value:
        display.colon = False
        display.print(timeset.year)
        
    elif not btn_right.value:
        cur_state_right = not btn_right.value
        if cur_state_right != prev_state_right:
            if pps_state:
                gps.send_command(b"PMTK285,0,100")
                print("PPS off")
            if not pps_state:
                gps.send_command(b"PMTK285,2,100")
                print("PPS on")
            pps_state = not pps_state
        prev_state_right = cur_state_right
        
    elif not btn_left.value and not btn_confirm.value and not btn_right.value:
        microcontroller.reset()
  • You are using a PULL_UP on the button, so it is always pressed. Pull it down and make sure that the way it is wired pulls it up, when pressed. Really you probably don't even need the PULL in the first place because you can just do that with a resistor. Also, you could wire it through an interrupt instead of checking every frame if it is pressed. – OneMadGypsy Jul 17 '23 at 16:59
  • @OneMadGypsy I have to pull it up, as I didn't account for a resistor when building it. It's already glued and soldered now. – Commodore 64 Jul 17 '23 at 17:10
  • Either way, your button logic is backwards. Pressing it should pull it up. If you have already soldered your button to pull it down, on press, then you have to change your code to `if not btn.is_pressed()` - meaning all of your code will technically say the opposite of what you want, when referring to the button. If you messed up and have your button also pulling up, on press, then the simple fix is to change your code to pull it DOWN, while at rest. – OneMadGypsy Jul 17 '23 at 17:13
  • @OneMadGypsy I tried pulling it down, and also reversing all the not statements, but that just stopped the buttons from being pressed. I should probably note, I connected it from GPIO to Ground, not 3V3 to GPIO. Besides, I fixed the code. I had put the if statements in the wrong order. I'll answer it. – Commodore 64 Jul 17 '23 at 17:17
  • @OneMadGypsy That was a fault on my part. I've learnt from it, (and a lot of other things in this project such as buying inefficient parts and having to solder instead of just plug in), but it does work now. The code just *looks* a bit terrible, but it works fine. The parts were all put together in a way that I knew would work, I just had to figure out the code. – Commodore 64 Jul 17 '23 at 17:23

2 Answers2

1

Due to your setup, your values will be backwards. You are pulling the button up so, at rest, it's value will be 1. You have your button wired to ground so, when you press it, the value will be 0

All that being said. Here is the logic to capture button events

rbtn = btn_right.value

while True:
    #value = 1, last_value = 0
    #in your setup, this means released
    if btn_right.value and not rbtn:
        #do something
        ...

    #last_value = 1, value = 0
    #in your setup, this means pressed
    if rbtn and not btn_right.value:
        #do something
        ...
    
    #update
    rbtn = btn_right.value
OneMadGypsy
  • 4,640
  • 3
  • 10
  • 26
  • This works really well. I apologise if I came off as stubborn in my (now deleted) comments. Seeing this, I don't know why Adafruit doesn't use something like this in their tutorials. It's similar to yours, but overcomplicated. – Commodore 64 Jul 17 '23 at 18:09
  • No apologies necessary, friend. I am glad I could help. Stop soldering your parts together before your stuff works. If this was anything more complicated than a button or LED you might be in trouble. – OneMadGypsy Jul 17 '23 at 18:11
  • One question: does your code require `time.sleep()`? I know it's not in the example, but it seems to not work well when repeatedly pressing the button, which is fine for my purpose, but may not be in other situations. – Commodore 64 Jul 17 '23 at 18:15
  • @Commodore64 - it doesn't require `time.sleep` but you have to keep in mind that the button is caught according to the speed of `while`. It is absolutely possible to press or release between cycles, and for it to be ignored. Using an interrupt may definitely be a better way to go. – OneMadGypsy Jul 17 '23 at 18:16
  • Got it. Maybe I should learn C. – Commodore 64 Jul 17 '23 at 18:17
  • @Commodore64 - you should learn and study computer science and it's child concepts. Then you don't really learn C (or any other language). You already understand everything you can do as a programmer, you're just re-syntaxing what you already know. I feel like all of this stuff is something you can pick up very quickly by simply studying something that has USEFUL examples. If you see a for loop in C are you automatically completely lost as to what a for loop does, just cause it's in C? It's no different syntactically than a JS for loop and probably 100 other languages. – OneMadGypsy Jul 17 '23 at 18:41
0

The problem was that I had put the wrong code in or out of the if statement.

It looked like this:

while True:
    if button.value:
        cur_state_right = button.value
        if cur_state_right != prev_state_right:
            # do task
        prev_state_right = cur_state_right

However it should have been like this:

prev_state_right = btn_right.value
while True:
    cur_state_right = btn_right.value
    if cur_state_right != prev_state_right:
        if cur_state_right:
            # do task
        prev_state_right = cur_state_right
  • The only 2 values are `1` and `0`. In your case `1` means that the button is not pressed. All you need is: `if not btn_right.value:`. All that other var juggling is just wasting memory, forcing unnecessary branches, and junking up your code. – OneMadGypsy Jul 17 '23 at 17:30