Example Program for EVAL-AD7606

Dependencies:   platform_drivers

app/ad7606_data_capture.c

Committer:
mahphalke
Date:
2020-10-16
Revision:
3:83b3133f544a
Parent:
1:819ac9aa5667
Child:
6:32de160dce43

File content as of revision 3:83b3133f544a:

/***************************************************************************//**
 *   @file    ad7606_data_capture.c
 *   @brief   Data capture interface for AD7606 IIO application
 *   @details This module handles the AD7606 data capturing
********************************************************************************
 * Copyright (c) 2020 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 <stdlib.h>

#include "app_config.h"
#include "ad7606_data_capture.h"
#include "ad7606_support.h"
#include "gpio_extra.h"

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

#if (AD7606X_ADC_RESOLUTION > 16)
#define		BYTES_PER_SAMPLE	sizeof(uint32_t)
#else
#define		BYTES_PER_SAMPLE	sizeof(uint16_t)
#endif

/* Max size of the acquisition buffer (in terms of samples) */
#define DATA_BUFFER_SIZE		(8192)

/* 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, but this period here makes sure
 * we are not stuck into a loop forever, in case data capture interrupted
 * or failed in between */
#define BUF_READ_TIMEOUT	(5000000)

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

/* Device instance for background adc conversion callback */
static volatile struct ad7606_dev *dev = NULL;

/*
 *@enum		acq_buffer_state_e
 *@details	Enum holding the data acquisition buffer states
 **/
typedef enum {
	BUF_EMPTY,
	BUF_AVAILABLE,
	BUF_FULL
} acq_buffer_state_e;

/*
 *@struct	acq_buf_t
 *@details	Structure holding the data acquisition buffer parameters
 **/
typedef struct {
	acq_buffer_state_e state;	// buffer state
	uint32_t rd_indx;			// buffer read index
	uint32_t wr_indx;			// buffer write index
	uint32_t align_cnt;			// buffer alignment counter
#if (AD7606X_ADC_RESOLUTION > 16)
	uint32_t data[DATA_BUFFER_SIZE];
#else
	uint16_t data[DATA_BUFFER_SIZE];
#endif
} acq_buf_t;

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

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

/* Active channels to be captured */
static volatile uint32_t active_channels = 0;

/* Number of active channels */
static uint8_t num_of_active_channels = 0;

/* Minimum number of bytes to be read from device over digital interface */
static volatile uint8_t min_bytes_to_read = 0;

/* Number of channels requested for read into acquisition buffer */
static volatile uint8_t chn_read_cnt = 0;

/* Number of samples requested by IIO client */
static uint16_t num_of_samples = 0;

/* Max available size of buffer */
static uint16_t max_available_buffer_size = 0;

/******************************************************************************/
/************************ Functions Declarations ******************************/
/******************************************************************************/

static void adjust_buffer_offset(uint8_t *arr_indx, uint8_t *offset_end,
				 uint8_t *mask);

/******************************************************************************/
/************************ Functions Definitions *******************************/
/******************************************************************************/

/*!
 * @brief	Function to init the data capture for AD7606 device
 * @param	device[in]- Device instance
 * @return	none
 */
int32_t iio_data_capture_init(struct ad7606_dev *device)
{
	/* Save the device structure for background conversion */
	dev = malloc(sizeof(struct ad7606_dev *));
	if (!dev) {
		return FAILURE;
	}

	memcpy(&dev, &device, sizeof(dev));

	return SUCCESS;
}


/*!
 * @brief	Function to read the ADC raw data for single channel
 * @param	device[in]- Device instance
 * @param	chn[in] - Input channel
 * @return	adc raw data/sample
 */
int32_t single_data_read(void *device, uint8_t chn, polarity_e polarity)
{
	int32_t adc_data = 0;
	uint32_t adc_raw[AD7606X_ADC_RESOLUTION] = { 0 };

	if (ad7606_read(device, adc_raw) != SUCCESS) {
		adc_raw[chn] = 0;
	}

	if (polarity == BIPOLAR) {
		/* Check for negative adc value for bipolar inputs */
		if (adc_raw[chn] >= ADC_MAX_COUNT_BIPOLAR) {
			/* Take the 2s complement for the negative counts (>full scale value) */
			adc_data = ADC_MAX_COUNT_UNIPOLAR - adc_raw[chn];
			adc_data = 0 - adc_data;
		} else {
			adc_data = adc_raw[chn];
		}
	} else {
		adc_data = adc_raw[chn];
	}

	return adc_data;
}


/*!
 * @brief	Function to store the number of requested samples from IIO client
 * @param	bytes[in] - Number of bytes corresponding to requested samples
 * @return	none
 */
