Skip to content

Commit f34f68b

Browse files
committed
Add support for fixed Zephyr displays
This adds zephyr_display. It acts similar to BusDisplay because we write regions to update to Zephyr.
1 parent 8ad3a2f commit f34f68b

74 files changed

Lines changed: 1797 additions & 74 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/actions/deps/ports/zephyr-cp/action.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ name: Fetch Zephyr port deps
33
runs:
44
using: composite
55
steps:
6-
- name: Get libusb and mtools
6+
- name: Get Linux build dependencies
77
if: runner.os == 'Linux'
88
run: |
9+
sudo dpkg --add-architecture i386
910
sudo apt-get update
10-
sudo apt-get install -y libusb-1.0-0-dev libudev-dev mtools
11+
sudo apt-get install -y libusb-1.0-0-dev libudev-dev pkg-config libsdl2-dev:i386 libsdl2-image-dev:i386 mtools
12+
echo "PKG_CONFIG_PATH=/usr/lib/i386-linux-gnu/pkgconfig${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH}" >> $GITHUB_ENV
1113
shell: bash
1214
- name: Setup Zephyr project
1315
uses: zephyrproject-rtos/action-zephyr-setup@v1

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
- Capture CircuitPython output by finding the matching device in `/dev/serial/by-id`
22
- You can mount the CIRCUITPY drive by doing `udisksctl mount -b /dev/disk/by-label/CIRCUITPY` and access it via `/run/media/<user>/CIRCUITPY`.
33
- `circup` is a command line tool to install libraries and examples to CIRCUITPY.
4+
- When connecting to serial devices on Linux use /dev/serial/by-id. These will be more stable than /dev/ttyACM*.

locale/circuitpython.pot

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,8 +166,8 @@ msgstr ""
166166
msgid "%q must be %d"
167167
msgstr ""
168168

169-
#: py/argcheck.c shared-bindings/busdisplay/BusDisplay.c
170-
#: shared-bindings/displayio/Bitmap.c
169+
#: ports/zephyr-cp/bindings/zephyr_display/Display.c py/argcheck.c
170+
#: shared-bindings/busdisplay/BusDisplay.c shared-bindings/displayio/Bitmap.c
171171
#: shared-bindings/framebufferio/FramebufferDisplay.c
172172
#: shared-bindings/is31fl3741/FrameBuffer.c
173173
#: shared-bindings/rgbmatrix/RGBMatrix.c
@@ -459,6 +459,7 @@ msgstr ""
459459
msgid ", in %q\n"
460460
msgstr ""
461461

462+
#: ports/zephyr-cp/bindings/zephyr_display/Display.c
462463
#: shared-bindings/busdisplay/BusDisplay.c
463464
#: shared-bindings/epaperdisplay/EPaperDisplay.c
464465
#: shared-bindings/framebufferio/FramebufferDisplay.c
@@ -647,6 +648,7 @@ msgstr ""
647648
msgid "Baudrate not supported by peripheral"
648649
msgstr ""
649650

651+
#: ports/zephyr-cp/common-hal/zephyr_display/Display.c
650652
#: shared-module/busdisplay/BusDisplay.c
651653
#: shared-module/framebufferio/FramebufferDisplay.c
652654
msgid "Below minimum frame rate"
@@ -669,6 +671,7 @@ msgstr ""
669671
msgid "Both RX and TX required for flow control"
670672
msgstr ""
671673

674+
#: ports/zephyr-cp/bindings/zephyr_display/Display.c
672675
#: shared-bindings/busdisplay/BusDisplay.c
673676
#: shared-bindings/framebufferio/FramebufferDisplay.c
674677
msgid "Brightness not adjustable"
@@ -934,6 +937,10 @@ msgstr ""
934937
msgid "Display must have a 16 bit colorspace."
935938
msgstr ""
936939

940+
#: ports/zephyr-cp/common-hal/zephyr_display/Display.c
941+
msgid "Display not ready"
942+
msgstr ""
943+
937944
#: shared-bindings/busdisplay/BusDisplay.c
938945
#: shared-bindings/epaperdisplay/EPaperDisplay.c
939946
#: shared-bindings/framebufferio/FramebufferDisplay.c
@@ -1144,6 +1151,7 @@ msgstr ""
11441151
msgid "Generic Failure"
11451152
msgstr ""
11461153

