Display corruption

Hi all!

Been trying to get to grips with Python and the Inky Frame! I have an intermittent issue with display corruption like the attached photo. It happens every few days after running flawlessly and when it gets into this state, I need to do a full power reset to resolve the issue (if I nuke and flash the base firmware, with examples, the issue appears on the default main script too - I have to disconnect the USB & battery to resolve the issue).

I assume that my script itself isn’t directly buggy, but there is a memory issue somewhere.

Anyone got any ideas what I should be doing to try and diagnose the problem?

Not got the code handy to share - the script downloads an image from Dropbox, syncs with an iCal and gets the weather data online, updating the display 6 times a day.

Any help appreciated!

How exactly are you running your script? Does it use one of the InkyFrame sleep-methods? The reason I am asking: the InkyFrame sleep-methods actually don’t sleep, but turn off power - but only if you are running from batteries. So this is like a full reset. If instead you just use normal Python sleep or are running from USB, the program runs and runs and memory problems can indeed add up.

Thanks for the feedback! I’m calling inky_frame.sleep_for(), which I think is the right sleep method for Inky on battery power?

It had been a while since I checked, but I also get this when I try using an SD card (normally I use dropbox. The SD card was the one that came with the Inky frame and the error persists after flashing the nuke image to the pico).

SD card mount failed: timeout waiting for v2 card
WARNING: SD card mount failed but IMAGE_SOURCE is SD_CARD
Error reading SD card: [Errno 2] ENOENT

Was going to upload my source code, but the forums here only allow uploading graphics

inky_frame.sleep_for() is correct. But it only kicks in if you don’t connect USB. In your original post you talk about removing USB, so I am not sure if you are really running from battery.

You can post your code as text: just embed it in triple-backslashes (before and after the code). This way it is cleanly formatted.

I normally run off of battery power, the USB comment was just mentioned as part of my debugging process, but I appreciate that wasn’t clear! The entire script is over 3000 lines long, so might not make sense to paste it all(!). I’ll try to get a dropbox link later, but here is my main function for now:

\\\

def main():
global _is_connected, _has_time, IMAGE_SOURCE
print(“Starting Inky Frame Calendar Display”)
force_garbage_collection()
_safe_remove_clip()
display.set_pen(WHITE)
display.clear()
cleanup_temp_files()

# Retry WiFi connection
for attempt in range(MAX_WIFI_RETRIES):
    if connect_wifi():
        _is_connected = True
        break
    else:
        print(f"WiFi attempt {attempt + 1} failed. Retrying in {RETRY_DELAY} seconds...")
        time.sleep(RETRY_DELAY)
else:
    print("WiFi unavailable after retries – switching to SD card mode")
    _is_connected = False
    IMAGE_SOURCE = "SD_CARD"

# Mount SD card if needed, with fallback to Dropbox if WiFi is available
if IMAGE_SOURCE == "SD_CARD":
    if not mount_sd_card():
        print("WARNING: SD card mount failed but IMAGE_SOURCE is SD_CARD")
    # If SD still not present, fall back to Dropbox when WiFi is up
    try:
        os.statvfs("/sd")
    except Exception:
        if _is_connected:
            print("SD not available; falling back to DROPBOX")
            IMAGE_SOURCE = "DROPBOX"

# Sync time via NTP (+ simple UK DST adjustment)
try:
    import ntptime
    ntptime.settime()
    rtc = machine.RTC()
    utc_time = rtc.datetime()
    year, month, day = utc_time[0], utc_time[1], utc_time[2]
    hour = utc_time[4]
    if is_dst_active(year, month, day):
        hour += 1
        if hour >= 24:
            hour -= 24
            day += 1
    adjusted_time = (year, month, day, utc_time[3], hour, utc_time[5], utc_time[6], 0)
    rtc.datetime(adjusted_time)
    _has_time = True
    print("Time synchronized and adjusted for UK timezone")
except Exception as e:
    _has_time = False
    print("NTP sync failed:", e)

force_garbage_collection()

# ---------- MOVE IMAGE SELECTION/DECODE EARLY ----------
image_path = get_random_image()
if image_path:
    print("Using image:", image_path)