void store_requested_samples_count(size_t bytes)
{
	/* This gets the number of samples requested by IIO client for all active channels */
	num_of_samples = bytes / BYTES_PER_SAMPLE;

	/* Get the actual available size of buffer by aligning with number of requested samples.
	 * e.g. if requested samples are 1050, the max available size of buffer is:
	 * available size = (8192 / 1050) * 1050 = 7 * 1050 = 7350.
	 * The max samples to be requested should always be less than half the max size of buffer
	 * (in this case: 8192 / 2 = 4096).
	 * */
	max_available_buffer_size = ((DATA_BUFFER_SIZE / num_of_samples) *
				     num_of_samples);
}


/*!
 * @brief	Function to read new samples into buffer without timeout IIO request
 * @param	input_buffer[in] - Input data acquisition buffer
 * @param	output_buffer[in, out] - Output data buffer
 * @param	samples_to_read[in] - Number of samples to read
 * @return	none
 */
static void wait_and_read_new_samples(volatile acq_buf_t *input_buffer,
				      char *output_buffer,
				      size_t samples_to_read)
{
	int32_t buff_rd_wr_indx_offset; 			// Offset b/w buffer read and write indexes
	uint32_t timeout = BUF_READ_TIMEOUT;  		// Buffer new data read timeout count
	size_t bytes = samples_to_read * BYTES_PER_SAMPLE;

	/* Copy the bytes into buffer provided there is enough offset b/w read and write counts.
	* If there is overlap b/w read and write indexes (read is faster than write), empty buffer
	* should be returned to IIO client to avoid IIO request timeout */
	do {
		buff_rd_wr_indx_offset = (int32_t)input_buffer->wr_indx -
					 (int32_t)input_buffer->rd_indx;
		timeout--;
	} while (((buff_rd_wr_indx_offset < (int32_t)(samples_to_read))
		  || (input_buffer->wr_indx < input_buffer->rd_indx)) && (timeout > 0));

	if ((timeout == 0) || (input_buffer->wr_indx <= 0)) {
		/* This returns the empty buffer */
		return;
	}

	memcpy(output_buffer,
	       (void const *)&input_buffer->data[input_buffer->rd_indx],
	       bytes);
	input_buffer->rd_indx += samples_to_read;
}


/*!
 * @brief	Function to read and align the ADC buffered raw data
 * @param	device[in]- Device instance
 * @param	pbuf[out] - Buffer to load ADC raw data
 * @param	bytes[in] - Number of bytes to be read
 * @param	active_chns_mask[in] - Active channels mask
 * @return	Number of bytes read
 */
size_t buffered_data_read(char *pbuf, size_t bytes, size_t offset,
			  uint32_t active_chns_mask)
{
	size_t samples_to_read = bytes / BYTES_PER_SAMPLE;	// Bytes to sample conversion

	/* Make sure requested samples size is less than ADC buffer size. Return constant
	 * value to avoid IIO client getting timed-out */
	if (num_of_samples >= max_available_buffer_size) {
		memset(pbuf, 1, bytes);
		return bytes;
	}

	wait_and_read_new_samples(&acq_buffer, pbuf, samples_to_read);

	/* Make buffer available again once read completely */
	if (acq_buffer.rd_indx >= max_available_buffer_size) {
		acq_buffer.rd_indx = 0;
		acq_buffer.wr_indx = 0;
		acq_buffer.align_cnt = 0;
		acq_buffer.state = BUF_AVAILABLE;
	}

	return bytes;
}


/*!
 * @brief	Function to perform background ADC conversion and data capture
 * @return	none
 * @details	This is an External Interrupt callback function/ISR, which is tied up to
 *			falling edge trigger of BUSY pin. It is trigered when previous data
 *			conversion is over and BUSY line goes low. Upon trigger, conversion
 *			results are read into acquisition buffer and next conversion is triggered.
 *			This continues until background conversion is stopped.
 */
