Skip to content
Open
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
6 changes: 4 additions & 2 deletions .github/actions/deps/ports/zephyr-cp/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ name: Fetch Zephyr port deps
runs:
using: composite
steps:
- name: Get libusb and mtools
- name: Get Linux build dependencies
if: runner.os == 'Linux'
run: |
sudo dpkg --add-architecture i386
export PKG_CONFIG_PATH=/usr/lib/i386-linux-gnu/pkgconfig
sudo apt-get update
sudo apt-get install -y libusb-1.0-0-dev libudev-dev mtools
sudo apt-get install -y libusb-1.0-0-dev libudev-dev pkg-config libsdl2-dev:i386 libsdl2-image-dev:i386 mtools
shell: bash
- name: Setup Zephyr project
uses: zephyrproject-rtos/action-zephyr-setup@v1
Expand Down
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
- Capture CircuitPython output by finding the matching device in `/dev/serial/by-id`
- You can mount the CIRCUITPY drive by doing `udisksctl mount -b /dev/disk/by-label/CIRCUITPY` and access it via `/run/media/<user>/CIRCUITPY`.
- `circup` is a command line tool to install libraries and examples to CIRCUITPY.
- When connecting to serial devices on Linux use /dev/serial/by-id. These will be more stable than /dev/ttyACM*.
16 changes: 14 additions & 2 deletions locale/circuitpython.pot
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,8 @@ msgstr ""
msgid "%q must be %d"
msgstr ""

#: py/argcheck.c shared-bindings/busdisplay/BusDisplay.c
#: shared-bindings/displayio/Bitmap.c
#: ports/zephyr-cp/bindings/zephyr_display/Display.c py/argcheck.c
#: shared-bindings/busdisplay/BusDisplay.c shared-bindings/displayio/Bitmap.c
#: shared-bindings/framebufferio/FramebufferDisplay.c
#: shared-bindings/is31fl3741/FrameBuffer.c
#: shared-bindings/rgbmatrix/RGBMatrix.c
Expand Down Expand Up @@ -458,6 +458,7 @@ msgstr ""
msgid ", in %q\n"
msgstr ""

#: ports/zephyr-cp/bindings/zephyr_display/Display.c
#: shared-bindings/busdisplay/BusDisplay.c
#: shared-bindings/epaperdisplay/EPaperDisplay.c
#: shared-bindings/framebufferio/FramebufferDisplay.c
Expand Down Expand Up @@ -646,6 +647,7 @@ msgstr ""
msgid "Baudrate not supported by peripheral"
msgstr ""

#: ports/zephyr-cp/common-hal/zephyr_display/Display.c
#: shared-module/busdisplay/BusDisplay.c
#: shared-module/framebufferio/FramebufferDisplay.c
msgid "Below minimum frame rate"
Expand All @@ -668,6 +670,7 @@ msgstr ""
msgid "Both RX and TX required for flow control"
msgstr ""

#: ports/zephyr-cp/bindings/zephyr_display/Display.c
#: shared-bindings/busdisplay/BusDisplay.c
#: shared-bindings/framebufferio/FramebufferDisplay.c
msgid "Brightness not adjustable"
Expand Down Expand Up @@ -941,6 +944,10 @@ msgstr ""
msgid "Display must have a 16 bit colorspace."
msgstr ""

#: ports/zephyr-cp/common-hal/zephyr_display/Display.c
msgid "Display not ready"
msgstr ""

#: shared-bindings/busdisplay/BusDisplay.c
#: shared-bindings/epaperdisplay/EPaperDisplay.c
#: shared-bindings/framebufferio/FramebufferDisplay.c
Expand Down Expand Up @@ -1144,6 +1151,7 @@ msgstr ""
msgid "Generic Failure"
msgstr ""

#: ports/zephyr-cp/bindings/zephyr_display/Display.c
#: shared-bindings/framebufferio/FramebufferDisplay.c
#: shared-module/busdisplay/BusDisplay.c
#: shared-module/framebufferio/FramebufferDisplay.c
Expand Down Expand Up @@ -2407,6 +2415,10 @@ msgstr ""
msgid "Update failed"
msgstr ""

