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
2 changes: 0 additions & 2 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
# https://EditorConfig.org

root = true

[*]
Expand Down
4 changes: 0 additions & 4 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,6 @@
*.ogv filter=lfs diff=lfs merge=lfs -text

# Common data transfer formats
#*.csv filter=lfs diff=lfs merge=lfs -text
#*.tsv filter=lfs diff=lfs merge=lfs -text
#*.json filter=lfs diff=lfs merge=lfs -text
#*.xml filter=lfs diff=lfs merge=lfs -text
*.parquet filter=lfs diff=lfs merge=lfs -text
*.feather filter=lfs diff=lfs merge=lfs -text
*.msgpack filter=lfs diff=lfs merge=lfs -text
Expand Down
13 changes: 2 additions & 11 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ permissions:
contents: read

jobs:
# Repository checks: the Git hooks and the Nix flake.
checks:
runs-on: ubuntu-latest
timeout-minutes: 20
Expand All @@ -45,12 +44,7 @@ jobs:
- name: Evaluate the Nix flake
run: nix flake check --no-build

# NuttX builds with the apt toolchains. They prove the submodules, the
# system dependencies, and the build flow stay working. Ubuntu ships
# gcc-riscv64-unknown-elf with the rv32imac soft-float multilib the
# Espressif boards need, under the name their board configuration asks for,
# so no toolchain override belongs here. The boards themselves are not in
# CI, so nothing is flashed or run.
# Builds NuttX images for configured boards to verify build pipeline.
nuttx-build:
runs-on: ubuntu-latest
timeout-minutes: 45
Expand All @@ -72,10 +66,7 @@ jobs:
- name: Install system dependencies
run: make setup-deps

# Ubuntu ships gcc-riscv64-unknown-elf without a C library, and the
# Espressif HAL needs sys/lock.h from one. This toolchain carries newlib.
# It is pinned to the GCC 14 release on purpose, because GCC 15 and newer
# default to C23, which removed the ATOMIC_VAR_INIT macro that the HAL still uses.
# Pinned GCC 14 toolchain with newlib required for ESP32-P4 HAL.
- name: Install the Espressif RISC-V toolchain
if: matrix.name == 'ESP32-P4'
env:
Expand Down
13 changes: 2 additions & 11 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,10 @@ venv/
.Python
build/

# ESP-IDF fetches managed components into each project directory, and the path
# is not configurable. The pin is dependencies.lock, which is committed, so the
# fetched source stays out of the repository the same way the uv virtual
# environment does.
# ESP-IDF managed component sources (pinned by dependencies.lock)
managed_components/

# NuttX builds an out-of-tree application where its sources live, because it is
# reached through the apps/external symlink. So object files and dependency
# lists land under experiments/ rather than under external/ like every other
# build product. The object file names embed an absolute path, which is a second
# reason they must never be committed.
# NuttX out-of-tree application build outputs
experiments/**/*.o
experiments/**/Make.dep
experiments/**/.depend
Expand Down Expand Up @@ -88,7 +81,6 @@ out2/
*.wal
*.sqlite

# Dependency lock files (uncomment to ignore)
poetry.lock

# Documentation files
Expand All @@ -100,7 +92,6 @@ result-*
.nix-devshell-profile*

# Miscellaneous files and directories to ignore
# Add any additional file patterns a directory names that should be ignored down here
.env
.claude/
.codex
Expand Down
1 change: 1 addition & 0 deletions .python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.14
281 changes: 79 additions & 202 deletions AGENTS.md

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion CLAUDE.md

This file was deleted.

69 changes: 14 additions & 55 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,83 +1,54 @@
NUTTX_DIR ?= external/nuttx
BOARD ?= esp32p4-function-ev-board:nsh
# The RISC-V toolchain in the dev shell is built from source, so a garbage
# collection would cost an hour to undo. This profile is a GC root.
# Nix dev shell profile kept as a GC root for the RISC-V toolchain.
DEVSHELL_PROFILE ?= .nix-devshell-profile
PORT ?= /dev/ttyUSB0
TTY ?= /dev/ttyACM0
BAUD ?= 115200
UF2 ?= $(NUTTX_DIR)/nuttx.uf2

