/* * Copyright 2022 sysmocom - s.f.m.c. GmbH * * Author: Eric Wild * * 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 . * See the COPYING file in the main directory for details. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 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>("dummy")); std::vector burstvec; for (auto i : fl2) { auto *f = readf>(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 rx_dummy_buffer[5000]; const std::vector 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 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 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; } }