Implementing a New Bus Pirate Command

A step-by-step guide to creating a global or mode command for Bus Pirate 5/6/7 firmware.
Reference implementation: src/commands/global/dummy.c


Overview

A command is a user-facing function invoked at the Bus Pirate prompt (e.g. flash, dummy, W). Commands come in two flavors:

TypeScopeExample
GlobalAvailable in every mode (or restricted to non-HiZ)W (PSU enable), ls, dummy
ModeAvailable only when a specific mode is activeflash (SPI), bridge (UART)

Both types share the same handler signature and the same bp_cmd definition system. The difference is where they are registered and a few structural fields.

What a Command Provides

Every command consists of:

  • Definition (bp_command_def_t) — single source of truth for help, parsing, hints, completion
  • Handler function — entry point called by the dispatcher
  • Header file — exports the def and handler (global) or command table (mode)
  • Registration — entry in commands[] (global) or a mode’s command table (mode)

What bp_cmd Drives From One Definition

The bp_command_def_t struct drives five concerns from a single definition:

  1. Help displaydummy -h shows usage, flags, and actions
  2. CLI parsing — flags, positionals, and action verbs
  3. Value validation — range and choice constraints
  4. Interactive prompting — fallback wizard when args are missing
  5. Linenoise hints & tab-completion — ghost text and <Tab> suggestions

File Structure

Global Command

FilePurpose
src/commands/global/mycmd.cAll command logic — def, handler, constraints
src/commands/global/mycmd.hextern for def + handler declaration
src/commands.cRegistration in the commands[] dispatch table

Mode Command

FilePurpose
src/commands/<mode>/mycmd.cAll command logic — def, handler, constraints
src/commands/<mode>/mycmd.hextern for def + handler declaration
src/mode/hw<mode>.cCommand table array (e.g. hwuart_commands[])
src/mode/hw<mode>.hextern for command table + count
src/modes.cWires the mode’s command table via .mode_commands

Step 1: Includes

Start with the required headers. Annotate each include so future readers know what it provides:

#include <stdio.h>
#include <string.h>
#include "pico/stdlib.h"
#include "pirate.h"
#include "command_struct.h"
#include "fatfs/ff.h"              // File system (FatFS)
#include "pirate/storage.h"        // File system helpers
#include "lib/bp_args/bp_cmd.h"    // Unified command parsing, validation, prompting, help, hints
#include "ui/ui_help.h"            // Help display utilities
#include "system_config.h"         // Current Bus Pirate system configuration
#include "pirate/amux.h"           // Analog voltage measurement functions
#include "pirate/button.h"         // Button press functions

The only required includes for a minimal command are stdio.h, pirate.h, command_struct.h, system_config.h, lib/bp_args/bp_cmd.h, and your own header. Add others as needed by your logic.


Step 2: Usage Examples

Displayed when the user enters mycmd -h. The first entry is the synopsis; remaining entries are labeled examples:

static const char* const usage[] = {
    "dummy [init|test]\r\n\t[-b(utton)] [-i(nteger) <value>] [-f <file>]",
    "Initialize:%s dummy init",
    "Test:%s dummy test",
    "Test, require button press:%s dummy test -b",
    "Integer, value required:%s dummy -i 123",
    "Interactive integer prompt:%s dummy init -b",
    "Create/write/read file:%s dummy -f dummy.txt",
    "Kitchen sink:%s dummy test -b -i 123 -f dummy.txt",
};
EntryFormatNotes
FirstSynopsis stringUse \r\n\t for line wrapping
Rest"Label:%s command example"%s is replaced with the Bus Pirate prompt name

Step 3: Actions / Subcommands (Optional)

If your command has verb-style subcommands (e.g. flash probe, flash dump), define an actions array. Actions are matched by bp_cmd_get_action() — this replaces manual strcmp() on positional arguments.

enum dummy_actions {
    DUMMY_INIT = 1, // enum values should start at 1 (0 = no action)
    DUMMY_TEST = 2,
};

static const bp_command_action_t dummy_action_defs[] = {
    { DUMMY_INIT, "init", T_HELP_DUMMY_INIT },  // "dummy init" → action=DUMMY_INIT
    { DUMMY_TEST, "test", T_HELP_DUMMY_TEST },   // "dummy test" → action=DUMMY_TEST
};

