Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ microcontroller = true
mipidsi = false
msgpack = false
neopixel_write = false
nvm = false
nvm = true # Zephyr board has nvm
onewireio = false
os = true
paralleldisplaybus = false
Expand Down
22 changes: 22 additions & 0 deletions ports/zephyr-cp/boards/adafruit_feather_nrf52840_uf2.overlay
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,28 @@
/delete-node/ partitions;
};

/delete-node/ &storage_partition;
/delete-node/ &code_partition;

&flash0 {
partitions {
code_partition: partition@26000 {
label = "Application";
reg = <0x00026000 0x000c4000>;
};

storage_partition: partition@ea000 {
label = "storage";
reg = <0x000ea000 0x00008000>;
};

nvm_partition: partition@f2000 {
label = "nvm";
reg = <0x000f2000 0x00002000>;
};
};
};

&uart0 {
status = "okay";
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ microcontroller = true
mipidsi = false
msgpack = false
neopixel_write = false
nvm = false
nvm = true # Zephyr board has nvm
onewireio = false
os = true
paralleldisplaybus = false
Expand Down
7 changes: 6 additions & 1 deletion ports/zephyr-cp/boards/native_sim.overlay
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,12 @@

circuitpy_partition: partition@0 {
label = "circuitpy";
reg = <0x00000000 DT_SIZE_K(2048)>;
reg = <0x00000000 DT_SIZE_K(2040)>;
};

nvm_partition: partition@1fe000 {
label = "nvm";
reg = <0x001fe000 0x00002000>;
};
};
};
Expand Down
6 changes: 2 additions & 4 deletions ports/zephyr-cp/common-hal/microcontroller/__init__.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
#include "common-hal/microcontroller/Pin.h"
#include "common-hal/microcontroller/Processor.h"

// #include "shared-bindings/nvm/ByteArray.h"
#include "shared-bindings/nvm/ByteArray.h"
#include "shared-bindings/microcontroller/__init__.h"
#include "shared-bindings/microcontroller/Pin.h"
#include "shared-bindings/microcontroller/Processor.h"
Expand Down Expand Up @@ -93,14 +93,12 @@ const mcu_processor_obj_t common_hal_mcu_processor_obj = {
},
};

#if CIRCUITPY_NVM && CIRCUITPY_INTERNAL_NVM_SIZE > 0
#if CIRCUITPY_NVM
// The singleton nvm.ByteArray object.
const nvm_bytearray_obj_t common_hal_mcu_nvm_obj = {
.base = {
.type = &nvm_bytearray_type,
},
.start_address = (uint8_t *)CIRCUITPY_INTERNAL_NVM_START_ADDR,
.len = CIRCUITPY_INTERNAL_NVM_SIZE,
};
#endif

Expand Down
103 changes: 103 additions & 0 deletions ports/zephyr-cp/common-hal/nvm/ByteArray.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// This file is part of the CircuitPython project: https://circuitpython.org
//
// SPDX-FileCopyrightText: Copyright (c) 2025 Scott Shawcroft for Adafruit Industries
//
// SPDX-License-Identifier: MIT

#include "py/runtime.h"
#include "common-hal/nvm/ByteArray.h"
#include "shared-bindings/nvm/ByteArray.h"

#include <string.h>

#include <zephyr/drivers/flash.h>
#include <zephyr/storage/flash_map.h>

#define NVM_PARTITION nvm_partition

#if FIXED_PARTITION_EXISTS(NVM_PARTITION)

static const struct flash_area *nvm_area = NULL;
static size_t nvm_erase_size = 0;

static bool ensure_nvm_open(void) {
if (nvm_area != NULL) {
return true;
}
int rc = flash_area_open(FIXED_PARTITION_ID(NVM_PARTITION), &nvm_area);
if (rc != 0) {
return false;
}

const struct device *dev = flash_area_get_device(nvm_area);
struct flash_pages_info info;
flash_get_page_info_by_offs(dev, nvm_area->fa_off, &info);
nvm_erase_size = info.size;

return true;
}

uint32_t common_hal_nvm_bytearray_get_length(const nvm_bytearray_obj_t *self) {
if (!ensure_nvm_open()) {
return 0;
}
return nvm_area->fa_size;
}

