
Pico Plus 2 RP2350 THT
aaronsilinskas/aura-prototype
A monorepo to prototype Aura Game Engine core features.

A monorepo to prototype Aura Game Engine core features.
Pico RP2350 USB Power is an open-source printed circuit board by aaronsilinskas, from the aaronsilinskas/aura-prototype repository on GitHub, published under the MIT licence. It is a 4-layer board of 75 × 63 mm with 47 parts on 56 nets, routed with 343 tracks and 58 vias.
It is built around the 74HCT244D, AO3406 and AO3401A, with the AO3400A among its other parts. Its parts include 22 connectors, 17 resistors, 5 transistors, 1 IC and 1 diode. It belongs with the RP2040 and RP2350 boards of this library.
The prototype firmware and game engine for Aura, a platform for interactive adventure games played in the real world with handheld devices and props that light up, make sound, and react to how you play. Aura is being built by Mindwidgets; the site explains what Aura is and how it works today.
This repository is where the software is worked out: a game engine that turns buttons, motion, infrared, and radio into games, and an animation engine for the lights, sound, and vibration players get back. It is written in Python and runs on CircuitPython microcontrollers, and most of it also runs on a desktop so it can be tested without hardware.
Aura is in prototype. This is working code under active development, not a kit.
electronics/ holds only
rough sketches so far. The prototype is hand-wired from off-the-shelf breakout
boards.packs/scenes/, one folder per scene, alongside scenes for
testing hardware and browsing effects.Issues and pull requests are not being sought yet. To hear when there is something to build or play, sign up at mindwidgets.com.
effects/ Composable animation engine for lights, sound, and vibration
engine/ Event-driven game loop: rules, scenes, state, packs, and I/O abstractions
packs/ Shared effects and rules, and the scenes (games) built from them
magic/ Spell and aura game logic (elements, buffs and debuffs, cast types)
hardware/ CircuitPython drivers and the hardware abstractions above them
app/ Wires the engine to the hardware and runs a scene on a device
examples/ Entry points: a desktop terminal demo and the on-device scene runner
scripts/ Deploy and maintenance scripts that run on the desktop
electronics/ Circuit board designs (rough sketches only, so far)
docs/ Domain map, glossary, and hardware measurementsA few words used throughout the code:
See docs/domain.md for key types and constraints, and
GLOSSARY.md for the full glossary.
You need Python 3.10 or newer and uv.
uv sync # install the development tools
uv run pytest # run the test suiteTo watch the effects engine run, start the terminal demo from the repository root (macOS or Linux, in a terminal with 24-bit color):
PYTHONPATH=. uv run python examples/ansi-multi-effect-demo.pyPress a for fire, b for water, c for lightning, d to stop them all, and q
to quit.
There is no single finished device. The firmware builds whichever of these parts the device's config file declares, and leaves out the rest:
| Part | What the code supports |
|---|---|
| Microcontroller | RP2350 with PSRAM, running CircuitPython 10. Pimoroni's Pico 2 boards with PSRAM work well, for example the Pico Plus 2 or Pico LiPo 2. A standard Raspberry Pi Pico 2 has no PSRAM and will not work |
| Lights | IS31FL3741 13×9 RGB LED matrix, NeoPixel strips |
| Sound | I2S audio output |
| Vibration | DRV2605L haptic driver |
| Motion | LIS3DH accelerometer, MMC5603 magnetometer |
| Buttons | Any GPIO pins |
| Infrared | Up to three emitters (line, cone, and area) and one or more receivers |
| Radio | RFM69HCW at 915 MHz |
| Storage | SD card over SPI |
Measurements taken on real hardware are in
docs/hardware/recorded-metrics.md. The
infrared and radio links were tested with two separate tools,
infrared-analyzer and
rfm69-analyzer.
These are working notes for a hand-wired prototype, not a build guide.
A device reads two JSON files from the root of its CIRCUITPY drive. Both are
required; there are no built-in defaults.
aura-device.json declares the hardware: which parts are fitted and which
pins they use. Each part is built only if its section is present and enabled.
examples/aura-device.pico-2.json
is a complete example, tested on Pimoroni Pico 2 boards.
examples/aura-device.simulated.json
is for the test-only Simulated device, not for flashing to a board.
aura-boot.json picks the scene to boot, by its folder name under
packs/scenes/:
{
"default_scene": "red_light_green_light"
}If the device has an SD card, a scene saved to the card's aura-state.json
takes priority over default_scene.
To put the code on a board:
Install CircuitPython 10 on the board, and copy the Adafruit libraries for the
parts you declared into CIRCUITPY/lib/.
Download the matching mpy-cross compiler: scripts/setup_mpy_cross.sh
Copy both JSON files to the CIRCUITPY drive.
Deploy the scene runner and all modules:
uv run python scripts/deploy.py examples/hardware/scene_demo.pyThe board reboots and starts the scene.
aura-device.json in brief{
"pixels": [
{
"type": "matrix",
"cols": 13,
"scope_rows": {
"global.buff": [0, 1], "global.debuff": [1, 2], "global.main": [2, 5],
"personal": [5, 7], "directional": [7, 8], "ambient": [8, 9]
}
}
],
"buttons": ["GP26", "GP27"],
"ir": {
"rx": "GP13",
"line": "GP10",
"cone": "GP11",
"area_of_effect": "GP12"
},
"audio": {
"voices": 2,
"max_volume": 0.1,
"i2s_bit_clock": "GP7",
"i2s_word_select": "GP8",
"i2s_data": "GP6"
}
}pixels is a list of light outputs. type is "matrix" (IS31FL3741, at most
one) or "neopixel". Each entry maps scopes, the named regions an effect can
be shown in, to rows of the matrix (scope_rows) or to pixel ranges of a strip
(scope_pixels).buttons is a list of board pin names.ir takes rx (one receiver pin, or a list of them) and at least a line
emitter; cone and area_of_effect emitters are optional. Each scene chooses
the infrared message format it uses: Aura's own by default, or the tag scene's
format, which works with off-the-shelf infrared tag gear.audio takes voices (how many sounds can play at once), max_volume
(0.0 to 1.0), and the three I2S pins.The example file also shows the accelerometer, magnetometer, haptics, radio,
sdcard, i2c, spi, high_current_rail, and neopixel_current_budget sections.
neopixel_current_budget is optional: when the NeoPixel strips could draw more than
current_limit_ma at full white, the device dims them at boot to fit, never below
min_brightness.
(InputEvent | NetworkEvent)
│
▼
GameEngine holds rule list; stateless — caller owns GameState
│ engine.update(state)
▼
GameRule.handle_event(event, state)
│ rules read/write state.data, queue new events,
│ call state.effect_controls, call state.scene_controls
▼
EffectManager routes effects to EffectOutput by scope
│
▼
EffectOutput hardware: RGB strip, audio, hapticRules are stateless — all mutable game data lives in GameState.data. Rules
read and write state.data, fire effects via state.effect_controls, and
trigger scene transitions via state.scene_controls.
A pack is a directory containing a pack manifest and one .py file per
item. scan_pack_roots (engine/pack_catalog.py) discovers every pack and
scene under the pack roots in one scan; PackRegistry imports items lazily on
first use.
effects.json; each .py file exports BUILD: a
(config) → Effect callable. Effect names use "pack.effect" format (e.g. "elements.fire").rules.json; each .py file exports RULE: a GameRule instance.The manifest is a JSON object stating the pack's version and, optionally, declaring the pack's Settings:
{
"version": "1.0",
"settings": {
"hold_seconds": {"type": "float", "default": 5.0, "description": "Seconds to hold."}
}
}A Setting's type is int, float (an int value is accepted), bool,
str or list. A list is checked at the top level only; the pack's code
checks what is inside it. A Setting with no default is required: every scene declaring the pack
must set it. A scene sets a pack's Settings inline, in that pack's reference
in scene.json (see Scenes); the scan checks each override and
resolves every Setting (the default, then the scene's override, then any
setting_overrides passed to the scan), raising SettingOverrideError naming
the scene, pack and Setting. Rules read them through
state.settings.get(<pack>, <name>) or its typed forms (get_int,
get_float, get_bool, get_str), which have no default argument and no
write path. Settings never enter GameState's get/set store.
A folder without a manifest is not a pack and is skipped. A manifest that is
not a JSON object, or declares a Setting with an unknown type or a default of
the wrong type, fails the scan with MalformedManifestError.
A pack's Settings are part of its versioned interface: adding a Setting with a default is a minor bump; adding a required Setting, or removing, renaming or retyping one, is a major bump. The scan does not enforce this.
Version format is MAJOR.MINOR. A Scene declares minimum required versions;
the catalog scan checks them at boot (same-major, installed-minor ≥ required-minor).
A Scene is a declarative bundle, described by its scene.json:
{
"version": "1.0",
"effect_packs": [{"name": "elements", "version": "1.0"}],
"rule_packs": [
{"name": "return_to_lobby", "version": "1.0", "settings": {"hold_seconds": 2.0}}
],
"settings": {
"window_seconds": {"type": "float", "default": 3.0, "description": "Seconds per window."}
}
}version — the scene's own MAJOR.MINOR versioneffect_packs — ordered {"name": ..., "version": ...} objects (an optional settings object holds that pack's Setting overrides, checked at scan; effects do not receive them yet); version is the minimum compatible version, same major, checked at scanrule_packs — the same object form; all rules loaded and appended in declared ordersettings — the scene's own Setting declarations, in the manifest's shape; rules
read them in the scene namespace, and a Scene Config builds from them with the typed reads
(state.settings.get_float("scene", "window_seconds")). A bad declaration fails the scan
with MalformedSceneErrorir_codec — the IR codec the scene runs (default "aura")Any other key fails the scan with MalformedSceneError. A scene carries no
starting data: rules seed their own runtime state through state.get/state.set.
SceneManager owns a scene stack. load(name) replaces the stack; overlay(name) pushes
on top (suspending the current scene); pop() restores the previous scene. Transitions are
deferred to end-of-tick — rules call state.scene_controls.load/overlay/pop() during a tick
and the transition is applied after engine.update(state) returns. Every scene the manager
unloads or suspends has its effects stopped on Scope.ALL automatically.
This repo splits licensing by area:
effects/, engine/, magic/, packs/, hardware/, app/, scripts/, …) —
MIT.electronics/) — CERN-OHL-P-2.0, governed by
electronics/LICENSE.docs/) — CC-BY-4.0.Each area's LICENSE file is authoritative for its scope; pyproject.toml declares the
software license for tooling. No per-file license headers are used.
| Qty | Value | Footprint | References |
|---|---|---|---|
| 6 | 100 | Resistor_SMD:R_0805_2012Metric | R7, R8, R12, R13, R14, R15 |
| 5 | 10K | Resistor_SMD:R_0805_2012Metric | R1, R3, R5, R10, R11 |
| 3 | 22 | Resistor_SMD:R_0805_2012Metric | R2, R4, R6 |
| 3 | AO3406 | Package_TO_SOT_SMD:SOT-23 | Q1, Q2, Q3 |
| 2 | 100K | Resistor_SMD:R_0805_2012Metric | R16, R17 |
| 1 | 33K | Resistor_SMD:R_0805_2012Metric | R9 |
| 1 | 74HCT244 | Package_SO:TSSOP-20_4.4x6.5mm_P0.65mm | U1 |
| 1 | AO3401A | Package_TO_SOT_SMD:SOT-23 | Q4 |
| 1 | BUTTON A | Connector_PinHeader_2.54mm:PinHeader_1x02_P2.54mm_Vertical | J14 |
| 1 | BUTTON B | Connector_PinHeader_2.54mm:PinHeader_1x02_P2.54mm_Vertical | J15 |
| 1 | C20917 | Package_TO_SOT_SMD:SOT-23 | Q5 |
| 1 | I2C_1 | Connector_PinHeader_2.54mm:PinHeader_1x04_P2.54mm_Vertical | J3 |
| 1 | I2C_2 | Connector_PinHeader_2.54mm:PinHeader_1x04_P2.54mm_Vertical | J4 |
| 1 | I2C_3 | Connector_PinHeader_2.54mm:PinHeader_1x04_P2.54mm_Vertical | J5 |
| 1 | I2S AMP | Connector_PinHeader_2.54mm:PinHeader_1x05_P2.54mm_Vertical | J7 |
| 1 | IR RX 1 | Connector_PinHeader_2.54mm:PinHeader_1x03_P2.54mm_Vertical | J11 |
| 1 | IR RX 2 | Connector_PinHeader_2.54mm:PinHeader_1x03_P2.54mm_Vertical | J12 |
| 1 | IR RX 3 | Connector_PinHeader_2.54mm:PinHeader_1x03_P2.54mm_Vertical | J13 |
| 1 | IR TX AOE | Connector_PinHeader_2.54mm:PinHeader_1x02_P2.54mm_Vertical | J10 |
| 1 | IR TX CONE | Connector_PinHeader_2.54mm:PinHeader_1x02_P2.54mm_Vertical | J9 |
| 1 | IR TX LINE | Connector_PinHeader_2.54mm:PinHeader_1x02_P2.54mm_Vertical | J8 |
| 1 | LED STRIP 1 | Connector_PinHeader_2.54mm:PinHeader_1x03_P2.54mm_Vertical | J16 |
| 1 | LED STRIP 2 | Connector_PinHeader_2.54mm:PinHeader_1x03_P2.54mm_Vertical | J17 |
| 1 | LED STRIP 3 | Connector_PinHeader_2.54mm:PinHeader_1x03_P2.54mm_Vertical | J18 |
| 1 | LED STRIP 4 | Connector_PinHeader_2.54mm:PinHeader_1x03_P2.54mm_Vertical | J19 |
| 1 | LED STRIP 5 | Connector_PinHeader_2.54mm:PinHeader_1x03_P2.54mm_Vertical | J20 |
| 1 | LED STRIP 6 | Connector_PinHeader_2.54mm:PinHeader_1x03_P2.54mm_Vertical | J21 |
| 1 | POWER | Connector_PinHeader_2.54mm:PinHeader_1x02_P2.54mm_Vertical | J22 |
| 1 | PWR | LED_SMD:LED_0805_2012Metric | D2 |
| 1 | RFM69HCW | Connector_PinHeader_2.54mm:PinHeader_1x07_P2.54mm_Vertical | J1 |
| 1 | RaspberryPi_Pico | Module:RaspberryPi_Pico_Common_THT | A1 |
| 1 | SD CARD | Connector_PinHeader_2.54mm:PinHeader_1x06_P2.54mm_Vertical | J2 |
| 1 | STEMMA | Connector_JST:JST_SH_BM04B-SRSS-TB_1x04-1MP_P1.00mm_Vertical | J6 |