# A Pico in BOOTSEL mode mounts as a mass storage device. The Pico 2 labels the
# volume RP2350, and the original Pico labels it RPI-RP2. Copying a UF2 there
# flashes the board without the udev rules that picotool needs.
# Pico mass storage mount in BOOTSEL mode (RP2350 or RPI-RP2).
PICO_MOUNT ?= $(shell findmnt -rn -o TARGET -S LABEL=RP2350 2>/dev/null || \
findmnt -rn -o TARGET -S LABEL=RPI-RP2 2>/dev/null)

# A configuration saved under configs/nuttx/<board>/<name>. The board name
# comes from the path, and any in-tree configuration of that board serves as
# the scaffold, because the saved defconfig then replaces the generated one.
# NuttX strips CONFIG_APPS_DIR when it writes a defconfig, so it has to go
# back in before olddefconfig fills in the remaining defaults.
# Saved NuttX defconfig path under configs/nuttx/<board>/<name>.
SAVED_CONFIG ?=
SAVED_BOARD = $(notdir $(patsubst %/,%,$(dir $(SAVED_CONFIG))))
NUTTX_APPS_DIR ?= ../nuttx-apps

# Each entry is <submodule directory>:<patch directory>. A patch that must
# survive lives here rather than as a commit inside the submodule.
# NUTTX_APPS_DIR is relative to the kernel tree, because CONFIG_APPS_DIR needs
# it that way. The patch sets use a repository-relative path instead.
# Submodule patch mappings: <submodule directory>:<patch directory>.
PATCH_SETS = $(NUTTX_DIR):patches/nuttx external/nuttx-apps:patches/nuttx-apps

# The Pico SDK, a second SDK alongside NuttX. A project is any directory with a
# CMakeLists.txt, so PICOSDK_SRC points at one under experiments/.
# Pico SDK project configuration.
PICOSDK_DIR ?= external/pico-sdk
PICOSDK_BOARD ?= pico2_w
PICOSDK_SRC ?= experiments/picosdk-hello
PICOSDK_BUILD ?= build/picosdk/$(notdir $(PICOSDK_SRC))

# ESP-IDF, the Espressif SDK. It is the practical option for the ESP32-P4,
# whose NuttX console does not reach a shell. IDF_TOOLS_PATH keeps the
# downloaded toolchains inside the repository rather than in the home
# directory, so a clean checkout is self contained.
# ESP-IDF toolchain and build directories.
ESPIDF_DIR ?= external/esp-idf
ESPIDF_TOOLS ?= $(CURDIR)/build/espressif
ESPIDF_TARGET ?= esp32p4
ESPIDF_SRC ?= experiments/espidf-hello
ESPIDF_BUILD ?= $(CURDIR)/build/espidf/$(notdir $(ESPIDF_SRC))
ESPIDF_PORT ?= /dev/ttyACM0

# The onboard ESP32-C6 is reached with a 3.3 V USB to UART adapter, which
# appears as /dev/ttyUSB0 rather than /dev/ttyACM0. This board exposes the C6
# only as solder pads, so the joints have to be soldered. No ESP32-P4 GPIO
# reaches the C6's bootstrap either, so it cannot be flashed from the P4. See
# docs/esp32p4.md.
# Serial port and build directory for the onboard ESP32-C6.
C6_PORT ?= /dev/ttyUSB0
C6_BUILD ?= $(CURDIR)/build/espidf/espidf-c6-slave

# idf.py writes the resolved sdkconfig next to the project by default. Sending
# it to the build directory keeps generated files out of the repository, at the
# cost of losing menuconfig edits on espidf-distclean. sdkconfig.defaults in
# the project directory is the file to edit for changes worth keeping.
# Store resolved sdkconfig in the build directory.
ESPIDF_ARGS ?= -C $(ESPIDF_SRC) -B $(ESPIDF_BUILD) \
-D SDKCONFIG=$(ESPIDF_BUILD)/sdkconfig

# The CYW43439 blob that a wireless build embeds. The NuttX board build writes
# a dummy in its place when it is missing, and the resulting image runs but
# never talks to the wireless chip, so the build checks for it.
# CYW43439 wireless firmware blob checked during wireless NuttX builds.
CYW43_FIRMWARE ?= \
external/pico-sdk/lib/cyw43-driver/firmware/43439A0-7.95.49.00.combined