Each bp_command_action_t entry:

FieldPurpose
.actionEnum value returned by bp_cmd_get_action() (start at 1)
.verbString the user types after the command name
.descriptionT_ translation key for help display (0 = placeholder)

If your command does not have subcommands, skip this and set .actions = NULL, .action_count = 0 in the def.


Step 4: Value Constraints

Each validated parameter gets a bp_val_constraint_t that defines its type, valid range (or choices), default value, and prompt text. Constraints power three things simultaneously:

  • bp_cmd_flag() — validates the CLI value against the range
  • bp_cmd_prompt() — drives an interactive prompt with range display
  • Help display — shows valid range in the help output

Integer range (BP_VAL_UINT32):

static const bp_val_constraint_t integer_range = {
    .type = BP_VAL_UINT32,
    .u = { .min = 0, .max = 65535, .def = 0 },
    .prompt = 0, // T_ key for interactive prompt title (0 = placeholder)
    .hint = 0,   // T_ key for hint subtitle (0 = placeholder)
};
FieldPurpose
.u.min, .u.maxValid range (inclusive)
.u.defDefault value when flag is absent on CLI
.promptT_ translation key for interactive menu title (0 = placeholder)
.hintT_ translation key for subtitle below prompt (0 = placeholder)

Named choices (BP_VAL_CHOICE):

For parameters that select from a list of named options (e.g. parity, output type), use BP_VAL_CHOICE with a bp_val_choice_t array:

static const bp_val_choice_t output_choices[] = {
    { "push-pull",  "pp", 0, 0 }, // value=0
    { "open-drain", "od", 0, 1 }, // value=1
};
static const bp_val_constraint_t output_choice = {
    .type = BP_VAL_CHOICE,
    .choice = { .choices = output_choices, .count = 2, .def = 0 },
    .prompt = 0,
};

Each bp_val_choice_t entry:

FieldPurpose
.nameCLI string the user types (e.g. "push-pull")
.aliasShort alias (e.g. "pp")
.labelT_ key for interactive menu label (0 = placeholder)
.valueInteger stored when selected

All Constraint Types

TypeUnionFields
BP_VAL_UINT32.u.min, .max, .def
BP_VAL_INT32.i.min, .max, .def
BP_VAL_FLOAT.f.min, .max, .def
BP_VAL_CHOICE.choice.choices, .count, .def

If a flag takes a string value (e.g. a filename) with no numeric validation, omit the constraint — use bp_cmd_get_string() directly.


Step 5: Flag / Option Table

Maps CLI flags to constraints. Each entry defines a flag name, short name, argument type, hint text, description, and optional constraint. The array must end with a { 0 } sentinel:

static const bp_command_opt_t dummy_opts[] = {
    { "button",  'b', BP_ARG_NONE,     NULL,    T_HELP_DUMMY_B_FLAG },
    { "integer", 'i', BP_ARG_REQUIRED, "value", T_HELP_DUMMY_I_FLAG, &integer_range },
    { "file",    'f', BP_ARG_REQUIRED, "file",  T_HELP_DUMMY_FILE_FLAG },
    { 0 }, // ← sentinel — always required
};
FieldPurpose
long_name--button on the command line
short_name-b on the command line
arg_typeBP_ARG_NONE (boolean switch) or BP_ARG_REQUIRED (takes a value)
arg_hintShown in help text: -i <value>. NULL for boolean flags
descriptionT_ key for help text (0 = placeholder)
constraintPointer to a bp_val_constraint_t, or omit/NULL for no auto-validation

Supported flag syntax on the command line:

FormatExample
-f value-i 123
-f=value-i=123
--long value--integer 123
--long=value--integer=123

Step 6: Command Definition

The master struct that ties everything together. Must be non-static so it can be exported via the header and wired into the registration array:

const bp_command_def_t dummy_def = {
    .name = "dummy",
    .description = 0x00,        // T_ key for `h` listing (0x00 = no description)
    .actions = dummy_action_defs,
    .action_count = count_of(dummy_action_defs),
    .opts = dummy_opts,
    .usage = usage,
    .usage_count = count_of(usage),
};
FieldRequired?Purpose
.nameYesCommand string users type at the prompt
.descriptionNoT_ key for h listing (0 = hidden / placeholder)
.actionsNoPointer to action verb array (NULL if no subcommands)
.action_countNoNumber of action entries (0 if no subcommands)
.optsNoPointer to flag table (NULL if no flags)
.usageYesPointer to usage string array
.usage_countYesNumber of usage entries
.positionalsNoPointer to positional args table (NULL for most commands)
.positional_countNoNumber of positional entries