void do_conversion_callback(void)
{
#if (AD7606X_ADC_RESOLUTION == 18)
	uint8_t arr_indx = 0;
	uint8_t offset_end = 2;
	uint8_t mask = 0xff;
#else
	uint8_t mask = 0x01;
#endif

	if (start_adc_data_capture == true) {

		/* Read the conversion result for required number of bytes */
		if (ad7606_read_conversion_data(dev, min_bytes_to_read) == SUCCESS) {

			/* Extract the data based on the active channels selected in IIO client.
			 * Note: The extraction of data based on active channel needs
			 * to be done since its not possible to capture individual
			 * channel in AD7606 devices */
			for (uint8_t chn = 0; chn < chn_read_cnt; chn++) {
				if (active_channels & mask) {
					if (acq_buffer.state == BUF_AVAILABLE) {
#if (AD7606X_ADC_RESOLUTION == 18)
						/* For AD7606C device (18-bit resolution) */
						acq_buffer.data[acq_buffer.wr_indx++] =
							(((uint32_t)(dev->data[arr_indx] & mask) << 16) |    			// MSB
							 ((uint32_t)dev->data[arr_indx + 1] << 8) |
							 ((uint32_t)(dev->data[arr_indx + 2] >> (8 - offset_end))));   	// LSB
#else

						acq_buffer.data[acq_buffer.wr_indx++] =
							(uint16_t)(((uint16_t)dev->data[chn << 1] << 8) |   // MSB
								   dev->data[(chn << 1) + 1]);     				// LSB
#endif

						acq_buffer.align_cnt++;

						/* Monitor primary buffer full state */
						if (acq_buffer.wr_indx >= max_available_buffer_size) {
							acq_buffer.state = BUF_FULL;

							/* This aligns the buffer to first active channel once all requested samples are transmitted */
							if (acq_buffer.align_cnt >= num_of_samples) {
								acq_buffer.align_cnt = 0;
							}
#if (AD7606X_ADC_RESOLUTION == 18)
							adjust_buffer_offset(&arr_indx, &offset_end, &mask);
#endif
							mask <<= 1;
							continue;
						}

						/* This aligns the buffer to first active channel once all requested samples are transmitted */
						if (acq_buffer.align_cnt >= num_of_samples) {
							acq_buffer.align_cnt = 0;
							break;
						}
					}
				}

#if (AD7606X_ADC_RESOLUTION == 18)
				adjust_buffer_offset(&arr_indx, &offset_end, &mask);
#endif

				mask <<= 1;
			}

			/* Trigger next conversion */
			if (ad7606_convst(dev) != SUCCESS) {
				start_adc_data_capture = false;
			}
		} else {
			start_adc_data_capture = false;
		}
	}
}


/*!
 * @brief	Function to trigger bakground ADC conversion for new READBUFF
 *              request from IIO client (for active channels)
 * @return	Conversion start status
 */
void start_background_data_capture(uint32_t ch_mask, size_t bytes_count)
{
	uint8_t mask = 0x1;

	/* Make sure requested samples size is less than ADC buffer size */
	if (num_of_samples >= max_available_buffer_size)
		return;

	active_channels = ch_mask;

	stop_background_data_capture();

	/* Find the minimum number of bytes to be read from device during
	 * background conversion (Reading all bytes/channels adds extra overhead,
	 * so restrict the minimum reads to whatever is possible).
	 * Note: This happens only at the start of buffer read request. */
	for (uint8_t chn = 1, indx = 0; chn <= AD7606X_ADC_CHANNELS; chn++, indx++) {
		if (ch_mask & mask) {
			/* 1 sample = 2 or 4 bytes */
			min_bytes_to_read = chn * BYTES_PER_SAMPLE;
			if (min_bytes_to_read > AD7606X_ADC_RESOLUTION) {
				min_bytes_to_read = AD7606X_ADC_RESOLUTION;
			}

			/* Get the count for number of samples to be stored into acquisition buffer */
			chn_read_cnt = chn;
			num_of_active_channels++;
		}

		mask <<= 1;
	}

	/* Make primary acquisition buffer available and start conversion */
	acq_buffer.state = BUF_AVAILABLE;
	if (ad7606_convst(dev) == SUCCESS) {
		start_adc_data_capture = true;
	} else {
		start_adc_data_capture = false;
	}
}


/*!
 * @brief	Function to stop background ADC data capture
 * @return	none
 */
void stop_background_data_capture(void)
{
	/* Reset data capture flags */
	start_adc_data_capture = false;
	num_of_active_channels = 0;
	min_bytes_to_read = 0;
	chn_read_cnt = 0;

	/* Reset acquisition buffer states and clear old data */
	acq_buffer.state = BUF_EMPTY;

	memset((void *)acq_buffer.data, 0, sizeof(acq_buffer.data));

	acq_buffer.wr_indx = 0;
	acq_buffer.rd_indx = 0;
	acq_buffer.align_cnt = 0;
}


/*!
 * @brief	Function to adjust the data buffer offset
 * @param	*arr_indx[in, out]- Array index
 * @param	*offset_end[out] - offset to extract LSB from 18-bit data
 * @param	*mask[out] - channel select mask
 * @return	none
 */
static void adjust_buffer_offset(uint8_t *arr_indx, uint8_t *offset_end,
				 uint8_t *mask)
{
	*arr_indx += 2;

	/* Track array to reach at the middle (9 bytes apart) to change the offsets */
	if (*arr_indx == ((AD7606X_ADC_RESOLUTION / 2) - 1)) {
		(*arr_indx)++;
		*offset_end = 2;
		*mask = 0xff;
	} else {
		*mask = (0xff >> *offset_end);
		*offset_end += 2;
	}
}