From b79895c9990c77f7aa5c72ca3795e9d0d8443dfb Mon Sep 17 00:00:00 2001 From: Tom Tsou Date: Sat, 3 Jun 2017 18:31:48 -0700 Subject: siggen: Add osmo-siggen for GSM/EDGE test signal generation * Adds 4 and 1 pulse Laurent GMSK modulators * Adds NCO based modulator * Adds synchronization and frequency burst generators * Adds GPIO frame trigger on GPIO-0 * Tested on USRP B210 only * Requires C++14 support B210 GPIO Pin Map J504 --------- fp_gpio<0> | 1 | 2 | fp_gpio<1> --------- fp_gpio<2> | 3 | 4 | fp_gpio<3> --------- fp_gpio<4> | 5 | 6 | fp_gpio<5> --------- fp_gpio<6> | 7 | 8 | fp_gpio<7> --------- gnd | 9 | 10| gnd --------- Change-Id: I891725d7f0cfa97c79e64f978a60dc11a206195c Signed-off-by: Tom Tsou --- CommonLibs/Configuration.cpp | 1 - Transceiver52M/Makefile.am | 10 +- Transceiver52M/UHDDevice.cpp | 54 +++-- Transceiver52M/osmo-siggen.cpp | 490 +++++++++++++++++++++++++++++++++++++++++ Transceiver52M/radioDevice.h | 3 +- Transceiver52M/sigProcLib.cpp | 348 ++++++++++++++++++++++++++++- Transceiver52M/sigProcLib.h | 6 + 7 files changed, 889 insertions(+), 23 deletions(-) create mode 100644 Transceiver52M/osmo-siggen.cpp diff --git a/CommonLibs/Configuration.cpp b/CommonLibs/Configuration.cpp index 14e5400..9b166dd 100644 --- a/CommonLibs/Configuration.cpp +++ b/CommonLibs/Configuration.cpp @@ -82,7 +82,6 @@ float ConfigurationRecord::floatNumber() const ConfigurationTable::ConfigurationTable(const char* filename, const char *wCmdName, ConfigurationKeyMap wSchema) { - gLogEarly(LOG_INFO, "opening configuration table from path %s", filename); // Connect to the database. int rc = sqlite3_open(filename,&mDB); // (pat) When I used malloc here, sqlite3 sporadically crashes. diff --git a/Transceiver52M/Makefile.am b/Transceiver52M/Makefile.am index c2ab9ca..c8b20cd 100644 --- a/Transceiver52M/Makefile.am +++ b/Transceiver52M/Makefile.am @@ -69,7 +69,7 @@ libtransceiver_la_SOURCES = \ radioInterfaceResamp.cpp \ radioInterfaceMulti.cpp -bin_PROGRAMS = osmo-trx +bin_PROGRAMS = osmo-trx osmo-siggen noinst_HEADERS = \ Complex.h \ @@ -99,10 +99,18 @@ osmo_trx_LDADD = \ $(GSM_LA) \ $(COMMON_LA) $(SQLITE3_LIBS) +osmo_siggen_SOURCES = osmo-siggen.cpp +osmo_siggen_LDADD = \ + libtransceiver.la \ + $(ARCH_LA) \ + $(GSM_LA) \ + $(COMMON_LA) $(SQLITE3_LIBS) + if USRP1 libtransceiver_la_SOURCES += USRPDevice.cpp osmo_trx_LDADD += $(USRP_LIBS) else libtransceiver_la_SOURCES += UHDDevice.cpp osmo_trx_LDADD += $(UHD_LIBS) $(FFTWF_LIBS) +osmo_siggen_LDADD += $(UHD_LIBS) $(FFTWF_LIBS) endif diff --git a/Transceiver52M/UHDDevice.cpp b/Transceiver52M/UHDDevice.cpp index ecf2c34..446976f 100644 --- a/Transceiver52M/UHDDevice.cpp +++ b/Transceiver52M/UHDDevice.cpp @@ -212,7 +212,7 @@ public: ~uhd_device(); int open(const std::string &args, int ref, bool swap_channels); - bool start(); + bool start(bool tx_only); bool stop(); bool restart(); void setPriority(float prio); @@ -224,6 +224,7 @@ public: int writeSamples(std::vector &bufs, int len, bool *underrun, TIMESTAMP timestamp, bool isControl); + void triggerGPIO(TIMESTAMP ts); bool updateAlignment(TIMESTAMP timestamp); bool setTxFreq(double wFreq, size_t chan); @@ -784,7 +785,7 @@ bool uhd_device::restart() return flush_recv(10); } -bool uhd_device::start() +bool uhd_device::start(bool tx_only) { LOG(INFO) << "Starting USRP..."; @@ -802,12 +803,21 @@ bool uhd_device::start() async_event_thrd->start((void * (*)(void*))async_event_loop, (void*)this); // Start streaming - if (!restart()) + if (!tx_only && !restart()) return false; + // Setup GPIO + usrp_dev->set_gpio_attr("FP0", "CTRL", 0x00); + usrp_dev->set_gpio_attr("FP0", "DDR", 0x01); + // Display usrp time - double time_now = usrp_dev->get_time_now().get_real_secs(); - LOG(INFO) << "The current time is " << time_now << " seconds"; + auto now = usrp_dev->get_time_now(); + LOG(INFO) << "The current time is " << now.get_real_secs() << " seconds"; + + if (tx_only) { + auto start = uhd::time_spec_t(now.get_real_secs() + 1.0); + ts_initial = start.to_ticks(tx_rate); + } started = true; return true; @@ -972,6 +982,27 @@ int uhd_device::readSamples(std::vector &bufs, int len, bool *overrun, return len; } +#define GSM_FRAME_PERIOD (120e-3/26) +#define GPIO_FRAME_ADVANCE 5 +#define GPIO_ON_PERIOD (GSM_FRAME_PERIOD / 2) + +/* + * Trigger GPIO a handful of frames ahead of the current sample timestamp. + * This extends the number of GPIO triggers in the device side command + * queue and prevents late packet and underrun errors on the RF sample path. + */ +void uhd_device::triggerGPIO(TIMESTAMP ticks) +{ + auto ts = uhd::time_spec_t::from_ticks(ticks, tx_rate); + auto adv = uhd::time_spec_t(GPIO_FRAME_ADVANCE * GSM_FRAME_PERIOD); + auto per = uhd::time_spec_t(GPIO_ON_PERIOD); + + usrp_dev->set_command_time(ts - adv); + usrp_dev->set_gpio_attr("FP0", "OUT", 0x01); + usrp_dev->set_command_time(ts - adv + per); + usrp_dev->set_gpio_attr("FP0", "OUT", 0x00); +} + int uhd_device::writeSamples(std::vector &bufs, int len, bool *underrun, unsigned long long timestamp,bool isControl) { @@ -981,13 +1012,7 @@ int uhd_device::writeSamples(std::vector &bufs, int len, bool *underrun metadata.end_of_burst = false; metadata.time_spec = uhd::time_spec_t::from_ticks(timestamp, tx_rate); - *underrun = false; - - // No control packets - if (isControl) { - LOG(ERR) << "Control packets not supported"; - return 0; - } + if (underrun) *underrun = false; if (bufs.size() != chans) { LOG(ALERT) << "Invalid channel combination " << bufs.size(); @@ -997,10 +1022,9 @@ int uhd_device::writeSamples(std::vector &bufs, int len, bool *underrun // Drop a fixed number of packets (magic value) if (!aligned) { drop_cnt++; - if (drop_cnt == 1) { LOG(DEBUG) << "Aligning transmitter: stop burst"; - *underrun = true; + if (underrun) *underrun = true; metadata.end_of_burst = true; } else if (drop_cnt < 30) { LOG(DEBUG) << "Aligning transmitter: packet advance"; @@ -1014,7 +1038,7 @@ int uhd_device::writeSamples(std::vector &bufs, int len, bool *underrun } thread_enable_cancel(false); - size_t num_smpls = tx_stream->send(bufs, len, metadata); + size_t num_smpls = tx_stream->send(bufs, len, metadata, 1.0); thread_enable_cancel(true); if (num_smpls != (unsigned) len) { diff --git a/Transceiver52M/osmo-siggen.cpp b/Transceiver52M/osmo-siggen.cpp new file mode 100644 index 0000000..6005c58 --- /dev/null +++ b/Transceiver52M/osmo-siggen.cpp @@ -0,0 +1,490 @@ +/* + * GSM Signal Generator + * + * Copyright (C) 2017 Ettus Research LLC + * + * 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. + * + * Author: Tom Tsou + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "sigProcLib.h" +#include "radioDevice.h" + +extern "C" { +#include "convolve.h" +#include "convert.h" +} + +ConfigurationTable gConfig; + +#define DEFAULT_TX_SPS 4 +#define DEFAULT_TX_AMPL 0.5 +#define DEFAULT_TX_GAIN 50 +#define DEFAULT_TX_FREQ 1e9 +#define DEFAULT_OFFSET 0.0 + +using namespace std; + +enum GsmModType { + MOD_LAURENT4, + MOD_LAURENT2, + MOD_LAURENT1, + MOD_NCO, + NUM_MODS, +}; + +enum BurstType { + BURST_NORMAL, + BURST_ACCESS, + BURST_FREQ, + BURST_SYNC, + BURST_EDGE, + NUM_BURSTS, +}; + +enum BurstTSC { + TSC0, TSC1, TSC2, TSC3, TSC4, TSC5, TSC6, TSC7, +}; + +struct Config { + string args = ""; + string logl = "NOTICE"; + unsigned sps = DEFAULT_TX_SPS; + double offset = DEFAULT_OFFSET; + bool swap = false; + float ampl = DEFAULT_TX_AMPL; + double freq = DEFAULT_TX_FREQ; + double gain = DEFAULT_TX_GAIN; + BurstTSC tsc = TSC0; + GsmModType mod = MOD_LAURENT2; + BurstType burst = BURST_NORMAL; + RadioDevice::ReferenceType ref = RadioDevice::REF_INTERNAL; +}; + +static shared_ptr modulateGMSK(BitVector &bits, GsmModType modType) +{ + switch (modType) { + case MOD_LAURENT4: return shared_ptr(modulateBurstLaurent4(bits)); + case MOD_LAURENT2: return shared_ptr(modulateBurstLaurent2(bits)); + case MOD_LAURENT1: return shared_ptr(modulateBurstLaurent1(bits)); + case MOD_NCO: return shared_ptr(modulateBurstNCO(bits)); + default: return shared_ptr(modulateBurstLaurent2(bits)); + }; +} + +static shared_ptr generateNormalBurst(BurstTSC tsc, GsmModType modType) +{ + auto tail = vector(3, 0); + auto data0 = vector(57); + auto data1 = vector(57); + auto steal = vector(1, 0); + auto train = vector(26); + + auto ti = begin(GSM::gTrainingSequence[tsc]); + for (auto &t : train) t = *ti++; + for (auto &d : data0) d = rand() % 2; + for (auto &d : data1) d = rand() % 2; + + auto bits = BitVector(NORMAL_BURST_NBITS); + auto bi = bits.begin(); + + for (auto t : tail) *bi++ = t; + for (auto d : data0) *bi++ = d; + for (auto s : steal) *bi++ = s; + for (auto t : train) *bi++ = t; + for (auto s : steal) *bi++ = s; + for (auto d : data1) *bi++ = d; + for (auto t : tail) *bi++ = t; + + return modulateGMSK(bits, modType); +} + +static shared_ptr generateRABurst(GsmModType modType) +{ + auto tail0 = vector(8, 0); + auto train = vector(41); + auto data = vector(36); + auto tail1 = vector(3, 0); + + auto ti = begin(GSM::gRACHBurst); + for (auto &t : train) t = *ti++; + for (auto &d : data) d = rand() % 2; + + auto bits = BitVector(88); + auto bi = bits.begin(); + + for (auto t : tail0) *bi++ = t; + for (auto t : train) *bi++ = t; + for (auto d : data) *bi++ = d; + for (auto t : tail1) *bi++ = t; + + return modulateGMSK(bits, modType); +} + +static shared_ptr generateFreqBurst(GsmModType modType) +{ + auto tail = vector(3, 0); + auto fixed = vector(142); + + auto bits = BitVector(148); + auto bi = bits.begin(); + + for (auto t : tail) *bi++ = t; + for (auto f : fixed) *bi++ = f; + for (auto t : tail) *bi++ = t; + + return modulateGMSK(bits, modType); +} + +static shared_ptr generateSyncBurst(GsmModType modType) +{ + auto tail = vector(3, 0); + auto data0 = vector(39); + auto data1 = vector(39); + + /* 64 length synchronization sequence */ + vector train { + 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, + 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, + 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, + }; + for (auto &d : data0) d = rand() % 2; + for (auto &d : data1) d = rand() % 2; + + auto bits = BitVector(148); + auto bi = bits.begin(); + + for (auto t : tail) *bi++ = t; + for (auto d : data0) *bi++ = d; + for (auto t : train) *bi++ = t; + for (auto d : data1) *bi++ = d; + for (auto t : tail) *bi++ = t; + + return modulateGMSK(bits, modType); +} + +static shared_ptr generateEDGEBurst(BurstTSC tsc) +{ + auto tail = vector>(3); + auto data0 = vector>(58); + auto train = vector>(26); + auto data1 = vector>(58); + + extern const Complex psk8_table[8]; + for (auto &t : tail) t = psk8_table[0b111]; + for (auto &d : data0) d = psk8_table[rand() % 8]; + for (auto &d : data1) d = psk8_table[rand() % 8]; + + auto ti = begin(GSM::gEdgeTrainingSequence[tsc]); + for (auto &t : train) { + unsigned i = (*(ti + 0) & 0b001) << 0 | + (*(ti + 1) & 0b001) << 1 | + (*(ti + 2) & 0b001) << 2; + t = psk8_table[i]; + ti += 3; + } + + /* NBITS refers to 148 symbols in this case */ + auto burst = signalVector(NORMAL_BURST_NBITS); + auto bi = burst.begin(); + + for (auto t : tail) *bi++ = t; + for (auto d : data0) *bi++ = d; + for (auto t : train) *bi++ = t; + for (auto d : data1) *bi++ = d; + for (auto t : tail) *bi++ = t; + + return shared_ptr(shapeEdgeBurst(burst)); +} + +/* Perform float-integer conversion and write to the device */ +static void sendBurst(shared_ptr usrp, TIMESTAMP &ts, + shared_ptr sv, float ampl) +{ + auto buffer = vector>(sv->size()); + + transform(sv->begin(), sv->end(), buffer.begin(), [ampl](Complex x) { + const float scale = SHRT_MAX * ampl; + return Complex(x.real()*scale, x.imag()*scale); + }); + + auto buffers = vector(1, reinterpret_cast(&buffer.front())); + ts += usrp->writeSamples(buffers, buffer.size(), nullptr, ts, true); +} + +static void print_help() +{ + fprintf(stdout, "Options:\n" + " -h, --help This text\n" + " -a, --args UHD device args\n" + " -l --log Logging level (%s)\n" + " -b, --burst Burst type (%s)\n" + " -r, --ref Frequency reference (%s)\n" + " -f, --freq Tx RF frequency\n" + " -g, --gain Tx RF gain\n" + " -s, --sps Tx samples-per-symbol (only 4 supported)\n" + " -m, --mod GSMK modulator type (%s)\n" + " -p, --ampl Tx amplitude (0.0 - 1.0)\n" + " -o, --offset Baseband frequency offset\n" + " -t, --tsc Normal and EDGE burst training sequence (0-7)\n" + " -S, --swap Swap channels\n\n", + "'err', 'warn', 'notice', 'info', 'debug'", + "'normal', 'access', 'freq', 'sync', 'edge'", + "'internal', 'external', 'gps'", + "'laurent4', 'laurent2', 'laurent1', 'nco'" + ); +} + +static void print_config(Config &config) +{ + const map modMap = { + { MOD_LAURENT4, "Laurent-4" }, + { MOD_LAURENT2, "Laurent-2" }, + { MOD_LAURENT1, "Laurent-1" }, + { MOD_NCO, "NCO" }, + }; + + const map burstMap = { + { BURST_NORMAL, "Normal" }, + { BURST_ACCESS, "Access" }, + { BURST_FREQ, "Frequency" }, + { BURST_SYNC, "Synchronization" }, + { BURST_EDGE, "EDGE" }, + }; + + const map refMap = { + { RadioDevice::REF_INTERNAL, "Internal" }, + { RadioDevice::REF_EXTERNAL, "External" }, + { RadioDevice::REF_GPS, "GPS" }, + }; + + auto yesno = [](bool x) { return x ? "yes" : "no"; }; + + ostringstream ost(""); + ost << "Config Settings" << endl; + ost << " Log level............... " << config.logl << std::endl; + ost << " Device args............. " << "\"" << config.args << "\"" << endl; + ost << " Samples-per-Symbol...... " << config.sps << endl; + ost << " RF frequency............ " << config.freq/1e9 << " GHz" << endl; + ost << " RF gain................. " << config.gain << " dB" << endl; + ost << " Reference............... " << refMap.at(config.ref) << endl; + ost << " Burst type.............. " << burstMap.at(config.burst) << endl; + ost << " Modulator type.......... " << modMap.at(config.mod) << endl; + ost << " Baseband offset......... " << config.offset/1e6 << " MHz" << endl; + ost << " Swap channels........... " << yesno(config.swap) << endl; + cout << ost << endl; +} + +static bool handle_options(int argc, char **argv, Config &config) +{ + int option; + + const struct option longopts[] = { + { "help", 0, nullptr, 'h' }, + { "log", 1, nullptr, 'l' }, + { "args", 1, nullptr, 'a' }, + { "ref" , 1, nullptr, 'r' }, + { "freq", 1, nullptr, 'f' }, + { "gain", 1, nullptr, 'g' }, + { "mod", 1, nullptr, 'm' }, + { "offset", 1, nullptr, 'o' }, + { "sps", 1, nullptr, 's' }, + { "ampl", 1, nullptr, 'p' }, + { "tsc", 1, nullptr, 'r' }, + { "burst", 1, nullptr, 'b' }, + { "swap", 1, nullptr, 'w' }, + }; + + const map logMap = { + { "emerg", "EMERG" }, + { "EMERG", "EMERG" }, + { "alert", "ALERT" }, + { "ALERT", "ALERT" }, + { "err", "ERR" }, + { "ERR", "ERR" }, + { "warn", "WARNING" }, + { "WARN", "WARNING" }, + { "notice", "NOTICE" }, + { "NOTICE", "NOTICE" }, + { "info", "INFO" }, + { "INFO", "INFO" }, + { "debug", "DEBUG" }, + { "DEBUG", "DEBUG" }, + }; + + const map modMap = { + { "laurent4", MOD_LAURENT4 }, + { "laurent2", MOD_LAURENT2 }, + { "laurent1", MOD_LAURENT1 }, + { "nco", MOD_NCO }, + }; + + const map burstMap = { + { "normal", BURST_NORMAL }, + { "access", BURST_ACCESS }, + { "freq", BURST_FREQ }, + { "sync", BURST_SYNC }, + { "edge", BURST_EDGE }, + }; + + const map refMap = { + { "internal", RadioDevice::REF_INTERNAL }, + { "external", RadioDevice::REF_EXTERNAL }, + { "gpsdo", RadioDevice::REF_GPS }, + { "gps", RadioDevice::REF_GPS }, + }; + + while ((option = getopt_long(argc, argv, "ha:l:r:f:g:m:o:s:p:t:b:w", longopts, nullptr)) != -1) { + switch (option) { + case 'a': + config.args = optarg; + break; + case 'f': + config.freq = atof(optarg); + break; + case 'g': + config.gain = atof(optarg); + break; + case 'o': + config.offset = atof(optarg); + break; + case 's': + if (atoi(optarg) != 4) { + printf("Unsupported SPS = %i\n", atoi(optarg)); + return false; + } + break; + case 'p': + config.ampl = atof(optarg); + break; + case 't': + if (atoi(optarg) < TSC0 || atoi(optarg) > TSC7) { + printf("Invalid training sequence %i", atoi(optarg)); + return false; + } + config.tsc = static_cast(atoi(optarg)); + break; + case 'w': + config.swap = true; + break; + case 'l': + if (logMap.count(optarg) > 0) { + config.logl = logMap.at(optarg); + } else { + printf("Invalid log parameter '%s'\n\n", optarg); + return false; + } + break; + case 'r': + if (refMap.count(optarg) > 0) { + config.ref = refMap.at(optarg); + } else { + printf("Invalid reference parameter '%s'\n\n", optarg); + return false; + } + break; + case 'm': + if (modMap.count(optarg) > 0) { + config.mod = modMap.at(optarg); + } else { + printf("Invalid modulation parameter '%s'\n\n", optarg); + return false; + } + break; + case 'b': + if (burstMap.count(optarg) > 0) { + config.burst = burstMap.at(optarg); + } else { + printf("Invalid burst type parameter '%s'\n\n", optarg); + return false; + } + break; + case 'h': + default: + return false; + } + } + + return true; +} + +int main(int argc, char **argv) +{ + Config config; + if (!handle_options(argc, argv, config)) { + print_help(); + return -EINVAL; + } + + print_config(config); + + gLogInit("osmo-siggen", config.logl.c_str(), LOG_LOCAL7); + + convolve_init(); + convert_init(); + sigProcLibSetup(); + + /* Device setup */ + shared_ptr usrp(RadioDevice::make(config.sps, config.sps, RadioDevice::NORMAL, 1, config.offset)); + usrp->open(config.args, config.ref, config.swap); + usrp->setTxFreq(config.freq); + usrp->setTxGain(config.gain); + usrp->start(true); + usrp->setPriority(0.5); + + /* Bind all burst-modulator configurations */ + auto makeBurstGenerator = [&config]()->function()> { + switch (config.burst) { + case BURST_EDGE: return bind(generateEDGEBurst, config.tsc); + case BURST_ACCESS: return bind(generateRABurst, config.mod); + case BURST_FREQ: return bind(generateFreqBurst, config.mod); + case BURST_SYNC: return bind(generateSyncBurst, config.mod); + case BURST_NORMAL: + default: return bind(generateNormalBurst, config.tsc, config.mod); + } + }; + + auto burstGenerator = makeBurstGenerator(); + auto ts = usrp->initialWriteTimestamp(); + auto frameTrigger = []() { + static int tn = 0; + return ++tn % 8 == 0; + }; + + while (1) { + try { + if (frameTrigger()) usrp->triggerGPIO(ts); + sendBurst(usrp, ts, burstGenerator(), config.ampl); + } catch (const exception &e) { + cout << e.what() << endl; + break; + } + } + + sigProcLibDestroy(); +} diff --git a/Transceiver52M/radioDevice.h b/Transceiver52M/radioDevice.h index 3624c58..d2e349d 100644 --- a/Transceiver52M/radioDevice.h +++ b/Transceiver52M/radioDevice.h @@ -58,7 +58,7 @@ class RadioDevice { virtual ~RadioDevice() { } /** Start the USRP */ - virtual bool start()=0; + virtual bool start(bool txOnly = false)=0; /** Stop the USRP */ virtual bool stop()=0; @@ -91,6 +91,7 @@ class RadioDevice { @param isControl Set if data is a control packet, e.g. a ping command @return The number of samples actually written */ + virtual void triggerGPIO(TIMESTAMP timestamp) = 0; virtual int writeSamples(std::vector &bufs, int len, bool *underrun, TIMESTAMP timestamp, bool isControl = false) = 0; diff --git a/Transceiver52M/sigProcLib.cpp b/Transceiver52M/sigProcLib.cpp index 3a9a529..7176ce2 100644 --- a/Transceiver52M/sigProcLib.cpp +++ b/Transceiver52M/sigProcLib.cpp @@ -64,7 +64,7 @@ static signalVector *GMSKReverseRotation1 = NULL; /* Precomputed fractional delay filters */ static signalVector *delayFilters[DELAYFILTS]; -static const Complex psk8_table[8] = { +extern const Complex psk8_table[8] = { Complex(-0.70710678, 0.70710678), Complex( 0.0, -1.0), Complex( 0.0, 1.0), @@ -110,8 +110,9 @@ struct CorrelationSequence { * for SSE instructions. */ struct PulseSequence { - PulseSequence() : c0(NULL), c1(NULL), c0_inv(NULL), empty(NULL), - c0_buffer(NULL), c1_buffer(NULL), c0_inv_buffer(NULL) + PulseSequence() : c0(NULL), c1(NULL), c2(NULL), c3(NULL), c0_inv(NULL), empty(NULL), + c0_buffer(NULL), c1_buffer(NULL), c2_buffer(NULL), + c3_buffer(NULL), c0_inv_buffer(NULL), g(NULL) { } @@ -119,19 +120,29 @@ struct PulseSequence { { delete c0; delete c1; + delete c2; + delete c3; delete c0_inv; delete empty; + delete g; free(c0_buffer); free(c1_buffer); + free(c2_buffer); + free(c3_buffer); } signalVector *c0; signalVector *c1; + signalVector *c2; + signalVector *c3; signalVector *c0_inv; signalVector *empty; void *c0_buffer; void *c1_buffer; + void *c2_buffer; + void *c3_buffer; void *c0_inv_buffer; + signalVector *g; }; static CorrelationSequence *gMidambles[] = {NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL}; @@ -442,6 +453,121 @@ static bool generateInvertC0Pulse(PulseSequence *pulse) return true; } +static bool generateGPulse(int sps, PulseSequence *pulse) +{ + int len; + + if (!pulse) + return false; + + switch (sps) { + case 4: + len = 12; + break; + default: + return false; + } + + pulse->g = new signalVector(len); + pulse->g->isReal(true); + + /* Enable alignment for SSE usage */ + pulse->c3->setAligned(true); + + signalVector::iterator xP = pulse->g->begin(); + + switch (sps) { + case 4: + *xP++ = 9.36941412e-03; + *xP++ = 3.08922969e-02; + *xP++ = 7.76167091e-02; + *xP++ = 1.50953651e-01; + *xP++ = 2.31509315e-01; + *xP++ = 2.85056778e-01; + *xP++ = 2.85056778e-01; + *xP++ = 2.31509315e-01; + *xP++ = 1.50953651e-01; + *xP++ = 7.76167091e-02; + *xP++ = 3.08922969e-02; + *xP++ = 9.36941412e-03; + break; + } + + return true; +} + +static bool generateC3Pulse(int sps, PulseSequence *pulse) +{ + int len; + + if (!pulse) + return false; + + switch (sps) { + case 4: + len = 4; + break; + default: + return false; + } + + pulse->c3_buffer = convolve_h_alloc(len); + pulse->c3 = new signalVector((complex *) pulse->c3_buffer, 0, len); + pulse->c3->isReal(true); + + /* Enable alignment for SSE usage */ + pulse->c3->setAligned(true); + + signalVector::iterator xP = pulse->c3->begin(); + + switch (sps) { + case 4: + /* BT = 0.30 */ + *xP++ = 0.0; + *xP++ = 9.66809925e-04; + *xP++ = 1.14560468e-03; + *xP++ = 5.28599308e-04; + } + + return true; +} + +static bool generateC2Pulse(int sps, PulseSequence *pulse) +{ + int len; + + if (!pulse) + return false; + + switch (sps) { + case 4: + len = 4; + break; + default: + return false; + } + + pulse->c2_buffer = convolve_h_alloc(len); + pulse->c2 = new signalVector((complex *) pulse->c2_buffer, 0, len); + pulse->c2->isReal(true); + + /* Enable alignment for SSE usage */ + pulse->c2->setAligned(true); + + signalVector::iterator xP = pulse->c2->begin(); + + switch (sps) { + case 4: + /* BT = 0.30 */ + *xP++ = 0.0; + *xP++ = 5.28599308e-04; + *xP++ = 1.14560468e-03; + *xP++ = 9.66809925e-04; + } + + return true; +} + static bool generateC1Pulse(int sps, PulseSequence *pulse) { int len; @@ -540,6 +666,9 @@ static PulseSequence *generateGSMPulse(int sps) *xP++ = 2.84385729e-02; *xP++ = 4.46348606e-03; generateC1Pulse(sps, pulse); + generateC2Pulse(sps, pulse); + generateC3Pulse(sps, pulse); + generateGPulse(sps, pulse); } else { center = (float) (len - 1.0) / 2.0; @@ -617,12 +746,186 @@ static void rotateBurst2(signalVector &burst, double phase) burst[i] = burst[i] * rot; } +signalVector *modulateBurstNCO(const BitVector &bits) +{ + auto sps = 4; + auto burst = signalVector(625); + auto it = burst.begin(); + burst.isReal(true); + + /* Leading differential bit */ + *it = 2.0 * (bits[0] & 0x01) - 1.0; + it += sps; + + /* Main burst bits */ + for (size_t i = 1; i < bits.size(); i++) { + *it = 2.0 * ((bits[i-1] & 0x01) ^ (bits[i] & 0x01)) - 1.0; + it += sps; + } + + /* Trailing differential bit */ + *it = 2.0 * (bits[bits.size()-1] & 0x01) - 1.0; + + auto shaped = convolve(&burst, GSMPulse4->g, NULL, START_ONLY); + auto rotate = new signalVector(shaped->size()); + + auto itr = rotate->begin(); + auto its = shaped->begin(); + double accum = 0.0; + while (itr != rotate->end()) { + *itr++ = Complex(cos(accum), sin(accum)); + accum -= its++->real(); + } + + /* + * Hack off guard interval at start and end + * These values make E4406A happy with TSC detection + */ + itr = rotate->end() - 25; + while (itr != rotate->end()) + *itr++ = Complex(0, 0); + + itr = rotate->begin(); + while (itr != rotate->begin() + 1) + *itr++ = Complex(0, 0); + + delete shaped; + return rotate; +} + /* * Ignore the guard length argument in the GMSK modulator interface * because it results in 624/628 sized bursts instead of the preferred * burst length of 625. Only 4 SPS is supported. */ -static signalVector *modulateBurstLaurent(const BitVector &bits) +signalVector *modulateBurstLaurent4(const BitVector &bits) +{ + int burst_len, sps = 4; + float phase; + signalVector *c0_pulse, *c1_pulse, *c2_pulse, *c3_pulse, + *c0_shaped, *c1_shaped, *c2_shaped, *c3_shaped; + signalVector::iterator c0_itr, c1_itr, c2_itr, c3_itr; + + c0_pulse = GSMPulse4->c0; + c1_pulse = GSMPulse4->c1; + c2_pulse = GSMPulse4->c2; + c3_pulse = GSMPulse4->c3; + + if (bits.size() > 156) + return NULL; + + burst_len = 625; + + auto c0_burst = signalVector(burst_len, c0_pulse->size()); + auto c1_burst = signalVector(burst_len, c1_pulse->size()); + auto c2_burst = signalVector(burst_len, c2_pulse->size()); + auto c3_burst = signalVector(burst_len, c3_pulse->size()); + + c0_itr = c0_burst.begin(); + c1_itr = c1_burst.begin(); + c2_itr = c2_burst.begin(); + c3_itr = c3_burst.begin(); + + /* Padded differential tail bits */ + *c0_itr = 2.0 * (0x00 & 0x01) - 1.0; + c0_itr += sps; + + /* Main burst bits */ + for (unsigned i = 0; i < bits.size(); i++) { + *c0_itr = 2.0 * (bits[i] & 0x01) - 1.0; + c0_itr += sps; + } + + /* Padded differential tail bits */ + *c0_itr = 2.0 * (0x00 & 0x01) - 1.0; + + /* Generate C0 phase coefficients */ + GMSKRotate(c0_burst, sps); + c0_burst.isReal(false); + + /* Generate C1, C2, C3 phase coefficients */ + c0_itr = c0_burst.begin(); + c0_itr += sps * 2; + c1_itr += sps * 2; + c2_itr += sps * 2; + c3_itr += sps * 2; + + /* Bit 0 */ + auto p1 = bits[0] & 0x01; + auto p2 = 0; + auto p3 = p1 ^ p2; + + *c1_itr = *c0_itr * Complex(0, 2.0 * p1 - 1.0); + *c2_itr = *c0_itr * Complex(0, 2.0 * p2 - 1.0); + *c3_itr = *c0_itr * Complex(2.0 * p3 - 1.0, 0); + + c0_itr += sps; + c1_itr += sps; + c2_itr += sps; + c3_itr += sps; + + /* Bit 1 */ + p1 = (bits[1] & 0x01) ^ (bits[0] & 0x01); + p2 = (bits[0] & 0x01); + p3 = p1 ^ p2; + + *c1_itr = *c0_itr * Complex(0, 2.0 * p1 - 1.0); + *c2_itr = *c0_itr * Complex(0, 2.0 * p2 - 1.0); + *c3_itr = *c0_itr * Complex(2.0 * p3 - 1.0, 0); + + c0_itr += sps; + c1_itr += sps; + c2_itr += sps; + c3_itr += sps; + + /* Bit 2 - end */ + for (size_t i = 3; i < bits.size(); i++) { + p1 = (bits[i-1] & 0x01) ^ (bits[i-2] & 0x01); + p2 = (bits[i-2] & 0x01) ^ (bits[i-3] & 0x01); + p3 = p1 ^ p2; + + *c1_itr = *c0_itr * Complex(0, 2.0 * p1 - 1.0); + *c2_itr = *c0_itr * Complex(0, 2.0 * p2 - 1.0); + *c3_itr = *c0_itr * Complex(2.0 * p3 - 1.0, 0); + + c0_itr += sps; + c1_itr += sps; + c2_itr += sps; + c3_itr += sps; + } + + /* Residual bits (unfinished) */ + int i = bits.size(); + phase = 2.0 * ((bits[i-1] & 0x01) ^ (bits[i-2] & 0x01)) - 1.0; + *c1_itr = *c0_itr * Complex(0, phase); + + /* Pulse shape all component functions */ + c0_shaped = convolve(&c0_burst, c0_pulse, NULL, START_ONLY); + c1_shaped = convolve(&c1_burst, c1_pulse, NULL, START_ONLY); + c2_shaped = convolve(&c2_burst, c2_pulse, NULL, START_ONLY); + c3_shaped = convolve(&c3_burst, c3_pulse, NULL, START_ONLY); + + /* Combine shaped outputs into C0 */ + c0_itr = c0_shaped->begin(); + c1_itr = c1_shaped->begin(); + c2_itr = c2_shaped->begin(); + c3_itr = c3_shaped->begin(); + for (unsigned i = 0; i < c0_shaped->size(); i++ ) + *c0_itr++ += *c1_itr++ + *c2_itr++ + *c3_itr++; + + delete c1_shaped; + delete c2_shaped; + delete c3_shaped; + + return c0_shaped; +} + +/* + * Ignore the guard length argument in the GMSK modulator interface + * because it results in 624/628 sized bursts instead of the preferred + * burst length of 625. Only 4 SPS is supported. + */ +signalVector *modulateBurstLaurent2(const BitVector &bits) { int burst_len, sps = 4; float phase; @@ -704,6 +1007,41 @@ static signalVector *modulateBurstLaurent(const BitVector &bits) return c0_shaped; } +signalVector *modulateBurstLaurent1(const BitVector &bits) +{ + if (bits.size() > 156) return NULL; + + int sps = 4; + auto burst = signalVector(625, GSMPulse4->c0->size()); + auto itr = burst.begin(); + burst.isReal(true); + + /* Padded differential tail bits */ + *itr = -1.0; + itr += sps; + + /* Main burst bits */ + for (unsigned i = 0; i < bits.size(); i++) { + *itr = 2.0 * (bits[i] & 0x01) - 1.0; + itr += sps; + } + + /* Padded differential tail bits */ + *itr = -1.0; + + /* Generate C0 phase coefficients */ + GMSKRotate(burst, sps); + burst.isReal(false); + + /* Pulse shaping */ + return convolve(&burst, GSMPulse4->c0, NULL, START_ONLY); +} + +signalVector *modulateBurstLaurent(const BitVector &bits) +{ + return modulateBurstLaurent2(bits); +} + static signalVector *rotateEdgeBurst(const signalVector &symbols, int sps) { signalVector *burst; @@ -771,7 +1109,7 @@ static signalVector *mapEdgeSymbols(const BitVector &bits) * pulse filter combination of the GMSK Laurent represenation whereas 8-PSK * uses a single pulse linear filter. */ -static signalVector *shapeEdgeBurst(const signalVector &symbols) +signalVector *shapeEdgeBurst(const signalVector &symbols) { size_t nsyms, nsamps = 625, sps = 4; signalVector *burst, *shape; diff --git a/Transceiver52M/sigProcLib.h b/Transceiver52M/sigProcLib.h index 9bc7e10..e3aa932 100644 --- a/Transceiver52M/sigProcLib.h +++ b/Transceiver52M/sigProcLib.h @@ -65,12 +65,18 @@ signalVector *modulateBurst(const BitVector &wBurst, int guardPeriodLength, int sps, bool emptyPulse = false); +signalVector *modulateBurstLaurent4(const BitVector &wBurst); +signalVector *modulateBurstLaurent2(const BitVector &wBurst); +signalVector *modulateBurstLaurent1(const BitVector &wBurst); +signalVector *modulateBurstNCO(const BitVector &wBurst); + /** 8-PSK modulate a burst of bits */ signalVector *modulateEdgeBurst(const BitVector &bits, int sps, bool emptyPulse = false); /** Generate a EDGE burst with random payload - 4 SPS (625 samples) only */ signalVector *generateEdgeBurst(int tsc); +signalVector *shapeEdgeBurst(const signalVector &symbols); /** Generate an empty burst - 4 or 1 SPS */ signalVector *generateEmptyBurst(int sps, int tn); -- cgit v1.2.3