else:
    print("No image available")
force_garbage_collection()

# ---------- Fetch weather and calendar AFTER the image ----------
weather_data = None
week_events = {}
if _is_connected:
    weather_data = get_weather_forecast()
    force_garbage_collection()
    week_events = get_week_agenda_ics()
    force_garbage_collection()
else:
    print("Skipping online data fetch (no connection)")

# Ensure today's weather is available (snapshot fallback)
try:
    now = time.localtime()
    today_str = "{:04d}{:02d}{:02d}".format(now[0], now[1], now[2])
    if not weather_data or today_str not in weather_data:
        print("Today's weather missing – attempting snapshot fallback")
        snap = get_current_weather_snapshot()
        if snap:
            weather_data = weather_data or {}
            weather_data[today_str] = snap
            print("Using current weather snapshot for", today_str)
        else:
            print("Snapshot fallback failed")
except Exception as e:
    print("Header weather fallback failed:", e)

# Moon phases
moon_phase = None
if _is_connected:
    moon_phase = get_week_moon_phases_weatherapi(HOME_LON, HOME_LAT)
    force_garbage_collection()

# Draw image and agenda
try:
    agenda_x = draw_image(image_path, 0, 0, WIDTH, HEIGHT)
    print("Image width used / agenda starts at x:", agenda_x)
except Exception as e:
    print("Error drawing image:", e)
    agenda_x = 10

# Generate AI or local summary
try:
    available_width = WIDTH - agenda_x - 10
    ai_summary = generate_ai_summary(week_events, weather_data, moon_phase, available_width)
except Exception as e:
    print("Error generating summary:", e)
    ai_summary = "Unable to generate summary."

# Draw agenda pane & update display
try:
    draw_agenda_summary(agenda_x, ai_summary, weather_data)
    display.update()
    print("Display updated successfully")
except Exception as e:
    print("Error updating display:", e)
    try:
        import sys
        sys.print_exception(e)
    except Exception:
        pass

# Final cleanup
weather_data = None
week_events = None
moon_phase = None
force_garbage_collection()
cleanup_temp_files()
force_garbage_collection()

# Sleep scheduling
try:
    sleep_minutes = calculate_sleep_time()
    print(f"Entering deep sleep for {sleep_minutes} minutes")
    inky_frame.sleep_for(sleep_minutes)
except Exception as e:
    print("Error calculating sleep time:", e)
    print("Sleeping for default 60 minutes")
    inky_frame.sleep_for(60)
\\\

My bad, I meant back-ticks not backslash for code-formatting.

Anyhow, this code looks ok. The inky_frame.sleep_for() will turn off the device. So if you see your problem once in a while it has nothing to do with the system running for too long.

So I would not blame the code. I could imagine two problems: power or a hardware problem. Before writing to support, you should check your battery level (VSYS). I am also using an InkyFrame and one information I always put on the screen is the current battery level to have an idea about when it is time to change/recharge the LiPo.

Interesting, I hadn’t been thinking about the battery pack. I’m using fresh Lithium rechargeable AAs. I believe Lithium batteries deliver a voltage similar to Alkaline Vs NiMH, which I think deliver 0.3V less typically.

I did a quick look at the docs for the Inky Frame and didn’t see how to get the VSYS value and what the thresholds should be (i.e. for good, bad, brownout levels etc)? Have you got sample code I could try?

There are many types of Lithium rechargeable AAs. Some deliver 1.5V, others nominal 3.7V and fully charged 4.2V. If you use a pair of the latter, you are far out of specs. Maybe you can link to your batteries? You can also check them with a digital volt meter. You will then have the no-load voltage, which is ok for detecting the type you are using.

From the software side, see Read system voltage on Pico W and estimate battery percentage · GitHub. This will give you roughly the voltage that the system is running with. Note that the percentage calculation in this gist is not valid, since there is no linear relationship between voltage and capacity. But i is fine for a first crude approximation.

Thanks, that helps! I assume that code would get the VSYS of the cable if plugged in? So I couldn’t use the prints in Thonny of the voltage for the test?

