XIAO RP2350 with Micropython v1.24.0-preview.201.g269a0e0e1 does not start main.py

Hi Members!
In Micropython is is normal that after a reset the following files are executed in the following order:

1) boot.py
2) main.py

My XIAO RP2350 is flashed with:
MicroPython v1.24.0-preview.201.g269a0e0e1 on 2024-08-09; Raspberry Pi Pico2 with RP2350.

After a reset of this device only boot.by is executed.
The script main.py is not executed.
boot.py mounts succssfully the SDCard that is in the Seeed Expansion Board Base onto which the XIAO RP2350 is placed. At the end of boot.py the current directory is changed to the root directory of the device.

When I run main.py from within Thonny “by hand” this script runs without problem.
The file main.py has 308 lines of code. It is 11 kBytes in length.
In the XIAO RP2350’s memory there is a folder /lib that has only sdcard.py in it.
The length of sdcard.py has 306 lines of code. It is 11 kBytes in length.
In the root folder (‘/’) there are only boot.py (length 2 kBytes) and main.py.
All other needed library modules are in the SDCard in /sd/lib.

Here is the code of boot.py

# boot.py
# created for Seeed XIAO RP2350 with Seeed Epansion Board Base
# for MicroPython
# 2025-05-05 by Paulus Schulinck (Github handle: @PaulskPt)
# The idea is to call this script after booting
# and then use library files that are on the SDCard in folder /sd/lib
#
import machine
import os
import time
import sys
from lib.sdcard import SDCard

my_debug = False

fileTypeDir  = 0x4000
fileTypeFile = 0x8000

PIN_SD_SCK  = machine.Pin.board.GP2
PIN_SD_MOSI = machine.Pin.board.GP3
PIN_SD_MISO = machine.Pin.board.GP4
PIN_SD_CS   = machine.Pin.board.GP28 # A2/D2

# Setup for SD Card
sd_spi = machine.SPI(0, 
        sck=machine.Pin(PIN_SD_SCK,   machine.Pin.OUT),  # for Presto it was pin 34
        mosi=machine.Pin(PIN_SD_MOSI, machine.Pin.OUT),  # same, pin 35
        miso=machine.Pin(PIN_SD_MISO, machine.Pin.OUT))  # same, pin 36

def mount_sd():
    sd = SDCard(sd_spi, machine.Pin(PIN_SD_CS))       # same, pin 39

    ret = False

    try:
        # Mount the SD to the directory 'sd'
        os.mount(sd, "/sd")
        os.chdir("/sd")
        print("SDCard mounted")
        if my_debug:
            print(f"Current directory: \"{os.getcwd()}\"")
        os.chdir("/")  # go back to the root directory
        ret = True
    except OSError as exc:
        print("mounting SDCard failed")
        sys.print_exception(exc)
        # print(f"Error: {exc}")
        
    return ret

if __name__ == '__main__':
    mount_sd()

Here is the code of main.py:

# 
# Micropython script for a Seeed XIAO RP2350 attached to a Seeed Expansion Board Base
# Test to receive ntp unixtime from another device: Pimoroni Pico Plus 2 with RM2 module attached
# also display/print sensor values from either mcp9808 or bme280 (see global flags below).
# by Paulus Schulinck (Github handle: @PaulskPt)
# 2025-05-08
# License: MIT
# Note: script uses modules in /sd/lib. The script in boot.py tries to mount the SDCard
#
import struct
import array, random
from machine import Pin, UART
import rp2
import time
import utime
import os

use_bme280 = True
use_mcp9808 = False
my_debug = False

try:
    os.chdir('/sd')

    from lib.pcf8563 import *
    if use_mcp9808:
        from lib.mcp9808 import MCP9808
    if use_bme280:
        from lib.bme280_f import BME280
    from lib.ssd1306 import SSD1306_I2C
    tz_offset = 0
    from lib.secrets import TIMEZONE_OFFSET # get the local timezone offset from GMT
    tz_offset = int(TIMEZONE_OFFSET)
    print(f"TIMEZONE_OFFSET = {TIMEZONE_OFFSET}")
    
    os.chdir('/')
    
