The AD7705 dual channel analog digital converter with AVRs

07 Oct 2021 - tsp
Last update 07 Oct 2021
Reading time 17 mins

Introduction

The AD7705 is a programmable gain dual channel 16 bit sigma-delta analog digital converter that’s controlled via an 3 wire SPI bus (in addition to a chip select, single reset, input and data ready output that one can but doesn’t have to use). So in worst case one requires 6 pins connecting the microcontroller / host with the ADC. It’s well suited for low speed applications (up to around 200 Hz or 500 Hz depending on the master clock running at 1 MHz or 2.4576 MHz - or double the frequencies using the divide by two logic which is what’s done on most breakout boards).

Since I’m using this ADC again in a physics experiment (to be specific in an electron beam control system) I thought it would be a good idea to write a short summary on how to typically use this part.

Register access

The maximum SPI frequency reachable by the AD7705 is 5 MHz - which is just a little bit above the capabilities of an AVR running with 16 MHz and not using SPI2X (the AVR covers stable SPI operation from it’s master clock /2 down to /128 - so at 16 MHz the range supported would be 8 MHz down to 125 kHz, without SPI2X it covers 4 MHz and lower).

The ADC contains 8 different registers (i.e. 3 register selection bits).

Index RS 2:0 Size (Bits) Register Comments
0 000 8 Communication register Selects the register that is accessed as well as the channel (and controls standby mode)
1 001 8 Setup register Configured operational mode, gain, buffers, etc.
2 010 8 Clock register First register written after reset. Configures clock source and frequency
3 011 16 Data register Obviously used to transfer ADC data
4 100 8 Test register Should not be used except by the manufacturer
5 101 reserved No operation Reserved
6 110 24 Offset register Allows one to configure the offset of the ADC
7 111 24 Gain register Configures gain of the amplifier

All access is done via a serial interface consisting of a serial clock (SCK) as well as typical master out / slave in (MOSI, DIN) and master in / slave out (MISO, DOUT) lines. During access the ADC has to be selected using a chip select signal. One could of course tie ties permanently to ground to permanently select the chip - but it’s a good idea to not do so since de-asserting and reasserting the chip select also resynchronizes the serial link in case there had been some spurious signals on the clock line. It’s of course also possible to resynchronize only in case communication errors arise but the simpler approach is just to do for every transaction. Access is done through an internal shift register on the device as usual for SPI. If synchronization is lost at any time one can also simply write 32 clock cycles keeping MOSI high (i.e. write a sequence of ones for 4 bytes). This resets the part.

It’s possible to use hardware SPI on the AVR as well as using software bit banging to access the ADC. The latter one can be used in situation in which the hardware SPI implementation has already been used for something different.

The SPI port is capable of pretty fast access - it usually requires a high and low period of SCK for at least $100 ns$ and a maximum of $100 ns$ delay until the data shifted out is guaranteed to be stable after SCK felt to low:

Read timing:

Operation Timing
CS falling edge to SCK rising edge $120 ns$
SCK falling edge to data valid $ \leq 80 ns$ at 5V operation, $\leq 100 ns$ at 3.3V operation
high and low pulse width $ \geq 100 ns$
bus release after rising SCK $ \leq 100 ns$

Write timing:

Operation Timing
CS falling edge to SCK rising edge $120 ns$
Data valid to SCK rising edge $ \geq 20 ns $
high and low pulse width $ \geq 100 ns$
bus release after rising SCK $ \leq 100 ns$

Communication register (0)

All access starts with an write access to the communication register.

Bit Mnemonic Description
7 DRDY Writes always have to be 0, on read it provides the status of the data ready pin
6:4 RS2:0 Register selection (values see above)
3 R/W Read / write access selection (0: write, 1: read)
2 STBY Standby. Writing 1 puts the part into standby mode (~ 10 uA consumption), 0: normal operation mode
1:0 CH1:0 Channel selection bits

The channel selection bits select a channel but also a given operating mode.

