AD7708 ADC Timming Issues Help - Page 1
Forum section: electronics / general

author: diyaudio
author_rank: Frequent Contributor
timestamp: June 09, 2014, 08:21:40 pm
content: Hi allI've been buried with datasheet details with a ADC I'm sampling the AD7708, so far everything seems to be working from a configuration point of view, however I hit a point where I don’t have a clue how to proceed as the datasheet makes no reference to timing diagrams or application notes.The problem / confusion is two parts…How do I reconstruct the 16-bit ADC value from the read-only register inside the ADC? I tried the following.1) I used the SPI data in line to issue ISR when the SPI buffer is full. (SPI enhanced mode) , I get nothing the code locks up when I read the SPI1BUF register.2) Then I tried a more formal approach and just read the port and built the data as it gets shifted in, however this works so so…A successful ADC conversion is signalled via the READY pin which I can confirm is low (indicating success), but the ADC 16-bit conversion data packet only occurs approximately 350us later (while the READY pin is still low)How do I go about correctly synchronising the conversion data with the READY PIN at the moment it seems incorrect as the conversion codes are random 65535 (Full scale) with 2.5v applied then it reads 65535 with 0v at the ADC inputCode:adc_spi_start(0); // Setup ADC SPI.adc_write_byte(ADC_REG_FILTER); // (ADC Filter Register)adc_write_byte(0x45); // CHOP=Enabled, fADC=19.79(Hz, tADC=50.34(ms)adc_write_byte(ADC_REG_CTRL); // (ADC Control Register)adc_write_byte(0xf); // CH-1, enable unipolar coding mode, Input range ± 2.56 V// ADC Calibration START.adc_write_byte(ADC_REG_MODE); // (ADC Mode Register)adc_write_byte(0x14); // Internal zero calibration, 10 pseudo-differential input, reference selected is REFIN1(+) and REFIN1(?) for the active channel.// analog negative input (AINCOM) is unbuffered allowing it to be tied to AGND in single-ended input configuration, and chopping is enabled.while(ADC_RDY);adc_write_byte(ADC_REG_MODE); // (ADC Mode Register)adc_write_byte(0x15); // Internal Full-Scale Calibration, 10 pseudo-differential input, reference selected is REFIN1(+) and REFIN1(?) for the active channel.// analog negative input (AINCOM) is unbuffered allowing it to be tied to AGND in single-ended input configuration, and chopping is enabled.while(ADC_RDY);// ADC Calibration END.adc_write_byte(ADC_REG_MODE); // (ADC Mode Register)adc_write_byte(0x03); // Continuous conversion modewhile(ADC_RDY);adc_write_byte(ADC_REG_CTRL); // (ADC Control Register)adc_write_byte(0xf); // select channel 1 and its operating range.while(ADC_RDY);close_spi(); // Close Setup ADC SPI.while(true){#if TEST_ADC_READadc_spi_start(0); // Setup ADC SPI.adc_write_byte(ADC_REG_DATA);// adc_write_byte(ADC_REG_STATUS);// while(ADC_RDY);close_spi(); // Close Setup ADC SPI.#endif}void __attribute__((__interrupt__,__auto_psv__)) _CNInterrupt(void){/*When a CN interrupt occurs, the user should read the PORT register associated with the CNpin(s). This will clear the mismatch condition and set up the CN logic to detect the next pinchange. The current PORT value can be compared to the PORT read value obtained at the lastCN interrupt to determine the pin that changed.The CN pins have a minimum input pulse-width specification. Refer to the ?ElectricalCharacteristics? section of the specific device data sheet for further details.*/if( ADC_RDY == LOW ){__delay_us(350);g_adc_value = adc_read_word();}process_rotray_encoder();IFS1bits.CNIF = 0; // Clear flag for on change interrupt}long adc_read_word(){char i;long adc_data = 0;for( i = 1; i <= 16; ++i ) // Get the ADC data as a 16-bit value.{adc_data = adc_data << 1; // Shift one bit left prepare place for next bit.if(ADC_DATA == HIGH) // ADC bit high ?{adc_data |= 0x01; // Append 1-bit at the start of word.}}return adc_data;}

author: dannyf
author_rank: Super Contributor
timestamp: June 10, 2014, 10:52:50 am
content: QuoteThat's not a question about the timing.the datasheet suggests a write to the communications register, with A3..0 loaded with the address of the dataregister (0b0100), and the next cycles will read the data register which contains the adc results.QuoteNo point in trying: just read the datasheet.QuoteYou have to make sure that your spi portion of the code is already working - use a scope on the data lines + click line. Or a logic analyzer.

