Skip to main content

MicroPython on Zephyr - MCXN947

In my recent post on MicroPython I looked at the Zephyr port of MicroPython on the i.MXRT1060 EVK. As that board has an SD card connector populated, I was able to use an VFAT SD card for storing Python programs. For the FRDM-MCXN947 the situation is different. Although the MCXN947 micro-controller features an SD controller, the Freedom board is lacking the actual SD card connector as it is not populated, probably to save money. As I am a software guy, mounting this SMD connector is more difficult than looking for software alternatives, so let's find out if we can use the internal flash or the external QSPI as a file system instead.

micropython-logo.jpeg

zephyr,flash-disk to the rescue

The MicroPython documentation on "Filesystems and Storage" notes that we can use two subsystems to host file systems. The Zephyr Disk Access API or the Zephyr Flash map API allows this abstraction. We used the former with the SD card on the i.MXRT1060 and the latter should allow us to target internal flash.

Studying all the relevant documentation and other samples, this boils down to adding a section to the device tree which links to another part of the device tree describing the partitions. For this demo, I use this:

/ {
        flash_disk {
                compatible = "zephyr,flash-disk";
                partition = <&storage_partition>;
                disk-name = "SOC-NV-FLASH";
                cache-size = <4096>;
        };
};

Note that the cache-size parameter is the minimum erase block size of the flash and according to the device tree of the MCXN947, this needs to be 4 KiB. As we will see, this parameter prompted another patch to MicroPython as 4096 is hard coded for mounting the file system on boot and it should really be read from the device tree instead.

But for now, let's add this file as ports/zephyr/boards/frdm_mcxn947_mcxn947_cpu0.overlay to our MicroPython source tree. The Zephyr build system will then pick up this overlay during compilation for this board. One further problem is that although the SD connector is not populated on the board, the default device tree and settings still enable it. Trying to mount an SD card but not finding one induces a long time-out during boot, so we need to explicitly disable this and enable only the flash map api. Again, we will do this with a kconf fragment named ports/zephyr/boards/frdm_mcxn947_mcxn947_cpu0.conf, so it will only be picked up compiling for this board:

CONFIG_DISK_DRIVER_SDMMC=n
CONFIG_DISK_DRIVER_FLASH=y
CONFIG_FLASH=y
CONFIG_FLASH_MAP=y

All of these changes are also contained in the fixes-for-zephyr-4.0 branch in my fork of the MicroPython repository. Having this branch checked out, let's compile and flash the binary:

dzu@krikkit:/opt/src/git/micropython/ports/zephyr (fixes-for-zephyr-4.0)$ west build -b frdm_mcxn947/mcxn947/cpu0
-- west build: generating a build system
Loading Zephyr default modules (Zephyr base).
-- Application: /opt/src/git/micropython/ports/zephyr
-- CMake version: 3.31.5
-- Found Python3: /usr/bin/python3 (found suitable version "3.13.1", minimum required is "3.10") found components: Interpreter
-- Cache files will be written to: /home/dzu/.cache/zephyr
-- Zephyr version: 4.0.99 (/opt/src/git/zephyrproject/zephyr)
-- Found west (found suitable version "1.3.0", minimum required is "0.14.0")
-- Board: frdm_mcxn947, qualifiers: mcxn947/cpu0
-- Found host-tools: zephyr 0.16.8 (/opt/zephyr-sdk-0.16.8)
-- Found toolchain: zephyr 0.16.8 (/opt/zephyr-sdk-0.16.8)
-- Found Dtc: /opt/zephyr-sdk-0.16.8/sysroots/x86_64-pokysdk-linux/usr/bin/dtc (found suitable version "1.6.0", minimum required is "1.4.6")
-- Found BOARD.dts: /opt/src/git/zephyrproject/zephyr/boards/nxp/frdm_mcxn947/frdm_mcxn947_mcxn947_cpu0.dts
-- Found devicetree overlay: /opt/src/git/micropython/ports/zephyr/boards/frdm_mcxn947_mcxn947_cpu0.overlay
-- Generated zephyr.dts: /opt/src/git/micropython/ports/zephyr/build/frdm_mcxn947/mcxn947/cpu0/zephyr/zephyr.dts
-- Generated pickled edt: /opt/src/git/micropython/ports/zephyr/build/frdm_mcxn947/mcxn947/cpu0/zephyr/edt.pickle
-- Generated devicetree_generated.h: /opt/src/git/micropython/ports/zephyr/build/frdm_mcxn947/mcxn947/cpu0/zephyr/include/generated/zephyr/devicetree_generated.h
-- Including generated dts.cmake file: /opt/src/git/micropython/ports/zephyr/build/frdm_mcxn947/mcxn947/cpu0/zephyr/dts.cmake

