Skip to content

Manually Setting Time

We need a program that will use three buttons to set the time.

The Mode Button

The first button is called the "mode" button. It will cycle through four internal "states" of our clock. Here are the modes:

  1. Mode 0: Clock Running - this is the normal mode of operation
  2. Mode 1: Setting the Hour
  3. Mode 2: Setting the Minute
  4. Mode 3: Setting AM/PM

Here is a walkthrough of the button-mode-test.py program, breaking it down into clear, digestible concepts for everyone new to MicroPython.

Code Walkthrough

1. Basic Setup and Imports

1
2
3
from mp_button import Button
from time import localtime, sleep
from machine import Pin

Let's understand what these lines do: - These are import statements that bring in code we need to use - Button is a special module that helps us work with physical buttons - localtime and sleep are time-related functions - Pin lets us work with the physical pins on our Raspberry Pi Pico

2. Setting Up the Pins

1
2
3
mode_pin = Pin(16, Pin.IN, Pin.PULL_UP)
next_pin = Pin(17, Pin.IN, Pin.PULL_UP)
previous_pin = Pin(18, Pin.IN, Pin.PULL_UP)

Here's what's happening: - We're setting up three different pins (16, 17, and 18) on the Pico - Each pin is set as an input (Pin.IN) - meaning it receives signals rather than sends them - Pin.PULL_UP means the pin is naturally "high" (1) until a button press makes it "low" (0) - Think of it like a light switch that's normally on, and pressing the button turns it off

3. Global Variables

1
2
3
4
5
6
7
8
9
counter_pressed = 0
counter_released = 0
mode = 0  # default clock running
mode_names = ["run","set hour","set minute","set AM/PM"]
mode_count = len(mode_names)
now = localtime()
hours = now[3]
minutes = now[4]
am_pm = 0

These are our program's variables: - counter_pressed and counter_released keep track of button presses - mode tells us which setting we're currently adjusting (starts at 0) - mode_names is a list of the different modes our clock can be in - mode_count is how many modes we have (4 in this case) - now gets the current time from the Pico - hours and minutes store the current time values - am_pm keeps track of whether it's morning (0) or afternoon (1)

4. Button Handler Functions

1
2
3
4
5
6
def button_mode_irq(button, event):
    global mode, hours, minutes
    if event == Button.PRESSED:
        mode += 1
        mode = mode % mode_count
        print('new mode:', mode, mode_names[mode])

This is our mode button handler: - global tells Python we want to change variables outside this function - When the button is pressed, we increase the mode by 1 - The % (modulo) operator helps us cycle back to 0 after reaching the last mode - For example: if mode is 3 and we add 1, 4 % 4 = 0, so we go back to the first mode

5. Next and Previous Button Handlers

1
2
3
4
5
6
7
8
9
def button_next_irq(button, event):
    global mode, hours, minutes, am_pm
    if event == Button.PRESSED:
        if mode == 1:
            hours += 1
        if mode == 2:
            minutes += 1
        if mode == 3:
            am_pm = 1 if am_pm == 0 else 0

The next/previous buttons: - They only work when we're in a setting mode (not mode 0) - Mode 1: adjust hours - Mode 2: adjust minutes - Mode 3: toggle between AM and PM - The previous button does the same thing but decreases values

6. Creating Button Objects

1
2
3
button_mode = Button(16, False, button_mode_irq, internal_pullup = True, debounce_time = 100)
button_next = Button(17, False, button_next_irq, internal_pullup = True, debounce_time = 100)
button_previous = Button(18, False, button_previous_irq, internal_pullup = True, debounce_time = 100)

Here we create our button objects: - Each button gets a pin number (16, 17, or 18) - We tell it which function to call when pressed (the _irq functions) - debounce_time = 100 prevents multiple triggers from one press - Think of debouncing like waiting a moment to make sure someone really pressed the button once

7. Main Loop

1
2
3
4
while(True):
    button_mode.update()
    button_next.update()
    button_previous.update()

