uhd/host/examples/spi.cpp
Max Becker 3ed25c39c7 host: Complete X4xx GPIO SPI support
Update the X4xx SPI core, radio-control integration, examples, and
tests for transactions up to 64 bits and the revised register map.
2026-09-29 13:12:32 +02:00

159 lines
6.3 KiB
C++

//
// Copyright 2026 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: GPL-3.0-or-later
//
// Example for SPI testing.
//
// This example shows how to work with SPI which is based on the GPIO
// interface of the X410.
#include <uhd/features/spi_getter_iface.hpp>
#include <uhd/usrp/multi_usrp.hpp>
#include <uhd/utils/safe_main.hpp>
#include <stdlib.h>
#include <boost/program_options.hpp>
#include <cstdint>
#include <iostream>
static const size_t SPI_DEFAULT_CLK_PIN = 0;
static const size_t SPI_DEFAULT_SDI_PIN = 1;
static const size_t SPI_DEFAULT_SDO_PIN = 2;
static const size_t SPI_DEFAULT_CS_PIN = 3;
static const size_t SPI_DEFAULT_PAYLOAD_LENGTH = 32;
static const std::string SPI_DEFAULT_PAYLOAD = "0xfefe";
namespace po = boost::program_options;
int UHD_SAFE_MAIN(int argc, char* argv[])
{
// variables to be set by po
std::string args;
size_t clk;
size_t sdi;
size_t sdo;
size_t cs;
size_t payload_length;
size_t clk_divider;
std::string payload_str;
uint64_t payload;
// setup the program options
po::options_description desc("Allowed options");
// clang-format off
desc.add_options()
("help", "help message")
("args", po::value<std::string>(&args)->default_value(""), "multi uhd device address args")
("list-banks", "print list of banks before running tests")
("clk", po::value<size_t>(&clk)->default_value(SPI_DEFAULT_CLK_PIN), "number of pin for SPI clock")
("sdo", po::value<size_t>(&sdo)->default_value(SPI_DEFAULT_SDO_PIN), "number of pin for serial data out")
("sdi", po::value<size_t>(&sdi)->default_value(SPI_DEFAULT_SDI_PIN), "number of pin for serial data in")
("cs", po::value<size_t>(&cs)->default_value(SPI_DEFAULT_CS_PIN), "number of pin for chip select")
("payload", po::value<std::string>(&payload_str)->default_value(SPI_DEFAULT_PAYLOAD), "payload as integer value")
("length", po::value<size_t>(&payload_length)->default_value(SPI_DEFAULT_PAYLOAD_LENGTH), "payload length in bits")
("clk-div", po::value<size_t>(&clk_divider), "clock divider for SPI")
;
// clang-format on
po::variables_map vm;
po::store(po::parse_command_line(argc, argv, desc), vm);
po::notify(vm);
// print the help message
if (vm.count("help")) {
std::cout << argv[0] << " " << desc << std::endl;
return ~0;
}
// create a usrp device
std::cout << std::endl;
std::cout << "Creating the usrp device with: " << args << "..." << std::endl;
auto usrp = uhd::usrp::multi_usrp::make(args);
if (vm.count("list-banks")) {
std::cout << "Available GPIO banks: " << std::endl;
auto banks = usrp->get_gpio_banks(0);
for (auto& bank : banks) {
std::cout << "* " << bank << std::endl;
}
}
// Get the SPI getter interface from where we'll get the SPI interface itself
if (!usrp->get_radio_control().has_feature<uhd::features::spi_getter_iface>()) {
std::cout << "Error: Could not find SPI_Getter_Iface. Please check if your FPGA "
"image is up to date.\n";
return EXIT_FAILURE;
}
auto& spi_getter_iface =
usrp->get_radio_control().get_feature<uhd::features::spi_getter_iface>();
// Set all available pins to SPI for GPIO0 and GPIO1
std::vector<std::string> sources(12, "DB0_SPI");
usrp->set_gpio_src("GPIO0", sources);
usrp->set_gpio_src("GPIO1", sources);
// Create peripheral configuration per peripheral
uhd::features::spi_periph_config_t periph_cfg;
periph_cfg.periph_clk = clk;
periph_cfg.periph_sdi = sdi;
periph_cfg.periph_sdo = sdo;
periph_cfg.periph_cs = cs;
// The vector holds the peripheral configs with index=peripheral number
std::vector<uhd::features::spi_periph_config_t> periph_cfgs;
periph_cfgs.push_back(periph_cfg);
// Set the data direction register
uint32_t outputs = 0x0;
outputs |= 1 << periph_cfg.periph_clk;
outputs |= 1 << periph_cfg.periph_sdo;
outputs |= 1 << periph_cfg.periph_cs;
usrp->set_gpio_attr("GPIOA", "DDR", outputs & 0xFFFFFF);
auto spi_ref = spi_getter_iface.get_spi_ref(periph_cfgs);
std::cout << "Using pins: " << std::endl
<< " Clock = " << (int)(periph_cfg.periph_clk) << std::endl
<< " SDO = " << (int)(periph_cfg.periph_sdo) << std::endl
<< " SDI = " << (int)(periph_cfg.periph_sdi) << std::endl
<< " CS = " << (int)(periph_cfg.periph_cs) << std::endl
<< std::endl;
payload = strtoull(payload_str.c_str(), NULL, 0);
const size_t max_payload_bits = 64;
if (payload_length == 0 || payload_length > max_payload_bits) {
std::cerr << "Error: --length must be between 1 and " << max_payload_bits
<< " bits." << std::endl;
return EXIT_FAILURE;
}
const uint64_t payload_mask =
payload_length == 64 ? UINT64_MAX : (UINT64_C(1) << payload_length) - UINT64_C(1);
if (payload & ~payload_mask) {
std::cout << "WARNING: Payload 0x" << std::hex << payload
<< " has bits set outside of the specified length " << std::dec
<< payload_length << " bits. Remaining payload 0x" << std::hex
<< (payload & payload_mask) << std::endl;
payload &= payload_mask;
}
std::cout << "Writing payload: 0x" << std::hex << payload << " with length "
<< std::dec << payload_length << " bits" << std::endl;
// The spi_config_t holds items like the clock divider and the SDI and SDO edges
uhd::spi_config_t config;
if (vm.count("clk-div")) {
config.divider = clk_divider;
config.use_custom_divider = true;
}
config.mosi_edge = config.EDGE_RISE;
config.miso_edge = config.EDGE_FALL;
// Do the SPI transaction. There are write() and read() methods available, too.
std::cout << "Performing SPI transaction..." << std::endl;
const uint64_t read_data =
payload_length > 32
? spi_ref->transact_spi64(0, config, payload, payload_length, true)
: spi_ref->transact_spi32(
0, config, static_cast<uint32_t>(payload), payload_length, true);
std::cout << "Data read: 0x" << std::hex << read_data << std::endl;
return EXIT_SUCCESS;
}