CH1:0 AIN(+) AIN(-) Calibration register pair
00 AIN1(+) AIN1(-) Calibration register pair 0
01 AIN2(+) AIN2(-) Calibration register pair 1
10 AIN1(-) AIN1(-) Calibration register pair 0
11 AIN2(-) AIN2(-) Calibration register pair 1

In case CH1 is set the AIN1(-) or AIN2(-) pin is internally shorted to itself. This can be used to evaluate noise performance without external parts being connected - which is rather interesting in case one wants to determine noise floor of the ADC before a measurement.

Setup register (1)

Reset value: 0x01

Bit Mnemonic Description
7:6 MD1:0 Operational mode selection (see below)
5:3 GD2:0 Gain selection bits for the on-chip PGA (see below)
2 B/U Bipolar (0) or unipolar (1) operation
1 BUF Buffer control. If this is set to 1 the internal buffer is enabled (increases input impedance)
0 FSYNC Filter synchronization. If 1 components of the digital filter are held in reset, when set to 0 the analog filter runs

The AD7705 supports 4 normal operating modes that are selected by MD1:0:

Mode MD1:0 Description
0 00 Normal mode. The device performs normal conversions.
1 01 Self calibration mode. Calibrates the channel that is selected via CH bits in the communication register. For zero scale the inputs are shorted, for full scale an internal reference is used. After calibration has finished MD bits return to 0 again (and DRDY gets asserted)
2 10 Zero scale calibration. Performs calibration on the externally applied voltage (!). After calibration is finished MD bits return to 0
3 11 Full scale calibration. Performs calibration on the externally applied voltage (!). After calibration is finished MD bits return to 0

The gain selection is specified as $2^{setting}$:

GD2:0 Gain
000 1
001 2
010 4
011 8
100 16
101 32
110 64
111 128

Clock register (2)

Reset value: 0x05

Bit Mnemonic Description
7:5 Reserved Write 0
4 CLKDIS Master clock disable. 1 disables the master clock output (MCLK OUT). In case a ceramic oscillator is used 1 disabled all conversions.
3 CLKDIV Clock division. If this is set to 1 the input clock signal at MCLK IN is divided by two before being used internally
2 CLK Clock selection (Master clock $2.4576 MHz$: set to 1, $1 MHz$ set to 0)
1:0 FS1:0 Filter selection. Selects the output update rate and the -3dB filter cutoff frequency
CLK FS1:0 Output update rate -3 dB filter cutoff
0 00 20 Hz 5.24 Hz
0 01 25 Hz 6.55 Hz
0 10 100 Hz 26.2 Hz
0 11 200 Hz 52.4 Hz
1 00 50 Hz 13.1 Hz
1 01 60 Hz 15.7 Hz
1 10 250 Hz 65.5 Hz
1 11 500 Hz 131 Hz

Data register (3)

This 16 bit register contains the most recently sampled data value of the ADC. In case a write is executed the written data is silently dropped. One should only try to access the data register while DRDY is active (low).

Test register (4)

This register is used internally by the manufacturer to test the device. The features it exposes are undocumented.

Zero scale calibration register (5)

The 24 bit zero scale calibration is used in conjunction with the full scale calibration register. Note that the device is not able to access the register content while the microprocessor does access it - the data values sampled during this period will not be correctly scaled. Reading uncalibrated data can be avoided by setting FSYNC before accessing these registers.

Full scale calibration register (6)

The 24 bit full scale calibration is used in conjunction with the zero scale calibration register. Note that the device is not able to access the register content while the microprocessor does access it - the data values sampled during this period will not be correctly scaled. Reading uncalibrated data can be avoided by setting FSYNC before accessing these registers.

AVR implementation: Using the hardware SPI interface

TL;DR:

Using the hardware interface is of course always the desired way of using SPI. In this case the hardware takes over control of the clock source and shifts data in and out of the data register. Anytime a transfer has finished the SPI_STC_vect interrupt is triggered (if interrupt based processing has been enabled) and one can either transfer the next data element or turn off SPI. One might also perform polling on the SPIF bit in the SPI status register to check when operations have finished which is what I usually do during initialization if this always stays the same during the whole device operation.

This way one can implement a simple state machine for processing of ADC values after one used a synchronous approach to initialize the ADC.