warning: DISK_DRIVER_FLASH (defined at drivers/disk/Kconfig.flash:4) was assigned the value 'y' but
got the value 'n'. Check these unsatisfied dependencies: DISK_DRIVERS (=n). See
http://docs.zephyrproject.org/latest/kconfig.html#CONFIG_DISK_DRIVER_FLASH and/or look up
DISK_DRIVER_FLASH in the menuconfig/guiconfig interface. The Application Development Primer, Setting
Configuration Values, and Kconfig - Tips and Best Practices sections of the manual might be helpful
too.

Parsing /opt/src/git/micropython/ports/zephyr/Kconfig
Loaded configuration '/opt/src/git/zephyrproject/zephyr/boards/nxp/frdm_mcxn947/frdm_mcxn947_mcxn947_cpu0_defconfig'
Merged configuration '/opt/src/git/micropython/ports/zephyr/prj.conf'
Merged configuration '/opt/src/git/micropython/ports/zephyr/boards/frdm_mcxn947_mcxn947_cpu0.conf'
Configuration saved to '/opt/src/git/micropython/ports/zephyr/build/frdm_mcxn947/mcxn947/cpu0/zephyr/.config'
Kconfig header saved to '/opt/src/git/micropython/ports/zephyr/build/frdm_mcxn947/mcxn947/cpu0/zephyr/include/generated/zephyr/autoconf.h'
-- Found GnuLd: /opt/zephyr-sdk-0.16.8/arm-zephyr-eabi/arm-zephyr-eabi/bin/ld.bfd (found version "2.38")
-- The C compiler identification is GNU 12.2.0
-- The CXX compiler identification is GNU 12.2.0
-- The ASM compiler identification is GNU
-- Found assembler: /opt/zephyr-sdk-0.16.8/arm-zephyr-eabi/bin/arm-zephyr-eabi-gcc
CMake Warning at /opt/src/git/zephyrproject/zephyr/subsys/random/CMakeLists.txt:12 (message):


      Warning: CONFIG_TIMER_RANDOM_GENERATOR and CONFIG_TEST_CSPRNG_GENERATOR are
      not truly random generators. This capability is not secure and it is
      provided for testing purposes only. Use it carefully.


Load components for MCXN947_cm33_core0:
driver_common component is included.
driver_reset component is included.
device_CMSIS component is included.
CMSIS_Include_core_cm component is included.
device_system component is included.
driver_lpflexcomm component is included.
driver_lpuart component is included.
driver_flexspi component is included.
driver_mcx_spc component is included.
driver_lpcmp component is included.
driver_port component is included.
driver_cache_cache64 component is included.
driver_flashiap component is included.
-- Using ccache: /usr/bin/ccache
CMake Warning at /opt/src/git/zephyrproject/zephyr/CMakeLists.txt:1002 (message):
  No SOURCES given to Zephyr library: drivers__entropy

  Excluding target from build.


-- Found Python3: /usr/bin/python3 (found version "3.13.1") found components: Interpreter
-- Configuring done (31.6s)
-- Generating done (0.4s)
-- Build files have been written to: /opt/src/git/micropython/ports/zephyr/build/frdm_mcxn947/mcxn947/cpu0
-- west build: building application
[1/456] Preparing syscall dependency handling