bool common_hal_nvm_bytearray_set_bytes(const nvm_bytearray_obj_t *self,
uint32_t start_index, uint8_t *values, uint32_t len) {
if (!ensure_nvm_open()) {
return false;
}

uint32_t address = start_index;
while (len > 0) {
uint32_t page_offset = address % nvm_erase_size;
uint32_t page_start = address - page_offset;
uint32_t write_len = MIN(len, nvm_erase_size - page_offset);

uint8_t *buffer = m_malloc(nvm_erase_size);
if (buffer == NULL) {
return false;
}

// Read the full erase page.
int rc = flash_area_read(nvm_area, page_start, buffer, nvm_erase_size);
if (rc != 0) {
m_free(buffer);
return false;
}

// Modify the relevant bytes.
memcpy(buffer + page_offset, values, write_len);

// Erase the page.
rc = flash_area_erase(nvm_area, page_start, nvm_erase_size);
if (rc != 0) {
m_free(buffer);
return false;
}

// Write the page back.
rc = flash_area_write(nvm_area, page_start, buffer, nvm_erase_size);
m_free(buffer);
if (rc != 0) {
return false;
}

address += write_len;
values += write_len;
len -= write_len;
}
return true;
}

void common_hal_nvm_bytearray_get_bytes(const nvm_bytearray_obj_t *self,
uint32_t start_index, uint32_t len, uint8_t *values) {
if (!ensure_nvm_open()) {
return;
}
flash_area_read(nvm_area, start_index, values, len);
}

#endif // FIXED_PARTITION_EXISTS(NVM_PARTITION)
13 changes: 13 additions & 0 deletions ports/zephyr-cp/common-hal/nvm/ByteArray.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// This file is part of the CircuitPython project: https://circuitpython.org
//
// SPDX-FileCopyrightText: Copyright (c) 2025 Scott Shawcroft for Adafruit Industries
//
// SPDX-License-Identifier: MIT

#pragma once

#include "py/obj.h"

typedef struct {
mp_obj_base_t base;
} nvm_bytearray_obj_t;
5 changes: 5 additions & 0 deletions ports/zephyr-cp/cptools/zephyr2cp.py
Original file line number Diff line number Diff line change
Expand Up @@ -893,4 +893,9 @@ def zephyr_dts_to_cp_board(board_id, portdir, builddir, zephyrbuilddir): # noqa
board_info["flash_count"] = len(flashes)
board_info["rotaryio"] = bool(ioports)
board_info["usb_num_endpoint_pairs"] = usb_num_endpoint_pairs

# Detect NVM partition from the device tree.
nvm_node = device_tree.label2node.get("nvm_partition")
board_info["nvm"] = nvm_node is not None

return board_info
3 changes: 3 additions & 0 deletions ports/zephyr-cp/mpconfigport.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@

#define CIRCUITPY_DEBUG_TINYUSB 0

// NVM size is determined at runtime from the Zephyr partition table.
#define CIRCUITPY_INTERNAL_NVM_SIZE 1

// Disable native _Float16 handling for host builds.
#define MICROPY_FLOAT_USE_NATIVE_FLT16 (0)

Expand Down
73 changes: 73 additions & 0 deletions ports/zephyr-cp/tests/test_nvm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# SPDX-FileCopyrightText: 2025 Scott Shawcroft for Adafruit Industries
# SPDX-License-Identifier: MIT

"""Test NVM functionality on native_sim."""

import pytest


NVM_BASIC_CODE = """\
import microcontroller

nvm = microcontroller.nvm
print(f"nvm length: {len(nvm)}")

# Write some bytes
nvm[0] = 42
nvm[1] = 99
print(f"nvm[0]: {nvm[0]}")
print(f"nvm[1]: {nvm[1]}")

# Write a slice
nvm[2:5] = b"\\x01\\x02\\x03"
print(f"nvm[2:5]: {list(nvm[2:5])}")

print("done")
"""


@pytest.mark.circuitpy_drive({"code.py": NVM_BASIC_CODE})
def test_nvm_read_write(circuitpython):
"""Test basic NVM read and write operations."""
circuitpython.wait_until_done()

output = circuitpython.serial.all_output
assert "nvm length: 8192" in output
assert "nvm[0]: 42" in output
assert "nvm[1]: 99" in output
assert "nvm[2:5]: [1, 2, 3]" in output
assert "done" in output


NVM_PERSIST_CODE = """\
import microcontroller

nvm = microcontroller.nvm
value = nvm[0]
print(f"nvm[0]: {value}")

if value == 255:
# First run: write a marker
nvm[0] = 123
print("wrote marker")
else:
print(f"marker found: {value}")

print("done")
"""


@pytest.mark.circuitpy_drive({"code.py": NVM_PERSIST_CODE})
@pytest.mark.code_py_runs(2)
def test_nvm_persists_across_reload(circuitpython):
"""Test that NVM data persists across soft reloads."""
circuitpython.serial.wait_for("wrote marker")
# Trigger soft reload
circuitpython.serial.write("\x04")
circuitpython.wait_until_done()

output = circuitpython.serial.all_output
assert "nvm[0]: 255" in output
assert "wrote marker" in output
assert "marker found: 123" in output
assert "done" in output
Loading