1% 2% fmfsk.m 3% Author: Brady O'Brien 3 Feb 2016 4% Copyright 2016 David Rowe 5% 6% All rights reserved. 7% 8% This program is free software; you can redistribute it and/or modify 9% it under the terms of the GNU Lesser General Public License veRbion 2, as 10% published by the Free Software Foundation. This program is 11% distributed in the hope that it will be useful, but WITHOUT ANY 12% WARRANTY; without even the implied warranty of MERCHANTABILITY or 13% FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public 14% License for more details. 15% 16% You should have received a copy of the GNU Lesser General Public License 17% along with this program; if not, see <http://www.gnu.org/licenses/>. 18 19% mancyfsk.m modem, extracted and made suitable for C implementation 20 21fm; 22pkg load signal; 23pkg load parallel; 241; 25 26% Init fmfsk modem 27%Fs is sample frequency 28%Rb is pre-manchester bit rate 29function states = fmfsk_init(Fs,Rb) 30 assert(mod(Fs,Rb*2)==0); 31 32 %Current fixed processing buffer size, in non-ME bits 33 nbit = 96; 34 35 states.Rb = Rb; 36 states.Rs = Rb*2; % Manchester-encoded bitrate 37 states.Fs = Fs; 38 states.Ts = Fs/states.Rs; 39 states.N = nbit*2*states.Ts; 40 states.nin = states.N; % Samples in the next demod cycle 41 states.nstash = states.Ts*2; % How many samples to stash away between proc cycles for timing adjust 42 states.nmem = states.N+(4*states.Ts); 43 states.nsym = nbit*2; 44 states.nbit = nbit; 45 46 %tates.nsym = floor(states.Rs*.080); 47 %states.nbit = floor(states.Rb*.080) 48 %Old sample memory 49 50 states.oldsamps = zeros(1,states.nmem); 51 52 %Last sampled-stream output, for odd bitstream generation 53 states.lastint = 0; 54 55 %Some stats 56 states.norm_rx_timing = 0; 57 58endfunction 59 60%Generate a stream of manchester-coded bits to be sent 61% to any ordinary FM modulator or VCO or something 62function tx = fmfsk_mod(states,inbits) 63 Ts = states.Ts; 64 tx = zeros(1,length(inbits)*2); 65 for ii = 1:length(inbits) 66 st = 1 + (ii-1)*Ts*2; 67 md = st+Ts-1; 68 en = md+Ts; 69 if inbits(ii)==0 70 tx(st:md) = -ones(1,Ts); 71 tx(md+1:en) = ones(1,Ts); 72 else 73 tx(st:md) = ones(1,Ts); 74 tx(md+1:en) = -ones(1,Ts); 75 end 76 end 77endfunction 78 79%Demodulate a bag of bits from the output of an FM demodulator 80% This function produces nbits output bits and takes states.nin samples 81function [rx_bits states] = fmfsk_demod(states,rx) 82 Ts = states.Ts; 83 Fs = states.Fs; 84 Rs = states.Rs; 85 nin = states.nin; 86 N = states.N; 87 nsym = states.nsym; 88 nbits = states.nsym/2; 89 nmem = states.nmem; 90 nstash = states.nstash; 91 92 nold = nmem-nin; 93 ssamps = states.oldsamps; 94 95 96 %Shift in nin samples 97 ssamps(1:nold) = ssamps(nmem-nold+1:nmem); 98 ssamps(nold+1:nmem) = rx; 99 states.oldsamps = ssamps; 100 101 rx_filt = zeros(1,(nsym+1)*Ts); 102 %Integrate Ts input samples at every offset 103 %This is the same thing as filtering with a filter of all ones 104 % out to Ts. 105 % It's implemented like this for ease of C-porting 106 for ii=(1:(nsym+1)*Ts) 107 st = ii; 108 en = st+Ts-1; 109 rx_filt(ii) = sum(ssamps(st:en)); 110 end 111 states.rx_filt = rx_filt; 112 % Fine timing estimation ------------------------------------------------------ 113 114 % Estimate fine timing using line at Rs/2 that Manchester encoding provides 115 % We need this to sync up to Manchester codewords. 116 Np = length(rx_filt); 117 w = 2*pi*(Rs)/Fs; 118 x = (rx_filt .^ 2) * exp(-j*w*(0:Np-1))'; 119 norm_rx_timing = angle(x)/(2*pi)-.42; 120 121 rx_timing = round(norm_rx_timing*Ts); 122 123 %If rx timing is too far out, ask for more or less sample the next time 124 % around to even it all out 125 next_nin = N; 126 if norm_rx_timing > -.2; 127 next_nin += Ts/2; 128 end 129 if norm_rx_timing < -.65; 130 next_nin -= Ts/2; 131 end 132 133 states.nin = next_nin; 134 states.norm_rx_timing = norm_rx_timing; 135 %'Even' and 'Odd' manchester bitstream. 136 % We'll figure out which to produce later 137 rx_even = zeros(1,nbits); 138 rx_odd = zeros(1,nbits); 139 apeven = 0; 140 apodd = 0; 141 142 sample_offset = (Ts/2)+Ts+rx_timing-1; 143 144 symsamp = zeros(1,nsym); 145 146 % Figure out the bits of the 'even' and 'odd' ME streams 147 % Also sample rx_filt offset by what fine timing determined along the way 148 % Note: ii is a zero-indexed array pointer, for less mind-breaking c portage 149 lastv = states.lastint; 150 for ii = (0:nsym-1) 151 currv = rx_filt(sample_offset+(ii*Ts)+1); 152 mdiff = lastv-currv; 153 lastv = currv; 154 mbit = mdiff>0; 155 symsamp(ii+1) = currv; 156 if mod(ii,2)==1 157 apeven += abs(mdiff); 158 rx_even( floor(ii/2)+1 ) = mbit; 159 else 160 apodd += abs(mdiff); 161 rx_odd( floor(ii/2)+1 ) = mbit; 162 end 163 end 164 states.symsamp = symsamp; 165 % Decide on the correct ME alignment 166 if(apeven>apodd) 167 rx_bits = rx_even; 168 else 169 rx_bits = rx_odd; 170 end 171 172 states.lastint = lastv; 173endfunction 174 175% run_sim copypasted from fsk_horus.m 176% simulation of tx and rx side, add noise, channel impairments ---------------------- 177 178function fmfsk_run_sim(EbNodB,timing_offset=0,de=0,of=0,hpf=0) 179 test_frame_mode = 2; 180 frames = 70; 181 %EbNodB = 3; 182 %timing_offset = 0.0; % see resample() for clock offset below 183 %fading = 0; % modulates tx power at 2Hz with 20dB fade depth, 184 % to simulate balloon rotating at end of mission 185 df = 0; % tx tone freq drift in Hz/s 186 dA = 1; % amplitude imbalance of tones (note this affects Eb so not a gd idea) 187 188 more off 189 rand('state',1); 190 randn('state',1); 191 192 Fs = 48000; 193 Rbit = 2400; 194 195 % ---------------------------------------------------------------------- 196 197 fm_states.pre_emp = 0; 198 fm_states.de_emp = de; 199 fm_states.Ts = Fs/(Rbit*2); 200 fm_states.Fs = Fs; 201 fm_states.fc = Fs/4; 202 fm_states.fm_max = 3E3; 203 fm_states.fd = 5E3; 204 fm_states.output_filter = of; 205 fm_states = analog_fm_init(fm_states); 206 207 % ---------------------------------------------------------------------- 208 209 states = fmfsk_init(Fs,Rbit); 210 211 states.verbose = 0x1; 212 Rs = states.Rs; 213 nsym = states.nsym; 214 Fs = states.Fs; 215 nbit = states.nbit; 216 217 EbNo = 10^(EbNodB/10); 218 variance = states.Fs/(states.Rb*EbNo); 219 220 % set up tx signal with payload bits based on test mode 221 222 if test_frame_mode == 1 223 % test frame of bits, which we repeat for convenience when BER testing 224 test_frame = round(rand(1, states.nbit)); 225 tx_bits = []; 226 for i=1:frames+1 227 tx_bits = [tx_bits test_frame]; 228 end 229 end 230 if test_frame_mode == 2 231 % random bits, just to make sure sync algs work on random data 232 tx_bits = round(rand(1, states.nbit*(frames+1))); 233 end 234 if test_frame_mode == 3 235 % repeating sequence of all symbols 236 % great for initial test of demod if nothing else works, 237 % look for this pattern in rx_bits 238 239 % ...10101... 240 tx_bits = zeros(1, states.nbit*(frames+1)); 241 tx_bits(1:2:length(tx_bits)) = 1; 242 243 end 244 245 load fm_radio_filt_model.txt 246 247 [b, a] = cheby1(4, 1, 300/Fs, 'high'); % 300Hz HPF to simulate FM radios 248 249 tx_pmod = fmfsk_mod(states, tx_bits); 250 tx = analog_fm_mod(fm_states, tx_pmod); 251 252 tx = tx(10:length(tx)); 253 254 if(timing_offset>0) 255 tx = resample(tx, 1000,1001); % simulated 1000ppm sample clock offset 256 end 257 258 259 noise = sqrt(variance)*randn(length(tx),1); 260 rx = tx + noise'; 261 262 %Demod by analog fm 263 rx = analog_fm_demod(fm_states, rx); 264 265 %High-pass filter to simulate the FM radios 266 if hpf>0 267 printf("high-pass filtering!\n") 268 rx = filter(b,a,rx); 269 end 270 rx = filter(filt,1,rx); 271 272 figure(4) 273 title("Spectrum of rx-ed signal after FM demod and FM radio channel"); 274 plot(20*log10(abs(fft(rx)))) 275 figure(5) 276 title("Time domain of rx-ed signal after FM demod and FM radio channel"); 277 plot(rx) 278 %rx = real(rx); 279 %b1 = fir2(100, [0 4000 5200 48000]/48000, [1 1 0.5 0.5]); 280 %rx = filter(b1,1,rx); 281 %[b a] = cheby2(6,40,[3000 6000]/(Fs/2)); 282 %rx = filter(b,a,rx); 283 %rx = sign(rx); 284 %rx(find (rx > 1)) = 1; 285 %rx(find (rx < -1)) = -1; 286 287 % dump simulated rx file 288 289 timing_offset_samples = round(timing_offset*states.Ts); 290 st = 1 + timing_offset_samples; 291 rx_bits_buf = zeros(1,2*nbit); 292 x_log = []; 293 timing_nl_log = []; 294 norm_rx_timing_log = []; 295 f_int_resample_log = []; 296 f_log = []; 297 EbNodB_log = []; 298 rx_bits_log = []; 299 rx_bits_sd_log = []; 300 301 for f=1:frames 302 303 % extract nin samples from input stream 304 305 nin = states.nin; 306 en = st + states.nin - 1; 307 sf = rx(st:en); 308 st += nin; 309 310 % demodulate to stream of bits 311 312 [rx_bits states] = fmfsk_demod(states, sf); 313 314 rx_bits_buf(1:nbit) = rx_bits_buf(nbit+1:2*nbit); 315 rx_bits_buf(nbit+1:2*nbit) = rx_bits; 316 rx_bits_log = [rx_bits_log rx_bits]; 317 318 end 319 320 ber = 1; 321 ox = 1; 322 rx_bits = rx_bits_log; 323 bitcnt = length(tx_bits); 324 for offset = (1:100) 325 nerr = sum(xor(rx_bits(offset:length(rx_bits)),tx_bits(1:length(rx_bits)+1-offset))); 326 bern = nerr/(bitcnt-offset); 327 if(bern < ber) 328 ox = offset; 329 best_nerr = nerr; 330 end 331 ber = min([ber bern]); 332 end 333 offset = ox; 334 figure(3); 335 plot(xor(rx_bits(ox:length(rx_bits)),tx_bits(1:length(rx_bits)+1-ox))) 336 337 printf("BER: %f Errors: %d Bits:%d\n",ber,best_nerr,bitcnt-offset); 338 339 endfunction 340 341 342% demodulate a file of 8kHz 16bit short samples -------------------------------- 343 344 345 346 347