From 9a1107ff389274104f8ec6336b7cc80646311df2 Mon Sep 17 00:00:00 2001 From: Thomas Kolb Date: Sat, 22 Jan 2022 22:42:05 +0100 Subject: [PATCH] Added correlator + small test program --- impl/CMakeLists.txt | 2 + impl/src/correlator.c | 113 +++++++++++++++++++++++++++++++++++ impl/src/correlator.h | 116 ++++++++++++++++++++++++++++++++++++ impl/test/CMakeLists.txt | 11 ++++ impl/test/test_correlator.c | 51 ++++++++++++++++ 5 files changed, 293 insertions(+) create mode 100644 impl/src/correlator.c create mode 100644 impl/src/correlator.h create mode 100644 impl/test/CMakeLists.txt create mode 100644 impl/test/test_correlator.c diff --git a/impl/CMakeLists.txt b/impl/CMakeLists.txt index fef02bd..eb98d50 100644 --- a/impl/CMakeLists.txt +++ b/impl/CMakeLists.txt @@ -38,3 +38,5 @@ target_link_libraries( fftw3f fec ) + +add_subdirectory(test) diff --git a/impl/src/correlator.c b/impl/src/correlator.c new file mode 100644 index 0000000..3c33ae8 --- /dev/null +++ b/impl/src/correlator.c @@ -0,0 +1,113 @@ +#include +#include +#include + +#include "correlator.h" + +bool correlator_init(correlator_ctx_t *ctx, const float complex *search_seq, size_t nsymbols) +{ + // copy arguments and initialize variables + ctx->search_sequence_len = nsymbols; + + ctx->input_history = NULL; + ctx->search_sequence = NULL; + + ctx->history_ptr = 0; + + // Determine the smallest power of two greater than nsymbols + ctx->buffer_size = 1; + while(nsymbols > 0) { + ctx->buffer_size <<= 1; + nsymbols >>= 1; + } + + // If buffer_size = 0b1000, then buffer_size_mask=0b0111 + ctx->buffer_size_mask = ctx->buffer_size - 1; + + // Allocate the buffers + ctx->input_history = malloc(ctx->buffer_size * sizeof(ctx->input_history[0])); + if(!ctx->input_history) { + fprintf(stderr, "correlator: malloc() failed!\n"); + goto fail; + } + + ctx->search_sequence = malloc(ctx->buffer_size * sizeof(ctx->search_sequence[0])); + if(!ctx->search_sequence) { + fprintf(stderr, "correlator: malloc() failed!\n"); + goto fail; + } + + // Clear the history buffer + memset(ctx->input_history, 0, ctx->buffer_size * sizeof(ctx->input_history[0])); + + // Prepare the search sequence. + // The complex values are conjugated in the process. + for(size_t i = 0; i < ctx->search_sequence_len; i++) { + ctx->search_sequence[i] = conjf(search_seq[i]); + } + + // Success! + return true; + +fail: + correlator_free(ctx); + return false; +} + + +void correlator_free(correlator_ctx_t *ctx) +{ + if(ctx->input_history) { + free(ctx->input_history); + ctx->input_history = NULL; + } + + if(ctx->search_sequence) { + free(ctx->search_sequence); + ctx->input_history = NULL; + } + + ctx->buffer_size = 0; + ctx->search_sequence_len = 0; +} + + +float complex correlator_step(correlator_ctx_t *ctx, float complex sample) +{ + float complex result = 0; + + // increment and wrap the history pointer + ctx->history_ptr++; + ctx->history_ptr &= ctx->buffer_size_mask; + + // copy the input sample to the history + ctx->input_history[ctx->history_ptr] = sample; + + size_t n = (ctx->history_ptr - ctx->search_sequence_len + 1) & ctx->buffer_size_mask; + + // calculate the correlation + for(size_t m = 0; m < ctx->search_sequence_len; m++) { + size_t nm = (n + m) & ctx->buffer_size_mask; + + result += ctx->search_sequence[m] * ctx->input_history[nm]; + } + + return result; +} + + +void correlator_get_input_history(correlator_ctx_t *ctx, float complex *history) +{ + size_t n = (ctx->history_ptr - ctx->search_sequence_len + 1) & ctx->buffer_size_mask; + + for(size_t m = 0; m < ctx->search_sequence_len; m++) { + size_t nm = (n + m) & ctx->buffer_size_mask; + history[m] = ctx->input_history[nm]; + } +} + + +const float complex* correlator_get_conj_search_sequence(correlator_ctx_t *ctx) +{ + return ctx->search_sequence; +} diff --git a/impl/src/correlator.h b/impl/src/correlator.h new file mode 100644 index 0000000..9e67a56 --- /dev/null +++ b/impl/src/correlator.h @@ -0,0 +1,116 @@ +#ifndef CORRELATOR_H +#define CORRELATOR_H + +/*! + * \file + * + * \brief A cross-correlator. + * + * \details + * This module implements a cross-correlator that correlates a static sequence + * (like a preamble) with a continuous stream of symbols. For example, it can + * be used to detect the start of a packet in a stream of samples. + */ + +#include +#include +#include + +/*! + * \brief Context information for the correlator. + */ +typedef struct +{ + float complex *search_sequence; + float complex *input_history; + + /*! + * \brief The history pointer. + * + * \details + * Always points to the last updated sample in the input_history. On every + * written sample, the pointer is incremented and wrapped using + * buffer_size_mask. Therefore, older samples have lower indices in the + * array. + */ + size_t history_ptr; + + size_t search_sequence_len; + size_t buffer_size; + size_t buffer_size_mask; +} correlator_ctx_t; + + +/*! + * \brief Initialize a correlator. + * + * \details + * Initializes all variables in the given context and imports the search sequence. + * + * The search sequence needs to be given such that index 0 contains the + * „oldest“ correlated sample. + * + * \param ctx The correlator context to initialize. + * \param search_seq The search sequence. + * \param nsymbols Length of the search sequence. + * + * \returns Whether the correlator was initialized successfully. + */ +bool correlator_init(correlator_ctx_t *ctx, const float complex *search_seq, size_t nsymbols); + +/*! + * \brief Delete a correlator. + * + * \param ctx The correlator context to free. + */ +void correlator_free(correlator_ctx_t *ctx); + +/*! + * \brief Process one sample in the correlator. + * + * The input sample will be stored in the internal history buffer and then the + * following calculation will be executed, where k determines the “age” of the + * sample and n is the size of the search sequence: + * + * result = sum{k = 0..n-1} (input_history[k] * search_sequence[k]) + * + * \param ctx The correlator context to use. + * \param sample The input sample to process. + * + * \returns The new correlator output value. + */ +float complex correlator_step(correlator_ctx_t *ctx, float complex sample); + +/*! + * \brief Retrieve the history of input values. + * + * \details + * This function gives the last n input values, where n is the size of the + * search sequence. + * + * The values will be ordered such that the oldest value has index 0. + * + * Note that this function is rather expensive as it copies all the data. + * However it might releave the user of keeping track of the input samples in + * case the history is only used occasionally. + * + * \param ctx The correlator context to use. + * \param history Pointer to the output array. Must have space for n values. + */ +void correlator_get_input_history(correlator_ctx_t *ctx, float complex *history); + +/*! + * \brief Retrieve the conjugated search sequence. + * + * \details + * Returns a pointer to the internally used search sequence, which corresponds + * to the sequence given to \ref correlator_init(), but all values are + * conjugated. + * + * \param ctx The correlator context to use. + * \returns A pointer to the internal search sequence. + */ +const float complex* correlator_get_conj_search_sequence(correlator_ctx_t *ctx); + + +#endif // CORRELATOR_H diff --git a/impl/test/CMakeLists.txt b/impl/test/CMakeLists.txt new file mode 100644 index 0000000..429a5b2 --- /dev/null +++ b/impl/test/CMakeLists.txt @@ -0,0 +1,11 @@ +add_executable( + test_correlator + ../src/correlator.c + ../src/correlator.h + test_correlator.c +) + +target_link_libraries( + test_correlator + m +) diff --git a/impl/test/test_correlator.c b/impl/test/test_correlator.c new file mode 100644 index 0000000..06db660 --- /dev/null +++ b/impl/test/test_correlator.c @@ -0,0 +1,51 @@ +#include + +#include + +#define ARRAY_LEN(x) (sizeof(x) / sizeof(x[0])) + +static const float complex SEARCH_SEQ[5] = {-1, 1, 1, -1, 1}; +static const float complex INPUT_SEQ[10] = {1, 1, -1, 1, -1, 1, 1, -1, 1, -1}; + +int main(void) +{ + correlator_ctx_t c; + + float complex output_seq[ARRAY_LEN(INPUT_SEQ)]; + + if(!correlator_init(&c, SEARCH_SEQ, ARRAY_LEN(SEARCH_SEQ))) { + fprintf(stderr, "Failed to initialize the correlator!\n"); + return 1; + } + + for(size_t i = 0; i < ARRAY_LEN(INPUT_SEQ); i++) { + output_seq[i] = correlator_step(&c, INPUT_SEQ[i]); + printf("%3zu %.3f+%.3fj %.3f+%.3fj", i, + creal(INPUT_SEQ[i]), cimag(INPUT_SEQ[i]), + creal(output_seq[i]), cimag(output_seq[i])); + + if(cabsf(output_seq[i]) > ((size_t)ARRAY_LEN(SEARCH_SEQ) - 0.5f)) { + printf(" <<< found!\n"); + + const complex float *search_seq_conj = correlator_get_conj_search_sequence(&c); + + complex float history[ARRAY_LEN(SEARCH_SEQ)]; + correlator_get_input_history(&c, history); + + printf(" Search sequence: "); + for(size_t i = 0; i < ARRAY_LEN(SEARCH_SEQ); i++) { + printf("%.3f%+.3fj ", crealf(search_seq_conj[i]), cimagf(search_seq_conj[i])); + } + printf("\n Input history: "); + for(size_t i = 0; i < ARRAY_LEN(SEARCH_SEQ); i++) { + printf("%.3f%+.3fj ", crealf(history[i]), cimagf(history[i])); + } + } + + printf("\n"); + } + + correlator_free(&c); + + return 0; +}