Started switching to pilot symbols instead of subcarriers. Still need to reconcile the channel estimation.
120 lines
3.3 KiB
Python
Executable File
120 lines
3.3 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
"""
|
|
Shitty OFDM simulator designed to make it so I understand OFDM.
|
|
Hopefully eventually this modem design makes it onto an fpga.
|
|
|
|
TODO:
|
|
FIX CHANNEL ESTIMATION
|
|
Change channel estimation to pre-amble symbols
|
|
Add comments for functions
|
|
Add more errors, like a shifted signal
|
|
Add support for 16-QAM, 64-QAM, etc...
|
|
Add some sort of payload support, i.e. be able to drop the padding at the end
|
|
|
|
I don't believe this architecture will work too well for an FPGA, right now it's kind of hacky,
|
|
and obviously there are parallelisation improvements on an FPGA, so this will likely have to be redone.
|
|
|
|
Right now though it's just proof of concept to see if I can get a reliable signal to work.
|
|
|
|
"""
|
|
|
|
import numpy as np
|
|
import matplotlib.pyplot as plt
|
|
|
|
import channel
|
|
import qam
|
|
|
|
from serpar import parallelise, serialise
|
|
|
|
def cp_add(in_data, prefix_len):
|
|
"""
|
|
Adds a cyclic prefix to an array of symbols, with a specified length.
|
|
|
|
This changes the linear convolution of the data into a circular convolution,
|
|
allowing easier equalization.
|
|
As well, it helps remove inter-symbol interference.
|
|
"""
|
|
|
|
out_data = np.ndarray((len(in_data), len(in_data[0]) + prefix_len), dtype=np.csingle)
|
|
|
|
for i in range(len(in_data)):
|
|
cp = in_data[i][-prefix_len:]
|
|
out_data[i] = np.hstack([cp, in_data[i]])
|
|
|
|
return out_data
|
|
|
|
def cp_remove(in_data, prefix_len):
|
|
"""
|
|
Removes cyclic prefix from retrieved data. Naively assumes that data is correctly aligned.
|
|
"""
|
|
|
|
out_data = np.ndarray((len(in_data), len(in_data[0]) - prefix_len), dtype=np.csingle)
|
|
|
|
for i in range(len(in_data)):
|
|
out_data[i] = in_data[i][prefix_len:]
|
|
|
|
return out_data
|
|
|
|
|
|
def check_theoretical_channel(tx):
|
|
H_exact = np.fft.fft([1, 0, 0.3 + 0.3j], len(tx[0]))
|
|
|
|
rx = np.ndarray((len(tx[0])), dtype=np.csingle)
|
|
|
|
for i in range(len(tx[0])):
|
|
rx[i] = H_exact[i] * tx[0][i]
|
|
|
|
return rx
|
|
|
|
|
|
if __name__ == '__main__':
|
|
with open('data.txt', 'r') as file:
|
|
data = file.read()
|
|
|
|
bytes = bytearray(data, 'utf8')
|
|
|
|
# Turn data into a parallelised form, able to be QAM-modulated
|
|
parallel = parallelise(64, bytes)
|
|
|
|
# modulate data with a QAM scheme
|
|
modulated = np.array(qam.modulate(parallel, pilots=0))
|
|
|
|
# Insert known "start" symbol
|
|
start_symbol = np.zeros((1,64), dtype=np.csingle)
|
|
for i in range(64):
|
|
start_symbol[0][i] = 1 + 1j
|
|
|
|
modulated = np.append(start_symbol, modulated, axis=0)
|
|
|
|
|
|
# Run IFFT to get a time-domain signal to send
|
|
ofdm_time = np.fft.ifft(modulated)
|
|
|
|
# Add cyclic prefix to each symbol
|
|
tx = cp_add(ofdm_time, 16)
|
|
|
|
theoretical_rx = check_theoretical_channel(tx)
|
|
|
|
# Simulate effects of a multipath channel
|
|
rx = channel.sim(tx)
|
|
|
|
# Remove cyclic prefix from incoming symbols
|
|
ofdm_cp_removed = cp_remove(rx, 16)
|
|
|
|
# Bring symbols back into frequency domain to get carrier channels
|
|
to_equalize = np.fft.fft(ofdm_cp_removed)
|
|
|
|
# Find an estimate for channel effect
|
|
H_est = channel.estimate(to_equalize, pilots=0)
|
|
|
|
# Equalise based on estimated channel
|
|
to_decode = channel.equalize(to_equalize, H_est)
|
|
|
|
# Demodulate symbol into output data
|
|
to_serialise = qam.demodulate(to_decode, pilots=0)
|
|
|
|
# Turn data back into string
|
|
data = serialise(64, to_serialise)
|
|
print(data)
|