Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/EttusResearch/uhd/llms.txt

Use this file to discover all available pages before exploring further.

Generation-3 and later USRP devices expose general-purpose I/O (GPIO) pins directly on the motherboard, independent of any daughterboard. These pins are driven by the FPGA, which includes an ATR (Automatic Transmit/Receive) state machine that can change pin values automatically in response to radio state transitions—transmitting, receiving, idle, or full-duplex. This makes GPIO ideal for controlling external amplifiers, switches, LEDs, or any hardware that must track radio state with microsecond precision.

GPIO Banks

Each USRP exposes one or more named GPIO banks. The bank name is the first argument to every GPIO API call. Use get_gpio_banks() to enumerate available banks at runtime:
auto banks = usrp->get_gpio_banks(0); // mboard index 0
for (const auto& bank : banks) {
    std::cout << "Bank: " << bank << std::endl;
}
Common bank names by device:
DeviceBank NameWidthConnector
X3x0FP012 bitsFront panel DB15
E31xINT06 bitsInternal header
E320FP08 bitsMini-HDMI (Type C)
N3x0 / X410FP0device-dependentFront panel
On X4xx devices with two HDMI GPIO connectors, the banks may be named FP0A and FP0B. Check get_gpio_banks() to confirm the names for your specific device and firmware image.

GPIO Attributes

Every GPIO bank is configured through a set of named attributes (registers). Pass the attribute name as a string to set_gpio_attr() or get_gpio_attr().
AttributeDescription
CTRLPer-pin control mode: 1 = ATR (automatic), 0 = manual
DDRData Direction Register: 1 = output, 0 = input
OUTManual output value (only meaningful when CTRL bit = 0)

API

// Set attribute bits selected by mask
void set_gpio_attr(const std::string& bank,
                   const std::string& attr,
                   const uint32_t value,
                   const uint32_t mask = 0xffffffff,
                   const size_t mboard = 0);

// Read a full attribute register
uint32_t get_gpio_attr(const std::string& bank,
                       const std::string& attr,
                       const size_t mboard = 0);
The mask parameter is a bitmask that limits which bits are written. Bits that are 0 in the mask are left unchanged. This lets you update individual pins without disturbing others.

ATR Operation Explained

ATR works by storing four output values in the FPGA—one for each radio state—and automatically selecting among them based on whether TX and RX streams are active. You configure it per-pin:
  • Set the corresponding CTRL bit to 1 to hand control to ATR.
  • Set DDR to 1 to make the pin an output.
  • Write the desired logic level for each radio state into ATR_0X, ATR_RX, ATR_TX, and ATR_XX.
Pins where CTRL = 0 are driven by the OUT register and are not affected by radio state.

Configuration Example

This example configures a 12-bit front-panel GPIO bank on an X310. GPIO[6] drives an external PA enable line via ATR; GPIO[4] is a manually controlled indicator:
#include <uhd/usrp/multi_usrp.hpp>

// Pin masks
#define AMP_GPIO_MASK  (1 << 6)   // external PA enable, ATR-controlled
#define MAN_GPIO_MASK  (1 << 4)   // manual indicator output
#define ATR_MASKS      (AMP_GPIO_MASK | MAN_GPIO_MASK)

// CTRL: 1 = ATR, 0 = manual
#define ATR_CONTROL    (AMP_GPIO_MASK)           // GPIO6 → ATR, GPIO4 → manual

// DDR: 1 = output, 0 = input
#define GPIO_DDR       (AMP_GPIO_MASK | MAN_GPIO_MASK)

// Assume an existing multi_usrp::sptr named usrp

// 1. Set control mode and data direction
usrp->set_gpio_attr("FP0", "CTRL", ATR_CONTROL, ATR_MASKS);
usrp->set_gpio_attr("FP0", "DDR",  GPIO_DDR,    ATR_MASKS);

// 2. Drive GPIO4 high manually
usrp->set_gpio_attr("FP0", "OUT", MAN_GPIO_MASK, MAN_GPIO_MASK);