#: ports/zephyr-cp/bindings/zephyr_display/Display.c
msgid "Use board.DISPLAY"
msgstr ""

#: ports/zephyr-cp/common-hal/busio/I2C.c
#: ports/zephyr-cp/common-hal/busio/SPI.c
#: ports/zephyr-cp/common-hal/busio/UART.c
Expand Down
1 change: 1 addition & 0 deletions ports/zephyr-cp/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@AGENTS.md
28 changes: 24 additions & 4 deletions ports/zephyr-cp/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,24 @@ BUILD ?= build-$(BOARD)

TRANSLATION ?= en_US

.DEFAULT_GOAL := $(BUILD)/zephyr-cp/zephyr/zephyr.elf
# Compute shield args once. Command-line SHIELD/SHIELDS values override board defaults from circuitpython.toml.
ifneq ($(strip $(BOARD)),)
WEST_SHIELD_ARGS := $(shell SHIELD_ORIGIN="$(origin SHIELD)" SHIELDS_ORIGIN="$(origin SHIELDS)" SHIELD="$(SHIELD)" SHIELDS="$(SHIELDS)" python cptools/get_west_shield_args.py $(BOARD))
endif

.PHONY: $(BUILD)/zephyr-cp/zephyr/zephyr.elf flash recover debug run run-sim clean menuconfig all clean-all test fetch-port-submodules
WEST_CMAKE_ARGS := -DZEPHYR_BOARD_ALIASES=$(CURDIR)/boards/board_aliases.cmake -Dzephyr-cp_TRANSLATION=$(TRANSLATION)

# When DEBUG=1, apply additional Kconfig fragments for debug-friendly settings.
DEBUG_CONF_FILE ?= $(CURDIR)/debug.conf
ifeq ($(DEBUG),1)
WEST_CMAKE_ARGS += -Dzephyr-cp_EXTRA_CONF_FILE=$(DEBUG_CONF_FILE)
endif

.PHONY: $(BUILD)/zephyr-cp/zephyr/zephyr.elf flash recover debug debug-jlink debugserver attach run run-sim clean menuconfig all clean-all test fetch-port-submodules

$(BUILD)/zephyr-cp/zephyr/zephyr.elf:
python cptools/pre_zephyr_build_prep.py $(BOARD)
west build -b $(BOARD) -d $(BUILD) --sysbuild -- -DZEPHYR_BOARD_ALIASES=$(CURDIR)/boards/board_aliases.cmake -Dzephyr-cp_TRANSLATION=$(TRANSLATION)
west build -b $(BOARD) -d $(BUILD) $(WEST_SHIELD_ARGS) --sysbuild -- $(WEST_CMAKE_ARGS)

$(BUILD)/firmware.elf: $(BUILD)/zephyr-cp/zephyr/zephyr.elf
cp $^ $@
Expand All @@ -37,6 +48,15 @@ recover: $(BUILD)/zephyr-cp/zephyr/zephyr.elf
debug: $(BUILD)/zephyr-cp/zephyr/zephyr.elf
west debug -d $(BUILD)

debug-jlink: $(BUILD)/zephyr-cp/zephyr/zephyr.elf
west debug --runner jlink -d $(BUILD)

debugserver: $(BUILD)/zephyr-cp/zephyr/zephyr.elf
west debugserver -d $(BUILD)

attach: $(BUILD)/zephyr-cp/zephyr/zephyr.elf
west attach -d $(BUILD)

run: $(BUILD)/firmware.exe
$^

Expand All @@ -51,7 +71,7 @@ run-sim:
build-native_native_sim/firmware.exe --flash=build-native_native_sim/flash.bin --flash_rm -wait_uart -rt

