uhd/host/examples/gpio.cpp
Martin Braun 91f8a9b8b2 examples: gpio: Replace get_time_now() with steady_clock::now()
The gpio example would continously call get_time_now() to time a loop.
There is no need to query a device here, so we query the system timer
instead.

This fixes an issue where the large amounts of control traffic could
slow down TX, causing the TX and FDX tests to fail. This was only ever
seen on the X300_HG over 1GigE.
2019-11-26 12:21:34 -08:00

465 lines
19 KiB
C++

//
// Copyright 2014-15 Ettus Research LLC
// Copyright 2018 Ettus Research, a National Instruments Company
//
// SPDX-License-Identifier: GPL-3.0-or-later
//
// Example for GPIO testing and bit banging.
//
// This example was originally designed to test the 11 bit wide front panel
// GPIO on the X300 series and has since been adapted to work with any GPIO
// bank on any USRP and provide optional bit banging. Please excuse the
// clutter. Also, there is no current way to detect the width of the
// specified GPIO bank, so the user must specify the width with the --bits
// flag if more than 11 bits.
//
// GPIO Testing:
// For testing, GPIO bits are set as follows:
// GPIO[0] = ATR output 1 at idle
// GPIO[1] = ATR output 1 during RX
// GPIO[2] = ATR output 1 during TX
// GPIO[3] = ATR output 1 during full duplex
// GPIO[4] = output
// GPIO[n:5] = input (all other pins)
// The testing cycles through idle, TX, RX, and full duplex, dwelling on each
// test case (default 2 seconds), and then comparing the readback register with
// the expected values of the outputs for verification. The values of all GPIO
// registers are displayed at the end of each test case. Outputs can be
// physically looped back to inputs to manually verify the inputs.
//
// GPIO Bit Banging:
// GPIO banks have the standard registers of DDR for data direction and OUT
// for output values. Users can bit bang the GPIO bits by using this example
// with the --bitbang flag and specifying the --ddr and --out flags to set the
// values of the corresponding registers. The READBACK register is
// continuously read for the duration of the dwell time (default 2 seconds) so
// users can monitor changes on the inputs.
//
// Automatic Transmit/Receive (ATR):
// In addition to the standard DDR and OUT registers, the GPIO banks also
// have ATR (Automatic Transmit/Receive) control registers that allow the
// GPIO pins to be automatically set to specific values when the USRP is
// idle, transmitting, receiving, or operating in full duplex mode. The
// description of these registers is below:
// CTRL - Control (0=manual, 1=ATR)
// ATR_0X - Values to be set when idle
// ATR_RX - Output values to be set when receiving
// ATR_TX - Output values to be set when transmitting
// ATR_XX - Output values to be set when operating in full duplex
// This code below contains examples of setting all these registers. On
// devices with multiple radios, the ATR for the front panel GPIO is driven
// by the state of the first radio (0 or A).
//
// The UHD API
// The multi_usrp::set_gpio_attr() method is the UHD API for configuring and
// controlling the GPIO banks. The parameters to the method are:
// bank - the name of the GPIO bank (typically "FP0" for front panel GPIO,
// "TX<n>" for TX daughter card GPIO, or
// "RX<n>" for RX daughter card GPIO)
// attr - attribute (register) to change ("DDR", "OUT", "CTRL", "ATR_0X",
// "ATR_RX", "ATR_TX", "ATR_XX")
// value - the value to be set
// mask - a mask indicating which bits in the specified attribute register are
// to be changed (default is all bits).
#include <uhd/convert.hpp>
#include <uhd/usrp/multi_usrp.hpp>
#include <uhd/utils/safe_main.hpp>
#include <uhd/utils/thread.hpp>
#include <stdint.h>
#include <stdlib.h>
#include <boost/format.hpp>
#include <boost/program_options.hpp>
#include <chrono>
#include <csignal>
#include <iostream>
#include <thread>
static const std::string GPIO_DEFAULT_CPU_FORMAT = "fc32";
static const std::string GPIO_DEFAULT_OTW_FORMAT = "sc16";
static const double GPIO_DEFAULT_RX_RATE = 500e3;
static const double GPIO_DEFAULT_TX_RATE = 500e3;
static const double GPIO_DEFAULT_DWELL_TIME = 2.0;
static const std::string GPIO_DEFAULT_GPIO = "FP0";
static const size_t GPIO_DEFAULT_NUM_BITS = 11;
static const std::string GPIO_DEFAULT_CTRL = "0x0"; // all as user controlled
static const std::string GPIO_DEFAULT_DDR = "0x0"; // all as inputs
static const std::string GPIO_DEFAULT_OUT = "0x0";
static inline uint32_t GPIO_BIT(const size_t x)
{
return (1 << x);
}
namespace po = boost::program_options;
static bool stop_signal_called = false;
void sig_int_handler(int)
{
stop_signal_called = true;
}
std::string to_bit_string(uint32_t val, const size_t num_bits)
{
std::string out;
for (int i = num_bits - 1; i >= 0; i--) {
std::string bit = ((val >> i) & 1) ? "1" : "0";
out += " ";
out += bit;
}
return out;
}
void output_reg_values(const std::string bank,
const uhd::usrp::multi_usrp::sptr& usrp,
const size_t num_bits)
{
const std::vector<std::string> attrs = {
"CTRL", "DDR", "ATR_0X", "ATR_RX", "ATR_TX", "ATR_XX", "OUT", "READBACK"};
std::cout << (boost::format("%10s ") % "Bit");
for (int i = num_bits - 1; i >= 0; i--)
std::cout << (boost::format(" %2d") % i);
std::cout << std::endl;
for (const auto& attr : attrs) {
const uint32_t gpio_bits = uint32_t(usrp->get_gpio_attr(bank, attr));
std::cout << (boost::format("%10s:%s") % attr
% to_bit_string(gpio_bits, num_bits))
<< std::endl;
}
}
int UHD_SAFE_MAIN(int argc, char* argv[])
{
// variables to be set by po
std::string args;
std::string cpu, otw;
double rx_rate, tx_rate, dwell;
std::string gpio;
size_t num_bits;
std::string ctrl_str;
std::string ddr_str;
std::string out_str;
// 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")
("repeat", "repeat loop until Ctrl-C is pressed")
("list-banks", "print list of banks before running tests")
("cpu", po::value<std::string>(&cpu)->default_value(GPIO_DEFAULT_CPU_FORMAT), "cpu data format")
("otw", po::value<std::string>(&otw)->default_value(GPIO_DEFAULT_OTW_FORMAT), "over the wire data format")
("rx_rate", po::value<double>(&rx_rate)->default_value(GPIO_DEFAULT_RX_RATE), "rx sample rate")
("tx_rate", po::value<double>(&tx_rate)->default_value(GPIO_DEFAULT_TX_RATE), "tx sample rate")
("dwell", po::value<double>(&dwell)->default_value(GPIO_DEFAULT_DWELL_TIME), "dwell time in seconds for each test case")
("bank", po::value<std::string>(&gpio)->default_value(GPIO_DEFAULT_GPIO), "name of gpio bank")
("bits", po::value<size_t>(&num_bits)->default_value(GPIO_DEFAULT_NUM_BITS), "number of bits in gpio bank")
("bitbang", "single test case where user sets values for CTRL, DDR, and OUT registers")
("ddr", po::value<std::string>(&ddr_str)->default_value(GPIO_DEFAULT_DDR), "GPIO DDR reg value")
("out", po::value<std::string>(&out_str)->default_value(GPIO_DEFAULT_OUT), "GPIO OUT reg value")
;
// 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 << boost::format("gpio %s") % desc << std::endl;
return ~0;
}
// create a usrp device
std::cout << std::endl;
std::cout << boost::format("Creating the usrp device with: %s...") % args
<< std::endl;
uhd::usrp::multi_usrp::sptr usrp = uhd::usrp::multi_usrp::make(args);
std::cout << boost::format("Using Device: %s") % usrp->get_pp_string() << std::endl;
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;
}
}
std::cout << "Using GPIO bank: " << gpio << std::endl;
// print out initial unconfigured state of FP GPIO
std::cout << "Initial GPIO values:" << std::endl;
output_reg_values(gpio, usrp, num_bits);
// configure GPIO registers
uint32_t ddr = strtoul(ddr_str.c_str(), NULL, 0);
uint32_t out = strtoul(out_str.c_str(), NULL, 0);
uint32_t ctrl = 0;
uint32_t atr_idle = 0;
uint32_t atr_rx = 0;
uint32_t atr_tx = 0;
uint32_t atr_duplex = 0;
uint32_t mask = (1 << num_bits) - 1;
if (!vm.count("bitbang")) {
// set up GPIO outputs:
// GPIO[0] = ATR output 1 at idle
ctrl |= GPIO_BIT(0);
atr_idle |= GPIO_BIT(0);
ddr |= GPIO_BIT(0);
// GPIO[1] = ATR output 1 during RX
ctrl |= GPIO_BIT(1);
ddr |= GPIO_BIT(1);
atr_rx |= GPIO_BIT(1);
// GPIO[2] = ATR output 1 during TX
ctrl |= GPIO_BIT(2);
ddr |= GPIO_BIT(2);
atr_tx |= GPIO_BIT(2);
// GPIO[3] = ATR output 1 during full duplex
ctrl |= GPIO_BIT(3);
ddr |= GPIO_BIT(3);
atr_duplex |= GPIO_BIT(3);
// GPIO[4] = output
ddr |= GPIO_BIT(4);
}
// set data direction register (DDR)
usrp->set_gpio_attr(gpio, "DDR", ddr, mask);
// set control register
usrp->set_gpio_attr(gpio, "CTRL", ctrl, mask);
// set output values (OUT)
usrp->set_gpio_attr(gpio, "OUT", out, mask);
// set ATR registers
usrp->set_gpio_attr(gpio, "ATR_0X", atr_idle, mask);
usrp->set_gpio_attr(gpio, "ATR_RX", atr_rx, mask);
usrp->set_gpio_attr(gpio, "ATR_TX", atr_tx, mask);
usrp->set_gpio_attr(gpio, "ATR_XX", atr_duplex, mask);
// print out initial state of FP GPIO
std::cout << "\nConfigured GPIO values:" << std::endl;
output_reg_values(gpio, usrp, num_bits);
std::cout << std::endl;
// set up streams
uhd::stream_args_t rx_args(cpu, otw);
uhd::stream_args_t tx_args(cpu, otw);
uhd::rx_streamer::sptr rx_stream = usrp->get_rx_stream(rx_args);
uhd::tx_streamer::sptr tx_stream = usrp->get_tx_stream(tx_args);
uhd::stream_cmd_t rx_cmd(uhd::stream_cmd_t::STREAM_MODE_START_CONTINUOUS);
rx_cmd.stream_now = true;
usrp->set_rx_rate(rx_rate);
usrp->set_tx_rate(tx_rate);
// set up buffers for tx and rx
const size_t max_samps_per_packet = rx_stream->get_max_num_samps();
const size_t nsamps_per_buff = max_samps_per_packet;
std::vector<char> rx_buff(
max_samps_per_packet * uhd::convert::get_bytes_per_item(cpu));
std::vector<char> tx_buff(
max_samps_per_packet * uhd::convert::get_bytes_per_item(cpu));
std::vector<void*> rx_buffs, tx_buffs;
for (size_t ch = 0; ch < rx_stream->get_num_channels(); ch++)
rx_buffs.push_back(&rx_buff.front()); // same buffer for each channel
for (size_t ch = 0; ch < tx_stream->get_num_channels(); ch++)
tx_buffs.push_back(&tx_buff.front()); // same buffer for each channel
uhd::rx_metadata_t rx_md;
uhd::tx_metadata_t tx_md;
tx_md.has_time_spec = false;
tx_md.start_of_burst = true;
double timeout = 0.01;
auto dwell_time = std::chrono::milliseconds(static_cast<int64_t>(dwell * 1000));
int loop = 0;
uint32_t rb, expected;
// register signal handler
std::signal(SIGINT, &sig_int_handler);
if (!vm.count("bitbang")) {
// Test the mask parameter of the multi_usrp::set_gpio_attr API
// We only need to test once with no dwell time
std::cout << "\nTesting mask..." << std::flush;
// send a value of all 1's to the DDR with a mask for only upper most bit
usrp->set_gpio_attr(gpio, "DDR", ~0, GPIO_BIT(num_bits - 1));
// upper most bit should now be 1, but all the other bits should be unchanged
rb = usrp->get_gpio_attr(gpio, "DDR") & mask;
expected = ddr | GPIO_BIT(num_bits - 1);
if (rb == expected)
std::cout << "pass:" << std::endl;
else
std::cout << "fail:" << std::endl;
output_reg_values(gpio, usrp, num_bits);
// restore DDR value
usrp->set_gpio_attr(gpio, "DDR", ddr, mask);
}
while (not stop_signal_called) {
int failures = 0;
if (vm.count("repeat"))
std::cout << "Press Ctrl + C to quit..." << std::endl;
if (vm.count("bitbang")) {
// dwell and continuously read back GPIO values
auto stop_time = std::chrono::steady_clock::now() + dwell_time;
while (
not stop_signal_called and std::chrono::steady_clock::now() < stop_time) {
rb = usrp->get_gpio_attr(gpio, "READBACK");
std::cout << "\rREADBACK: " << to_bit_string(rb, num_bits);
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
std::cout << std::endl;
} else {
// test user controlled GPIO and ATR idle by setting bit 4 high for 1 second
std::cout << "\nTesting user controlled GPIO and ATR idle output..."
<< std::flush;
usrp->set_gpio_attr(gpio, "OUT", GPIO_BIT(4), GPIO_BIT(4));
auto stop_time = std::chrono::steady_clock::now() + dwell_time;
while (
not stop_signal_called and std::chrono::steady_clock::now() < stop_time) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
rb = usrp->get_gpio_attr(gpio, "READBACK");
expected = GPIO_BIT(4) | GPIO_BIT(0);
if ((rb & expected) != expected) {
++failures;
std::cout << "fail:" << std::endl;
if ((rb & GPIO_BIT(0)) == 0)
std::cout << "Bit 0 should be set, but is not" << std::endl;
if ((rb & GPIO_BIT(4)) == 0)
std::cout << "Bit 4 should be set, but is not" << std::endl;
} else {
std::cout << "pass:" << std::endl;
}
output_reg_values(gpio, usrp, num_bits);
usrp->set_gpio_attr(gpio, "OUT", 0, GPIO_BIT(4));
if (stop_signal_called)
break;
// test ATR RX by receiving for 1 second
std::cout << "\nTesting ATR RX output..." << std::flush;
rx_cmd.stream_mode = uhd::stream_cmd_t::STREAM_MODE_START_CONTINUOUS;
rx_stream->issue_stream_cmd(rx_cmd);
stop_time = std::chrono::steady_clock::now() + dwell_time;
while (
not stop_signal_called and std::chrono::steady_clock::now() < stop_time) {
try {
rx_stream->recv(rx_buffs, nsamps_per_buff, rx_md, timeout);
} catch (...) {
}
}
rb = usrp->get_gpio_attr(gpio, "READBACK");
expected = GPIO_BIT(1);
if ((rb & expected) != expected) {
++failures;
std::cout << "fail:" << std::endl;
std::cout << "Bit 1 should be set, but is not" << std::endl;
} else {
std::cout << "pass:" << std::endl;
}
output_reg_values(gpio, usrp, num_bits);
rx_stream->issue_stream_cmd(uhd::stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS);
// clear out any data left in the rx stream
try {
rx_stream->recv(rx_buffs, nsamps_per_buff, rx_md, timeout);
} catch (...) {
}
if (stop_signal_called)
break;
// test ATR TX by transmitting for 1 second
std::cout << "\nTesting ATR TX output..." << std::flush;
stop_time = std::chrono::steady_clock::now() + dwell_time;
tx_md.start_of_burst = true;
tx_md.end_of_burst = false;
while (
not stop_signal_called and std::chrono::steady_clock::now() < stop_time) {
try {
tx_stream->send(tx_buffs, nsamps_per_buff, tx_md, timeout);
tx_md.start_of_burst = false;
} catch (...) {
}
}
rb = usrp->get_gpio_attr(gpio, "READBACK");
expected = GPIO_BIT(2);
if ((rb & expected) != expected) {
++failures;
std::cout << "fail:" << std::endl;
std::cout << "Bit 2 should be set, but is not" << std::endl;
} else {
std::cout << "pass:" << std::endl;
}
output_reg_values(gpio, usrp, num_bits);
tx_md.end_of_burst = true;
try {
tx_stream->send(tx_buffs, nsamps_per_buff, tx_md, timeout);
} catch (...) {
}
if (stop_signal_called)
break;
// test ATR RX by transmitting and receiving for 1 second
std::cout << "\nTesting ATR full duplex output..." << std::flush;
rx_cmd.stream_mode = uhd::stream_cmd_t::STREAM_MODE_START_CONTINUOUS;
rx_stream->issue_stream_cmd(rx_cmd);
tx_md.start_of_burst = true;
tx_md.end_of_burst = false;
stop_time = std::chrono::steady_clock::now() + dwell_time;
while (
not stop_signal_called and std::chrono::steady_clock::now() < stop_time) {
try {
tx_stream->send(tx_buffs, nsamps_per_buff, tx_md, timeout);
tx_md.start_of_burst = false;
rx_stream->recv(rx_buffs, nsamps_per_buff, rx_md, timeout);
} catch (...) {
}
}
rb = usrp->get_gpio_attr(gpio, "READBACK");
expected = GPIO_BIT(3);
if ((rb & expected) != expected) {
++failures;
std::cout << "fail:" << std::endl;
std::cout << "Bit 3 should be set, but is not" << std::endl;
} else {
std::cout << "pass:" << std::endl;
}
output_reg_values(gpio, usrp, num_bits);
rx_stream->issue_stream_cmd(uhd::stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS);
tx_md.end_of_burst = true;
try {
tx_stream->send(tx_buffs, nsamps_per_buff, tx_md, timeout);
} catch (...) {
}
// clear out any data left in the rx stream
try {
rx_stream->recv(rx_buffs, nsamps_per_buff, rx_md, timeout);
} catch (...) {
}
std::cout << std::endl;
if (failures)
std::cout << failures << " tests failed" << std::endl;
else
std::cout << "All tests passed!" << std::endl;
}
if (!vm.count("repeat"))
break;
if (not stop_signal_called)
std::cout << (boost::format("\nLoop %d completed") % ++loop) << std::endl;
}
// finished
std::cout << std::endl << "Done!" << std::endl << std::endl;
return EXIT_SUCCESS;
}