├── 7220-5.7.1.9 └── cm-sdr.ld ├── LICENSE ├── Makefile ├── README.md ├── cm-sdr.c ├── cm-sdr.h ├── external.h ├── fm-stream.py ├── tune.py └── upload.py /7220-5.7.1.9/cm-sdr.ld: -------------------------------------------------------------------------------- 1 | memset = 0x80522d7c; 2 | memcpy = 0x80004f30; 3 | malloc = 0x80596998; 4 | printf = 0x8052b178; 5 | socket = 0x80332fd0; 6 | close = 0x80412978; 7 | bind = 0x800ae7bc; 8 | listen = 0x80412ed4; 9 | accept = 0x80413118; 10 | send = 0x80413240; 11 | recv = 0x804134bc; 12 | recvfrom = 0x804135cc; 13 | sendto = 0x804133d8; 14 | create_thread = 0x80387ecc; 15 | start_thread = 0x80388198; 16 | tune_aux_channel = 0x80082108; 17 | int2double = 0x80010f30; 18 | double_div = 0x80010c98; 19 | sleep = 0x802f3a88; 20 | sleep2 = 0x8045d808; 21 | sem_init = 0x802f3a10; 22 | sem_wait = 0x80409384; 23 | sem_post = 0x80409500; 24 | sem_getcount = 0x804095e8; 25 | 26 | SECTIONS 27 | { 28 | . = 0x80810000; 29 | .start : { *(.start) } 30 | .text : { *(.text) } 31 | .data : { *(.data) } 32 | .rodata : { *(.rodata) } 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 stdw 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PLATFORM=7220-5.7.1.9 2 | TCPREFIX=mips-linux- 3 | CC=$(TCPREFIX)gcc 4 | OBJCOPY=$(TCPREFIX)objcopy 5 | LDSCRIPT=$(PLATFORM)/cm-sdr.ld 6 | CFLAGS=-march=mips32 -mabi=eabi -msoft-float -mno-abicalls -fno-builtin -nostdlib -nodefaultlibs -nostartfiles -T $(LDSCRIPT) 7 | 8 | default: cm-sdr.bin 9 | 10 | cm-sdr.elf: cm-sdr.c 11 | $(CC) cm-sdr.c -o $@ $(CFLAGS) 12 | 13 | cm-sdr.bin: cm-sdr.elf 14 | $(OBJCOPY) -O binary -j .start -j .text -j .data -j .rodata $< $@ 15 | 16 | 17 | .PHONY: clean 18 | 19 | clean: 20 | rm -f cm-sdr.elf cm-sdr.bin 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cm-sdr 2 | 3 | cm-sdr is a piece of software to turn a Broadcom BCM3383-based cable modem into 4 | a general purpose software defined radio. 5 | 6 | ## Status 7 | 8 | In its current state, cm-sdr serves as a proof of concept. It can currently 9 | stream I/Q data to a peer via TCP. A script to demodulate and play FM radio 10 | broadcasts is also provided. 11 | 12 | cm-sdr is dependent on many functions and memory mapped registers in the 13 | unmodified firmare. Thus, at the moment it is highly tailored to one specific 14 | cable modem model and firmware version. 15 | 16 | ## TODO 17 | 18 | * Figure out how to decrease sample rate 19 | * Transmitting 20 | * Usability improvemnts 21 | * Porting 22 | 23 | ## Usage 24 | 25 | ### Building 26 | 27 | * Download the toolchain: https://github.com/Broadcom/stbgcc-4.8/releases/download/stbgcc-4.8-1.0/stbgcc-4.8-1.0.tar.bz2 28 | * Extract it and add it to your path `export PATH=/opt/toolchains/stbgcc-4.8-1.0/bin/:$PATH` 29 | * Build with `make` 30 | 31 | ### Running 32 | * Ensure Python 3 is installed 33 | * Load binary with the provided python script `python3 upload.py` 34 | * Connect to modem and begin recieving data `nc 192.168.100.1 1337 > data` 35 | * To run FM demo, ensure numpy, scipy, and sounddevice are installed 36 | * Use the tune.py script and netcat to set the frequency and pipe the data into the demodulation script `python3 tune.py --freq 107900000 | nc 192.168.100.1 1337 | python3 fm-stream.py` 37 | -------------------------------------------------------------------------------- /cm-sdr.c: -------------------------------------------------------------------------------- 1 | #include "external.h" 2 | #include "cm-sdr.h" 3 | 4 | #define SAMPLE_SIZE 8 /* bytes per sample */ 5 | #define BLOCK 1024*1024*2 /* number of samples per buffer */ 6 | #define NUM_BUFS 3 /* number of buffers */ 7 | 8 | #define BIND_ADDR 0xc0a86401 /* 192.168.100.1 */ 9 | #define PORT 1337 /* listen port */ 10 | #define IP_STACK 2 11 | 12 | /* Globals */ 13 | 14 | /* holds frequency and downsampling factor */ 15 | cm_sdr_cfg cfg = { 16 | buf: {0} 17 | }; 18 | 19 | int* buf[NUM_BUFS]; 20 | int* r_sem[NUM_BUFS]; /* read ready */ 21 | int* w_sem[NUM_BUFS]; /* write ready */ 22 | int connected = 0; /* connection status */ 23 | int r_index = 0; /* current read (send) buffer */ 24 | int w_index = 0; /* current write buffer */ 25 | 26 | 27 | 28 | int __start(void) 29 | { 30 | int i; 31 | int res; 32 | int id = 0; 33 | 34 | /* initialize buffers */ 35 | buf[0] = (int*) malloc(BLOCK * SAMPLE_SIZE * NUM_BUFS); 36 | memset(buf[0], 0, BLOCK * SAMPLE_SIZE * NUM_BUFS); 37 | 38 | for (i = 0; i < NUM_BUFS; i++) 39 | { 40 | if (i != 0) 41 | buf[i] = buf[i-1] + (BLOCK * SAMPLE_SIZE / sizeof(int)); 42 | /* 43 | * Set write semaphore inital value to 1 so they are available 44 | * immediately. 45 | */ 46 | r_sem[i] = sem_init(1, 1, 0, "cm-sdr-r"); 47 | w_sem[i] = sem_init(1, 1, 1, "cm-sdr-w"); 48 | } 49 | 50 | /* start the sampling and network threads */ 51 | res = create_thread("cm-sdr", 22, 0x2000, &id); 52 | res = start_thread(id, (void*)sampler, NULL); 53 | 54 | id = 0; 55 | res = create_thread("cm-sdr-net", 16, 0x2000, &id); 56 | res = start_thread(id, (void*)net, NULL); 57 | 58 | return res; 59 | } 60 | 61 | int net(void) 62 | { 63 | void* sock; 64 | void* conn; 65 | sock_addr_t sa = {0}; 66 | int n = 1; 67 | int i; 68 | unsigned int sent = 0; 69 | int* end; 70 | unsigned int size; 71 | int recvd = 0; 72 | 73 | if (NULL == (sock = socket(AF_INET, 1, 6, IP_STACK))) 74 | return -1; 75 | 76 | sa.addr = BIND_ADDR; 77 | 78 | if (0 != bind(sock, &sa, PORT)) 79 | return -2; 80 | 81 | if (0 != listen(sock, 1)) 82 | return -3; 83 | 84 | while (1) 85 | { 86 | if (NULL == (conn = accept(sock, 0, 0))) 87 | return -4; 88 | 89 | recvd = 0; 90 | while (recvd < sizeof(cfg.buf)) 91 | { 92 | i = recv(conn, cfg.buf + recvd, sizeof(cfg.buf) - recvd, 0); 93 | if (i <= 0) 94 | { 95 | close(conn); 96 | conn = NULL; 97 | break; 98 | } 99 | recvd += i; 100 | } 101 | 102 | if (!conn) 103 | continue; 104 | 105 | /* set tuner frequency */ 106 | tune_aux_channel(double_div(int2double(cfg.cfg_freq), 1000000.0)); 107 | sleep2(10); 108 | 109 | connected = 1; 110 | while(connected) 111 | { 112 | /* wait for data */ 113 | sem_wait(r_sem[r_index], 1, 0); 114 | /* compact the data in place */ 115 | end = compact(buf[r_index], buf[r_index], BLOCK); 116 | 117 | sent = 0; 118 | size = (end - buf[r_index]) * sizeof(unsigned int*); 119 | while (sent < size && n > 0) { 120 | n = send(conn, (char*)buf[r_index] + sent, size - sent, 0); 121 | sent += n; 122 | } 123 | 124 | /* buffer is ready to be written to again */ 125 | sem_post(w_sem[r_index]); 126 | r_index = (r_index + 1) % NUM_BUFS; 127 | 128 | /* do this check last so the semaphores will be ready */ 129 | if (n <= 0) { 130 | /* close socket and wait for new connection */ 131 | connected = 0; 132 | n = 1; 133 | close(conn); 134 | conn = NULL; 135 | } 136 | } 137 | } 138 | } 139 | 140 | int sampler(void) 141 | { 142 | /* wait for connection before reading */ 143 | while (1) 144 | { 145 | while (!connected) 146 | { 147 | sleep(500); 148 | } 149 | read_data(); 150 | } 151 | return 0; 152 | } 153 | 154 | void read_data(void) 155 | { 156 | volatile unsigned int* t; 157 | t = (volatile unsigned int*)0xb3c1e858; 158 | *t &= 0xffff7fff; 159 | 160 | t = (volatile unsigned int*)0xb4e001e8; 161 | *t &= 0xfffc0fff; 162 | *t |= 0x00016000; 163 | 164 | /* buffer size in bytes */ 165 | t = (volatile unsigned int*)0xb4e00170; 166 | *t = BLOCK * SAMPLE_SIZE; 167 | 168 | t = (volatile unsigned int*) 0xb4e0007c; 169 | *t = 0x003f0000; 170 | 171 | t = (volatile unsigned int*) 0xb4e0016c; 172 | *t = 0x00000006; 173 | 174 | t = (volatile unsigned int*) 0xb4e00278; 175 | *t |= 0x00000040; 176 | 177 | /* set adc to 14? bit mode */ 178 | t = (volatile unsigned int*) 0xb3c10008; 179 | *t &= 0xffff0000; 180 | *t |= 0x00000008; 181 | /* *t |= 0x00000108; */ 182 | 183 | while (connected) 184 | { 185 | /* wake up when writable */ 186 | sem_wait(w_sem[w_index], 1, 0); 187 | 188 | /* set destination buffer (physical address) */ 189 | t = (volatile unsigned int*)0xb4e00178; 190 | *t = (volatile unsigned int)(buf[w_index]) & 0x1fffffff; 191 | 192 | /* set status bit */ 193 | t = (volatile unsigned int*) 0xb4e0016c; 194 | *t |= 0x00000001; 195 | 196 | /* wait for completion */ 197 | while (*t & 1) 198 | { 199 | sleep(2); 200 | } 201 | 202 | sem_post(r_sem[w_index]); /* indicate buffer is ready to read */ 203 | w_index = (w_index + 1) % NUM_BUFS; 204 | } 205 | 206 | 207 | t = (volatile unsigned int*) 0xb3c1e858; 208 | *t |= 0x00008000; 209 | 210 | t = (volatile unsigned int*) 0xb4e00278; 211 | *t &= 0xffffffbf; 212 | } 213 | 214 | unsigned int* compact(unsigned int* dest, unsigned int* src, unsigned int size) 215 | { 216 | unsigned int* p = dest; 217 | unsigned int* end; 218 | unsigned int* i = src; 219 | unsigned int* q = src + 1; 220 | 221 | /* 222 | * If the 0x200000 bit is not set, the word is a Q value so move past 223 | * it to the first I value and also drop the last word as well 224 | * because it is a lone I value with no Q. 225 | */ 226 | if (!(src[0] & 0x200000)) 227 | { 228 | i += 1; 229 | q += 1; 230 | src += 1; 231 | size -= 2; 232 | } 233 | 234 | 235 | end = src + (size * (SAMPLE_SIZE / sizeof(unsigned int))); 236 | while (q < end) 237 | { 238 | if ((*i & 0x3fff) > 0x2000) /* I is negative */ 239 | *p = ((*i & 0x3fff) - 0x4000) << 16; 240 | else 241 | *p = (*i << 16); 242 | if ((*q & 0x3fff) > 0x2000) /* Q is negative */ 243 | *p |= ((*q & 0x3fff) - 0x4000) & 0xffff; 244 | else 245 | *p |= (*q & 0xffff); 246 | p += 1; 247 | /* 248 | * skip samples to reduce sample rate, speed up this function, 249 | * and decrese the amount of data to send back 250 | */ 251 | i += 2 * cfg.cfg_downsample; 252 | q += 2 * cfg.cfg_downsample; 253 | } 254 | return p; 255 | } 256 | -------------------------------------------------------------------------------- /cm-sdr.h: -------------------------------------------------------------------------------- 1 | #ifndef _CM_SDR_H 2 | #define _CM_SDR_H 3 | 4 | typedef union { 5 | char buf[sizeof(unsigned int)*2]; 6 | struct { 7 | unsigned int _freq; 8 | unsigned int _downsample; 9 | } __attribute__((packed)) cfg; 10 | } cm_sdr_cfg; 11 | 12 | #define cfg_freq cfg._freq 13 | #define cfg_downsample cfg._downsample 14 | 15 | 16 | /* 17 | * Entrypoint 18 | */ 19 | int __start(void) __attribute__((section(".start"))); 20 | 21 | /* 22 | * Sampler thread 23 | */ 24 | int sampler(void); 25 | 26 | /* 27 | * Network thread 28 | */ 29 | int net(void); 30 | 31 | 32 | /* 33 | * Read samples while connected 34 | */ 35 | void read_data(void); 36 | 37 | /* 38 | * Copy the samples from the start buffer into the dest buffer, packing the I 39 | * and Q values into one 4 byte word and decimating. 40 | */ 41 | unsigned int* compact(unsigned int* dest, unsigned int* src, unsigned int size); 42 | 43 | #endif /* _CM_SDR_H */ 44 | -------------------------------------------------------------------------------- /external.h: -------------------------------------------------------------------------------- 1 | #ifndef _EXTERNAL_H 2 | #define _EXTERNAL_H 3 | 4 | /* constants */ 5 | #define NULL (void*) 0 6 | 7 | #define AF_INET 0x02 8 | #define AF_INET6 0x1C 9 | 10 | 11 | /* structs */ 12 | 13 | typedef struct sock_addr_s { 14 | unsigned int _pad1; 15 | unsigned int _pad2; 16 | unsigned int addr; 17 | unsigned int _pad4; 18 | unsigned int _pad5; 19 | unsigned int _pad6; 20 | unsigned int _pad7; 21 | unsigned int _pad8; 22 | } sock_addr_t; 23 | 24 | 25 | 26 | /* stdlib functions */ 27 | 28 | void* memset(void* buf, int c, unsigned int n); 29 | void* memcpy(void* dst, const void* src, int n); 30 | void* malloc(unsigned int size); 31 | int printf(char* str, ...); 32 | 33 | 34 | /* network functions */ 35 | 36 | /* 37 | * Create a new socket with IP protocol ip_proto on IP stack number ip_stack 38 | * _a and _b are unknown, use 1 and 6 respectively for IPv4 TCP. 39 | * Use 2 and 0x11 for UDP. 40 | * Returns NULL on failure and pointer to socket handle on success. 41 | */ 42 | void* socket(int ip_proto, int _a, int _b, int ip_stack); 43 | 44 | /* 45 | * Close the give socket. 46 | */ 47 | void close(void* handle); 48 | 49 | /* 50 | * Bind the valid socket handle to the given address and port. 51 | * Returns 0 on success. 52 | */ 53 | int bind(void* handle, void* addr, int port); 54 | 55 | /* 56 | * Listen for a TCP connection on the bound socket. 57 | * Arg _a is unknown, probably backlog like posix listen(). Use 1. 58 | * Returns 0 on success. 59 | */ 60 | int listen(void* handle, int _a); 61 | 62 | /* 63 | * Accept a connection on a listening socket. 64 | * Args _a and _b are unknown, use 0 and 0 respectively. 65 | * Returns a pointer to a new socket handle on success or NULL on failure. 66 | */ 67 | void* accept(void* handle, int _a, int _b); 68 | 69 | /* 70 | * Send len bytes of data at the buffer buf on the connection handle. 71 | * Arg _a is unknown, use 0. 72 | * Returns the number of bytes send on success. 73 | */ 74 | int send(void* handle, char* buf, int len, int _a); 75 | 76 | /* 77 | * Read up to len bytes of data from the connection handle into buffer buf. 78 | * Arg _a is unknown, use 0. 79 | * Returns the number of bytes read on success. 80 | */ 81 | int recv(void* handle, char* buf, int len, int _a); 82 | 83 | /* 84 | * For UDP sockets 85 | */ 86 | int recvfrom(void* handle, char* buf, int len, int flags, int* addr, int* addrlen); 87 | int sendto(void* handle, char* buf, int len, int flags, int* addr, int addrlen); 88 | 89 | 90 | 91 | /* transceiver functions */ 92 | 93 | /* 94 | * Set the freqency of the extra channel used by the spectrum analyzer 95 | */ 96 | void tune_aux_channel(double mhz_freq); 97 | 98 | 99 | /* misc functions */ 100 | 101 | /* 102 | * Convert int to double because casting results in 103 | * "undefined reference to `__floatunsidf'" 104 | */ 105 | double int2double(int i); 106 | 107 | /* 108 | * Divide a by b. 109 | */ 110 | double double_div(double a, double b); 111 | 112 | /* 113 | * Sleep for t milliseconds. 114 | */ 115 | void sleep(unsigned int t); 116 | 117 | /* 118 | * Wait for t system ticks. Doesn't seem to cause a context switch. Mostly 119 | * used to let hardware settle after setting certain registers. 120 | */ 121 | void sleep2(unsigned int t); 122 | 123 | /* 124 | * Create a thread with the given name, priority between 1 and 30(?) or 0 for 125 | * auto, stack size and a pointer to be filled with the thread id. 126 | */ 127 | int create_thread(char* name, int priority, unsigned int stack_size, int* id); 128 | 129 | /* 130 | * Start a thread with the valid id returned from create_thread, running the 131 | * specifed function. Unknown how params work, use zero arg functions and pass 132 | * NULL for now. 133 | */ 134 | int start_thread(int id, void* function, void* params); 135 | 136 | /* 137 | * Create a counting semaphore with the specified maximum and inital values 138 | * and name. _a is unknown, use 1. 139 | */ 140 | int* sem_init(int _a, int max, int initial, char* name); 141 | 142 | /* 143 | * Reduce the semaphore count by 1 or block if it is currently 0. Set block to 144 | * 1 to block forever or to 0 to block for a maximum of timeout seconds. 145 | */ 146 | int sem_wait(int* sem, int block, int timeout); 147 | 148 | /* 149 | * Add one to the given semaphore. 150 | */ 151 | int sem_post(int* sem); 152 | 153 | /* 154 | * Get the current count of the given semaphore. 155 | */ 156 | int sem_getcount(int* sem); 157 | 158 | #endif /* _EXTERNAL_H */ 159 | -------------------------------------------------------------------------------- /fm-stream.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import argparse 3 | import time 4 | import queue 5 | import threading 6 | import numpy as np 7 | import matplotlib.pyplot as plt 8 | import scipy.signal as signal 9 | import scipy.fftpack 10 | import sounddevice as sd 11 | from scipy.io.wavfile import write 12 | 13 | parser = argparse.ArgumentParser() 14 | parser.add_argument('--downsample', default=32, type=int, help='Downsampling factor of input. Default = 32') 15 | parser.add_argument('--shift', default=0.0, type=float, help='Freqency shift in Mhz.') 16 | parser.add_argument('--bandwidth', default=200000, type=int, help='Bandwidth of channel in hertz.') 17 | args = parser.parse_args() 18 | 19 | 20 | OUT_FS = 48000 # target 21 | FS_STRETCH = 0.95 22 | SND_DEV_FS = 48000 23 | 24 | IN_FS = int(15000000 / args.downsample) 25 | SHIFT = 1.0e6 * args.shift 26 | 27 | def demod(raw): 28 | SAMPLE_RATE = IN_FS 29 | 30 | # read and format data 31 | data = np.frombuffer(raw, dtype=np.dtype('>i2')).astype(np.float32).view(np.complex64) 32 | 33 | # center 34 | data=data * np.exp(-1j*2*np.pi*SHIFT*np.arange(start=0,step=1,stop=data.size)/SAMPLE_RATE) 35 | 36 | # decimate 37 | target = args.bandwidth 38 | downsample = int(SAMPLE_RATE / target) 39 | 40 | if downsample > 1: 41 | data = signal.decimate(data, downsample, ftype='fir') 42 | SAMPLE_RATE /= downsample 43 | 44 | # fm demodlation 45 | # https://stackoverflow.com/questions/53706653/continuously-play-sampled-audio-data-in-python 46 | data = data[1:] * np.conj(data[:-1]) 47 | data = np.angle(data) 48 | 49 | target = OUT_FS 50 | data_time = np.size(data)/SAMPLE_RATE 51 | out_samples = int(data_time * target) 52 | data = signal.resample(data, out_samples) 53 | SAMPLE_RATE = target 54 | 55 | # de-emphasis filter 56 | d = SAMPLE_RATE * 75e-6 # Calculate the # of samples to hit the -3dB point 57 | x1 = np.exp(-1/d) # Calculate the decay between each sample 58 | b = [1-x1] # Create the filter coefficients 59 | a = [1,-x1] 60 | data = signal.lfilter(b,a,data) 61 | 62 | # normalize 63 | if np.max(np.abs(data)) == 0: 64 | data = np.int16(data) 65 | else: 66 | data = np.int16(data/np.max(np.abs(data)) * 32767) 67 | 68 | return data.tobytes() 69 | 70 | 71 | 72 | chunk = 1024*512 73 | blksize = int(len(demod(bytes(chunk))) / 2) 74 | 75 | q = queue.Queue(maxsize=5) 76 | event = threading.Event() 77 | 78 | 79 | def callback(outdata, frames, time1, status): 80 | global OUT_FS 81 | global chunk 82 | data = b'' 83 | 84 | try: 85 | data = q.get_nowait() 86 | except queue.Empty: # if queue if empty slow down consumption 87 | OUT_FS = int(OUT_FS / FS_STRETCH) 88 | chunk = (int(chunk*FS_STRETCH) // 4) * 4 89 | print(f'Queue is empty. Increased sample rate: {OUT_FS}', file=sys.stderr) 90 | 91 | if len(data) < len(outdata): 92 | outdata[:len(data)] = data 93 | outdata[len(data):] = b'\x00' * (len(outdata) - len(data)) 94 | else: 95 | outdata[:] = data[:len(outdata)] 96 | 97 | 98 | for i in range(2): 99 | data = demod(sys.stdin.buffer.read(chunk)) 100 | q.put_nowait(data) 101 | 102 | stream = sd.RawOutputStream( 103 | samplerate=SND_DEV_FS, blocksize=blksize, 104 | device=0, channels=1, dtype='int16', 105 | callback=callback, finished_callback=event.set) 106 | 107 | with stream: 108 | raw = sys.stdin.buffer.read(chunk) 109 | while len(raw) > 0: 110 | data = demod(raw) 111 | try: 112 | q.put_nowait(data) 113 | except: # if queue is full, speed up consumption 114 | OUT_FS = int(OUT_FS * FS_STRETCH) 115 | chunk = (int(chunk/FS_STRETCH) // 4) * 4 116 | print(f'Queue is full. Decreased sample rate: {OUT_FS}') 117 | raw = sys.stdin.buffer.read(chunk) 118 | event.wait() 119 | 120 | 121 | #if __name__ == '__main__': 122 | # main() 123 | -------------------------------------------------------------------------------- /tune.py: -------------------------------------------------------------------------------- 1 | '''generate the configuration to tune and set the decimation factor''' 2 | import argparse 3 | import struct 4 | import sys 5 | 6 | parser = argparse.ArgumentParser() 7 | parser.add_argument('--freq', type=int, required=True, help='Tuner center frequency in hertz') 8 | parser.add_argument('--downsample', type=int, default=32, help='Downsampling factor. Default = 32') 9 | args = parser.parse_args() 10 | 11 | cfg = struct.pack('>II', args.freq, args.downsample) 12 | sys.stdout.buffer.write(cfg) 13 | -------------------------------------------------------------------------------- /upload.py: -------------------------------------------------------------------------------- 1 | import telnetlib 2 | import time 3 | 4 | HOST = '192.168.100.1' 5 | USER = b'technician' 6 | PASS = b'abcd' 7 | 8 | DEST = 0x80810000 9 | FILE = 'cm-sdr.bin' 10 | 11 | def main(): 12 | t = telnetlib.Telnet(HOST) 13 | get(t, b'Login:') 14 | t.write(USER + b'\r\n') 15 | get(t, b'Password:') 16 | t.write(PASS + b'\r\n') 17 | if b'Console' in get(t, b'>'): 18 | authenticate(t) 19 | 20 | t.write(b'cd ..\r\n') 21 | stop_scan(t) 22 | 23 | upload_file(t, FILE, DEST) 24 | 25 | # execute function at start address 26 | t.write(b'call func -r -a ' + bytes(hex(DEST), 'utf-8') + b'\r\n') 27 | get(t, b'Return') 28 | 29 | 30 | # get/print output up to stop 31 | def get(t, stop): 32 | output = t.read_until(stop) 33 | print(output.decode('latin-1')) 34 | return output 35 | 36 | # get full permissions 37 | def authenticate(t): 38 | t.write(b'su\r\n') 39 | get(t, b':') 40 | t.write(b'brcm\r\n') 41 | get(t, b'>') 42 | 43 | # delete task that floods output 44 | def stop_scan(t): 45 | t.write(b'cd cm_hal\r\n') 46 | get(t, b'>') 47 | t.write(b'scan_stop\r\n') 48 | get(t, b'>') 49 | t.write(b'cd ..\r\n') 50 | get(t, b'>') 51 | 52 | # write file to RAM 53 | def upload_file(t, filename, address): 54 | with open(filename, 'rb') as f: 55 | payload = f.read() 56 | 57 | dest = address 58 | for off in range(0, len(payload), 4): 59 | addr = hex(dest + off) 60 | val = payload[off:off+4].hex() 61 | cmd = bytes(f'write_memory -s 4 {addr} 0x{val}\r\n', 'utf-8') 62 | t.write(cmd) 63 | t.read_until(b'>') 64 | 65 | 66 | if __name__ == '__main__': 67 | main() 68 | --------------------------------------------------------------------------------