diff options
Diffstat (limited to '')
-rw-r--r-- | notebooks/FrameSynchronization.ipynb | 73 | ||||
-rw-r--r-- | src/gr-fadingui/grc/fadingui_xor_frame_sync.block.yml | 37 | ||||
-rw-r--r-- | src/gr-fadingui/python/datasource.py | 3 | ||||
-rw-r--r-- | src/gr-fadingui/python/xor_frame_sync.py | 58 | ||||
-rw-r--r-- | tests/fadingui/QAM/qam_nogui.grc | 25 | ||||
-rwxr-xr-x | tests/fadingui/QAM/qam_nogui.py | 4 |
6 files changed, 136 insertions, 64 deletions
diff --git a/notebooks/FrameSynchronization.ipynb b/notebooks/FrameSynchronization.ipynb index 9a7719f..e0187b2 100644 --- a/notebooks/FrameSynchronization.ipynb +++ b/notebooks/FrameSynchronization.ipynb @@ -3,7 +3,7 @@ { "cell_type": "code", "execution_count": 1, - "id": "5d5acf43", + "id": "57a55a8c", "metadata": {}, "outputs": [], "source": [ @@ -16,7 +16,7 @@ }, { "cell_type": "markdown", - "id": "4b294887", + "id": "dc4b9dfd", "metadata": {}, "source": [ "# Digital Frame Synchronization\n" @@ -24,22 +24,21 @@ }, { "cell_type": "code", - "execution_count": 14, - "id": "5bec50ff", - "metadata": {}, + "execution_count": 27, + "id": "025c6919", + "metadata": { + "scrolled": false + }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Header sequence (N=16): [1 1 1 1 0 1 1 1 0 1 1 1 1 1 0 1]\n", - "Cross correlation: [ 3. 3. 4. 4. 4. 4. 3. 6. 7. 7. 3. 6. 6. 8. 7. 6. 7. 11.\n", - " 8. 7. 7. 10. 8. 8. 10. 8. 10. 7. 8. 8. 8. 10. 8. 11. 12. 9.\n", - " 8. 10. 8. 11. 9. 10. 9. 16. 10. 10. 10. 12. 10. 14. 9. 10. 9. 13.\n", - " 11. 9. 11. 8. 9. 11. 9. 9. 8. 8. 7. 9. 12. 5. 7. 9. 7. 10.\n", - " 12. 6. 6. 9. 8. 6. 8. 5. 5. 8. 7. 5. 5. 4. 3. 4. 3. 3.\n", - " 3. 3.]\n", - "Corrlation peak value: 16.0\n" + "Header (N=16): [1 0 1 1 1 1 1 0 1 1 1 0 1 1 1 1]\n", + "Stream (N=80): [0 0 1 1 0 0 0 0 1 0 1 1 1 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 1 1 1 1 0 1 1 1\n", + " 1 1 0 1 1 1 0 1 1 1 1 1 0 1 1 1 0 1 1 0 1 0 1 1 0 0 1 1 1 0 0 1 1 1 1 1 0\n", + " 0 1 1 1 0 1]\n", + "Correlation peak value: 16 at i=47\n" ] }, { @@ -48,13 +47,13 @@ "<StemContainer object of 3 artists>" ] }, - "execution_count": 14, + "execution_count": 27, "metadata": {}, "output_type": "execute_result" }, { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "<Figure size 1800x360 with 1 Axes>" ] @@ -67,42 +66,40 @@ ], "source": [ "# Create test data\n", - "seq = np.array([(0xbeef >> i) & 0x1 for i in range(4 * 4)])\n", - "# seq = np.array([1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1])\n", - "\n", + "seq = np.unpackbits(np.array([0xbe, 0xef], dtype=np.dtype(\"uint8\")))\n", "stream = np.concatenate([\n", - " np.random.randint(low=0, high=2, size=30), seq, np.random.randint(low=0, high=2, size=30)\n", + " np.random.randint(low=0, high=2, size=32), seq, np.random.randint(low=0, high=2, size=32)\n", "])\n", "\n", - "print(f\"Header sequence (N={len(seq)}): {seq}\")\n", - "# print(f\"Data stream: {stream}\")\n", + "print(f\"Header (N={len(seq)}): {seq}\")\n", + "print(f\"Stream (N={len(stream)}): {stream}\")\n", "\n", - "# compute cross correlation\n", - "def distance(v):\n", - " return len(seq) - sum(np.logical_xor(v, seq))\n", - "\n", - "fifo = RingBuffer(len(seq))\n", - "xcorr = RingBuffer(len(stream) + len(seq))\n", + "# Create buffers for cross correlation\n", + "fifo = RingBuffer(len(seq), dtype=np.dtype(\"uint8\"))\n", + "xcorr = RingBuffer(len(stream) + len(seq), dtype=np.dtype(\"uint8\"))\n", "\n", "## fill FIFO with zeros\n", "fifo.extend(np.zeros(fifo.maxlen))\n", "\n", - "for i in range(len(stream) + len(seq)):\n", - " #print(np.array(fifo))\n", - " xcorr.append(distance(np.array(fifo)))\n", + "def correlation(v):\n", + " n = len(seq)\n", + " d = np.logical_xor(v, seq) # or bitwise_xor, no difference in this case\n", + " return n - sum(d)\n", + " \n", + "for i in range(len(stream) + len(seq) + 1):\n", + " xcorr.append(correlation(np.array(fifo)))\n", " \n", + " # append stream data\n", " # if the stream is finished use zeros\n", - " fifo.pop()\n", - " if i < len(stream):\n", - " fifo.appendleft(stream[i])\n", - " else:\n", - " fifo.appendleft(0)\n", + " fifo.append(stream[i] if i < len(stream) else 0)\n", "\n", - "print(f\"Cross correlation: {np.array(xcorr)}\")\n", - "print(f\"Correlation peak value: {max(np.array(xcorr))}\")\n", + "# unwrap values\n", + "xc = np.array(xcorr)\n", + "# print(f\"Cross correlation: {xc}\")\n", + "print(f\"Correlation peak value: {np.amax(xc)} at i={np.argmax(xc)}\")\n", "\n", "plt.figure(figsize = (25, 5))\n", - "plt.stem(np.array(xcorr))" + "plt.stem(xc)" ] } ], diff --git a/src/gr-fadingui/grc/fadingui_xor_frame_sync.block.yml b/src/gr-fadingui/grc/fadingui_xor_frame_sync.block.yml index 92be2a8..35eb132 100644 --- a/src/gr-fadingui/grc/fadingui_xor_frame_sync.block.yml +++ b/src/gr-fadingui/grc/fadingui_xor_frame_sync.block.yml @@ -1,10 +1,10 @@ id: fadingui_xor_frame_sync -label: xor_frame_sync +label: XOR Correlation Synchronizer category: '[fadingui]' templates: imports: import fadingui - make: fadingui.xor_frame_sync() + make: fadingui.xor_frame_sync(sync_pattern=${pattern}, buffer_size=${buffer_size}) # Make one 'parameters' list entry for every parameter you want settable from the GUI. # Keys include: @@ -12,12 +12,17 @@ templates: # * label (label shown in the GUI) # * dtype (e.g. int, float, complex, byte, short, xxx_vector, ...) parameters: -- id: ... - label: ... - dtype: ... -- id: ... - label: ... - dtype: ... +- id: pattern + label: Bit pattern + dtype: raw + +- id: pattern_len + label: Pattern length + dtype: raw + +- id: buffer_size + label: Delay buffer size + dtype: int # Make one 'inputs' list entry per input and one 'outputs' list entry per output. # Keys include: @@ -27,18 +32,14 @@ parameters: # * vlen (optional - data stream vector length. Default is 1) # * optional (optional - set to 1 for optional inputs. Default is 0) inputs: -- label: ... - domain: ... - dtype: ... - vlen: ... - optional: ... +- label: in + domain: stream + dtype: byte outputs: -- label: ... - domain: ... - dtype: ... - vlen: ... - optional: ... +- label: out + domain: stream + dtype: byte # 'file_format' specifies the version of the GRC yml format used in the file # and should usually not be changed. diff --git a/src/gr-fadingui/python/datasource.py b/src/gr-fadingui/python/datasource.py index 764b4d5..ab2f441 100644 --- a/src/gr-fadingui/python/datasource.py +++ b/src/gr-fadingui/python/datasource.py @@ -10,7 +10,8 @@ from gnuradio import gr class datasource(gr.sync_block): """ - Loads data from a file choosen in the graphical user interface. + Loads data from a file choosen in the graphical user interface, splits into + chunks and puts a preamble in front of it(frame). """ HEADER_LEN = 11; diff --git a/src/gr-fadingui/python/xor_frame_sync.py b/src/gr-fadingui/python/xor_frame_sync.py index 9d9064f..735a031 100644 --- a/src/gr-fadingui/python/xor_frame_sync.py +++ b/src/gr-fadingui/python/xor_frame_sync.py @@ -4,24 +4,76 @@ # Copyright 2021 Naoki Pross. -import numpy +import numpy as np +from numpy_ringbuffer import RingBuffer + from gnuradio import gr + class xor_frame_sync(gr.sync_block): """ docstring for block xor_frame_sync """ - def __init__(self, sync_pattern): + def __init__(self, sync_pattern, buffer_size): gr.sync_block.__init__(self, name="xor_frame_sync", in_sig=[np.byte], out_sig=[np.byte]) + # binary pattern to match + self.pattern = np.array(sync_pattern, dtype=np.dtype("uint8")) + self.nbits = len(sync_pattern) + + # buffer to delay the data + self.delay_fifo = RingBuffer(buffer_size, dtype=np.byte) + + # buffers to store cross correlation data + self.xcorr = RingBuffer(buffer_size, dtype=np.dtype("uint8")) + + # synchronization state + self.synchronized = False + self.delay = 0 + + def xcorrelation(self): + """ + Compute the binary correlation between the stream and the stored + pattern. Binary correlation between two bit vectors is just size of the + vector(s) minus the number of bits that differ. + """ + unpacked = np.unpackbits(self.delay_fifo[0]) + return self.nbits - sum(np.logical_xor(unpacked, self.pattern)) + def work(self, input_items, output_items): + """ + Process the inputs, that means: + + - Check that the buffer is synchronized, i.e. there is the sync + pattern appears every k bits, where k is the size of the packet. + + - If the buffer is not synchronized, compute a binary cross + correlation to find how by much the stream should be delayed. + """ inp = input_items[0] out = output_items[0] - out[:] = inp + # Add data to delay buffer + self.delay_fifo.appendleft(inp) + + # TODO: check for synchronization, else compute + + # Compute correlation + if not self.synchronized: + self.xcorr.append(self.xcorrelation()) + + peak = np.argmax(self.xcorr) + if self.xcorr[peak] != self.nbits: + print(f"Warning! XOR correlation is not perfect (peak value = {self.xcorr[peak]})") + + self.delay = peak + self.synchronized = True + + # return data with delay + out[:] = self.delay_fifo[self.delay] return len(output_items[0]) diff --git a/tests/fadingui/QAM/qam_nogui.grc b/tests/fadingui/QAM/qam_nogui.grc index 2c6abf8..e9028b6 100644 --- a/tests/fadingui/QAM/qam_nogui.grc +++ b/tests/fadingui/QAM/qam_nogui.grc @@ -265,7 +265,7 @@ blocks: bus_sink: false bus_source: false bus_structure: null - coordinate: [2336, 424.0] + coordinate: [2584, 456.0] rotation: 0 state: true - name: blocks_throttle_0 @@ -429,7 +429,7 @@ blocks: bus_sink: false bus_source: false bus_structure: null - coordinate: [2136, 420.0] + coordinate: [2144, 452.0] rotation: 0 state: true - name: digital_pfb_clock_sync_xxx_0 @@ -489,6 +489,24 @@ blocks: coordinate: [2096, 180.0] rotation: 0 state: true +- name: fadingui_xor_frame_sync_0 + id: fadingui_xor_frame_sync + parameters: + affinity: '' + alias: '' + buffer_size: '128' + comment: '' + maxoutbuf: '0' + minoutbuf: '0' + pattern: '[]' + pattern_len: '10' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [2304, 436.0] + rotation: 0 + state: true connections: - [blocks_throttle_0, '0', channels_channel_model_0, '0'] @@ -500,9 +518,10 @@ connections: - [digital_costas_loop_cc_0, '0', digital_constellation_decoder_cb_0, '0'] - [digital_costas_loop_cc_0, '0', fadingui_dearpygui_sink_0, '0'] - [digital_diff_decoder_bb_0, '0', digital_map_bb_0, '0'] -- [digital_map_bb_0, '0', blocks_null_sink_0, '0'] +- [digital_map_bb_0, '0', fadingui_xor_frame_sync_0, '0'] - [digital_pfb_clock_sync_xxx_0, '0', digital_cma_equalizer_cc_0, '0'] - [fadingui_datasource_0, '0', blocks_vector_to_stream_0, '0'] +- [fadingui_xor_frame_sync_0, '0', blocks_null_sink_0, '0'] metadata: file_format: 1 diff --git a/tests/fadingui/QAM/qam_nogui.py b/tests/fadingui/QAM/qam_nogui.py index e9aa15b..56fc49b 100755 --- a/tests/fadingui/QAM/qam_nogui.py +++ b/tests/fadingui/QAM/qam_nogui.py @@ -52,6 +52,7 @@ class qam_nogui(gr.top_block): ################################################## # Blocks ################################################## + self.fadingui_xor_frame_sync_0 = fadingui.xor_frame_sync(sync_pattern=[], buffer_size=128) self.fadingui_dearpygui_sink_0 = fadingui.dearpygui_sink(sock_addr='udp://localhost:31415', ui_element_id=0) self.fadingui_datasource_0 = fadingui.datasource(vec_len=2037, header_len=11, sock_addr='udp://', file_list=["./lena512color.tiff"]) self.digital_pfb_clock_sync_xxx_0 = digital.pfb_clock_sync_ccf(sps, timing_loop_bw, rrc_taps, nfilts, nfilts/2, 1.5, 1) @@ -93,9 +94,10 @@ class qam_nogui(gr.top_block): self.connect((self.digital_costas_loop_cc_0, 0), (self.digital_constellation_decoder_cb_0, 0)) self.connect((self.digital_costas_loop_cc_0, 0), (self.fadingui_dearpygui_sink_0, 0)) self.connect((self.digital_diff_decoder_bb_0, 0), (self.digital_map_bb_0, 0)) - self.connect((self.digital_map_bb_0, 0), (self.blocks_null_sink_0, 0)) + self.connect((self.digital_map_bb_0, 0), (self.fadingui_xor_frame_sync_0, 0)) self.connect((self.digital_pfb_clock_sync_xxx_0, 0), (self.digital_cma_equalizer_cc_0, 0)) self.connect((self.fadingui_datasource_0, 0), (self.blocks_vector_to_stream_0, 0)) + self.connect((self.fadingui_xor_frame_sync_0, 0), (self.blocks_null_sink_0, 0)) def get_sps(self): |