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:
| Device | Bank Name | Width | Connector |
|---|
| X3x0 | FP0 | 12 bits | Front panel DB15 |
| E31x | INT0 | 6 bits | Internal header |
| E320 | FP0 | 8 bits | Mini-HDMI (Type C) |
| N3x0 / X410 | FP0 | device-dependent | Front 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().
Direction and Control
ATR State Values
Readback
| Attribute | Description |
|---|
CTRL | Per-pin control mode: 1 = ATR (automatic), 0 = manual |
DDR | Data Direction Register: 1 = output, 0 = input |
OUT | Manual output value (only meaningful when CTRL bit = 0) |
| Attribute | Radio State | Pin value applied when… |
|---|
ATR_0X | Idle | No TX or RX active |
ATR_RX | RX only | Receiving but not transmitting |
ATR_TX | TX only | Transmitting but not receiving |
ATR_XX | Full duplex | Both TX and RX active simultaneously |
| Attribute | Description |
|---|
READBACK | Current logic level of all pins marked as inputs (DDR = 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:
| Pin | Signal |
|---|
| 1 | +3.3 V |
| 2–13 | Data[0] – Data[11] |
| 14–15 | GND |
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:
| GPIO | Mini-HDMI Pin | HDMI 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.
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.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']}"
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] }
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)