This is our main program loop: - It runs forever (that's what while True means) - Each time through the loop, we check if any buttons were pressed - The update() function handles all the button checking for us

Full Program Listing

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
from mp_button import Button
from time import localtime, sleep
from machine import Pin

mode_pin = Pin(16, Pin.IN, Pin.PULL_UP)
next_pin = Pin(17, Pin.IN, Pin.PULL_UP)
previous_pin = Pin(18, Pin.IN, Pin.PULL_UP)

# we create a counter to increment as we press
# and one to increment as we release
counter_pressed = 0
counter_released = 0
mode = 0 # default clock running
mode_names = ["run","set hour","set minute","set AM/PM"]
mode_count = len(mode_names)
now = localtime()
hours = now[3]
minutes = now[4]
am_pm = 0

# the following method (function) will be invoked
# when the button changes state
# the Button module expects a callback to handle 
# - pin number
# - event (Button.PRESSED | Button.RELEASED)
# the event contains a string 'pressed' or 'released'
# which can be used in your code to act upon
def button_mode_irq(button, event):
    global mode, hours, minutes
    if event == Button.PRESSED:
        mode +=1
        # cycle back to zero if greater than mode_count
        mode =  mode % mode_count
        print('new mode:', mode, mode_names[mode])

def button_next_irq(button, event):
    global mode, hours, minutes, am_pm
    if event == Button.PRESSED:
        if mode == 1:
            hours += 1
        if mode == 2:
            minutes += 1
        if mode == 3:
            if am_pm == 0:
                am_pm = 1
            else:
                am_pm = 0
        print('next button:', hours, minutes, am_pm)

def button_previous_irq(button, event):
    global mode, hours, minutes, am_pm
    if event == Button.PRESSED:
        if mode == 1:
            hours -= 1
        if mode == 2:
            minutes -= 1
        if mode == 3:
            if am_pm == 0:
                am_pm = 1
            else:
                am_pm = 0
        print('prev button:', hours, minutes, am_pm)

button_mode = Button(16, False, button_mode_irq, internal_pullup = True, debounce_time = 100)
button_next = Button(17, False, button_next_irq, internal_pullup = True, debounce_time = 100)
button_previous = Button(18, False, button_previous_irq, internal_pullup = True, debounce_time = 100)

print("year:", now[0], "month:", now[1], "day-of-month:", now[2], "hours", now[3], "minutes:", now[4])
while(True):
    button_mode.update()
    button_next.update()
    button_previous.update()

The Decrement Time

The following line need some explanation:

1
hour = ((hour - 2) % 12) + 1
Let me break down that line, which handles decrementing hours while staying within the 1-12 range:

Let's walk through it step by step:

  1. First, we subtract 2 from the current hour: (hour - 2)
  2. We subtract 2 (not 1) because we'll add 1 back at the end
  3. This shift is necessary because we want to work with 0-11 for the modulo operation

  4. Then we take modulo 12: % 12

  5. This ensures our number wraps around within 0-11
  6. For example, if hour was 1, then (1-2) = -1, and -1 % 12 = 11

  7. Finally, we add 1: + 1

  8. This shifts our range from 0-11 back to 1-12

Here's an example sequence to show how it works: - Starting at hour = 1: - (1 - 2) = -1 - -1 % 12 = 11 - 11 + 1 = 12 - Starting at hour = 12: - (12 - 2) = 10 - 10 % 12 = 10 - 10 + 1 = 11

This gives us the desired behavior of decrementing through the sequence: 12 → 11 → 10 → ... → 1 → 12

Updating the Display

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
from machine import Pin
from utime import localtime, sleep, ticks_ms, ticks_diff
import tm1637

# Pin setup
CLK_PIN = 0
DST_PIN = 1
PM_PIN = 25

mode_pin = Pin(16, Pin.IN, Pin.PULL_UP)
next_pin = Pin(17, Pin.IN, Pin.PULL_UP)
previous_pin = Pin(18, Pin.IN, Pin.PULL_UP)
# LED value 0 indicates AM, value 1 indicates PM
pm_pin = Pin(PM_PIN, Pin.OUT)

# Time state
now = localtime()
hour = now[3]
minute = now[4]
second = now[5]

tm = tm1637.TM1637(clk=Pin(CLK_PIN), dio=Pin(DST_PIN))

mode = 0
mode_names = ["run", "set hour", "set minute", "set AM/PM"]
mode_count = len(mode_names)

# Debounce state
last_mode_press = 0
last_next_press = 0
last_prev_press = 0
DEBOUNCE_MS = 100

def format_time():
    return f"{hour:d}:{minute:02d}:{second:02d} {'PM' if is_pm else 'AM'}"

def set_pm():
    if hour < 12:
        is_pm = False
        pm_pin.value(0)
    else:
        is_pm = True
        pm_pin.value(1)

def handle_mode(pin):
    global mode, last_mode_press
    current_time = ticks_ms()
    if ticks_diff(current_time, last_mode_press) > DEBOUNCE_MS:
        mode = (mode + 1) % mode_count
        print(f"Mode: {mode_names[mode]}")
        last_mode_press = current_time

def handle_next(pin):
    global hour, minute, is_pm, last_next_press
    current_time = ticks_ms()
    if ticks_diff(current_time, last_next_press) > DEBOUNCE_MS:
        if mode == 1:  # Set hour
            hour = (hour % 12) + 1
        elif mode == 2:  # Set minute
            minute = (minute + 1) % 60
        elif mode == 3:  # Toggle AM/PM
            is_pm = not is_pm

        if mode != 0:
            print(format_time())
        last_next_press = current_time

def handle_previous(pin):
    global hour, minute, is_pm, last_prev_press
    current_time = ticks_ms()
    if ticks_diff(current_time, last_prev_press) > DEBOUNCE_MS:
        if mode == 1:  # Set hour
            hour = ((hour - 2) % 12) + 1
        elif mode == 2:  # Set minute
            minute = (minute - 1) % 60
        elif mode == 3:  # Toggle AM/PM
            is_pm = not is_pm

        if mode != 0:
            print(format_time())
        last_prev_press = current_time

# numbers modified to not dispaly the leading zero
def numbers_nlz(num1, num2, colon=True):
    """Display two numeric values -9 through 99, with a leading space before
    single-digit first numbers and separated by a colon."""
    num1 = max(-9, min(num1, 99))
    num2 = max(-9, min(num2, 99))
    prefix = ' ' if num1 < 10 else ''
    # print(f'"{prefix}{num1:d}{num2:0>2d}"')
    segments = tm.encode_string(f'{prefix}{num1:d}{num2:0>2d}')
    if colon:
        segments[1] |= 0x80  # colon on
    tm.write(segments)

# Set up interrupts
mode_pin.irq(trigger=Pin.IRQ_FALLING, handler=handle_mode)
next_pin.irq(trigger=Pin.IRQ_FALLING, handler=handle_next)
previous_pin.irq(trigger=Pin.IRQ_FALLING, handler=handle_previous)

# Main loop
print("Clock started. Press mode button to change settings.")

while True:
    second = localtime()[5]
    if mode == 0:  # Only update display in run mode
        print(format_time())
        # flash the colon on and off every second
        if (second % 2): # modulo 2 will be true for odd numbers
            numbers_nlz(hour, minute, True)
        else:
            numbers_nlz(hour, minute, False)
        set_pm()
        sleep(1)