diff options
Diffstat (limited to 'src/examples/ad9361-iiostream.c')
-rw-r--r-- | src/examples/ad9361-iiostream.c | 294 |
1 files changed, 294 insertions, 0 deletions
diff --git a/src/examples/ad9361-iiostream.c b/src/examples/ad9361-iiostream.c new file mode 100644 index 0000000..0756b7b --- /dev/null +++ b/src/examples/ad9361-iiostream.c @@ -0,0 +1,294 @@ +/* + * libiio - AD9361 IIO streaming example + * + * Copyright (C) 2014 IABG mbH + * Author: Michael Feilen <feilen_at_iabg.de> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + **/ + +#include <stdbool.h> +#include <stdint.h> +#include <string.h> +#include <signal.h> +#include <stdio.h> + +#ifdef __APPLE__ +#include <iio/iio.h> +#else +#include <iio.h> +#endif + +/* helper macros */ +#define MHZ(x) ((long long)(x*1000000.0 + .5)) +#define GHZ(x) ((long long)(x*1000000000.0 + .5)) + +#define ASSERT(expr) { \ + if (!(expr)) { \ + (void) fprintf(stderr, "assertion failed (%s:%d)\n", __FILE__, __LINE__); \ + (void) abort(); \ + } \ +} + +/* RX is input, TX is output */ +enum iodev { RX, TX }; + +/* common RX and TX streaming params */ +struct stream_cfg { + long long bw_hz; // Analog banwidth in Hz + long long fs_hz; // Baseband sample rate in Hz + long long lo_hz; // Local oscillator frequency in Hz + const char* rfport; // Port name +}; + +/* static scratch mem for strings */ +static char tmpstr[64]; + +/* IIO structs required for streaming */ +static struct iio_context *ctx = NULL; +static struct iio_channel *rx0_i = NULL; +static struct iio_channel *rx0_q = NULL; +static struct iio_channel *tx0_i = NULL; +static struct iio_channel *tx0_q = NULL; +static struct iio_buffer *rxbuf = NULL; +static struct iio_buffer *txbuf = NULL; + +static bool stop; + +/* cleanup and exit */ +static void shutdown() +{ + printf("* Destroying buffers\n"); + if (rxbuf) { iio_buffer_destroy(rxbuf); } + if (txbuf) { iio_buffer_destroy(txbuf); } + + printf("* Disabling streaming channels\n"); + if (rx0_i) { iio_channel_disable(rx0_i); } + if (rx0_q) { iio_channel_disable(rx0_q); } + if (tx0_i) { iio_channel_disable(tx0_i); } + if (tx0_q) { iio_channel_disable(tx0_q); } + + printf("* Destroying context\n"); + if (ctx) { iio_context_destroy(ctx); } + exit(0); +} + +static void handle_sig(int sig) +{ + printf("Waiting for process to finish...\n"); + stop = true; +} + +/* check return value of attr_write function */ +static void errchk(int v, const char* what) { + if (v < 0) { fprintf(stderr, "Error %d writing to channel \"%s\"\nvalue may not be supported.\n", v, what); shutdown(); } +} + +/* write attribute: long long int */ +static void wr_ch_lli(struct iio_channel *chn, const char* what, long long val) +{ + errchk(iio_channel_attr_write_longlong(chn, what, val), what); +} + +/* write attribute: string */ +static void wr_ch_str(struct iio_channel *chn, const char* what, const char* str) +{ + errchk(iio_channel_attr_write(chn, what, str), what); +} + +/* helper function generating channel names */ +static char* get_ch_name(const char* type, int id) +{ + snprintf(tmpstr, sizeof(tmpstr), "%s%d", type, id); + return tmpstr; +} + +/* returns ad9361 phy device */ +static struct iio_device* get_ad9361_phy(struct iio_context *ctx) +{ + struct iio_device *dev = iio_context_find_device(ctx, "ad9361-phy"); + ASSERT(dev && "No ad9361-phy found"); + return dev; +} + +/* finds AD9361 streaming IIO devices */ +static bool get_ad9361_stream_dev(struct iio_context *ctx, enum iodev d, struct iio_device **dev) +{ + switch (d) { + case TX: *dev = iio_context_find_device(ctx, "cf-ad9361-dds-core-lpc"); return *dev != NULL; + case RX: *dev = iio_context_find_device(ctx, "cf-ad9361-lpc"); return *dev != NULL; + default: ASSERT(0); return false; + } +} + +/* finds AD9361 streaming IIO channels */ +static bool get_ad9361_stream_ch(struct iio_context *ctx, enum iodev d, struct iio_device *dev, int chid, struct iio_channel **chn) +{ + *chn = iio_device_find_channel(dev, get_ch_name("voltage", chid), d == TX); + if (!*chn) + *chn = iio_device_find_channel(dev, get_ch_name("altvoltage", chid), d == TX); + return *chn != NULL; +} + +/* finds AD9361 phy IIO configuration channel with id chid */ +static bool get_phy_chan(struct iio_context *ctx, enum iodev d, int chid, struct iio_channel **chn) +{ + switch (d) { + case RX: *chn = iio_device_find_channel(get_ad9361_phy(ctx), get_ch_name("voltage", chid), false); return *chn != NULL; + case TX: *chn = iio_device_find_channel(get_ad9361_phy(ctx), get_ch_name("voltage", chid), true); return *chn != NULL; + default: ASSERT(0); return false; + } +} + +/* finds AD9361 local oscillator IIO configuration channels */ +static bool get_lo_chan(struct iio_context *ctx, enum iodev d, struct iio_channel **chn) +{ + switch (d) { + // LO chan is always output, i.e. true + case RX: *chn = iio_device_find_channel(get_ad9361_phy(ctx), get_ch_name("altvoltage", 0), true); return *chn != NULL; + case TX: *chn = iio_device_find_channel(get_ad9361_phy(ctx), get_ch_name("altvoltage", 1), true); return *chn != NULL; + default: ASSERT(0); return false; + } +} + +/* applies streaming configuration through IIO */ +bool cfg_ad9361_streaming_ch(struct iio_context *ctx, struct stream_cfg *cfg, enum iodev type, int chid) +{ + struct iio_channel *chn = NULL; + + // Configure phy and lo channels + printf("* Acquiring AD9361 phy channel %d\n", chid); + if (!get_phy_chan(ctx, type, chid, &chn)) { return false; } + wr_ch_str(chn, "rf_port_select", cfg->rfport); + wr_ch_lli(chn, "rf_bandwidth", cfg->bw_hz); + wr_ch_lli(chn, "sampling_frequency", cfg->fs_hz); + + // Configure LO channel + printf("* Acquiring AD9361 %s lo channel\n", type == TX ? "TX" : "RX"); + if (!get_lo_chan(ctx, type, &chn)) { return false; } + wr_ch_lli(chn, "frequency", cfg->lo_hz); + return true; +} + +/* simple configuration and streaming */ +int main (int argc, char **argv) +{ + // Streaming devices + struct iio_device *tx; + struct iio_device *rx; + + // RX and TX sample counters + size_t nrx = 0; + size_t ntx = 0; + + // Stream configurations + struct stream_cfg rxcfg; + struct stream_cfg txcfg; + + // Listen to ctrl+c and ASSERT + signal(SIGINT, handle_sig); + + // RX stream config + rxcfg.bw_hz = MHZ(2); // 2 MHz rf bandwidth + rxcfg.fs_hz = MHZ(2.5); // 2.5 MS/s rx sample rate + rxcfg.lo_hz = GHZ(2.5); // 2.5 GHz rf frequency + rxcfg.rfport = "A_BALANCED"; // port A (select for rf freq.) + + // TX stream config + txcfg.bw_hz = MHZ(1.5); // 1.5 MHz rf bandwidth + txcfg.fs_hz = MHZ(2.5); // 2.5 MS/s tx sample rate + txcfg.lo_hz = GHZ(2.5); // 2.5 GHz rf frequency + txcfg.rfport = "A"; // port A (select for rf freq.) + + printf("* Acquiring IIO context\n"); + ASSERT((ctx = iio_create_default_context()) && "No context"); + ASSERT(iio_context_get_devices_count(ctx) > 0 && "No devices"); + + printf("* Acquiring AD9361 streaming devices\n"); + ASSERT(get_ad9361_stream_dev(ctx, TX, &tx) && "No tx dev found"); + ASSERT(get_ad9361_stream_dev(ctx, RX, &rx) && "No rx dev found"); + + printf("* Configuring AD9361 for streaming\n"); + ASSERT(cfg_ad9361_streaming_ch(ctx, &rxcfg, RX, 0) && "RX port 0 not found"); + ASSERT(cfg_ad9361_streaming_ch(ctx, &txcfg, TX, 0) && "TX port 0 not found"); + + printf("* Initializing AD9361 IIO streaming channels\n"); + ASSERT(get_ad9361_stream_ch(ctx, RX, rx, 0, &rx0_i) && "RX chan i not found"); + ASSERT(get_ad9361_stream_ch(ctx, RX, rx, 1, &rx0_q) && "RX chan q not found"); + ASSERT(get_ad9361_stream_ch(ctx, TX, tx, 0, &tx0_i) && "TX chan i not found"); + ASSERT(get_ad9361_stream_ch(ctx, TX, tx, 1, &tx0_q) && "TX chan q not found"); + + printf("* Enabling IIO streaming channels\n"); + iio_channel_enable(rx0_i); + iio_channel_enable(rx0_q); + iio_channel_enable(tx0_i); + iio_channel_enable(tx0_q); + + printf("* Creating non-cyclic IIO buffers with 1 MiS\n"); + rxbuf = iio_device_create_buffer(rx, 1024*1024, false); + if (!rxbuf) { + perror("Could not create RX buffer"); + shutdown(); + } + txbuf = iio_device_create_buffer(tx, 1024*1024, false); + if (!txbuf) { + perror("Could not create TX buffer"); + shutdown(); + } + + printf("* Starting IO streaming (press CTRL+C to cancel)\n"); + while (!stop) + { + ssize_t nbytes_rx, nbytes_tx; + char *p_dat, *p_end; + ptrdiff_t p_inc; + + // Schedule TX buffer + nbytes_tx = iio_buffer_push(txbuf); + if (nbytes_tx < 0) { printf("Error pushing buf %d\n", (int) nbytes_tx); shutdown(); } + + // Refill RX buffer + nbytes_rx = iio_buffer_refill(rxbuf); + if (nbytes_rx < 0) { printf("Error refilling buf %d\n",(int) nbytes_rx); shutdown(); } + + // READ: Get pointers to RX buf and read IQ from RX buf port 0 + p_inc = iio_buffer_step(rxbuf); + p_end = iio_buffer_end(rxbuf); + for (p_dat = (char *)iio_buffer_first(rxbuf, rx0_i); p_dat < p_end; p_dat += p_inc) { + // Example: swap I and Q + const int16_t i = ((int16_t*)p_dat)[0]; // Real (I) + const int16_t q = ((int16_t*)p_dat)[1]; // Imag (Q) + ((int16_t*)p_dat)[0] = q; + ((int16_t*)p_dat)[1] = i; + } + + // WRITE: Get pointers to TX buf and write IQ to TX buf port 0 + p_inc = iio_buffer_step(txbuf); + p_end = iio_buffer_end(txbuf); + for (p_dat = (char *)iio_buffer_first(txbuf, tx0_i); p_dat < p_end; p_dat += p_inc) { + // Example: fill with zeros + // 12-bit sample needs to be MSB alligned so shift by 4 + // https://wiki.analog.com/resources/eval/user-guides/ad-fmcomms2-ebz/software/basic_iq_datafiles#binary_format + ((int16_t*)p_dat)[0] = 0 << 4; // Real (I) + ((int16_t*)p_dat)[1] = 0 << 4; // Imag (Q) + } + + // Sample counter increment and status output + nrx += nbytes_rx / iio_device_get_sample_size(rx); + ntx += nbytes_tx / iio_device_get_sample_size(tx); + printf("\tRX %8.2f MSmp, TX %8.2f MSmp\n", nrx/1e6, ntx/1e6); + } + + shutdown(); + + return 0; +} |