except OSError as exc:
    print(f"Error: {exc}")
    raise

PIN_WIRE1_SCL = machine.Pin.board.GP7
PIN_WIRE1_SDA = machine.Pin.board.GP6
i2c = machine.I2C(id=1, scl=machine.Pin(PIN_WIRE1_SCL), sda=machine.Pin(PIN_WIRE1_SDA), freq=400000)

if use_mcp9808:
    sensor = MCP9808(i2c) # create an instance of the MCP9808 sensor object
if use_bme280:
    bme280 = BME280(i2c=i2c)

oled = SSD1306_I2C(128, 32, i2c) # create an instance of the OLED object
 
rtc = PCF8563(i2c) # create an instance of the rtc object
if my_debug:
    print(f"type(rtc) = {type(rtc)}")

monthsLst = ["", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]

wdDict = {0: "Mon",
          1: "Tue",
          2: "Wed",
          3: "Thu",
          4: "Fri",
          5: "Sat",
          6: "Sun"}

brill = 10  # Let the RGB Led shine just a bit

RED   = (0,   brill,     0)
BLUE  = (brill,   0,     0)
GREEN = (0,       0, brill)
BLACK = (0,       0,     0)


uart = UART(0, 9600, tx = machine.Pin.board.GP0, rx= machine.Pin.board.GP1)
if my_debug:
    print(f"type(uart) = {type(uart)}")

local_time_lst = []
weekdayStr = ""
yearday = 0
#    yy, mo, dd, wd, hh, mm, ss
dt = (25, 5, 5, 0, 18, 20, 40) # pro-forma datetime tuple
rtc.DateTime(dt) # set the rtc
if my_debug:
    print(rtc.DateTime())

# setup for RGB Led:
NUM_LEDS = 1
LED_PIN = 22    # PICO_DEFAULT_WS2812_PIN
POWER_PIN = 23  # PICO_DEFAULT_WS2812_POWER_PIN

# Global brightness variable (0.0 to 1.0)
BRIGHTNESS = 0.1

@rp2.asm_pio(sideset_init=rp2.PIO.OUT_LOW, out_shiftdir=rp2.PIO.SHIFT_LEFT, autopull=True, pull_thresh=24)
def ws2812():
    T1 = 2
    T2 = 5
    T3 = 3
    wrap_target()
    label("bitloop")
    out(x, 1)               .side(0)    [T3 - 1]
    jmp(not_x, "do_zero")   .side(1)    [T1 - 1]
    jmp("bitloop")          .side(1)    [T2 - 1]
    label("do_zero")
    nop()                   .side(0)    [T2 - 1]
    wrap()

# Set up the power pin
power_pin = Pin(POWER_PIN, Pin.OUT)
power_pin.value(1)  # Turn on power to the LED

# Create the StateMachine with the ws2812 program, outputting on LED_PIN
sm = rp2.StateMachine(0, ws2812, freq=8_000_000, sideset_base=Pin(LED_PIN))

# Start the StateMachine, it will wait for data on its FIFO.
sm.active(1)

def set_led_color(color):
    #TAG = "set_led_color(): "
    color2 = 0
    if isinstance(color, tuple):
        color2 = color[0] | (color[1] << 8) | (color[2] << 16)
        #print(TAG + "color = ({:d},{:d},{:d})".format(color[0], color[1], color[2]))
        #print(TAG + "color2 = 0x{:02x}".format(color2))
    else:
        color2 = color
    sm.put(array.array("I", [color2]), 8)

#--- end of setup for RGB Led ---


# [2026, 5, 4, 6, 18, 20, 40]

