IIO firmware for the AD4110

Dependencies:   tempsensors sdp_k1_sdram

app/ad4110_data_capture.c

Committer:
Janani Sunil
Date:
2022-08-01
Revision:
1:a78dbaa4b05d
Parent:
0:6ca37a8f8ba9

File content as of revision 1:a78dbaa4b05d:

/***************************************************************************//**
 * @file    ad4110_data_capture.c
 * @brief   Source file for AD4110 Data capture
********************************************************************************
* Copyright (c) 2022 Analog Devices, Inc.
* All rights reserved.
*
* This software is proprietary to Analog Devices, Inc. and its licensors.
* By using this software you agree to the terms of the associated
* Analog Devices Software License Agreement.
*******************************************************************************/

/******************************************************************************/
/***************************** Include Files **********************************/
/******************************************************************************/

#include <string.h>
#include "ad4110_data_capture.h"
#include "ad4110_iio.h"
#include "ad4110.h"
#include "no_os_error.h"
#include "app_config.h"

/******************************************************************************/
/********************* Macros and Constants Definition ************************/
/******************************************************************************/

/* Timeout count to avoid stuck into potential infinite loop while checking
 * for new data into an acquisition buffer. The actual timeout factor is determined
 * through 'sampling_frequency' attribute of IIO app, but this period here makes sure
 * we are not stuck into a forever loop in case data capture is interrupted
 * or failed in between.
 * Note: This timeout factor is dependent upon the MCU clock frequency. Below timeout
 * is tested for SDP-K1 platform @180Mhz default core clock */
#define BUF_READ_TIMEOUT	0xffffffff

/******************************************************************************/
/******************** Variables and User Defined Data Types *******************/
/******************************************************************************/

/*
 *@enum	 acq_buffer_state_e
 *@details Data acquisition buffer states
 **/
typedef enum {
	BUF_AVAILABLE,
	BUF_EMPTY,
	BUF_FULL
} acq_buffer_state_e;

/*
 *@struct acq_buf_t
 *@details Data acquisition buffer parameters
 **/
typedef struct {
	acq_buffer_state_e state;	// Buffer state
	uint32_t wr_indx;			// Buffer write index (incremented per sample read)
	uint32_t rd_indx;			// Buffer read index (incremented per sample read)
	int8_t *wr_pdata;			// Data buffer write pointer
	int8_t *rd_pdata;			// Data buffer read pointer
	bool reindex_buffer;		// Reindex buffer to 0th channel
} acq_buf_t;

/* ADC data acquisition buffers */
static volatile acq_buf_t acq_buffer;

/* Number of samples requested by IIO client */
static volatile uint32_t num_of_requested_samples = 0;

/* Number of active channels */
static volatile uint8_t num_of_active_channels;

/* ADC sample/raw data size in bytes */
static volatile uint8_t sample_size_in_bytes;

/* ADC data buffer */
#if !defined(USE_SDRAM_CAPTURE_BUFFER)
int8_t adc_data_buffer[DATA_BUFFER_SIZE] = { 0 };
#endif

/* List of input channels to be captured */
static volatile uint8_t input_channels[AD4110_NUM_CHANNELS];

/* Flag to indicate data capture status */
static volatile bool start_cont_data_capture = false;

/* Max available buffer size (after considering the data alignment with IIO buffer) */
static volatile uint32_t max_buffer_sz;

/* Current active channel index */
static volatile uint8_t chn_indx;

/******************************************************************************/
/************************** Functions Declaration *****************************/
/******************************************************************************/

/******************************************************************************/
/************************** Functions Definition ******************************/
/******************************************************************************/

/*!
 * @brief Reset the data capture specific variables
 * @return none
 */
static void reset_data_capture(void)
{
	/* Reset data capture flags */
	num_of_active_channels = 0;
	start_cont_data_capture = false;

	/* Reset acquisition buffer states and clear old data */
	acq_buffer.state = BUF_EMPTY;
	acq_buffer.wr_indx = 0;
	acq_buffer.rd_indx = 0;
	acq_buffer.reindex_buffer = false;

	acq_buffer.wr_pdata = adc_data_buffer;
	acq_buffer.rd_pdata = adc_data_buffer;
	max_buffer_sz = DATA_BUFFER_SIZE;
}


