Intermittent SD card read/write failures and corrupted logs on OpenMV (EIO, ENOENT, file not found after save)

I’m experiencing several persistent issues with reading and writing files to the SD card on my OpenMV RT1062 camera. I’ve encountered multiple error types that seem related to storage reliability, and I’m hoping someone can help identify the root cause.

Issue 1: File Not Found Immediately After Saving

I’m saving a raw image to the SD card and then immediately trying to read it back, but I get a “Could not find the file” error. Here’s the relevant code:
```
try:
    raw_path = f"{MY_IMAGE_DIR}/{my_addr}_{event_epoch_ms}_raw.jpg"
    logger.debug(f"Saving raw image to {raw_path} : imbytesize = {len(img.bytearray())}")
    img.save(raw_path)
    utime.sleep_ms(500)
    logger.info(f"Saved raw image: {raw_path}: raw size = {len(img.bytearray())} bytes")
except Exception as e:
    logger.warning(f"[PIR] Failed to save raw image: {e}")
    continue

# read raw file
try:
    img = image.Image(raw_path)
    imgbytes = img.bytearray()
    logger.info(f"[PIR] Captured image, size: {len(imgbytes)} bytes")
except Exception as e:
    logger.error(f"[PIR] Failed read image file: {e}")
    continue
```

The logs show:
```
19:54:13,489 - INFO - [PIR] Motion detected - (interrupt) capturing image...
19:54:13,665 - INFO - Saved raw image: /sdcard/myimages/221_1381780453000_raw.jpg: raw size = 1843200 bytes
19:54:13,670 - ERROR - [PIR] Failed read image file: Could not find the file

The file appears to save successfully (1.8MB), but reading it back immediately fails. I’ve added a 500ms delay, but it doesn’t help.
Issue 2: EIO Error When Reading Encrypted Files

When trying to read previously saved encrypted files, I sometimes get an EIO (Input/Output Error):
```
try:
    logger.info(f"[IMG] Reading encrypted image of creator: {creator}, file: {enc_filepath}")
    with open(enc_filepath, "rb") as f:
        enc_msgbytes = f.read()
    logger.info(f"[IMG] Read encrypted image of creator: {creator}, file: {len(enc_msgbytes)} bytes")
except Exception as e:
    logger.error(f"[IMG] Failed to read encrypted image from file, image re-queued {enc_filepath}, e: {e}")
    imgpaths_to_send.append(img_entry)
    break
```

Log output:
```
20:10:16,897 - INFO - [IMG] Reading encrypted image from file: /sdcard/myimages/221_1381780661000.enc
20:10:16,917 - ERROR - [IMG] Failed to read encrypted image from file, image re-queued /sdcard/myimages/221_1381780661000.enc, e: [Errno 5] EIO
```

**Issue 3: ENOENT Error (File Not Found)**

Sometimes the same code produces ENOENT errors for files that should exist:
```
10:40:39,240 - INFO - [IMG] Reading encrypted image of creator: 221, file: /sdcard/myimages/221_1381833132000.enc
10:40:39,247 - ERROR - [IMG] Failed to read encrypted image from file, image re-queued /sdcard/myimages/221_1381833132000.enc, e: [Errno 2] ENOENT
**Issue 4: Corrupted Log Output**