menuconfig:
west build --sysbuild -d $(BUILD) -t menuconfig
west build $(WEST_SHIELD_ARGS) --sysbuild -d $(BUILD) -t menuconfig -- $(WEST_CMAKE_ARGS)

clean:
rm -rf $(BUILD)
Expand Down
30 changes: 30 additions & 0 deletions ports/zephyr-cp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,36 @@ If a local `./CIRCUITPY/` folder exists, its files are used as the simulator's C

Edit files in `./CIRCUITPY` (for example `code.py`) and rerun `make run-sim` to test changes.

## Shields

Board defaults can be set in `boards/<vendor>/<board>/circuitpython.toml`:

```toml
SHIELDS = ["shield1", "shield2"]
```

For example, `boards/renesas/ek_ra8d1/circuitpython.toml` enables:

```toml
SHIELDS = ["rtkmipilcdb00000be"]
```

You can override shield selection from the command line:

```sh
# Single shield
make BOARD=renesas_ek_ra8d1 SHIELD=rtkmipilcdb00000be

# Multiple shields (comma, semicolon, or space separated)
make BOARD=my_vendor_my_board SHIELDS="shield1,shield2"
```

Behavior and precedence:

- If `SHIELD` or `SHIELDS` is explicitly provided, it overrides board defaults.
- If neither is provided, defaults from `circuitpython.toml` are used.
- Use `SHIELD=` (empty) to disable a board default shield for one build.

## Testing other boards

[Any Zephyr board](https://docs.zephyrproject.org/latest/boards/index.html#) can
Expand Down
195 changes: 195 additions & 0 deletions ports/zephyr-cp/bindings/zephyr_display/Display.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
// This file is part of the CircuitPython project: https://circuitpython.org
//
// SPDX-FileCopyrightText: Copyright (c) 2026 Scott Shawcroft for Adafruit Industries
//
// SPDX-License-Identifier: MIT

#include "bindings/zephyr_display/Display.h"

#include "py/objproperty.h"
#include "py/objtype.h"
#include "py/runtime.h"
#include "shared-bindings/displayio/Group.h"
#include "shared-module/displayio/__init__.h"

static mp_obj_t zephyr_display_display_make_new(const mp_obj_type_t *type,
size_t n_args,
size_t n_kw,
const mp_obj_t *all_args) {
(void)type;
(void)n_args;
(void)n_kw;
(void)all_args;
mp_raise_NotImplementedError(MP_ERROR_TEXT("Use board.DISPLAY"));
return mp_const_none;
}

static zephyr_display_display_obj_t *native_display(mp_obj_t display_obj) {
mp_obj_t native = mp_obj_cast_to_native_base(display_obj, &zephyr_display_display_type);
mp_obj_assert_native_inited(native);
return MP_OBJ_TO_PTR(native);
}

static mp_obj_t zephyr_display_display_obj_show(mp_obj_t self_in, mp_obj_t group_in) {
(void)self_in;
(void)group_in;
mp_raise_AttributeError(MP_ERROR_TEXT(".show(x) removed. Use .root_group = x"));
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_2(zephyr_display_display_show_obj, zephyr_display_display_obj_show);

static mp_obj_t zephyr_display_display_obj_refresh(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
enum {
ARG_target_frames_per_second,
ARG_minimum_frames_per_second,
};
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_target_frames_per_second, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = mp_const_none} },
{ MP_QSTR_minimum_frames_per_second, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} },
};

mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);

zephyr_display_display_obj_t *self = native_display(pos_args[0]);

uint32_t maximum_ms_per_real_frame = NO_FPS_LIMIT;
mp_int_t minimum_frames_per_second = args[ARG_minimum_frames_per_second].u_int;
if (minimum_frames_per_second > 0) {
maximum_ms_per_real_frame = 1000 / minimum_frames_per_second;
}

uint32_t target_ms_per_frame;
if (args[ARG_target_frames_per_second].u_obj == mp_const_none) {
target_ms_per_frame = NO_FPS_LIMIT;
} else {
target_ms_per_frame = 1000 / mp_obj_get_int(args[ARG_target_frames_per_second].u_obj);
}