# The architecture of the current NuttX configuration, empty when the tree is not configured yet.
NUTTX_ARCH = $(shell sed -n 's/^CONFIG_ARCH="\(.*\)"$$/\1/p' $(NUTTX_DIR)/.config 2>/dev/null)

# NuttX takes its command prefix from the board configuration, and the
# Espressif boards ask for riscv64-unknown-elf-. A RISC-V build points NuttX at
# whichever usable compiler is on PATH instead, like riscv32-none-elf- from the Nix
# dev shell, or riscv32-esp-elf- from the toolchain Espressif publishes, which
# is what CI installs. The Debian riscv64-unknown-elf package cannot build
# these boards, because it ships no C library, and the Espressif HAL needs
# sys/lock.h from one.
# Select available 32-bit RISC-V cross-compiler prefix from PATH.
ifeq ($(NUTTX_ARCH),risc-v)
ifeq ($(origin CROSSDEV),undefined)
CROSSDEV := $(shell \
Expand Down Expand Up @@ -155,13 +126,7 @@ setup-hooks: ## Install Git hooks (pre-commit and pre-push)
test-hooks: ## Run the Git hooks on all files
uv run pre-commit run --all-files --hook-stage pre-push

# NuttX. A second OS gets its own prefixed group (zephyr-build, and so on).
#
# The patches in a set stack: a later one may edit lines an earlier one added,
# so the earlier one no longer reverses on its own once the later one is on.
# A patch that applies in neither direction is therefore taken as applied when
# every file it touches is already modified or added in the tree. The reverse
# check is still tried first, since it is the stricter answer when it works.
# Apply stacked patches under patches/ to submodules.
.PHONY: nuttx-patch
nuttx-patch: ## Apply the patches under patches/ to the NuttX submodules
@for set in $(PATCH_SETS); do \
Expand Down Expand Up @@ -195,7 +160,7 @@ nuttx-patch: ## Apply the patches under patches/ to the NuttX submodules
done; \
done

# Reverted last to first, since a later patch may sit on top of an earlier one.
# Revert patches in reverse order.
.PHONY: nuttx-unpatch
nuttx-unpatch: ## Remove the patches under patches/ from the NuttX submodules
@for set in $(PATCH_SETS); do \
Expand All @@ -214,21 +179,15 @@ nuttx-unpatch: ## Remove the patches under patches/ from the NuttX submodules
done; \
done

# A branch switch or a hard reset reverts only the tracked edits. A file the
# patch added stays behind. The patch then applies in neither direction: forward
# fails because the file exists, reverse fails because the edits are gone. This
# target clears that state.
# Reset patches and clear untracked files left by reverted patches.
.PHONY: nuttx-repatch
nuttx-repatch: nuttx-unpatch nuttx-patch ## Reset the patches to a consistent state

.PHONY: cyw43-firmware
cyw43-firmware: ## Generate the CYW43439 firmware blob the wireless build needs
uv run python tools/cyw43-firmware.py

# Out-of-tree NuttX application. nuttx-apps looks for apps/external and its
# own .gitignore covers that name, so the symlink leaves the submodule clean
# and the Kconfig generator picks the directory up on its own.

# Symlink out-of-tree application into external/nuttx-apps/external.
EXTERNAL_APP ?= experiments/pico-face

.PHONY: nuttx-link-app
Expand Down
32 changes: 12 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ make shell
```

> [!IMPORTANT]
> The first `make shell` builds the RISC-V cross-compiler toolchain from source, which can take some time.
> When the first build is done, run `make shell-pin`, so a Nix garbage collection does not discard the build results.
> The Espressif boards also need `make install`, which installs esptool. Note that the Raspberry Pi Pico 2 does not need it.
> The first `make shell` builds the RISC-V cross-compiler from source.
> Run `make shell-pin` afterward to protect it from Nix garbage collection.
> Espressif boards also needs `make install` for esptool.

#### 3. Build and run on the Pico 2

Expand All @@ -42,19 +42,17 @@ make flash-pico-uf2 # Hold the BOOTSEL button pressed down while plu
make console TTY=/dev/ttyACM0 # Picocom at 115200 baud; quit with C-a C-x
```

Press Enter twice after the console opens to get the `nsh>` prompt.
The first Enter only echoes, and the session starts on the second one.
Press Enter twice after opening the console to reach the `nsh>` prompt.

<div align="center">
<img alt="NuttShell" src="docs/assets/images/pico2w-nsh-1.jpeg" width="100%">
</div>

The `usbnsh` configuration puts the NuttShell on the USB cable, so no extra hardware is needed.
The `nsh` configuration puts it on UART0 (at GP0 and GP1 pins), which needs a USB to UART adapter.
The `usbnsh` configuration provides a console over USB.
The `nsh` configuration routes to UART0 pins GP0 and GP1 via a USB-to-UART adapter.

Both flash targets need the board in BOOTSEL mode.
`make flash-pico-uf2` copies the image to the drive that the board exposes, and needs no special permissions.
`make flash-pico` uses picotool, which needs the udev rules that `make setup-udev` installs once.
Flashing needs BOOTSEL mode;
`make flash-pico-uf2` copies the UF2 to the exposed drive, while `make flash-pico` uses picotool (which needs `make setup-udev`).

#### 4. Build and run on the ESP32-P4

Expand All @@ -66,19 +64,13 @@ make nuttx-flash-esp PORT=/dev/ttyACM0
make console TTY=/dev/ttyACM0
```

The board's USB-C port connects to an onboard WCH CH343 USB to UART bridge on UART0
(GPIO 37 and GPIO 38), appearing as `/dev/ttyACM0` with USB vendor `1a86`.

> [!NOTE]
> An ESP32-P4 with a silicon revision below v3.0 halts in the revision check that the stock configuration compiles in.
> Configure such a chip from the saved configuration instead, with
> `make nuttx-configure-saved SAVED_CONFIG=configs/nuttx/esp32p4-function-ev-board/nsh-rev1`.
> It sets `CONFIG_ESP32P4_SELECTS_REV_LESS_V3` and `CONFIG_ESP32P4_REV_MIN_100` and boots to an interactive NuttShell on UART0.
> The onboard WCH CH343 bridge links UART0 (GPIO 37 and GPIO 38) to `/dev/ttyACM0`.

> [!NOTE]
> Serial access needs membership in the `dialout` group.
> Run `sudo usermod -aG dialout $USER`, then log in again.
> If you are not a member of the group, you will not have permission to access the serial port.
> ESP32-P4 silicon revisions below v3.0 need configuring via
> `make nuttx-configure-saved SAVED_CONFIG=configs/nuttx/esp32p4-function-ev-board/nsh-rev1` to bypass the revision check.
> Serial access needs membership in the `dialout` group (can be added by running `sudo usermod -aG dialout $USER`).

---

Expand Down
5 changes: 1 addition & 4 deletions experiments/espidf-c6-slave/main/idf_component.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
dependencies:
# The coprocessor half of ESP-Hosted. Built for the esp32c6 target, this is
# the firmware the C6 on the ESP32-P4 function EV board should be running.
# Host and coprocessor come from the same version on purpose, since a
# mismatch is the leading suspect for the link not working.
# Coprocessor firmware for onboard ESP32-C6; version matched with host.
espressif/esp_hosted: "^3.0.6"
9 changes: 3 additions & 6 deletions experiments/espidf-c6-slave/sdkconfig.defaults
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
# The coprocessor role, not the host role. The component defaults to host.
# Coprocessor role for ESP-Hosted.
CONFIG_ESP_HOSTED_CP=y

# SDIO is the only wire between the P4 and the C6 on this board. The component
# already defaults to SDIO on a chip with an SDIO slave, which the C6 has, but
# pinning it means a future default change cannot silently produce firmware that
# speaks the wrong bus.
# SDIO transport between ESP32-P4 and ESP32-C6.
CONFIG_EH_TRANSPORT_CP_SDIO=y

# The C6's own console, reachable on the PROG_C6 header with a UART adapter.
# Console on UART for PROG_C6 header adapter.
CONFIG_ESP_CONSOLE_UART_DEFAULT=y
16 changes: 3 additions & 13 deletions experiments/espidf-hello/sdkconfig.defaults
Original file line number Diff line number Diff line change
@@ -1,20 +1,10 @@
# This sample is pre-production silicon, revision v1.3, and ESP-IDF defaults
# to a minimum of v3.1. The bootloader checks the revision and refuses to boot
# an image built for a newer one. ESP-IDF states that revisions below v3.0 and
# revisions from v3.0 up have large hardware differences and that support for
# the two is mutually exclusive, so both options are needed together. NuttX
# spells these the same way, having taken the names from here.
# Silicon revision v1.3 compatibility options.
CONFIG_ESP32P4_SELECTS_REV_LESS_V3=y
CONFIG_ESP32P4_REV_MIN_100=y

# The board carries a WCH CH343 USB to UART bridge on UART0, reached through
# its UART connector, which enumerates as 1a86:55d3. The default UART console
# therefore works over that cable with no extra hardware. The chip's own USB
# Serial/JTAG controller stays on as the secondary console, which is the
# ESP-IDF default, so the same output reaches the other connector as well.
# Primary console on UART0 (onboard CH343 bridge); secondary on USB Serial/JTAG.
CONFIG_ESP_CONSOLE_UART_DEFAULT=y
CONFIG_ESP_CONSOLE_SECONDARY_USB_SERIAL_JTAG=y

# PSRAM is left off because this program does not need it, not because it is
# broken. It works at up to 200 MHz; see experiments/espidf-psram.
# PSRAM disabled for this basic test (see experiments/espidf-psram).
CONFIG_SPIRAM=n
8 changes: 3 additions & 5 deletions experiments/espidf-psram/sdkconfig.defaults
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
# See experiments/espidf-hello for why these two are needed.
# Silicon revision v1.3 compatibility options.
CONFIG_ESP32P4_SELECTS_REV_LESS_V3=y
CONFIG_ESP32P4_REV_MIN_100=y

# The console goes to UART0, which the onboard CH343 bridge reaches, so a
# panic from the PSRAM controller is visible.
# Primary console on UART0 (onboard CH343 bridge).
CONFIG_ESP_CONSOLE_UART_DEFAULT=y
CONFIG_ESP_CONSOLE_SECONDARY_USB_SERIAL_JTAG=y

# The point of the experiment. The board carries 32 MB in 16-line mode, and
# the slowest clock is the most forgiving starting point.
# 32 MB PSRAM in 16-line mode at 200 MHz.
CONFIG_SPIRAM=y
CONFIG_SPIRAM_MODE_HEX=y
CONFIG_SPIRAM_SPEED_200M=y
12 changes: 1 addition & 11 deletions experiments/espidf-wifi/main/idf_component.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,4 @@
dependencies:
# The ESP32-P4 has no radio. esp_wifi_remote supplies the esp_wifi API and
# esp_hosted carries it over SDIO to the onboard ESP32-C6.
#
# These two must be paired. esp_hosted's own Kconfig turns itself off when
# CONFIG_ESP_HOST_WIFI_ENABLED is set, and on when esp_wifi_remote selects
# the hosted library, so the two mechanisms are mutually exclusive.
#
# Pinned to match experiments/espidf-c6-slave, which builds the coprocessor
# half from the same version. Waveshare pins 1.4.* for ESP-IDF 5.x in its own
# working P4 example, and 1.4.7, 2.7.4, and 3.0.6 all fail identically here,
# so once we control both halves the newest matched pair is the right choice.
# Proxies esp_wifi calls over SDIO to onboard C6; version matched with c6-slave.
espressif/esp_wifi_remote: "*"
espressif/esp_hosted: "^3.0.6"
8 changes: 3 additions & 5 deletions experiments/espidf-wifi/sdkconfig.defaults
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
# See experiments/espidf-hello for why these two are needed.
# Silicon revision v1.3 compatibility options.
CONFIG_ESP32P4_SELECTS_REV_LESS_V3=y
CONFIG_ESP32P4_REV_MIN_100=y

# The console goes to UART0, reached by the onboard CH343 bridge.
# Primary console on UART0 (onboard CH343 bridge).
CONFIG_ESP_CONSOLE_UART_DEFAULT=y
CONFIG_ESP_CONSOLE_SECONDARY_USB_SERIAL_JTAG=y

# Route the proxied Wi-Fi API through ESP-Hosted rather than the in-tree
# CONFIG_ESP_HOST_WIFI_ENABLED path, which disables the esp_hosted component
# and whose controller target is an ESP32 rather than the C6 on this board.
# Route Wi-Fi API through ESP-Hosted over SDIO to onboard C6.
CONFIG_ESP_WIFI_REMOTE_LIBRARY_HOSTED=y
Loading
Loading