Hardware for apps
SDK 1.xWritten for core 1.53+ and gui 1.23+.
An app never talks to a chip. It reaches the board through crosspad-core and crosspad-gui interfaces, and the same calls work on both board revisions and, where the part exists, in the PC simulator. This page lists what the board offers, the call that reaches each part, and where the simulator or a revision behaves differently. For the pins and drivers underneath, see the BSP reference.
At a glance
Section titled “At a glance”| Hardware | What your app gets | API |
|---|---|---|
| 16 pressure-sensitive pads | A press with velocity, pressure while held (aftertouch), left/right position while held, and a release | IPadLogicHandler, PadManager |
| 16 RGB LEDs, one under each pad | A colour per pad, pressed and playing colours, brightness, whole 16-pad frames | PadManager::setPadColor, PadLedController, PadAnimator |
| Display, 320×240 | An app area of about 320×210 under the transport bar | LVGL; CrossPad’s look in cp_design |
| Touch screen | Taps and drags on your widgets, swipes | LVGL pointer input; swipes in touch_gesture.h |
| Encoder with a push switch | Focus moves and clicks through your widgets | getNavigationEncoder() and an LVGL group |
| Power button | A short click is “back” | setOnBack, ui_back_push |
| Two audio codecs | Kit samples on the pads, recording and preview, your own sound node | postSamplePlayback, ISampleTools, IAudioNode |
| Speakers and output jacks | A level and a mute per physical output | IOutputLevels (getPlatformServices().outputLevels) |
| Motion sensor | Taps and other gestures as events; tilt angles, acceleration, orientation and shake through IImu | IImu, MotionGesture events (EventData) |
| Haptic motor | Short vibration presets | IVibration, VibrationPreset |
| SD card | Files | stdio under /sdcard |
| Built-in assets storage | Your icon and the PNG images you ship with the app | assetPathPrefix() |
| USB | MIDI out to a computer, MIDI notes in | getMidiOutput(), NoteOn/NoteOff events (EventTypes) |
| Bluetooth | MIDI over BLE | getMidiOutput() (routed by the user’s settings) |
| STM32 co-processor | The host-protocol SysEx channel | stm32MessageHandler in PlatformServices |
| WiFi | Nothing: there is no app API for WiFi | — |
What the simulator does differently: Simulator vs CrossPad.
Check the service, not the capability flags. hasCapability() describes the platform in general, not the parts that answered on this board. Ask the service itself: getImu().isAvailable(), getVibration().isAvailable(), and a null check on pointers such as getKitManager() and the members of getPlatformServices().
The screen
Section titled “The screen”The display is 320×240. On the firmware, the top 30 px are the transport bar (MODE, PLAY, STOP, BPM and the rest), the same in every app, and your app gets the area under it: about 320×210. The simulator’s main screen does not build the transport bar; a 22 px status bar sits there instead, so its app area is 320×218. Size your root with LV_PCT(100) rather than in pixels, as every example does, and it fits both.
CrossPad’s own fonts and colours are in cp_design. Its pixel fonts cover plain ASCII only: text that can hold other characters, such as a kit or file name from the card, stays on LVGL’s Montserrat fonts, which the firmware ships with accented Latin letters.
When your screen draws the pads, draw them the way the player sees them: pad 1 bottom-left, so the top row on screen is pads 13 to 16. For a grid of cells in rows 0 to 3 from the top, the pad index is (3 - row) * 4 + column. Pads and LEDs draws its grid this way.
The pads are numbered 1 to 16, from the bottom-left, row by row. In the API a pad is an index from 0 to 15: index = pad − 1.
13 14 15 16 9 10 11 12 5 6 7 8 1 2 3 4 (front edge)An app that wants the pads registers a pad logic, an IPadLogicHandler, and opens with REGISTER_APP_PL (How an app works shows the registration). Every callback is pure virtual except onPadPan, so your handler implements all the others, even if some stay empty. While it has the pads, its callbacks get:
| Callback | Values |
|---|---|
onPadPress(pm, pad, velocity) | pad 0–15, velocity 0–127 |
onPadPressure(pm, pad, pressure) | 0–127, while the pad is held (aftertouch) |
onPadPan(pm, pad, pan) | left/right position of the finger, while held, on change only: −64 (left) to 63 (right) on the board, 0 in the middle. Optional to implement. |
onPadRelease(pm, pad) | — |
onActivate(pm), onDeactivate(pm) | your logic got the pads, or lost them |
One pad logic has the pads at a time. When no app has claimed them, the Sampler (the base pad logic) plays them. While your logic has them, PadManager skips its defaults for a press and a release (the note-on and note-off light, the MIDI note, the NoteOn, NoteOff, PadPressed and PadReleased events): your logic lights, sounds and reports what it wants. A held pad’s pressure still reaches the LED controller, which uses it when the LED feedback mode is aftertouch. To keep the kit sounding under your app, call the base logic from yours (Play a sample). pm.pressSource(pad) tells a finger from a press that a running loop plays (EventSource::SequencerCallback). The callbacks run on the pad tasks, not the LVGL task; see Threads.
Where a pad hit goes. With your pad logic active, only what your logic calls happens (dashed blue arrows); otherwise the Sampler plays the kit, lights the pad and sends the MIDI note. Pressure reaches the LED controller either way.
LEDs. PadManager::setPadColor(pad, r, g, b) gives a pad your colour until you call clearPadColor(pad), which hands it back to the default colours. Clear what you set when your logic loses the pads, in onDeactivate. PadLedController holds the pressed and playing colours, the brightness and the animation mode. PadAnimator shows a whole 16-pad frame (showFrame), a progress bar and success or error flashes.
The board has two ES8388 audio codecs. Codec 0 drives the built-in speakers and the Out 1/2 jack, one stream at two levels; codec 1 drives the Out 3/4 jack. The inputs are the built-in microphones and two stereo line inputs, Line 1/2 and Line 3/4.
The ways an app makes or takes sound:
- Play the kit.
getEventBus().postSamplePlayback(note, wavIdx, isOn, EventSource::App, velocity, …)plays a pad of the current kit through the sample engine, as a pad hit would. The kit itself comes fromgetKitManager(), which is null where there is no kit system. Recipe: Play a sample. - Record and preview.
ISampleTools(getPlatformServices().sampleTools) selects an input, records, previews a file or a region of it, and lists the card. Null where the platform has none, including the simulator. - Your own sound. An
IAudioNodeis a generator or effect the audio task calls for every buffer. - Output levels.
IOutputLevels(getPlatformServices().outputLevels, null where the board has none, including the simulator) lists the physical outputs and gives each a level from 0 to 100 and a mute.name(o)is what the board calls outputo: on a v2 boardSpeakers,Out 1/2andOut 3/4; on v1, where Out 1/2 has no level of its own,SpeakersandOut 3/4. These are the player’s own levels, the ones Settings → Audio shows, and a change is kept across power cycles: change them only when that is what the player asked for.
Motion and haptics
Section titled “Motion and haptics”The motion sensor is an LSM6DS3TR-C.
Read it from the LVGL task, in a timer: crosspad::getImu().getData(d) fills d with the latest acceleration, rotation and the filtered pitch and roll angles in degrees, and returns false when there is nothing to read. getOrientation() gives the face the board rests on, and isShaking() and isFreeFall() report those two states. Gestures the sensor recognises itself, which do not go through getImu() (single tap, double tap, free fall, wake-up, a change of face, still) arrive as MotionGesture events (EventData): subscribe to them on the event bus.
The haptic motor is a DRV2605. crosspad::getVibration().vibrate(VibrationPreset::ImuTap) queues a preset and returns at once, so you can call it from an event callback.
Both are optional parts. Check isAvailable() before you rely on either: in the simulator both return false, and getData() then returns false too. An app that checks shows a “no sensor” state instead, as the Motion example does. Recipe: Motion and vibration.
Storage
Section titled “Storage”SD card. On the board the card is mounted at /sdcard. Read and write it with stdio (fopen, fread, fwrite); the built-in apps keep their files under /sdcard/crosspad/. In the simulator the card is a folder you choose, so a path that starts with /sdcard works on the board only. For fast reads, see Memory.
Assets. The PNG files in your app’s assets/ folder are copied into the board’s built-in assets storage (LittleFS) at build time, next to every other app’s files. crosspad_gui::getGuiPlatform().assetPathPrefix() gives the folder’s path on the platform you run on; put it in front of a file name to open one of your files. Name your files after your app, because all apps share the one folder (see Limits). An OTA update does not rewrite the assets: after changing an asset, send it to the board as in Start from the template, step 5.
Connections
Section titled “Connections”- MIDI out.
getMidiOutput()sends notes, control change, aftertouch, program change and pitch bend. Channels count from 0 (0 is MIDI channel 1). Where the messages go (USB, Bluetooth, the second USB port) follows the player’s MIDI settings, so your app does not choose a port.sendSysExreaches USB and Bluetooth hosts only. Recipe: MIDI out. - MIDI in. Where a note arrives decides where it goes. Over USB, a note arrives as a
NoteOnorNoteOffevent with sourceEventSource::MidiInput. Over Bluetooth, a note that maps to a pad is played as that pad: it goes to the pad logic that has the pads (withpressSource()returningMidiInput), not to the event bus; only notes outside the pads’ range arrive as events. Apps receive MIDI notes; other incoming messages are handled by the firmware. A computer’s MIDI clock reaches apps asTempoChangedevents with sourceMidiInput. - Bluetooth MIDI. Sending goes through
getMidiOutput()like any other MIDI. The BLE link itself isgetBleMidi(); apps send throughgetMidiOutput()and do not need it. - STM32 co-processor. It scans the pads, runs the power button and the charger, and carries the second USB port. Apps do not talk to it, except for the host-protocol SysEx that DAW Control uses:
getPlatformServices().stm32MessageHandler(null where the platform has no such link). - USB modes. Whether the main USB port carries audio or serial is the player’s choice in Settings → USB, not an app’s.
- WiFi. No app API: WiFi belongs to the firmware itself (WiFi).
Board revisions
Section titled “Board revisions”Two board revisions are in use, v2 (PCB r20) and v1 (PCB r16 / r19). You build for one or the other, and an app’s code is the same for both. Two differences can reach an app:
- Motion. The v1 board has no interrupt line from the motion sensor, so the driver reads the sensor on a 20 ms timer instead of when it has new data. The API is the same.
- Line 3/4. On v2, the Line 3/4 input shares its wires with one output pair of each codec. Listening to Line 3/4 switches that output pair off while it is selected. On v1 the inputs are separate.
Everything else that differs (the EEPROM, the back-cover switch, the jack sensing, the pins) stays inside the board support package. Apps never #ifdef a revision: code that needs to know about a revision belongs in the BSP, behind its BSP_HAS_* flags, not in an app. The pins of each revision are in Pinout v1 and Pinout v2.
What the simulator does differently, part by part: Simulator vs CrossPad.