├── .gitignore ├── IQ16.c ├── IQ16.h ├── Makefile ├── README.md ├── fm.c ├── fm.h ├── fsk.c ├── fsk.h ├── mavg.c ├── mavg.h ├── rb.c.UNUSED ├── rb.h.UNUSED ├── rfm_protocol.c ├── rfm_protocol.h ├── rtl_rfm.c ├── rtl_rfm.h ├── rtl_sdr_driver.c ├── rtl_sdr_driver.h ├── squelch.c └── squelch.h /.gitignore: -------------------------------------------------------------------------------- 1 | rtl_rfm 2 | junk/* 3 | 4 | # Prerequisites 5 | *.d 6 | 7 | # Object files 8 | *.o 9 | *.ko 10 | *.obj 11 | *.elf 12 | 13 | # Linker output 14 | *.ilk 15 | *.map 16 | *.exp 17 | 18 | # Precompiled Headers 19 | *.gch 20 | *.pch 21 | 22 | # Libraries 23 | *.lib 24 | *.a 25 | *.la 26 | *.lo 27 | 28 | # Shared objects (inc. Windows DLLs) 29 | *.dll 30 | *.so 31 | *.so.* 32 | *.dylib 33 | 34 | # Executables 35 | *.exe 36 | *.out 37 | *.app 38 | *.i*86 39 | *.x86_64 40 | *.hex 41 | 42 | # Debug files 43 | *.dSYM/ 44 | *.su 45 | *.idb 46 | *.pdb 47 | 48 | # Kernel Module Compile Results 49 | *.mod* 50 | *.cmd 51 | .tmp_versions/ 52 | modules.order 53 | Module.symvers 54 | Mkfile.old 55 | dkms.conf 56 | -------------------------------------------------------------------------------- /IQ16.c: -------------------------------------------------------------------------------- 1 | #include "rtl_rfm.h" 2 | 3 | void decimate(IQDecimator *d, IQPair sample) 4 | { 5 | d->acci += sample.i; 6 | d->accq += sample.q; 7 | d->count++; 8 | 9 | if (d->count == d->downsample) 10 | { 11 | d->samplehandler((IQPair) { 12 | .i = d->acci / d->downsample, 13 | .q = d->accq / d->downsample 14 | }); // divide and convert to signed 15 | 16 | d->count = d->acci = d->accq = 0; 17 | } 18 | } 19 | 20 | #ifdef CHECKNOMACRO 21 | IQPair complex_conjugate(IQPair arg) 22 | { 23 | return (IQPair) {.q=arg.q, .i=-arg.i}; 24 | } 25 | 26 | IQPair complex_product(IQPair arg1, IQPair arg2) 27 | { 28 | return (IQPair) 29 | { 30 | .q = (arg1.q * arg2.q) - (arg1.i * arg2.i), 31 | .i = (arg1.q * arg2.i) + (arg1.i * arg2.q) 32 | }; 33 | } 34 | #endif -------------------------------------------------------------------------------- /IQ16.h: -------------------------------------------------------------------------------- 1 | #ifndef _IQ16_GUARD 2 | #define _IQ16_GUARD 3 | 4 | //#define CHECKNOMACRO 5 | 6 | #define TAU INT16_MAX * 2 7 | 8 | typedef struct IQPair 9 | { 10 | int16_t i; 11 | int16_t q; 12 | } IQPair; 13 | 14 | #define IQPAIR_SCALAR_QUOTIENT(x, y) ((IQPair) {.i=x.i/y, .q=x.q/y}) 15 | 16 | #ifdef CHECKNOMACRO 17 | IQPair complex_conjugate(IQPair arg); 18 | IQPair complex_product(IQPair arg1, IQPair arg2); 19 | 20 | #define IQPAIR_CONJUGATE complex_conjugate 21 | #define IQPAIR_PRODUCT complex_product 22 | #else 23 | #define IQPAIR_CONJUGATE(x)\ 24 | ((IQPair) {\ 25 | .q=x.q,\ 26 | .i=-x.i\ 27 | }) 28 | 29 | #define IQPAIR_PRODUCT(x, y)\ 30 | ((IQPair) {\ 31 | .q=(x.q*y.q)-(x.i*y.i),\ 32 | .i=(x.q*y.i)+(x.i*y.q)\ 33 | }) 34 | #endif 35 | 36 | typedef void (*SampleHandler)(IQPair sample); 37 | 38 | typedef struct IQDecimatorS 39 | { 40 | uint32_t acci, accq; 41 | uint16_t count, downsample; 42 | SampleHandler samplehandler; 43 | } IQDecimator; 44 | 45 | void decimate(IQDecimator *d, IQPair sample); 46 | 47 | #endif -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TARGET = rtl_rfm 2 | LIBS = -lm $(shell pkg-config --libs librtlsdr) -pthread 3 | CC = gcc 4 | CFLAGS = -g -std=gnu11 -Wall -Wextra -O3 -W $(shell pkg-config --cflags librtlsdr) 5 | 6 | 7 | .PHONY: default all clean 8 | 9 | default: $(TARGET) 10 | all: default 11 | 12 | OBJECTS = $(patsubst %.c, %.o, $(wildcard *.c)) 13 | HEADERS = $(wildcard *.h) 14 | 15 | %.o: %.c $(HEADERS) 16 | $(CC) $(CFLAGS) -c $< -o $@ 17 | 18 | .PRECIOUS: $(TARGET) $(OBJECTS) 19 | 20 | $(TARGET): $(OBJECTS) 21 | $(CC) $(OBJECTS) -Wall $(LIBS) -o $@ 22 | 23 | clean: 24 | -rm -f *.o 25 | -rm -f $(TARGET) 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | RTL-RFM 2 | ======= 3 | FSK/GFSK Decoder for RTL-SDR 4 | 5 | Usage 6 | ----- 7 | 8 | ``` 9 | RTL_RFM, (C) Ryan Suchocki 10 | 11 | Usage: rtl_rfm [-hsqd] [-f freq] [-g gain] [-p error] 12 | 13 | Option flags: 14 | -h Show this message 15 | -s Read input from stdin 16 | -q Quiet. Only output good messages 17 | -d Show Debug Plot 18 | -f Frequency [869412500] 19 | -g Gain [50] 20 | -p PPM error [47] 21 | ``` 22 | -------------------------------------------------------------------------------- /fm.c: -------------------------------------------------------------------------------- 1 | #include "rtl_rfm.h" 2 | #include "fm.h" 3 | 4 | static inline int16_t abs16(int16_t value) 5 | { 6 | uint16_t temp = value >> 15; // make a mask of the sign bit 7 | value ^= temp; // toggle the bits if value is negative 8 | value += temp & 1; // add one if value was negative 9 | return value; 10 | } 11 | 12 | // Linear approximation of atan2() in int16-space. Calculated in four quadrants. 13 | // This relies on the fact that |sin(x)| - |cos(x)| is vaguely linear from x = 0 to tau/4 14 | 15 | static inline int16_t atan2_int16(int16_t y, int16_t x) 16 | { 17 | int16_t absx = abs16(x), absy = abs16(y); 18 | 19 | int32_t denominator = absy + absx; 20 | if (denominator == 0) return 0; // avoid DBZ and skip rest of function 21 | 22 | int32_t numerator = (int32_t)(TAU/8) * (int32_t)(absy - absx); 23 | 24 | int16_t theta = ((numerator << 3) / denominator) >> 3; 25 | 26 | if (y >= 0) // Note: Cartesian plane quadrants 27 | { 28 | if (x >= 0) return (TAU* 1/8) + theta; // quadrant I Theta counts 'towards the y axis', 29 | else return (TAU* 3/8) - theta; // quadrant II So, negate it in quadrants II and IV 30 | } 31 | else 32 | { 33 | if (x < 0) return (TAU*-3/8) + theta; // quadrant III. -3/8 = 5/8 34 | else return (TAU*-1/8) - theta; // quadrant IV. -1/8 = 7/8 35 | } 36 | } 37 | 38 | IQPair previous = {0, 0}; 39 | 40 | int16_t fm_demod(IQPair sample) 41 | { 42 | //IQPair product = complex_multiply(sample, complex_conjugate(previous)); 43 | IQPair product = IQPAIR_PRODUCT(sample, IQPAIR_CONJUGATE(previous)); 44 | previous = sample; 45 | return -atan2_int16(product.i, product.q); // INT16_MAX / M_PI * atan2(product.i, product.q) 46 | } -------------------------------------------------------------------------------- /fm.h: -------------------------------------------------------------------------------- 1 | #ifndef _FM_H_GUARD 2 | #define _FM_H_GUARD 3 | 4 | int16_t fm_demod(IQPair sample); 5 | 6 | #endif -------------------------------------------------------------------------------- /fsk.c: -------------------------------------------------------------------------------- 1 | #include "rtl_rfm.h" 2 | #include "fsk.h" 3 | 4 | #include "mavg.h" 5 | 6 | int windowsize; 7 | 8 | void fsk_init(int samplerate, int baudrate) 9 | { 10 | windowsize = samplerate / baudrate; 11 | mavg_init(&mavg_filter, windowsize); 12 | } 13 | 14 | void fsk_cleanup() 15 | { 16 | mavg_cleanup(&mavg_filter); 17 | } 18 | 19 | #define SCOPEWIDTH 128 20 | 21 | void print_waveform(int16_t sample, int16_t prevsample, uint8_t thebit, int clk, int32_t magnitude_squared) 22 | { 23 | int y = (SCOPEWIDTH/2) + ((SCOPEWIDTH/2) * sample) / (INT16_MAX); 24 | y = (y < 0) ? 0 : (y > SCOPEWIDTH) ? SCOPEWIDTH : y; 25 | 26 | for (int i = 0; i < SCOPEWIDTH; i++) 27 | { 28 | if (i == y) putchar('X'); 29 | else if (i == (SCOPEWIDTH/2)) putchar('|'); 30 | else putchar(' '); 31 | } 32 | 33 | printf("%.2f", sqrt(magnitude_squared)); 34 | 35 | if ((sample < 0 && prevsample >= 0) || (sample > 0 && prevsample <= 0)) 36 | { 37 | printf("K"); 38 | 39 | if (clk > 0 && clk <= (windowsize/2)) 40 | { 41 | printf ("^%d", clk); // clock has happened recently 42 | } 43 | else if (clk > (windowsize/2) && clk < (windowsize)) 44 | { 45 | printf("v%d", clk); // clock is happening soon 46 | } 47 | else 48 | { 49 | putchar('<'); // clock is locked on 50 | } 51 | } 52 | 53 | if (clk == 0) printf("\t%d", thebit); 54 | 55 | printf("\n"); 56 | } 57 | 58 | extern bool debugplot; 59 | extern int32_t magnitude_squared; 60 | 61 | uint8_t fsk_decode(int16_t sample) 62 | { 63 | static int clk = 1; 64 | static int16_t prevsample = 0; 65 | 66 | clk = (clk + 1) % windowsize; 67 | 68 | uint8_t thebit = (mavg_count(&mavg_filter, sample) > 0) ? 1 : 0; 69 | 70 | // Zero-Crossing Detector for phase correction: 71 | if ((sample < 0 && prevsample >= 0) || (sample >= 0 && prevsample < 0)) 72 | { 73 | if (clk > 0 && clk <= (windowsize/2)) 74 | { 75 | clk -= (windowsize+1); // delay clock 76 | } 77 | else if (clk > (windowsize/2) && clk < (windowsize)) 78 | { 79 | clk = (clk + 1) % windowsize; // advance clock 80 | } // else clock locked on! Nothing to do... 81 | } 82 | 83 | if (debugplot) print_waveform(sample, prevsample, thebit, clk, magnitude_squared); 84 | 85 | prevsample = sample; // record previous sample for the purposes of zero-crossing detection 86 | 87 | return clk ? 2 : thebit; // Process the bit when clock == 0! 88 | } -------------------------------------------------------------------------------- /fsk.h: -------------------------------------------------------------------------------- 1 | #ifndef _FSK_H_GUARD 2 | #define _FSK_H_GUARD 3 | 4 | #include "mavg.h" 5 | 6 | void fsk_init(int samplerate, int baudrate); 7 | void fsk_cleanup(void); 8 | uint8_t fsk_decode(int16_t sample); 9 | 10 | Mavg mavg_filter; 11 | 12 | #endif -------------------------------------------------------------------------------- /mavg.c: -------------------------------------------------------------------------------- 1 | #include "rtl_rfm.h" 2 | #include "mavg.h" 3 | 4 | bool mavg_init(Mavg *filter, uint16_t newsize) { 5 | 6 | filter->size = newsize; 7 | filter->data = malloc(sizeof(int16_t) * newsize); 8 | filter->index = 0; 9 | filter->count = 0; 10 | filter->counthold = 0; 11 | filter->hold = false; 12 | 13 | for (int i = 0; i < newsize; i++) filter->data[i] = 0; 14 | 15 | return true; 16 | } 17 | 18 | bool mavg_cleanup(Mavg *filter) { 19 | 20 | free(filter->data); 21 | 22 | return true; 23 | } 24 | 25 | int32_t mavg_count(Mavg *filter, int16_t sample) { 26 | 27 | filter->index = (filter->index + 1) % filter->size; 28 | filter->count += sample - filter->data[filter->index]; 29 | filter->data[filter->index] = sample; 30 | 31 | return filter->count; 32 | } 33 | 34 | int16_t mavg_hipass(Mavg *filter, int16_t sample) { 35 | 36 | if (!filter->hold) filter->counthold = mavg_count(filter, sample); 37 | return sample - (filter->counthold / filter->size); 38 | } 39 | 40 | int16_t mavg_lopass(Mavg *filter, int16_t sample) { 41 | 42 | return mavg_count(filter, sample) / filter->size; 43 | } -------------------------------------------------------------------------------- /mavg.h: -------------------------------------------------------------------------------- 1 | #ifndef _MAVG_H_GUARD 2 | #define _MAVG_H_GUARD 3 | 4 | typedef struct 5 | { 6 | uint16_t size; 7 | int16_t *data; 8 | uint16_t index; 9 | int32_t count; 10 | int32_t counthold; 11 | bool hold; 12 | } Mavg; 13 | 14 | bool mavg_init(Mavg *filter, uint16_t newsize); 15 | bool mavg_cleanup(Mavg *filter); 16 | int32_t mavg_count(Mavg *filter, int16_t sample); 17 | 18 | int16_t mavg_hipass(Mavg *filter, int16_t sample); 19 | int16_t mavg_lopass(Mavg *filter, int16_t sample); 20 | 21 | #endif -------------------------------------------------------------------------------- /rb.c.UNUSED: -------------------------------------------------------------------------------- 1 | #include "rb.h" 2 | 3 | rb_info_t new_rb(int s) 4 | { 5 | pthread_mutex_t *m = malloc(sizeof(pthread_mutex_t)); 6 | pthread_mutex_init(m, NULL); 7 | 8 | pthread_cond_t *cne = malloc(sizeof(pthread_cond_t)); 9 | pthread_cond_init(cne, NULL); 10 | 11 | pthread_cond_t *cnf = malloc(sizeof(pthread_cond_t)); 12 | pthread_cond_init(cnf, NULL); 13 | 14 | uint16_t *in = malloc(sizeof(uint16_t)); 15 | *in = 0; 16 | 17 | uint16_t *out = malloc(sizeof(uint16_t)); 18 | *out = 0; 19 | 20 | return (rb_info_t) { 21 | .size = s, 22 | .buffer = malloc(sizeof(IQPair) * s), 23 | .rb_in = in, 24 | .rb_out = out, 25 | .resampled_buffer_mutex = m, 26 | .resampled_buffer_not_empty = cne, 27 | .resampled_buffer_not_full = cnf 28 | }; 29 | } 30 | 31 | void free_rb(rb_info_t rb) 32 | { 33 | if (rb.resampled_buffer_mutex) 34 | free(rb.resampled_buffer_mutex); 35 | 36 | if (rb.resampled_buffer_not_empty) 37 | free(rb.resampled_buffer_not_empty); 38 | 39 | if (rb.resampled_buffer_not_full) 40 | free(rb.resampled_buffer_not_full); 41 | 42 | if (rb.rb_in) 43 | free(rb.rb_in); 44 | 45 | if (rb.rb_out) 46 | free(rb.rb_out); 47 | 48 | if (rb.buffer) 49 | free(rb.buffer); 50 | } 51 | 52 | IQPair rb_get(rb_info_t rb) 53 | { 54 | pthread_mutex_lock(rb.resampled_buffer_mutex); 55 | while (*rb.rb_out == *rb.rb_in) 56 | { 57 | pthread_cond_wait(rb.resampled_buffer_not_empty, rb.resampled_buffer_mutex); 58 | } 59 | 60 | IQPair result = rb.buffer[*rb.rb_out]; 61 | *rb.rb_out = (*rb.rb_out + 1) % rb.size; 62 | 63 | pthread_cond_signal(rb.resampled_buffer_not_full); 64 | pthread_mutex_unlock(rb.resampled_buffer_mutex); 65 | 66 | return result; 67 | } 68 | 69 | void rb_put(rb_info_t rb, IQPair sample) 70 | { 71 | pthread_mutex_lock(rb.resampled_buffer_mutex); 72 | while ((*rb.rb_in + 1) % rb.size == *rb.rb_out) 73 | { 74 | fprintf(stderr, "WARNING: WAITING ON FULL BUFFER!\n"); 75 | pthread_cond_wait(rb.resampled_buffer_not_full, rb.resampled_buffer_mutex); 76 | } 77 | 78 | rb.buffer[*rb.rb_in] = sample; 79 | *rb.rb_in = (*rb.rb_in + 1) % rb.size; 80 | 81 | pthread_cond_signal(rb.resampled_buffer_not_empty); 82 | pthread_mutex_unlock(rb.resampled_buffer_mutex); 83 | } 84 | 85 | void rb_cancel(rb_info_t rb) 86 | { 87 | pthread_cond_signal(rb.resampled_buffer_not_full); 88 | pthread_cond_signal(rb.resampled_buffer_not_empty); 89 | } -------------------------------------------------------------------------------- /rb.h.UNUSED: -------------------------------------------------------------------------------- 1 | #ifndef _RB_H_GUARD 2 | #define _RB_H_GUARD 3 | 4 | #include "rtl_rfm.h" 5 | 6 | typedef struct RB_INFO_s 7 | { 8 | int size; 9 | IQPair *buffer; 10 | uint16_t *rb_in; 11 | uint16_t *rb_out; 12 | pthread_mutex_t *resampled_buffer_mutex; // need to init these 13 | pthread_cond_t *resampled_buffer_not_empty; 14 | pthread_cond_t *resampled_buffer_not_full; 15 | } rb_info_t; 16 | 17 | rb_info_t new_rb(int s); 18 | void free_rb(rb_info_t rb); 19 | void rb_cancel(rb_info_t rb); 20 | IQPair rb_get(rb_info_t rb); 21 | void rb_put(rb_info_t rb, IQPair sample); 22 | #endif -------------------------------------------------------------------------------- /rfm_protocol.c: -------------------------------------------------------------------------------- 1 | #include "rtl_rfm.h" 2 | #include "rfm_protocol.h" 3 | 4 | #define CRC_INIT 0x1D0F 5 | #define CRC_POLY 0x1021 6 | #define CRC_POST 0xFFFF 7 | #define AMBLEMASK 0x0000FFFF 8 | #define AMBLE 0x00002DCA 9 | 10 | uint16_t crc = CRC_INIT; 11 | uint16_t thecrc = 0; 12 | CB_VOID open_cb; 13 | CB_VOID close_cb; 14 | 15 | uint8_t packet_buffer[256]; 16 | uint8_t packet_bi = 0; 17 | int bytesexpected = 0; 18 | int bitphase = -1; 19 | uint8_t thisbyte = 0; 20 | uint32_t amble = 0; 21 | 22 | void rfm_init(CB_VOID o, CB_VOID c) 23 | { 24 | open_cb = o; 25 | close_cb = c; 26 | } 27 | 28 | void docrc(uint8_t thebyte) 29 | { 30 | crc = crc ^ (thebyte << 8); 31 | 32 | for (int i = 0; i < 8; i++) 33 | { 34 | crc = crc & 0x8000 ? (crc << 1) ^ CRC_POLY : crc << 1; 35 | } 36 | } 37 | 38 | char *process_byte(uint8_t thebyte, CB_VOID close_cb) 39 | { 40 | char *result = NULL; 41 | 42 | if (bytesexpected < 0) //expecting length byte! 43 | { 44 | bytesexpected = thebyte + 2; // +2 for crc 45 | docrc(thebyte); 46 | return NULL; 47 | } 48 | 49 | if (bytesexpected > 0) 50 | { 51 | if (bytesexpected > 2) 52 | { 53 | packet_buffer[packet_bi++] = thebyte; 54 | docrc(thebyte); 55 | } 56 | else 57 | { 58 | thecrc <<= 8; 59 | thecrc |= thebyte; 60 | } 61 | 62 | bytesexpected--; 63 | } 64 | 65 | if (bytesexpected == 0) 66 | { 67 | result = malloc(sizeof(char) * (packet_bi + 1)); 68 | memcpy(result, packet_buffer, packet_bi); 69 | result[packet_bi] = 0; 70 | 71 | crc ^= CRC_POST; 72 | if (crc == thecrc) 73 | { 74 | printv("CRC OK\n"); 75 | } else { 76 | printv("CRC FAIL! [%02X] [%02X]\n", crc, thecrc); 77 | } 78 | 79 | bitphase = -1; // search for new preamble 80 | 81 | close_cb(); // reset offset hold. 82 | } 83 | 84 | return result; 85 | } 86 | 87 | char *rfm_decode(uint8_t thebit) 88 | { 89 | if (thebit > 1) return NULL; 90 | 91 | if (bitphase < 0) 92 | { 93 | amble = (amble << 1) | (thebit & 0b1); 94 | 95 | if ((amble & AMBLEMASK) == AMBLE)// detect 2 sync bytes "2D4C" = 0010'1101'1100'1010 96 | { 97 | open_cb(); 98 | 99 | packet_bi = 0; // reset the packet buffer 100 | bitphase = 0; // lock to current bit phase 101 | crc = CRC_INIT; // reset CRC engine 102 | bytesexpected = -1; // tell the byte processor to expect a length byte 103 | } 104 | } 105 | else 106 | { 107 | thisbyte = (thisbyte << 1) | (thebit & 0b1); 108 | bitphase++; 109 | 110 | if (bitphase > 7) 111 | { 112 | bitphase = 0; 113 | return process_byte(thisbyte, close_cb); 114 | } 115 | } 116 | 117 | return NULL; 118 | } 119 | 120 | void rfm_reset() 121 | { 122 | bytesexpected = 0; // Length byte not expected yet. 123 | bitphase = -1; // search for new preamble 124 | thisbyte = 0; 125 | amble = 0; 126 | packet_bi = 0; 127 | } -------------------------------------------------------------------------------- /rfm_protocol.h: -------------------------------------------------------------------------------- 1 | #ifndef _RFM_H_GUARD 2 | #define _RFM_H_GUARD 3 | 4 | void rfm_init(CB_VOID o, CB_VOID c); 5 | char *rfm_decode(uint8_t thebit); 6 | void rfm_reset(); 7 | 8 | #endif -------------------------------------------------------------------------------- /rtl_rfm.c: -------------------------------------------------------------------------------- 1 | // rtl_rfm: FSK1200 Decoder 2 | // R. Suchocki 3 | 4 | #include "rtl_rfm.h" 5 | 6 | #include "rtl_sdr_driver.h" 7 | #include "squelch.h" 8 | #include "fm.h" 9 | #include "fsk.h" 10 | #include "rfm_protocol.h" 11 | 12 | #define RESAMP_BUFFER_SIZE 2048 * 32 // 2048 resampled in one burst from driver 13 | 14 | bool quiet = false; 15 | bool debugplot = false; 16 | int freq = 869412500 - 10000; 17 | int gain = 496; // 49.6 18 | int ppm = 0; 19 | int baudrate = 4800; 20 | int channelsamplerate = 20000; 21 | int samplerate = 40000/**4*/; 22 | 23 | RTLSDRInfo_t device; 24 | 25 | Mavg hipass_filter, lopass_filter; 26 | 27 | void printv(const char *format, ...) 28 | { 29 | va_list args; 30 | va_start(args, format); 31 | 32 | if(!quiet) 33 | vprintf(format, args); 34 | 35 | va_end(args); 36 | } 37 | 38 | char *print_sanitize(char* buf) 39 | { 40 | if (buf) 41 | { 42 | for (unsigned int i = 0; i < strlen(buf); i++) 43 | { 44 | uint8_t chr = buf[i]; 45 | 46 | if (chr >= 32) 47 | { 48 | putchar(chr); 49 | } 50 | else 51 | { 52 | printf("[%02X]", chr); 53 | } 54 | } 55 | } 56 | 57 | return buf; 58 | } 59 | 60 | void filter_reset(void) 61 | { 62 | hipass_filter.hold = false; // reset offset hold. 63 | } 64 | 65 | void filter_hold(void) 66 | { 67 | hipass_filter.hold = true; 68 | printv(">> GOT SYNC WORD, "); 69 | float foffset = (float) hipass_filter.counthold * (float) channelsamplerate / (float) INT16_MAX / (float) hipass_filter.size / 2.0; 70 | float eppm = 1000000.0 * foffset / freq; 71 | printv("(OFFSET %.0fHz = %.1f PPM)", foffset, eppm); 72 | } 73 | 74 | void squelch_close_cb(void) 75 | { 76 | rfm_reset(); 77 | filter_reset(); 78 | } 79 | 80 | static inline int16_t bandpass(int16_t sample) 81 | { 82 | return mavg_hipass(&hipass_filter, mavg_lopass(&lopass_filter, sample)); 83 | } 84 | 85 | void channelhandler(IQPair sample) 86 | { 87 | //if (squelch(sample, squelch_close_cb)) 88 | { 89 | TRY_FREE( 90 | print_sanitize( 91 | rfm_decode( 92 | fsk_decode( 93 | bandpass( 94 | fm_demod( 95 | sample)))))); 96 | } 97 | } 98 | 99 | IQDecimator channel1dec = {.acci=0, .accq=0, .count=0, .downsample=2, .samplehandler=channelhandler}; 100 | 101 | typedef struct Oscillator_s 102 | { 103 | int steps; 104 | int phase; 105 | IQPair *lut; 106 | } Oscillator; 107 | 108 | Oscillator *osc_init(int samplerate, int lo_freq) 109 | { 110 | Oscillator *result = malloc(sizeof(Oscillator)); 111 | 112 | result->steps = samplerate/lo_freq; 113 | result->phase = 0; 114 | 115 | result->lut = malloc(sizeof(IQPair) * abs(result->steps)); 116 | 117 | for (int t = 0; t < abs(result->steps); t++) 118 | { 119 | result->lut[t].i = INT8_MAX * cos(2 * M_PI * (float) t / result->steps); 120 | result->lut[t].q = INT8_MAX * sin(2 * M_PI * (float) t / result->steps); 121 | } 122 | 123 | return result; 124 | } 125 | 126 | IQPair osc(Oscillator *o) 127 | { 128 | IQPair result = o->lut[o->phase]; 129 | 130 | o->phase = (o->phase + 1) % (abs(o->steps)); 131 | 132 | return result; 133 | } 134 | 135 | Oscillator *channel1_lo; 136 | 137 | static inline void channelize(IQPair sample) 138 | { 139 | IQPair LO = osc(channel1_lo); 140 | 141 | decimate(&channel1dec, IQPAIR_SCALAR_QUOTIENT(IQPAIR_PRODUCT(sample, LO), INT8_MAX)); 142 | } 143 | 144 | void samplehandlerfn(IQPair sample) 145 | { 146 | sample.i -= 128; 147 | sample.q -= 128; 148 | 149 | //channelhandler(channelize(sample)); 150 | 151 | // Do this for each channel: 152 | channelize(sample); 153 | } 154 | 155 | void intHandler(int signum) 156 | { 157 | printf("\n\n>> Caught signal %d, cancelling...\n", signum); 158 | 159 | rtl_sdr_cancel(device); 160 | } 161 | 162 | int main (int argc, char **argv) 163 | { 164 | channel1_lo = osc_init(samplerate, -10000); 165 | 166 | 167 | 168 | 169 | char *helpmsg = "RTL_RFM, (C) Ryan Suchocki\n" 170 | "\nUsage: rtl_rfm [-hsqd] [-f freq] [-g gain] [-p error] \n\n" 171 | "Option flags:\n" 172 | " -h Show this message\n" 173 | " -q Quiet. Only output good messages\n" 174 | " -d Show Debug Plot\n" 175 | " -f Frequency [869412500]\n" 176 | " -g Gain [49.6]\n" 177 | " -p PPM error\n"; 178 | 179 | int c; 180 | 181 | while ((c = getopt(argc, argv, "hqdf:g:p:")) != -1) 182 | { 183 | switch (c) 184 | { 185 | case 'h': fprintf(stdout, "%s", helpmsg); exit(EXIT_SUCCESS); break; 186 | case 'q': quiet = true; break; 187 | case 'd': debugplot = true; break; 188 | case 'f': freq = atoi(optarg); break; 189 | case 'g': gain = atof(optarg) * 10; break; 190 | case 'p': ppm = atoi(optarg); break; 191 | case '?': 192 | default: exit(EXIT_FAILURE); 193 | } 194 | } 195 | 196 | signal(SIGINT, intHandler); 197 | 198 | printv(">> STARTING RTL_RFM ...\n"); 199 | 200 | float fc = channelsamplerate * 0.443 / (8 * 16); // ~= baudrate * 0.0276; Set at 8*16 symbols so that the filter is centered after the 16-symbol sync word 201 | int hipass_filtersize = (0.443 * channelsamplerate) / fc; // Number of points of mavg filter = (0.443 * Fchannelsamplerate) / Fc 202 | 203 | float fc2 = baudrate * 0.5; 204 | int lopass_filtersize = ((float) (0.443 * (float) channelsamplerate)) / fc2; 205 | 206 | lopass_filtersize = (lopass_filtersize < 1) ? 1 : lopass_filtersize; 207 | 208 | printv(">> Setting filters at '%.2fHz < signal < %.2fHz' (hipass size: %i, lopass size: %i)\n", fc, fc2, hipass_filtersize, lopass_filtersize); 209 | 210 | printv(">> RXBw is %.1fkHz around %.4fMHz.\n", (float)channelsamplerate/1000.0, (float)freq/1000000.0); 211 | 212 | mavg_init(&hipass_filter, hipass_filtersize); 213 | mavg_init(&lopass_filter, lopass_filtersize); 214 | 215 | rfm_init(filter_hold, filter_reset); 216 | fsk_init(channelsamplerate/*/4*/, baudrate); 217 | 218 | int error = hw_init(&device, freq, samplerate, gain, ppm, samplehandlerfn); 219 | 220 | if (error < 0) 221 | { 222 | fprintf(stderr, ">> INIT FAILED. (%d)\n", error); 223 | exit(EXIT_FAILURE); 224 | } 225 | 226 | printv(">> RTL_RFM READY\n\n"); 227 | 228 | int result = 0; 229 | 230 | driver_thread_fn(&device); 231 | 232 | mavg_cleanup(&hipass_filter); 233 | mavg_cleanup(&lopass_filter); 234 | 235 | fsk_cleanup(); 236 | 237 | printv(">> RTL_FM FINISHED. GoodBye!\n"); 238 | 239 | return(result); 240 | } -------------------------------------------------------------------------------- /rtl_rfm.h: -------------------------------------------------------------------------------- 1 | #ifndef _MAIN_H_GUARD 2 | #define _MAIN_H_GUARD 3 | 4 | // Project-wide helper macros: 5 | 6 | #define UNUSED(x) (void)(x) 7 | #define TRY_FREE(it) { if (it) free(it); } 8 | 9 | // Project-wide includes: 10 | 11 | // C Standard Library (POSIX) 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include "IQ16.h" 26 | 27 | // Project-wide globals: 28 | 29 | void printv(const char *format, ...); 30 | 31 | typedef void (*CB_VOID)(void); 32 | 33 | #endif -------------------------------------------------------------------------------- /rtl_sdr_driver.c: -------------------------------------------------------------------------------- 1 | #include "rtl_rfm.h" 2 | #include "rtl_sdr_driver.h" 3 | 4 | // Valid sample rates from the driver lib: 5 | // 225001 - 300000 Hz 6 | // 900001 - 3200000 Hz 7 | // sample loss is to be expected for rates > 2400000 8 | #define ISVALIDSAMPLERATE(x) ((x >= 225001 && x <= 300000) || (x >= 900001 && x <= 3200000)) 9 | #define DRIVER_BUFFER_SIZE (16 * 32 * 512) 10 | 11 | int hw_init(RTLSDRInfo_t *target, int freq, int samplerate, int gain, int ppm, SampleHandler sh) 12 | { 13 | target->dec.downsample = 1; 14 | target->dec.acci = 0; 15 | target->dec.accq = 0; 16 | target->dec.count = 0; 17 | target->dec.samplehandler = sh; 18 | 19 | while (!ISVALIDSAMPLERATE(samplerate * target->dec.downsample)) 20 | { 21 | target->dec.downsample++; 22 | 23 | if (samplerate * target->dec.downsample > 2400000) 24 | { 25 | printf("NO SUITABLE DEVICE SAMPLE RATE!\n"); 26 | return -8; 27 | } 28 | } 29 | 30 | printv("sample rate is %dHz, device rate is %dHz, decimating by %d\n", samplerate, samplerate * target->dec.downsample, target->dec.downsample); 31 | // check bounds? 32 | 33 | if (rtlsdr_open(&target->dev, 0) < 0) return -1; 34 | if (rtlsdr_set_center_freq(target->dev, freq) < 0) return -2; // Set freq before sample rate to avoid "PLL NOT LOCKED" 35 | if (rtlsdr_set_sample_rate(target->dev, samplerate * target->dec.downsample) < 0) return -3; 36 | if (rtlsdr_set_tuner_gain_mode(target->dev, 1) < 0) return -4; 37 | if (rtlsdr_set_tuner_gain(target->dev, gain) < 0) return -5; 38 | if (ppm != 0 && rtlsdr_set_freq_correction(target->dev, ppm) < 0) return -6; 39 | if (rtlsdr_reset_buffer(target->dev) < 0) return -7; 40 | 41 | return 0; 42 | } 43 | 44 | void rtlsdr_callback(uint8_t *buf, uint32_t len, void *context) 45 | { 46 | RTLSDRInfo_t *target = context; 47 | 48 | for (int i = 0; i < (int)len-1; i+=2) 49 | { 50 | decimate(&target->dec, (IQPair) {.i=buf[i], .q=buf[i+1]}); 51 | } 52 | } 53 | 54 | void *driver_thread_fn(void *context) 55 | { 56 | RTLSDRInfo_t *target = context; 57 | 58 | rtlsdr_read_async(target->dev, rtlsdr_callback, target, 0, DRIVER_BUFFER_SIZE); 59 | 60 | rtlsdr_close(target->dev); 61 | 62 | return NULL; 63 | } 64 | 65 | void rtl_sdr_cancel(RTLSDRInfo_t target) 66 | { 67 | rtlsdr_cancel_async(target.dev); 68 | } -------------------------------------------------------------------------------- /rtl_sdr_driver.h: -------------------------------------------------------------------------------- 1 | #ifndef _RTLSDRDRIVER_H_GUARD 2 | #define _RTLSDRDRIVER_H_GUARD 3 | 4 | #include 5 | 6 | typedef struct RTLSDRInfoS 7 | { 8 | rtlsdr_dev_t *dev; 9 | IQDecimator dec; 10 | } RTLSDRInfo_t; 11 | 12 | int hw_init(RTLSDRInfo_t *target, int freq, int samplerate, int gain, int ppm, SampleHandler sh); 13 | void *driver_thread_fn(void *context); 14 | void rtl_sdr_cancel(RTLSDRInfo_t target); 15 | 16 | #endif -------------------------------------------------------------------------------- /squelch.c: -------------------------------------------------------------------------------- 1 | #include "rtl_rfm.h" 2 | #include "squelch.h" 3 | 4 | #define SQUELCH_THRESH 10 5 | #define SQUELCH_NUM 16 6 | 7 | bool squelch_state = false; // 0 is squelched, 1 is receiving 8 | int squelch_count = 0; 9 | 10 | int32_t magnitude_squared; 11 | extern bool debugplot; 12 | 13 | bool squelch(IQPair sample, CB_VOID close_cb) 14 | { 15 | magnitude_squared = sample.i * sample.i + sample.q * sample.q; 16 | 17 | if (squelch_state) 18 | { 19 | if (magnitude_squared < (SQUELCH_THRESH * SQUELCH_THRESH)) 20 | { 21 | squelch_count--; 22 | if(squelch_count <= 0) 23 | { 24 | squelch_state = false; 25 | 26 | if (debugplot) fprintf(stderr, "SQUELCH CLOSE"); 27 | close_cb(); 28 | } 29 | } 30 | else 31 | { 32 | squelch_count = SQUELCH_NUM; 33 | } 34 | 35 | return true; 36 | } 37 | else 38 | { 39 | if (magnitude_squared > (SQUELCH_THRESH * SQUELCH_THRESH)) 40 | { 41 | squelch_count++; 42 | if (squelch_count >= SQUELCH_NUM) 43 | { 44 | squelch_state = true; 45 | 46 | if (debugplot) fprintf(stderr, "SQUELCH OPEN"); 47 | } 48 | } else 49 | { 50 | squelch_count = 0; 51 | } 52 | 53 | return false; 54 | } 55 | } -------------------------------------------------------------------------------- /squelch.h: -------------------------------------------------------------------------------- 1 | #ifndef _SQUELCH_H_GUARD 2 | #define _SQUELCH_H_GUARD 3 | 4 | bool squelch(IQPair sample, CB_VOID close_cb); 5 | 6 | #endif --------------------------------------------------------------------------------