// 3. Configure ATR states for GPIO6 (PA enable)
//    PA off when idle
usrp->set_gpio_attr("FP0", "ATR_0X", 0,            AMP_GPIO_MASK);
//    PA off during RX only
usrp->set_gpio_attr("FP0", "ATR_RX", 0,            AMP_GPIO_MASK);
//    PA ON during TX only
usrp->set_gpio_attr("FP0", "ATR_TX", AMP_GPIO_MASK, AMP_GPIO_MASK);
//    PA off during full-duplex (TX+RX)
usrp->set_gpio_attr("FP0", "ATR_XX", 0,            AMP_GPIO_MASK);
After this setup, GPIO[6] will automatically go high whenever the USRP transitions into TX-only mode and drop low in all other states.
To adapt this example for the E31x internal GPIO bank, substitute "INT0" for "FP0". The API is identical; only the bank name differs.

Reading GPIO State

Read the current logic level of all input pins using the READBACK attribute:
uint32_t rb = usrp->get_gpio_attr("FP0", "READBACK");

// Check if bit 7 is high
if (rb & (1 << 7)) {
    std::cout << "GPIO[7] is HIGH" << std::endl;
}
You can also read back the current value of any configuration register:
uint32_t ctrl_val = usrp->get_gpio_attr("FP0", "CTRL");
uint32_t ddr_val  = usrp->get_gpio_attr("FP0", "DDR");

X3x0 Front Panel Pin Mapping

The X310 front panel GPIO uses a DB15 connector with 12 data pins:
PinSignal
1+3.3 V
2–13Data[0] – Data[11]
14–15GND
The +3.3 V rail on the X310 GPIO connector is for ESD clamping only. Do not attempt to source more than 5 mA per pin, and do not rely on the 3.3 V rail for significant supply current.

E320 GPIO Pin Mapping

The E320 exposes 8 GPIO pins through a Mini-HDMI (Type C) connector:
GPIOMini-HDMI PinHDMI Type A Pin
Data[0]8 (Data 0+)7
Data[1]9 (Data 0−)9
Data[2]11 (Clock 0+)10
Data[3]12 (Clock 0−)12
Data[4]14 (CEC)13
Data[5]15 (SCL)15
Data[6]16 (SDA)16
Data[7]17 (Utility)14

Driving GPIO from an RFNoC Block

For custom FPGA designs, GPIO pins can be wired to the outputs of a user RFNoC block rather than the built-in ATR state machine. This enables fully custom GPIO logic implemented in HDL.
1

Declare GPIO ports in your block HDL

output wire [GPIO_WIDTH-1:0] gpio_out,
output wire [GPIO_WIDTH-1:0] gpio_ddr,
input  wire [GPIO_WIDTH-1:0] gpio_in
Drive gpio_ddr[i] = 1 for outputs, 0 for inputs.
2

Advertise the GPIO capability in the block YAML

parameters:
    GPIO_WIDTH: ${ config.device.parameters['FP_GPIO_BANK_WIDTH'] }
io_ports:
    gpio:
        type: gpio
        drive: master
        parameters:
            width: "${parameters['GPIO_WIDTH']}"
3

Connect the block to device pins in the image core YAML

connections:
    # Connect all GPIO pins to this block
    - { srcblk: my_gpio_block0, srcport: gpio,
        dstblk: device, dstport: fp_gpio }
    # Or connect a subset (pin 0 → GPIO[0], pin 1 → GPIO[3])
    - { srcblk: my_gpio_block0, srcport: gpio[0],
        dstblk: device, dstport: fp_gpio[0] }
    - { srcblk: my_gpio_block0, srcport: gpio[1],
        dstblk: device, dstport: fp_gpio[3] }
4

Set the GPIO source in software

Tell UHD which pins are controlled by your RFNoC block:
import uhd
usrp = uhd.usrp.MultiUSRP("addr=<device IP>")
bank = usrp.get_gpio_src_banks()[0]
srcs = usrp.get_gpio_src(bank)
# Mark pins 0 and 3 as driven by the user RFNoC block
srcs[0] = "USER_APP"
srcs[3] = "USER_APP"
usrp.set_gpio_src(bank, srcs)

Build docs developers (and LLMs) love