diff --git a/.gitignore b/.gitignore index 876c50b..df84c8b 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,7 @@ gps-sdr-sim-lut # Temporary files *.swp + *~ # Byte-compiled / optimized / DLL files __pycache__/ @@ -177,3 +178,10 @@ dmypy.json # Cython debug symbols cython_debug/ + + +# Netbeans project folder +nbproject/* + +# Last used USER_MOTION_SIZE variable +.user-motion-size diff --git a/Makefile b/Makefile index 439c022..98f5fdb 100644 --- a/Makefile +++ b/Makefile @@ -5,16 +5,40 @@ all: gps-sdr-sim SHELL=/bin/bash CC=gcc -CFLAGS=-O3 -Wall LDFLAGS=-lm -lpthread +CFLAGS=-O3 -Wall -D_FILE_OFFSET_BITS=64 +ifdef USER_MOTION_SIZE +CFLAGS+=-DUSER_MOTION_SIZE=$(USER_MOTION_SIZE) +endif + gps-sdr-sim: gpssim.o ${CC} $< ${LDFLAGS} -o $@ +gpssim.o: .user-motion-size gpssim.h + +.user-motion-size: .FORCE + @if [ -f .user-motion-size ]; then \ + if [ "`cat .user-motion-size`" != "$(USER_MOTION_SIZE)" ]; then \ + echo "Updating .user-motion-size"; \ + echo "$(USER_MOTION_SIZE)" >| .user-motion-size; \ + fi; \ + else \ + echo "$(USER_MOTION_SIZE)" > .user-motion-size; \ + fi; + clean: - rm -f gpssim.o gps-sdr-sim *.bin + rm -f gpssim.o gps-sdr-sim *.bin .user-motion-size time: gps-sdr-sim time ./gps-sdr-sim -e brdc3540.14n -u circle.csv -b 1 time ./gps-sdr-sim -e brdc3540.14n -u circle.csv -b 8 time ./gps-sdr-sim -e brdc3540.14n -u circle.csv -b 16 + +.FORCE: + +YEAR?=$(shell date +"%Y") +Y=$(patsubst 20%,%,$(YEAR)) +%.$(Y)n: + wget -q ftp://cddis.gsfc.nasa.gov/gnss/data/daily/$(YEAR)/brdc/$@.Z -O $@.Z + uncompress $@.Z diff --git a/README.md b/README.md index ba536b3..079a1f1 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ GPS-SDR-SIM generates GPS baseband signal data streams, which can be converted to RF using software-defined radio (SDR) platforms, such as -[bladeRF](http://nuand.com/), [HackRF](https://github.com/mossmann/hackrf/wiki), and [USRP](http://www.ettus.com/). +[ADALM-Pluto](https://wiki.analog.com/university/tools/pluto), [bladeRF](http://nuand.com/), [HackRF](https://github.com/mossmann/hackrf/wiki), and [USRP](http://www.ettus.com/). ### Windows build instructions @@ -18,6 +18,7 @@ to RF using software-defined radio (SDR) platforms, such as $ gcc gpssim.c -lm -lpthread -O3 -o gps-sdr-sim ``` + ### Realtime by Gnuradio Use -n option can connect to a TCP source in Gnuradio for realtime simulation. @@ -36,11 +37,7 @@ cd into /mapserver, and run python mapper.py ``` -Then visit http://127.0.0.1:8080/static/baidumap.html to use the baidu Online map. - - - -Actually I don't want to use BaiduMap.. But because of the GFW, I can't access Google.. +Then visit http://127.0.0.1:8080/static/baidumap.html to use the Online map. You can write an map which can POST data to http://127.0.0.1:8080/post like this @@ -48,7 +45,24 @@ You can write an map which can POST data to http://127.0.0.1:8080/post like this lon=116&lat=39&hgt=10 ``` -to replace Baidumap. + + +### Using bigger user motion files + +In order to use user motion files with more than 30000 samples (at 10Hz), the `USER_MOTION_SIZE` +variable can be set to the maximum time of the user motion file in seconds. It is advisable to do +this using make so gps-sdr-bin can update the size when needed. e.g: + +``` +$ make USER_MOTION_SIZE=4000 +``` + +This variable can also be set when compiling directly with GCC: + +``` +$ gcc gpssim.c -lm -O3 -o gps-sdr-sim -DUSER_MOTION_SIZE=4000 +``` + ### Generating the GPS signal file @@ -58,20 +72,20 @@ The sampling rate of the user motion has to be 10Hz. The user is also able to assign a static location directly through the command line. The user specifies the GPS satellite constellation through a GPS broadcast -ephemeris file. The daily GPS broadcast ephemers file (brdc) is a merge of the -individual site navigation files into one. The archive for the daily file is: - -[ftp://cddis.gsfc.nasa.gov/gnss/data/daily/](ftp://cddis.gsfc.nasa.gov/gnss/data/daily/) +ephemeris file. The daily GPS broadcast ephemeris file (brdc) is a merge of the +individual site navigation files into one. The archive for the daily file can +be downloaded from: https://cddis.nasa.gov/archive/gnss/data/daily/. Access +to this site requires registration, which is free. These files are then used to generate the simulated pseudorange and Doppler for the GPS satellites in view. This simulated range data is then used to generate the digitized I/Q samples for the GPS signal. -The bladeRF command line interface requires I/Q pairs stored as signed +The bladeRF and ADALM-Pluto command line interface requires I/Q pairs stored as signed 16-bit integers, while the hackrf_transfer and gps-sdr-sim-uhd.py support signed bytes. -HackRF and bladeRF require 2.6 MHz sample rate, while the USRP2 requires +HackRF, bladeRF and ADALM-Pluto require 2.6 MHz sample rate, while the USRP2 requires 2.5 MHz (an even integral decimator of 100 MHz). The simulation start time can be specified if the corresponding set of ephemerides @@ -92,11 +106,12 @@ Options: -e RINEX navigation file for GPS ephemerides (required) -u User motion file (dynamic mode) -g NMEA GGA stream (dynamic mode) + -c ECEF X,Y,Z in meters (static mode) e.g. 3967283.15,1022538.18,4872414.48 -l Lat,Lon,Hgt (static mode) e.g. 30.286502,120.032669,100 -t Scenario start time YYYY/MM/DD,hh:mm:ss -T Overwrite TOC and TOE to scenario start time -d Duration [sec] (dynamic mode max: 300 static mode max: 86400) - -o I/Q sampling data file (default: gpssim.bin) + -o I/Q sampling data file (default: gpssim.bin ; use - for stdout) -s Sampling frequency [Hz] (default: 2600000) -b I/Q data format [1/8/16] (default: 16) -i Disable ionospheric delay for spacecraft scenario @@ -130,6 +145,8 @@ Use Gnuradio to realtime simulate 3000s: The TX port of a particular SDR platform is connected to the GPS receiver under test through a DC block and a fixed 50-60dB attenuator. +#### BladeRF: + The simulated GPS signal file, named "gpssim.bin", can be loaded into the bladeRF for playback as shown below: @@ -145,25 +162,50 @@ tx start ``` You can also execute these commands via the `bladeRF-cli` script option as below: - ``` > bladeRF-cli -s bladerf.script ``` -For the HackRF: +#### HackRF: ``` > hackrf_transfer -t gpssim.bin -f 1575420000 -s 2600000 -a 1 -x 0 ``` -For UHD supported devices (tested with USRP2 only): +#### UHD supported devices (tested with USRP2 only): ``` > gps-sdr-sim-uhd.py -t gpssim.bin -s 2500000 -x 0 ``` +#### LimeSDR (in case of 1 Msps 1-bit file, to get full BaseBand dynamic and low RF power): + +``` +> limeplayer -s 1000000 -b 1 -d 2047 -g 0.1 < ../circle.1b.1M.bin +``` + +#### ADALM-Pluto (PlutoSDR): + +The ADALM-Pluto device is expected to have its network interface up and running and is accessible +via "pluto.local" by default. + +Default settings: +``` +> plutoplayer -t gpssim.bin +``` +Set TX attenuation: +``` +> plutoplayer -t gpssim.bin -a -30.0 +``` +Default -20.0dB. Applicable range 0.0dB to -80.0dB in 0.25dB steps. + +Set RF bandwidth: +``` +> plutoplayer -t gpssim.bin -b 3.0 +``` +Default 3.0MHz. Applicable range 1.0MHz to 5.0MHz. ### License -Copyright © 2015 Takuji Ebinuma +Copyright © 2015-2018 Takuji Ebinuma Distributed under the [MIT License](http://www.opensource.org/licenses/mit-license.php). diff --git a/gps-sdr-sim-uhd.py b/gps-sdr-sim-uhd.py index 47bfc78..0437dd5 100755 --- a/gps-sdr-sim-uhd.py +++ b/gps-sdr-sim-uhd.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # a small script to transmit simulated GPS samples via UHD # (C) 2015 by Harald Welte # Licensed under the MIT License (see LICENSE) @@ -20,16 +20,11 @@ class top_block(gr.top_block): ################################################## # Blocks ################################################## - self.uhd_usrp_sink = uhd.usrp_sink( - ",".join(("", "")), - uhd.stream_args( - cpu_format="fc32", - channels=range(1), - ), - ) + self.uhd_usrp_sink = uhd.usrp_sink(options.args, uhd.stream_args(cpu_format="fc32")) self.uhd_usrp_sink.set_samp_rate(options.sample_rate) self.uhd_usrp_sink.set_center_freq(options.frequency, 0) self.uhd_usrp_sink.set_gain(options.gain, 0) + self.uhd_usrp_sink.set_clock_source(options.clock_source) if options.bits == 16: # a file source for the file generated by the gps-sdr-sim @@ -55,7 +50,13 @@ class top_block(gr.top_block): self.connect((self.blocks_file_source, 0), (self.blocks_char_to_short, 0)) self.connect((self.blocks_char_to_short, 0), (self.blocks_interleaved_short_to_complex, 0)) - self.connect((self.blocks_interleaved_short_to_complex, 0), (self.uhd_usrp_sink, 0)) + # scale complex values to within +/- 1.0 so don't overflow USRP DAC + # scale of 1.0/2**11 will scale it to +/- 0.5 + self.blocks_multiply_const = blocks.multiply_const_vcc((1.0/2**11, )) + + # establish the connections + self.connect((self.blocks_interleaved_short_to_complex, 0), (self.blocks_multiply_const, 0)) + self.connect((self.blocks_multiply_const, 0), (self.uhd_usrp_sink, 0)) def get_options(): parser = OptionParser(option_class=eng_option) @@ -71,11 +72,16 @@ def get_options(): help="set output file name [default=gpssim.bin]") parser.add_option("-b", "--bits", type="eng_float", default=16, help="set size of every sample [default=16]") + parser.add_option("-a", "--args", type="string", default="", + help="set UHD arguments [default='']") + parser.add_option("-c", "--clock_source", type="string", default="internal", + help="set clock source [default='internal']") + (options, args) = parser.parse_args() if len(args) != 0: parser.print_help() - raise SystemExit, 1 + raise SystemExit(1) return (options) @@ -83,6 +89,6 @@ if __name__ == '__main__': (options) = get_options() tb = top_block(options) tb.start() - raw_input('Press Enter to quit: ') + input('Press Enter to quit: ') tb.stop() tb.wait() diff --git a/gpssim.c b/gpssim.c index 988d5ff..c37435e 100644 --- a/gpssim.c +++ b/gpssim.c @@ -96,8 +96,8 @@ int allocatedSat[MAX_SAT]; /*! \brief Subtract two vectors of double * \param[out] y Result of subtraction - * \param[in] x1 Minuend of subtracion - * \param[in] x2 Subtrahend of subtracion + * \param[in] x1 Minuend of subtraction + * \param[in] x2 Subtrahend of subtraction */ void subVect(double *y, const double *x1, const double *x2) { @@ -128,7 +128,7 @@ double dotProd(const double *x1, const double *x2) } /* !\brief generate the C/A code sequence for a given Satellite Vehicle PRN - * \param[in] prn PRN nuber of the Satellite Vehicle + * \param[in] prn PRN number of the Satellite Vehicle * \param[out] ca Caller-allocated integer array of 1023 bytes */ void codegen(int *ca, int prn) @@ -220,7 +220,7 @@ void gps2date(const gpstime_t *g, datetime_t *t) return; } -/*! \brief Convert Earth-centered Earth-fixed (ECEF) into Lat/Long/Heighth +/*! \brief Convert Earth-centered Earth-fixed (ECEF) into Lat/Long/Height * \param[in] xyz Input Array of X, Y and Z ECEF coordinates * \param[out] llh Output Array of Latitude, Longitude and Height */ @@ -353,7 +353,7 @@ void ecef2neu(const double *xyz, double t[3][3], double *neu) return; } -/*! \brief Convert North-Eeast-Up to Azimuth + Elevation +/*! \brief Convert North-East-Up to Azimuth + Elevation * \param[in] neu Input position in North-East-Up format * \param[out] azel Output array of azimuth + elevation as double */ @@ -375,7 +375,7 @@ void neu2azel(double *azel, const double *neu) * \param[in] eph Ephemeris data of the satellite * \param[in] g GPS time at which position is to be computed * \param[out] pos Computed position (vector) - * \param[out] vel Computed velociy (vector) + * \param[out] vel Computed velocity (vector) * \param[clk] clk Computed clock */ void satpos(ephem_t eph, gpstime_t g, double *pos, double *vel, double *clk) @@ -667,7 +667,7 @@ void eph2sbf(const ephem_t eph, const ionoutc_t ionoutc, unsigned long sbf[5][N_ } /*! \brief Count number of bits set to 1 - * \param[in] v long word in whihc bits are counted + * \param[in] v long word in which bits are counted * \returns Count of bits set to 1 */ unsigned long countBits(unsigned long v) @@ -730,7 +730,7 @@ unsigned long computeChecksum(unsigned long source, int nib) if (nib) // Non-information bearing bits for word 2 and 10 { /* - Solve bits 23 and 24 to presearve parity check + Solve bits 23 and 24 to preserve parity check with zeros in bits 29 and 30. */ @@ -812,7 +812,7 @@ gpstime_t incGpsTime(gpstime_t g0, double dt) return(g1); } -/*! \brief Read Ephemersi data from the RINEX Navigation file */ +/*! \brief Read Ephemeris data from the RINEX Navigation file */ /* \param[out] eph Array of Output SV ephemeris data * \param[in] fname File name of the RINEX file * \returns Number of sets of ephemerides in the file @@ -1514,7 +1514,7 @@ int generateNavMsg(gpstime_t g, channel_t *chan, int init) // Sanity check if (((chan->dwrd[1])&(0x1FFFFUL<<13)) != ((tow&0x1FFFFUL)<<13)) { - printf("\nWARNING: Invalid TOW in subframe 5.\n"); + fprintf(stderr, "\nWARNING: Invalid TOW in subframe 5.\n"); return(0); } */ @@ -1620,9 +1620,12 @@ int allocateChannel(channel_t *chan, ephem_t *eph, ionoutc_t ionoutc, gpstime_t r_ref = rho.range; phase_ini = (2.0*r_ref - r_xyz)/LAMBDA_L1; +#ifdef FLOAT_CARR_PHASE + chan[i].carr_phase = phase_ini - floor(phase_ini); +#else phase_ini -= floor(phase_ini); - chan[i].carr_phase = (unsigned int)(512 * 65536.0 * phase_ini); - + chan[i].carr_phase = (unsigned int)(512.0 * 65536.0 * phase_ini); +#endif // Done. break; } @@ -1648,11 +1651,12 @@ int allocateChannel(channel_t *chan, ephem_t *eph, ionoutc_t ionoutc, gpstime_t void usage(void) { - printf("Usage: gps-sdr-sim [options]\n" + fprintf(stderr, "Usage: gps-sdr-sim [options]\n" "Options:\n" " -e RINEX navigation file for GPS ephemerides (required)\n" " -u User motion file (dynamic mode)\n" " -g NMEA GGA stream (dynamic mode)\n" + " -c ECEF X,Y,Z in meters (static mode) e.g. 3967283.154,1022538.181,4872414.484\n" " -l Lat,Lon,Hgt (static mode) e.g. 35.681298,139.766247,10.0\n" " -t Scenario start time YYYY/MM/DD,hh:mm:ss\n" " -T Overwrite TOC and TOE to scenario start time\n" @@ -1727,7 +1731,7 @@ int main(int argc, char *argv[]) int iduration; int verb; - int timeoverwrite = FALSE; // Overwirte the TOC and TOE in the RINEX file + int timeoverwrite = FALSE; // Overwrite the TOC and TOE in the RINEX file ionoutc_t ionoutc; @@ -1759,7 +1763,7 @@ int main(int argc, char *argv[]) exit(1); } - while ((result=getopt(argc,argv,"e:u:g:l:o:s:b:T:t:d:ivn:w"))!=-1) + while ((result=getopt(argc,argv,"e:u:g:c:l:o:s:b:T:t:d:ivn:w"))!=-1) { switch (result) { @@ -1774,6 +1778,11 @@ int main(int argc, char *argv[]) strcpy(umfile, optarg); nmeaGGA = TRUE; break; + case 'c': + // Static ECEF coordinates input mode + staticLocationMode = TRUE; + sscanf(optarg,"%lf,%lf,%lf",&xyz[0][0],&xyz[0][1],&xyz[0][2]); + break; case 'l': // Static geodetic coordinates input mode // Added by scateu@gmail.com @@ -1781,6 +1790,7 @@ int main(int argc, char *argv[]) sscanf(optarg,"%lf,%lf,%lf",&llh[0],&llh[1],&llh[2]); llh[0] = llh[0] / R2D; // convert to RAD llh[1] = llh[1] / R2D; // convert to RAD + llh2xyz(llh,xyz[0]); // Convert llh to xyz break; case 'o': strcpy(outfile, optarg); @@ -1789,7 +1799,7 @@ int main(int argc, char *argv[]) samp_freq = atof(optarg); if (samp_freq<1.0e6) { - printf("ERROR: Invalid sampling frequency.\n"); + fprintf(stderr, "ERROR: Invalid sampling frequency.\n"); exit(1); } break; @@ -1797,7 +1807,7 @@ int main(int argc, char *argv[]) data_format = atoi(optarg); if (data_format!=SC01 && data_format!=SC08 && data_format!=SC16) { - printf("ERROR: Invalid I/Q data format.\n"); + fprintf(stderr, "ERROR: Invalid I/Q data format.\n"); exit(1); } break; @@ -1819,7 +1829,7 @@ int main(int argc, char *argv[]) t0.sec = (double)gmt->tm_sec; date2gps(&t0, &g0); - + break; } case 't': @@ -1827,7 +1837,7 @@ int main(int argc, char *argv[]) if (t0.y<=1980 || t0.m<1 || t0.m>12 || t0.d<1 || t0.d>31 || t0.hh<0 || t0.hh>23 || t0.mm<0 || t0.mm>59 || t0.sec<0.0 || t0.sec>=60.0) { - printf("ERROR: Invalid date and time.\n"); + fprintf(stderr, "ERROR: Invalid date and time.\n"); exit(1); } t0.sec = floor(t0.sec); @@ -1869,7 +1879,7 @@ int main(int argc, char *argv[]) if (navfile[0]==0) { - printf("ERROR: GPS ephemeris file is not specified.\n"); + fprintf(stderr, "ERROR: GPS ephemeris file is not specified.\n"); exit(1); } @@ -1884,7 +1894,7 @@ int main(int argc, char *argv[]) if (duration<0.0 || (duration>((double)USER_MOTION_SIZE)/10.0 && !staticLocationMode) || (duration>STATIC_MAX_DURATION && staticLocationMode&&usesocket==false)) { - printf("ERROR: Invalid duration.\n"); + fprintf(stderr, "ERROR: Invalid duration.\n"); exit(1); } iduration = (int)(duration*10.0 + 0.5); @@ -1910,12 +1920,12 @@ int main(int argc, char *argv[]) if (numd==-1) { - printf("ERROR: Failed to open user motion / NMEA GGA file.\n"); + fprintf(stderr, "ERROR: Failed to open user motion / NMEA GGA file.\n"); exit(1); } else if (numd==0) { - printf("ERROR: Failed to read user motion / NMEA GGA data.\n"); + fprintf(stderr, "ERROR: Failed to read user motion / NMEA GGA data.\n"); exit(1); } @@ -1927,14 +1937,13 @@ int main(int argc, char *argv[]) { // Static geodetic coordinates input mode: "-l" // Added by scateu@gmail.com - printf("Using static location mode.\n"); - llh2xyz(llh,xyz[0]); // Convert llh to xyz + fprintf(stderr, "Using static location mode.\n"); numd = iduration; } /* - printf("xyz = %11.1f, %11.1f, %11.1f\n", xyz[0][0], xyz[0][1], xyz[0][2]); - printf("llh = %11.6f, %11.6f, %11.1f\n", llh[0]*R2D, llh[1]*R2D, llh[2]); + fprintf(stderr, "xyz = %11.1f, %11.1f, %11.1f\n", xyz[0][0], xyz[0][1], xyz[0][2]); + fprintf(stderr, "llh = %11.6f, %11.6f, %11.1f\n", llh[0]*R2D, llh[1]*R2D, llh[2]); */ //////////////////////////////////////////////////////////// // Read ephemeris @@ -1944,19 +1953,19 @@ int main(int argc, char *argv[]) if (neph==0) { - printf("ERROR: No ephemeris available.\n"); + fprintf(stderr, "ERROR: No ephemeris available.\n"); exit(1); } if ((verb==TRUE)&&(ionoutc.vflg==TRUE)) { - printf(" %12.3e %12.3e %12.3e %12.3e\n", + fprintf(stderr, " %12.3e %12.3e %12.3e %12.3e\n", ionoutc.alpha0, ionoutc.alpha1, ionoutc.alpha2, ionoutc.alpha3); - printf(" %12.3e %12.3e %12.3e %12.3e\n", + fprintf(stderr, " %12.3e %12.3e %12.3e %12.3e\n", ionoutc.beta0, ionoutc.beta1, ionoutc.beta2, ionoutc.beta3); - printf(" %19.11e %19.11e %9d %9d\n", + fprintf(stderr, " %19.11e %19.11e %9d %9d\n", ionoutc.A0, ionoutc.A1, ionoutc.tot, ionoutc.wnt); - printf("%6d\n", ionoutc.dtls); + fprintf(stderr, "%6d\n", ionoutc.dtls); } for (sv=0; sv0) - printf("%02d %6.1f %5.1f %11.1f %5.1f\n", chan[i].prn, + fprintf(stderr, "%02d %6.1f %5.1f %11.1f %5.1f\n", chan[i].prn, chan[i].azel[0]*R2D, chan[i].azel[1]*R2D, chan[i].rho0.d, chan[i].rho0.iono_delay); } @@ -2171,8 +2193,9 @@ int main(int argc, char *argv[]) // Update code phase and data bit counters computeCodePhase(&chan[i], rho, 0.1); - chan[i].carr_phasestep = (int)(512 * 65536.0 * chan[i].f_carr * delt); - +#ifndef FLOAT_CARR_PHASE + chan[i].carr_phasestep = (int)round(512.0 * 65536.0 * chan[i].f_carr * delt); +#endif // Path loss path_loss = 20200000.0/rho.d; @@ -2194,8 +2217,11 @@ int main(int argc, char *argv[]) { if (chan[i].prn>0) { - iTable = (chan[i].carr_phase >> 16) & 511; - +#ifdef FLOAT_CARR_PHASE + iTable = (int)floor(chan[i].carr_phase*512.0); +#else + iTable = (chan[i].carr_phase >> 16) & 0x1ff; // 9-bit index +#endif ip = chan[i].dataBit * chan[i].codeCA * cosTable512[iTable] * gain[i]; qp = chan[i].dataBit * chan[i].codeCA * sinTable512[iTable] * gain[i]; @@ -2223,7 +2249,7 @@ int main(int argc, char *argv[]) chan[i].iword++; /* if (chan[i].iword>=N_DWRD) - printf("\nWARNING: Subframe word buffer overflow.\n"); + fprintf(stderr, "\nWARNING: Subframe word buffer overflow.\n"); */ } @@ -2232,11 +2258,20 @@ int main(int argc, char *argv[]) } } - // Set currnt code chip + // Set current code chip chan[i].codeCA = chan[i].ca[(int)chan[i].code_phase]*2-1; // Update carrier phase +#ifdef FLOAT_CARR_PHASE + chan[i].carr_phase += chan[i].f_carr * delt; + + if (chan[i].carr_phase >= 1.0) + chan[i].carr_phase -= 1.0; + else if (chan[i].carr_phase<0.0) + chan[i].carr_phase += 1.0; +#else chan[i].carr_phase += chan[i].carr_phasestep; +#endif } } @@ -2326,14 +2361,14 @@ int main(int argc, char *argv[]) else allocateChannel(chan, eph[ieph], ionoutc, grx, xyz[0], elvmask); - // Show ditails about simulated channels + // Show details about simulated channels if (verb==TRUE) { - printf("\n"); + fprintf(stderr, "\n"); for (i=0; i0) - printf("%02d %6.1f %5.1f %11.1f %5.1f\n", chan[i].prn, + fprintf(stderr, "%02d %6.1f %5.1f %11.1f %5.1f\n", chan[i].prn, chan[i].azel[0]*R2D, chan[i].azel[1]*R2D, chan[i].rho0.d, chan[i].rho0.iono_delay); } } @@ -2352,13 +2387,13 @@ int main(int argc, char *argv[]) grx = incGpsTime(grx, 0.1); // Update time counter - printf("\rTime into run = %4.1f", subGpsTime(grx, g0)); + fprintf(stderr, "\rTime into run = %4.1f", subGpsTime(grx, g0)); fflush(stdout); } tend = clock(); - printf("\nDone!\n"); + fprintf(stderr, "\nDone!\n"); // Free I/Q buffer free(iq_buff); @@ -2368,7 +2403,7 @@ int main(int argc, char *argv[]) fclose(fp); else sockclose(sockc); // Process time - printf("Process time = %.1f [sec]\n", (double)(tend-tstart)/CLOCKS_PER_SEC); + fprintf(stderr, "Process time = %.1f [sec]\n", (double)(tend-tstart)/CLOCKS_PER_SEC); return(0); } diff --git a/gpssim.h b/gpssim.h index b2332db..1f56d7c 100644 --- a/gpssim.h +++ b/gpssim.h @@ -1,6 +1,8 @@ #ifndef GPSSIM_H #define GPSSIM_H +#define FLOAT_CARR_PHASE // For RKT simulation. Higher computational load, but smoother carrier phase. + #define TRUE (1) #define FALSE (0) #define true (1) @@ -15,7 +17,9 @@ #define MAX_CHAN (16) /*! \brief Maximum number of user motion points */ +#ifndef USER_MOTION_SIZE #define USER_MOTION_SIZE (3000) // max duration at 10Hz +#endif /*! \brief Maximum duration for static mode*/ #define STATIC_MAX_DURATION (86400) // second @@ -160,8 +164,12 @@ typedef struct int ca[CA_SEQ_LEN]; /*< C/A Sequence */ double f_carr; /*< Carrier frequency */ double f_code; /*< Code frequency */ +#ifdef FLOAT_CARR_PHASE + double carr_phase; +#else unsigned int carr_phase; /*< Carrier phase */ int carr_phasestep; /*< Carrier phasestep */ +#endif double code_phase; /*< Code phase */ gpstime_t g0; /*!< GPS time at start */ unsigned long sbf[5][N_DWRD_SBF]; /*!< current subframe */ diff --git a/player/Makefile b/player/Makefile new file mode 100644 index 0000000..0f5fc95 --- /dev/null +++ b/player/Makefile @@ -0,0 +1,28 @@ +DIALECT = -std=c11 +CFLAGS += $(DIALECT) -O3 -g -W -Wall +LIBS = -lm + +CFLAGS += $(shell pkg-config --cflags libbladeRF) +CFLAGS += $(shell pkg-config --cflags libhackrf) +CFLAGS += $(shell pkg-config --cflags libiio libad9361) + +.PHONY: all bladeplayer hackplayer limeplayer plutoplayer clean +all: bladeplayer hackplayer limeplayer plutoplayer + +%.o: %.c *.h + $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ + +bladeplayer: bladeplayer.o $(SDR_OBJ) $(COMPAT) + $(CC) -g -o $@ $^ $(LDFLAGS) $(LIBS) $(shell pkg-config --libs libbladeRF) + +hackplayer: hackplayer.o $(COMPAT) + $(CC) -g -o $@ $^ $(LDFLAGS) $(LIBS) $(shell pkg-config --libs libhackrf) + +limeplayer: limeplayer.c + gcc -O2 -Wall -o limeplayer limeplayer.c -lLimeSuite + +plutoplayer: plutoplayer.o $(COMPAT) + $(CC) -g -o $@ $^ $(LDFLAGS) $(LIBS) $(shell pkg-config --libs libiio libad9361) + +clean: + rm -f *.o bladeplayer hackplayer limeplayer plutoplayer diff --git a/player/README.md b/player/README.md new file mode 100644 index 0000000..a0bbf54 --- /dev/null +++ b/player/README.md @@ -0,0 +1,59 @@ +## How to build the player software on Linux + +### bladeRF + +#### Build and install libbladeRF + +https://github.com/Nuand/bladeRF/wiki/Getting-Started:-Linux + +#### Build bladeplayer + +``` +$ make bladeplayer +``` + +### HackRF One + +#### Build and install libhackrf + +https://github.com/mossmann/hackrf/tree/master/host + +#### Build hackplayer + +``` +$ make hackplayer +``` + +### LimeSDR + +#### Build and install libLimeSuite + +http://wiki.myriadrf.org/Lime_Suite + +#### Build limeplayer + +``` +$ make limeplayer +``` + +### ADALM-Pluto + +#### Build and install libiio + +https://wiki.analog.com/resources/tools-software/linux-software/libiio + +#### Build and insatall libad9361 + +``` +$ git clone https://github.com/analogdevicesinc/libad9361-iio.git +$ cd libad9361-iio +$ cmake ./ +$ make all +$ sudo make install +``` + +#### Build plutoplayer + +``` +$ make plutoplayer +``` \ No newline at end of file diff --git a/player/bladeplayer.c b/player/bladeplayer.c index a04bf09..e202428 100644 --- a/player/bladeplayer.c +++ b/player/bladeplayer.c @@ -7,7 +7,9 @@ #ifdef _WIN32 #include "getopt.h" #else +#include #include +#include #endif #define TX_FREQUENCY 1575420000 @@ -25,291 +27,291 @@ void usage(void) { - fprintf(stderr, "Usage: bladeplayer [options]\n" - " -f I/Q sampling data file (required)\n" - " -b I/Q data format [1/16] (default: 16)\n" - " -g TX VGA1 gain (default: %d)\n", - TX_VGA1); + fprintf(stderr, "Usage: bladeplayer [options]\n" + " -f I/Q sampling data file (required)\n" + " -b I/Q data format [1/16] (default: 16)\n" + " -g TX VGA1 gain (default: %d)\n", + TX_VGA1); - return; + return; } int main(int argc, char *argv[]) { - int status; - char *devstr = NULL; - struct bladerf *dev = NULL; + int status; + char *devstr = NULL; + struct bladerf *dev = NULL; - FILE *fp; - int16_t *tx_buffer; - enum state {INIT, READ_FILE, PAD_TRAILING, DONE}; - enum state state = INIT; + FILE *fp; + int16_t *tx_buffer; + enum state {INIT, READ_FILE, PAD_TRAILING, DONE}; + enum state state = INIT; - int compressed = 0; - uint8_t *read_buffer; - size_t samples_read; - int16_t lut[256][8]; - int16_t amp = AMPLITUDE; - int i,k; + int compressed = 0; + uint8_t *read_buffer; + size_t samples_read; + int16_t lut[256][8]; + int16_t amp = AMPLITUDE; + uint32_t i,k; - int gain = TX_VGA1; - int result; - int data_format; - char txfile[128]; + int gain = TX_VGA1; + int result; + int data_format; + char txfile[128]; - // Empty TX file name - txfile[0] = 0; + // Empty TX file name + txfile[0] = 0; - if (argc<3) { - usage(); - exit(1); - } + if (argc<3) { + usage(); + exit(1); + } - while ((result=getopt(argc,argv,"g:b:f:"))!=-1) - { - switch (result) - { - case 'g': - gain = atoi(optarg); - if (gain>-4 || gain<-35) - { - printf("ERROR: Invalid TX VGA1 gain.\n"); - exit(1); - } - break; - case 'b': - data_format = atoi(optarg); - if (data_format!=1 && data_format!=16) - { - printf("ERROR: Invalid I/Q data format.\n"); - exit(1); - } - else if (data_format==1) - compressed = 1; - break; - case 'f': - strcpy(txfile, optarg); - break; - case ':': - case '?': - usage(); - exit(1); - default: - break; - } - } + while ((result=getopt(argc,argv,"g:b:f:"))!=-1) + { + switch (result) + { + case 'g': + gain = atoi(optarg); + if (gain>-4 || gain<-35) + { + printf("ERROR: Invalid TX VGA1 gain.\n"); + exit(1); + } + break; + case 'b': + data_format = atoi(optarg); + if (data_format!=1 && data_format!=16) + { + printf("ERROR: Invalid I/Q data format.\n"); + exit(1); + } + else if (data_format==1) + compressed = 1; + break; + case 'f': + strcpy(txfile, optarg); + break; + case ':': + case '?': + usage(); + exit(1); + default: + break; + } + } - // Open TX file. - if (txfile[0]==0) - { - printf("ERROR: I/Q sampling data file is not specified.\n"); - exit(1); - } + // Open TX file. + if (txfile[0]==0) + { + printf("ERROR: I/Q sampling data file is not specified.\n"); + exit(1); + } - fp = fopen(txfile, "rb"); + fp = fopen(txfile, "rb"); - if (fp==NULL) { - fprintf(stderr, "ERROR: Failed to open TX file: %s\n", argv[1]); - exit(1); - } + if (fp==NULL) { + fprintf(stderr, "ERROR: Failed to open TX file: %s\n", argv[1]); + exit(1); + } - // Initializing device. - printf("Opening and initializing device...\n"); + // Initializing device. + printf("Opening and initializing device...\n"); - status = bladerf_open(&dev, devstr); - if (status != 0) { - fprintf(stderr, "Failed to open device: %s\n", bladerf_strerror(status)); - goto out; - } + status = bladerf_open(&dev, devstr); + if (status != 0) { + fprintf(stderr, "Failed to open device: %s\n", bladerf_strerror(status)); + goto out; + } - status = bladerf_set_frequency(dev, BLADERF_MODULE_TX, TX_FREQUENCY); - if (status != 0) { - fprintf(stderr, "Faield to set TX frequency: %s\n", bladerf_strerror(status)); - goto out; - } - else { - printf("TX frequency: %u Hz\n", TX_FREQUENCY); - } + status = bladerf_set_frequency(dev, BLADERF_MODULE_TX, TX_FREQUENCY); + if (status != 0) { + fprintf(stderr, "Faield to set TX frequency: %s\n", bladerf_strerror(status)); + goto out; + } + else { + printf("TX frequency: %u Hz\n", TX_FREQUENCY); + } - status = bladerf_set_sample_rate(dev, BLADERF_MODULE_TX, TX_SAMPLERATE, NULL); - if (status != 0) { - fprintf(stderr, "Failed to set TX sample rate: %s\n", bladerf_strerror(status)); - goto out; - } - else { - printf("TX sample rate: %u sps\n", TX_SAMPLERATE); - } + status = bladerf_set_sample_rate(dev, BLADERF_MODULE_TX, TX_SAMPLERATE, NULL); + if (status != 0) { + fprintf(stderr, "Failed to set TX sample rate: %s\n", bladerf_strerror(status)); + goto out; + } + else { + printf("TX sample rate: %u sps\n", TX_SAMPLERATE); + } - status = bladerf_set_bandwidth(dev, BLADERF_MODULE_TX, TX_BANDWIDTH, NULL); - if (status != 0) { - fprintf(stderr, "Failed to set TX bandwidth: %s\n", bladerf_strerror(status)); - goto out; - } - else { - printf("TX bandwidth: %u Hz\n", TX_BANDWIDTH); - } + status = bladerf_set_bandwidth(dev, BLADERF_MODULE_TX, TX_BANDWIDTH, NULL); + if (status != 0) { + fprintf(stderr, "Failed to set TX bandwidth: %s\n", bladerf_strerror(status)); + goto out; + } + else { + printf("TX bandwidth: %u Hz\n", TX_BANDWIDTH); + } - status = bladerf_set_txvga1(dev, gain); - if (status != 0) { - fprintf(stderr, "Failed to set TX VGA1 gain: %s\n", bladerf_strerror(status)); - goto out; - } - else { - printf("TX VGA1 gain: %d dB\n", gain); - } + status = bladerf_set_txvga1(dev, gain); + if (status != 0) { + fprintf(stderr, "Failed to set TX VGA1 gain: %s\n", bladerf_strerror(status)); + goto out; + } + else { + printf("TX VGA1 gain: %d dB\n", gain); + } - status = bladerf_set_txvga2(dev, TX_VGA2); - if (status != 0) { - fprintf(stderr, "Failed to set TX VGA2 gain: %s\n", bladerf_strerror(status)); - goto out; - } - else { - printf("TX VGA2 gain: %d dB\n", TX_VGA2); - } + status = bladerf_set_txvga2(dev, TX_VGA2); + if (status != 0) { + fprintf(stderr, "Failed to set TX VGA2 gain: %s\n", bladerf_strerror(status)); + goto out; + } + else { + printf("TX VGA2 gain: %d dB\n", TX_VGA2); + } - // Application code goes here. - printf("Running...\n"); + // Application code goes here. + printf("Running...\n"); - // Allocate a buffer to hold each block of samples to transmit. - tx_buffer = (int16_t*)malloc(SAMPLES_PER_BUFFER * 2 * sizeof(int16_t)); - - if (tx_buffer == NULL) { - fprintf(stderr, "Failed to allocate TX buffer.\n"); - goto out; - } + // Allocate a buffer to hold each block of samples to transmit. + tx_buffer = (int16_t*)malloc(SAMPLES_PER_BUFFER * 2 * sizeof(int16_t)); - // if compressed - read_buffer = (uint8_t*)malloc(SAMPLES_PER_BUFFER / 4); + if (tx_buffer == NULL) { + fprintf(stderr, "Failed to allocate TX buffer.\n"); + goto out; + } - if (read_buffer == NULL) { - fprintf(stderr, "Failed to allocate read buffer.\n"); - goto out; - } + // if compressed + read_buffer = (uint8_t*)malloc(SAMPLES_PER_BUFFER / 4); - for (i=0; i<256; i++) - { - for (k=0; k<8; k++) - lut[i][k] = ((i>>(7-k))&0x1)?amp:-amp; - } + if (read_buffer == NULL) { + fprintf(stderr, "Failed to allocate read buffer.\n"); + goto out; + } - // Configure the TX module for use with the synchronous interface. - status = bladerf_sync_config(dev, - BLADERF_MODULE_TX, - BLADERF_FORMAT_SC16_Q11, - NUM_BUFFERS, - SAMPLES_PER_BUFFER, - NUM_TRANSFERS, - TIMEOUT_MS); + for (i=0; i<256; i++) + { + for (k=0; k<8; k++) + lut[i][k] = ((i>>(7-k))&0x1)?amp:-amp; + } - if (status != 0) { - fprintf(stderr, "Failed to configure TX sync interface: %s\n", bladerf_strerror(status)); - goto out; - } + // Configure the TX module for use with the synchronous interface. + status = bladerf_sync_config(dev, + BLADERF_MODULE_TX, + BLADERF_FORMAT_SC16_Q11, + NUM_BUFFERS, + SAMPLES_PER_BUFFER, + NUM_TRANSFERS, + TIMEOUT_MS); - // We must always enable the modules *after* calling bladerf_sync_config(). - status = bladerf_enable_module(dev, BLADERF_MODULE_TX, true); - if (status != 0) { - fprintf(stderr, "Failed to enable TX module: %s\n", bladerf_strerror(status)); - goto out; - } + if (status != 0) { + fprintf(stderr, "Failed to configure TX sync interface: %s\n", bladerf_strerror(status)); + goto out; + } - // Keep writing samples while there is more data to send and no failures have occurred. - while (state != DONE && status == 0) { + // We must always enable the modules *after* calling bladerf_sync_config(). + status = bladerf_enable_module(dev, BLADERF_MODULE_TX, true); + if (status != 0) { + fprintf(stderr, "Failed to enable TX module: %s\n", bladerf_strerror(status)); + goto out; + } - int16_t *tx_buffer_current = tx_buffer; - unsigned int buffer_samples_remaining = SAMPLES_PER_BUFFER; + // Keep writing samples while there is more data to send and no failures have occurred. + while (state != DONE && status == 0) { - // if compressed - unsigned int read_samples_remaining = SAMPLES_PER_BUFFER / 4; + int16_t *tx_buffer_current = tx_buffer; + unsigned int buffer_samples_remaining = SAMPLES_PER_BUFFER; - // Keep adding to the buffer until it is full or a failure occurs - while (buffer_samples_remaining > 0 && status == 0 && state != DONE) { - size_t samples_populated = 0; + // if compressed + unsigned int read_samples_remaining = SAMPLES_PER_BUFFER / 4; - switch(state) { - case INIT: - case READ_FILE: - // Read from the input file - if (compressed) - { - int16_t *write_buffer_current = tx_buffer; + // Keep adding to the buffer until it is full or a failure occurs + while (buffer_samples_remaining > 0 && status == 0 && state != DONE) { + size_t samples_populated = 0; - samples_read = fread(read_buffer, - sizeof(uint8_t), - read_samples_remaining, - fp); + switch(state) { + case INIT: + case READ_FILE: + // Read from the input file + if (compressed) + { + int16_t *write_buffer_current = tx_buffer; - samples_populated = samples_read * 4; - buffer_samples_remaining = read_samples_remaining * 4; + samples_read = fread(read_buffer, + sizeof(uint8_t), + read_samples_remaining, + fp); - // Expand compressed data into TX buffer - for (i=0; i - #include #include -#include - +#ifdef _WIN32 #include - #ifdef _WIN64 typedef int64_t ssize_t; #else typedef int32_t ssize_t; #endif - typedef int bool; #define true 1 #define false 0 +#include "getopt.h" +#else +#include +#include +#include +#include +#endif +#include static hackrf_device* device = NULL; @@ -25,199 +28,210 @@ volatile uint32_t byte_count = 0; volatile bool do_exit = false; -static transceiver_mode_t transceiver_mode = TRANSCEIVER_MODE_TX; +//static transceiver_mode_t transceiver_mode = TRANSCEIVER_MODE_TX; #define FD_BUFFER_SIZE (8*1024) #define FREQ_ONE_MHZ (1000000ull) +#ifdef _WIN32 BOOL WINAPI sighandler(int signum) { - if (CTRL_C_EVENT == signum) { - fprintf(stdout, "Caught signal %d\n", signum); - do_exit = true; - return TRUE; - } - return FALSE; + if(CTRL_C_EVENT == signum) { + fprintf(stdout, "Caught signal %d\n", signum); + do_exit = true; + return TRUE; + } + return FALSE; } +#else +static void sighandler(int signum) { + fprintf(stdout, "Caught signal %d\n", signum); + do_exit = true; +} +#endif int tx_callback(hackrf_transfer* transfer) { - size_t bytes_to_read; + size_t bytes_to_read; - if( fd != NULL ) - { - ssize_t bytes_read; - byte_count += transfer->valid_length; - bytes_to_read = transfer->valid_length; - - bytes_read = fread(transfer->buffer, 1, bytes_to_read, fd); - - if (bytes_read != bytes_to_read) { - return -1; // EOF - } else { - return 0; - } - } else { - return -1; - } + if( fd != NULL ) + { + ssize_t bytes_read; + byte_count += transfer->valid_length; + bytes_to_read = transfer->valid_length; + + bytes_read = fread(transfer->buffer, 1, bytes_to_read, fd); + + if (bytes_read != bytes_to_read) { + return -1; // EOF + } else { + return 0; + } + } else { + return -1; + } } static void usage() { - fprintf(stderr, "Usage: hackplayer [options]\n" - " -t Transmit data from file (required)\n"); + fprintf(stderr, "Usage: hackplayer [options]\n" + " -t Transmit data from file (required)\n"); - return; + return; } int main(int argc, char** argv) { - int opt; - int result; - const char* path = NULL; - uint32_t sample_rate_hz = 2600000; - uint32_t baseband_filter_bw_hz = 0; - unsigned int txvga_gain=0; - uint64_t freq_hz = 1575420000; - uint32_t amp_enable = 1; + int opt; + int result; + const char* path = NULL; + uint32_t sample_rate_hz = 2600000; + uint32_t baseband_filter_bw_hz = 0; + unsigned int txvga_gain=0; + uint64_t freq_hz = 1575420000; + uint32_t amp_enable = 1; - while( (opt = getopt(argc, argv, "t:")) != EOF ) - { - result = HACKRF_SUCCESS; - switch( opt ) - { - case 't': - path = optarg; - break; - default: - printf("unknown argument '-%c %s'\n", opt, optarg); - usage(); - return EXIT_FAILURE; - } + while( (opt = getopt(argc, argv, "t:")) != EOF ) + { + result = HACKRF_SUCCESS; + switch( opt ) + { + case 't': + path = optarg; + break; + default: + printf("unknown argument '-%c %s'\n", opt, optarg); + usage(); + return EXIT_FAILURE; + } - if( result != HACKRF_SUCCESS ) { - printf("argument error: '-%c %s' %s (%d)\n", opt, optarg, hackrf_error_name(result), result); - usage(); - return EXIT_FAILURE; - } - } + if( result != HACKRF_SUCCESS ) { + printf("argument error: '-%c %s' %s (%d)\n", opt, optarg, hackrf_error_name(result), result); + usage(); + return EXIT_FAILURE; + } + } - if( path == NULL ) { - printf("specify a path to a file to transmit\n"); - usage(); - return EXIT_FAILURE; - } + if( path == NULL ) { + printf("specify a path to a file to transmit\n"); + usage(); + return EXIT_FAILURE; + } - // Compute default value depending on sample rate - baseband_filter_bw_hz = hackrf_compute_baseband_filter_bw_round_down_lt(sample_rate_hz); + // Compute default value depending on sample rate + baseband_filter_bw_hz = hackrf_compute_baseband_filter_bw_round_down_lt(sample_rate_hz); - result = hackrf_init(); - if( result != HACKRF_SUCCESS ) { - printf("hackrf_init() failed: %s (%d)\n", hackrf_error_name(result), result); - usage(); - return EXIT_FAILURE; - } + result = hackrf_init(); + if( result != HACKRF_SUCCESS ) { + printf("hackrf_init() failed: %s (%d)\n", hackrf_error_name(result), result); + usage(); + return EXIT_FAILURE; + } - result = hackrf_open_by_serial(NULL, &device); - if( result != HACKRF_SUCCESS ) { - printf("hackrf_open() failed: %s (%d)\n", hackrf_error_name(result), result); - usage(); - return EXIT_FAILURE; - } + result = hackrf_open_by_serial(NULL, &device); + if( result != HACKRF_SUCCESS ) { + printf("hackrf_open() failed: %s (%d)\n", hackrf_error_name(result), result); + usage(); + return EXIT_FAILURE; + } - fd = fopen(path, "rb"); - if( fd == NULL ) { - printf("Failed to open file: %s\n", path); - return EXIT_FAILURE; - } + fd = fopen(path, "rb"); + if( fd == NULL ) { + printf("Failed to open file: %s\n", path); + return EXIT_FAILURE; + } - // Change fd buffer to have bigger one to store or read data on/to HDD - result = setvbuf(fd , NULL , _IOFBF , FD_BUFFER_SIZE); - if( result != 0 ) { - printf("setvbuf() failed: %d\n", result); - usage(); - return EXIT_FAILURE; - } + // Change fd buffer to have bigger one to store or read data on/to HDD + result = setvbuf(fd , NULL , _IOFBF , FD_BUFFER_SIZE); + if( result != 0 ) { + printf("setvbuf() failed: %d\n", result); + usage(); + return EXIT_FAILURE; + } - SetConsoleCtrlHandler( (PHANDLER_ROUTINE) sighandler, TRUE ); +#ifdef _WIN32 + SetConsoleCtrlHandler( (PHANDLER_ROUTINE) sighandler, TRUE ); +#else + signal(SIGINT, sighandler); +#endif - printf("call hackrf_sample_rate_set(%.03f MHz)\n", ((float)sample_rate_hz/(float)FREQ_ONE_MHZ)); - result = hackrf_set_sample_rate_manual(device, sample_rate_hz, 1); - if( result != HACKRF_SUCCESS ) { - printf("hackrf_sample_rate_set() failed: %s (%d)\n", hackrf_error_name(result), result); - usage(); - return EXIT_FAILURE; - } + printf("call hackrf_sample_rate_set(%.03f MHz)\n", ((float)sample_rate_hz/(float)FREQ_ONE_MHZ)); + result = hackrf_set_sample_rate_manual(device, sample_rate_hz, 1); + if( result != HACKRF_SUCCESS ) { + printf("hackrf_sample_rate_set() failed: %s (%d)\n", hackrf_error_name(result), result); + usage(); + return EXIT_FAILURE; + } - printf("call hackrf_baseband_filter_bandwidth_set(%.03f MHz)\n", - ((float)baseband_filter_bw_hz/(float)FREQ_ONE_MHZ)); - result = hackrf_set_baseband_filter_bandwidth(device, baseband_filter_bw_hz); - if( result != HACKRF_SUCCESS ) { - printf("hackrf_baseband_filter_bandwidth_set() failed: %s (%d)\n", hackrf_error_name(result), result); - usage(); - return EXIT_FAILURE; - } + printf("call hackrf_baseband_filter_bandwidth_set(%.03f MHz)\n", + ((float)baseband_filter_bw_hz/(float)FREQ_ONE_MHZ)); + result = hackrf_set_baseband_filter_bandwidth(device, baseband_filter_bw_hz); + if( result != HACKRF_SUCCESS ) { + printf("hackrf_baseband_filter_bandwidth_set() failed: %s (%d)\n", hackrf_error_name(result), result); + usage(); + return EXIT_FAILURE; + } - result = hackrf_set_txvga_gain(device, txvga_gain); - result |= hackrf_start_tx(device, tx_callback, NULL); + result = hackrf_set_txvga_gain(device, txvga_gain); + result |= hackrf_start_tx(device, tx_callback, NULL); - if( result != HACKRF_SUCCESS ) { - printf("hackrf_start_?x() failed: %s (%d)\n", hackrf_error_name(result), result); - usage(); - return EXIT_FAILURE; - } + if( result != HACKRF_SUCCESS ) { + printf("hackrf_start_?x() failed: %s (%d)\n", hackrf_error_name(result), result); + usage(); + return EXIT_FAILURE; + } - printf("call hackrf_set_freq(%.03f MHz)\n", ((double)freq_hz/(double)FREQ_ONE_MHZ)); - result = hackrf_set_freq(device, freq_hz); - if( result != HACKRF_SUCCESS ) { - printf("hackrf_set_freq() failed: %s (%d)\n", hackrf_error_name(result), result); - usage(); - return EXIT_FAILURE; - } + printf("call hackrf_set_freq(%.03f MHz)\n", ((double)freq_hz/(double)FREQ_ONE_MHZ)); + result = hackrf_set_freq(device, freq_hz); + if( result != HACKRF_SUCCESS ) { + printf("hackrf_set_freq() failed: %s (%d)\n", hackrf_error_name(result), result); + usage(); + return EXIT_FAILURE; + } - printf("call hackrf_set_amp_enable(%u)\n", amp_enable); - result = hackrf_set_amp_enable(device, (uint8_t)amp_enable); - if( result != HACKRF_SUCCESS ) { - printf("hackrf_set_amp_enable() failed: %s (%d)\n", hackrf_error_name(result), result); - usage(); - return EXIT_FAILURE; - } + printf("call hackrf_set_amp_enable(%u)\n", amp_enable); + result = hackrf_set_amp_enable(device, (uint8_t)amp_enable); + if( result != HACKRF_SUCCESS ) { + printf("hackrf_set_amp_enable() failed: %s (%d)\n", hackrf_error_name(result), result); + usage(); + return EXIT_FAILURE; + } - printf("Stop with Ctrl-C\n"); - while( (hackrf_is_streaming(device) == HACKRF_TRUE) && (do_exit == false) ) { - // Show something? - } + printf("Stop with Ctrl-C\n"); + while( (hackrf_is_streaming(device) == HACKRF_TRUE) && (do_exit == false) ) { + // Show something? + } - result = hackrf_is_streaming(device); - if (do_exit) { - printf("\nUser cancel, exiting...\n"); - } else { - printf("\nExiting... hackrf_is_streaming() result: %s (%d)\n", hackrf_error_name(result), result); - } + result = hackrf_is_streaming(device); + if (do_exit) { + printf("\nUser cancel, exiting...\n"); + } else { + printf("\nExiting... hackrf_is_streaming() result: %s (%d)\n", hackrf_error_name(result), result); + } - if(device != NULL) { - result = hackrf_stop_tx(device); - if( result != HACKRF_SUCCESS ) { - printf("hackrf_stop_tx() failed: %s (%d)\n", hackrf_error_name(result), result); - } else { - printf("hackrf_stop_tx() done\n"); - } + if(device != NULL) { + result = hackrf_stop_tx(device); + if( result != HACKRF_SUCCESS ) { + printf("hackrf_stop_tx() failed: %s (%d)\n", hackrf_error_name(result), result); + } else { + printf("hackrf_stop_tx() done\n"); + } - result = hackrf_close(device); - if( result != HACKRF_SUCCESS ) - { - printf("hackrf_close() failed: %s (%d)\n", hackrf_error_name(result), result); - } else { - printf("hackrf_close() done\n"); - } - - hackrf_exit(); - printf("hackrf_exit() done\n"); - } + result = hackrf_close(device); + if( result != HACKRF_SUCCESS ) + { + printf("hackrf_close() failed: %s (%d)\n", hackrf_error_name(result), result); + } else { + printf("hackrf_close() done\n"); + } - if(fd != NULL) { - fclose(fd); - fd = NULL; - printf("fclose(fd) done\n"); - } + hackrf_exit(); + printf("hackrf_exit() done\n"); + } - printf("exit\n"); - return EXIT_SUCCESS; + if(fd != NULL) { + fclose(fd); + fd = NULL; + printf("fclose(fd) done\n"); + } + + printf("exit\n"); + return EXIT_SUCCESS; } diff --git a/player/limeplayer.c b/player/limeplayer.c new file mode 100644 index 0000000..d272d4b --- /dev/null +++ b/player/limeplayer.c @@ -0,0 +1,401 @@ +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define EXIT_CODE_CONTROL_C (-3) +#define EXIT_CODE_NO_DEVICE (-2) +#define EXIT_CODE_LMS_OPEN (-1) + +#define TX_FREQUENCY 1575420000.0 +#define TX_SAMPLERATE 2500000.0 +#define TX_BANDWIDTH 5000000.0 +#define DEFAULT_ANTENNA 1 // antenna with BW [30MHz .. 2000MHz] + +#define STRINGIFY2(X) #X +#define STRINGIFY(X) STRINGIFY2(X) + +static int control_c_received = 0; + +static void control_c_handler (int sig, siginfo_t *siginfo, void *context){ + control_c_received = 1; +} + +static void print_usage(const char *progname){ + printf("Usage: %s [option] < file" "\n" + "\t" "-g or --gain with gain in [0.0 .. 1.0] set the so-called normalized RF gain in LimeSDR (default: 1.0 max RF power)" "\n" + "\t" "-c or --channel with channel either 0 or 1 (default: 0)" "\n" + "\t" "-a or --antenna with antenna in { 0, 1, 2 } (default:" STRINGIFY(DEFAULT_ANTENNA) ")" "\n" + "\t" "-i or --index select LimeSDR if multiple devices connected (default: 0)" "\n" + "\t" "-b or --bits select bit count in IQ sample in { 1, 8, 12, 16 }, (default: 16)" "\n" + "\t" "-s or --samplerate configure BB sample rate (default: " STRINGIFY(TX_SAMPLERATE) ")" "\n" + "\t" "-d --dynamic configure dynamic for the 1-bit mode (default: 2047, max 12-bit signed value supported by LimeSDR)" "\n" + "Example:" "\n" + "\t" "./limeplayer -s 1000000 -b 1 -d 1023 -g 0.1 < ../circle.1b.1M.bin" "\n", progname); + exit(0); +} + +int main(int argc, char *const argv[]){ + struct sigaction control_c; + + memset(&control_c, 0, sizeof(control_c)); + control_c.sa_sigaction = &control_c_handler; + + /* The SA_SIGINFO flag tells sigaction() to use the sa_sigaction field, not sa_handler. */ + control_c.sa_flags = SA_SIGINFO; + + if (sigaction(SIGTERM, &control_c, NULL) < 0) { + perror ("sigaction"); + return(EXIT_CODE_CONTROL_C); + } + if (sigaction(SIGQUIT, &control_c, NULL) < 0) { + perror ("sigaction"); + return(EXIT_CODE_CONTROL_C); + } + if (sigaction(SIGINT, &control_c, NULL) < 0) { + perror ("sigaction"); + return(EXIT_CODE_CONTROL_C); + } + + int device_count = LMS_GetDeviceList(NULL); + if(device_count < 1){ + return(EXIT_CODE_NO_DEVICE); + } + lms_info_str_t *device_list = malloc(sizeof(lms_info_str_t) * device_count); + device_count = LMS_GetDeviceList(device_list); + + int i = 0; + while(i < device_count){ + // printf("device[%d/%d]=%s" "\n", i + 1, device_count, device_list[i]); + i++; + } + + double gain = 1.0; + int32_t antenna = DEFAULT_ANTENNA; + int32_t channel = 0; + int32_t index = 0; + int32_t bits = 16; + double sampleRate = TX_SAMPLERATE; + int32_t dynamic = 2047; + + while (1) { + int option_index = 0; + static struct option long_options[] = { + {"gain", required_argument, 0, 'g' }, + {"channel", required_argument, 0, 'c' }, + {"antenna", required_argument, 0, 'a' }, + {"index", required_argument, 0, 'i'}, + {"bits", required_argument, 0, 'b'}, + {"samplerate", required_argument, 0, 's'}, + {"dynamic", required_argument, 0, 'd'}, + {0, 0, 0, 0 } + }; + + int c = getopt_long(argc, argv, "g:c:a:i:s:b:d:", long_options, &option_index); + if (c == -1) + break; + + switch (c) { + case 0: + #if 1 + fprintf(stderr, "option %s", long_options[option_index].name); + if (optarg) + fprintf(stderr, " with arg %s", optarg); + fprintf(stderr, "\n"); + #endif + + break; + + case 'a': + antenna = strtol(optarg, NULL, 0); + break; + case 'b': + bits = strtol(optarg, NULL, 0); + break; + case 'c': + channel = strtol(optarg, NULL, 0); + break; + case 'g': + gain = strtod(optarg, NULL); + break; + case 'i': + index = strtol(optarg, NULL, 0); + break; + case 's': + sampleRate = strtod(optarg, NULL); + break; + case 'd': + dynamic = strtol(optarg, NULL, 0); + if(dynamic > 2047){ + dynamic = 2047; + } + break; + default: + print_usage(argv[0]); + break; + } + } + // Use correct values + // Use existing device + if(index < 0){ + index = 0; + } + if(index >= device_count){ + index = 0; + } + printf("Using device index %d [%s]" "\n", index, device_list[index]); + + // Normalized gain shall be in [0.0 .. 1.0] + if(gain < 0.0){ + gain = 0.0; + } + if(gain > 1.0){ + gain = 1.0; + } + printf("Using normalized gain %lf" "\n", gain); + + + lms_device_t *device = NULL; + + if(LMS_Open(&device, device_list[index], NULL)){ + return(EXIT_CODE_LMS_OPEN); + } + + int lmsReset = LMS_Reset(device); + if(lmsReset){ + printf("lmsReset %d(%s)" "\n", lmsReset, LMS_GetLastErrorMessage()); + } + int lmsInit = LMS_Init(device); + if(lmsInit){ + printf("lmsInit %d(%s)" "\n", lmsInit, LMS_GetLastErrorMessage()); + } + + int channel_count = LMS_GetNumChannels(device, LMS_CH_TX); + // printf("Tx channel count %d" "\n", channel_count); + if(channel < 0){ + channel = 0; + } + if(channel >= channel_count){ + channel = 0; + } + printf("Using channel %d" "\n", channel); + + int antenna_count = LMS_GetAntennaList(device, LMS_CH_TX, channel, NULL); + // printf("TX%d Channel has %d antenna(ae)" "\n", channel, antenna_count); + lms_name_t antenna_name[antenna_count]; + if(antenna_count > 0){ + int i = 0; + lms_range_t antenna_bw[antenna_count]; + LMS_GetAntennaList(device, LMS_CH_TX, channel, antenna_name); + for(i = 0 ; i < antenna_count ; i++){ + LMS_GetAntennaBW(device, LMS_CH_TX, channel, i, antenna_bw + i); + // printf("Channel %d, antenna [%s] has BW [%lf .. %lf] (step %lf)" "\n", channel, antenna_name[i], antenna_bw[i].min, antenna_bw[i].max, antenna_bw[i].step); + } + } + if(antenna < 0){ + antenna = DEFAULT_ANTENNA; + } + if(antenna >= antenna_count){ + antenna = DEFAULT_ANTENNA; + } + // LMS_SetAntenna(device, LMS_CH_TX, channel, antenna); // SetLOFrequency should take care of selecting the proper antenna + + LMS_SetNormalizedGain(device, LMS_CH_TX, channel, gain); + // Disable all other channels + LMS_EnableChannel(device, LMS_CH_TX, 1 - channel, false); + LMS_EnableChannel(device, LMS_CH_RX, 0, true); /* LimeSuite bug workaround (needed since LimeSuite git rev 52d6129 - or v18.06.0) */ + LMS_EnableChannel(device, LMS_CH_RX, 1, false); + // Enable our Tx channel + LMS_EnableChannel(device, LMS_CH_TX, channel, true); + + int setLOFrequency = LMS_SetLOFrequency(device, LMS_CH_TX, channel, TX_FREQUENCY); + if(setLOFrequency){ + printf("setLOFrequency(%lf)=%d(%s)" "\n", TX_FREQUENCY, setLOFrequency, LMS_GetLastErrorMessage()); + } + +#ifdef __USE_LPF__ + lms_range_t LPFBWRange; + LMS_GetLPFBWRange(device, LMS_CH_TX, &LPFBWRange); + // printf("TX%d LPFBW [%lf .. %lf] (step %lf)" "\n", channel, LPFBWRange.min, LPFBWRange.max, LPFBWRange.step); + double LPFBW = TX_BANDWIDTH; + if(LPFBW < LPFBWRange.min){ + LPFBW = LPFBWRange.min; + } + if(LPFBW > LPFBWRange.max){ + LPFBW = LPFBWRange.min; + } + int setLPFBW = LMS_SetLPFBW(device, LMS_CH_TX, channel, LPFBW); + if(setLPFBW){ + printf("setLPFBW(%lf)=%d(%s)" "\n", LPFBW, setLPFBW, LMS_GetLastErrorMessage()); + } + int enableLPF = LMS_SetLPF(device, LMS_CH_TX, channel, true); + if(enableLPF){ + printf("enableLPF=%d(%s)" "\n", enableLPF, LMS_GetLastErrorMessage()); + } +#endif + + lms_range_t sampleRateRange; + int getSampleRateRange = LMS_GetSampleRateRange(device, LMS_CH_TX, &sampleRateRange); + if(getSampleRateRange){ + printf("getSampleRateRange=%d(%s)" "\n", getSampleRateRange, LMS_GetLastErrorMessage()); + }else{ + // printf("sampleRateRange [%lf MHz.. %lf MHz] (step=%lf Hz)" "\n", sampleRateRange.min / 1e6, sampleRateRange.max / 1e6, sampleRateRange.step); + } + + printf("Set sample rate to %lf ..." "\n", sampleRate); + int setSampleRate = LMS_SetSampleRate(device, sampleRate, 0); + if(setSampleRate){ + printf("setSampleRate=%d(%s)" "\n", setSampleRate, LMS_GetLastErrorMessage()); + } + double actualHostSampleRate = 0.0; + double actualRFSampleRate = 0.0; + int getSampleRate = LMS_GetSampleRate(device, LMS_CH_TX, channel, &actualHostSampleRate, &actualRFSampleRate); + if(getSampleRate){ + printf("getSampleRate=%d(%s)" "\n", getSampleRate, LMS_GetLastErrorMessage()); + }else{ + printf("actualRate %lf (Host) / %lf (RF)" "\n", actualHostSampleRate, actualRFSampleRate); + } + + printf("Calibrating ..." "\n"); + int calibrate = LMS_Calibrate(device, LMS_CH_TX, channel, TX_BANDWIDTH, 0); + if(calibrate){ + printf("calibrate=%d(%s)" "\n", calibrate, LMS_GetLastErrorMessage()); + } + + printf("Setup TX stream ..." "\n"); + lms_stream_t tx_stream = {.channel = channel, .fifoSize = 1024*1024, .throughputVsLatency = 0.5, .isTx = true, .dataFmt = LMS_FMT_I12}; + int setupStream = LMS_SetupStream(device, &tx_stream); + if(setupStream){ + printf("setupStream=%d(%s)" "\n", setupStream, LMS_GetLastErrorMessage()); + } + + struct s16iq_sample_s { + signed short int i; + signed short int q; + }; + + int nSamples = (int)sampleRate / 100; + struct s16iq_sample_s *sampleBuffer = (struct s16iq_sample_s*)malloc(sizeof(struct s16iq_sample_s) * nSamples); + + LMS_StartStream(&tx_stream); + + int loop = 0; + if((12 == bits) || (16 == bits)){ + // File contains interleaved 16-bit IQ values, either with only 12-bit data, or with 16-bit data + while((0 == control_c_received) && fread(sampleBuffer, sizeof(struct s16iq_sample_s), nSamples, stdin)){ + loop++; + if(0 == (loop % 100)){ + struct timeval tv; + gettimeofday(&tv, NULL); + printf("gettimeofday()=> %ld:%06ld ; ", tv.tv_sec, tv.tv_usec); + lms_stream_status_t status; + LMS_GetStreamStatus(&tx_stream, &status); //Obtain TX stream stats + printf("TX rate:%lf MB/s" "\n", status.linkRate / 1e6); + } + if(16 == bits){ + // Scale down to 12-bit + // Quick and dirty, so -1 (0xFFFF) to -15 (0xFFF1) scale down to -1 instead of 0 + int i = 0; + while(i < nSamples){ + sampleBuffer[i].i >>= 4; + sampleBuffer[i].q >>= 4; + i++; + } + } + int sendStream = LMS_SendStream(&tx_stream, sampleBuffer, nSamples, NULL, 1000); + if(sendStream < 0){ + printf("sendStream %d(%s)" "\n", sendStream, LMS_GetLastErrorMessage()); + } + } + }else if(8 == bits){ + // File contains interleaved signed 8-bit IQ values + struct s8iq_sample_s { + signed char i; + signed char q; + }; + struct s8iq_sample_s *fileSamples = (struct s8iq_sample_s*)malloc(sizeof(struct s8iq_sample_s) * nSamples); + while((0 == control_c_received) && fread(fileSamples, sizeof(struct s8iq_sample_s), nSamples, stdin)){ + loop++; + if(0 == (loop % 100)){ + struct timeval tv; + gettimeofday(&tv, NULL); + printf("gettimeofday()=> %ld:%06ld ; ", tv.tv_sec, tv.tv_usec); + lms_stream_status_t status; + LMS_GetStreamStatus(&tx_stream, &status); //Obtain TX stream stats + printf("TX rate:%lf MB/s" "\n", status.linkRate / 1e6); + } + // Up-Scale to 12-bit + int i = 0; + while(i < nSamples){ + sampleBuffer[i].i = (fileSamples[i].i << 4); + sampleBuffer[i].q = (fileSamples[i].q << 4); + i++; + } + int sendStream = LMS_SendStream(&tx_stream, sampleBuffer, nSamples, NULL, 1000); + if(sendStream < 0){ + printf("sendStream %d(%s)" "\n", sendStream, LMS_GetLastErrorMessage()); + } + } + free(fileSamples); + }else if(1 == bits){ + // File contains interleaved signed 1-bit IQ values + // Each byte is IQIQIQIQ + int16_t expand_lut[256][8]; + int i, j; + for (i=0; i<256; i++){ + for (j=0; j<8; j++){ + expand_lut[i][j] = ((i>>(7-j))&0x1)?dynamic:-dynamic; + } + } + printf("1-bit mode: using dynamic=%d" "\n", dynamic); + // printf("sizeof(expand_lut[][])=%d, sizeof(expand_lut[0])=%d" "\n", sizeof(expand_lut), sizeof(expand_lut[0])); + int8_t *fileBuffer = (int8_t*)malloc(sizeof(int8_t) * nSamples); + while((0 == control_c_received) && fread(fileBuffer, sizeof(int8_t), nSamples / 4, stdin)){ + loop++; + if(0 == (loop % 100)){ + struct timeval tv; + gettimeofday(&tv, NULL); + printf("gettimeofday()=> %ld:%06ld ; ", tv.tv_sec, tv.tv_usec); + lms_stream_status_t status; + LMS_GetStreamStatus(&tx_stream, &status); //Obtain TX stream stats + printf("TX rate:%lf MB/s" "\n", status.linkRate / 1e6); + } + // Expand + int src = 0; + int dst = 0; + while(src < (nSamples / 4)){ + memcpy(sampleBuffer + dst, expand_lut + fileBuffer[src], sizeof(expand_lut[0])); + dst += 4; + src++; + } + int sendStream = LMS_SendStream(&tx_stream, sampleBuffer, nSamples, NULL, 1000); + if(sendStream < 0){ + printf("sendStream %d(%s)" "\n", sendStream, LMS_GetLastErrorMessage()); + } + } + free(fileBuffer); + } + + LMS_StopStream(&tx_stream); + LMS_DestroyStream(device, &tx_stream); + + free(sampleBuffer); + + LMS_EnableChannel(device, LMS_CH_TX, channel, false); + LMS_Close(device); + + if(control_c_received){ + return(EXIT_CODE_CONTROL_C); + } + return(0); +} + diff --git a/player/limeplayer_win/Default_TX_Antenna.jpg b/player/limeplayer_win/Default_TX_Antenna.jpg new file mode 100644 index 0000000..db78636 Binary files /dev/null and b/player/limeplayer_win/Default_TX_Antenna.jpg differ diff --git a/player/limeplayer_win/getopt.c b/player/limeplayer_win/getopt.c new file mode 100644 index 0000000..dab5fd8 --- /dev/null +++ b/player/limeplayer_win/getopt.c @@ -0,0 +1,110 @@ +/* +* Copyright (c) 1987, 1993, 1994 +* The Regents of the University of California. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in the +* documentation and/or other materials provided with the distribution. +* 3. All advertising materials mentioning features or use of this software +* must display the following acknowledgement: +* This product includes software developed by the University of +* California, Berkeley and its contributors. +* 4. Neither the name of the University nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +* SUCH DAMAGE. +*/ + +#include "getopt.h" +#include +#include + +int opterr = 1, /* if error message should be printed */ + optind = 1, /* index into parent argv vector */ + optopt, /* character checked for validity */ + optreset; /* reset getopt */ +char *optarg; /* argument associated with option */ + +#define BADCH (int)'?' +#define BADARG (int)':' +#define EMSG "" + +/* + * getopt -- + * Parse argc/argv argument vector. + * + * Note: Unlike GNU getopt(), after a non-option argument, all further + * arguments are considered also non-options. This is similar to + * the way non-GNU Unix systems work. + */ +int getopt(int nargc, char * const nargv[], const char *ostr) +{ + static char *place = EMSG; /* option letter processing */ + const char *oli; /* option letter list index */ + + if (optreset || !*place) { /* update scanning pointer */ + optreset = 0; + if (optind >= nargc || *(place = nargv[optind]) != '-') { + place = EMSG; + return (-1); + } + if (place[1] && *++place == '-') { /* found "--" */ + ++optind; + place = EMSG; + return (-1); + } + } /* option letter okay? */ + if ((optopt = (int)*place++) == (int)':' || + !(oli = strchr(ostr, optopt))) { + /* + * if the user didn't specify '-' as an option, + * assume it means -1. + */ + if (optopt == (int)'-') + return (-1); + if (!*place) + ++optind; + if (opterr && *ostr != ':') + (void)printf("illegal option -- %c\n", optopt); + return (BADCH); + } + if (*++oli != ':') { /* don't need argument */ + optarg = NULL; + if (!*place) + ++optind; + } + else { /* need an argument */ + if (*place) /* no white space */ + optarg = place; + else if (nargc <= ++optind) { /* no arg */ + place = EMSG; + if (*ostr == ':') + return (BADARG); + if (opterr) + (void)printf("option requires an argument -- %c\n", optopt); + return (BADCH); + } + else /* white space */ + optarg = nargv[optind]; + place = EMSG; + ++optind; + } + return (optopt); /* dump back option letter */ +} + \ No newline at end of file diff --git a/player/limeplayer_win/getopt.h b/player/limeplayer_win/getopt.h new file mode 100644 index 0000000..5978e22 --- /dev/null +++ b/player/limeplayer_win/getopt.h @@ -0,0 +1,9 @@ +#ifndef GETOPT_H +#define GETOPT_H + +extern char *optarg; +extern int optind; + +int getopt(int nargc, char * const nargv[], const char *ostr) ; + +#endif diff --git a/player/limeplayer_win/limeplayer.c b/player/limeplayer_win/limeplayer.c new file mode 100644 index 0000000..346a2f8 --- /dev/null +++ b/player/limeplayer_win/limeplayer.c @@ -0,0 +1,409 @@ +#define _CRT_SECURE_NO_WARNINGS + +#define WIN32_LEAN_AND_MEAN +#include +#include + +#include +#include +#include +#ifdef _WIN32 +#include "getopt.h" +#else +#include +#endif + +#include +// Configration Properties: +// C/C++ --> General --> Additional Include Directories: C:\Program Files\PothosSDR\include +// Linker --> General --> Additional Library Directories: C:\Program Files\PothosSDR\lib +// Linker --> Input --> Additional Dependencies: LimeSuite.lib + +#define EXIT_CODE_NO_DEVICE (-2) +#define EXIT_CODE_LMS_OPEN (-1) + +#define TX_FREQUENCY 1575420000.0 +#define TX_SAMPLERATE 2500000.0 +#define TX_BANDWIDTH 5000000.0 +#define DEFAULT_ANTENNA 1 // antenna with BW [30MHz .. 2000MHz] + +typedef struct timeval { + long tv_sec; + long tv_usec; +} timeval; + +int gettimeofday(struct timeval * tp, struct timezone * tzp) +{ + // Note: some broken versions only have 8 trailing zero's, the correct epoch has 9 trailing zero's + // This magic number is the number of 100 nanosecond intervals since January 1, 1601 (UTC) + // until 00:00:00 January 1, 1970 + static const uint64_t EPOCH = ((uint64_t)116444736000000000ULL); + + SYSTEMTIME system_time; + FILETIME file_time; + uint64_t time; + + GetSystemTime(&system_time); + SystemTimeToFileTime(&system_time, &file_time); + time = ((uint64_t)file_time.dwLowDateTime); + time += ((uint64_t)file_time.dwHighDateTime) << 32; + + tp->tv_sec = (long)((time - EPOCH) / 10000000L); + tp->tv_usec = (long)(system_time.wMilliseconds * 1000); + return 0; +} + +void usage(const char *progname) +{ + fprintf(stderr, "Usage: %s [options]\n" + " -f select IQ sample data file to transmit (required)\n" + " -g with gain in [0.0 .. 1.0] set the so-called normalized RF gain in LimeSDR (default: 1.0 max RF power)\n" + " -c with channel either 0 or 1 (default: 0)\n" + " -a with antenna in { 0, 1, 2 } (default: 1)\n" + " -i select LimeSDR if multiple devices connected (default: 0)\n" + " -b select bit count in IQ sample in { 1, 8, 12, 16 }, (default: 16)\n" + " -s configure baseband sample rate (default: 2500000.0)\n" + " -d configure dynamic for the 1-bit mode (default: 2047, max 12-bit signed value supported by LimeSDR)\n", + progname); + + return; +} + +int main(int argc, char *const argv[]) +{ + if (argc<3) { + usage(argv[0]); + exit(1); + } + + int device_count = LMS_GetDeviceList(NULL); + + if (device_count < 1) + { + fprintf(stderr, "ERROR: No device was found.\n"); + return(EXIT_CODE_NO_DEVICE); + } + + lms_info_str_t *device_list = malloc(sizeof(lms_info_str_t) * device_count); + device_count = LMS_GetDeviceList(device_list); + + int i = 0; + while (i < device_count) + { + printf("device[%d/%d]=%s" "\n", i + 1, device_count, device_list[i]); + i++; + } + + double gain = 1.0; + int32_t antenna = DEFAULT_ANTENNA; + int32_t channel = 0; + int32_t index = 0; + int32_t bits = 16; + double sampleRate = TX_SAMPLERATE; + int32_t dynamic = 2047; + + int result; + char txfile[128]; + + txfile[0] = 0; // Empty TX file name + + while ((result = getopt(argc, argv, "f:g:c:a:i:b:s:d:")) != -1) + { + switch (result) + { + case 'f': + strcpy(txfile, optarg); + break; + case 'a': + antenna = strtol(optarg, NULL, 0); + break; + case 'b': + bits = strtol(optarg, NULL, 0); + break; + case 'c': + channel = strtol(optarg, NULL, 0); + break; + case 'g': + gain = strtod(optarg, NULL); + break; + case 'i': + index = strtol(optarg, NULL, 0); + break; + case 's': + sampleRate = strtod(optarg, NULL); + break; + case 'd': + dynamic = strtol(optarg, NULL, 0); + if (dynamic > 2047) + dynamic = 2047; + break; + case '?': + usage(argv[0]); + exit(1); + default: + break; + } + } + + // Open TX file. + FILE *fp; + + if (txfile[0] == 0) + { + printf("ERROR: I/Q sampling data file is not specified.\n"); + exit(1); + } + + fp = fopen(txfile, "rb"); + + if (fp == NULL) { + fprintf(stderr, "ERROR: Failed to open TX file: %s\n", argv[1]); + exit(1); + } + + // Use existing device + if (index < 0) + index = 0; + + if (index >= device_count) + index = 0; + + printf("Using device index %d [%s]" "\n", index, device_list[index]); + + // Normalized gain shall be in [0.0 .. 1.0] + if (gain < 0.0) + gain = 0.0; + if (gain > 1.0) + gain = 1.0; + + printf("Using normalized gain %lf" "\n", gain); + + // Open device and initialize. + lms_device_t *device = NULL; + + if (LMS_Open(&device, device_list[index], NULL)) + return(EXIT_CODE_LMS_OPEN); + + int lmsReset = LMS_Reset(device); + if (lmsReset) + printf("lmsReset %d(%s)" "\n", lmsReset, LMS_GetLastErrorMessage()); + + int lmsInit = LMS_Init(device); + if (lmsInit) + printf("lmsInit %d(%s)" "\n", lmsInit, LMS_GetLastErrorMessage()); + + // Select channel. + int channel_count = LMS_GetNumChannels(device, LMS_CH_TX); + // printf("Tx channel count %d" "\n", channel_count); + if (channel < 0) + channel = 0; + if (channel >= channel_count) + channel = 0; + + printf("Using channel %d" "\n", channel); + + // Select antenna. + int antenna_count = LMS_GetAntennaList(device, LMS_CH_TX, channel, NULL); + printf("TX%d Channel has %d antenna(ae)" "\n", channel, antenna_count); + + lms_name_t *antenna_name = malloc(sizeof(lms_name_t) * antenna_count); + + if (antenna_count > 0) + { + int i = 0; + lms_range_t *antenna_bw = malloc(sizeof(lms_range_t) * antenna_count); + LMS_GetAntennaList(device, LMS_CH_TX, channel, antenna_name); + for (i = 0; i < antenna_count; i++) + { + LMS_GetAntennaBW(device, LMS_CH_TX, channel, i, antenna_bw + i); + printf("Channel %d, antenna [%s] has BW [%lf .. %lf] (step %lf)" "\n", channel, antenna_name[i], antenna_bw[i].min, antenna_bw[i].max, antenna_bw[i].step); + } + } + + if (antenna < 0) + antenna = DEFAULT_ANTENNA; + if (antenna >= antenna_count) + antenna = DEFAULT_ANTENNA; + // LMS_SetAntenna(device, LMS_CH_TX, channel, antenna); // SetLOFrequency should take care of selecting the proper antenna + + LMS_SetNormalizedGain(device, LMS_CH_TX, channel, gain); + // Disable all other channels + LMS_EnableChannel(device, LMS_CH_TX, 1 - channel, false); + LMS_EnableChannel(device, LMS_CH_RX, 0, false); + LMS_EnableChannel(device, LMS_CH_RX, 1, false); + // Enable our Tx channel + LMS_EnableChannel(device, LMS_CH_TX, channel, true); + + int setLOFrequency = LMS_SetLOFrequency(device, LMS_CH_TX, channel, TX_FREQUENCY); + if (setLOFrequency) + printf("setLOFrequency(%lf)=%d(%s)" "\n", TX_FREQUENCY, setLOFrequency, LMS_GetLastErrorMessage()); + + // Set sample rate. + lms_range_t sampleRateRange; + int getSampleRateRange = LMS_GetSampleRateRange(device, LMS_CH_TX, &sampleRateRange); + if (getSampleRateRange) + printf("getSampleRateRange=%d(%s)" "\n", getSampleRateRange, LMS_GetLastErrorMessage()); + else + { + printf("sampleRateRange [%lf MHz.. %lf MHz] (step=%lf Hz)" "\n", sampleRateRange.min / 1e6, sampleRateRange.max / 1e6, sampleRateRange.step); + } + + printf("Set sample rate to %lf ..." "\n", sampleRate); + int setSampleRate = LMS_SetSampleRate(device, sampleRate, 0); + if (setSampleRate) + printf("setSampleRate=%d(%s)" "\n", setSampleRate, LMS_GetLastErrorMessage()); + + double actualHostSampleRate = 0.0; + double actualRFSampleRate = 0.0; + int getSampleRate = LMS_GetSampleRate(device, LMS_CH_TX, channel, &actualHostSampleRate, &actualRFSampleRate); + if (getSampleRate) + printf("getSampleRate=%d(%s)" "\n", getSampleRate, LMS_GetLastErrorMessage()); + else + printf("actualRate %lf (Host) / %lf (RF)" "\n", actualHostSampleRate, actualRFSampleRate); + + printf("Calibrating ..." "\n"); + int calibrate = LMS_Calibrate(device, LMS_CH_TX, channel, TX_BANDWIDTH, 0); + if (calibrate) + printf("calibrate=%d(%s)" "\n", calibrate, LMS_GetLastErrorMessage()); + + printf("Setup TX stream ..." "\n"); + lms_stream_t tx_stream = { .channel = channel,.fifoSize = 1024 * 1024,.throughputVsLatency = 0.5,.isTx = true,.dataFmt = LMS_FMT_I12 }; + int setupStream = LMS_SetupStream(device, &tx_stream); + if (setupStream) + printf("setupStream=%d(%s)" "\n", setupStream, LMS_GetLastErrorMessage()); + + struct s16iq_sample_s { + signed short int i; + signed short int q; + }; + + int nSamples = (int)sampleRate / 100; + struct s16iq_sample_s *sampleBuffer = (struct s16iq_sample_s*)malloc(sizeof(struct s16iq_sample_s) * nSamples); + + LMS_StartStream(&tx_stream); + + int loop = 0; + if ((12 == bits) || (16 == bits)) + { + // File contains interleaved 16-bit IQ values, either with only 12-bit data, or with 16-bit data + while (fread(sampleBuffer, sizeof(struct s16iq_sample_s), nSamples, fp)) + { + loop++; + if (0 == (loop % 100)) + { + struct timeval tv; + gettimeofday(&tv, NULL); + printf("gettimeofday()=> %ld:%06ld ; ", tv.tv_sec, tv.tv_usec); + lms_stream_status_t status; + LMS_GetStreamStatus(&tx_stream, &status); //Obtain TX stream stats + printf("TX rate:%lf MB/s" "\n", status.linkRate / 1e6); + } + if (16 == bits) + { + // Scale down to 12-bit + // Quick and dirty, so -1 (0xFFFF) to -15 (0xFFF1) scale down to -1 instead of 0 + int i = 0; + while (i < nSamples) { + sampleBuffer[i].i >>= 4; + sampleBuffer[i].q >>= 4; + i++; + } + } + int sendStream = LMS_SendStream(&tx_stream, sampleBuffer, nSamples, NULL, 1000); + if (sendStream < 0) + printf("sendStream %d(%s)" "\n", sendStream, LMS_GetLastErrorMessage()); + } + } + else if (8 == bits) + { + // File contains interleaved signed 8-bit IQ values + struct s8iq_sample_s { + signed char i; + signed char q; + }; + struct s8iq_sample_s *fileSamples = (struct s8iq_sample_s*)malloc(sizeof(struct s8iq_sample_s) * nSamples); + while (fread(fileSamples, sizeof(struct s8iq_sample_s), nSamples, fp)) + { + loop++; + if (0 == (loop % 100)) + { + struct timeval tv; + gettimeofday(&tv, NULL); + printf("gettimeofday()=> %ld:%06ld ; ", tv.tv_sec, tv.tv_usec); + lms_stream_status_t status; + LMS_GetStreamStatus(&tx_stream, &status); //Obtain TX stream stats + printf("TX rate:%lf MB/s" "\n", status.linkRate / 1e6); + } + // Up-Scale to 12-bit + int i = 0; + while (i < nSamples) + { + sampleBuffer[i].i = (fileSamples[i].i << 4); + sampleBuffer[i].q = (fileSamples[i].q << 4); + i++; + } + int sendStream = LMS_SendStream(&tx_stream, sampleBuffer, nSamples, NULL, 1000); + if (sendStream < 0) + printf("sendStream %d(%s)" "\n", sendStream, LMS_GetLastErrorMessage()); + } + free(fileSamples); + } + else if (1 == bits) + { + // File contains interleaved signed 1-bit IQ values + // Each byte is IQIQIQIQ + int16_t expand_lut[256][8]; + int i, j; + for (i = 0; i<256; i++) + { + for (j = 0; j<8; j++) + expand_lut[i][j] = ((i >> (7 - j)) & 0x1) ? dynamic : -dynamic; + } + printf("1-bit mode: using dynamic=%d" "\n", dynamic); + // printf("sizeof(expand_lut[][])=%d, sizeof(expand_lut[0])=%d" "\n", sizeof(expand_lut), sizeof(expand_lut[0])); + int8_t *fileBuffer = (int8_t*)malloc(sizeof(int8_t) * nSamples); + while (fread(fileBuffer, sizeof(int8_t), nSamples / 4, fp)) + { + loop++; + if (0 == (loop % 100)) + { + struct timeval tv; + gettimeofday(&tv, NULL); + printf("gettimeofday()=> %ld:%06ld ; ", tv.tv_sec, tv.tv_usec); + lms_stream_status_t status; + LMS_GetStreamStatus(&tx_stream, &status); //Obtain TX stream stats + printf("TX rate:%lf MB/s" "\n", status.linkRate / 1e6); + } + // Expand + int src = 0; + int dst = 0; + while (src < (nSamples / 4)) + { + memcpy(sampleBuffer + dst, expand_lut + fileBuffer[src], sizeof(expand_lut[0])); + dst += 4; + src++; + } + int sendStream = LMS_SendStream(&tx_stream, sampleBuffer, nSamples, NULL, 1000); + if (sendStream < 0) + printf("sendStream %d(%s)" "\n", sendStream, LMS_GetLastErrorMessage()); + } + free(fileBuffer); + } + + LMS_StopStream(&tx_stream); + LMS_DestroyStream(device, &tx_stream); + + free(sampleBuffer); + + printf("Closing device...\n"); + LMS_EnableChannel(device, LMS_CH_TX, channel, false); + LMS_Close(device); + + // Close TX file + fclose(fp); + + printf("Done.\n"); + + return(0); +} + diff --git a/player/limeplayer_win/limeplayer.png b/player/limeplayer_win/limeplayer.png new file mode 100644 index 0000000..b7a1ba2 Binary files /dev/null and b/player/limeplayer_win/limeplayer.png differ diff --git a/player/plutoplayer.c b/player/plutoplayer.c new file mode 100644 index 0000000..d3cb99b --- /dev/null +++ b/player/plutoplayer.c @@ -0,0 +1,245 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#define NOTUSED(V) ((void) V) +#define MHZ(x) ((long long)(x*1000000.0 + .5)) +#define GHZ(x) ((long long)(x*1000000000.0 + .5)) +#define NUM_SAMPLES 2600000 +#define BUFFER_SIZE (NUM_SAMPLES * 2 * sizeof(int16_t)) + + +struct stream_cfg { + long long bw_hz; // Analog banwidth in Hz + long long fs_hz; // Baseband sample rate in Hz + long long lo_hz; // Local oscillator frequency in Hz + const char* rfport; // Port name + double gain_db; // Hardware gain +}; + +static void usage() { + fprintf(stderr, "Usage: plutoplayer [options]\n" + " -t Transmit data from file (required)\n" + " -a Set TX attenuation [dB] (default -20.0)\n" + " -b Set RF bandwidth [MHz] (default 5.0)\n" + " -u ADALM-Pluto URI\n" + " -n ADALM-Pluto network IP or hostname (default pluto.local)\n"); + return; +} + +static bool stop = false; + +static void handle_sig(int sig) +{ + NOTUSED(sig); + stop = true; +} + +static char* readable_fs(double size, char *buf, size_t buf_size) { + int i = 0; + const char* units[] = {"B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"}; + while (size > 1024) { + size /= 1024; + i++; + } + snprintf(buf, buf_size, "%.*f %s", i, size, units[i]); + return buf; +} + +/* + * + */ +int main(int argc, char** argv) { + char buf[1024]; + int opt; + const char* path = NULL; + struct stream_cfg txcfg; + FILE *fp = NULL; + const char *uri = NULL; + const char *ip = NULL; + + // TX stream default config + txcfg.bw_hz = MHZ(3.0); // 3.0 MHz RF bandwidth + txcfg.fs_hz = MHZ(2.6); // 2.6 MS/s TX sample rate + txcfg.lo_hz = GHZ(1.575420); // 1.57542 GHz RF frequency + txcfg.rfport = "A"; + txcfg.gain_db = -20.0; + + struct iio_context *ctx = NULL; + struct iio_device *tx = NULL; + struct iio_device *phydev = NULL; + struct iio_channel *tx0_i = NULL; + struct iio_channel *tx0_q = NULL; + struct iio_buffer *tx_buffer = NULL; + + while ((opt = getopt(argc, argv, "t:a:b:n:u:")) != EOF) { + switch (opt) { + case 't': + path = optarg; + break; + case 'a': + txcfg.gain_db = atof(optarg); + if(txcfg.gain_db > 0.0) txcfg.gain_db = 0.0; + if(txcfg.gain_db < -80.0) txcfg.gain_db = -80.0; + break; + case 'b': + txcfg.bw_hz = MHZ(atof(optarg)); + if(txcfg.bw_hz > MHZ(5.0)) txcfg.bw_hz = MHZ(5.0); + if(txcfg.bw_hz < MHZ(1.0)) txcfg.bw_hz = MHZ(1.0); + break; + case 'u': + uri = optarg; + break; + case 'n': + ip = optarg; + break; + default: + printf("Unknown argument '-%c %s'\n", opt, optarg); + usage(); + return EXIT_FAILURE; + } + } + + signal(SIGINT, handle_sig); + + if( path == NULL ) { + printf("Specify a path to a file to transmit\n"); + usage(); + return EXIT_FAILURE; + } + + fp = fopen(path, "rb"); + if (fp==NULL) { + fprintf(stderr, "ERROR: Failed to open TX file: %s\n", path); + return EXIT_FAILURE; + } + fseek(fp, 0L, SEEK_END); + size_t sz = ftell(fp); + fseek(fp, 0L, SEEK_SET); + readable_fs((double)sz, buf, sizeof(buf)); + printf("* Transmit file size: %s\n", buf); + + printf("* Acquiring IIO context\n"); + ctx = iio_create_default_context(); + if (ctx == NULL) { + if(ip != NULL) { + ctx = iio_create_network_context(ip); + } else if (uri != NULL) { + ctx = iio_create_context_from_uri(uri); + } else { + ctx = iio_create_network_context("pluto.local"); + } + } + + if (ctx == NULL) { + iio_strerror(errno, buf, sizeof(buf)); + fprintf(stderr, "Failed creating IIO context: %s\n", buf); + return false; + } + + struct iio_scan_context *scan_ctx; + struct iio_context_info **info; + scan_ctx = iio_create_scan_context(NULL, 0); + if (scan_ctx) { + int info_count = iio_scan_context_get_info_list(scan_ctx, &info); + if(info_count > 0) { + printf("* Found %s\n", iio_context_info_get_description(info[0])); + iio_context_info_list_free(info); + } + iio_scan_context_destroy(scan_ctx); + } + + printf("* Acquiring devices\n"); + int device_count = iio_context_get_devices_count(ctx); + if (!device_count) { + fprintf(stderr, "No supported PLUTOSDR devices found.\n"); + goto error_exit; + } + fprintf(stderr, "* Context has %d device(s).\n", device_count); + + printf("* Acquiring TX device\n"); + tx = iio_context_find_device(ctx, "cf-ad9361-dds-core-lpc"); + if (tx == NULL) { + iio_strerror(errno, buf, sizeof(buf)); + fprintf(stderr, "Error opening PLUTOSDR TX device: %s\n", buf); + goto error_exit; + } + + iio_device_set_kernel_buffers_count(tx, 8); + + phydev = iio_context_find_device(ctx, "ad9361-phy"); + struct iio_channel* phy_chn = iio_device_find_channel(phydev, "voltage0", true); + iio_channel_attr_write(phy_chn, "rf_port_select", txcfg.rfport); + iio_channel_attr_write_longlong(phy_chn, "rf_bandwidth", txcfg.bw_hz); + iio_channel_attr_write_longlong(phy_chn, "sampling_frequency", txcfg.fs_hz); + iio_channel_attr_write_double(phy_chn, "hardwaregain", txcfg.gain_db); + + iio_channel_attr_write_bool( + iio_device_find_channel(phydev, "altvoltage0", true) + , "powerdown", true); // Turn OFF RX LO + + iio_channel_attr_write_longlong( + iio_device_find_channel(phydev, "altvoltage1", true) + , "frequency", txcfg.lo_hz); // Set TX LO frequency + + printf("* Initializing streaming channels\n"); + tx0_i = iio_device_find_channel(tx, "voltage0", true); + if (!tx0_i) + tx0_i = iio_device_find_channel(tx, "altvoltage0", true); + + tx0_q = iio_device_find_channel(tx, "voltage1", true); + if (!tx0_q) + tx0_q = iio_device_find_channel(tx, "altvoltage1", true); + + printf("* Enabling IIO streaming channels\n"); + iio_channel_enable(tx0_i); + iio_channel_enable(tx0_q); + + ad9361_set_bb_rate(iio_context_find_device(ctx, "ad9361-phy"), txcfg.fs_hz); + + printf("* Creating TX buffer\n"); + + tx_buffer = iio_device_create_buffer(tx, NUM_SAMPLES, false); + if (!tx_buffer) { + fprintf(stderr, "Could not create TX buffer.\n"); + goto error_exit; + } + + iio_channel_attr_write_bool( + iio_device_find_channel(iio_context_find_device(ctx, "ad9361-phy"), "altvoltage1", true) + , "powerdown", false); // Turn ON TX LO + + int32_t ntx = 0; + short *ptx_buffer = (short *)iio_buffer_start(tx_buffer); + + printf("* Transmit starts...\n"); + // Keep writing samples while there is more data to send and no failures have occurred. + while (!feof(fp) && !stop) { + fread(ptx_buffer, sizeof(short), BUFFER_SIZE / sizeof(short),fp); + // Schedule TX buffer + ntx = iio_buffer_push(tx_buffer); + if (ntx < 0) { + printf("Error pushing buf %d\n", (int) ntx); + break; + } + } + printf("Done.\n"); + +error_exit: + fclose(fp); + iio_channel_attr_write_bool( + iio_device_find_channel(iio_context_find_device(ctx, "ad9361-phy"), "altvoltage1", true) + , "powerdown", true); // Turn OFF TX LO + + if (tx_buffer) { iio_buffer_destroy(tx_buffer); } + if (tx0_i) { iio_channel_disable(tx0_i); } + if (tx0_q) { iio_channel_disable(tx0_q); } + if (ctx) { iio_context_destroy(ctx); } + return EXIT_SUCCESS; +} +