burst-sender/burstelchen.cpp

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;
}
}