author: diyaudio
author_rank: Frequent Contributor
timestamp: June 10, 2014, 11:04:12 am
content: Quote from: dannyf on June 10, 2014, 10:52:50 amHey dannyf.I have read the datasheet, I'm getting closer to a solution but I still need help. Here is the part I miss understood.I thought after querying the data register, the READY pin is low, this was not the case...Quote from the datasheet" the RDY bit of the Status Register is monitored to determine when the Data Register is updated."There are two ways the ISR or polling method to query data im using the polling method.Code:while(ADC_RDY == LOW){adc_spi_start(0); // Setup ADC SPI.adc_write_byte(ADC_REG_DATA); // Query the ready-only data register.g_adc_value = adc_read_word(); // Now get the conversion data.// close_spi(); // Close Setup ADC SPI.}See the timing attachment the problem is clear I think, the adc conversion return data is 16-bit, the SPI queries the registers in 8-bit mode, how do I go about querying the ADC data register (which is in 8-bit mode) then wait for the data which is now in 16-bit mode..see attachment ADC_1-VOLT-SPI_CLK_VS_ADC_DATAAs I slowly change the ten turn pot I can clearly see the data packets change both traces are changing, but why are they so far spaced ?I expected one continuous 16-bit adc conversion data packets, not separate bursts.

author: dannyf
author_rank: Super Contributor
timestamp: June 10, 2014, 11:10:38 am
content: The datasheet talks about the _RDY bit being cleared after a period of time as well.QuoteTwo ways:1) wait for the _RDY pin to go low, which seems to be what you are doing; This is the fastest approach2) wait for the RDY bit to go high. This approach saves a pin but requires that you read the status register constantly and test the RDY bit.Approach 1 can be done via polling or interrupt; Approach 2 can be done via polling only.

author: diyaudio
author_rank: Frequent Contributor
timestamp: June 10, 2014, 11:25:06 am
content: Quote from: dannyf on June 10, 2014, 11:10:38 amYes, that's what I'm doing.I also noticed that some of the shy amounts of sample code actually manually clocks the SPI, I assume its probably got to do with this issue.Code:/*unsigned*/ u16 read16(void){/*unsigned char*/ u8 stevec;/*unsigned int*/ u16 result=0;DIN_AD_1;chip_select_adc(0); //CS_AD0_1;SCL_AD_1;SCL_AD_1;chip_select_adc(1); //CS_AD0_0;stevec = 16;do {SCL_AD_0;SCL_AD_0;result = result << 1;if(DOUT_AD) result |= 0x01;SCL_AD_1;SCL_AD_1;} while(--stevec);chip_select_adc(0); //CS_AD0_1;return result;}

author: dannyf
author_rank: Super Contributor
timestamp: June 10, 2014, 12:28:46 pm
content: QuoteThere are two options so I am not sure which is "that" you are referring to.QuoteNot sure what "this issue" is, but there are many reasons to use software spi.As your modules are built on the spi layer, you have to eliminate any doubt that it is not working before you can go any further.One possibility, since you didn't show how those write routines work, is your management of the _CS line: a spi routine that manages the CS line on a per byte basis has no use.

author: diyaudio
author_rank: Frequent Contributor
timestamp: June 10, 2014, 12:58:42 pm
content: Quote from: dannyf on June 10, 2014, 12:28:46 pmSorry, for the nebulous response I'm responding and prototyping simultaneously.Yes, I meant I'm polling the ready status as you noted.As for the software SPI clock okay so here is the result, I think I'm almost there. see attachment.The attachment shows a SPI software clock manually clocked at 16-bits and the response shows clear the result. alsonoted the SPI data timing order1) First the query to the data register 8 bits.2) Shutdown the hardware SPI and clock the SPI clock using software 16-bits long see the data response.Code:// ADC conversion ready ?while(ADC_RDY == LOW){adc_spi_start(0); // Setup ADC SPI.adc_write_byte(ADC_REG_DATA); // Query the ready-only data register.close_spi(); // Close Setup ADC SPI.ADC_CS = LOW;char i = 0;for( i = 1; i <= 16; ++i ) // Get the ADC data as a 16-bit value.{PORTBbits.RB7 = LOW;__delay_us(1);PORTBbits.RB7 = HIGH;__delay_us(1);}ADC_CS = HIGH;}

author: dannyf
author_rank: Super Contributor
timestamp: June 10, 2014, 01:23:46 pm
content: That seems to be very convoluted.Here is what I would do:Code:#define AD7708_READY() (ADC_RDY == LOW) //true if adc_ready pin is low//read ad7708/7718, current channel, right aligned//READY pin assumed to be low at this point//CS pin managed inside this routine - may need to change if multiple adc7708 is readunsigned long adc7708_read(void) {unsigned long tmp=0; //hold the intermediarate data//READY pin is already low//put down CS pin - idles highSPI_SELECT(ADC7708_CS);spi_write_byte(ADC7708_REG_DATA); //set to read the datatmp = spi_write_byte(0x00); //read msbtmp <<= 8; tmp |= spi_write_byte(0x00); //read 2nd byte//tmp <<= 8; tmp |= spi_write_byte(0x00); //read 3rd byte for adc7718//pull CS pin highSPI_DESELECT(ADC7708_CS);return tmp;}By linking in the hardware or software spi_write_byte(), you would have switched easily between hardware or software spi modules. The high level routines should not care whether the spi transmission is hardware or software.

author: diyaudio
author_rank: Frequent Contributor
timestamp: June 10, 2014, 01:45:16 pm
content: Okay the problem has been addressed as I suspected it was a timing issue.Firstly, when clocking the ADC while writing to the various calibration, channel selection, filter registers works as expected. this is all done in 8-bit mode.Secondly, when performing a write and read things are a bit more tricky, all I did was query the ADC data register via the adc config register(in read mode, 8-bit register) using the SPI hardware, then turn off the SPI peripheral, when querying the data register I software clock 16-bits and build the data as it shifts in from the adc data pin. (I find this method very easy to read from a code point of view)See the image for details.Unipolar ModeIn unipolar mode of operation the output coding is straight binary. With an analog input voltage of 0 V the output code is 0000Hexfor the AD7708 and 000000Hex for the AD7718. With an analog input voltage of 1.024 V REF /Gain the output code is FFFFHexfor the AD7708 and FFFFFF Hex for the AD7718. The output code for any analog input voltage can be represented as follows:Code = (AIN × GAIN × 2N)/(1.024 × V REF )whereAIN is the analog input voltage andN = 16 for the AD7708 and N = 24 for the AD7718.AIN = 2.5VGAIN = 12N = 2 ^ 16VREF = 2.5ADC CODE @ full scale = 6400Note there is some noise as im using a breadboard so noisy codes will be expected, noise immunity was not the goal here.

author: diyaudio
author_rank: Frequent Contributor
timestamp: June 10, 2014, 02:04:12 pm
content: Quote from: dannyf on June 10, 2014, 01:23:46 pmThis is what I did.Code:long adc_read_word(){long adc_data = 0;char i = 0;ADC_CS = LOW;for( i = 1; i <= 16; ++i ) // Get the ADC data as a 16-bit value.{PORTBbits.RB7 = LOW;__delay_us(1);adc_data = adc_data << 1; // Shift one bit left prepare place for next bit.if(ADC_DATA == HIGH) // ADC bit high ?adc_data |= 0x01; // Append 1-bit at the start of word.PORTBbits.RB7 = HIGH;__delay_us(1);}ADC_CS = HIGH;return adc_data;}

author: dannyf
author_rank: Super Contributor
timestamp: June 10, 2014, 02:26:49 pm
content: QuoteThat's generally not a good way to manage the code - it makes it impossible to use the code on other chips (7718 for example).

author: diyaudio
author_rank: Frequent Contributor
timestamp: June 10, 2014, 02:30:07 pm
content: Quote from: dannyf on June 10, 2014, 02:26:49 pmtrue, I can now after verifying the prototype, start moving things towards portability.

author: dannyf
author_rank: Super Contributor
timestamp: June 10, 2014, 05:05:55 pm
content: Code:PORTBbits.RB7 = LOW;__delay_us(1);adc_data = adc_data << 1; // Shift one bit left prepare place for next bit.if(ADC_DATA == HIGH) // ADC bit high ?adc_data |= 0x01; // Append 1-bit at the start of word.PORTBbits.RB7 = HIGH;__delay_us(1);1) The chip can take much faster than clock than those delays would suggest. So I would take them out;2) You want to produce as close to 50% duty cycle clock pulses as you can. So you should play with the placement of RB7 setting / clearing to achieve that.I usually do something liks this:Code:set_clk();if (ADC_DATA==HIGH) adc_data |= 0x01;else adc_dat|= 0x00;clr_clk();adc_data = adc_data << 1;Putting the shift of adc_data after clearing the clock helps produce equal on / off time on the clock.Having to OR '0' produces equal length execution for the if statement.