1154+
#: ports/zephyr-cp/bindings/zephyr_display/Display.c
11471155
#: shared-bindings/framebufferio/FramebufferDisplay.c
11481156
#: shared-module/busdisplay/BusDisplay.c
11491157
#: shared-module/framebufferio/FramebufferDisplay.c
@@ -2406,6 +2414,10 @@ msgstr ""
24062414
msgid "Update failed"
24072415
msgstr ""
24082416

2417+
#: ports/zephyr-cp/bindings/zephyr_display/Display.c
2418+
msgid "Use board.DISPLAY"
2419+
msgstr ""
2420+
24092421
#: ports/zephyr-cp/common-hal/busio/I2C.c
24102422
#: ports/zephyr-cp/common-hal/busio/SPI.c
24112423
#: ports/zephyr-cp/common-hal/busio/UART.c

ports/zephyr-cp/CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
@AGENTS.md

ports/zephyr-cp/Makefile

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,24 @@ BUILD ?= build-$(BOARD)
88

99
TRANSLATION ?= en_US
1010

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

13-
.PHONY: $(BUILD)/zephyr-cp/zephyr/zephyr.elf flash recover debug run run-sim clean menuconfig all clean-all test fetch-port-submodules
16+
WEST_CMAKE_ARGS := -DZEPHYR_BOARD_ALIASES=$(CURDIR)/boards/board_aliases.cmake -Dzephyr-cp_TRANSLATION=$(TRANSLATION)
17+
18+
# When DEBUG=1, apply additional Kconfig fragments for debug-friendly settings.
19+
DEBUG_CONF_FILE ?= $(CURDIR)/debug.conf
20+
ifeq ($(DEBUG),1)
21+
WEST_CMAKE_ARGS += -Dzephyr-cp_EXTRA_CONF_FILE=$(DEBUG_CONF_FILE)
22+
endif
23+
24+
.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
1425

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

1930
$(BUILD)/firmware.elf: $(BUILD)/zephyr-cp/zephyr/zephyr.elf
2031
cp $^ $@
@@ -37,6 +48,15 @@ recover: $(BUILD)/zephyr-cp/zephyr/zephyr.elf
3748
debug: $(BUILD)/zephyr-cp/zephyr/zephyr.elf
3849
west debug -d $(BUILD)
3950

51+
debug-jlink: $(BUILD)/zephyr-cp/zephyr/zephyr.elf
52+
west debug --runner jlink -d $(BUILD)
53+
54+
debugserver: $(BUILD)/zephyr-cp/zephyr/zephyr.elf
55+
west debugserver -d $(BUILD)
56+
57+
attach: $(BUILD)/zephyr-cp/zephyr/zephyr.elf
58+
west attach -d $(BUILD)
59+
4060
run: $(BUILD)/firmware.exe
4161
$^
4262

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

5373
menuconfig:
54-
west build --sysbuild -d $(BUILD) -t menuconfig
74+
west build $(WEST_SHIELD_ARGS) --sysbuild -d $(BUILD) -t menuconfig -- $(WEST_CMAKE_ARGS)
5575

5676
clean:
5777
rm -rf $(BUILD)

ports/zephyr-cp/README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,36 @@ If a local `./CIRCUITPY/` folder exists, its files are used as the simulator's C
4242

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

45+
## Shields
46+
47+
Board defaults can be set in `boards/<vendor>/<board>/circuitpython.toml`:
48+
49+
```toml
50+
SHIELDS = ["shield1", "shield2"]
51+
```
52+
53+
For example, `boards/renesas/ek_ra8d1/circuitpython.toml` enables:
54+
55+
```toml
56+
SHIELDS = ["rtkmipilcdb00000be"]
57+
```
58+
59+
You can override shield selection from the command line:
60+
61+
```sh
62+
# Single shield
63+
make BOARD=renesas_ek_ra8d1 SHIELD=rtkmipilcdb00000be
64+
65+
# Multiple shields (comma, semicolon, or space separated)
66+
make BOARD=my_vendor_my_board SHIELDS="shield1,shield2"
67+
```
68+
69+
Behavior and precedence:
70+
71+
- If `SHIELD` or `SHIELDS` is explicitly provided, it overrides board defaults.
72+
- If neither is provided, defaults from `circuitpython.toml` are used.
73+
- Use `SHIELD=` (empty) to disable a board default shield for one build.
74+
4575
## Testing other boards
4676

