Clock/Stopwatch coding help

Hi

I have been working on a little first project and am almost there but stuck on what to change in the code. I have used this code as a basis and been trying to modify it to suit my project.

I want to have a clock displayed on my e-ink display (no date). When button a is pressed it draws a stopwatch underneath and pauses the clock while the stopwatch counts up. When the button is pressed again it clears and resets the stopwatch and resumes the clock at the correct time.
I have managed to get it to do almost that, except the stopwatch doesnā€™t clear after each second it counted. It just keep each drawn time on the screen and so quickly ends up with a mess of multiple drawn numbers over each other.

I didnā€™t want to paste all the code here and I canā€™t seem to attach the .py file, so how can I share the code?

Thanks in advance for the help.

Regards

Click the </> ā€œpreformatted textā€ option.
Then clip and past your code into it.
That will put your code in an easy to navigate code window.
I do it all the time when posting a wall of code. ;)

I will paste the code below. I am aware that there are likely a lot of mistakes or extra code that probably does nothing, but I am still a beginner and it mostly works as a I need it to so far.

Any advice or tips are appreciated to help me improve šŸ‘

import time
import machine
from picographics import PicoGraphics, DISPLAY_INKY_PACK


# Buttons
button_a = machine.Pin(12, machine.Pin.IN, pull=machine.Pin.PULL_UP)
button_b = machine.Pin(13, machine.Pin.IN, pull=machine.Pin.PULL_UP)
button_c = machine.Pin(14, machine.Pin.IN, pull=machine.Pin.PULL_UP)

# Display
graphics = PicoGraphics(DISPLAY_INKY_PACK)
WIDTH, HEIGHT = graphics.get_bounds()
graphics.set_update_speed(3)
graphics.set_font("serif")

# RTC
rtc = machine.RTC()

set_clock = False
stopwatch_running = False  # Flag for stopwatch state
stopwatch_seconds = 0
stopwatch_minutes = 0
stopwatch_hours = 0

# Timer for stopwatch updates
timer = machine.Timer()

# Managing stopwatch update timing
def update_stopwatch(timer):
    global stopwatch_seconds, stopwatch_minutes


    if stopwatch_running:
#       graphics.clear() - just makes the whole display go black constantly
        stopwatch_seconds += 1
        if stopwatch_seconds == 60:
            stopwatch_seconds = 0
            stopwatch_minutes += 1
        draw_stopwatch()  # Update display to reflect stopwatch changes


# Button handling function
def button(pin):
    global stopwatch_running, stopwatch_seconds, stopwatch_minutes

    time.sleep(0.01)
    if pin.value():
        return

    if pin == button_a:
        if stopwatch_running:
            stopwatch_running = False  # Stop the stopwatch
            stopwatch_seconds = 0  # Reset counter
            stopwatch_minutes = 0
            stopwatch_hours = 0
            
        else:
            stopwatch_running = True  # Start the stopwatch
            draw_clock()

    draw_clock()


button_a.irq(trigger=machine.Pin.IRQ_FALLING, handler=button)

# Main clock display draw function
def draw_clock():
    hms = "{:02}:{:02}:{:02}".format(hour, minute, second)

    hms_width = graphics.measure_text(hms, 1.8)
    hms_offset = int((WIDTH / 2) - (hms_width / 2))
    h_width = graphics.measure_text(hms[0:2], 1.8)
    mi_width = graphics.measure_text(hms[3:5], 1.8)
    mi_offset = graphics.measure_text(hms[0:3], 1.8)

    graphics.set_pen(15)
    graphics.clear()
    graphics.set_pen(0)

    # No "thickness" setting in PG so, uh, fake it!
    graphics.text(hms, hms_offset, 40, scale=1.8)
    graphics.text(hms, hms_offset, 41, scale=1.8)
    graphics.text(hms, hms_offset + 1, 40, scale=1.8)
    graphics.text(hms, hms_offset - 1, 40, scale=1.8)
    
    graphics.update()

# Main stopwatch display draw function
def draw_stopwatch():
        
    stopwatch_text = f"{stopwatch_hours:02}:{stopwatch_minutes:02}:{stopwatch_seconds:02}"
    stopwatch_width = graphics.measure_text(stopwatch_text, 1.0)
    stopwatch_offset = int((WIDTH / 2) - (stopwatch_width / 2))
    
