Skip to content

Commit 5b021a6

Browse files
tannewtclaude
andcommitted
Add NVM support for Zephyr port
Implement nvm.ByteArray using Zephyr flash_area API, auto-detect NVM partition size from device tree, and add 8KB NVM partition to feather nrf52840 matching the non-Zephyr Nordic port layout. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 2a1f56e commit 5b021a6

File tree

10 files changed

+229
-7
lines changed

10 files changed

+229
-7
lines changed

ports/zephyr-cp/boards/adafruit/feather_nrf52840_zephyr/autogen_board_info.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ microcontroller = true
7373
mipidsi = false
7474
msgpack = false
7575
neopixel_write = false
76-
nvm = false
76+
nvm = true # Zephyr board has nvm
7777
onewireio = false
7878
os = true
7979
paralleldisplaybus = false

ports/zephyr-cp/boards/adafruit_feather_nrf52840_uf2.overlay

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,28 @@
1717
/delete-node/ partitions;
1818
};
1919

20+
/delete-node/ &storage_partition;
21+
/delete-node/ &code_partition;
22+
23+
&flash0 {
24+
partitions {
25+
code_partition: partition@26000 {
26+
label = "Application";
27+
reg = <0x00026000 0x000c4000>;
28+
};
29+
30+
storage_partition: partition@ea000 {
31+
label = "storage";
32+
reg = <0x000ea000 0x00008000>;
33+
};
34+
35+
nvm_partition: partition@f2000 {
36+
label = "nvm";
37+
reg = <0x000f2000 0x00002000>;
38+
};
39+
};
40+
};
41+
2042
&uart0 {
2143
status = "okay";
2244
};

ports/zephyr-cp/boards/native/native_sim/autogen_board_info.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ microcontroller = true
7373
mipidsi = false
7474
msgpack = false
7575
neopixel_write = false
76-
nvm = false
76+
nvm = true # Board has NVM partition
7777
onewireio = false
7878
os = true
7979
paralleldisplaybus = false

ports/zephyr-cp/boards/native_sim.overlay

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,12 @@
3131

3232
circuitpy_partition: partition@0 {
3333
label = "circuitpy";
34-
reg = <0x00000000 DT_SIZE_K(2048)>;
34+
reg = <0x00000000 DT_SIZE_K(2040)>;
35+
};
36+
37+
nvm_partition: partition@1fe000 {
38+
label = "nvm";
39+
reg = <0x001fe000 0x00002000>;
3540
};
3641
};
3742
};

ports/zephyr-cp/common-hal/microcontroller/__init__.c

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
#include "common-hal/microcontroller/Pin.h"
1212
#include "common-hal/microcontroller/Processor.h"
1313

14-
// #include "shared-bindings/nvm/ByteArray.h"
14+
#include "shared-bindings/nvm/ByteArray.h"
1515
#include "shared-bindings/microcontroller/__init__.h"
1616
#include "shared-bindings/microcontroller/Pin.h"
1717
#include "shared-bindings/microcontroller/Processor.h"
@@ -93,14 +93,12 @@ const mcu_processor_obj_t common_hal_mcu_processor_obj = {
9393
},
9494
};
9595

