Troubleshooting
Common pitfalls and their solutions.
cargo test fails with target errors
The picodroid firmware crate is bare-metal and the workspace sets no default Cargo target, so bare cargo test can’t pick a host triple and fails. Use the test script instead:
./scripts/test.shThis runs tests on the host target automatically.
./scripts/flash.sh never exits
This is expected. flash.sh flashes the firmware and then streams RTT log output indefinitely. Run it in a separate terminal or in the background:
./scripts/flash.sh --app helloworld &blinky loops forever in the simulator
The blinky app blinks an LED in an infinite loop, which means the simulator will never exit. Kill it after a timeout:
# macOS (no built-in timeout command)perl -e 'alarm 5; exec @ARGV' ./scripts/sim.sh --app blinky
# Linuxtimeout 5 ./scripts/sim.sh --app blinkyClippy fails when run on the host
Bare cargo clippy fails because there’s no default target set and the firmware crate needs an explicit target plus board feature flags. Use the feature flags:
# RP2040PICODROID_APK_PATH=build/apks/helloworld.papk cargo clippy --no-default-features --features board-testbench-rp2040 -- --deny=warnings
# RP2350PICODROID_APK_PATH=build/apks/helloworld.papk cargo clippy --target thumbv8m.main-none-eabihf --no-default-features --features board-testbench-rp2350 -- --deny=warnings
# Simulator (host)PICODROID_APK_PATH=build/apks/helloworld.papk cargo clippy --target "$(rustc -vV | awk '/^host:/ { print $2 }')" --no-default-features --features sim,board-testbench-rp2350 -- --deny=warningsOr just run the full pre-commit suite which handles all of this:
./scripts/pre-commitUART / COM port issues with pdb
- The default serial port is
/dev/cu.usbmodem102at 115200 baud - Connect your terminal (CoolTerm, screen, etc.) BEFORE flashing — the USB CDC port enumerates during boot
- Avoid raw
stty/echocommands to the port — they can cause a USB reset and disconnect the device - If the port disappears, unplug and replug the Pico, then re-run
pdb devicesto find the new port name
Pre-commit hook not running
The hook must be symlinked after cloning:
ln -s ../../scripts/pre-commit .git/hooks/pre-commitTo verify it is installed: ls -la .git/hooks/pre-commit should show it pointing to ../../scripts/pre-commit.
PAPK framework-map-version incompatible with firmware
The firmware panics at PAPK load with something like:
PAPK framework-map-version incompatible with firmware (firmware = 0.0.0): FrameworkVersionMismatchThe two most common causes:
-
Firmware and PAPK disagree about
--shrink. Shrinking is opt-in per build. If you built the firmware without--shrinkbut the PAPK with it (or vice versa), load-time linkage would fail — soverify_compatrejects the combination up front. Rebuild both with the same flag:Terminal window # Either both off (default)./scripts/build-apk.sh --app <name>./scripts/flash.sh --app <name># Or both on./scripts/build-apk.sh --app <name> --shrink./scripts/flash.sh --app <name> --shrink -
PAPK was packaged against a shrink-map release newer than the firmware’s (both sides
--shrink-on, but PAPK’s Cargo.toml version bumped past what the firmware knows). Rebuild the PAPK against the current source tree.
FrameworkVersionMissing means the PAPK predates the manifest key
entirely (legacy, pre-M1). Also fixed by rebuilding. See
Class-name shrinker for the full compatibility story.
api contract: FAILED — the app build stops in verifyApiContract
Apps compile against the host JDK’s full java.*, but pico-jvm implements a
subset; verifyApiContract (part of assemblePapk) rejects any java.*
class or member the runtime does not serve before it can die on device as
NoSuchMethod. The report (examples/<app>/build/reports/api-contract.txt)
lists each reference with the reason, the call sites and a hint — e.g.
java/util/LinkedList → use ArrayList, String.matches → no regex,
System.out → picodroid.util.Log. Consult the
compatibility matrix for the supported
surface. An EXCLUDED ON BOARD section means the target board drops that
class from its framework (framework_class_excludes in its board.toml);
build for a larger board or probe-and-degrade.
-Ppicodroid.apiContract=warn (or off) bypasses the check while
experimenting, e.g. ./gradlew :examples:myapp:assemblePapk -Ppicodroid.apiContract=warn.
Do not edit sdk/api-contract.tsv — it is generated from the runtime’s
tables; to support a new member add the builtin arm and its
BUILTIN_METHODS row, then run scripts/gen-api-contract.sh.
pdb install says “Refusing to install”
pdb install runs a host-side compatibility pre-flight against the
device’s running firmware before erasing flash. Two messages you may see:
-
“PAPK is incompatible with running firmware” — the PAPK and the running firmware disagree about
--shrink(or the PAPK’s release map version is newer). The on-device PAPK is untouched. Rebuild the PAPK with the matching--shrinksetting and re-runpdb install. -
“Firmware advertises ‘picodroid/2.0’, which predates the framework-map-version protocol field” — the firmware was built before the compat-check protocol.
pdb installwon’t push to it over USB. Reflash the firmware via SWD with./scripts/flash.sh, which brings up apicodroid/2.1build that advertises the field.
If --skip-host-check is passed (HIL test usage) and the device-side
check still fires, pdb reports device rejected install: STATUS_INCOMPAT — same fix as case 1.
Java formatting check fails
Java sources must follow Google Java Style. Reformat before committing:
./scripts/format_java.sh formatThe formatter JAR is downloaded automatically on first use. JDK 11+ is required.
Gradle build fails with “JAVA_HOME is not set” or “no Java runtime”
Java compilation runs through the Gradle wrapper (./gradlew) in-tree — no separate Gradle install is needed, but a JDK 11+ must be on PATH. Install one (see getting-started.md → JDK) and verify with javac --version. If JAVA_HOME isn’t set, point it at your JDK install root before rebuilding.
registerListener returns false / sensor event never fires
Three common causes:
- No
[[sensor]]entry inboard.toml.SensorManager.getDefaultSensor(type)returnsnullif the board doesn’t declare a matching sensor. Add an entry — see porting-guide.md → board.toml reference. - I2C wiring mismatch. The BME688 driver uses the
bus+addrfromboard.toml. Verify the sensor ACKs on that bus withexamples/i2cdemo. - Registration cap.
SensorManagerallows up to 8 concurrent registrations. CallunregisterListener()fromonPause()/onDestroy()-equivalent paths to avoid leaking slots across app swaps.
Networking
Build fails with vendor/cyw43-driver is the unpatched upstream
The vendor/cyw43-driver submodule moved to the patched picodroid fork. A checkout cloned before the switch still points at upstream, and the network build fails early rather than producing broken WiFi firmware. Re-sync the submodule:
git submodule sync && git submodule update --init vendor/cyw43-driverRTT shows wifi: no SSID configured (PICODROID_WIFI_SSID) — not joining
WiFi credentials are build-time environment variables, baked into the image — setting them at flash or run time does nothing. Rebuild (and reflash) with them set; until then the stack starts but stays offline:
PICODROID_WIFI_SSID='MyAP' PICODROID_WIFI_PASS='secret' ./scripts/flash.sh --board testbench_rp2350w --app netdemo --releaseSockets fail immediately after boot
The WiFi join takes ~6 s and DHCP completes around 10 s after boot, so an app that opens a socket in its first moments races the link and loses. Poll NetworkInfo.isConnected() against a deadline (the example apps wait up to 30 s) before opening sockets — see WiFi & networking setup.
Repeated net: down lines over RTT
The join is retrying. This is a known issue — see Known issues & current limits. Once the join succeeds you’ll see net: up, ip a.b.c.d.
HttpURLConnection hangs or throws at connect()
HTTPS URLs are rejected—HttpURLConnectionis HTTP/1.1 only; no TLS. Use the raw socket API if you need TLS and are willing to bundle it.setFixedLengthStreamingMode() required for output— for POST/PUT, callsetDoOutput(true)andsetFixedLengthStreamingMode(n)with the exact body byte count beforeconnect().- Hangs are usually DNS-resolution failures against an unreachable host. There is no per-operation timeout parameter yet; check that the
Hostheader resolves from the device’s network. Connection: closeis always sent — keep-alive / pipelining is not supported, so oneHttpURLConnection= one request.