Files
firmware/justfile
T
swaits a1b92356b3 feat(boards): LilyGo T-Beam (classic) support
First classic-ESP32 (non-S3) board in the firmware, and first SX127x
radio. ESP32 + SX1276 + SSD1306 OLED + AXP192 PMIC, CP2102 USB-UART
bridge. Pin map verified from MeshCore variants/lilygo_tbeam_SX1276.

The AXP192 gates power to the LoRa chip and OLED. Its I²C bus is
shared with the display, so the firmware builds I²C0 in blocking mode
first for the PMIC init, then converts the same handle to async and
hands it to the display task — avoids double-consuming the peripheral.

Lora-phy's Sx127x support drives DIO0 as the sole IRQ (SX127x has no
BUSY line); the chip runs from a crystal, tx_boost routes +20 dBm
through PA_BOOST.

The `esp-hal` / `esp-rtos` / `esp-backtrace` / `esp-println` deps have
been restructured so the chip flavour (esp32 vs esp32s3) is selected
at the per-board feature level instead of baked into the dep lines.
This keeps the existing S3 boards building on esp32s3 while the new
T-Beam feature selects classic esp32 via the same dependency set.

LoRaBoard gains supported_sf_bitmap() / supported_bw_bitmap() methods
(with SX126x defaults) so the T-Beam can report SF6–SF12 via GET_INFO
— the SX1276 has no SF5. radio_chip_id() reports 0x0011 per
donglora-protocol RadioChipId::Sx1276. freq_range_hz widens to the
SX1276 datasheet span of 137 MHz–1020 MHz.

New modules:
- src/hal/esp32.rs           — classic-ESP32 MCU primitives
- src/board/esp32.rs         — Sx127x radio + SSD1306 display pipe
- src/board/lilygo_tbeam.rs  — the board file
- src/driver/axp.rs          — AXP192 init added alongside AXP2101
2026-04-23 10:42:42 -06:00

225 lines
9.5 KiB
Makefile

