Forked repository for pushing changes

adc_data_capture.c

Committer:
mahphalke
Date:
2021-07-13
Revision:
17:af1f2416dd26
Child:
18:5ae03a197e59

File content as of revision 17:af1f2416dd26:

/***************************************************************************//**
 *   @file    adc_data_capture.c
 *   @brief   ADC common data capture interface for IIO based applications
 *   @details This module handles the ADC data capturing for IIO client
********************************************************************************
 * Copyright (c) 2021 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 <stdint.h>
#include <stdbool.h>
#include <string.h>

#include "adc_data_capture.h"
#include "error.h"

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

/* IIO buffer request size in bytes */
#define IIO_BUF_READ_REQUEST_SIZE	(256)

/* 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	(100000000)

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

/* Extern declaration for data capture operations structure variable */
extern struct data_capture_ops data_capture_ops;

/*
 *@enum		acq_buffer_state_e
 *@details	Enum holding the data acquisition buffer states
 **/
typedef enum {
	BUF_AVAILABLE,
	BUF_EMPTY,
	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 sample_cnt;  				// buffer data/sample counter
	uint32_t data[DATA_BUFFER_SIZE];  	// buffer data (adc raw values)
} acq_buf_t;

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

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

/* Number of active channels in any data buffer read request */
static volatile uint8_t num_of_active_channels = 0;

/* Channel data alignment variables */
static volatile uint8_t buff_chn_indx = 0;
static volatile bool do_chn_alignment = false;

/* Count to track number of actual samples requested by IIO client */
static volatile uint16_t num_of_requested_samples = 0;

/* Actual or max available size of acquisition buffer */
static volatile uint16_t max_available_buffer_size = 0;

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

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

/*!
 * @brief	Function to read the ADC raw data for single channel
 * @param	input_chn[in] - Input channel to sample and read data for
 * @param	raw_data[in, out]- ADC raw data
 * @return	SUCCESS in case of success, FAILURE otherwise
 */
int32_t single_data_read(uint8_t input_chn, uint32_t *raw_data)
{
	uint32_t read_adc_data = 0;
	int32_t status = SUCCESS;

	do {
		if (data_capture_ops.save_prev_active_chns) {
			/* Save previously active channels */
			if (data_capture_ops.save_prev_active_chns() != SUCCESS) {
				status = FAILURE;
				break;
			}
		}

		if (data_capture_ops.disable_all_chns) {
			/* Disable all channels */
			if (data_capture_ops.disable_all_chns() != SUCCESS) {
				status = FAILURE;
				break;
			}
		}

		if (data_capture_ops.enable_curr_chn) {
			/* Enable user input channel */
			if (data_capture_ops.enable_curr_chn(input_chn) != SUCCESS) {
				status = FAILURE;
				break;
			}
		}

		if (data_capture_ops.enable_single_read_conversion) {
			/* Enable single data read conversion */
			if (data_capture_ops.enable_single_read_conversion() != SUCCESS) {
				status = FAILURE;
				break;
			}
		}

		/* Read the data */
		if (data_capture_ops.wait_for_conv_and_read_data(&read_adc_data, input_chn) !=
		    SUCCESS) {
			status = FAILURE;
			break;
		}
	} while (0);

	if (data_capture_ops.restore_prev_active_chns) {
		/* Enable back channels which were disabled prior to single data read */
		data_capture_ops.restore_prev_active_chns();
	}

	if (data_capture_ops.disable_conversion) {
		/* Exit from conversion mode */
		data_capture_ops.disable_conversion();
	}

	*raw_data = read_adc_data;
	return status;
}


/*!
 * @brief	Function to store the number of actul requested samples from IIO client
 * @param	bytes[in] - Number of bytes corresponding to requested samples
 * @param	sample_size_in_bytes[in] - Size of each sample in bytes (eqv to ADC resolution)
 * @return	none
 */
void store_requested_samples_count(size_t bytes, uint8_t sample_size_in_bytes)
{
	/* This gets the number of samples requested by IIO client for all active channels */
	num_of_requested_samples = bytes / sample_size_in_bytes;

	/* Get the actual available size of buffer by aligning with number of requested samples.
	 * e.g. if requested samples are 400, the max available size of buffer is:
	 * available size = (2048 / 400) * 400 = 5 * 400 = 2000.
	 * The max samples to be requested should always be less than half the max size of buffer
	 * (in this case: 2048 / 2 = 1024).
	 * */
	max_available_buffer_size = ((DATA_BUFFER_SIZE / num_of_requested_samples) *
				     num_of_requested_samples);
}


/*!
 * @brief	Function to read new samples into buffer without IIO request timeout
 * @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
 * @param	sample_size_in_bytes[in] - Size of each sample in bytes (eqv to ADC resolution)
 * @return	none
 */