Occasionally, my log messages become severely corrupted with repeated special characters, making debugging difficult. Here's an example:
```
20:21:10,616 - INFO - [⋙ sending....] dest=65535, msg_typ:N, len:1 bytes, single packet
20:21:10,770 - INFO - [⮕ SENT to 65535] [*] 9 bytes, MSG_UID = b'N\xdd�������������������������... [thousands of corrupted characters continue]

@anand - Can you provide me with a minimal example script that demonstrates the issue? I need something to debug with. But, you need to remove all excess code from it except for what’s required to generate the bug.

Hey @kwagyeman, Our codebase has grown significantly, and due to recent refactoring, it has become difficult to reliably reproduce the same error in the updated code. Could you please help by explaining the possible root causes of this error? Additionally, it would be helpful if you could outline the key points or considerations we should keep in mind to eliminate or prevent this issue going forward.

Hi, the behavior you are experiencing is typically memory corruption. So, there’s a method, etc. that’s bugged in either OpenMV’s modules or MicroPython. To fix this, you need to be able to reproduce the bug. Unfortunately, these are the hardest bugs to fix as they are challenging to find.

The most reliable way to find the issue is to strip down the code until it works without issues, then re-add features until you see failures.

We take these bus very seriously and will endeavor to fix them, however, you need to provide me with the most reduced example script that causes the problem.

This one is a differnet storage Issue :

I’m experiencing image corruption when saving to the SD card on OpenMV RT1062. Images saved with img.save() and binary files written to the SD card become corrupted and can’t be read back. The issue persists even with os.sync() and write delays. I’ve created a test repository with reproduction scripts: GitHub - anand2532/watchmen-image-loop . Any help would be appreciated.

Hi anand,

Memory corruption typically causes these types of issues.

While I appreciate you posting your entire project on github… I can’t debug all of that for you. Please reduce the lines of code to create the error to the most minimal possible script.

I.e, something like:

i = 0
while(True):
   img = sensor.snapshot()
   img.save("image-%d.jpg", i)
   i += 1

You need to be able to reduce it down to one line of code, or a package that, when added, breaks things. Then I can debug and fix it.

Hi Kwabena,

Thanks for your reply. Here is the reduced code. (one file)

The issue we are facing is that we can save images in flash memory, but when saving to the SD card memory, most of the photos become corrupt. A photo of the corrupted images is also attached. We have tried replacing the new SD card multiple times, but either this saving method is incorrect or there’s an issue with how the memory card is being used.

Please check what the issue is.

Steps to reproduce: Use an external SD card as storage, run the code below for 10-20 images (or longer), and check the saved images. (If the issue doesn’t occur initially, try ejecting the system 2-3 times and rerun the code).

import os
import gc
import utime
import machine
import sensor

# Initialize RTC
rtc = machine.RTC()
rtc.datetime((2025, 1, 1, 0, 0, 0, 0, 0))

# Setup filesystem
def get_fs_root():
    try:
        os.listdir('/sdcard')
        return "/sdcard"
    except OSError:
        print("SD card not found, using /flash")
        return "/flash"

def create_dir(path):
    try:
        parts = [p for p in path.split('/') if p]
        parent = '/' + '/'.join(parts[:-1])
        if parts[-1] not in os.listdir(parent):
            os.mkdir(path)
    except OSError as e:
        print(f"Failed to create {path}: {e}")

FS_ROOT = get_fs_root()
MY_IMAGE_DIR = f"{FS_ROOT}/myimages"
create_dir(MY_IMAGE_DIR)

# Initialize camera
sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.HD)

def get_epoch_ms():
    return utime.time_ns() // 1_000_000

# Main capture loop
def main():
    capture_count = 0
    SLEEP_TIME = 2
    print("Starting image capture...")

    while True:
        img = None
        try:
            capture_count += 1
            print(f"Capturing image #{capture_count}")
            
            img = sensor.snapshot()
            raw_path = f"{MY_IMAGE_DIR}/{get_epoch_ms()}_raw.jpg"
            img.save(raw_path)
            utime.sleep_ms(1600)
            os.sync()
            utime.sleep_ms(1600)
            print(f"Saved: {raw_path}")
            
        except Exception as e:
            print(f"Error: {e}")
        finally:
            if img:
                del img
            gc.collect()
            utime.sleep_ms(1600)
        
        utime.sleep(SLEEP_TIME)

if __name__ == "__main__":
    main()

Thank You

Hi, I can follow the script now.

So, the script as you wrote it doesn’t have any way to stop writing… so, when you power off the camera to see the disk the camera may be in the middle of a write. If it’s updating the sd card, this will corrupt the FAT.

Just to be clear here… when you write to a FAT file system, chunks of the file are writen in 512 byte blocks up to a sector size. Once the sector size is hit, a data structure called the file allocation table (where the FAT name comes from) is updated which links the current sector number to point to the next free sector number in the FAT. Then blocks of the next sector are written.

If power is lost while the FAT is being updated, this corrupts multiple files and folders at the same time.

There are other types of file systems that with journaling (i.e., the ability to handle power off in the middle of writes) like LittleFS that work with MicroPython, but, such file systems aren’t universally understood by Windows, Mac, and Linux.

DSLRs and other devices which record to FAT SD cards avoid this issue by just controlling power on/off and when they are writing.

I modified the script like so and all images are fine:

import os
import gc
import utime
import machine
import sensor

# Initialize RTC
rtc = machine.RTC()
rtc.datetime((2025, 1, 1, 0, 0, 0, 0, 0))

# Setup filesystem
def get_fs_root():
    try:
        os.listdir('/sdcard')
        return "/sdcard"
    except OSError:
        print("SD card not found, using /flash")
        return "/flash"

def create_dir(path):
    try:
        parts = [p for p in path.split('/') if p]
        parent = '/' + '/'.join(parts[:-1])
        if parts[-1] not in os.listdir(parent):
            os.mkdir(path)
    except OSError as e:
        print(f"Failed to create {path}: {e}")

FS_ROOT = get_fs_root()
MY_IMAGE_DIR = f"{FS_ROOT}/myimages"
create_dir(MY_IMAGE_DIR)

# Initialize camera
sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.HD)

def get_epoch_ms():
    return utime.time_ns() // 1_000_000

# Main capture loop
def main():
    capture_count = 0
    SLEEP_TIME = 2
    print("Starting image capture...")

    for i in range(100):
        img = None
        try:
            capture_count += 1
            print(f"Capturing image #{capture_count}")
            
            img = sensor.snapshot()
            raw_path = f"{MY_IMAGE_DIR}/{get_epoch_ms()}_raw.jpg"
            img.save(raw_path)
            utime.sleep_ms(1600)
            os.sync()
            utime.sleep_ms(1600)
            print(f"Saved: {raw_path}")
            
        except Exception as e:
            print(f"Error: {e}")
        finally:
            if img:
                del img
            gc.collect()
            utime.sleep_ms(1600)
        
        utime.sleep(SLEEP_TIME)
        print(i)

if __name__ == "__main__":
    main()

After it finished I reset the camera from the IDE, coppied the images over, and all were not corrupt.