/*!
 * @brief Trigger a data capture in continuous/burst mode
 * @return 0 in case of success, negative error code otherwise
 */
static int32_t adc_start_data_capture(void)
{
	return ad4110_set_adc_mode(ad4110_dev_inst, AD4110_CONTINOUS_CONV_MODE);
}


/*!
 * @brief Stop a data capture operation
 * @return 0 in case of success, negative error code otherwise
 */
static int32_t adc_stop_data_capture(void)
{
	return ad4110_set_adc_mode(ad4110_dev_inst, AD4110_STANDBY_MODE);
}


/*!
 * @brief Function to prepare the data ADC capture for new READBUFF
 *	    request from IIO client (for active channels)
 * @param chn_mask[in] - Channels to enable for data capturing
 * @param num_of_chns[in] - ADC channel count
 * @param sample_size[in] - Sample size in bytes
 * @return 0 in case of success, negative error code otherwise
 */
int32_t prepare_data_transfer(uint32_t ch_mask, uint8_t num_of_chns,
			      uint8_t sample_size)
{
	int32_t ret;
	uint8_t ch_id;
	uint8_t mask = 0x1;

	/* Reset the data capture module */
	reset_data_capture();

	sample_size_in_bytes = sample_size;

	/* Enable Active channels requested and Disable the remaining */
	for (ch_id = 0;
	     ch_id < num_of_chns; ch_id++) {
		if (ch_mask & mask) {
			ret = ad4110_set_channel_status(ad4110_dev_inst, ch_id, true);
			if (ret) {
				return ret;
			}
			num_of_requested_samples++;
		} else {
			ret = ad4110_set_channel_status(ad4110_dev_inst, ch_id, false);
			if (ret) {
				return ret;
			}
		}
		mask <<= 1;
	}

	/* Trigger continuous data capture */
#if (DATA_CAPTURE_MODE == CONTINUOUS_DATA_CAPTURE)
	ret = adc_start_data_capture();
	if (ret) {
		return ret;
	}

	acq_buffer.state = BUF_AVAILABLE;
	start_cont_data_capture = true;

	/* Pull the cs line low to detect the EOC bit during data capture */
	ret = no_os_gpio_set_value(csb_gpio, NO_OS_GPIO_LOW);
	if (ret) {
		return ret;
	}

	ret = no_os_irq_enable(external_int_desc, IRQ_INT_ID);
	if(ret) {
		return ret;
	}
#endif

	return 0;
}


/*!
 * @brief Function to end data capture
 * @return 0 in case of success, negative error code otherwise
 */
int32_t end_data_transfer(void)
{
	start_cont_data_capture = false;

	/* Reset data capture module specific flags and variables */
	reset_data_capture();

	/* Stop ADC data capture */
	return adc_stop_data_capture();
}


/*!
 * @brief Capture requested number of ADC samples in burst mode
 * @param pbuf[out] - Pointer to ADC data buffer
 * @param nb_of_samples[in] - Number of samples to be read
 * @return 0 in case of success, negative error code otherwise
 */
static int32_t read_burst_data(int8_t *pbuf, uint32_t nb_of_samples)
{
	uint32_t sample_index = 0;
	uint32_t adc_raw_data = 0;
	int32_t ret;

	if (adc_start_data_capture()) {
		return -EIO;
	}

	while (sample_index < nb_of_samples) {
		/* Wait for the RDY Bit to go low to notify end of conversion */
		ret = ad4110_wait_for_rdy_low(ad4110_dev_inst, AD4110_ADC_CONV_TIMEOUT);
		if (ret) {
			return ret;
		}

		/* Read the converted data from the Data register */
		ret = ad4110_spi_int_data_reg_read(ad4110_dev_inst, &adc_raw_data);
		if (ret) {
			return ret;
		}

		/* Copy raw data to the buffer */
		memcpy((uint8_t*)pbuf, &adc_raw_data, sample_size_in_bytes);
		sample_index++;
		pbuf += sample_size_in_bytes;
	}

	/* Stop any active conversion */
	ret = adc_stop_data_capture();
	if (ret) {
		return ret;
	}

	return 0;
}