static void wait_and_read_new_samples(char *output_buffer,
				      size_t samples_to_read,
				      uint8_t sample_size_in_bytes)
{
	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 * sample_size_in_bytes;

	/* 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 = (acq_buffer.wr_indx - acq_buffer.rd_indx);
		timeout--;
	} while (((buff_rd_wr_indx_offset < (int32_t)(samples_to_read))
		  || (acq_buffer.wr_indx < acq_buffer.rd_indx)) && (timeout > 0)
		 && start_adc_data_capture);

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

	memcpy(output_buffer,
	       (void const *)&acq_buffer.data[acq_buffer.rd_indx],
	       bytes);
	acq_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, uint8_t sample_size_in_bytes)
{
	size_t samples_to_read = bytes /
				 sample_size_in_bytes;   	// 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_requested_samples >= max_available_buffer_size) {
		memset(pbuf, 1, bytes);
		return bytes;
	}

	wait_and_read_new_samples(pbuf, samples_to_read, sample_size_in_bytes);

	/* 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.sample_cnt = 0;
		acq_buffer.state = BUF_AVAILABLE;
	}

	return bytes;
}


/*!
 * @brief	This is an ISR (Interrupt Service Routine) to monitor end of conversion event.
 * @param	*ctx[in] - Callback context (unused)
 * @param	event[in] - Callback event (unused)
 * @param	extra[in] - Callback extra (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)
 * @note	This function also handles the logic to align the zeroth channel data after
 *			every 'n' sample transmission. This is required to visualize data properly
 *			on IIO client application (more details listed below).
 */
void data_capture_callback(void *ctx, uint32_t event, void *extra)
{
	uint32_t adc_sample;

	if (start_adc_data_capture == true) {
		do {
			/* Read the data for channel which has been sampled recently */
			if (data_capture_ops.read_sampled_data(&adc_sample, buff_chn_indx) != SUCCESS) {
				break;
			}

			/* If next sample/data in acquisition buffer is required to be of the zeroth
			 * channel from a list of enabled channels (i.e. chn data alignment needed), then
			 * wait until a conversion end event is triggered for that zeroth enabled channel */
			/* Note: As shown below, after nth sample read, next sample must be for the zeroth
			 *       channel present in the list of enabled channels */
			/*+-----------------------+-------------------------+---------------------------+
			 *| 0 | 1 | 2 | ------| n | 0 | 1 | 2 | --------| n | 0 | 1 | 2 |-----------| n |
			 *+-^-------------------^-+-^---------------------^-+-^-----------------------^-+
			 *  |                   |   |                     |   |                       |
			 * 0th chn data  last data  0th chn data   last data  0th chn data         last data
			 * n = number of requested samples
			 **/
			/* Wait until conversion event for the zeroth channel is triggered */
			if ((do_chn_alignment == true) && (buff_chn_indx != 0)) {
				/* Track the count for recently sampled channel */
				buff_chn_indx++;
				if (buff_chn_indx >= num_of_active_channels) {
					buff_chn_indx = 0;
				}

				/* If recent sampled channel is not a zeroth enabled channel
				 * in the channels list, return without storing data into buffer */
				break;
			}

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

			if (acq_buffer.state == BUF_AVAILABLE) {
				/* Reset channel data alignment flag if control reach here */
				do_chn_alignment = false;

				acq_buffer.data[acq_buffer.wr_indx++] = adc_sample;
				acq_buffer.sample_cnt++;

				/* Check if current buffer is full */
				if (acq_buffer.wr_indx >= max_available_buffer_size) {
					acq_buffer.state = BUF_FULL;
				}

				/* Once all requested number of samples are transmitted, make sure
				 * next data to be loaded into acquisition buffer is for zeroth channel
				 * present in the channels list */
				if (acq_buffer.sample_cnt >= num_of_requested_samples) {
					acq_buffer.sample_cnt = 0;
					do_chn_alignment = true;
				}
			}
		} while (0);

		/* Trigger next continuous conversion (optional or device dependent) */
		if (data_capture_ops.trigger_next_cont_conversion) {
			data_capture_ops.trigger_next_cont_conversion();
		}
	}
}


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

	/* 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.sample_cnt = 0;

	buff_chn_indx = 0;
	do_chn_alignment = true;
}


/*!
 * @brief	Function to trigger ADC conversion for new READBUFF
 *              request from IIO client (for active channels)
 * @param	ch_mask[in] - Channels to enable for data capturing
 * @param	num_of_chns[in] - ADC channel count
 * @return	none
 */
void start_data_capture(uint32_t ch_mask, uint8_t num_of_chns)
{
	uint32_t mask = 0x1;

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

	if (data_capture_ops.disable_conversion) {
		/* Stop any previous conversion by putting ADC into standby mode */
		if (data_capture_ops.disable_conversion() != SUCCESS) {
			return;
		}
	}

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

	if (data_capture_ops.save_prev_active_chns) {
		/* Store the previous active channels */
		if (data_capture_ops.save_prev_active_chns() != SUCCESS) {
			return;
		}
	}

	/* Enable/Disable channels based on channel mask set in the IIO client */
	for (uint8_t chn = 0; chn < num_of_chns; chn++) {
		if (ch_mask & mask) {
			if (data_capture_ops.enable_curr_chn) {
				/* Enable the selected channel */
				if (data_capture_ops.enable_curr_chn(chn) != SUCCESS) {
					return;
				}
			}

			num_of_active_channels++;
		} else {
			if (data_capture_ops.disable_curr_chn) {
				/* Disable the selected channel */
				if (data_capture_ops.disable_curr_chn(chn) != SUCCESS) {
					return;
				}
			}
		}

		mask <<= 1;
	}

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


/*!
 * @brief	Function to stop ADC data capture
 * @return	none
 */
void stop_data_capture(void)
{
	start_adc_data_capture = false;

	if (data_capture_ops.disable_conversion) {
		/* Stop conversion */
		data_capture_ops.disable_conversion();
	}

	if (data_capture_ops.restore_prev_active_chns) {
		/* Enabled all previously active channels (active during conversion start) */
		data_capture_ops.restore_prev_active_chns();
	}

	reset_data_capture();
}