96-
#if CIRCUITPY_NVM && CIRCUITPY_INTERNAL_NVM_SIZE > 0
96+
#if CIRCUITPY_NVM
9797
// The singleton nvm.ByteArray object.
9898
const nvm_bytearray_obj_t common_hal_mcu_nvm_obj = {
9999
.base = {
100100
.type = &nvm_bytearray_type,
101101
},
102-
.start_address = (uint8_t *)CIRCUITPY_INTERNAL_NVM_START_ADDR,
103-
.len = CIRCUITPY_INTERNAL_NVM_SIZE,
104102
};
105103
#endif
106104

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
// This file is part of the CircuitPython project: https://circuitpython.org
2+
//
3+
// SPDX-FileCopyrightText: Copyright (c) 2025 Scott Shawcroft for Adafruit Industries
4+
//
5+
// SPDX-License-Identifier: MIT
6+
7+
#include "py/runtime.h"
8+
#include "common-hal/nvm/ByteArray.h"
9+
#include "shared-bindings/nvm/ByteArray.h"
10+
11+
#include <string.h>
12+
13+
#include <zephyr/drivers/flash.h>
14+
#include <zephyr/storage/flash_map.h>
15+
16+
#define NVM_PARTITION nvm_partition
17+
18+
#if FIXED_PARTITION_EXISTS(NVM_PARTITION)
19+
20+
static const struct flash_area *nvm_area = NULL;
21+
static size_t nvm_erase_size = 0;
22+
23+
static bool ensure_nvm_open(void) {
24+
if (nvm_area != NULL) {
25+
return true;
26+
}
27+
int rc = flash_area_open(FIXED_PARTITION_ID(NVM_PARTITION), &nvm_area);
28+
if (rc != 0) {
29+
return false;
30+
}
31+
32+
const struct device *dev = flash_area_get_device(nvm_area);
33+
struct flash_pages_info info;
34+
flash_get_page_info_by_offs(dev, nvm_area->fa_off, &info);
35+
nvm_erase_size = info.size;
36+
37+
return true;
38+
}
39+
40+
uint32_t common_hal_nvm_bytearray_get_length(const nvm_bytearray_obj_t *self) {
41+
if (!ensure_nvm_open()) {
42+
return 0;
43+
}
44+
return nvm_area->fa_size;
45+
}
46+
47+
bool common_hal_nvm_bytearray_set_bytes(const nvm_bytearray_obj_t *self,
48+
uint32_t start_index, uint8_t *values, uint32_t len) {
49+
if (!ensure_nvm_open()) {
50+
return false;
51+
}
52+
53+
uint32_t address = start_index;
54+
while (len > 0) {
55+
uint32_t page_offset = address % nvm_erase_size;
56+
uint32_t page_start = address - page_offset;
57+
uint32_t write_len = MIN(len, nvm_erase_size - page_offset);
58+
59+
uint8_t *buffer = m_malloc(nvm_erase_size);
60+
if (buffer == NULL) {
61+
return false;
62+
}
63+
64+
// Read the full erase page.
65+
int rc = flash_area_read(nvm_area, page_start, buffer, nvm_erase_size);
66+
if (rc != 0) {
67+
m_free(buffer);
68+
return false;
69+
}
70+
71+
// Modify the relevant bytes.
72+
memcpy(buffer + page_offset, values, write_len);
73+
74+
// Erase the page.
75+
rc = flash_area_erase(nvm_area, page_start, nvm_erase_size);
76+
if (rc != 0) {
77+
m_free(buffer);
78+
return false;
79+
}
80+
81+
// Write the page back.
82+
rc = flash_area_write(nvm_area, page_start, buffer, nvm_erase_size);
83+
m_free(buffer);
84+
if (rc != 0) {
85+
return false;
86+
}
87+
88+
address += write_len;
89+
values += write_len;
90+
len -= write_len;
91+
}
92+
return true;
93+
}
94+
95+
void common_hal_nvm_bytearray_get_bytes(const nvm_bytearray_obj_t *self,
96+
uint32_t start_index, uint32_t len, uint8_t *values) {
97+
if (!ensure_nvm_open()) {
98+
return;
99+
}
100+
flash_area_read(nvm_area, start_index, values, len);
101+
}
102+
103+
#endif // FIXED_PARTITION_EXISTS(NVM_PARTITION)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// This file is part of the CircuitPython project: https://circuitpython.org
2+
//
3+
// SPDX-FileCopyrightText: Copyright (c) 2025 Scott Shawcroft for Adafruit Industries
4+
//
5+
// SPDX-License-Identifier: MIT
6+
7+
#pragma once
8+
9+
#include "py/obj.h"
10+
11+
typedef struct {
12+
mp_obj_base_t base;
13+
} nvm_bytearray_obj_t;

