Hi all. I’m an embedded engineer, and I have lost more than one afternoon to a linker error saying the firmware is a few KB over, bisecting weeks of commits to find out where the flash went. So I built MemBrowse, a firmware memory observation platform.
PlatformIO is already good at showing memory footprint: the RAM/Flash bars after every build, pio run -t size for the section breakdown, and PIO Home’s Inspect view that takes a build apart down to individual symbols. Those give you a precise picture of one build on your machine.
What it can’t tell you is when memory changed and why. Which PR pushed FLASH from 86% to 91% last month? When you bump a library in lib_deps, did the new version add 3 KB, and in which functions?
MemBrowse takes the region limits from the linker script itself, so the numbers are the ones the linker enforces, per region, and a wrong memory region or origin shows up as a misconfiguration instead of going unnoticed. It runs in CI, keeps the history per commit and target, and posts the flash/RAM diff on every PR with the symbols responsible and the library or source file they came from. Bloat gets caught in review instead of before a release.
Here is Flipper Devices’ dashboard for their Flipper One MCU firmware. Every commit on dev is analyzed:
Seven months of FLASH on the RP2350 target of flipperone-mcu-firmware, every point a commit.
On the PR itself it looks like this:
From meshtastic/firmware#12113: a GPS driver change costing 2 KB of flash on one ESP32-C3 target and saving 7 KB on the Pico W, visible before merge.
It works with any ELF. The action reads the ELF and the linker scripts from your PlatformIO build, so ESP32/Xtensa, nRF52, STM32, RP2040 and RISC-V all work with the same step. GNU ld scripts and IAR .icf files are both supported. No library to add, no changes to platformio.ini. It is not tied to PlatformIO either; the Flipper firmware above is built without it.
Here is Meshtastic’s dashboard and how they use it. The Meshtastic firmware builds 180+ PlatformIO targets across ESP32, nRF52, RP2xx0 and STM32. Their build workflow exports the ELF, linker scripts and map file as an artifact, and a second workflow runs the action on every PR and push for each target, so every target has its own history and PR diff: membrowse.com/public/meshtastic/firmware. The workflow is .github/workflows/membrowse_report.yml in meshtastic/firmware if you want a multi-target reference.
A minimal workflow for one env:
name: Memory report
on: [push, pull_request]
jobs:
memory:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 2 # parent commit, for the diff
- uses: actions/setup-python@v5
with:
python-version: "3.x"
- run: pip install platformio
- run: pio run -e tbeam
- uses: membrowse/membrowse-action@v1
with:
elf: .pio/build/tbeam/firmware.elf
target_name: tbeam
api_key: ${{ secrets.MEMBROWSE_API_KEY }}
# ld: memory.ld sections.ld # optional: real per-region limits
# map_file: .pio/build/tbeam/output.map # optional: per-library attribution
- uses: membrowse/membrowse-action/comment-action@v1
if: github.event_name == 'pull_request'
with:
api_key: ${{ secrets.MEMBROWSE_API_KEY }}
commit: ${{ github.event.pull_request.head.sha }}
Fork PRs upload tokenless, so open-source projects get comments on external contributions too.
Setup with Claude Code if you don’t want to wire it by hand: the plugin analyzes your build, finds the linker scripts, and writes the workflows.
/plugin marketplace add membrowse/membrowse-action
/plugin install membrowse@membrowse-marketplace
/membrowse-integrate
Free for open source. Docs: docs.membrowse.com. Source and action: membrowse/membrowse-action.