#   graphics.clear_area(stopwatch_offset, 100, stopwatch_width, 40)  -  just refreshes nothing in the stopwatch space
    
    graphics.text(stopwatch_text, stopwatch_offset, 100, scale=1.0)
    graphics.text(stopwatch_text, stopwatch_offset, 101, scale=1.0)

    graphics.update()


year, month, day, wd, hour, minute, second, _ = rtc.datetime()


last_second = second

# Start the timer with a period of 1 second
timer.init(period=1000, mode=machine.Timer.PERIODIC, callback=update_stopwatch)


while True:
    if not set_clock:
        # Get current time only for clock display, not stopwatch
        if not stopwatch_running:
            year, month, day, wd, hour, minute, second, _ = rtc.datetime()
        if second != last_second:
            draw_clock()
            last_second = second
       
    time.sleep(0.01)


A lot of my code is borrowed (clip & pasted) from examples etc. No shame in doing that, IMHO. I have lots of code that I know what it does, but have no idea how it does it. ;)

Only just got up, 6 AM here. Will have a good look see in an hour or so after my morning walk with my dog.

1 Like

Fundamentally, the problem is that your draw_stopwatch routine doesnā€™t clear the space itā€™s going to draw the new digits, so obviously you end up with a mess with all the numbers written over each other.

Youā€™ll notice draw_clock makes a call to clear the screen; you probably canā€™t do that if you want to preserve the (frozen) clock when running the stopwatch, but you can just draw a solid rectangle over the stopwatch display, in the background colour, to do the same job.

I tried adding graphics.clear() and display.clear() in various locations in the code (including in the draw_stopwatch() function) to see if it would clear the screen before drawing a new call, but it either did nothing or made the whole display flash black.

Do you have any suggestions on what specifically I could add, and where?

The clear function clears the entire screen to the current pen colour - so for example, in the draw_clock function:

    graphics.set_pen(15)
    graphics.clear()
    graphics.set_pen(0)

sets the pen to 15 (white I guess?), ā€œclearsā€ the screen to that colour, and then sets it back to 0 (black) to draw the clock.

Actually it looks like you have a commented out clear_area in the stopwatch draw function (although Iā€™m not entirely sure that function exists!), but that would be where you need to blank out the stopwatch area. So, thatā€™s a good location for something like:

    graphics.set_pen(15)
    graphics.rectangle(x, y, width, height)
    graphics.set_pen(0)

(obviously replacing x, y, width and height with the actual area you need clearing)