aaronsilinskas/aura-prototype
A monorepo to prototype Aura Game Engine core features.

aaronsilinskas/aura-prototype
A monorepo to prototype Aura Game Engine core features.

aaronsilinskas/aura-prototype
A monorepo to prototype Aura Game Engine core features.

aaronsilinskas/aura-prototype
A monorepo to prototype Aura Game Engine core features.




ikatkov/battery-capacity-meter
Vref pin on Arduino Pro Mini breakout board is not on any pin, solder a wire to capacitor between pins 12 and 13. To the pin13 end.

Capibator/hardware
deals with research material on hardware, schematic, pcb design and bom etc.



It measures 75 × 63 mm and has 4 copper layers, 1.61 mm thick.
47 parts, built around the 74HCT244D, AO3406, AO3401A and AO3400A. The full list of its parts is on this page, with links to each part's symbol and footprint where PCBKit's library has them.
From the aaronsilinskas/aura-prototype repository on GitHub, at the commit this page was made from: the board is electronics/scratch/Pico RP2350 USB Power/Pico RP2350 USB Power.kicad_pcb and its schematic electronics/scratch/IR Tester/IR Tester.kicad_sch. PCBKit opens the board in the browser, with nothing to install.
Its repository publishes it under the MIT licence: you may reuse it on that licence's terms (keep the author's notice, and for a copyleft licence share your changes alike).