def unix_to_rtc():
    global unixtime, tz_offset, local_time_lst, weekdayStr, yearday
    if unixtime > 0:
        #                                   (yy, mo, dd, hh, mm, ss, wd, yd)
        # example of time.localtime result: (2025, 5, 5, 18, 26, 38, 0, 125) 
        lt = time.gmtime(unixtime + (tz_offset * 3600) )
        #              yy    mo     dd     wd     hh     mm     ss
        local_time_lst = [lt[0], lt[1], lt[2], lt[6], lt[3], lt[4], lt[5]]
        #weekdayStr = wdDict[lt[2]]
        #yearday = lt[7]
        rtc.DateTime(local_time_lst)
        print(f"rtc updated from ntp: {rtc.DateTime}")
        

def update_fm_ntp():
    global unixtime, weekdayStr, yearday
    t1 = "Unable to get time from NTP server.\n\nCheck your network and try again."
    TAG = "update_fm_ntp(): "
    ret = False
    
    # Try to receive unixtime via UART from Pimoroni Pico Plus 2 (with RM2 WiFi/BT module connected)
    if not my_debug:
        print("\nWaiting to receive unixtime from Pimoroni Pico Plus 2")
    ux_val = None
    try_cnt = 0
    try_cnt_max = 100
    buflen = 10
    while True:
        rx_buf = bytearray(buflen) # create a clean buffer
        rx_buf = uart.read()
        # print(f"type(rx_buf) = {type(rx_buf)}")
        if isinstance(rx_buf, bytes):
            set_led_color(GREEN)
            print("unixtime data received via UART")
            # print(f"rx_buf = {rx_buf}, list(rx_buf) = {list(rx_buf)}")
            try:
                for i in range(3, 0, -1):
                    if rx_buf[i] == 0:
                        break
            except IndexError: # occurred when something erratically occurred,
                #                for instance that the other device was reset.
                continue
            if i > 0:
                if my_debug:
                    print(f"nr of bytes (with a value > 0) = {i}")
                # Convert bytearray back to integer
                ux_val = struct.unpack(">L", rx_buf)[0]  # 'L' is for a 32-bit integer (unsigned long)
                if my_debug:
                    print(f"rx_buf = {rx_buf}, ux_val = {ux_val}")
                if ux_val > 0:
                    break
                else:
                    try_cnt += 1
                    if my_debug:
                        print(f"try_cnt = {try_cnt}")
                    if try_cnt >= try_cnt_max:
                        print("receive of unixtime timed out")
                        break
        time.sleep(0.01)
    if ux_val > 0:
        unixtime = ux_val + (tz_offset * 3600)
        if my_debug:
            print(f"ux_val = {ux_val}, unixtime (+ timezone offset) = {unixtime}")
        #unix_to_rtc()
        gmtTime = utime.localtime(ux_val)
        loctime = utime.localtime(unixtime)
        if my_debug:
            print(f"gmtTime = {gmtTime}")
            print(f"loctime = {loctime}")
        upd_time = ( loctime[0], loctime[1], loctime[2],
                     loctime[6],
                     loctime[3], loctime[4], loctime[5])
        weekdayStr = wdDict[loctime[6]]
        yearday = loctime[7]
        rtc.DateTime(upd_time)
        if my_debug:
            print(f"rtc updated from ntp: {rtc.DateTime()}")
        
        time.sleep(1) # leave the RGB Led on for a while!
        set_led_color(BLACK)
        
        ret = True

    return ret