[3/456] Generating ../build_info.yml
-- Found Python3: /usr/bin/python (found suitable version "3.13.1", minimum required is "3.10") found components: Interpreter
-- Cache files will be written to: /home/dzu/.cache/zephyr
[4/456] Generating include/generated/zephyr/version.h
-- Zephyr version: 4.0.99 (/opt/src/git/zephyrproject/zephyr), build: v4.0.0-5122-gcb772b0f60c3
[5/456] cd /opt/src/git/micropython/ports/zephyr/buil...yr/build/frdm_mcxn947/mcxn947/cpu0/genhdr/mpversion.h
GEN /opt/src/git/micropython/ports/zephyr/build/frdm_mcxn947/mcxn947/cpu0/genhdr/mpversion.h
[222/456] Generating genhdr/moduledefs.collected
Module registrations updated
[224/456] Generating genhdr/qstrdefs.collected.h
QSTR updated
[226/456] Generating genhdr/root_pointers.collected
Root pointer registrations updated
[437/456] Building C object CMakeFiles/micropython.dir/opt/src/git/micropython/lib/littlefs/lfs2.c.obj
/opt/src/git/micropython/lib/littlefs/lfs2.c:495:13: warning: 'lfs2_mlist_isopen' defined but not used [-Wunused-function]
  495 | static bool lfs2_mlist_isopen(struct lfs2_mlist *head,
      |             ^~~~~~~~~~~~~~~~~
[456/456] Linking C executable zephyr/zephyr.elf
Memory region         Used Size  Region Size  %age Used
           FLASH:      289896 B         2 MB     13.82%
             RAM:       96064 B       320 KB     29.32%
           SRAM1:          0 GB        96 KB      0.00%
        IDT_LIST:          0 GB        32 KB      0.00%
Generating files from /opt/src/git/micropython/ports/zephyr/build/frdm_mcxn947/mcxn947/cpu0/zephyr/zephyr.elf for board: frdm_mcxn947
dzu@krikkit:/opt/src/git/micropython/ports/zephyr (fixes-for-zephyr-4.0)$ add_path /usr/local/LinkServer
dzu@krikkit:/opt/src/git/micropython/ports/zephyr (fixes-for-zephyr-4.0)$ west flash -d build/frdm_mcxn947/mcxn947/cpu0/
-- west flash: rebuilding
[1/207] cd /opt/src/git/micropython/ports/zephyr/buil...yr/build/frdm_mcxn947/mcxn947/cpu0/genhdr/mpversion.h
-- west flash: using runner linkserver
RUNNER - gdb_port = 3333, semih port = 8888
-- runners.linkserver: LinkServer: /usr/local/LinkServer/LinkServer, version v24.12.21
INFO: Exact match for MCXN947:FRDM-MCXN947 found
INFO: Selected device MCXN947:FRDM-MCXN947
INFO: Getting available probes
INFO: Selecting probe by index
INFO: Selected probe #1 XCNMKAX0KS13T (MCU-LINK FRDM-MCXN947 (r0E7) CMSIS-DAP V3.153)
INFO: MCU-Link firmware update `check`: Probe ([XCNMKAX0KS13T] [MCU-LINK FRDM-MCXN947 (r0E7) CMSIS-DAP V3.153]) is already running the same firmware version as the included firmware version [3.153]
INFO: Selected device matches probe's target identification info
dzu@krikkit:/opt/src/git/micropython/ports/zephyr (fixes-for-zephyr-4.0)$

Looking at the console, we see MicroPython come up and try to mount the file system:

*** Booting Zephyr OS build v4.0.0-4697-gfe32a3d2f432 ***
MicroPython v1.25.0-preview.298.gf87e8b35b on 2025-02-13; zephyr-frdm_mcxn947 with mcxn947
Type "help()" for more information.
>>> import os
>>> os.listdir('/')
[]
>>>

As we do not see a directory, the flash was not mounted at boot, which is understandable as we did not yet format it as a file system. Doing so is straight forward according to the documentation. Note the second parameter for the FlashArea constructor specifies the minimum erase block size on the MCXN947:

>>> import vfs
>>> from zephyr import FlashArea
>>> bdev=FlashArea(FlashArea.STORAGE,4096)
>>> vfs.VfsLfs2.mkfs(bdev)
>>> vfs.mount(bdev,'/flash')
>>> 
>>> import os
>>> os.listdir('/')
['flash']
>>> os.listdir('/flash')
[]
>>> 

Once the storage has been formatted, MicroPython will try to mount it at boot time. When I first tried that, I noticed the mentioned hard coding of the erase block size of 4096 in main.c. The fix for reading this parameter from the device tree is also already in my github branch, so you should not see this problem at all.

Connecting Things Together

So with all of this in place, we can once again disconnect from the serial console and use pyboard.py for manipulating the file system:

dzu@krikkit:~$ pyboard.py -f ls /
ls :/
           0 flash/
dzu@krikkit:~$ pyboard.py -f ls /flash
ls :/flash
dzu@krikkit:~$ cat > main.py
print('Hello world from main.py!')
dzu@krikkit:~$ pyboard.py -f cp main.py :/flash/
cp main.py :/flash/
dzu@krikkit:~$ pyboard.py -f cat /flash/main.py
cat :/flash/main.py
print('Hello world from main.py!')
dzu@krikkit:~$ 

As can be seen, we can access the file-system mounted below /flash just fine and upload a simple main.py "application". Resetting the board with this in place indeed runs our code when booting the board:

*** Booting Zephyr OS build v4.0.0-5122-gcb772b0f60c3 ***
Hello world from main.py!
MicroPython v1.25.0-preview.302.gdb21205cd on 2025-02-14; zephyr-frdm_mcxn947 with mcxn947
Type "help()" for more information.
>>> 

Summary

Using Zephyr OS as the hardware abstraction layer is a very elegant way of making MicroPython hardware agnostic. The available abstraction layers allow us to use either SD cards or flash accessible by the micro-controller to store our scripts. Switching from one board which is also supported outside of Zephyr to one which is not, was absolutely trivial and shows the potential of Zephyr to become the "Linux for micro-controllers".

Update 2025-02-17

During the process of trying to push all these patches into the upstream MicroPython repository, I modified them so that they work in all supported versions of Zephyr, i.e. v3.7, v4.0 and top of tree. The cpu1_partition in the internal flash used in the first version of this document is only defined in top of tree, but the board also features a QSPI Winbond device with 64 MiB which fits our intention perfectly. Switching from the internal flash to QSPI was only a matter of changing the pointer in the device tree and these changes now work in v4.0 and in top of tree for frdm_mcxn947 and for v3.7, v4.0 and top of tree for mimxrt1060_evk and can hopefully be merged upstream.

Comments

Comments powered by Disqus