diff --git a/impl/src/config.h b/impl/src/config.h new file mode 100644 index 0000000..747bcaf --- /dev/null +++ b/impl/src/config.h @@ -0,0 +1,16 @@ +#ifndef CONFIG_H +#define CONFIG_H + +#include + +#define CHANNEL_CODE LIQUID_FEC_CONV_V27P34 +#define MODULATION LIQUID_MODEM_QAM16 + +#define HEADER_CHANNEL_CODE LIQUID_FEC_NONE +#define HEADER_MODULATION LIQUID_MODEM_QPSK + +#define PREAMBLE_MSEQ_M 6 +#define PREAMBLE_MSEQ_POLY LIQUID_MSEQUENCE_GENPOLY_M6 +#define PREAMBLE_MSEQ_INIT 0x00000001 + +#endif // CONFIG_H diff --git a/impl/src/main.c b/impl/src/main.c new file mode 100644 index 0000000..e5d6609 --- /dev/null +++ b/impl/src/main.c @@ -0,0 +1,97 @@ +#include +#include +#include +#include + +#include "utils.h" +#include "packet_mod.h" +#include "config.h" +#include "preamble.h" + +int main(void) +{ + uint8_t msg_org[] = "Hello Liquid! This is the message to transmit. Hopefully it can be decoded correctly..."; + + fec q = fec_create(CHANNEL_CODE, NULL); + + modem demod = modem_create(MODULATION); + + channel_cccf channel = channel_cccf_create(); + + float snr = 20.0f; + channel_cccf_add_awgn(channel, -snr, snr); + + + unsigned int k = fec_get_enc_msg_length(CHANNEL_CODE, sizeof(msg_org)); + + packet_mod_ctx_t pmod; + packet_mod_init(&pmod); + + packet_mod_set_data(&pmod, msg_org, sizeof(msg_org)); + packet_mod_encode(&pmod); + packet_mod_modulate(&pmod); + packet_mod_add_header(&pmod); + packet_mod_add_preamble(&pmod); + + size_t nsyms; + packet_mod_get_result_cf(&pmod, NULL, &nsyms); // determine number of symbols for allocation + + float complex msg_mod[nsyms]; + packet_mod_get_result_cf(&pmod, msg_mod, &nsyms); // get the data + + // channel + float complex msg_received[nsyms]; + + nsyms = nsyms - 63 - 8; + + //memcpy(msg_received, msg_mod, sizeof(msg_received)); // no noise in channel + + dump_array_cf(msg_mod, nsyms, 1.0f, "/tmp/tx.cpx"); + channel_cccf_execute_block(channel, msg_mod + 63 + 8, nsyms, msg_received); + dump_array_cf(msg_received, nsyms, 1.0f, "/tmp/rx.cpx"); + + // demodulate + unsigned int bps = modem_get_bps(demod); + + unsigned char msg_demod_syms[nsyms]; + unsigned char msg_demod[k+1]; + + for(size_t i = 0; i < nsyms; i++) { + unsigned int symbol; + + modem_demodulate(demod, msg_received[i], &symbol); + msg_demod_syms[i] = symbol; + } + + unsigned int received_bytes; + liquid_repack_bytes(msg_demod_syms, bps, nsyms, msg_demod, 8, k+1, &received_bytes); + + //assert(received_bytes == k); + + // decode + uint8_t msg_dec[sizeof(msg_org)]; + //memcpy(msg_dec, msg_enc, sizeof(msg_dec)); + fec_decode(q, sizeof(msg_dec), msg_demod, msg_dec); + + // compare original to decoded message + for(size_t i = 0; i < sizeof(msg_org); i++) + { + printf("%02x => %02x", msg_org[i], msg_dec[i]); + if(msg_org[i] != msg_dec[i]) { + printf(" <<< !!!\n"); + } else { + printf("\n"); + } + } + + unsigned int bit_errors = count_bit_errors_array(msg_org, msg_dec, sizeof(msg_org)); + printf("%u bit errors detected.\n", bit_errors); + + fec_destroy(q); + + modem_destroy(demod); + + channel_cccf_destroy(channel); + + printf("Done.\n"); +} diff --git a/impl/src/packet_mod.c b/impl/src/packet_mod.c new file mode 100644 index 0000000..2c37e6b --- /dev/null +++ b/impl/src/packet_mod.c @@ -0,0 +1,251 @@ +#include + +#include "preamble.h" +#include "config.h" + +#include "packet_mod.h" + +result_t packet_mod_init(packet_mod_ctx_t *ctx) +{ + ctx->pkt_bytes = NULL; + ctx->pkt_symbols = NULL; + + ctx->length = 0; + + ctx->fec = fec_create(CHANNEL_CODE, NULL); + ctx->modem = modem_create(MODULATION); + + ctx->state = NOT_STARTED; + + return OK; +} + + +result_t packet_mod_free(packet_mod_ctx_t *ctx) +{ + switch(ctx->state) { + case DATA_RAW: + case DATA_CODED: + free(ctx->pkt_bytes); + ctx->pkt_bytes = NULL; + break; + + case DATA_MODULATED: + case HEADER_ADDED: + case PREAMBLE_ADDED: + free(ctx->pkt_symbols); + ctx->pkt_symbols = NULL; + break; + + case NOT_STARTED: + // nothing to do + break; + } + + ctx->state = NOT_STARTED; + ctx->length = 0; + + fec_destroy(ctx->fec); + modem_destroy(ctx->modem); + + return OK; +} + + +result_t packet_mod_set_data(packet_mod_ctx_t *ctx, const unsigned char *data, size_t length) +{ + if(ctx->state != NOT_STARTED) { + return ERR_INVALID_STATE; + } + + ctx->pkt_bytes = malloc(length); + if(!ctx->pkt_bytes) { + return ERR_NO_MEM; + } + + memcpy(ctx->pkt_bytes, data, length); + ctx->length = length; + + ctx->state = DATA_RAW; + + return OK; +} + + +result_t packet_mod_encode(packet_mod_ctx_t *ctx) +{ + if(ctx->state != DATA_RAW) { + return ERR_INVALID_STATE; + } + + unsigned int enc_length = fec_get_enc_msg_length(CHANNEL_CODE, ctx->length); + + unsigned char *enc_msg = malloc(enc_length); + if(!enc_msg) { + return ERR_NO_MEM; + } + + ERR_CHECK_LIQUID(fec_encode(ctx->fec, ctx->length, ctx->pkt_bytes, enc_msg)); + + free(ctx->pkt_bytes); + + ctx->pkt_bytes = enc_msg; + ctx->length = enc_length; + + ctx->state = DATA_CODED; + + return OK; +} + + +result_t packet_mod_modulate(packet_mod_ctx_t *ctx) +{ + if(!((ctx->state == DATA_RAW) || (ctx->state == DATA_CODED))) { + return ERR_INVALID_STATE; + } + + unsigned int bps = modem_get_bps(ctx->modem); + unsigned int nsyms = (ctx->length * 8 + bps/2) / bps; + + unsigned char msg_sym_idcs[nsyms]; + + float complex *msg_mod = malloc(sizeof(float complex) * nsyms); + if(!msg_mod) { + return ERR_NO_MEM; + } + + ERR_CHECK_LIQUID(liquid_repack_bytes(ctx->pkt_bytes, 8, ctx->length, msg_sym_idcs, bps, nsyms, &nsyms)); + + for(size_t i = 0; i < nsyms; i++) { + ERR_CHECK_LIQUID(modem_modulate(ctx->modem, msg_sym_idcs[i], &msg_mod[i])); + } + + free(ctx->pkt_bytes); + ctx->pkt_bytes = NULL; + + ctx->pkt_symbols = msg_mod; + ctx->length = nsyms; + + ctx->state = DATA_MODULATED; + + return OK; +} + + +result_t packet_mod_add_header(packet_mod_ctx_t *ctx) +{ + if(ctx->state != DATA_MODULATED) { + return ERR_INVALID_STATE; + } + + if(ctx->length >= (1 << 16)) { + // packet length cannot be stored in the uint16_t in the header. + return ERR_SIZE; + } + + // build the header. All field are transferred in network byte order (big endian). + uint8_t header[4]; + + // set length + header[0] = (ctx->length >> 8) & 0xFF; + header[1] = (ctx->length >> 0) & 0xFF; + // set raw data CRC + header[2] = (ctx->raw_data_crc >> 8) & 0xFF; + header[3] = (ctx->raw_data_crc >> 0) & 0xFF; + + // the header is coded and modulated differently than the data. + fec hdr_fec = fec_create(HEADER_CHANNEL_CODE, NULL); + modem hdr_modem = modem_create(HEADER_MODULATION); + + // encode the header + unsigned int hdr_enc_length = fec_get_enc_msg_length(HEADER_CHANNEL_CODE, ctx->length); + unsigned char header_encoded[hdr_enc_length]; + + ERR_CHECK_LIQUID(fec_encode(hdr_fec, sizeof(header), header, header_encoded)); + + // modulate the header + unsigned int bps = modem_get_bps(hdr_modem); + unsigned int nsyms = (hdr_enc_length * 8 + bps/2) / bps; + + unsigned char header_sym_idcs[nsyms]; + float complex header_mod[nsyms]; + + ERR_CHECK_LIQUID(liquid_repack_bytes(header_encoded, 8, hdr_enc_length, header_sym_idcs, bps, nsyms, &nsyms)); + + for(size_t i = 0; i < nsyms; i++) { + ERR_CHECK_LIQUID(modem_modulate(hdr_modem, header_sym_idcs[i], &header_mod[i])); + } + + // concatenate the modulated header and message + size_t packet_length_with_header = ctx->length + nsyms; + float complex *packet_with_header = malloc(sizeof(float complex) * packet_length_with_header); + if(!packet_with_header) { + return ERR_NO_MEM; + } + + memcpy(packet_with_header, header_mod, sizeof(float complex) * nsyms); + memcpy(packet_with_header+nsyms, ctx->pkt_symbols, sizeof(float complex) * ctx->length); + + // replace the data in the context struct + free(ctx->pkt_symbols); + + ctx->pkt_symbols = packet_with_header; + ctx->length = packet_length_with_header; + + ctx->state = HEADER_ADDED; + + return OK; +} + + +result_t packet_mod_add_preamble(packet_mod_ctx_t *ctx) +{ + if(ctx->state != HEADER_ADDED) { + return ERR_INVALID_STATE; + } + + // concatenate the preamble with the message + size_t pre_length = preamble_get_symbol_count(); + + size_t packet_length_with_preamble = ctx->length + pre_length; + float complex *packet_with_preamble = malloc(sizeof(float complex) * packet_length_with_preamble); + if(!packet_with_preamble) { + return ERR_NO_MEM; + } + + memcpy(packet_with_preamble, preamble_get_symbols(), sizeof(float complex) * pre_length); + memcpy(packet_with_preamble+pre_length, ctx->pkt_symbols, sizeof(float complex) * ctx->length); + + // replace the data in the context struct + free(ctx->pkt_symbols); + + ctx->pkt_symbols = packet_with_preamble; + ctx->length = packet_length_with_preamble; + + ctx->state = PREAMBLE_ADDED; + + return OK; +} + + +result_t packet_mod_get_result_cf(packet_mod_ctx_t *ctx, float complex *data, size_t *length) +{ + if(!((ctx->state == DATA_MODULATED) || (ctx->state == HEADER_ADDED) || (ctx->state == PREAMBLE_ADDED))) { + return ERR_INVALID_STATE; + } + + if(!data) { + // data is NULL, so we only return the required length + *length = ctx->length; + return OK; + } + + if(*length < ctx->length) { + return ERR_NO_MEM; + } + + *length = ctx->length; + memcpy(data, ctx->pkt_symbols, *length * sizeof(float complex)); + + return OK; +} diff --git a/impl/src/packet_mod.h b/impl/src/packet_mod.h new file mode 100644 index 0000000..22a4637 --- /dev/null +++ b/impl/src/packet_mod.h @@ -0,0 +1,51 @@ +#ifndef PACKET_MOD +#define PACKET_MOD + +#include +#include + +#include + +#include "results.h" + +typedef enum +{ + NOT_STARTED, + DATA_RAW, + DATA_CODED, + DATA_MODULATED, + HEADER_ADDED, + PREAMBLE_ADDED, +} packet_mod_state_t; + +typedef struct +{ + unsigned char *pkt_bytes; + float complex *pkt_symbols; + + packet_mod_state_t state; + + modem modem; + fec fec; + + uint16_t raw_data_crc; + + size_t length; +} packet_mod_ctx_t; + + +result_t packet_mod_init(packet_mod_ctx_t *ctx); +result_t packet_mod_free(packet_mod_ctx_t *ctx); + +result_t packet_mod_set_data(packet_mod_ctx_t *ctx, const unsigned char *data, size_t length); +result_t packet_mod_encode(packet_mod_ctx_t *ctx); +result_t packet_mod_modulate(packet_mod_ctx_t *ctx); +result_t packet_mod_add_header(packet_mod_ctx_t *ctx); +result_t packet_mod_add_preamble(packet_mod_ctx_t *ctx); + +result_t packet_mod_dump(packet_mod_ctx_t *ctx, const char *filename); + +result_t packet_mod_get_result_b(packet_mod_ctx_t *ctx, unsigned char *data, size_t *length); +result_t packet_mod_get_result_cf(packet_mod_ctx_t *ctx, float complex *data, size_t *length); + +#endif // PACKET_MOD diff --git a/impl/src/preamble.c b/impl/src/preamble.c new file mode 100644 index 0000000..07c8638 --- /dev/null +++ b/impl/src/preamble.c @@ -0,0 +1,37 @@ +#include + +#include "config.h" + +#include "preamble.h" + +#define PREAMBLE_LEN ((1 << PREAMBLE_MSEQ_M) - 1) + +static float complex symbol_cache[PREAMBLE_LEN]; +static bool symbol_cache_initialized = false; + +const float complex* preamble_get_symbols(void) +{ + if(!symbol_cache_initialized) { + msequence mseq = msequence_create( + PREAMBLE_MSEQ_M, + PREAMBLE_MSEQ_POLY, + PREAMBLE_MSEQ_INIT); + + for(unsigned i = 0; i < PREAMBLE_LEN; i++) { + unsigned int bit = msequence_advance(mseq); + symbol_cache[i] = bit ? 1.0f : -1.0f; + } + + msequence_destroy(mseq); + + symbol_cache_initialized = true; + } + + return symbol_cache; +} + + +size_t preamble_get_symbol_count(void) +{ + return PREAMBLE_LEN; +} diff --git a/impl/src/preamble.h b/impl/src/preamble.h new file mode 100644 index 0000000..aecd71d --- /dev/null +++ b/impl/src/preamble.h @@ -0,0 +1,10 @@ +#ifndef PREAMBLE_H +#define PREAMBLE_H + +#include +#include + +const float complex* preamble_get_symbols(void); +size_t preamble_get_symbol_count(void); + +#endif // PREAMBLE_H diff --git a/impl/src/results.h b/impl/src/results.h new file mode 100644 index 0000000..fc8df70 --- /dev/null +++ b/impl/src/results.h @@ -0,0 +1,27 @@ +#ifndef RESULTS_H +#define RESULTS_H + +typedef enum { + OK, + ERR_INVALID_STATE, + ERR_NO_MEM, + ERR_SIZE, + ERR_LIQUID, +} result_t; + +#ifdef DEBUG_LIQUID +#include + +# define ERR_CHECK_LIQUID(call) \ + do { \ + liquid_error_code result; \ + if((result = (call)) != LIQUID_OK) { \ + fprintf(stderr, "Liquid call failed in %s:%d: %s\n", __FILE__, __LINE__, liquid_error_info(result)); \ + return ERR_LIQUID; \ + } \ + } while(0); +#else +# define ERR_CHECK_LIQUID(call) if((call) != LIQUID_OK) { return ERR_LIQUID; } +#endif + +#endif // RESULTS_H diff --git a/impl/src/utils.c b/impl/src/utils.c new file mode 100644 index 0000000..cdef365 --- /dev/null +++ b/impl/src/utils.c @@ -0,0 +1,66 @@ +#include + +#include "utils.h" + +bool dump_array_cf(const float complex *data, size_t n, float T, const char *filename) +{ + FILE *f = fopen(filename, "w"); + if(!f) { + return false; + } + + size_t written = fwrite("CPX_", 1, 4, f); + if(written != 4) { + goto err_close; + } + + written = fwrite(&T, sizeof(T), 1, f); + if(written != 1) { + goto err_close; + } + + written = fwrite(data, sizeof(data[0]), n, f); + if(written != n) { + goto err_close; + } + + fclose(f); + + return true; + +err_close: + fclose(f); + return false; +} + + +bool dump_array_f(const float *data, size_t n, float T, const char *filename) +{ + FILE *f = fopen(filename, "w"); + if(!f) { + return false; + } + + size_t written = fwrite("FLT_", 1, 4, f); + if(written != 4) { + goto err_close; + } + + written = fwrite(&T, sizeof(T), 1, f); + if(written != 1) { + goto err_close; + } + + written = fwrite(data, sizeof(data[0]), n, f); + if(written != n) { + goto err_close; + } + + fclose(f); + + return true; + +err_close: + fclose(f); + return false; +} diff --git a/impl/src/utils.h b/impl/src/utils.h new file mode 100644 index 0000000..5db5ad9 --- /dev/null +++ b/impl/src/utils.h @@ -0,0 +1,27 @@ +#ifndef UTILS_H +#define UTILS_H + +#include +#include +#include + +/*! Dump a array of complex numbers. + * + * \param data Pointer to the data to dump. + * \param n Number of data points to dump. + * \param T The sampling interval of the data (set to 1.0 if not relevant). + * \param filename The file to write to. + */ +bool dump_array_cf(const float complex *data, size_t n, float T, const char *filename); + + +/*! Dump a array of real numbers. + * + * \param data Pointer to the data to dump. + * \param n Number of data points to dump. + * \param T The sampling interval of the data (set to 1.0 if not relevant). + * \param filename The file to write to. + */ +bool dump_array_f(const float *data, size_t n, float T, const char *filename); + +#endif // UTILS_H