Added some comments
This commit is contained in:
parent
1eede7d8c4
commit
38f866cd1b
32
channel.py
32
channel.py
@ -16,6 +16,12 @@ pilot_value = 1 + 1j
|
||||
snr_db = 300
|
||||
|
||||
def sim(in_data):
|
||||
"""
|
||||
Simulate effects of communication channel.
|
||||
|
||||
Convolves the channel response, and adds random noise to the signal.
|
||||
"""
|
||||
|
||||
out_data = np.ndarray((len(in_data), len(in_data[0])), dtype=np.csingle)
|
||||
|
||||
# noise stuff is straight copied from the DSP illustrations article
|
||||
@ -34,6 +40,32 @@ def sim(in_data):
|
||||
# Again, most of this is stolen from the guide
|
||||
# this sort of stuff I had no idea about before I read the guide
|
||||
def estimate(in_data, pilots=0):
|
||||
"""
|
||||
Estimate channel effects by some cool properties.
|
||||
|
||||
Since the effects of the channel medium is a convolution of the transmitted signal,
|
||||
we can abuse this for some easy math.
|
||||
|
||||
We take the time domain input signal and turn that into a frequency domain signal.
|
||||
If we had a perfect channel, this frequency domain signal would exactly equal the signal
|
||||
transmitted. But it doesn't. However, we are in the frequency domain, which means
|
||||
convolution turns into multiplication, and to find the effect of the channel on each
|
||||
subcarrier, we can simply use division.
|
||||
|
||||
Unfortunately, we don't know what the original data was, so we use "pilot" subchannels, which transmit
|
||||
known information. We can use this to get estimates for each of the pilot carriers, and finally, we can interpolate
|
||||
these values to get an estimate for everything.
|
||||
|
||||
There are a few issues with this method:
|
||||
1: It is very estimate-ey. We have to interpolate from a subset of the carriers.
|
||||
2: It is quite inefficient. We are sending useless information on every symbol.
|
||||
|
||||
I think a better solution is to send a known symbol (or a set of known symbols)
|
||||
at the beginnning of each transmission instead. This means we get a full channel estimate
|
||||
every time. This also has the advantage of being able to synchronise the symbols. Since I
|
||||
will be implementing some sort of protocol anyways, I think this will be a good idea. As well,
|
||||
we move slow enough that the channel will not likely change significantly over a single packet.
|
||||
"""
|
||||
|
||||
all_carriers = np.arange(len(in_data[0]))
|
||||
|
||||
|
24
main.py
24
main.py
@ -7,7 +7,6 @@ Hopefully eventually this modem design makes it onto an fpga.
|
||||
TODO:
|
||||
Change channel estimation to pre-amble symbols
|
||||
Add comments for functions
|
||||
Explain what the main function is doing
|
||||
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
|
||||
@ -29,6 +28,14 @@ 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)):
|
||||
@ -38,6 +45,10 @@ def cp_add(in_data, prefix_len):
|
||||
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)):
|
||||
@ -54,25 +65,36 @@ if __name__ == '__main__':
|
||||
|
||||
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 = qam.modulate(parallel, pilots=20)
|
||||
|
||||
# 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)
|
||||
|
||||
# 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=20)
|
||||
|
||||
# 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=20)
|
||||
|
||||
# Turn data back into string
|
||||
data = serialise(64, to_serialise)
|
||||
print(data)
|
||||
|
8
qam.py
8
qam.py
@ -16,6 +16,10 @@ def modulate(in_data, pilots=0):
|
||||
"""
|
||||
Modulates into 4-QAM encoding, might change that number later.
|
||||
|
||||
This turns input data into a "constellation" of complex numbers,
|
||||
ready to be fed into an IFFT. This constellation could also be used
|
||||
directly with an IQ modulator.
|
||||
|
||||
Parameters:
|
||||
in_data - m X n array, m symbols to run on
|
||||
pilots (optional) - number of pilot signals to intersperse into carriers
|
||||
@ -54,6 +58,10 @@ def modulate(in_data, pilots=0):
|
||||
return out_data
|
||||
|
||||
def demodulate(in_data, pilots=0):
|
||||
"""
|
||||
Demodulates incoming signal.
|
||||
"""
|
||||
|
||||
all_carriers = np.arange(len(in_data[0]), dtype=int)
|
||||
|
||||
if pilots > 0:
|
||||
|
Loading…
Reference in New Issue
Block a user