284 lines
8.5 KiB
C++
284 lines
8.5 KiB
C++
/*
|
|
* Copyright 2022 sysmocom - s.f.m.c. GmbH
|
|
*
|
|
* Author: Eric Wild <ewild@sysmocom.de>
|
|
*
|
|
* SPDX-License-Identifier: AGPL-3.0+
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
* See the COPYING file in the main directory for details.
|
|
*/
|
|
|
|
#include <chrono>
|
|
#include <climits>
|
|
#include <complex>
|
|
#include <cstdint>
|
|
#include <cstring>
|
|
|
|
#include <chrono>
|
|
#include <thread>
|
|
#include <uhd/types/time_spec.hpp>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include <csignal>
|
|
#include <dirent.h>
|
|
|
|
#include <uhd/stream.hpp>
|
|
#include <uhd/types/metadata.hpp>
|
|
#include <uhd/usrp/multi_usrp.hpp>
|
|
#include <uhd/version.hpp>
|
|
|
|
#include "argvhelper.h"
|
|
#include "filehelper.h"
|
|
|
|
using namespace std;
|
|
|
|
const auto txFullScale = int(32767 * 0.3); // scale like osmotrx
|
|
const auto gsmrate = (1625e3 / 6) * 4;
|
|
uhd::usrp::multi_usrp::sptr uhd_dev;
|
|
uhd::tx_streamer::sptr tx_stream;
|
|
uhd::rx_streamer::sptr rx_stream;
|
|
double rxtickrate, txtickrate;
|
|
int p_ts_begin_offset, p_ts_end_offset;
|
|
|
|
unsigned int p_txfreq(881000 * 1000);
|
|
unsigned int p_txgain = 20;
|
|
float p_bandwidth = 0.5e6;
|
|
bool p_use_ext_ref = false;
|
|
|
|
static volatile bool g_exit_flag = false;
|
|
|
|
/* see https://kb.ettus.com/B200/B210/B200mini/B205mini#GPIO
|
|
"..initial state for the front-panel GPIOs is high-Z.."
|
|
no external pull-ups/pull-downs BUT FPGA config: pull-up!
|
|
*/
|
|
void gpio_setup()
|
|
{
|
|
auto gpio_bank = uhd_dev->get_gpio_banks(0).front();
|
|
std::cout << "using gpio bank: " << gpio_bank << std::endl;
|
|
std::cout << "available gpio banks: " << std::endl;
|
|
auto banks = uhd_dev->get_gpio_banks(0);
|
|
for (auto &bank : banks) {
|
|
std::cout << bank << std::endl;
|
|
}
|
|
|
|
// set all to manual
|
|
uhd_dev->set_gpio_attr("FP0", "CTRL", 0x00, 0xff);
|
|
uhd_dev->set_gpio_attr("FP0", "DDR", 0xff, 0xff);
|
|
uhd_dev->set_gpio_attr("FP0", "OUT", 0, 0xff);
|
|
}
|
|
|
|
void gpio_cmd(long long ts_begin, long long ts_end)
|
|
{
|
|
auto start = uhd::time_spec_t::from_ticks(ts_begin, txtickrate);
|
|
auto end = uhd::time_spec_t::from_ticks(ts_end, txtickrate);
|
|
|
|
uhd_dev->set_command_time(start);
|
|
uhd_dev->set_gpio_attr("FP0", "OUT", 1, 0xff);
|
|
uhd_dev->set_command_time(end);
|
|
uhd_dev->set_gpio_attr("FP0", "OUT", 0, 0xff);
|
|
}
|
|
|
|
uhd::usrp::multi_usrp::sptr init_device()
|
|
{
|
|
auto const lock_delay_ms = 500;
|
|
auto const mcr = 26e6;
|
|
|
|
auto const channel = 0;
|
|
|
|
// aligned to blade: 1020 samples per transfer
|
|
// std::string args = { "recv_frame_size=4092,send_frame_size=4092" };
|
|
uhd::tune_request_t tune_request(p_txfreq, 0);
|
|
|
|
try {
|
|
uhd_dev = uhd::usrp::multi_usrp::make(std::string(""));
|
|
} catch (const std::exception &e) {
|
|
std::cerr << e.what();
|
|
exit(0);
|
|
}
|
|
|
|
std::cout << "Using Device: " << uhd_dev->get_pp_string() << std::endl;
|
|
|
|
gpio_setup();
|
|
|
|
if (p_use_ext_ref) {
|
|
std::cout << "using EXTERNAL clock reference.." << std::endl;
|
|
uhd_dev->set_clock_source("external");
|
|
} else {
|
|
std::cout << "using internal clock reference.." << std::endl;
|
|
uhd_dev->set_clock_source("internal");
|
|
}
|
|
|
|
uhd_dev->set_master_clock_rate(mcr);
|
|
|
|
uhd_dev->set_tx_rate(gsmrate, channel);
|
|
uhd_dev->set_tx_freq(tune_request, channel);
|
|
uhd_dev->set_tx_gain(p_txgain, channel);
|
|
uhd_dev->set_tx_bandwidth(p_bandwidth, channel);
|
|
|
|
uhd_dev->set_rx_rate(gsmrate, channel);
|
|
uhd_dev->set_rx_freq(tune_request, channel);
|
|
uhd_dev->set_rx_gain(30, channel);
|
|
// uhd_dev->set_rx_bandwidth(bw, channel);
|
|
|
|
std::cerr << "waiting for internal/external clock source lock.." << std::endl;
|
|
|
|
auto is_ref_locked = []() {
|
|
return (uhd_dev->get_rx_sensor("lo_locked", channel).to_bool() &&
|
|
(p_use_ext_ref ? uhd_dev->get_mboard_sensor("ref_locked").to_bool() : 1));
|
|
};
|
|
|
|
while (!is_ref_locked())
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(lock_delay_ms));
|
|
std::cerr << "clock locked!" << std::endl;
|
|
|
|
uhd::stream_args_t stream_args("sc16", "sc16");
|
|
rx_stream = uhd_dev->get_rx_stream(stream_args);
|
|
uhd::stream_args_t stream_args2("sc16", "sc16");
|
|
tx_stream = uhd_dev->get_tx_stream(stream_args2);
|
|
|
|
rxtickrate = uhd_dev->get_rx_rate();
|
|
txtickrate = uhd_dev->get_tx_rate();
|
|
assert(rxtickrate == txtickrate);
|
|
|
|
std::cerr << "RX samples per packet: " << rx_stream->get_max_num_samps() << std::endl;
|
|
|
|
return uhd_dev;
|
|
}
|
|
|
|
void sighandler(int sig)
|
|
{
|
|
g_exit_flag = true;
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
std::string p_burstspath;
|
|
std::vector<std::string> args(argv + 1, argv + argc);
|
|
signal(SIGINT, sighandler);
|
|
signal(SIGTERM, sighandler);
|
|
|
|
for (auto i = args.begin(); i != args.end(); ++i) {
|
|
parsec(args, i, "-txfreq", 1000, &p_txfreq);
|
|
parsec(args, i, "-txgain", 1, &p_txgain);
|
|
parsec(args, i, "-bandwidth", 1.f, &p_bandwidth);
|
|
parsec(args, i, "-extref", &p_use_ext_ref);
|
|
parsec(args, i, "-burstpath", &p_burstspath);
|
|
parsec(args, i, "-ts_begin_offset", 1, &p_ts_begin_offset);
|
|
parsec(args, i, "-ts_end_offset", 1, &p_ts_end_offset);
|
|
}
|
|
|
|
if (p_burstspath.empty()) {
|
|
std::cerr << "parameters:" << std::endl
|
|
<< "-txfreq 881000 [khz]" << std::endl
|
|
<< "-txgain 5 [dB]" << std::endl
|
|
<< "-bandwidth 500000 [hz]" << std::endl
|
|
<< "-extref [use external gpsdo as ref]" << std::endl
|
|
<< "-burstpath ./foo/bar [path to bursts]" << std::endl
|
|
<< "-ts_begin_offset [shifts begin ts, in samples]" << std::endl
|
|
<< "-ts_end_offset [shifts end ts, in samples]" << std::endl;
|
|
}
|
|
std::cerr << "txfreq " << p_txfreq << std::endl;
|
|
std::cerr << "txgain " << p_txgain << std::endl;
|
|
std::cerr << "bandwidth " << p_bandwidth << std::endl;
|
|
std::cerr << "using ext clock ref: " << (p_use_ext_ref ? "yes " : "no") << std::endl;
|
|
|
|
auto fl = list_dir(p_burstspath);
|
|
auto fl2 = filter_entries(fl, ".cfile");
|
|
|
|
if (fl2.empty()) {
|
|
std::cerr << "file list empty, bursts missing? quitting.." << std::endl;
|
|
return 0;
|
|
}
|
|
std::cerr << "got " << fl2.size() << " burst files.." << std::endl;
|
|
|
|
using fdatat = decltype(readf<std::complex<float>>("dummy"));
|
|
|
|
std::vector<fdatat> burstvec;
|
|
for (auto i : fl2) {
|
|
auto *f = readf<std::complex<float>>(i.c_str());
|
|
burstvec.push_back(f);
|
|
// std::cerr << i << ":" << f->num_samp << std::endl;
|
|
}
|
|
|
|
auto usrp = init_device();
|
|
|
|
auto now_in_ticks = usrp->get_time_now().to_ticks(txtickrate);
|
|
auto rep_in_ticks = uhd::time_spec_t(1.).to_ticks(txtickrate);
|
|
auto tx_start = now_in_ticks + rep_in_ticks;
|
|
|
|
std::cerr << "start ts: " << tx_start << std::endl;
|
|
|
|
auto rx_dummy_thread = std::thread([tx_start]() {
|
|
std::complex<uint16_t> rx_dummy_buffer[5000];
|
|
const std::vector<void *> recv_buf(1, rx_dummy_buffer);
|
|
uhd::rx_metadata_t rx_md;
|
|
|
|
uhd::stream_cmd_t rx_cmd{ uhd::stream_cmd_t::STREAM_MODE_START_CONTINUOUS };
|
|
rx_cmd.time_spec = rx_cmd.time_spec.from_ticks(tx_start, txtickrate);
|
|
rx_stream->issue_stream_cmd(rx_cmd);
|
|
|
|
while (!g_exit_flag) {
|
|
rx_stream->recv(recv_buf, 5000, rx_md, 0.2, true);
|
|
}
|
|
|
|
uhd::stream_cmd_t stop_cmd{ uhd::stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS };
|
|
rx_stream->issue_stream_cmd(stop_cmd);
|
|
});
|
|
|
|
// std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
|
|
|
while (!g_exit_flag) {
|
|
for (auto *f : burstvec) {
|
|
std::vector<int16_t> burst_buf(f->num_samp * 2);
|
|
// auto RSSI = 10;
|
|
auto scalefac = txFullScale; //txFullScale * pow(10, -RSSI / 10);
|
|
convert_and_scale(burst_buf.data(), f->buf.get(), f->num_samp * 2, scalefac);
|
|
|
|
if (g_exit_flag)
|
|
break;
|
|
|
|
gpio_cmd(tx_start + p_ts_begin_offset, tx_start + f->num_samp + p_ts_end_offset);
|
|
|
|
uhd::tx_metadata_t m = {};
|
|
m.end_of_burst = true;
|
|
m.start_of_burst = true;
|
|
m.has_time_spec = true;
|
|
m.time_spec = m.time_spec.from_ticks(tx_start, txtickrate);
|
|
std::vector<void *> ptrs(1, burst_buf.data());
|
|
tx_stream->send(ptrs, f->num_samp, m, 1.0);
|
|
|
|
uhd::async_metadata_t async_md;
|
|
bool tx_ack = false;
|
|
while (!tx_ack && tx_stream->recv_async_msg(async_md, 1.5)) {
|
|
tx_ack = (async_md.event_code == uhd::async_metadata_t::EVENT_CODE_BURST_ACK);
|
|
}
|
|
std::cout << tx_start << " -> " << (tx_ack ? "tx ack @" : "tx NAK @") << " "
|
|
<< async_md.time_spec.to_ticks(txtickrate) << std::endl;
|
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
|
|
|
tx_start += rep_in_ticks;
|
|
}
|
|
}
|
|
|
|
g_exit_flag = true;
|
|
rx_dummy_thread.join();
|
|
|
|
for (auto *f : burstvec) {
|
|
delete f;
|
|
}
|
|
}
|