def pr_dt():
    global dt
    TAG = "pr_dt(): "
    if isinstance(dt, tuple) and len(dt) == 8:
        print("datetime received from NTP server: ", end = '\n')
        # print(f"weekday = {dt[6]}", end = '')
        if dt[6] in wdDict.keys():
            wday = wdDict[dt[6]]
        else:
            wday = str(dt[6])
        if int(TIMEZONE_OFFSET) >= 0: # in case timezone(s) UTC or EAST of UTC
            n = TIMEZONE_OFFSET.find('+')
            if n == -1: # not found
                tz = "+" + TIMEZONE_OFFSET
            else:
                tz = TIMEZONE_OFFSET
        else: # in case timezone(s) WEST of UTC
            n = TIMEZONE_OFFSET.find('-')
            if n == -1: # not found
                tz = "-" + TIMEZONE_OFFSET
            else:
                tz = TIMEZONE_OFFSET

    
        print("{:4d}-{:02d}-{:02d} T {:02d}:{:02d}:{:02d}, {:s}, yearday: {:3d}, timezone: UTC{:s}".format( \
            dt[0], dt[1], dt[2], dt[3], dt[4], dt[5],  wday, dt[7], tz))
        #   year, month, day, hours, minutes, seconds, wday, yearday))
    else:
        print(TAG + f"expected global variable dt being a tuple,\n\thowever received a type \"{type(dt)}\". Check your code!")

def disp_date():
    tm = time.localtime()
    year, month, day, _, _, _, _, _ = tm
    m = monthsLst[month]
    t = "{:02d} {:s} {:04d}".format(day, m, year)
    
    oled.fill(0)
    oled.text(t, 75, VERT_MIDDLE+80) # was: -30

def weekday():
    dt = rtc.DateTime()
    # print(f"weekday(): dt = {dt}")
    if dt[3] in wdDict.keys():
        wDay = wdDict[dt[3]]
    else:
        wDay = "?"
    return wDay
 
def dtToStr():
    loctime = rtc.DateTime()
    return "{:s} {:4d}-{:02d}-{:02d} {:02d}:{:02d}:{:02d}".format(
        wdDict[loctime[3]],
        loctime[0], loctime[1], loctime[2],
        loctime[4], loctime[5], loctime[6])


def main():
    if use_bme280:
        bme_val_idx = 0

    while True:
        t = ""
        try:
            update_fm_ntp()
            if use_mcp9808:
                tempC = sensor.get_temp()
                if isinstance(tempC, float):
                    t = "Temp: {:<5.2f}C".format(tempC)
            if use_bme280:
                if not my_debug:
                    v = bme280.values
                    print(f"bme280.values = {v}")
                    # example: bme280.values = ('22.40C', '1000.68hPa', '43.85%')

                if bme_val_idx == 0:
                    t = "Temp: {:s}".format(v[0])
                if bme_val_idx == 1:
                    t = "Press:{:s}".format(v[1])
                if bme_val_idx == 2:
                    t = "Hum: {:s}".format(v[2])
            
            dt = rtc.Date()
            print(f"date    = {dt}")
            tm = rtc.Time()
            print(f"time    = {tm}")
            print(f"weekday = {weekdayStr}")
            print(f"yearday = {yearday}")
            print(dtToStr(), end ='')
            print(' ', end='')
            print(t)
            
            oled.fill(0)
            oled.text(t,  0,  0)
            oled.text(dt, 0, 10)
            oled.text(weekday(), 0, 20)
            oled.text(tm, 30, 20)

            oled.show()
            if use_bme280:
                bme_val_idx += 1
                if bme_val_idx >=3:
                    bme_val_idx = 0
            time.sleep(3)
        except OSError as exc:
            print(f"Error: {exc.args[0]}")
            
if __name__ == '__main__':
    main()


Anyone has an idea what could be the cause the main.py doesn’t execute automatically after a reset or power-on?
Thank you,
Paulus

Instead of changing directory temporarily, mount the SD card to /sd but do not change to it at all inside boot.py. Modify your boot.py to avoid os.chdir entirely:

def mount_sd():
    sd = SDCard(sd_spi, machine.Pin(PIN_SD_CS))
    try:
        os.mount(sd, "/sd")
        print("SDCard mounted")
        return True
    except OSError as exc:
        print("mounting SDCard failed")
        sys.print_exception(exc)
        return False

if __name__ == '__main__':
    mount_sd()