(Iā€™m 90% sure that shapes in PicoGraphics are solid; if not you might need to do a bit more drawing thereā€¦

Thanks iā€™ll give that a try. Could you clarify if the x and y is the starting coordinate to draw the box? X would be the distance across right from the bottom left and y would be the distance up from the bottom left?

Ok, now I have added the same block as in the draw_clock function to the draw_stopwatch function and it seems to work, only now it clears the clock when correctly showing the stopwatch šŸ˜‚

Any ideas? lol

import time
import machine
from picographics import PicoGraphics, DISPLAY_INKY_PACK


# Buttons
button_a = machine.Pin(12, machine.Pin.IN, pull=machine.Pin.PULL_UP)
button_b = machine.Pin(13, machine.Pin.IN, pull=machine.Pin.PULL_UP)
button_c = machine.Pin(14, machine.Pin.IN, pull=machine.Pin.PULL_UP)

# Display
graphics = PicoGraphics(DISPLAY_INKY_PACK)
WIDTH, HEIGHT = graphics.get_bounds()
graphics.set_update_speed(3)
graphics.set_font("serif")

# RTC
rtc = machine.RTC()

set_clock = False
stopwatch_running = False  # Flag for stopwatch state
stopwatch_seconds = 0
stopwatch_minutes = 0
stopwatch_hours = 0

# Timer for stopwatch updates
timer = machine.Timer()

# Managing stopwatch update timing
def update_stopwatch(timer):
    global stopwatch_seconds, stopwatch_minutes


    if stopwatch_running:
        stopwatch_seconds += 1
        if stopwatch_seconds == 60:
            stopwatch_seconds = 0
            stopwatch_minutes += 1
        draw_stopwatch()  # Update display to reflect stopwatch changes


# Button handling function
def button(pin):
    global stopwatch_running, stopwatch_seconds, stopwatch_minutes

    time.sleep(0.01)
    if pin.value():
        return

    if pin == button_a:
        if stopwatch_running:
            stopwatch_running = False  # Stop the stopwatch
            stopwatch_seconds = 0  # Reset counter
            stopwatch_minutes = 0
            stopwatch_hours = 0
            
        else:
            stopwatch_running = True  # Start the stopwatch
            draw_clock()

    draw_clock()


button_a.irq(trigger=machine.Pin.IRQ_FALLING, handler=button)

# Main clock display draw function
def draw_clock():
    hms = "{:02}:{:02}:{:02}".format(hour, minute, second)

    hms_width = graphics.measure_text(hms, 1.8)
    hms_offset = int((WIDTH / 2) - (hms_width / 2))
    h_width = graphics.measure_text(hms[0:2], 1.8)
    mi_width = graphics.measure_text(hms[3:5], 1.8)
    mi_offset = graphics.measure_text(hms[0:3], 1.8)

    graphics.set_pen(15)
    graphics.clear()
    graphics.set_pen(0)

    # No "thickness" setting in PG so, uh, fake it!
    graphics.text(hms, hms_offset, 40, scale=1.8)
    graphics.text(hms, hms_offset, 41, scale=1.8)
    graphics.text(hms, hms_offset + 1, 40, scale=1.8)
    graphics.text(hms, hms_offset - 1, 40, scale=1.8)
    
    graphics.update()

# Main stopwatch display draw function
def draw_stopwatch():
        
    stopwatch_text = f"{stopwatch_hours:02}:{stopwatch_minutes:02}:{stopwatch_seconds:02}"
    stopwatch_width = graphics.measure_text(stopwatch_text, 1.0)
    stopwatch_offset = int((WIDTH / 2) - (stopwatch_width / 2))
    
    graphics.set_pen(15)
    graphics.clear()
    graphics.set_pen(0)
    
    graphics.text(stopwatch_text, stopwatch_offset, 100, scale=1.0)
    graphics.text(stopwatch_text, stopwatch_offset, 101, scale=1.0)

    graphics.update()


year, month, day, wd, hour, minute, second, _ = rtc.datetime()


last_second = second

# Start the timer with a period of 1 second
timer.init(period=1000, mode=machine.Timer.PERIODIC, callback=update_stopwatch)


while True:
    if not set_clock:
        # Get current time only for clock display, not stopwatch
        if not stopwatch_running:
            year, month, day, wd, hour, minute, second, _ = rtc.datetime()
        if second != last_second:
            draw_clock()
            last_second = second
       
    time.sleep(0.01)


Yes, as I said, clear will clear the whole screen, which obviously includes the clock.

And yes, x and y are the starting coordinates of the rectangle - see the manual for a fuller description (including a mention of using rectangles to clear portions of the screen!)

x is the distance across; I honestly canā€™t remember if y is measure up from the bottom or down from the top of the screen - Iā€™d guess top down, but I fiddle with stuff that draws from both ways so I usually get it wrong first time :-)

Itā€™s top down, near as I can remember. And it draws a solid shape / fills it with that color.

setting this as the code sorted it out -

    graphics.set_pen(15)
    graphics.rectangle(100,80,WIDTH,HEIGHT)
    graphics.set_pen(0)

Thanks for helping me sort that.

Now I just need to try and clean it up and figure out what is doing nothing and can be removed šŸ˜‚

I have a follow up query that I would appreciate your input on.

It works fine when I am connected to my pc and linked to Thonny. When is unplug the pico and reconnect a standard wall charger musb lead it starts up but starts the clock at 00:00:00.

Its like the rtc module needs to be connected to the pc to generate the correct time.

I have been using a pico h but I also have a pico w. Would it be easier to code getting the time from wifi instead of the rtc module, or is there a simple reason why the rtc module might not be displaying the correct time when plugged into wall power?

If the pico w is a better way to go, how would I integrate connecting to the wifi with my current script. Would I need a separate file, or could the code just also be in my main script?

Thanks in advance.

When you use Thonny, it helpfully sets the clock time on your Pico. When itā€™s just getting power from your wall plug, it doesnā€™t have anything to tell it - so yeah, the easiest solution (especially as you already have one!) is to get the PicoW to pick the time up from the network.

I confess Iā€™ve not fiddled with NTP in MicroPython (had to read the specs and work it out in C++ā€¦), but it looks like there are some good examples that do it - such as pimoroni-pico/micropython/examples/cosmic_unicorn/today.py at main Ā· pimoroni/pimoroni-pico Ā· GitHub