4777
[Any Zephyr board](https://docs.zephyrproject.org/latest/boards/index.html#) can
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
// This file is part of the CircuitPython project: https://circuitpython.org
2+
//
3+
// SPDX-FileCopyrightText: Copyright (c) 2026 Scott Shawcroft for Adafruit Industries
4+
//
5+
// SPDX-License-Identifier: MIT
6+
7+
#include "bindings/zephyr_display/Display.h"
8+
9+
#include "py/objproperty.h"
10+
#include "py/objtype.h"
11+
#include "py/runtime.h"
12+
#include "shared-bindings/displayio/Group.h"
13+
#include "shared-module/displayio/__init__.h"
14+
15+
static mp_obj_t zephyr_display_display_make_new(const mp_obj_type_t *type,
16+
size_t n_args,
17+
size_t n_kw,
18+
const mp_obj_t *all_args) {
19+
(void)type;
20+
(void)n_args;
21+
(void)n_kw;
22+
(void)all_args;
23+
mp_raise_NotImplementedError(MP_ERROR_TEXT("Use board.DISPLAY"));
24+
return mp_const_none;
25+
}
26+
27+
static zephyr_display_display_obj_t *native_display(mp_obj_t display_obj) {
28+
mp_obj_t native = mp_obj_cast_to_native_base(display_obj, &zephyr_display_display_type);
29+
mp_obj_assert_native_inited(native);
30+
return MP_OBJ_TO_PTR(native);
31+
}
32+
33+
static mp_obj_t zephyr_display_display_obj_show(mp_obj_t self_in, mp_obj_t group_in) {
34+
(void)self_in;
35+
(void)group_in;
36+
mp_raise_AttributeError(MP_ERROR_TEXT(".show(x) removed. Use .root_group = x"));
37+
return mp_const_none;
38+
}
39+
MP_DEFINE_CONST_FUN_OBJ_2(zephyr_display_display_show_obj, zephyr_display_display_obj_show);
40+
41+
static mp_obj_t zephyr_display_display_obj_refresh(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
42+
enum {
43+
ARG_target_frames_per_second,
44+
ARG_minimum_frames_per_second,
45+
};
46+
static const mp_arg_t allowed_args[] = {
47+
{ MP_QSTR_target_frames_per_second, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = mp_const_none} },
48+
{ MP_QSTR_minimum_frames_per_second, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} },
49+
};
50+
51+
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
52+
mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
53+
54+
zephyr_display_display_obj_t *self = native_display(pos_args[0]);
55+
56+
uint32_t maximum_ms_per_real_frame = NO_FPS_LIMIT;
57+
mp_int_t minimum_frames_per_second = args[ARG_minimum_frames_per_second].u_int;
58+
if (minimum_frames_per_second > 0) {
59+
maximum_ms_per_real_frame = 1000 / minimum_frames_per_second;
60+
}
61+
62+
uint32_t target_ms_per_frame;
63+
if (args[ARG_target_frames_per_second].u_obj == mp_const_none) {
64+
target_ms_per_frame = NO_FPS_LIMIT;
65+
} else {
66+
target_ms_per_frame = 1000 / mp_obj_get_int(args[ARG_target_frames_per_second].u_obj);
67+
}
68+
69+
return mp_obj_new_bool(common_hal_zephyr_display_display_refresh(
70+
self,
71+
target_ms_per_frame,
72+
maximum_ms_per_real_frame));
73+
}
74+
MP_DEFINE_CONST_FUN_OBJ_KW(zephyr_display_display_refresh_obj, 1, zephyr_display_display_obj_refresh);
75+
76+
static mp_obj_t zephyr_display_display_obj_get_auto_refresh(mp_obj_t self_in) {
77+
zephyr_display_display_obj_t *self = native_display(self_in);
78+
return mp_obj_new_bool(common_hal_zephyr_display_display_get_auto_refresh(self));
79+
}
80+
MP_DEFINE_CONST_FUN_OBJ_1(zephyr_display_display_get_auto_refresh_obj, zephyr_display_display_obj_get_auto_refresh);
81+
82+
static mp_obj_t zephyr_display_display_obj_set_auto_refresh(mp_obj_t self_in, mp_obj_t auto_refresh) {
83+
zephyr_display_display_obj_t *self = native_display(self_in);
84+
common_hal_zephyr_display_display_set_auto_refresh(self, mp_obj_is_true(auto_refresh));
85+
return mp_const_none;
86+
}
87+
MP_DEFINE_CONST_FUN_OBJ_2(zephyr_display_display_set_auto_refresh_obj, zephyr_display_display_obj_set_auto_refresh);
88+
89+
MP_PROPERTY_GETSET(zephyr_display_display_auto_refresh_obj,
90+
(mp_obj_t)&zephyr_display_display_get_auto_refresh_obj,
91+
(mp_obj_t)&zephyr_display_display_set_auto_refresh_obj);
92+
93+
static mp_obj_t zephyr_display_display_obj_get_brightness(mp_obj_t self_in) {
94+
zephyr_display_display_obj_t *self = native_display(self_in);
95+
mp_float_t brightness = common_hal_zephyr_display_display_get_brightness(self);
96+
if (brightness < 0) {
97+
mp_raise_RuntimeError(MP_ERROR_TEXT("Brightness not adjustable"));
98+
}
99+
return mp_obj_new_float(brightness);
100+
}
101+
MP_DEFINE_CONST_FUN_OBJ_1(zephyr_display_display_get_brightness_obj, zephyr_display_display_obj_get_brightness);
102+
103+
static mp_obj_t zephyr_display_display_obj_set_brightness(mp_obj_t self_in, mp_obj_t brightness_obj) {
104+
zephyr_display_display_obj_t *self = native_display(self_in);
105+
mp_float_t brightness = mp_obj_get_float(brightness_obj);
106+
if (brightness < 0.0f || brightness > 1.0f) {
107+
mp_raise_ValueError_varg(MP_ERROR_TEXT("%q must be %d-%d"), MP_QSTR_brightness, 0, 1);
108+
}
109+
bool ok = common_hal_zephyr_display_display_set_brightness(self, brightness);
110+
if (!ok) {
111+
mp_raise_RuntimeError(MP_ERROR_TEXT("Brightness not adjustable"));
112+
}
113+
return mp_const_none;
114+
}
115+
MP_DEFINE_CONST_FUN_OBJ_2(zephyr_display_display_set_brightness_obj, zephyr_display_display_obj_set_brightness);
116+
117+
MP_PROPERTY_GETSET(zephyr_display_display_brightness_obj,
118+
(mp_obj_t)&zephyr_display_display_get_brightness_obj,
119+
(mp_obj_t)&zephyr_display_display_set_brightness_obj);
120+
121+
static mp_obj_t zephyr_display_display_obj_get_width(mp_obj_t self_in) {
122+
zephyr_display_display_obj_t *self = native_display(self_in);
123+
return MP_OBJ_NEW_SMALL_INT(common_hal_zephyr_display_display_get_width(self));
124+
}
125+
MP_DEFINE_CONST_FUN_OBJ_1(zephyr_display_display_get_width_obj, zephyr_display_display_obj_get_width);
126+
MP_PROPERTY_GETTER(zephyr_display_display_width_obj, (mp_obj_t)&zephyr_display_display_get_width_obj);
127+
128+
static mp_obj_t zephyr_display_display_obj_get_height(mp_obj_t self_in) {
129+
zephyr_display_display_obj_t *self = native_display(self_in);
130+
return MP_OBJ_NEW_SMALL_INT(common_hal_zephyr_display_display_get_height(self));
131+
}
132+
MP_DEFINE_CONST_FUN_OBJ_1(zephyr_display_display_get_height_obj, zephyr_display_display_obj_get_height);
133+
MP_PROPERTY_GETTER(zephyr_display_display_height_obj, (mp_obj_t)&zephyr_display_display_get_height_obj);
134+
135+
static mp_obj_t zephyr_display_display_obj_get_rotation(mp_obj_t self_in) {
136+
zephyr_display_display_obj_t *self = native_display(self_in);
137+
return MP_OBJ_NEW_SMALL_INT(common_hal_zephyr_display_display_get_rotation(self));
138+
}
139+
MP_DEFINE_CONST_FUN_OBJ_1(zephyr_display_display_get_rotation_obj, zephyr_display_display_obj_get_rotation);
140+
141+
static mp_obj_t zephyr_display_display_obj_set_rotation(mp_obj_t self_in, mp_obj_t value) {
142+
zephyr_display_display_obj_t *self = native_display(self_in);
143+
common_hal_zephyr_display_display_set_rotation(self, mp_obj_get_int(value));
144+
return mp_const_none;
145+
}
146+
MP_DEFINE_CONST_FUN_OBJ_2(zephyr_display_display_set_rotation_obj, zephyr_display_display_obj_set_rotation);
147+
148+
MP_PROPERTY_GETSET(zephyr_display_display_rotation_obj,
149+
(mp_obj_t)&zephyr_display_display_get_rotation_obj,
150+
(mp_obj_t)&zephyr_display_display_set_rotation_obj);
151+
152+
static mp_obj_t zephyr_display_display_obj_get_root_group(mp_obj_t self_in) {
153+
zephyr_display_display_obj_t *self = native_display(self_in);
154+
return common_hal_zephyr_display_display_get_root_group(self);
155+
}
156+
MP_DEFINE_CONST_FUN_OBJ_1(zephyr_display_display_get_root_group_obj, zephyr_display_display_obj_get_root_group);
157+
158+
static mp_obj_t zephyr_display_display_obj_set_root_group(mp_obj_t self_in, mp_obj_t group_in) {
159+
zephyr_display_display_obj_t *self = native_display(self_in);
160+
displayio_group_t *group = NULL;
161+
if (group_in != mp_const_none) {
162+
group = MP_OBJ_TO_PTR(native_group(group_in));
163+
}
164+
165+
bool ok = common_hal_zephyr_display_display_set_root_group(self, group);
166+
if (!ok) {
167+
mp_raise_ValueError(MP_ERROR_TEXT("Group already used"));
168+
}
169+
return mp_const_none;
170+
}
171+
MP_DEFINE_CONST_FUN_OBJ_2(zephyr_display_display_set_root_group_obj, zephyr_display_display_obj_set_root_group);
172+
173+
MP_PROPERTY_GETSET(zephyr_display_display_root_group_obj,
174+
(mp_obj_t)&zephyr_display_display_get_root_group_obj,
175+
(mp_obj_t)&zephyr_display_display_set_root_group_obj);
176+
177+
static const mp_rom_map_elem_t zephyr_display_display_locals_dict_table[] = {
178+
{ MP_ROM_QSTR(MP_QSTR_show), MP_ROM_PTR(&zephyr_display_display_show_obj) },
179+
{ MP_ROM_QSTR(MP_QSTR_refresh), MP_ROM_PTR(&zephyr_display_display_refresh_obj) },
180+
181+
{ MP_ROM_QSTR(MP_QSTR_auto_refresh), MP_ROM_PTR(&zephyr_display_display_auto_refresh_obj) },
182+
{ MP_ROM_QSTR(MP_QSTR_brightness), MP_ROM_PTR(&zephyr_display_display_brightness_obj) },
183+
{ MP_ROM_QSTR(MP_QSTR_width), MP_ROM_PTR(&zephyr_display_display_width_obj) },
184+
{ MP_ROM_QSTR(MP_QSTR_height), MP_ROM_PTR(&zephyr_display_display_height_obj) },
185+
{ MP_ROM_QSTR(MP_QSTR_rotation), MP_ROM_PTR(&zephyr_display_display_rotation_obj) },
186+
{ MP_ROM_QSTR(MP_QSTR_root_group), MP_ROM_PTR(&zephyr_display_display_root_group_obj) },
187+
};
188+
static MP_DEFINE_CONST_DICT(zephyr_display_display_locals_dict, zephyr_display_display_locals_dict_table);
189+
190+
MP_DEFINE_CONST_OBJ_TYPE(
191+
zephyr_display_display_type,
192+
MP_QSTR_Display,
193+
MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS,
194+
make_new, zephyr_display_display_make_new,
195+
locals_dict, &zephyr_display_display_locals_dict);

0 commit comments

Comments
 (0)