As one can see from the timing diagram in the AD7705 datasheet the leading edge is falling, the trailing edge rising - thus one has to configure clock polarity CPOL = 1 in the SPCR. Since the data is also setup on leading and samples on trailing edge the clock phase CPHA = 1.

Synchronous implementation

Synchronous read and write on the AVR

The synchronous read and write routines are interesting for initialization during boot up or when one wants to do a synchronous implementation out of other reasons.

First one has to configure the SPI bus of the AVR. In the following code snipped I’m configuring:

I assume that chip select is attached to PB0 - the corresponding code is moved into static line routines for better readability.

/*
	Asserting and deasserting chip select including the minimum delay
*/
static inline void ad7705CSAssert() {
	PORTB = PORTB & 0xFE;
	/* Currently hardcoding two NOP's for 16 MHz AVR */
	__builtin_avr_nop();
	__builtin_avr_nop();
}
static inline void ad7705CSDeassert() {
	/* Currently hardcoding two NOP's for 16 MHz AVR */
	__builtin_avr_nop();
	__builtin_avr_nop();

	PORTB = PORTB | 0x01;
}

/*
	Initialize SPI for AD7705
*/
void ad7705InitSPI() {
	uint8_t sregOld = SREG;
	cli();

	DDRB = 0x17;
	PORTB = 0x13; /* Deassert nSS, assert nRESET, keep clock high */

	SPSR = 0x00; /* Clear SPI2X */
	uint8_t dummy = SPDR; /* Dummy read */

	SPCR = 0x5C;

	/* Re-enable interrupts if they've been enabled before */
	SREG = sregOld;
}

/*
	Transfer functions to read or write 8 and 16 bit
  values; one might also need 24 bit transfer in case
	one wants to read or write calibration registers
*/
static uint8_t ad7705SPITransfer8Sync(uint8_t bDataOut) {
	SPDR = bDataOut;
	while(((spStatus = SPSR) & 0x80) == 0) {
		__builtin_avr_nop();
	}
	return SPDR;
}

static uint16_t ad7705SPITransfer16Sync(uint16_t bDataOut) {
	uint8_t high;
	uint8_t low;

	high = (uint8_t)((bDataOut >> 8) & 0xFF);
	low  = (uint8_t)((bDataOut     ) & 0xFF);

	high = ad7705SPITransfer8Sync(high);
	low  = ad7705SPITransfer8Sync(low);

	return  ((((uint16_t)high) << 8) & 0xFF00) |  (((uint16_t)low) & 0x00FF);
}

static uint32_t ad7705SPITransfer24Sync(uint32_t bDataOut) {
	uint8_t b1;
	uint8_t b2;
	uint8_t b3;

	b1 = (uint8_t)((bDataOut >> 16) & 0xFF);
	b2 = (uint8_t)((bDataOut >>  8) & 0xFF);
	b3 = (uint8_t)((bDataOut      ) & 0xFF);

	b1 = ad7705SPITransfer8Sync(b1);
	b2 = ad7705SPITransfer8Sync(b2);
	b3 = ad7705SPITransfer8Sync(b3);

	return ((((uint32_t)b1) << 16) & 0x00FF0000)
	       | ((((uint32_t)b2) <<  8) & 0x00FF0000)
	       | ((((uint32_t)b3)      ) & 0x00FF0000);
}

A missing part is of course asserting and deasserting the chip select (SS) line of the chip

Using these simple I/O sequences one can simply initialize the ADC to the desired operation,

Synchronous initialization

During initialization the device will:

#define AD7705_UPDATE_HZ20           0x00
#define AD7705_UPDATE_HZ25           0x01
#define AD7705_UPDATE_HZ100          0x02
#define AD7705_UPDATE_HZ200          0x03

#define AD7705_UPDATE_HZ50           0x04
#define AD7705_UPDATE_HZ60           0x05
#define AD7705_UPDATE_HZ250          0x06
#define AD7705_UPDATE_HZ500          0x07