I have two brands of lithium batteries - both say they are 1.5V, but I haven’t done a manual reading to check their claims:
Zepath 4 x AA Li-ion Rechargeable Batteries, Lithium 3600mWh High Capacity aa Battery, Low Self-discharge 1.5V double a batteries, with Battery Protection Box: Amazon.co.uk: Electronics & Photo
8-Pack 1.5V AA Rechargeable Batteries,Kratax 3500mWh Lithium AA Battery,1600 Cycles Doulbe A Li-ion Battery,Max 3A Discharge[Charger not include]: Amazon.co.uk: Electronics & Photo

Although I suspect an issue with my setup than the hardware, given it’s new, I guess the battery supply wouldn’t account for the SD card issue? That happens even when the Pico is plugged in.

Yes, when you power from USB, your VSYS will be VBUS-0.1V (they are internally connected with a Schottky diode that reduces the voltage a bit). I read VSYS and put it on the screen, so I can also see battery voltages.

Regarding the rechargeable batteries: interesting, I did not know that something like this is available. It seems they have an internal step-down converter to 1.5V. That’s not the most efficient solution, but that should not bother you. I will see if I can order some in Germany to test them.

One thing you should know: usually it is not a good idea to put rechargeable batteries/cells in series. Due to different internal resistance, one cell tends to discharge into the other. For my weather station I use a pair of NiMH and it turns out that when the batteries fail one is almost empty and the other one not.

That is a valid point. The whole exercise is only to rule out some possible problems. If you can rule out the power-supply, then I recommend that you write to support and ask for a replacement, linking to this thread to show that you did a solid analysis.

The lithium batteries seem pretty reliable in my outdoor security cameras - I can normally get 6 months - 1 year from them. I don’t doubt that one might discharge into another, but if I’m getting a decent lifetime from them, it’s better than burning through throw away alkalines.

Based on the code shared, I modified my script to write the voltage levels each time the frame runs to a text file - this will stop me having to do spot checks. I’ve also written code to draw a battery low icon when VSYS drops below 3.5v - I’ve tested the icon will draw fine, but I’ve not seen it in normal operation (the frame has had around 3 refreshes since I wrote the code), so VSYS should still be reading >3.5V. I’ll collect data for a few more days and then figure out what to do next. The script will keep writing lines to the txt file, so I’ll need to disable the logging at some point!

Very good! So I will stay tuned.

I did some research on these batteries. A very (very!) complete write up is here: Mega Review: XTAR 4150mWh 2500mAh 1.5V Rechargeable Li-Ion AA Battery & L4 USB-C Charger | Gough's Tech Zone

These batteries are good for devices that need 1.5V (or a multiple of that).

But I do think these AAs are not suitable for the InkyFrame: you have two AA cells, each with an internal Li-ion cell at 4.2V-3.0V. The internal step down will convert this to 1.5V. With two cells, you are back to 3V. The buck-boost converter in the Pico-W will then convert these 3V again back to 3.3V.

Since the Pico-W can handle the standard range of Li-ion cells without any problems, it would be much more efficient to just plug a single-cell Li-ion directly into the InkyFrame.

This is just a side note, it is not related to your problem.

The battery pack uses 3 AAs, so I guess I should get 4.5V from ‘fresh’ batteries. Thanks for all this detail though, learning lots!

The display corruption happened again. Got a log of vsys for a number of runs at-least:
2025-10-21 | 18:02:36 | 4.46V
2025-10-21 | 21:01:57 | 4.34V
2021-01-01 | 00:00:59 | 4.33V
2025-10-22 | 12:00:56 | 4.34V
2025-10-22 | 15:00:46 | 4.33V
2025-10-22 | 18:00:58 | 4.40V
2025-10-22 | 21:21:15 | 4.34V
2025-10-23 | 06:01:34 | 4.34V
2025-10-23 | 09:00:53 | 4.33V
2025-10-23 | 12:01:13 | 4.46V
2025-10-23 | 15:00:52 | 4.32V

As far as I can tell, those are relatively healthy voltages?

Absolutely! I don’t know what else to try, so I think it is the time to write to support and ask for a replacement.