/*!
 * @brief Perform buffer read operations to read requested samples
 * @param nb_of_samples[in] - Requested number of samples to read
 * @return 0 in case of success, negative error code otherwise
 */
static int32_t buffer_read_operations(uint32_t nb_of_samples)
{
	uint32_t timeout = BUF_READ_TIMEOUT;
	int32_t offset;

	/* Wait until requested samples are available in the buffer to read */
	do {
		if (acq_buffer.wr_indx >= acq_buffer.rd_indx) {
			offset = acq_buffer.wr_indx - acq_buffer.rd_indx;
		} else {
			offset = max_buffer_sz + (acq_buffer.wr_indx - acq_buffer.rd_indx);
		}

		timeout--;
	} while ((offset < (int32_t)(nb_of_samples)) && (timeout > 0));

	if (timeout == 0) {
		/* This returns the empty buffer */
		return -EIO;
	}

	if (acq_buffer.rd_indx >= max_buffer_sz) {
		acq_buffer.rd_indx = 0;
	}

	return 0;
}


/*!
 * @brief Perform buffer write operations such as buffer full or empty
 *	  check, resetting buffer index and pointers, etc
 * @return none
 */
static void buffer_write_operations(void)
{
	acq_buffer.wr_indx++;

	/* Perform buffer full check and operations */
	if (acq_buffer.wr_indx >= max_buffer_sz) {
		if ((acq_buffer.rd_indx >= num_of_requested_samples)
		    && (acq_buffer.rd_indx != 0)) {
			/* Reset buffer write index and write pointer when enough
			 * space available in the buffer to wrap to start */
			acq_buffer.wr_indx = 0;

			acq_buffer.wr_pdata = adc_data_buffer;
			if (acq_buffer.rd_indx >= max_buffer_sz) {
				/* Wrap the read index and read pointer to start of buffer
				 * if buffer is completely read/emptied */
				acq_buffer.rd_indx = 0;
				acq_buffer.rd_pdata = adc_data_buffer;
			}

			acq_buffer.state = BUF_AVAILABLE;
		} else {
			/* Wait until enough space available to wrap buffer write index
			 * at the start of buffer */
			acq_buffer.wr_indx = max_buffer_sz;
			acq_buffer.state = BUF_FULL;
			acq_buffer.reindex_buffer = true;
		}
	}
}


/*!
 * @brief Read requested number of ADC samples in continuous mode
 * @param pbuf[in] - Pointer to data buffer
 * @param nb_of_samples[in] - Number of samples to read
 * @return 0 in case of success, negative error code otherwise
 * @note The actual sample capturing happens through interrupt. This
 *			function tracks the buffer read pointer to read block of data
 */
static int32_t read_continuous_conv_data(int8_t **pbuf, uint32_t nb_of_samples)
{
	volatile int8_t *rd_addr;
	int32_t ret;

	/* Determine the max available buffer size based on the requested
	 * samples count and actual avilable buffer size. Buffer should be
	 * capable of holding all requested 'n' samples from previous write
	 * index upto to the end of buffer, as data is read linearly
	 * from adc buffer in IIO library.
	 * E.g. If actual buffer size is 2048 samples and requested samples
	 * are 1600, max available buffer size is actually 1600. So in given
	 * iteration, only 1600 samples will be stored into buffer and after
	 * that buffer indexes will be wrapped to a start of buffer. If index
	 * is not wrapped, the next 1600 requested samples won't accomodate into
	 * remaining 448 samples space. As buffer is read in linear fashion, the
	 * read index can't be wrapped to start of buffer to read remaining samples.
	 * So max available space in this case is 2048 but only utilized space
	 * will be 1600 in single read buffer request from IIO client.
	 **/
	max_buffer_sz = ((DATA_BUFFER_SIZE / sample_size_in_bytes) /
			 nb_of_samples) * nb_of_samples;

	ret = buffer_read_operations(nb_of_samples);
	if (ret) {
		return ret;
	}

	/* Get the next read address */
	rd_addr = (volatile int8_t *)(acq_buffer.rd_pdata + (acq_buffer.rd_indx *
				      sample_size_in_bytes));
	acq_buffer.rd_indx += nb_of_samples;

	/* Update the IIO buffer pointer to point to next read start location */
	*pbuf = rd_addr;

	return 0;
}


