Porting firmware to the Bus Pirate with pirate-lib

Let’s talk a bit about porting firmware to the Bus Pirate, in case anyone else wants to try it.

copy pirate-lib

There is an evolving library of low level drivers for the Bus Pirate hardware I’m calling pirate-lib. It is not a stand alone submodule yet because it is still rapidly evolving.

  • Copy the pirate folder to the firmware source

copy pirate_config.h

Each project is customized with a pirate_config.h in the root directory of the source.

customize pirate_config.h

const char *hw_pin_label_ordered[] = {
    "3.3V",
    "IO0",
...
    "GND"
};



const char *func_pin_label_ordered[] = {
    "PicoProbe",
    "SCLK",
...
    "GND"
};


const char *direction_pin_label_ordered[]={ 
    "->",
    "->",
...
};

The three arrays contain text for the three columns on the display.

  • First column is 10 pin names on the left of the display. The first member might be customized if a power supply is active or if the target must provide the power. The rest are generally IO0…7+GND
  • Second column describes the pin use in this particular firmware. The first member of the second column is the firmware name and is printed at the top.
  • Third column I use for direction indication, but could be up to four letters of your choice :slight_smile:
static inline void pirate_options_init(void){
    #include "pirate/psu.h"
    #include "pirate/button.h"
    if(!button_get(0)){
        ui_lcd_update(hw_pin_label_ordered_3v3, func_pin_label_ordered, direction_pin_label_ordered);
        psu_enable(3.3, 0, true);
    }else{
        ui_lcd_update(hw_pin_label_ordered_vin, func_pin_label_ordered, direction_pin_label_ordered);
    }
    #include "pirate/pirate.h"
    #include "pirate/bio.h"
    #include "board_buspirate5_config.h"
    //configure buffer directions
    bio_set_buffer_dir(PROBE_UART_TX, true);
    bio_set_buffer_dir(PROBE_UART_RX, false);   
}

pirate_options_init() is called at the end of the pirate-lib initialization. This is a good place to put any customized startup code for the firmware port. Here we:

  • Check if button is pressed. Enable the power supply and draw the correct labels on the LCD
  • Use the pirate-lib bio (buffered io) functions to set the uart_tx buffer to output and the rx buffer to input

Keeping the Bus Pirate init code here instead of inside the ported firmware makes it a LOT easier to merge in updates from the original project later.

initialize Bus Pirate hardware

int main(void) {
    // Declare pins in binary information
    bi_decl_config();

    #ifdef BOARD_BUSPIRATE
    pirate_init();
    #endif
    board_init();
    usb_serial_init();
    cdc_uart_init();
    tusb_init();
    stdio_uart_init();

Include pirate/pirate.h in the main file of the firmware. Call pirate_init() near the beginning of the firmware. This will configure all the hardware to a safe state and call pirate_options_init() at the end.

configure and use buffered IO pins

// Buffer IO defines
#define BUFIO0 8
#define BUFIO1 9
#define BUFIO2 10
#define BUFIO3 11
#define BUFIO4 12
#define BUFIO5 13
#define BUFIO6 14
#define BUFIO7 15

The Bus Pirate IO pins have a bidirectional buffer. gpio0-7 control the direction of the buffer, gpio8…15 control the level of the buffer. You can find the gpios defined in pirate/pirate.h.

Probably the most commonly used part of pirate-lib are the buffered io pin functions in pirate/bio.h. Depending on how you use IO pin it will be easy or slightly annoying.

unidirectional io pins

void bio_set_buffer_dir(uint8_t bio, bool dir) {
    gpio_put(bio-8, dir);
}

For pins that don’t change direction (UART RX/TX, SPI, etc) you can simply use bio_set_buffer_dir(BUFIOn, false/true) to set the buffer to an input or output and then use the gpio as normal.

bidirectional pins

void bio_set_dir(uint8_t bio, bool dir) {
...
        if(dir) {
            // first set the buffer to output
            gpio_put(bio-8, BUFDIR_OUTPUT);
            // now set pin to output
            gpio_set_dir(bio, GPIO_OUT);
        } else {
            // first set the pin to input
            gpio_set_dir(bio, GPIO_IN);
            // now set buffer to input
            gpio_put(bio-8, BUFDIR_INPUT);
        }

Pins that change direction, such as I2C data pins, need to be carefully managed. bio_set_dir(BUFIOn, false/true) sets both the buffer and the pin directions in a single function.

To use this for open drain IO, such as pulling a jtag reset line low, set the gpio level to 0/ground. Then use bio_set_dir() to alternate between input (high) and output (low).

6 Likes