kopia lustrzana https://github.com/Guenael/rtlsdr-wsprd
Merge pull request #113 from Guenael/snr-fix
feat & dbg: .c2 files support, bench doc, normalize sig & dbg snrpull/114/head
commit
2d8d938a24
15
README.md
15
README.md
|
@ -136,3 +136,18 @@ Some manufacturers integrate a 0.5ppm TCXO. It's the best second option, after a
|
|||
- NooElec NESDR SMART : Works fine out of the box
|
||||
- RTL-SDR Blog 1PPM TCXO : Works with some drift, require additional mass, or a better enclosure
|
||||
- Other no-name like : RT820, E4000, FC0012, FC0013, can work, but require modification and usually drift a lot
|
||||
|
||||
## Performance & hardware tests
|
||||
|
||||
Some performance tests using:
|
||||
- Raspbian GNU/Linux 11 (bullseye) for Raspberry Pi devices
|
||||
- rtlsdr-wsprd version 0.4.2
|
||||
- Build with `clang -O3 -std=gnu17`
|
||||
|
||||
| Hardware | Supported | RX Load | Decode burst |
|
||||
| --------- | ------------------ | ------- | ------------ |
|
||||
| RPi-1 | :heavy_check_mark: | 23.2% | 8.4s |
|
||||
| RPi-2 | :heavy_check_mark: | 13.5% | 4.1s |
|
||||
| RPi-3 | :heavy_check_mark: | 10.9% | 2.1s |
|
||||
| RPi-4 | :heavy_check_mark: | 5.8% | 1.1s |
|
||||
| i7-5820K | :heavy_check_mark: | 1.7% | 0.5s |
|
||||
|
|
103
rtlsdr_wsprd.c
103
rtlsdr_wsprd.c
|
@ -94,7 +94,7 @@ struct receiver_options {
|
|||
bool selftest;
|
||||
bool writefile;
|
||||
bool readfile;
|
||||
char filename[33];
|
||||
char *filename;
|
||||
};
|
||||
|
||||
|
||||
|
@ -220,8 +220,8 @@ static void rtlsdr_callback(unsigned char *samples, uint32_t samples_count, void
|
|||
/* Save the result in the buffer */
|
||||
uint32_t idx = rx_state.bufferIndex;
|
||||
if (rx_state.iqIndex[idx] < (SIGNAL_LENGHT * SIGNAL_SAMPLE_RATE)) {
|
||||
rx_state.iSamples[idx][rx_state.iqIndex[idx]] = Isum / (32768.0 * DOWNSAMPLING);
|
||||
rx_state.qSamples[idx][rx_state.iqIndex[idx]] = Qsum / (32768.0 * DOWNSAMPLING);
|
||||
rx_state.iSamples[idx][rx_state.iqIndex[idx]] = Isum;
|
||||
rx_state.qSamples[idx][rx_state.iqIndex[idx]] = Qsum;
|
||||
rx_state.iqIndex[idx]++;
|
||||
}
|
||||
}
|
||||
|
@ -259,6 +259,29 @@ static void *decoder(void *arg) {
|
|||
if (rx_state.iqIndex[prevBuffer] < ( (SIGNAL_LENGHT - 3) * SIGNAL_SAMPLE_RATE ) )
|
||||
continue; /* Partial buffer during the first RX, skip it! */
|
||||
|
||||
/* Delete any previous samples tail */
|
||||
for (int i = rx_state.iqIndex[prevBuffer]; i < SIGNAL_LENGHT * SIGNAL_SAMPLE_RATE; i++) {
|
||||
rx_state.iSamples[prevBuffer][i] = 0.0;
|
||||
rx_state.qSamples[prevBuffer][i] = 0.0;
|
||||
}
|
||||
|
||||
/* Normalize the sample @-3dB */
|
||||
float maxSig = 0.0f;
|
||||
for (int i = 0; i < SIGNAL_LENGHT * SIGNAL_SAMPLE_RATE; i++) {
|
||||
float absI = fabs(rx_state.iSamples[prevBuffer][i]);
|
||||
float absQ = fabs(rx_state.qSamples[prevBuffer][i]);
|
||||
|
||||
if (absI > maxSig)
|
||||
maxSig = absI;
|
||||
if (absQ > maxSig)
|
||||
maxSig = absQ;
|
||||
}
|
||||
maxSig = 0.5 / maxSig;
|
||||
for (int i = 0; i < SIGNAL_LENGHT * SIGNAL_SAMPLE_RATE; i++) {
|
||||
rx_state.iSamples[prevBuffer][i] *= maxSig;
|
||||
rx_state.qSamples[prevBuffer][i] *= maxSig;
|
||||
}
|
||||
|
||||
/* Get the date at the beginning last recording session
|
||||
with 1 second margin added, just to be sure to be on this even minute
|
||||
*/
|
||||
|
@ -324,7 +347,7 @@ void postSpots(uint32_t n_results) {
|
|||
// "Table 'wsprnet_db.activity' doesn't exist" reported on web site...
|
||||
// Anyone has doc about this?
|
||||
if (n_results == 0) {
|
||||
snprintf(url, sizeof(url) - 1, "http://wsprnet.org/post?function=wsprstat&rcall=%s&rgrid=%s&rqrg=%.6f&tpct=%.2f&tqrg=%.6f&dbm=%d&version=rtlsdr-050&mode=2",
|
||||
snprintf(url, sizeof(url) - 1, "http://wsprnet.org/post?function=wsprstat&rcall=%s&rgrid=%s&rqrg=%.6f&tpct=%.2f&tqrg=%.6f&dbm=%d&version=rtlsdr-051&mode=2",
|
||||
dec_options.rcall,
|
||||
dec_options.rloc,
|
||||
rx_options.realfreq / 1e6,
|
||||
|
@ -347,7 +370,7 @@ void postSpots(uint32_t n_results) {
|
|||
}
|
||||
|
||||
for (uint32_t i = 0; i < n_results; i++) {
|
||||
snprintf(url, sizeof(url) - 1, "http://wsprnet.org/post?function=wspr&rcall=%s&rgrid=%s&rqrg=%.6f&date=%02d%02d%02d&time=%02d%02d&sig=%.0f&dt=%.1f&tqrg=%.6f&tcall=%s&tgrid=%s&dbm=%s&version=rtlsdr-050&mode=2",
|
||||
snprintf(url, sizeof(url) - 1, "http://wsprnet.org/post?function=wspr&rcall=%s&rgrid=%s&rqrg=%.6f&date=%02d%02d%02d&time=%02d%02d&sig=%.0f&dt=%.1f&tqrg=%.6f&tcall=%s&tgrid=%s&dbm=%s&version=rtlsdr-051&mode=2",
|
||||
dec_options.rcall,
|
||||
dec_options.rloc,
|
||||
dec_results[i].freq,
|
||||
|
@ -552,13 +575,70 @@ int32_t writeRawIQfile(float *iSamples, float *qSamples, char *filename) {
|
|||
}
|
||||
|
||||
|
||||
int32_t readC2file(float *iSamples, float *qSamples, char *filename) {
|
||||
float filebuffer[2 * SIGNAL_LENGHT * SIGNAL_SAMPLE_RATE];
|
||||
FILE *fd = fopen(filename, "rb");
|
||||
int32_t nread;
|
||||
double frequency;
|
||||
int type;
|
||||
char name[15];
|
||||
|
||||
if (fd == NULL) {
|
||||
fprintf(stderr, "Cannot open data file...\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Get the size of the file */
|
||||
fseek(fd, 0L, SEEK_END);
|
||||
int32_t recsize = ftell(fd) / (2 * sizeof(float)) - 26;
|
||||
fseek(fd, 0L, SEEK_SET);
|
||||
|
||||
/* Limit the file/buffer to the max samples */
|
||||
if (recsize > SIGNAL_LENGHT * SIGNAL_SAMPLE_RATE) {
|
||||
recsize = SIGNAL_LENGHT * SIGNAL_SAMPLE_RATE;
|
||||
}
|
||||
|
||||
/* Read the header */
|
||||
nread = fread(name, sizeof(char), 14, fd);
|
||||
nread = fread(&type, sizeof(int), 1, fd);
|
||||
nread = fread(&frequency, sizeof(double), 1, fd);
|
||||
dec_options.freq = frequency;
|
||||
|
||||
/* Read the IQ file */
|
||||
nread = fread(filebuffer, sizeof(float), 2 * recsize, fd);
|
||||
if (nread != 2 * recsize) {
|
||||
fprintf(stderr, "Cannot read all the data! %d\n", nread);
|
||||
fclose(fd);
|
||||
return 0;
|
||||
} else {
|
||||
fclose(fd);
|
||||
}
|
||||
|
||||
/* Convert the interleaved buffer into 2 buffers */
|
||||
for (int32_t i = 0; i < recsize; i++) {
|
||||
iSamples[i] = filebuffer[2 * i];
|
||||
qSamples[i] = -filebuffer[2 * i + 1]; // neg, convention used by wsprsim
|
||||
}
|
||||
|
||||
return recsize;
|
||||
}
|
||||
|
||||
|
||||
void decodeRecordedFile(char *filename) {
|
||||
static float iSamples[SIGNAL_LENGHT * SIGNAL_SAMPLE_RATE] = {0};
|
||||
static float qSamples[SIGNAL_LENGHT * SIGNAL_SAMPLE_RATE] = {0};
|
||||
static uint32_t samples_len;
|
||||
int32_t n_results = 0;
|
||||
|
||||
if (strcmp(&filename[strlen(filename)-3], ".iq") == 0) {
|
||||
samples_len = readRawIQfile(iSamples, qSamples, filename);
|
||||
} else if (strcmp(&filename[strlen(filename)-3], ".c2") == 0) {
|
||||
samples_len = readC2file(iSamples, qSamples, filename);
|
||||
} else {
|
||||
fprintf(stderr, "Not a valid extension!! (only .iq & .c2 files)\n");
|
||||
return;
|
||||
}
|
||||
|
||||
printf("Number of samples: %d\n", samples_len);
|
||||
|
||||
if (samples_len) {
|
||||
|
@ -691,7 +771,7 @@ void usage(void) {
|
|||
"Debugging options:\n"
|
||||
"\t-t decoder self-test (generate a signal & decode), no parameter\n"
|
||||
"\t-w write received signal and exit [filename prefix]\n"
|
||||
"\t-r read signal, decode and exit [filename]\n"
|
||||
"\t-r read signal with .iq or .c2 format, decode and exit [filename]\n"
|
||||
"\t (raw format: 375sps, float 32 bits, 2 channels)\n"
|
||||
"Example:\n"
|
||||
"\trtlsdr_wsprd -f 2m -c A1XYZ -l AB12cd -g 29 -o -4200\n");
|
||||
|
@ -830,13 +910,13 @@ int main(int argc, char **argv) {
|
|||
case 't': // Seft test (used in unit-test CI pipeline)
|
||||
rx_options.selftest = true;
|
||||
break;
|
||||
case 'w': // Read a signal and decode
|
||||
case 'w': // Write a signal and exit
|
||||
rx_options.writefile = true;
|
||||
snprintf(rx_options.filename, sizeof(rx_options.filename), "%.32s", optarg);
|
||||
rx_options.filename = optarg;
|
||||
break;
|
||||
case 'r': // Write a signal and exit
|
||||
case 'r': // Read a signal and decode
|
||||
rx_options.readfile = true;
|
||||
snprintf(rx_options.filename, sizeof(rx_options.filename), "%.32s", optarg);
|
||||
rx_options.filename = optarg;
|
||||
break;
|
||||
default:
|
||||
usage();
|
||||
|
@ -986,7 +1066,7 @@ int main(int argc, char **argv) {
|
|||
struct tm *gtm = gmtime(&rawtime);
|
||||
|
||||
/* Print used parameter */
|
||||
printf("\nStarting rtlsdr-wsprd (%04d-%02d-%02d, %02d:%02dz) -- Version 0.5.0\n",
|
||||
printf("\nStarting rtlsdr-wsprd (%04d-%02d-%02d, %02d:%02dz) -- Version 0.5.1\n",
|
||||
gtm->tm_year + 1900, gtm->tm_mon + 1, gtm->tm_mday, gtm->tm_hour, gtm->tm_min);
|
||||
printf(" Callsign : %s\n", dec_options.rcall);
|
||||
printf(" Locator : %s\n", dec_options.rloc);
|
||||
|
@ -1053,7 +1133,6 @@ int main(int argc, char **argv) {
|
|||
/* Destroy the lock/cond/thread */
|
||||
pthread_cond_destroy(&decState.ready_cond);
|
||||
pthread_mutex_destroy(&decState.ready_mutex);
|
||||
pthread_exit(NULL);
|
||||
|
||||
printf("Bye!\n");
|
||||
|
||||
|
|
|
@ -42,6 +42,7 @@ double atofs(char *s);
|
|||
int32_t parse_u64(char *s, uint64_t *const value);
|
||||
int32_t readRawIQfile(float *iSamples, float *qSamples, char *filename);
|
||||
int32_t writeRawIQfile(float *iSamples, float *qSamples, char *filename);
|
||||
int32_t readC2file(float *iSamples, float *qSamples, char *filename);
|
||||
void decodeRecordedFile(char *filename);
|
||||
float whiteGaussianNoise(float factor);
|
||||
int32_t decoderSelfTest();
|
||||
|
|
|
@ -454,10 +454,10 @@ int wspr_decode(float *idat,
|
|||
|
||||
/* Setup metric table */
|
||||
int32_t mettab[2][256];
|
||||
float bias = 0.42;
|
||||
float bias = 0.45;
|
||||
for (int i = 0; i < 256; i++) {
|
||||
mettab[0][i] = round(10 * (metric_tables[2][i] - bias));
|
||||
mettab[1][i] = round(10 * (metric_tables[2][255 - i] - bias));
|
||||
mettab[0][i] = roundf(10.0 * (metric_tables[2][i] - bias));
|
||||
mettab[1][i] = roundf(10.0 * (metric_tables[2][255 - i] - bias));
|
||||
}
|
||||
|
||||
/* Setup/Load hash tables */
|
||||
|
@ -573,12 +573,12 @@ int wspr_decode(float *idat,
|
|||
* corresponding to -7-26.3=-33.3dB in 2500 Hz bandwidth.
|
||||
* The corresponding threshold is -42.3 dB in 2500 Hz bandwidth for WSPR-15. */
|
||||
|
||||
float min_snr = powf(10.0, -7.0 / 10.0); // this is min snr in wspr bw
|
||||
float min_snr = powf(10.0, -8.0 / 10.0); // this is min snr in wspr bw
|
||||
float snr_scaling_factor = 26.3;
|
||||
|
||||
for (int j = 0; j < 411; j++) {
|
||||
smspec[j] = smspec[j] / noise_level - 1.0;
|
||||
if (smspec[j] < min_snr) smspec[j] = 0.1;
|
||||
if (smspec[j] < min_snr) smspec[j] = 0.1 * min_snr;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue