Example Program for EVAL-AD7606
Dependencies: platform_drivers
Diff: app/ad7606_data_capture.c
- Revision:
- 1:819ac9aa5667
- Child:
- 3:83b3133f544a
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/ad7606_data_capture.c Mon Oct 05 13:45:15 2020 +0530 @@ -0,0 +1,429 @@ +/***************************************************************************//** + * @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) + +/******************************************************************************/ +/********************** 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 = 5000000; // To avoid IIO data request timeout + 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; + } +}