/*!
 * @brief Read ADC raw data for recently sampled channel
 * @param adc_data[out] - Pointer to adc data read variable
 * @param input_chn[in] - Input channel
 * @return 0 in case of success, negative error code otherwise
 * @note This function is intended to call from the conversion end trigger
 *	 event. Therefore, this function should just read raw ADC data
 *	 without further monitoring conversion end event
 */
static int32_t adc_read_converted_sample(uint32_t *adc_data, uint8_t input_chn)
{
	int32_t ret;

	if (!adc_data) {
		return -EIO;
	}

	ret = ad4110_spi_int_data_reg_read(ad4110_dev_inst, adc_data);
	if (ret) {
		return ret;
	}

	/* Pull the cs line low to detect the EOC bit during data capture */
	ret = no_os_gpio_set_value(csb_gpio, NO_OS_GPIO_LOW);
	if (ret) {
		return ret;
	}

	ret = no_os_irq_enable(external_int_desc, IRQ_INT_ID);
	if (ret) {
		return ret;
	}

	return 0;
}


/*!
 * @brief This is an ISR (Interrupt Service Routine) to monitor end of conversion event.
 * @param ctx[in] - Callback context (unused)
 * @return none
 * @details This is an Interrupt callback function/ISR invoked in synchronous/asynchronous
 *			manner depending upon the application implementation. The conversion results
 *			are read into acquisition buffer and control continue to sample next channel.
 *			This continues until conversion is stopped (through IIO client command)
 */
void data_capture_callback(void *ctx)
{
	uint32_t adc_sample;
	volatile uint8_t *wr_addr;

	/* The callback function is triggered when the first falling edge is detected
	 * on the MISO pin, indicating End of Conversion. Interrupt is disabled
	 because any data transaction on the  SPI line could be interpreted
	 by the MCU as a falling edge.
	 */

	no_os_irq_disable(external_int_desc, IRQ_INT_ID);

	if (start_cont_data_capture == true) {
		/* Read the sample for channel which has been sampled recently */
		if (adc_read_converted_sample(&adc_sample,
					      input_channels[chn_indx]) == 0) {
			do {
				if (acq_buffer.state == BUF_AVAILABLE) {
					if (acq_buffer.reindex_buffer) {
						/* Buffer refilling must start with first active channel data
						 * for IIO client to synchronize the buffered data */
						if (chn_indx != 0) {
							break;
						}
						acq_buffer.reindex_buffer = false;
					}

					/* Copy adc samples into acquisition buffer to transport over
					 * communication link */
					wr_addr = (volatile uint8_t *)(acq_buffer.wr_pdata + (acq_buffer.wr_indx *
								       sample_size_in_bytes));
					memcpy((uint8_t *)wr_addr, &adc_sample, sample_size_in_bytes);
				}

				/* Perform buffer write operations */
				buffer_write_operations();
			} while (0);

			/* Track the count for recently sampled channel */
			chn_indx++;
			if (chn_indx >= num_of_active_channels) {
				chn_indx = 0;
			}
		}
	}
}


/*!
 * @brief Function to read the ADC buffered raw data requested by IIO client
 * @param pbuf[in] - Pointer to data buffer
 * @param nb_of_bytes[in] - Number of bytes to read
 * @return 0 in case of success, negative error code otherwise
 */
int32_t read_buffered_data(int8_t **pbuf, uint32_t nb_of_bytes)
{
	int32_t ret;
	num_of_requested_samples = nb_of_bytes / sample_size_in_bytes;

#if (DATA_CAPTURE_MODE == BURST_DATA_CAPTURE)
	ret = read_burst_data(*pbuf, num_of_requested_samples);
#else
	ret = read_continuous_conv_data(pbuf, num_of_requested_samples);
#endif
	if (ret) {
		return ret;
	}

	return 0;
}