Also make sure boot.py completes quickly — any delays in boot.py (e.g., slow SD init) might interfere with main.py execution on some boards.

Hi @ahsrabrifat,
Thank you for your reply and suggestion. I tried your boot.py code however it returns this error.

MPY: soft reboot
Traceback (most recent call last):
  File "boot.py", line 13, in <module>
  File "boot.py", line 2, in mount_sd
NameError: name 'SDCard' isn't defined

The version of Micropython flashed in my XIAO RP2350 does not have an sdcard module in it.
See the commands I gave in Thonny, Shell:

>>> import machine
>>> dir(machine)
['__class__', '__name__', 'ADC', 'I2C', 'I2S', 'PWM', 'PWRON_RESET', 'Pin', 'RTC', 'SPI', 'Signal', 'SoftI2C', 'SoftSPI', 'Timer', 'UART', 'USBDevice', 'WDT', 'WDT_RESET', '__dict__', 'bitstream', 'bootloader', 'deepsleep', 'dht_readinto', 'disable_irq', 'enable_irq', 'freq', 'idle', 'lightsleep', 'mem16', 'mem32', 'mem8', 'reset', 'reset_cause', 'soft_reset', 'time_pulse_us', 'unique_id']
>>> dir(machine.sdcard)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'module' object has no attribute 'sdcard'
>>> 

So, that is why I downloaded a sdcard.py file and put it in /lib.

After adding:

from lib.sdcard import SDCard
from machine import Pin

an error that PIN_SD_CS was not defined.

I added:

    sd = SDCard(machine.Pin(machine.Pin.board.GP28))

next the error:

MPY: soft reboot
Traceback (most recent call last):
  File "boot.py", line 16, in <module>
  File "boot.py", line 5, in mount_sd
TypeError: function takes 3 positional arguments but 2 were given

because class SDCard starts with:

class SDCard:
    def __init__(self, spi, cs, baudrate=1320000):
        self.spi = spi
        self.cs = cs

then errors that os and sys were not defined
I ended up with this code of 28 lines that worked:

from lib.sdcard import SDCard
from machine import Pin
import os, sys

def mount_sd():
    PIN_SD_SCK  = machine.Pin.board.GP2
    PIN_SD_MOSI = machine.Pin.board.GP3
    PIN_SD_MISO = machine.Pin.board.GP4
    PIN_SD_CS   = machine.Pin.board.GP28

    # Setup for SD Card
    sd_spi = machine.SPI(0, 
        sck=machine.Pin(PIN_SD_SCK,   machine.Pin.OUT),
        mosi=machine.Pin(PIN_SD_MOSI, machine.Pin.OUT),
        miso=machine.Pin(PIN_SD_MISO, machine.Pin.OUT))

    sd = SDCard(sd_spi, machine.Pin(PIN_SD_CS))
    try:
        os.mount(sd, "/sd")
        print("SDCard mounted")
        return True
    except OSError as exc:
        print("mounting SDCard failed")
        sys.print_exception(exc)
        return False

if __name__ == '__main__':
    mount_sd()

Resul at boot:


MPY: soft reboot
SDCard mounted
MicroPython v1.24.0-preview.201.g269a0e0e1 on 2024-08-09; Raspberry Pi Pico2 with RP2350

Type "help()" for more information.

>>> 

As you can see: this runs your boot.py with my changes OK however still not running main.py.
Maybe the micropython version is not good (enough) for this XIAO RP2350.

Next I tried to flash the latest version on micropython .org for the XIAO RP2350:
MPY download for the XIAO RP2350

I tried from the latest:

Firmware

Releases

v1.25.0 (2025-04-15) .uf2 / [Release notes] (latest)

and:

Firmware (RISC-V CPU mode)

Releases

v1.25.0 (2025-04-15) .uf2 / [Release notes] (latest)

Both didn’t boot.

Only the one which I had:

RPI_PICO2-20240809-v1.24.0-preview.201.g269a0e0e1.uf2

works.