ports/zephyr-cp/cptools/zephyr2cp.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -893,4 +893,9 @@ def zephyr_dts_to_cp_board(board_id, portdir, builddir, zephyrbuilddir): # noqa
893893
board_info["flash_count"] = len(flashes)
894894
board_info["rotaryio"] = bool(ioports)
895895
board_info["usb_num_endpoint_pairs"] = usb_num_endpoint_pairs
896+
897+
# Detect NVM partition from the device tree.
898+
nvm_node = device_tree.label2node.get("nvm_partition")
899+
board_info["nvm"] = nvm_node is not None
900+
896901
return board_info

ports/zephyr-cp/mpconfigport.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717

1818
#define CIRCUITPY_DEBUG_TINYUSB 0
1919

20+
// NVM size is determined at runtime from the Zephyr partition table.
21+
#define CIRCUITPY_INTERNAL_NVM_SIZE 1
22+
2023
// Disable native _Float16 handling for host builds.
2124
#define MICROPY_FLOAT_USE_NATIVE_FLT16 (0)
2225

ports/zephyr-cp/tests/test_nvm.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# SPDX-FileCopyrightText: 2025 Scott Shawcroft for Adafruit Industries
2+
# SPDX-License-Identifier: MIT
3+
4+
"""Test NVM functionality on native_sim."""
5+
6+
import pytest
7+
8+
9+
NVM_BASIC_CODE = """\
10+
import microcontroller
11+
12+
nvm = microcontroller.nvm
13+
print(f"nvm length: {len(nvm)}")
14+
15+
# Write some bytes
16+
nvm[0] = 42
17+
nvm[1] = 99
18+
print(f"nvm[0]: {nvm[0]}")
19+
print(f"nvm[1]: {nvm[1]}")
20+
21+
# Write a slice
22+
nvm[2:5] = b"\\x01\\x02\\x03"
23+
print(f"nvm[2:5]: {list(nvm[2:5])}")
24+
25+
print("done")
26+
"""
27+
28+
29+
@pytest.mark.circuitpy_drive({"code.py": NVM_BASIC_CODE})
30+
def test_nvm_read_write(circuitpython):
31+
"""Test basic NVM read and write operations."""
32+
circuitpython.wait_until_done()
33+
34+
output = circuitpython.serial.all_output
35+
assert "nvm length: 8192" in output
36+
assert "nvm[0]: 42" in output
37+
assert "nvm[1]: 99" in output
38+
assert "nvm[2:5]: [1, 2, 3]" in output
39+
assert "done" in output
40+
41+
42+
NVM_PERSIST_CODE = """\
43+
import microcontroller
44+
45+
nvm = microcontroller.nvm
46+
value = nvm[0]
47+
print(f"nvm[0]: {value}")
48+
49+
if value == 255:
50+
# First run: write a marker
51+
nvm[0] = 123
52+
print("wrote marker")
53+
else:
54+
print(f"marker found: {value}")
55+
56+
print("done")
57+
"""
58+
59+
60+
@pytest.mark.circuitpy_drive({"code.py": NVM_PERSIST_CODE})
61+
@pytest.mark.code_py_runs(2)
62+
def test_nvm_persists_across_reload(circuitpython):
63+
"""Test that NVM data persists across soft reloads."""
64+
circuitpython.serial.wait_for("wrote marker")
65+
# Trigger soft reload
66+
circuitpython.serial.write("\x04")
67+
circuitpython.wait_until_done()
68+
69+
output = circuitpython.serial.all_output
70+
assert "nvm[0]: 255" in output
71+
assert "wrote marker" in output
72+
assert "marker found: 123" in output
73+
assert "done" in output

0 commit comments

Comments
 (0)