return mp_obj_new_bool(common_hal_zephyr_display_display_refresh(
self,
target_ms_per_frame,
maximum_ms_per_real_frame));
}
MP_DEFINE_CONST_FUN_OBJ_KW(zephyr_display_display_refresh_obj, 1, zephyr_display_display_obj_refresh);

static mp_obj_t zephyr_display_display_obj_get_auto_refresh(mp_obj_t self_in) {
zephyr_display_display_obj_t *self = native_display(self_in);
return mp_obj_new_bool(common_hal_zephyr_display_display_get_auto_refresh(self));
}
MP_DEFINE_CONST_FUN_OBJ_1(zephyr_display_display_get_auto_refresh_obj, zephyr_display_display_obj_get_auto_refresh);

static mp_obj_t zephyr_display_display_obj_set_auto_refresh(mp_obj_t self_in, mp_obj_t auto_refresh) {
zephyr_display_display_obj_t *self = native_display(self_in);
common_hal_zephyr_display_display_set_auto_refresh(self, mp_obj_is_true(auto_refresh));
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_2(zephyr_display_display_set_auto_refresh_obj, zephyr_display_display_obj_set_auto_refresh);

MP_PROPERTY_GETSET(zephyr_display_display_auto_refresh_obj,
(mp_obj_t)&zephyr_display_display_get_auto_refresh_obj,
(mp_obj_t)&zephyr_display_display_set_auto_refresh_obj);

static mp_obj_t zephyr_display_display_obj_get_brightness(mp_obj_t self_in) {
zephyr_display_display_obj_t *self = native_display(self_in);
mp_float_t brightness = common_hal_zephyr_display_display_get_brightness(self);
if (brightness < 0) {
mp_raise_RuntimeError(MP_ERROR_TEXT("Brightness not adjustable"));
}
return mp_obj_new_float(brightness);
}
MP_DEFINE_CONST_FUN_OBJ_1(zephyr_display_display_get_brightness_obj, zephyr_display_display_obj_get_brightness);

static mp_obj_t zephyr_display_display_obj_set_brightness(mp_obj_t self_in, mp_obj_t brightness_obj) {
zephyr_display_display_obj_t *self = native_display(self_in);
mp_float_t brightness = mp_obj_get_float(brightness_obj);
if (brightness < 0.0f || brightness > 1.0f) {
mp_raise_ValueError_varg(MP_ERROR_TEXT("%q must be %d-%d"), MP_QSTR_brightness, 0, 1);
}
bool ok = common_hal_zephyr_display_display_set_brightness(self, brightness);
if (!ok) {
mp_raise_RuntimeError(MP_ERROR_TEXT("Brightness not adjustable"));
}
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_2(zephyr_display_display_set_brightness_obj, zephyr_display_display_obj_set_brightness);

MP_PROPERTY_GETSET(zephyr_display_display_brightness_obj,
(mp_obj_t)&zephyr_display_display_get_brightness_obj,
(mp_obj_t)&zephyr_display_display_set_brightness_obj);

static mp_obj_t zephyr_display_display_obj_get_width(mp_obj_t self_in) {
zephyr_display_display_obj_t *self = native_display(self_in);
return MP_OBJ_NEW_SMALL_INT(common_hal_zephyr_display_display_get_width(self));
}
MP_DEFINE_CONST_FUN_OBJ_1(zephyr_display_display_get_width_obj, zephyr_display_display_obj_get_width);
MP_PROPERTY_GETTER(zephyr_display_display_width_obj, (mp_obj_t)&zephyr_display_display_get_width_obj);

static mp_obj_t zephyr_display_display_obj_get_height(mp_obj_t self_in) {
zephyr_display_display_obj_t *self = native_display(self_in);
return MP_OBJ_NEW_SMALL_INT(common_hal_zephyr_display_display_get_height(self));
}
MP_DEFINE_CONST_FUN_OBJ_1(zephyr_display_display_get_height_obj, zephyr_display_display_obj_get_height);
MP_PROPERTY_GETTER(zephyr_display_display_height_obj, (mp_obj_t)&zephyr_display_display_get_height_obj);

static mp_obj_t zephyr_display_display_obj_get_rotation(mp_obj_t self_in) {
zephyr_display_display_obj_t *self = native_display(self_in);
return MP_OBJ_NEW_SMALL_INT(common_hal_zephyr_display_display_get_rotation(self));
}
MP_DEFINE_CONST_FUN_OBJ_1(zephyr_display_display_get_rotation_obj, zephyr_display_display_obj_get_rotation);

static mp_obj_t zephyr_display_display_obj_set_rotation(mp_obj_t self_in, mp_obj_t value) {
zephyr_display_display_obj_t *self = native_display(self_in);
common_hal_zephyr_display_display_set_rotation(self, mp_obj_get_int(value));
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_2(zephyr_display_display_set_rotation_obj, zephyr_display_display_obj_set_rotation);

MP_PROPERTY_GETSET(zephyr_display_display_rotation_obj,
(mp_obj_t)&zephyr_display_display_get_rotation_obj,
(mp_obj_t)&zephyr_display_display_set_rotation_obj);

static mp_obj_t zephyr_display_display_obj_get_root_group(mp_obj_t self_in) {
zephyr_display_display_obj_t *self = native_display(self_in);
return common_hal_zephyr_display_display_get_root_group(self);
}
MP_DEFINE_CONST_FUN_OBJ_1(zephyr_display_display_get_root_group_obj, zephyr_display_display_obj_get_root_group);

static mp_obj_t zephyr_display_display_obj_set_root_group(mp_obj_t self_in, mp_obj_t group_in) {
zephyr_display_display_obj_t *self = native_display(self_in);
displayio_group_t *group = NULL;
if (group_in != mp_const_none) {
group = MP_OBJ_TO_PTR(native_group(group_in));
}

bool ok = common_hal_zephyr_display_display_set_root_group(self, group);
if (!ok) {
mp_raise_ValueError(MP_ERROR_TEXT("Group already used"));
}
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_2(zephyr_display_display_set_root_group_obj, zephyr_display_display_obj_set_root_group);

MP_PROPERTY_GETSET(zephyr_display_display_root_group_obj,
(mp_obj_t)&zephyr_display_display_get_root_group_obj,
(mp_obj_t)&zephyr_display_display_set_root_group_obj);

static const mp_rom_map_elem_t zephyr_display_display_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR_show), MP_ROM_PTR(&zephyr_display_display_show_obj) },
{ MP_ROM_QSTR(MP_QSTR_refresh), MP_ROM_PTR(&zephyr_display_display_refresh_obj) },

{ MP_ROM_QSTR(MP_QSTR_auto_refresh), MP_ROM_PTR(&zephyr_display_display_auto_refresh_obj) },
{ MP_ROM_QSTR(MP_QSTR_brightness), MP_ROM_PTR(&zephyr_display_display_brightness_obj) },
{ MP_ROM_QSTR(MP_QSTR_width), MP_ROM_PTR(&zephyr_display_display_width_obj) },
{ MP_ROM_QSTR(MP_QSTR_height), MP_ROM_PTR(&zephyr_display_display_height_obj) },
{ MP_ROM_QSTR(MP_QSTR_rotation), MP_ROM_PTR(&zephyr_display_display_rotation_obj) },
{ MP_ROM_QSTR(MP_QSTR_root_group), MP_ROM_PTR(&zephyr_display_display_root_group_obj) },
};
static MP_DEFINE_CONST_DICT(zephyr_display_display_locals_dict, zephyr_display_display_locals_dict_table);

MP_DEFINE_CONST_OBJ_TYPE(
zephyr_display_display_type,
MP_QSTR_Display,
MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS,
make_new, zephyr_display_display_make_new,
locals_dict, &zephyr_display_display_locals_dict);
Loading
Loading