// WSPR transmitter for the Raspberry Pi. See accompanying README // file for a description on how to use this code. // License: // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 2 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 General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . // ha7ilm: added RPi2 support based on a patch to PiFmRds by Cristophe // Jacquet and Richard Hirst: http://git.io/vn7O9 // F5OEO : adapt to librpitx for cleaner spectrum #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "librpitx/src/librpitx.h" clkgpio *clk = NULL; ngfmdmasync *ngfmtest = NULL; #define ABORT(a) exit(a) // Used for debugging #define MARK std::cout << "Currently in file: " << __FILE__ << " line: " << __LINE__ << std::endl typedef enum { WSPR, TONE } mode_type; // WSRP nominal symbol time #define WSPR_SYMTIME (8192.0 / 12000.0) // How much random frequency offset should be added to WSPR transmissions // if the --offset option has been turned on. #define WSPR_RAND_OFFSET 80 #define WSPR15_RAND_OFFSET 8 // Disable the PWM clock and wait for it to become 'not busy'. void disable_clock() { } // Turn on TX void txon() { // ACCESS_BUS_ADDR(PADS_GPIO_0_27_BUS) = 0x5a000018 + 7; //16mA +10.6dBm disable_clock(); } // Turn transmitter on void txoff() { disable_clock(); } // Transmit symbol sym for tsym seconds. // // TODO: // Upon entering this function at the beginning of a WSPR transmission, we // do not know which DMA table entry is being processed by the DMA engine. #define PWM_CLOCKS_PER_ITER_NOMINAL 1000 void txSym( const int &sym_num, const double ¢er_freq, const double &tone_spacing, const double &tsym, const std::vector &dma_table_freq, const double &f_pwm_clk, struct PageInfo instrs[], struct PageInfo &constPage, int &bufPtr) { } // Turn off (reset) DMA engine void unSetupDMA() { txoff(); } // Truncate at bit lsb. i.e. set all bits less than lsb to zero. double bit_trunc( const double &d, const int &lsb) { return floor(d / pow(2.0, lsb)) * pow(2.0, lsb); } // Convert string to uppercase void to_upper( char *str) { while (*str) { *str = toupper(*str); str++; } } // Encode call, locator, and dBm into WSPR codeblock. void wspr( const char *call, const char *l_pre, const char *dbm, unsigned char *symbols) { // pack prefix in nadd, call in n1, grid, dbm in n2 char *c, buf[16]; strncpy(buf, call, 16); c = buf; to_upper(c); unsigned long ng, nadd = 0; if (strchr(c, '/')) { // prefix-suffix nadd = 2; int i = strchr(c, '/') - c; // stroke position int n = strlen(c) - i - 1; // suffix len, prefix-call len c[i] = '\0'; if (n == 1) ng = 60000 - 32768 + (c[i + 1] >= '0' && c[i + 1] <= '9' ? c[i + 1] - '0' : c[i + 1] == ' ' ? 38 : c[i + 1] - 'A' + 10); // suffix /A to /Z, /0 to /9 if (n == 2) ng = 60000 + 26 + 10 * (c[i + 1] - '0') + (c[i + 2] - '0'); // suffix /10 to /99 if (n > 2) { // prefix EA8/, right align ng = (i < 3 ? 36 : c[i - 3] >= '0' && c[i - 3] <= '9' ? c[i - 3] - '0' : c[i - 3] - 'A' + 10); ng = 37 * ng + (i < 2 ? 36 : c[i - 2] >= '0' && c[i - 2] <= '9' ? c[i - 2] - '0' : c[i - 2] - 'A' + 10); ng = 37 * ng + (i < 1 ? 36 : c[i - 1] >= '0' && c[i - 1] <= '9' ? c[i - 1] - '0' : c[i - 1] - 'A' + 10); if (ng < 32768) nadd = 1; else ng = ng - 32768; c = c + i + 1; } } int i = (isdigit(c[2]) ? 2 : isdigit(c[1]) ? 1 : 0); // last prefix digit of de-suffixed/de-prefixed callsign int n = strlen(c) - i - 1; // 2nd part of call len unsigned long n1; n1 = (i < 2 ? 36 : c[i - 2] >= '0' && c[i - 2] <= '9' ? c[i - 2] - '0' : c[i - 2] - 'A' + 10); n1 = 36 * n1 + (i < 1 ? 36 : c[i - 1] >= '0' && c[i - 1] <= '9' ? c[i - 1] - '0' : c[i - 1] - 'A' + 10); n1 = 10 * n1 + c[i] - '0'; n1 = 27 * n1 + (n < 1 ? 26 : c[i + 1] - 'A'); n1 = 27 * n1 + (n < 2 ? 26 : c[i + 2] - 'A'); n1 = 27 * n1 + (n < 3 ? 26 : c[i + 3] - 'A'); // if(rand() % 2) nadd=0; if (!nadd) { // Copy locator locally since it is declared const and we cannot modify // its contents in-place. char l[4]; strncpy(l, l_pre, 4); to_upper(l); // grid square Maidenhead locator (uppercase) ng = 180 * (179 - 10 * (l[0] - 'A') - (l[2] - '0')) + 10 * (l[1] - 'A') + (l[3] - '0'); } int p = atoi(dbm); // EIRP in dBm={0,3,7,10,13,17,20,23,27,30,33,37,40,43,47,50,53,57,60} int corr[] = {0, -1, 1, 0, -1, 2, 1, 0, -1, 1}; p = p > 60 ? 60 : p < 0 ? 0 : p + corr[p % 10]; unsigned long n2 = (ng << 7) | (p + 64 + nadd); // pack n1,n2,zero-tail into 50 bits char packed[11] = { static_cast(n1 >> 20), static_cast(n1 >> 12), static_cast(n1 >> 4), static_cast(((n1 & 0x0f) << 4) | ((n2 >> 18) & 0x0f)), static_cast(n2 >> 10), static_cast(n2 >> 2), static_cast((n2 & 0x03) << 6), 0, 0, 0, 0}; // convolutional encoding K=32, r=1/2, Layland-Lushbaugh polynomials int k = 0; int j, s; int nstate = 0; unsigned char symbol[176]; for (j = 0; j != sizeof(packed); j++) { for (i = 7; i >= 0; i--) { unsigned long poly[2] = {0xf2d05351L, 0xe4613c47L}; nstate = (nstate << 1) | ((packed[j] >> i) & 1); for (s = 0; s != 2; s++) { // convolve unsigned long n = nstate & poly[s]; int even = 0; // even := parity(n) while (n) { even = 1 - even; n = n & (n - 1); } symbol[k] = even; k++; } } } // interleave symbols const unsigned char npr3[162] = { 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0}; for (i = 0; i != 162; i++) { // j0 := bit reversed_values_smaller_than_161[i] unsigned char j0; p = -1; for (k = 0; p != i; k++) { for (j = 0; j != 8; j++) // j0:=bit_reverse(k) j0 = ((k >> j) & 1) | (j0 << 1); if (j0 < 162) p++; } symbols[j0] = npr3[j0] | symbol[i] << 1; // interleave and add sync std::vector } } // Wait for the system clock's minute to reach one second past 'minute' void wait_every( int minute) { time_t t; struct tm *ptm; for (;;) { time(&t); ptm = gmtime(&t); if ((ptm->tm_min % minute) == 0 && ptm->tm_sec == 0) break; usleep(1000); } usleep(1000000); // wait another second } void print_usage() { std::cout << "Usage:" << std::endl; std::cout << " wspr [options] callsign locator tx_pwr_dBm f1 ..." << std::endl; std::cout << " OR" << std::endl; std::cout << " wspr [options] --test-tone f" << std::endl; std::cout << std::endl; std::cout << "Options:" << std::endl; std::cout << " -h --help" << std::endl; std::cout << " Print out this help screen." << std::endl; std::cout << " -p --ppm ppm" << std::endl; std::cout << " Known PPM correction to 19.2MHz RPi nominal crystal frequency." << std::endl; std::cout << " -s --self-calibration" << std::endl; std::cout << " Check NTP before every transmission to obtain the PPM error of the" << std::endl; std::cout << " crystal (default setting!)." << std::endl; std::cout << " -f --free-running" << std::endl; std::cout << " Do not use NTP to correct frequency error of RPi crystal." << std::endl; std::cout << " -r --repeat" << std::endl; std::cout << " Repeatedly, and in order, transmit on all the specified command line freqs." << std::endl; std::cout << " -x --terminate " << std::endl; std::cout << " Terminate after n transmissions have been completed." << std::endl; std::cout << " -o --offset" << std::endl; std::cout << " Add a random frequency offset to each transmission:" << std::endl; std::cout << " +/- " << WSPR_RAND_OFFSET << " Hz for WSPR" << std::endl; std::cout << " +/- " << WSPR15_RAND_OFFSET << " Hz for WSPR-15" << std::endl; std::cout << " -t --test-tone freq" << std::endl; std::cout << " Simply output a test tone at the specified frequency. Only used" << std::endl; std::cout << " for debugging and to verify calibration." << std::endl; std::cout << " -n --no-delay" << std::endl; std::cout << " Transmit immediately, do not wait for a WSPR TX window. Used" << std::endl; std::cout << " for testing only." << std::endl; std::cout << std::endl; std::cout << "Frequencies can be specified either as an absolute TX carrier frequency, or" << std::endl; std::cout << "using one of the following strings. If a string is used, the transmission" << std::endl; std::cout << "will happen in the middle of the WSPR region of the selected band." << std::endl; std::cout << " LF LF-15 MF MF-15 160m 160m-15 80m 60m 40m 30m 20m 17m 15m 12m 10m 6m 4m 2m" << std::endl; std::cout << "-15 indicates the WSPR-15 region of band ." << std::endl; std::cout << std::endl; std::cout << "Transmission gaps can be created by specifying a TX frequency of 0" << std::endl; } void parse_commandline( // Inputs const int &argc, char *const argv[], // Outputs std::string &callsign, std::string &locator, std::string &tx_power, std::vector ¢er_freq_set, double &ppm, bool &self_cal, bool &repeat, bool &random_offset, double &test_tone, bool &no_delay, mode_type &mode, int &terminate) { // Default values ppm = 0; self_cal = true; repeat = false; random_offset = false; test_tone = NAN; no_delay = false; mode = WSPR; terminate = -1; static struct option long_options[] = { {"help", no_argument, 0, 'h'}, {"ppm", required_argument, 0, 'p'}, {"self-calibration", no_argument, 0, 's'}, {"free-running", no_argument, 0, 'f'}, {"repeat", no_argument, 0, 'r'}, {"terminate", required_argument, 0, 'x'}, {"offset", no_argument, 0, 'o'}, {"test-tone", required_argument, 0, 't'}, {"no-delay", no_argument, 0, 'n'}, {0, 0, 0, 0}}; while (true) { /* getopt_long stores the option index here. */ int option_index = 0; int c = getopt_long(argc, argv, "hp:sfrx:ot:n", long_options, &option_index); if (c == -1) break; switch (c) { char *endp; case 0: // Code should only get here if a long option was given a non-null // flag value. std::cout << "Check code!" << std::endl; ABORT(-1); break; case 'h': print_usage(); ABORT(-1); break; case 'p': ppm = strtod(optarg, &endp); if ((optarg == endp) || (*endp != '\0')) { std::cerr << "Error: could not parse ppm value" << std::endl; ABORT(-1); } break; case 's': self_cal = true; break; case 'f': self_cal = false; break; case 'r': repeat = true; break; case 'x': terminate = strtol(optarg, &endp, 10); if ((optarg == endp) || (*endp != '\0')) { std::cerr << "Error: could not parse termination argument" << std::endl; ABORT(-1); } if (terminate < 1) { std::cerr << "Error: termination parameter must be >= 1" << std::endl; ABORT(-1); } break; case 'o': random_offset = true; break; case 't': test_tone = strtod(optarg, &endp); mode = TONE; if ((optarg == endp) || (*endp != '\0')) { std::cerr << "Error: could not parse test tone frequency" << std::endl; ABORT(-1); } break; case 'n': no_delay = true; break; case '?': /* getopt_long already printed an error message. */ ABORT(-1); default: ABORT(-1); } } // Parse the non-option parameters unsigned int n_free_args = 0; while (optind < argc) { // Check for callsign, locator, tx_power if (n_free_args == 0) { callsign = argv[optind++]; n_free_args++; continue; } if (n_free_args == 1) { locator = argv[optind++]; n_free_args++; continue; } if (n_free_args == 2) { tx_power = argv[optind++]; n_free_args++; continue; } // Must be a frequency // First see if it is a string. double parsed_freq; if (!strcasecmp(argv[optind], "LF")) { parsed_freq = 137500.0; } else if (!strcasecmp(argv[optind], "LF-15")) { parsed_freq = 137612.5; } else if (!strcasecmp(argv[optind], "MF")) { parsed_freq = 475700.0; } else if (!strcasecmp(argv[optind], "MF-15")) { parsed_freq = 475812.5; } else if (!strcasecmp(argv[optind], "160m")) { parsed_freq = 1838100.0; } else if (!strcasecmp(argv[optind], "160m-15")) { parsed_freq = 1838212.5; } else if (!strcasecmp(argv[optind], "80m")) { parsed_freq = 3594100.0; } else if (!strcasecmp(argv[optind], "60m")) { parsed_freq = 5288700.0; } else if (!strcasecmp(argv[optind], "40m")) { parsed_freq = 7040100.0; } else if (!strcasecmp(argv[optind], "30m")) { parsed_freq = 10140200.0; } else if (!strcasecmp(argv[optind], "20m")) { parsed_freq = 14097100.0; } else if (!strcasecmp(argv[optind], "17m")) { parsed_freq = 18106100.0; } else if (!strcasecmp(argv[optind], "15m")) { parsed_freq = 21096100.0; } else if (!strcasecmp(argv[optind], "12m")) { parsed_freq = 24926100.0; } else if (!strcasecmp(argv[optind], "10m")) { parsed_freq = 28126100.0; } else if (!strcasecmp(argv[optind], "6m")) { parsed_freq = 50294500.0; } else if (!strcasecmp(argv[optind], "4m")) { parsed_freq = 70092500.0; } else if (!strcasecmp(argv[optind], "2m")) { parsed_freq = 144490500.0; } else if (!strcasecmp(argv[optind], "70cm")) { parsed_freq = 432300500.0; } else { // Not a string. See if it can be parsed as a double. char *endp; parsed_freq = strtod(argv[optind], &endp); if ((optarg == endp) || (*endp != '\0')) { std::cerr << "Error: could not parse transmit frequency: " << argv[optind] << std::endl; ABORT(-1); } } optind++; center_freq_set.push_back(parsed_freq); } // Convert to uppercase transform(callsign.begin(), callsign.end(), callsign.begin(), ::toupper); transform(locator.begin(), locator.end(), locator.begin(), ::toupper); // Check consistency among command line options. if (ppm && self_cal) { std::cout << "Warning: ppm value is being ignored!" << std::endl; ppm = 0.0; } if (mode == TONE) { if ((callsign != "") || (locator != "") || (tx_power != "") || (center_freq_set.size() != 0) || random_offset) { std::cerr << "Warning: callsign, locator, etc. are ignored when generating test tone" << std::endl; } random_offset = 0; if (test_tone <= 0) { std::cerr << "Error: test tone frequency must be positive" << std::endl; ABORT(-1); } } else { if ((callsign == "") || (locator == "") || (tx_power == "") || (center_freq_set.size() == 0)) { std::cerr << "Error: must specify callsign, locator, dBm, and at least one frequency" << std::endl; std::cerr << "Try: wspr --help" << std::endl; ABORT(-1); } } // Print a summary of the parsed options if (mode == WSPR) { std::cout << "WSPR packet contents:" << std::endl; std::cout << " Callsign: " << callsign << std::endl; std::cout << " Locator: " << locator << std::endl; std::cout << " Power: " << tx_power << " dBm" << std::endl; std::cout << "Requested TX frequencies:" << std::endl; std::stringstream temp; for (unsigned int t = 0; t < center_freq_set.size(); t++) { temp << std::setprecision(6) << std::fixed; temp << " " << center_freq_set[t] / 1e6 << " MHz" << std::endl; } std::cout << temp.str(); temp.str(""); if (self_cal) { temp << " NTP will be used to periodically calibrate the transmission frequency" << std::endl; } else if (ppm) { temp << " PPM value to be used for all transmissions: " << ppm << std::endl; } if (terminate > 0) { temp << " TX will stop after " << terminate << " transmissions." << std::endl; } else if (repeat) { temp << " Transmissions will continue forever until stopped with CTRL-C" << std::endl; } if (random_offset) { temp << " A small random frequency offset will be added to all transmissions" << std::endl; } if (temp.str().length()) { std::cout << "Extra options:" << std::endl; std::cout << temp.str(); } std::cout << std::endl; } else { std::stringstream temp; temp << std::setprecision(6) << std::fixed << "A test tone will be generated at frequency " << test_tone / 1e6 << " MHz" << std::endl; std::cout << temp.str(); if (self_cal) { std::cout << "NTP will be used to calibrate the tone frequency" << std::endl; } else if (ppm) { std::cout << "PPM value to be used to generate the tone: " << ppm << std::endl; } std::cout << std::endl; } } // Call ntp_adjtime() to obtain the latest calibration coefficient. void update_ppm( double &ppm) { struct timex ntx; int status; double ppm_new; ntx.modes = 0; /* only read */ status = ntp_adjtime(&ntx); if (status != TIME_OK) { // cerr << "Error: clock not synchronized" << std::endl; // return; } ppm_new = (double)ntx.freq / (double)(1 << 16); /* frequency scale */ if (abs(ppm_new) > 200) { std::cerr << "Warning: absolute ppm value is greater than 200 and is being ignored!" << std::endl; } else { if (ppm != ppm_new) { std::cout << " Obtained new ppm value: " << ppm_new << std::endl; } ppm = ppm_new; } } /* Return 1 if the difference is negative, otherwise 0. */ // From StackOverflow: // http://stackoverflow.com/questions/1468596/c-programming-calculate-elapsed-time-in-milliseconds-unix int timeval_subtract(struct timeval *result, struct timeval *t2, struct timeval *t1) { long int diff = (t2->tv_usec + 1000000 * t2->tv_sec) - (t1->tv_usec + 1000000 * t1->tv_sec); result->tv_sec = diff / 1000000; result->tv_usec = diff % 1000000; return (diff < 0); } void timeval_print(struct timeval *tv) { char buffer[30]; time_t curtime; // printf("%ld.%06ld", tv->tv_sec, tv->tv_usec); curtime = tv->tv_sec; // strftime(buffer, 30, "%m-%d-%Y %T", localtime(&curtime)); strftime(buffer, 30, "UTC %Y-%m-%d %T", gmtime(&curtime)); printf("%s.%03ld", buffer, (tv->tv_usec + 500) / 1000); } // Called when exiting or when a signal is received. void cleanup() { if (clk != NULL) { delete clk; clk = NULL; } if (ngfmtest != NULL) { delete ngfmtest; ngfmtest = NULL; } } // Called when a signal is received. Automatically calls cleanup(). void cleanupAndExit(int sig) { std::cerr << "Exiting with error; caught signal: " << sig << std::endl; cleanup(); ABORT(-1); } int main(const int argc, char *const argv[]) { // catch all signals (like ctrl+c, ctrl+z, ...) to ensure DMA is disabled for (int i = 0; i < 64; i++) { struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_handler = cleanupAndExit; sigaction(i, &sa, NULL); } atexit(cleanup); // Initialize the RNG srand(time(NULL)); // Parse arguments std::string callsign; std::string locator; std::string tx_power; std::vector center_freq_set; double ppm; bool self_cal; bool repeat; bool random_offset; double test_tone; bool no_delay; mode_type mode; int terminate; parse_commandline( argc, argv, callsign, locator, tx_power, center_freq_set, ppm, self_cal, repeat, random_offset, test_tone, no_delay, mode, terminate); int nbands = center_freq_set.size(); if (mode == TONE) { if (clk == NULL) clk = new clkgpio; clk->SetAdvancedPllMode(true); // Test tone mode... double wspr_symtime = WSPR_SYMTIME; double tone_spacing = 1.0 / wspr_symtime; std::stringstream temp; temp << std::setprecision(6) << std::fixed << "Transmitting test tone on frequency " << test_tone / 1.0e6 << " MHz" << std::endl; std::cout << temp.str(); std::cout << "Press CTRL-C to exit!" << std::endl; txon(); int bufPtr = 0; // Set to non-zero value to ensure setupDMATab is called at least once. double ppm_prev = 123456; double center_freq_actual; // SetTone clk->SetCenterFrequency(test_tone, 100); clk->enableclk(4); clk->SetFrequency(000); while (true) usleep(1000000); // Should never get here... } else { // WSPR mode // Create WSPR symbols unsigned char symbols[162]; wspr(callsign.c_str(), locator.c_str(), tx_power.c_str(), symbols); /* printf("WSPR codeblock: "); for (int i = 0; i < (signed)(sizeof(symbols)/sizeof(*symbols)); i++) { if (i) { std::cout << ","; } printf("%d", symbols[i]); } printf("\n"); */ std::cout << "Ready to transmit (setup complete)..." << std::endl; int band = 0; int n_tx = 0; for (;;) { // Calculate WSPR parameters for this transmission double center_freq_desired; center_freq_desired = center_freq_set[band]; bool wspr15 = (center_freq_desired > 137600 && center_freq_desired < 137625) || (center_freq_desired > 475800 && center_freq_desired < 475825) || (center_freq_desired > 1838200 && center_freq_desired < 1838225); double wspr_symtime = (wspr15) ? 8.0 * WSPR_SYMTIME : WSPR_SYMTIME; double tone_spacing = 1.0 / wspr_symtime; // Add random offset if ((center_freq_desired != 0) && random_offset) { center_freq_desired += (2.0 * rand() / ((double)RAND_MAX + 1.0) - 1.0) * (wspr15 ? WSPR15_RAND_OFFSET : WSPR_RAND_OFFSET); } // Status message before transmission std::stringstream temp; temp << std::setprecision(6) << std::fixed; temp << "Desired center frequency for " << (wspr15 ? "WSPR-15" : "WSPR") << " transmission: " << center_freq_desired / 1e6 << " MHz" << std::endl; std::cout << temp.str(); // Wait for WSPR transmission window to arrive. if (no_delay) { std::cout << " Transmitting immediately (not waiting for WSPR window)" << std::endl; } else { std::cout << " Waiting for next WSPR transmission window..." << std::endl; wait_every((wspr15) ? 15 : 2); } // Update crystal calibration information if (self_cal) { update_ppm(ppm); } // Create the DMA table for this center frequency std::vector dma_table_freq; double center_freq_actual; if (center_freq_desired) { center_freq_actual = center_freq_desired; } else { center_freq_actual = center_freq_desired; } // Send the message! // std::cout << "TX started!" << std::endl; if (center_freq_actual) { // Print a status message right before transmission begins. struct timeval tvBegin, tvEnd, tvDiff; gettimeofday(&tvBegin, NULL); std::cout << " TX started at: "; timeval_print(&tvBegin); std::cout << std::endl; struct timeval sym_start; struct timeval diff; int bufPtr = 0; int Upsample = 10000; int SR = Upsample * 1 / wspr_symtime; int FifoSize = 40000; bool usePWMSample = false; static float *FreqPWM = NULL; //New modulator and tx on ngfmtest = new ngfmdmasync(center_freq_actual, SR, 14, FifoSize, true); FreqPWM = (float *)malloc(Upsample * sizeof(float)); double FreqResolution = ngfmtest->GetFrequencyResolution(); double RealFreq = ngfmtest->GetRealFrequency(0); if (FreqResolution > tone_spacing) { fprintf(stderr, "Freq resolution=%f - Tone spacing =%f Erreur tuning=%f\n", FreqResolution, tone_spacing, RealFreq); usePWMSample = true; } for (int i = 0; i < 162; i++) { double tone_freq = -1.5 * tone_spacing + symbols[i] * tone_spacing - RealFreq; int Nbtx = 0; int f1 = 0; int Frac = ngfmtest->GetMasterFrac(0); int IntFreq = floor(tone_freq / FreqResolution); double ToneFreqInf = tone_freq - IntFreq; int Step = ToneFreqInf * Upsample / FreqResolution; if (!usePWMSample) { for (int j = 0; j < Upsample; j++) { FreqPWM[j] = tone_freq; } } else { // Todo : Implement PWMFrequency to obtain better frequency resolution for (int j = 0; j < Upsample; j++) { FreqPWM[j] = tone_freq; } } ngfmtest->SetFrequencySamples(FreqPWM, Upsample); } n_tx++; // Turn transmitter off ngfmtest->disableclk(4); delete ngfmtest; ngfmtest = NULL; free(FreqPWM); // End timestamp gettimeofday(&tvEnd, NULL); std::cout << " TX ended at: "; timeval_print(&tvEnd); timeval_subtract(&tvDiff, &tvEnd, &tvBegin); printf(" (%ld.%03ld s)\n", tvDiff.tv_sec, (tvDiff.tv_usec + 500) / 1000); } else { std::cout << " Skipping transmission" << std::endl; usleep(1000000); } // Advance to next band band = (band + 1) % nbands; if ((band == 0) && !repeat) { break; } if ((terminate > 0) && (n_tx >= terminate)) { break; } } } return 0; }