diff --git a/.github/workflows/build-firmware.yaml b/.github/workflows/build-firmware.yaml new file mode 100644 index 00000000..f525b431 --- /dev/null +++ b/.github/workflows/build-firmware.yaml @@ -0,0 +1,119 @@ +name: Build Frozen Firmware + +# This workflow builds custom CircuitPython firmware with PySquared libraries +# frozen (compiled into the firmware binary). +# +# Triggered on: +# - Push to tags matching v* (e.g., v2.0.0) +# - Manual workflow dispatch +# +# The workflow: +# 1. Sets up the ARM toolchain +# 2. Clones CircuitPython at a pinned version +# 3. Sets up frozen modules (PySquared + dependencies) +# 4. Builds firmware for all PROVES boards +# 5. Uploads firmware files as release artifacts + +on: + push: + tags: + - 'v*' + workflow_dispatch: + inputs: + circuitpython_version: + description: 'CircuitPython version to build' + required: false + default: '9.2.0' + +jobs: + build-firmware: + name: Build firmware for ${{ matrix.board }} + runs-on: ubuntu-latest + + strategy: + matrix: + # Build firmware for all supported PROVES boards + # Note: Use standard Raspberry Pi Pico boards until custom PROVES + # board definitions are added to CircuitPython + board: + - raspberry_pi_pico + - raspberry_pi_pico_w + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install ARM toolchain and dependencies + run: | + sudo apt-get update + sudo apt-get install -y \ + build-essential \ + git \ + python3 \ + python3-pip \ + gcc-arm-none-eabi \ + gettext + + - name: Set CircuitPython version + id: version + run: | + if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then + echo "version=${{ github.event.inputs.circuitpython_version }}" >> $GITHUB_OUTPUT + else + echo "version=9.2.0" >> $GITHUB_OUTPUT + fi + + - name: Build firmware + run: | + cd firmware + make setup CIRCUITPYTHON_VERSION=${{ steps.version.outputs.version }} + + # Note: add_dependencies.py needs to be updated to actually add submodules + # For now, we just link PySquared + # ./add_dependencies.py # This would add all dependencies + + # Build firmware + make firmware BOARD=${{ matrix.board }} + + - name: Upload firmware artifact + uses: actions/upload-artifact@v4 + with: + name: firmware-${{ matrix.board }} + path: firmware/build/${{ matrix.board }}-frozen-*.uf2 + if-no-files-found: error + + - name: Create release (if tag) + if: startsWith(github.ref, 'refs/tags/') + uses: softprops/action-gh-release@v1 + with: + files: firmware/build/${{ matrix.board }}-frozen-*.uf2 + draft: false + prerelease: false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + # Job to verify firmware can be built + verify-build: + name: Verify firmware build system + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y python3 python3-pip + + - name: Test dependency parser + run: | + cd firmware + python3 add_dependencies.py --list + + - name: Verify Makefile targets + run: | + cd firmware + make help diff --git a/README.md b/README.md index ec71e599..a339d0ce 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,7 @@ If you want to contribute to PySquared, follow these steps: ## Documentation - [Getting Started](https://proveskit.github.io/pysquared/getting-started/) +- [Frozen Modules](https://proveskit.github.io/pysquared/frozen-modules/) - Build custom CircuitPython firmware - [Design Guide](https://proveskit.github.io/pysquared/design-guide/) - [Configuration](https://proveskit.github.io/pysquared/api/#pysquared.config) - [Error Handling & Logging](https://proveskit.github.io/pysquared/api/#pysquared.logger) diff --git a/docs/frozen-modules.md b/docs/frozen-modules.md new file mode 100644 index 00000000..95df4b42 --- /dev/null +++ b/docs/frozen-modules.md @@ -0,0 +1,372 @@ +# Building Custom CircuitPython Firmware with Frozen Modules + +## Overview + +**Frozen modules** are Python libraries that are compiled directly into the CircuitPython firmware binary, rather than being stored as separate files on the CIRCUITPY filesystem. This approach provides several benefits: + +- **RAM Savings**: Frozen modules execute directly from flash memory, freeing up precious RAM for your application code +- **Flash Efficiency**: Compiled frozen modules take less space than `.mpy` files on the filesystem +- **Simplified Deployment**: Single firmware file contains everything - no need to copy libraries to each board +- **Faster Startup**: Pre-compiled modules load faster than filesystem-based libraries +- **Version Consistency**: Ensures all boards run exactly the same library versions + +This is particularly valuable for resource-constrained boards like SAMD21 or when deploying to many satellites. + +## When to Use Frozen Modules + +**Use frozen modules when:** +- You're deploying to many boards and want consistency +- Your board is RAM-constrained (e.g., SAMD21 non-Express boards) +- You want to prevent accidental library modifications on the board +- You need faster startup times +- You want to simplify your deployment process + +**Use filesystem libraries when:** +- You need to update libraries without reflashing firmware +- You're actively developing and testing new library versions +- You want flexibility to mix and match library versions +- Your board has plenty of RAM and flash + +## Prerequisites + +Building custom CircuitPython firmware requires: + +1. **Linux, macOS, or Windows with WSL**: The build system requires a Unix-like environment +2. **Build Tools**: + - `gcc-arm-none-eabi` (ARM cross-compiler) + - `git` (version control) + - `python3` and `pip` (for build scripts) + - `make` (build system) +3. **Disk Space**: ~5GB for CircuitPython source and build artifacts +4. **Time**: Initial build takes 10-30 minutes depending on your system + +## Build Process Overview + +The frozen module build process involves: + +1. **Clone CircuitPython Source**: Get the CircuitPython repository and all submodules +2. **Add PySquared Dependencies**: Configure which libraries to freeze into firmware +3. **Configure Board**: Specify board type and frozen module directories +4. **Build Firmware**: Compile CircuitPython with frozen modules +5. **Flash Firmware**: Upload the custom firmware to your board + +## Directory Structure + +We recommend creating a `firmware/` directory in this repository to contain all firmware-related build artifacts: + +``` +pysquared/ +├── firmware/ # Frozen module build directory (new) +│ ├── circuitpython/ # CircuitPython source (git submodule) +│ ├── frozen/ # Directory for libraries to freeze +│ │ ├── pysquared/ # PySquared library (symlink or copy) +│ │ └── adafruit_circuitpython_*/ # Adafruit dependencies (git submodules) +│ ├── boards/ # Custom board configurations +│ │ ├── proves_rp2040_v4/ +│ │ ├── proves_rp2040_v5/ +│ │ ├── proves_rp2350_v5a/ +│ │ └── proves_rp2350_v5b/ +│ ├── Makefile # Build automation +│ ├── build-firmware.sh # Build script +│ └── README.md # Firmware-specific documentation +├── circuitpython-workspaces/ # Existing PySquared source +└── docs/ # Documentation +``` + +## Step-by-Step Build Instructions + +### 1. Set Up Build Environment + +#### On Linux (Ubuntu/Debian): +```bash +sudo apt update +sudo apt install build-essential git python3 python3-pip gcc-arm-none-eabi +``` + +#### On macOS: +```bash +# Install Homebrew if not already installed +/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + +# Install ARM toolchain +brew install armmbed/formulae/arm-none-eabi-gcc +brew install python3 git +``` + +#### On Windows (WSL): +```bash +# First install WSL and Ubuntu from Microsoft Store, then: +sudo apt update +sudo apt install build-essential git python3 python3-pip gcc-arm-none-eabi +``` + +### 2. Clone CircuitPython Source + +Navigate to the `firmware/` directory and clone CircuitPython: + +```bash +cd firmware/ +git clone https://github.com/adafruit/circuitpython.git +cd circuitpython +git checkout # e.g., 9.0.5 +python3 tools/ci_fetch_deps.py raspberrypi +# Return to firmware directory and use UV to install build dependencies +cd .. +# Ensure UV is available (installed by root Makefile) +cd .. && make uv && cd firmware +# Install in UV virtual environment +../tools/uv-0.8.14/uv pip install -q -r circuitpython/requirements-dev.txt +``` + +**Note**: This fetches only submodules needed for RP2040/RP2350 (raspberrypi port), significantly reducing download size and time. To fetch all submodules (for other boards), use `make fetch-all-submodules` instead. + +**Important**: +- Always use a tagged stable release, not the main branch, to ensure reproducible builds. +- The `requirements-dev.txt` install provides Python tools needed for the build (cascadetoml, jinja2, typer, etc.). +- Using `make setup` (recommended) automatically installs dependencies in the UV virtual environment, avoiding system Python conflicts. + +### 3. Add Libraries to Freeze + +You have two options for adding libraries: + +#### Option A: Add as Git Submodules (Recommended for Dependencies) + +For each Adafruit library dependency in `circuitpython-workspaces/flight-software/pyproject.toml`: + +```bash +cd firmware/circuitpython/frozen +git submodule add https://github.com/adafruit/Adafruit_CircuitPython_INA219 +cd Adafruit_CircuitPython_INA219 +git checkout # Match version in pyproject.toml +``` + +#### Option B: Symlink PySquared Library + +For the PySquared library itself: + +```bash +cd firmware/circuitpython/frozen +ln -s ../../../circuitpython-workspaces/flight-software/src/pysquared pysquared +``` + +This keeps the source in one place and avoids duplication. + +### 4. Configure Board + +Create or modify the board configuration in `ports/raspberrypi/boards//mpconfigboard.mk`: + +```makefile +# Add PySquared and dependencies to frozen modules +FROZEN_MPY_DIRS += $(TOP)/frozen/pysquared +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_INA219 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_LIS2MDL +# ... add all other dependencies +``` + +### 5. Build Firmware + +**Recommended:** Use the firmware Makefile which ensures the correct Python environment: + +```bash +cd firmware +make firmware BOARD=raspberry_pi_pico +``` + +**Manual (advanced):** If building directly in CircuitPython source: + +```bash +cd firmware/circuitpython/ports/raspberrypi +# Use UV to run make with the correct Python environment +../../tools/uv-0.8.14/uv run --no-project make BOARD= +``` + +For PROVES Kit boards (or use equivalent Raspberry Pi Pico boards): +- `raspberry_pi_pico` (for v4, v5) +- `raspberry_pi_pico_w` (with WiFi) +- Custom PROVES board definitions (once configured) + +The build will create a `.uf2` file in `build-/firmware.uf2`. + +### 6. Flash Firmware + +1. Put your board in bootloader mode (double-press RESET button) +2. A USB drive named `RPI-RP2` or similar will appear +3. Copy the `firmware.uf2` file to this drive +4. The board will automatically reset and boot with the new firmware + +## Verifying Frozen Modules + +After flashing, connect to the board's serial console and run: + +```python +import sys +print(sys.modules) +``` + +Frozen modules will be listed and can be imported directly without being in `/lib`. + +Test that PySquared is frozen: + +```python +import pysquared +print(pysquared.__file__) # Should show it's built-in, not from filesystem +``` + +## Automating the Build + +To make builds easier, create a `firmware/Makefile`: + +```makefile +# Version pins +CIRCUITPYTHON_VERSION ?= 9.0.5 +BOARD ?= proves_rp2040_v5 + +.PHONY: all +all: firmware + +.PHONY: setup +setup: + git clone https://github.com/adafruit/circuitpython.git || true + cd circuitpython && git checkout $(CIRCUITPYTHON_VERSION) + cd circuitpython && python3 tools/ci_fetch_deps.py raspberrypi + # Use UV for installing build dependencies (avoids system Python conflicts) + cd .. && make uv && cd firmware + ../tools/uv-0.8.14/uv pip install -q -r circuitpython/requirements-dev.txt + $(MAKE) add-dependencies + +.PHONY: add-dependencies +add-dependencies: + # Add each dependency from pyproject.toml as a submodule + # This would parse the pyproject.toml and add submodules automatically + +.PHONY: firmware +firmware: + cd circuitpython/ports/raspberrypi && make BOARD=$(BOARD) + cp circuitpython/ports/raspberrypi/build-$(BOARD)/firmware.uf2 ./$(BOARD)-frozen-$(CIRCUITPYTHON_VERSION).uf2 + +.PHONY: clean +clean: + cd circuitpython/ports/raspberrypi && make BOARD=$(BOARD) clean +``` + +Then build with: +```bash +make BOARD=proves_rp2040_v5 firmware +``` + +## Updating Frozen Modules + +When you update PySquared or dependencies: + +1. Update the library source code +2. Rebuild the firmware: `make BOARD= firmware` +3. Flash the new firmware to all boards + +There's no way to update frozen modules without rebuilding and reflashing the entire firmware. + +## Troubleshooting + +### Build Fails with "arm-none-eabi-gcc: command not found" +Install the ARM toolchain for your platform (see prerequisites above). + +### Build Fails with Missing Submodules +For RP2040/RP2350 builds, run `python3 tools/ci_fetch_deps.py raspberrypi` in the circuitpython directory. For all ports, use `make fetch-all-submodules` instead. + +### Build Fails with Python Import Errors (cascadetoml, jinja2, typer, etc.) +First, ensure dependencies are installed: `make install-circuitpython-deps` + +If the error persists during build, ensure you're using the Makefile to build: `make firmware BOARD=`. This runs the build with UV's Python environment. Do NOT run `make` directly in the CircuitPython ports directory, as it won't have access to the installed packages. + +### "externally-managed-environment" Error on macOS +This occurs when trying to install packages system-wide. The Makefile now uses UV to install in a virtual environment. If you see this error, ensure you're using `make setup` or `make install-circuitpython-deps` rather than manual pip commands. + +### Firmware File is Too Large +- Remove unused frozen modules from `mpconfigboard.mk` +- Use `FROZEN_MPY_DIRS` instead of `FROZEN_PY_DIRS` (mpy is more compact) +- Consider building with optimizations: `make BOARD= OPTIMIZATION=-O2` + +### Board Won't Boot After Flashing +- Verify you built for the correct board +- Try re-flashing the official CircuitPython firmware first +- Check the build log for errors + +### Module Not Found After Freezing +- Verify the module directory is listed in `FROZEN_MPY_DIRS` +- Check that the module has an `__init__.py` +- Rebuild with `make clean` first to ensure fresh build + +## CI/CD Integration + +For automated builds in GitHub Actions, you can create a workflow that: + +1. Sets up the ARM toolchain +2. Clones CircuitPython at a pinned version +3. Adds dependencies as submodules +4. Builds firmware for all PROVES boards +5. Uploads firmware files as release artifacts + +Example `.github/workflows/build-firmware.yaml`: + +```yaml +name: Build Frozen Firmware + +on: + push: + tags: + - 'v*' + workflow_dispatch: + +jobs: + build-firmware: + runs-on: ubuntu-latest + strategy: + matrix: + board: [proves_rp2040_v4, proves_rp2040_v5, proves_rp2350_v5a, proves_rp2350_v5b] + + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install ARM toolchain + run: | + sudo apt update + sudo apt install -y gcc-arm-none-eabi build-essential git python3 python3-pip + + - name: Build firmware + run: | + cd firmware + make setup CIRCUITPYTHON_VERSION=9.0.5 + make firmware BOARD=${{ matrix.board }} + + - name: Upload firmware + uses: actions/upload-artifact@v4 + with: + name: firmware-${{ matrix.board }} + path: firmware/${{ matrix.board }}-frozen-*.uf2 +``` + +## Best Practices + +1. **Pin Versions**: Always use specific version tags for CircuitPython and libraries +2. **Document Dependencies**: Keep a clear record of what's frozen and why +3. **Test Thoroughly**: Test firmware on actual hardware before deploying to all boards +4. **Backup**: Keep copies of working firmware files for rollback +5. **Version Firmware**: Include version info in the firmware filename +6. **Automate**: Use Makefiles or scripts to make builds reproducible +7. **CI/CD**: Automate firmware builds for releases + +## References + +- [CircuitPython Building Documentation](https://docs.circuitpython.org/en/latest/BUILDING.html) +- [Adafruit Guide: Adding Frozen Modules](https://learn.adafruit.com/building-circuitpython/adding-frozen-modules) +- [CircuitPython Frozen Libraries Overview](https://learn.adafruit.com/welcome-to-circuitpython/library-file-types-and-frozen-libraries) +- [CircuitPython GitHub Repository](https://github.com/adafruit/circuitpython) + +## Support + +If you encounter issues building firmware: + +1. Check the [CircuitPython Discord](https://adafru.it/discord) #help-with-circuitpython channel +2. Open an issue in this repository with your build log +3. Review the CircuitPython documentation for your specific board diff --git a/firmware/.gitignore b/firmware/.gitignore new file mode 100644 index 00000000..04d60197 --- /dev/null +++ b/firmware/.gitignore @@ -0,0 +1,17 @@ +# CircuitPython source (cloned during setup) +circuitpython/ + +# Build artifacts +build/ +*.uf2 +*.bin +*.hex +*.elf + +# Temporary files +*.pyc +__pycache__/ +.DS_Store +*.swp +*.swo +*~ diff --git a/firmware/EXAMPLE.md b/firmware/EXAMPLE.md new file mode 100644 index 00000000..ca02d9cb --- /dev/null +++ b/firmware/EXAMPLE.md @@ -0,0 +1,341 @@ +# Example: Building Frozen Firmware for PROVES Kit + +This example walks through building custom CircuitPython firmware with PySquared frozen for a Raspberry Pi Pico (used in PROVES Kit v5). + +## Scenario + +You want to deploy PySquared to 10 satellites, and you want: +- All libraries built into the firmware (frozen) +- Same library versions on every satellite +- Minimal files on the filesystem +- Maximum available RAM for your application + +## Prerequisites + +- Linux, macOS, or Windows with WSL +- ARM cross-compiler installed +- ~5GB disk space +- 30-60 minutes for first build + +## Step-by-Step Walkthrough + +### 1. Install Build Tools + +```bash +# Ubuntu/Debian +sudo apt update +sudo apt install build-essential git python3 python3-pip gcc-arm-none-eabi + +# Verify installation +arm-none-eabi-gcc --version +# Should show: arm-none-eabi-gcc (GNU Arm Embedded Toolchain 10.3-2021.10) 10.3.1 +``` + +### 2. Initial Setup + +```bash +cd /path/to/pysquared/firmware +make setup +``` + +**What this does:** +- Clones CircuitPython 9.2.0 to `circuitpython/` +- Fetches all CircuitPython submodules (~5 minutes) +- Creates symlink: `circuitpython/frozen/pysquared -> ../../../circuitpython-workspaces/flight-software/src/pysquared` +- Shows you what dependencies need to be added + +**Expected output:** +``` +Checking prerequisites... +All prerequisites satisfied +Cloning CircuitPython 9.2.0... +Fetching CircuitPython submodules (raspberrypi port only)... +Installing CircuitPython build dependencies... +Setting up frozen modules... +Linking PySquared library to frozen directory... +PySquared linked + +Note: Dependencies must be added manually as git submodules +See docs/frozen-modules.md for instructions on adding each dependency + +Dependencies to add (from circuitpython-workspaces/flight-software/pyproject.toml): + - adafruit-circuitpython-ina219 + - adafruit-circuitpython-asyncio + ... +``` + +### 3. Add Dependencies + +Now add each dependency as a git submodule. We'll start with the standard Adafruit libraries: + +```bash +cd circuitpython/frozen + +# INA219 - Power monitor +git submodule add https://github.com/adafruit/Adafruit_CircuitPython_INA219 +cd Adafruit_CircuitPython_INA219 +git checkout 3.4.26 +cd .. + +# Asyncio - Async I/O support +git submodule add https://github.com/adafruit/adafruit_circuitpython_asyncio +cd adafruit_circuitpython_asyncio +git checkout 1.3.3 +cd .. + +# DRV2605 - Haptic motor driver +git submodule add https://github.com/adafruit/Adafruit_CircuitPython_DRV2605 +cd Adafruit_CircuitPython_DRV2605 +git checkout 1.3.4 +cd .. + +# LIS2MDL - Magnetometer +git submodule add https://github.com/adafruit/Adafruit_CircuitPython_LIS2MDL +cd Adafruit_CircuitPython_LIS2MDL +git checkout 2.1.23 +cd .. + +# LSM6DS - IMU +git submodule add https://github.com/adafruit/Adafruit_CircuitPython_LSM6DS +cd Adafruit_CircuitPython_LSM6DS +git checkout 4.5.13 +cd .. + +# MCP9808 - Temperature sensor +git submodule add https://github.com/adafruit/Adafruit_CircuitPython_MCP9808 +cd Adafruit_CircuitPython_MCP9808 +git checkout 3.3.24 +cd .. + +# NeoPixel - RGB LED control +git submodule add https://github.com/adafruit/Adafruit_CircuitPython_NeoPixel +cd Adafruit_CircuitPython_NeoPixel +git checkout 6.3.12 +cd .. + +# Register - Low-level I2C/SPI helper +git submodule add https://github.com/adafruit/Adafruit_CircuitPython_Register +cd Adafruit_CircuitPython_Register +git checkout 1.10.4 +cd .. + +# RFM - Radio module +git submodule add https://github.com/adafruit/Adafruit_CircuitPython_RFM +cd Adafruit_CircuitPython_RFM +git checkout 1.0.6 +cd .. + +# TCA9548A - I2C multiplexer (PROVES custom fork) +git submodule add https://github.com/proveskit/Adafruit_CircuitPython_TCA9548A +cd Adafruit_CircuitPython_TCA9548A +git checkout 1.1.0 +cd .. + +# Ticks - Timing utilities +git submodule add https://github.com/adafruit/Adafruit_CircuitPython_Ticks +cd Adafruit_CircuitPython_Ticks +git checkout 1.1.1 +cd .. + +# VEML7700 - Light sensor +git submodule add https://github.com/adafruit/Adafruit_CircuitPython_VEML7700 +cd Adafruit_CircuitPython_VEML7700 +git checkout 2.1.4 +cd .. + +# Hashlib - Hash functions +git submodule add https://github.com/adafruit/Adafruit_CircuitPython_hashlib +cd Adafruit_CircuitPython_hashlib +git checkout 1.4.19 +cd .. + +# PROVES SX126X - LoRa radio +git submodule add https://github.com/proveskit/micropySX126X +cd micropySX126X +git checkout 1.0.0 +cd .. + +# PROVES SX1280 - 2.4GHz radio +git submodule add https://github.com/proveskit/CircuitPython_SX1280 +cd CircuitPython_SX1280 +git checkout 1.0.4 +cd .. +``` + +**Tip:** You can create a script to automate this. See `add_dependencies.py` for a helper. + +### 4. Configure Board + +Now tell CircuitPython to include these modules in the firmware: + +```bash +cd /path/to/pysquared/firmware/circuitpython/ports/raspberrypi/boards/raspberry_pi_pico +``` + +Edit `mpconfigboard.mk` and add at the end: + +```makefile +# PySquared frozen modules +FROZEN_MPY_DIRS += $(TOP)/frozen/pysquared +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_INA219 +FROZEN_MPY_DIRS += $(TOP)/frozen/adafruit_circuitpython_asyncio +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_DRV2605 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_LIS2MDL +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_LSM6DS +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_MCP9808 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_NeoPixel +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Register +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_RFM +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_TCA9548A +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Ticks +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_VEML7700 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_hashlib +FROZEN_MPY_DIRS += $(TOP)/frozen/micropySX126X +FROZEN_MPY_DIRS += $(TOP)/frozen/CircuitPython_SX1280 +``` + +### 5. Build Firmware + +```bash +cd /path/to/pysquared/firmware +make firmware BOARD=raspberry_pi_pico +``` + +**Expected output:** +``` +Building firmware for raspberry_pi_pico... +CircuitPython version: 9.2.0 +Board: raspberry_pi_pico +... +[lots of compiler output] +... +Firmware built successfully! +Output: /path/to/pysquared/firmware/build/raspberry_pi_pico-frozen-9.2.0.uf2 +``` + +**Build time:** 10-30 minutes on first build. Subsequent builds are much faster (1-5 minutes). + +### 6. Flash to Board + +1. **Enter bootloader mode:** + - Connect your PROVES Kit board via USB + - Double-press the RESET button quickly + - A USB drive named `RPI-RP2` appears + +2. **Copy firmware:** + ```bash + cp build/raspberry_pi_pico-frozen-9.2.0.uf2 /media/username/RPI-RP2/ + ``` + (Adjust path for your OS - may be `/Volumes/RPI-RP2` on macOS or `D:\` on Windows) + +3. **Board auto-restarts** with new firmware + +### 7. Verify Frozen Modules + +Connect to serial console: +```bash +screen /dev/ttyACM0 # Linux - adjust device as needed +``` + +Test in REPL: +```python +>>> import sys +>>> print(sys.implementation) +(name='circuitpython', version=(9, 2, 0)) + +>>> import pysquared +>>> print(pysquared.__file__) +# Should NOT show a file path - it's built-in (frozen) + +>>> from pysquared.hardware.power_monitor import INA219Manager +>>> # Should import without needing files in /lib + +>>> import os +>>> os.listdir('/lib') +[] # Empty! All libraries are frozen +``` + +### 8. Deploy Your Code + +Now you only need to copy your application code: + +```python +# main.py +from pysquared import PySquared + +# All libraries are already built-in! +satellite = PySquared() +satellite.run() +``` + +Copy to board: +```bash +cp main.py config.json /media/username/CIRCUITPY/ +``` + +## Results + +**Before (standard deployment):** +- CircuitPython firmware: ~1.5 MB +- /lib directory: ~2-3 MB of .mpy files +- Available RAM: ~180 KB (on RP2040) +- Deploy to each board: Copy firmware + copy 50+ library files + +**After (frozen modules):** +- CircuitPython firmware: ~2.5 MB (includes libraries) +- /lib directory: 0 bytes +- Available RAM: ~200 KB (20KB more available!) +- Deploy to each board: Copy firmware + copy 2 files (main.py, config.json) + +## Troubleshooting + +**Problem:** Build fails with "No such file or directory: arm-none-eabi-gcc" + +**Solution:** Install ARM toolchain: +```bash +sudo apt install gcc-arm-none-eabi +``` + +--- + +**Problem:** Build fails with "git submodule error" + +**Solution:** Ensure you're in the right directory and submodule was added: +```bash +cd circuitpython +git submodule status # Check submodules +python3 tools/ci_fetch_deps.py raspberrypi # Re-fetch if needed +``` + +--- + +**Problem:** Import error after flashing: `ImportError: no module named 'adafruit_ina219'` + +**Solution:** Check that the module is listed in `mpconfigboard.mk`: +```bash +grep -r "INA219" circuitpython/ports/raspberrypi/boards/raspberry_pi_pico/mpconfigboard.mk +``` + +--- + +**Problem:** Firmware is too large (>2MB for RP2040) + +**Solution:** Build with size optimization: +```bash +make firmware BOARD=raspberry_pi_pico OPTIMIZATION=-Os +``` + +Or remove unused libraries from `mpconfigboard.mk`. + +## Next Steps + +- Build firmware for all your boards +- Test thoroughly on actual hardware +- Create a release with firmware files +- Deploy to your satellite fleet! + +## References + +- Complete guide: [docs/frozen-modules.md](../docs/frozen-modules.md) +- Quick reference: [QUICKSTART.md](QUICKSTART.md) +- CircuitPython docs: https://docs.circuitpython.org/en/latest/BUILDING.html diff --git a/firmware/Makefile b/firmware/Makefile new file mode 100644 index 00000000..ec417fbf --- /dev/null +++ b/firmware/Makefile @@ -0,0 +1,226 @@ +# PySquared Frozen Firmware Build System +# Builds custom CircuitPython firmware with PySquared libraries frozen + +# Configuration +CIRCUITPYTHON_VERSION ?= 9.2.0 +CIRCUITPYTHON_REPO ?= https://github.com/adafruit/circuitpython.git +BOARD ?= raspberry_pi_pico + +# Directories +FIRMWARE_DIR := $(shell pwd) +CP_DIR := $(FIRMWARE_DIR)/circuitpython +FROZEN_DIR := $(CP_DIR)/frozen +BUILD_OUTPUT_DIR := $(FIRMWARE_DIR)/build +PYSQUARED_SRC := $(FIRMWARE_DIR)/../circuitpython-workspaces/flight-software/src/pysquared + +# UV tool for package management +TOOLS_DIR := $(FIRMWARE_DIR)/../tools +UV_VERSION ?= 0.8.14 +UV_DIR := $(TOOLS_DIR)/uv-$(UV_VERSION) +UV := $(UV_DIR)/uv + +# Board-specific settings +RASPBERRYPI_BOARDS := raspberry_pi_pico raspberry_pi_pico_w +PORT_DIR := $(CP_DIR)/ports/raspberrypi +FIRMWARE_FILE := $(PORT_DIR)/build-$(BOARD)/firmware.uf2 + +# Colors for output +GREEN := \033[0;32m +YELLOW := \033[0;33m +RED := \033[0;31m +NC := \033[0m # No Color + +.PHONY: all +all: help + +.PHONY: help +help: ## Display this help message + @echo "PySquared Frozen Firmware Builder" + @echo "" + @echo "Usage: make [BOARD=] [CIRCUITPYTHON_VERSION=]" + @echo "" + @echo "Targets:" + @awk 'BEGIN {FS = ":.*##"; printf ""} /^[a-zA-Z_0-9-]+:.*?##/ { printf " ${GREEN}%-20s${NC} %s\n", $$1, $$2 } /^##@/ { printf "\n${YELLOW}%s${NC}\n", substr($$0, 5) } ' $(MAKEFILE_LIST) + @echo "" + @echo "Examples:" + @echo " make setup # Initial setup (clone CircuitPython, add dependencies)" + @echo " make firmware BOARD=raspberry_pi_pico # Build firmware for Raspberry Pi Pico" + @echo " make all-boards # Build firmware for all supported boards" + @echo " make clean BOARD=raspberry_pi_pico # Clean build artifacts" + +##@ Setup + +.PHONY: check-prereqs +check-prereqs: ## Check for required build tools + @echo "${GREEN}Checking prerequisites...${NC}" + @command -v git >/dev/null 2>&1 || { echo "${RED}git is required but not installed${NC}"; exit 1; } + @command -v make >/dev/null 2>&1 || { echo "${RED}make is required but not installed${NC}"; exit 1; } + @command -v python3 >/dev/null 2>&1 || { echo "${RED}python3 is required but not installed${NC}"; exit 1; } + @command -v arm-none-eabi-gcc >/dev/null 2>&1 || { echo "${RED}arm-none-eabi-gcc is required but not installed${NC}"; echo "Install with: sudo apt install gcc-arm-none-eabi"; exit 1; } + @echo "${GREEN}All prerequisites satisfied${NC}" + +.PHONY: setup +setup: check-prereqs clone-circuitpython install-circuitpython-deps setup-frozen-modules ## Complete first-time setup + @echo "${GREEN}Setup complete!${NC}" + @echo "Run 'make firmware BOARD=' to build firmware" + +.PHONY: clone-circuitpython +clone-circuitpython: ## Clone CircuitPython source + @if [ -d "$(CP_DIR)" ]; then \ + echo "${YELLOW}CircuitPython already cloned, checking out version $(CIRCUITPYTHON_VERSION)${NC}"; \ + cd $(CP_DIR) && git fetch --tags && git checkout $(CIRCUITPYTHON_VERSION); \ + else \ + echo "${GREEN}Cloning CircuitPython $(CIRCUITPYTHON_VERSION)...${NC}"; \ + git clone --depth 1 --branch $(CIRCUITPYTHON_VERSION) $(CIRCUITPYTHON_REPO) $(CP_DIR); \ + fi + @echo "${GREEN}Fetching CircuitPython submodules (raspberrypi port only)...${NC}" + @cd $(CP_DIR) && python3 tools/ci_fetch_deps.py raspberrypi + +.PHONY: install-circuitpython-deps +install-circuitpython-deps: ## Install CircuitPython build dependencies + @if [ ! -d "$(CP_DIR)" ]; then \ + echo "${RED}CircuitPython not cloned. Run 'make clone-circuitpython' first.${NC}"; \ + exit 1; \ + fi + @if [ ! -f "$(UV)" ]; then \ + echo "${YELLOW}UV not found. Installing UV first...${NC}"; \ + cd .. && $(MAKE) uv; \ + fi + @echo "${GREEN}Installing CircuitPython build dependencies in UV virtual environment...${NC}" + @$(UV) pip install -q -r $(CP_DIR)/requirements-dev.txt || \ + echo "${YELLOW}Warning: Some dependencies may have failed to install. Build may still work.${NC}" + +.PHONY: setup-frozen-modules +setup-frozen-modules: ## Set up frozen module directory with PySquared and dependencies + @echo "${GREEN}Setting up frozen modules...${NC}" + @mkdir -p $(FROZEN_DIR) + @$(MAKE) link-pysquared + @$(MAKE) add-dependencies + @echo "${GREEN}Frozen modules configured${NC}" + +.PHONY: link-pysquared +link-pysquared: ## Create symlink to PySquared source in frozen directory + @echo "Linking PySquared library to frozen directory..." + @if [ -L "$(FROZEN_DIR)/pysquared" ] || [ -d "$(FROZEN_DIR)/pysquared" ]; then \ + rm -rf $(FROZEN_DIR)/pysquared; \ + fi + @ln -s $(PYSQUARED_SRC) $(FROZEN_DIR)/pysquared + @echo "${GREEN}PySquared linked${NC}" + +.PHONY: add-dependencies +add-dependencies: ## Add PySquared dependencies as git submodules + @echo "${YELLOW}Note: Dependencies must be added manually as git submodules${NC}" + @echo "See docs/frozen-modules.md for instructions on adding each dependency" + @echo "" + @echo "Dependencies to add (from circuitpython-workspaces/flight-software/pyproject.toml):" + @echo " - adafruit-circuitpython-ina219" + @echo " - adafruit-circuitpython-asyncio" + @echo " - adafruit-circuitpython-drv2605" + @echo " - adafruit-circuitpython-lis2mdl" + @echo " - adafruit-circuitpython-lsm6ds" + @echo " - adafruit-circuitpython-mcp9808" + @echo " - adafruit-circuitpython-neopixel" + @echo " - adafruit-circuitpython-register" + @echo " - adafruit-circuitpython-rfm" + @echo " - adafruit-circuitpython-tca9548a" + @echo " - adafruit-circuitpython-ticks" + @echo " - adafruit-circuitpython-veml7700" + @echo " - adafruit-circuitpython-hashlib" + @echo " - proves-circuitpython-sx126" + @echo " - proves-circuitpython-sx1280" + @echo "" + @echo "Example command to add a dependency:" + @echo " cd $(FROZEN_DIR) && git submodule add https://github.com/adafruit/Adafruit_CircuitPython_INA219" + +##@ Building + +.PHONY: firmware +firmware: ## Build firmware for specified BOARD + @if [ ! -d "$(CP_DIR)" ]; then \ + echo "${RED}CircuitPython not found. Run 'make setup' first.${NC}"; \ + exit 1; \ + fi + @if [ ! -f "$(UV)" ]; then \ + echo "${YELLOW}UV not found. Installing UV first...${NC}"; \ + cd .. && $(MAKE) uv; \ + fi + @echo "${GREEN}Building firmware for $(BOARD)...${NC}" + @echo "CircuitPython version: $(CIRCUITPYTHON_VERSION)" + @echo "Board: $(BOARD)" + @mkdir -p $(BUILD_OUTPUT_DIR) + @cd $(PORT_DIR) && $(UV) run --no-project make BOARD=$(BOARD) + @if [ -f "$(FIRMWARE_FILE)" ]; then \ + cp $(FIRMWARE_FILE) $(BUILD_OUTPUT_DIR)/$(BOARD)-frozen-$(CIRCUITPYTHON_VERSION).uf2; \ + echo "${GREEN}Firmware built successfully!${NC}"; \ + echo "Output: $(BUILD_OUTPUT_DIR)/$(BOARD)-frozen-$(CIRCUITPYTHON_VERSION).uf2"; \ + else \ + echo "${RED}Build failed - firmware file not found${NC}"; \ + exit 1; \ + fi + +.PHONY: all-boards +all-boards: ## Build firmware for all supported boards + @for board in $(RASPBERRYPI_BOARDS); do \ + $(MAKE) firmware BOARD=$$board || exit 1; \ + done + @echo "${GREEN}All boards built successfully!${NC}" + +##@ Maintenance + +.PHONY: clean +clean: ## Clean build artifacts for specified BOARD + @if [ -d "$(PORT_DIR)" ]; then \ + echo "${YELLOW}Cleaning build for $(BOARD)...${NC}"; \ + cd $(PORT_DIR) && make BOARD=$(BOARD) clean; \ + rm -f $(BUILD_OUTPUT_DIR)/$(BOARD)-frozen-*.uf2; \ + fi + +.PHONY: clean-all +clean-all: ## Remove all build artifacts and CircuitPython source + @echo "${RED}Removing all build artifacts and CircuitPython source...${NC}" + @rm -rf $(CP_DIR) + @rm -rf $(BUILD_OUTPUT_DIR) + @echo "${GREEN}Clean complete${NC}" + +.PHONY: list-boards +list-boards: ## List available board configurations + @if [ -d "$(PORT_DIR)/boards" ]; then \ + echo "${GREEN}Available RP2040/RP2350 boards:${NC}"; \ + ls -1 $(PORT_DIR)/boards | grep -E "(pico|proves)" || echo "No boards found"; \ + else \ + echo "${RED}CircuitPython not set up. Run 'make setup' first.${NC}"; \ + fi + +.PHONY: update-circuitpython +update-circuitpython: ## Update CircuitPython to a different version + @if [ -d "$(CP_DIR)" ]; then \ + echo "${YELLOW}Updating CircuitPython to $(CIRCUITPYTHON_VERSION)...${NC}"; \ + cd $(CP_DIR) && git fetch --tags && git checkout $(CIRCUITPYTHON_VERSION); \ + cd $(CP_DIR) && python3 tools/ci_fetch_deps.py raspberrypi; \ + echo "${GREEN}CircuitPython updated${NC}"; \ + else \ + echo "${RED}CircuitPython not cloned. Run 'make setup' first.${NC}"; \ + exit 1; \ + fi + +##@ Information + +.PHONY: info +info: ## Show build configuration + @echo "${GREEN}PySquared Frozen Firmware Build Configuration${NC}" + @echo "" + @echo "CircuitPython Version: $(CIRCUITPYTHON_VERSION)" + @echo "CircuitPython Directory: $(CP_DIR)" + @echo "Frozen Modules Directory: $(FROZEN_DIR)" + @echo "Build Output Directory: $(BUILD_OUTPUT_DIR)" + @echo "PySquared Source: $(PYSQUARED_SRC)" + @echo "" + @echo "Current Board: $(BOARD)" + @echo "Port Directory: $(PORT_DIR)" + @echo "" + @if [ -d "$(FROZEN_DIR)" ]; then \ + echo "Frozen Modules:"; \ + ls -1 $(FROZEN_DIR) 2>/dev/null | sed 's/^/ - /' || echo " (none)"; \ + else \ + echo "Frozen Modules: (not set up)"; \ + fi diff --git a/firmware/QUICKSTART.md b/firmware/QUICKSTART.md new file mode 100644 index 00000000..15154753 --- /dev/null +++ b/firmware/QUICKSTART.md @@ -0,0 +1,186 @@ +# Quick Reference: Building Frozen Firmware + +This is a condensed reference for building custom CircuitPython firmware with PySquared libraries frozen. For complete details, see [frozen-modules.md](frozen-modules.md). + +## Prerequisites + +**Ubuntu/Debian:** +```bash +sudo apt update +sudo apt install build-essential git python3 python3-pip gcc-arm-none-eabi +``` + +**macOS:** +```bash +brew install armmbed/formulae/arm-none-eabi-gcc python3 git +``` + +**Windows:** Use WSL and follow Ubuntu instructions + +## Quick Build Guide + +### 1. First Time Setup + +```bash +cd firmware +make setup +``` + +This clones CircuitPython 9.2.0, fetches only the submodules needed for RP2040/RP2350 boards (raspberrypi port), and installs the required Python build dependencies in the UV virtual environment (avoiding system Python conflicts on macOS), significantly reducing download size and time. + +### 2. Add Dependencies (Manual) + +Each dependency from `circuitpython-workspaces/flight-software/pyproject.toml` needs to be added as a git submodule: + +```bash +cd circuitpython/frozen + +# Example: Add INA219 library +git submodule add https://github.com/adafruit/Adafruit_CircuitPython_INA219 +cd Adafruit_CircuitPython_INA219 +git checkout 3.4.26 # Match version in pyproject.toml + +# Repeat for each dependency... +``` + +**Dependencies to add:** +- Adafruit_CircuitPython_INA219 (v3.4.26) +- Adafruit_CircuitPython_asyncio (v1.3.3) +- Adafruit_CircuitPython_DRV2605 (v1.3.4) +- Adafruit_CircuitPython_LIS2MDL (v2.1.23) +- Adafruit_CircuitPython_LSM6DS (v4.5.13) +- Adafruit_CircuitPython_MCP9808 (v3.3.24) +- Adafruit_CircuitPython_NeoPixel (v6.3.12) +- Adafruit_CircuitPython_Register (v1.10.4) +- Adafruit_CircuitPython_RFM (v1.0.6) +- Adafruit_CircuitPython_TCA9548A (custom fork) +- Adafruit_CircuitPython_Ticks (v1.1.1) +- Adafruit_CircuitPython_VEML7700 (v2.1.4) +- Adafruit_CircuitPython_hashlib (v1.4.19) +- micropySX126X (v1.0.0) +- CircuitPython_SX1280 (v1.0.4) + +Or use the helper script (still needs manual verification): +```bash +cd firmware +./add_dependencies.py --list # See what would be added +``` + +### 3. Configure Board + +Edit the board configuration to include frozen modules: + +```bash +cd circuitpython/ports/raspberrypi/boards/raspberry_pi_pico/ +``` + +Add to `mpconfigboard.mk`: +```makefile +# PySquared frozen modules +FROZEN_MPY_DIRS += $(TOP)/frozen/pysquared +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_INA219 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_LIS2MDL +# ... add all other dependencies +``` + +### 4. Build Firmware + +```bash +cd firmware +make firmware BOARD=raspberry_pi_pico +``` + +**Important:** Always use `make firmware` from the firmware directory. This ensures the build runs with UV's Python environment where dependencies are installed. + +Output: `build/raspberry_pi_pico-frozen-9.2.0.uf2` + +### 5. Flash Firmware + +1. Put board in bootloader mode (double-press RESET) +2. Copy `build/raspberry_pi_pico-frozen-9.2.0.uf2` to the USB drive that appears +3. Board will reset and boot with frozen modules + +### 6. Verify + +Connect to serial console and test: +```python +import pysquared +print(pysquared.__file__) # Should show it's built-in +``` + +## Common Commands + +```bash +# List available boards +make list-boards + +# Build for specific board +make firmware BOARD=raspberry_pi_pico + +# Build for all boards +make all-boards + +# Clean build artifacts +make clean BOARD=raspberry_pi_pico + +# Remove everything including CircuitPython +make clean-all + +# Show configuration +make info + +# Update to different CircuitPython version +make update-circuitpython CIRCUITPYTHON_VERSION=9.1.4 +``` + +## Troubleshooting + +| Problem | Solution | +|---------|----------| +| `arm-none-eabi-gcc: command not found` | Install ARM toolchain (see prerequisites) | +| `No rule to make target` | Check board name with `make list-boards` | +| Firmware too large | Remove unused dependencies or use `OPTIMIZATION=-Os` | +| Import error after flashing | Verify dependency is in `mpconfigboard.mk` | +| Build fails with submodule error | Run `python3 tools/ci_fetch_deps.py raspberrypi` in circuitpython/ | +| Python import errors (cascadetoml, jinja2, etc.) | Run `make install-circuitpython-deps` | +| `externally-managed-environment` error (macOS) | Use `make setup` - now installs in UV virtual environment | + +## Advanced + +**Build with different CircuitPython version:** +```bash +make setup CIRCUITPYTHON_VERSION=9.1.4 +``` + +**Optimize for size:** +```bash +make firmware BOARD=raspberry_pi_pico OPTIMIZATION=-Os +``` + +**Verbose build:** +```bash +make firmware BOARD=raspberry_pi_pico V=1 +``` + +## What Gets Frozen + +When you build firmware: +- ✅ PySquared library (`pysquared` package) +- ✅ All dependencies from pyproject.toml +- ✅ No need for `/lib` directory on the board +- ❌ User code (main.py, config.json) still goes on filesystem + +## Updating + +To update frozen modules: +1. Update library source code +2. Rebuild firmware: `make firmware BOARD=` +3. Flash new firmware to all boards + +**Note:** You cannot update frozen modules without reflashing firmware. + +## Support + +- Full guide: [frozen-modules.md](frozen-modules.md) +- CircuitPython docs: https://docs.circuitpython.org/ +- Issues: https://github.com/proveskit/pysquared/issues diff --git a/firmware/README.md b/firmware/README.md new file mode 100644 index 00000000..627a0cbc --- /dev/null +++ b/firmware/README.md @@ -0,0 +1,153 @@ +# PySquared Frozen Firmware Builder + +This directory contains the infrastructure for building custom CircuitPython firmware with PySquared libraries frozen (compiled into the firmware binary). + +## Quick Start + +See the main [Frozen Modules Documentation](../docs/frozen-modules.md) for complete instructions. + +### Prerequisites + +1. Linux, macOS, or Windows with WSL +2. ARM cross-compiler: `gcc-arm-none-eabi` +3. Build tools: `git`, `make`, `python3` + +Install on Ubuntu/Debian: +```bash +sudo apt update +sudo apt install build-essential git python3 python3-pip gcc-arm-none-eabi +``` + +### Build Firmware + +1. **Initial setup** (only needed once): + ```bash + make setup + ``` + This clones CircuitPython and sets up dependencies. + +2. **Build for a specific board**: + ```bash + make firmware BOARD=proves_rp2040_v5 + ``` + +3. **Flash firmware** to your board: + - Put board in bootloader mode (double-press RESET) + - Copy `build/-frozen-.uf2` to the USB drive that appears + +## Supported Boards + +- `proves_rp2040_v4` - PROVES Kit v4 (RP2040) +- `proves_rp2040_v5` - PROVES Kit v5 (RP2040) +- `proves_rp2350_v5a` - PROVES Kit v5a (RP2350) +- `proves_rp2350_v5b` - PROVES Kit v5b (RP2350) + +## What Gets Frozen + +All dependencies from `circuitpython-workspaces/flight-software/pyproject.toml` are frozen into the firmware: + +- `pysquared` library (the flight software itself) +- `adafruit-circuitpython-ina219` +- `adafruit-circuitpython-asyncio` +- `adafruit-circuitpython-drv2605` +- `adafruit-circuitpython-lis2mdl` +- `adafruit-circuitpython-lsm6ds` +- `adafruit-circuitpython-mcp9808` +- `adafruit-circuitpython-neopixel` +- `adafruit-circuitpython-register` +- `adafruit-circuitpython-rfm` +- `adafruit-circuitpython-tca9548a` +- `adafruit-circuitpython-ticks` +- `adafruit-circuitpython-veml7700` +- `adafruit-circuitpython-hashlib` +- `proves-circuitpython-sx126` +- `proves-circuitpython-sx1280` + +## Makefile Targets + +- `make setup` - Clone CircuitPython and dependencies (first-time setup) +- `make firmware BOARD=` - Build firmware for specified board +- `make all-boards` - Build firmware for all PROVES boards +- `make clean` - Clean build artifacts for a board +- `make clean-all` - Remove all build artifacts and CircuitPython source +- `make help` - Show available targets + +## Configuration + +Edit `Makefile` to change: + +- `CIRCUITPYTHON_VERSION` - Version of CircuitPython to build (default: 9.2.0) +- Board-specific frozen modules in board configuration files + +## Directory Structure + +After running `make setup`: + +``` +firmware/ +├── circuitpython/ # CircuitPython source (git clone) +│ ├── frozen/ # Libraries to freeze +│ │ ├── pysquared/ # Symlink to PySquared source +│ │ └── ... # Adafruit dependencies (git submodules) +│ └── ports/ +│ └── raspberrypi/ # RP2040/RP2350 builds +├── boards/ # Custom board configurations +├── build/ # Output firmware files +├── Makefile # Build automation +└── README.md # This file +``` + +## Troubleshooting + +### "arm-none-eabi-gcc: command not found" +Install the ARM toolchain (see Prerequisites above). + +### "No rule to make target 'build-'" +Check that BOARD name is correct. Use `make list-boards` to see available boards. + +### Firmware too large +Remove unused dependencies or optimize build: +```bash +make firmware BOARD= OPTIMIZATION=-Os +``` + +### Import errors after flashing +Verify all dependencies are in the frozen modules list. Check `mpconfigboard.mk` for your board. + +## Advanced Usage + +### Building Custom CircuitPython Version + +```bash +make setup CIRCUITPYTHON_VERSION=9.1.4 +make firmware BOARD=proves_rp2040_v5 +``` + +### Adding Custom Frozen Modules + +1. Add the library to `circuitpython/frozen/` +2. Update `boards//mpconfigboard.mk` +3. Rebuild: `make firmware BOARD=` + +### Build Options + +```bash +# Optimize for size +make firmware BOARD= OPTIMIZATION=-Os + +# Enable debug symbols +make firmware BOARD= DEBUG=1 + +# Verbose build output +make firmware BOARD= V=1 +``` + +## CI/CD + +See `.github/workflows/build-firmware.yaml` for automated builds on GitHub Actions. + +## More Information + +- [Complete Frozen Modules Guide](../docs/frozen-modules.md) +- [CircuitPython Build Documentation](https://docs.circuitpython.org/en/latest/BUILDING.html) +- [PySquared Documentation](https://proveskit.github.io/pysquared/) diff --git a/firmware/add_dependencies.py b/firmware/add_dependencies.py new file mode 100755 index 00000000..35816890 --- /dev/null +++ b/firmware/add_dependencies.py @@ -0,0 +1,220 @@ +#!/usr/bin/env python3 +""" +Add PySquared dependencies to CircuitPython frozen modules. + +This script reads the dependencies from circuitpython-workspaces/flight-software/pyproject.toml +and helps set up the corresponding repositories as git submodules in the CircuitPython frozen/ directory. +""" + +import argparse +import re +import subprocess +import sys +from pathlib import Path + + +def parse_pyproject_dependencies(pyproject_path: Path) -> list[dict]: + """Parse dependencies from pyproject.toml.""" + dependencies = [] + + with open(pyproject_path) as f: + content = f.read() + + # Find the dependencies section + deps_section = re.search(r"dependencies\s*=\s*\[(.*?)\]", content, re.DOTALL) + if not deps_section: + print("Error: Could not find dependencies section in pyproject.toml") + return [] + + deps_text = deps_section.group(1) + + # Parse each dependency line + for line in deps_text.split("\n"): + line = line.strip().strip(",").strip('"') + if not line or line.startswith("#"): + continue + + dep_info = {} + + # Check if it's a git dependency + if "@" in line and "git+" in line: + # Format: "package @ git+https://github.com/org/repo@version" + # First split on ' @ ' to separate package from URL + parts = line.split(" @ ", 1) + package_name = parts[0].strip() + git_url = parts[1].strip() if len(parts) > 1 else "" + + if git_url.startswith("git+"): + git_url = git_url[4:] # Remove 'git+' prefix + + # Now split URL and version on last '@' + if "@" in git_url: + url, version = git_url.rsplit("@", 1) + dep_info["name"] = package_name + dep_info["url"] = url + dep_info["version"] = version + dep_info["type"] = "git" + elif "==" in line: + # Format: "package==version" + package_name, version = line.split("==") + package_name = package_name.strip() + version = version.strip() + + # Convert PyPI package name to GitHub repo name + # adafruit-circuitpython-ina219 -> Adafruit_CircuitPython_INA219 + if package_name.startswith("adafruit-circuitpython-"): + lib_name = package_name.replace("adafruit-circuitpython-", "") + lib_name_parts = lib_name.split("-") + lib_name_camel = "".join( + p.upper() if len(p) <= 3 else p.capitalize() for p in lib_name_parts + ) + repo_name = f"Adafruit_CircuitPython_{lib_name_camel}" + url = f"https://github.com/adafruit/{repo_name}" + + dep_info["name"] = package_name + dep_info["url"] = url + dep_info["version"] = version + dep_info["type"] = "pypi" + + if dep_info: + dependencies.append(dep_info) + + return dependencies + + +def add_submodule(frozen_dir: Path, dep: dict, dry_run: bool = False) -> bool: + """Add a dependency as a git submodule.""" + # Determine directory name + if dep["type"] == "git": + # Extract repo name from URL + repo_name = dep["url"].rstrip("/").split("/")[-1] + if repo_name.endswith(".git"): + repo_name = repo_name[:-4] + else: + # Use the repo name from URL + repo_name = dep["url"].rstrip("/").split("/")[-1] + + submodule_path = frozen_dir / repo_name + + # Check if submodule already exists + if submodule_path.exists(): + print(f" ⚠️ {repo_name} already exists, skipping") + return True + + print(f" 📦 Adding {dep['name']} ({repo_name})") + print(f" URL: {dep['url']}") + print(f" Version: {dep['version']}") + + if dry_run: + print(" [DRY RUN - would add submodule]") + return True + + try: + # Add as git submodule + cmd = ["git", "submodule", "add", dep["url"], str(submodule_path)] + subprocess.run( + cmd, check=True, cwd=frozen_dir.parent, capture_output=True, text=True + ) + + # Checkout specific version + subprocess.run( + ["git", "checkout", dep["version"]], + check=True, + cwd=submodule_path, + capture_output=True, + text=True, + ) + + print(" ✅ Added successfully") + return True + except subprocess.CalledProcessError as e: + print(f" ❌ Failed to add: {e.stderr}") + return False + + +def main(): + parser = argparse.ArgumentParser( + description="Add PySquared dependencies to CircuitPython frozen modules" + ) + parser.add_argument( + "--pyproject", + type=Path, + default=Path(__file__).parent.parent + / "circuitpython-workspaces" + / "flight-software" + / "pyproject.toml", + help="Path to pyproject.toml (default: ../circuitpython-workspaces/flight-software/pyproject.toml)", + ) + parser.add_argument( + "--frozen-dir", + type=Path, + default=Path(__file__).parent / "circuitpython" / "frozen", + help="Path to CircuitPython frozen directory (default: ./circuitpython/frozen)", + ) + parser.add_argument( + "--dry-run", + action="store_true", + help="Show what would be done without actually doing it", + ) + parser.add_argument( + "--list", action="store_true", help="List dependencies without adding them" + ) + + args = parser.parse_args() + + # Validate paths + if not args.pyproject.exists(): + print(f"Error: pyproject.toml not found at {args.pyproject}") + sys.exit(1) + + if not args.list and not args.frozen_dir.parent.exists(): + print(f"Error: CircuitPython directory not found at {args.frozen_dir.parent}") + print("Run 'make setup' first to clone CircuitPython") + sys.exit(1) + + # Parse dependencies + print(f"📖 Reading dependencies from {args.pyproject}") + dependencies = parse_pyproject_dependencies(args.pyproject) + + if not dependencies: + print("No dependencies found") + sys.exit(1) + + print(f"\n✨ Found {len(dependencies)} dependencies\n") + + # List mode - just show dependencies + if args.list: + for dep in dependencies: + print(f" • {dep['name']}") + print(f" URL: {dep['url']}") + print(f" Version: {dep['version']}") + print(f" Type: {dep['type']}") + print() + return + + # Create frozen directory if it doesn't exist + args.frozen_dir.mkdir(parents=True, exist_ok=True) + + # Add each dependency + if args.dry_run: + print("🔍 DRY RUN MODE - No changes will be made\n") + + success_count = 0 + for dep in dependencies: + if add_submodule(args.frozen_dir, dep, args.dry_run): + success_count += 1 + print() + + print( + f"\n{'📋 Would add' if args.dry_run else '✅ Added'} {success_count}/{len(dependencies)} dependencies" + ) + + if not args.dry_run and success_count > 0: + print("\n⚠️ Don't forget to:") + print(" 1. Commit the new submodules to git") + print(" 2. Update board mpconfigboard.mk files to include frozen modules") + print(" 3. Build firmware with: make firmware BOARD=") + + +if __name__ == "__main__": + main() diff --git a/firmware/boards/README.md b/firmware/boards/README.md new file mode 100644 index 00000000..c9c58d1b --- /dev/null +++ b/firmware/boards/README.md @@ -0,0 +1,125 @@ +# Board Configuration Examples for PROVES Kit + +This directory contains example board configuration files for PROVES Kit hardware. + +These configurations show how to add PySquared libraries as frozen modules to CircuitPython firmware for different PROVES board versions. + +## Usage + +After running `make setup`, these configurations need to be integrated into the CircuitPython source tree: + +### Option 1: Create New Board Definitions + +If you want to create new board variants specifically for PROVES Kit: + +```bash +# Copy board config to CircuitPython source +cp boards/proves_rp2040_v5/mpconfigboard.mk \ + circuitpython/ports/raspberrypi/boards/proves_rp2040_v5/ + +cp boards/proves_rp2040_v5/mpconfigboard.h \ + circuitpython/ports/raspberrypi/boards/proves_rp2040_v5/ + +cp boards/proves_rp2040_v5/pins.c \ + circuitpython/ports/raspberrypi/boards/proves_rp2040_v5/ +``` + +### Option 2: Modify Existing Board Definitions + +If you want to add frozen modules to existing Raspberry Pi Pico boards: + +```bash +# Edit the mpconfigboard.mk for your board +# For example, for Raspberry Pi Pico: +cd circuitpython/ports/raspberrypi/boards/raspberry_pi_pico/ + +# Add this to the end of mpconfigboard.mk: +cat >> mpconfigboard.mk << 'EOF' + +# PySquared frozen modules +FROZEN_MPY_DIRS += $(TOP)/frozen/pysquared +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_INA219 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_LIS2MDL +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_LSM6DS +# ... add all other dependencies +EOF +``` + +## Board Files + +Each board directory should contain: + +- **mpconfigboard.h** - C header with board-specific pin definitions and features +- **mpconfigboard.mk** - Makefile with board configuration including frozen modules +- **pins.c** - Pin mapping for CircuitPython +- **board.c** (optional) - Board-specific initialization code + +## Example: Adding Frozen Modules to mpconfigboard.mk + +```makefile +# Base board configuration +USB_VID = 0x2E8A +USB_PID = 0x0003 +USB_PRODUCT = "PROVES Kit v5" +USB_MANUFACTURER = "PROVES" + +CHIP_VARIANT = RP2040 +CHIP_FAMILY = rp2 + +EXTERNAL_FLASH_DEVICES = "W25Q16JVxQ" + +# Add PySquared and all dependencies as frozen modules +FROZEN_MPY_DIRS += $(TOP)/frozen/pysquared + +# Adafruit CircuitPython Libraries +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Ina219 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_asyncio +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_DRV2605 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Lis2mdl +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_LSM6DS +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Mcp9808 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Register +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_RFM +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_TCA9548A +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Ticks +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Veml7700 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Hashlib + +# PROVES CircuitPython Libraries +FROZEN_MPY_DIRS += $(TOP)/frozen/micropySX126X +FROZEN_MPY_DIRS += $(TOP)/frozen/CircuitPython_SX1280 +``` + +## PROVES Kit Board Variants + +The PROVES Kit has multiple hardware versions: + +| Board | Chip | Flash | Description | +|-------|------|-------|-------------| +| v4 | RP2040 | 2MB | Early version | +| v5 | RP2040 | 2MB | Current RP2040 version | +| v5a | RP2350 | 4MB | New RP2350 variant A | +| v5b | RP2350 | 4MB | New RP2350 variant B | + +Each may need slightly different configurations for: +- Pin assignments +- Flash chip specifications +- Peripheral configurations + +## Building with Custom Board + +After setting up board files: + +```bash +# Build for custom PROVES board +make firmware BOARD=proves_rp2040_v5 + +# Or modify an existing board +make firmware BOARD=raspberry_pi_pico +``` + +## References + +- [CircuitPython Board Porting Guide](https://learn.adafruit.com/how-to-add-a-new-board-to-circuitpython) +- [RP2040 Board Examples](https://github.com/adafruit/circuitpython/tree/main/ports/raspberrypi/boards) +- [PySquared Dependencies](../../circuitpython-workspaces/flight-software/pyproject.toml) diff --git a/mkdocs.yaml b/mkdocs.yaml index d0ca5f40..983186ed 100644 --- a/mkdocs.yaml +++ b/mkdocs.yaml @@ -15,6 +15,7 @@ nav: - Home: index.md - Getting Started: getting-started.md - Radio Test: radio-test.md + - Frozen Modules: frozen-modules.md - Design Guide: design-guide.md - Contributing Guide: contributing.md - API Reference: api.md @@ -74,6 +75,8 @@ plugins: - getting-started.md Radio Test: - radio-test.md + Frozen Modules: + - frozen-modules.md Design Guide: - design-guide.md Contributing Guide: diff --git a/pyproject.toml b/pyproject.toml index 74099219..668dcab0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -67,4 +67,5 @@ verbose = 2 color = true exclude = [ "circuitpython-workspaces/typeshed", + "firmware", ]