Step 7: The Handler Function

This is the entry point called when the user types your command. The dispatcher passes a command_result struct — res->help_flag is set automatically if the user entered -h.

7a: Help Check

Always handle help first. bp_cmd_help_check() displays auto-generated help (usage examples, flags table, actions list) and returns true if -h was given:

void dummy_handler(struct command_result* res) {

    if (bp_cmd_help_check(&dummy_def, res->help_flag)) {
        return;
    }

7b: Safety / Precondition Checks (Optional)

If your command requires a valid voltage reference, call ui_help_check_vout_vref(). To restrict to a specific mode, check system_config.mode — but mode commands (registered per-mode) are the preferred way to scope commands:

    printf("Current mode: %d\r\n", system_config.mode);

    if (!ui_help_check_vout_vref()) {
        printf("Warning: Vout pin is not connected to a valid voltage source\r\n");
    } else {
        printf("Vout pin is connected to a valid voltage source\r\n");
    }

7c: Action Resolution

bp_cmd_get_action() matches the first non-flag token against the actions array. Returns true and writes the enum value, or false if no action token is present:

    uint32_t action = 0;
    if (bp_cmd_get_action(&dummy_def, &action)) {
        printf("Action: %s (enum=%d)\r\n",
               (action == DUMMY_INIT ? "init" : "test"),
               action);
    } else {
        printf("No action given (try: dummy init, dummy test)\r\n");
    }

Use a switch on the action enum in real commands to dispatch to separate logic paths.

7d: Boolean Flag — bp_cmd_find_flag()

Returns true if the flag is present, false if not. No value is consumed — this is a simple on/off switch:

    bool b_flag = bp_cmd_find_flag(&dummy_def, 'b');
    printf("Flag -b is %s\r\n", (b_flag ? "set" : "not set"));
    if (b_flag) {
        printf("Press Bus Pirate button to continue\r\n");
        while (!button_get(0)) {
            tight_loop_contents();
        }
        printf("Button pressed\r\n");
    }

7e: Constraint-Aware Integer Flag — bp_cmd_flag()

bp_cmd_flag() uses the constraint on the opt to parse, validate, and provide a default. This is the preferred way to get a validated integer from a flag:

    uint32_t value;
    bp_cmd_status_t i_status = bp_cmd_flag(&dummy_def, 'i', &value);

    if (i_status == BP_CMD_OK) {
        printf("Flag -i is set with value %d\r\n", value);
    } else if (i_status == BP_CMD_INVALID) {
        // Constraint violation — the API already printed the range error.
        printf("Flag -i has an invalid value. Try -i 0\r\n");
        system_config.error = true;
        return;
    } else { // BP_CMD_MISSING — flag not entered, default written to value
        printf("Flag -i is not set (default: %d)\r\n", value);
    }

bp_cmd_flag() return values:

StatusMeaning
BP_CMD_OKFlag found, value parsed and valid — written to output
BP_CMD_MISSINGFlag not on command line — constraint default written to output
BP_CMD_INVALIDFlag present but failed validation — error already printed

7f: Interactive Prompt Fallback — The Dual-Path Pattern

If a flag is missing, you can fall back to an interactive prompt driven by the same constraint. This is the “dual-path” pattern — the user can provide the value on the CLI (dummy init -i 123) or be prompted interactively:

    if (action == DUMMY_INIT && i_status == BP_CMD_MISSING) {
        printf("No -i flag given — entering interactive prompt:\r\n");
        // bp_cmd_prompt() displays a menu driven by the constraint,
        // validates input, and loops on error. Returns BP_CMD_OK or BP_CMD_EXIT.
        bp_cmd_status_t prompt_st = bp_cmd_prompt(&integer_range, &value);
        if (prompt_st != BP_CMD_OK) {
            printf("Prompt cancelled\r\n");
            return;
        }
        printf("User entered: %d\r\n", value);
    }

bp_cmd_prompt() return values:

StatusMeaning
BP_CMD_OKUser entered a valid value
BP_CMD_EXITUser cancelled (pressed x)

For a full dual-path example with saved settings, see dummy1.c (mode setup) or w_psu.c (command).

7g: String Flag — bp_cmd_get_string()

Copies the flag’s value as a string into a buffer. Returns true if present, false if not. Use for filenames, search strings, etc.:

    char file[13]; // 8.3 filename + null = 13 characters max
    bool f_flag = bp_cmd_get_string(&dummy_def, 'f', file, sizeof(file));

    if (!f_flag) {
        printf("Flag -f is not set\r\n");
    } else {
        printf("Flag -f is set with file name %s\r\n", file);
        // ... use the filename ...
    }

7h: Error Reporting

To signal an error back to the command dispatcher (for chaining with ; || &&), set system_config.error = true and return:

    system_config.error = true;
    return;

Step 8: The Header File

Global Command Header

Export the def and handler function. The def must be extern const so commands.c can reference it:

/**
 * @file dummy.h
 * @brief Dummy/test command interface.
 * @details Provides placeholder command for testing.
 */

extern const struct bp_command_def dummy_def;

/**
 * @brief Handler for dummy test command.
 * @param res  Command result structure
 */
void dummy_handler(struct command_result* res);

Mode Command Header

Mode command handlers and defs are declared in separate headers under src/commands/<mode>/. The main mode header (src/mode/hwxxx.h) exports the command table and its count:

// In src/mode/hwuart.h (bottom):
extern const struct _mode_command_struct hwuart_commands[];
extern const uint32_t hwuart_commands_count;
// In src/commands/uart/bridge.h (individual command):
extern const struct bp_command_def uart_bridge_def;
void uart_bridge_handler(struct command_result* res);

Step 9: Registration

Global Commands — commands.c

Add your command to the commands[] array. Each entry is a _global_command_struct:

{ .command="dummy", .allow_hiz=true, .func=&dummy_handler,
  .def=&dummy_def, .description_text=0x00, .category=CMD_CAT_HIDDEN },
FieldPurpose
.commandString users type at the prompt
.funcPointer to your handler function
.defPointer to your bp_command_def_t (enables help, hints, completion)
.allow_hiztrue = works in HiZ mode, false = requires active mode
.description_textT_ key for the h help listing (0x00 = hidden from listing)
.categoryHelp menu group (see categories below)

Don’t forget the include at the top of commands.c:

#include "commands/global/dummy.h"

Command Categories

CategoryShown Under
CMD_CAT_IOPin I/O, power, measurement
CMD_CAT_CONFIGURETerminal, display, mode config
CMD_CAT_SYSTEMInfo, reboot, selftest
CMD_CAT_FILESStorage and file operations
CMD_CAT_SCRIPTScripting and macros
CMD_CAT_TOOLSUtilities and converters
CMD_CAT_MODEMode selection
CMD_CAT_HIDDENNot shown in help listing

Mode Commands — Per-Mode Command Table

Mode commands are registered in the mode’s source file as an array of _mode_command_struct. The command name comes from the def (not a separate .command field):

// In src/mode/hwuart.c:
const struct _mode_command_struct hwuart_commands[] = {
    {   .func=&nmea_decode_handler,
        .def=&nmea_decode_def,
        .supress_fala_capture=true
    },
    {   .func=&uart_bridge_handler,
        .def=&uart_bridge_def,
        .supress_fala_capture=true
    },
    { 0 }, // sentinel
};
const uint32_t hwuart_commands_count = count_of(hwuart_commands);
FieldPurpose
.funcPointer to the handler function
.defPointer to the bp_command_def_t (command name comes from .def->name)
.supress_fala_capturetrue = disable follow-along logic analyzer during this command

The command table is then wired into the modes[] array in modes.c:

[HWUART] = {
    // ...protocol function pointers...
    .mode_commands = hwuart_commands,
    .mode_commands_count = &hwuart_commands_count,
    // ...
},

Global vs. Mode Commands — Comparison

AspectGlobal CommandMode Command
ScopeAvailable in all modes (or non-HiZ)Only when mode is active
Registration struct_global_command_struct_mode_command_struct
Registered incommands[] in commands.ce.g. hwuart_commands[] in mode file
Command name.command string fieldComes from .def->name
HiZ control.allow_hiz fieldN/A — only active when mode is selected
Help category.category (CMD_CAT_*)N/A — listed under mode help
FALA suppressionN/A.supress_fala_capture field
Header exportsextern def + handlerextern command table + count

Step 10: CMakeLists.txt

Add your .c file to the build. Open src/CMakeLists.txt and add your source file to the appropriate section:

# Global command:
commands/global/mycmd.c

# Mode command (under the mode's section):
commands/uart/mycmd.c

Parsing API Quick Reference

Simple Queries (No Validation)

FunctionReturnsPurpose
bp_cmd_find_flag(def, 'e')boolIs flag present? (boolean switch)
bp_cmd_get_uint32(def, 'n', &val)boolParse flag value as uint32
bp_cmd_get_int32(def, 'n', &val)boolParse flag value as int32
bp_cmd_get_float(def, 'f', &val)boolParse flag value as float
bp_cmd_get_string(def, 'f', buf, len)boolCopy flag value as string
bp_cmd_get_positional_string(def, pos, buf, len)boolPositional arg as string
bp_cmd_get_positional_uint32(def, pos, &val)boolPositional arg as uint32
bp_cmd_get_remainder(def, &ptr, &len)boolRaw text after command name

Constraint-Aware (Validates + Provides Defaults)

FunctionReturnsPurpose
bp_cmd_flag(def, 'u', &out)bp_cmd_status_tParse + validate flag with constraint
bp_cmd_positional(def, pos, &out)bp_cmd_status_tParse + validate positional with constraint
bp_cmd_prompt(constraint, &out)bp_cmd_status_tInteractive prompt from constraint

Action Resolution

FunctionReturnsPurpose
bp_cmd_get_action(def, &action)boolMatch first non-flag token to actions array

Help

FunctionReturnsPurpose
bp_cmd_help_check(def, help_flag)boolShow help if -h, return true if displayed
bp_cmd_help_show(def)voidUnconditional help display

Status Codes

StatusMeaning
BP_CMD_OKValue obtained and valid
BP_CMD_MISSINGNot on command line (default written for flags)
BP_CMD_INVALIDPresent but failed validation (error already printed)
BP_CMD_EXITUser cancelled interactive prompt

Patterns & Recipes

Simple Command (Help + One Flag)

Minimal pattern — help check and a boolean flag:

void mycmd_handler(struct command_result* res) {
    if (bp_cmd_help_check(&mycmd_def, res->help_flag)) return;

    bool verbose = bp_cmd_find_flag(&mycmd_def, 'v');
    // ... do work ...
}

Command with Actions

Route to different logic based on a subcommand verb:

    uint32_t action = 0;
    bp_cmd_get_action(&mycmd_def, &action);
    switch (action) {
        case MYCMD_PROBE: /* ... */ break;
        case MYCMD_DUMP:  /* ... */ break;
        default: printf("No action given\r\n"); break;
    }

Dual-Path (CLI + Interactive Fallback)

Try the CLI flag first; if missing, prompt interactively:

    uint32_t voltage;
    bp_cmd_status_t st = bp_cmd_flag(&mycmd_def, 'v', &voltage);
    if (st == BP_CMD_INVALID) { system_config.error = true; return; }
    if (st == BP_CMD_MISSING) {
        if (bp_cmd_prompt(&voltage_constraint, &voltage) != BP_CMD_OK) return;
    }

Enable/Disable Command Pairs

Commands like W/w (PSU on/off) use separate defs sharing usage strings:

// W = enable, w = disable — separate handlers, separate defs
const bp_command_def_t psu_enable_def  = { .name="W", .opts=psu_opts, ... };
const bp_command_def_t psu_disable_def = { .name="w", ... };

Checklist

  • Create src/commands/global/mycmd.c (or src/commands/<mode>/mycmd.c)
  • Define usage string array
  • Define action enum + bp_command_action_t[] (if subcommands needed)
  • Define bp_val_constraint_t for each validated parameter
  • Define bp_command_opt_t[] flag table (sentinel-terminated with { 0 })
  • Define bp_command_def_t (non-static, exported)
  • Implement handler: bp_cmd_help_check() → preconditions → parsing → logic
  • Create header file: extern for def + handler declaration
  • Global: Add to commands[] in commands.c with include
  • Mode: Add to mode’s command table array + wire in modes.c
  • Add .c file to src/CMakeLists.txt
  • Build and verify