#define AD7705_MASTERCLK_MHZ1	       0x00
#define AD7705_MASTERCLK_MHZ2	       0x80
#define AD7705_MASTERCLK_MHZ24576    0x40
#define AD7705_MASTERCLK_MHZ49152    0xC0

#define AD7705_CH1                   0x00
#define AD7705_CH2                   0x01

/*
	Note: clock setup requires CS to be asserted ...

     * Writes into communication register to prime write into clock register
		 * Writes into clock register
*/
void ad7705ClockSetupSync(
	uint8_t adMasterClock,
	uint8_t adUpdateFrq,
	uint8_t channel
) {
	ad7705CSAssert();
	ad7705SPITransfer8Sync(0x20 | channel);
	ad7705SPITransfer8Sync(adMasterClock | adUpdateFrq);
	ad7705CSDeassert();
}

#define AD7705_SETUP_BUFFER_ENABLE   0x02
#define AD7705_SETUP_BUFFER_DISABLE  0x00

#define AD7705_SETUP_UNIPOLAR        0x04
#define AD7705_SETUP_BIPOLAR         0x00

#define AD7705_SYNCHRONIZE           0x01

void ad7705SetupAndCalibrateSync(
	uint8_t gain,
	uint8_t flags,
	uint8_t channel
) {
	ad7705CSAssert();
	ad7705SPITransfer8Sync(0x10 | channel);
	ad7705SPITransfer8Sync(flags | ((gain & 0x07) << 3) | 0x40);
	ad7705CSDeassert();
}
void ad7705SetupNormalSync(
	uint8_t gain,
	uint8_t flags,
	uint8_t channel
) {
	ad7705CSAssert();
	ad7705SPITransfer8Sync(0x10 | channel);
	ad7705SPITransfer8Sync(flags | ((gain & 0x07) << 3));
	ad7705CSDeassert();
}

Synchronous data reading

To read data one just has to wait till DRDY is asserted. Reading data without a valid DRDY signal is undefined behavior. I’m assuming in this sample that DRDY is attached to PB5. After one has made sure the device has asserted DRDY one writes into the communication register to prime for the read of the data register (note to select the same channel and not writing FSYNC or anything similar)

uint16_t ad7705GetADCSync() {
	uint16_t result;

	while((PORTB & 0x20) != 0) {
		__builtin_avr_nop();
	}

	ad7705CSAssert();

	ad7705SPITransfer8Sync(0x38); /* Or 0x39 if channel 2 should be selected */
	result = ad7705SPITransfer16Sync(0xFFFF);

	ad7705CSDeassert();

	return result;
}

Synchronous chip reset

There are multiple ways to reset the device:

void ad7705ResetSoftSync() {
	ad7705CSAssert();

	ad7705SPITransfer8Sync(0xFF);
	ad7705SPITransfer8Sync(0xFF);
	ad7705SPITransfer8Sync(0xFF);
	ad7705SPITransfer8Sync(0xFF);

	ad7705CSDeassert();
}

Synchronous sample program

The following example program will just initialize the first ADC channel, initialize the USART at a BAUD rate of 115200 and then dump measurements results whenever they get available as fast as possible. Everything will be done synchronous - including the serial line transmission (this of course limits the capabilities of this code). The code is available as a GitHub GIST

Asynchronous data reading

Usually the better way of handling the ADCs data transfer in many cases - when the microcontroller has to perform other tasks too - is interrupt / even driven. Since the ADC signals new available data via it’s DRDY data ready line it’s simple as registering an pin change interrupt. Another way is to synchronously check for the pin change interrupt and then perform the SPI transaction asynchronously.

To be completed: This article will be extended hopefully in near future.

This article is tagged: Programming, AVR, ANSI C, Tutorial, DAC, Electronics, Basics, DIY, Measurements, Microcontroller


Data protection policy

Dipl.-Ing. Thomas Spielauer, Wien (webcomplains389t48957@tspi.at)

This webpage is also available via TOR at http://rh6v563nt2dnxd5h2vhhqkudmyvjaevgiv77c62xflas52d5omtkxuid.onion/

Valid HTML 4.01 Strict Powered by FreeBSD IPv6 support