set shell := ["bash", "-c"]
# Board definitions: feature target chip
# Xtensa targets use the self-contained esp toolchain (espup-installed) with
# `-Zbuild-std=core,alloc`. Using `+esp` rather than `+nightly` keeps the
# compiler and rust-src versions aligned — the public nightly drifts ahead of
# the esp fork and breaks core-stdlib compilation otherwise.
# Tool versions are pinned in mise.toml; run `just setup` to install everything.
heltec_v3 := "heltec_v3 xtensa-esp32s3-none-elf esp32s3"
heltec_v3_uart := "heltec_v3_uart xtensa-esp32s3-none-elf esp32s3"
heltec_v4 := "heltec_v4 xtensa-esp32s3-none-elf esp32s3"
elecrow_thinknode_m2 := "elecrow_thinknode_m2 xtensa-esp32s3-none-elf esp32s3"
lilygo_tbeam := "lilygo_tbeam xtensa-esp32-none-elf esp32"
lilygo_tbeam_supreme := "lilygo_tbeam_supreme xtensa-esp32s3-none-elf esp32s3"
rak_wisblock_4631 := "rak_wisblock_4631 thumbv7em-none-eabihf nRF52840_xxAA"
wio_tracker_l1 := "wio_tracker_l1 thumbv7em-none-eabihf nRF52840_xxAA"
waveshare_rp2040_lora := "waveshare_rp2040_lora thumbv6m-none-eabi rp2040"
builds_dir := "builds"
version := `sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml`
# All known boards
boards := "heltec_v3 heltec_v3_uart heltec_v4 elecrow_thinknode_m2 lilygo_tbeam lilygo_tbeam_supreme rak_wisblock_4631 waveshare_rp2040_lora wio_tracker_l1"
# Install all required tools and toolchains
setup:
mise trust --yes
mise install
@# ESP Xtensa toolchain (not managed by mise)
@if ! rustup toolchain list | grep -q "^esp"; then \
echo "ESP toolchain not found, installing via espup (this may take a while)..."; \
espup install || { echo "error: espup install failed" >&2; exit 1; }; \
fi
@# nightly rust-src for -Zbuild-std
@if ! rustup component list --toolchain nightly --installed 2>/dev/null | grep -q "^rust-src"; then \
rustup component add rust-src --toolchain nightly; \
fi
@# ARM targets
@rustup target list --installed | grep -q "^thumbv6m-none-eabi$" || rustup target add thumbv6m-none-eabi
@rustup target list --installed | grep -q "^thumbv7em-none-eabihf$" || rustup target add thumbv7em-none-eabihf
# Build release firmware for all boards, installing toolchains as needed
build-all:
@trap 'exit 130' INT; \
for board in {{boards}}; do \
if just _can_build $board; then \
echo "── building $board ──"; \
just build $board || exit $?; \
else \
echo "── skipping $board (toolchain install failed) ──"; \
fi; \
done
# Check all boards compile, installing toolchains as needed
check-all:
@trap 'exit 130' INT; \
for board in {{boards}}; do \
if just _can_build $board; then \
echo "── checking $board ──"; \
just check $board || exit $?; \
else \
echo "── skipping $board (toolchain install failed) ──"; \
fi; \
done
# Check a single board compiles (clippy with warnings as errors)
check board:
@just _cargo {{board}} "clippy --release" "-- -D warnings"
# Build release firmware and copy to builds/ with a readable name
build board profile="release":
@just _cargo {{board}} "build --{{profile}}"
@just _copy_firmware {{board}} {{profile}}
# Build and flash a board (espflash for Xtensa, UF2 DFU for ARM)
flash board:
@just _ensure_tools
@just build {{board}} release
@read -r feat target chip <<< "$(just _info {{board}})"; \
case "$target" in \
xtensa-*) \
port=$(just _find_port "303a:1001" "1209:5741" "10c4:ea60" "1a86:7522" "1a86:7523"); \
if [ -n "$port" ]; then \
echo "Using $port"; \
espflash flash -p "$port" "{{builds_dir}}/donglora-{{board}}-v{{version}}.elf"; \
else \
echo "No port found, falling back to espflash auto-detection..." >&2; \
espflash flash "{{builds_dir}}/donglora-{{board}}-v{{version}}.elf"; \
fi ;; \
*) just _flash_uf2 {{board}} ;; \
esac
# Flash ARM board via debug probe (requires J-Link or similar)
flash-probe board:
@just _ensure_tools
@just build {{board}} release
@read -r feat target chip <<< "$(just _info {{board}})"; \
probe-rs run --chip $chip "{{builds_dir}}/donglora-{{board}}-v{{version}}.elf"
# Show binary size for a release build
size board:
@just _cargo {{board}} "size --release"
# Run host-side protocol unit tests
test:
DONGLORA_HOST_TEST=1 cargo test
# Check for outdated dependencies
outdated:
cargo outdated
# ── Private helpers ───────────────────────────────────────────────────
# Install mise-managed tools if any are missing (fast no-op when current)
[private]
_ensure_tools:
@mise trust --yes . 2>/dev/null; mise install --quiet
# Run a cargo command for a board.
# Xtensa: esp toolchain (cargo + rustc + rust-src together) + -Zbuild-std=core
[private]
_cargo board cmd extra_args="":
@just _ensure_tools
@read -r feat target chip <<< "$(just _info {{board}})"; \
extra=""; buildstd=""; \
case "$target" in xtensa-*) \
just _require_esp_toolchain; \
[ -f "$HOME/export-esp.sh" ] && . "$HOME/export-esp.sh"; \
extra="+esp"; buildstd="-Zbuild-std=core,alloc";; \
esac; \
cargo $extra {{cmd}} --target "$target" --features "$feat" $buildstd {{extra_args}}
# Ensure a board's toolchain is available, auto-installing if needed
[private]
_can_build board:
@read -r feat target chip <<< "$(just _info {{board}})"; \
case "$target" in \
xtensa-*) just _require_esp_toolchain ;; \
*) rustup target list --installed | grep -q "^$target$" || rustup target add "$target" ;; \
esac
# Ensure the ESP Xtensa toolchain is installed (espup installed via mise).
# The esp toolchain ships with its own cargo, rustc, and rust-src — no extra
# components are needed on top.
[private]
_require_esp_toolchain:
@just _ensure_tools
@if ! rustup toolchain list | grep -q "^esp"; then \
echo "ESP toolchain not found, installing via espup (this may take a while)..." >&2; \
espup install || { echo "error: espup install failed" >&2; exit 1; }; \
fi
# Find a serial port matching any of the given VID:PID pairs (checked in order)
[private]
_find_port +vid_pids:
@for vidpid in {{vid_pids}}; do \
vid="${vidpid%%:*}"; pid="${vidpid##*:}"; \
for dev in /dev/ttyACM* /dev/ttyUSB*; do \
[ -e "$dev" ] || continue; \
info=$(udevadm info --query=property --name="$dev" 2>/dev/null) || continue; \
dev_vid=$(echo "$info" | sed -n 's/^ID_VENDOR_ID=//p' | tr '[:upper:]' '[:lower:]'); \
dev_pid=$(echo "$info" | sed -n 's/^ID_MODEL_ID=//p' | tr '[:upper:]' '[:lower:]'); \
if [ "$dev_vid" = "$vid" ] && [ "$dev_pid" = "$pid" ]; then \
echo "$dev"; exit 0; \
fi; \
done; \
done
[private]
_copy_firmware board profile:
@mkdir -p {{builds_dir}}
@read -r feat target chip <<< "$(just _info {{board}})"; \
src="target/$target/{{profile}}/donglora"; \
dst="{{builds_dir}}/donglora-{{board}}-v{{version}}"; \
if [ -f "$src" ]; then \
cp "$src" "$dst.elf"; echo "→ $dst.elf"; \
case "$target" in \
xtensa-*) \
mise exec -- espflash save-image --chip "$chip" --merge --skip-padding "$src" "$dst.bin"; \
echo "→ $dst.bin"; \
;; \
thumbv6m-*) \
mise exec -- rust-objcopy -O ihex "$src" "$dst.hex"; \
mise exec -- cargo-hex-to-uf2 hex-to-uf2 --input-path "$dst.hex" --output-path "$dst.uf2" --family rp2040; \
rm "$dst.hex"; \
echo "→ $dst.uf2"; \
;; \
thumbv7em-*) \
mise exec -- rust-objcopy -O ihex "$src" "$dst.hex"; \
mise exec -- cargo-hex-to-uf2 hex-to-uf2 --input-path "$dst.hex" --output-path "$dst.uf2" --family nrf52840; \
rm "$dst.hex"; \
echo "→ $dst.uf2"; \
;; \
esac; \
fi
# Copy UF2 to a mounted UF2 bootloader drive
[private]
_flash_uf2 board:
@uf2="{{builds_dir}}/donglora-{{board}}-v{{version}}.uf2"; \
if [ ! -f "$uf2" ]; then \
echo "error: $uf2 not found — run 'just build {{board}}' first" >&2; exit 1; \
fi; \
mnt="$(find /run/media -maxdepth 3 -name INFO_UF2.TXT -printf '%h\n' -quit 2>/dev/null)"; \
if [ -z "$mnt" ]; then \
echo "error: no UF2 drive found — double-tap reset to enter bootloader" >&2; exit 1; \
fi; \
echo "Copying to $mnt ..."; \
cp "$uf2" "$mnt/"; \
sync; \
echo "Done — board will reboot automatically"
[private]
_info name:
@if [ "{{name}}" == "heltec_v3" ]; then echo "{{heltec_v3}}"; \
elif [ "{{name}}" == "heltec_v3_uart" ]; then echo "{{heltec_v3_uart}}"; \
elif [ "{{name}}" == "heltec_v4" ]; then echo "{{heltec_v4}}"; \
elif [ "{{name}}" == "elecrow_thinknode_m2" ]; then echo "{{elecrow_thinknode_m2}}"; \
elif [ "{{name}}" == "lilygo_tbeam" ]; then echo "{{lilygo_tbeam}}"; \
elif [ "{{name}}" == "lilygo_tbeam_supreme" ]; then echo "{{lilygo_tbeam_supreme}}"; \
elif [ "{{name}}" == "rak_wisblock_4631" ]; then echo "{{rak_wisblock_4631}}"; \
elif [ "{{name}}" == "waveshare_rp2040_lora" ]; then echo "{{waveshare_rp2040_lora}}"; \
elif [ "{{name}}" == "wio_tracker_l1" ]; then echo "{{wio_tracker_l1}}"; \
else echo "Unknown board: {{name}}" >&2; exit 1; fi