Arduino & AD7746

July 18, 2009

in Sensors, Tinkering

In my ongoing series about cool sensors Interactive Matter presents the AD7746 capacity sensor:

Arduino & AD7746

What is a capacity sensor good for? You can of course make a device to identify small capacitance (up to 4pF) but that’s boring. Much more interesting is how it reacts to touching.

Read on how to connect it to the Arduino and how to use it to sense touching

Fortunately there is a basic sketch how to use an AD7746 with Arduino. Unfortunately this sketch has one or two awkward structures and inconsistencies. Much more unfortunately it somehow works. So lets clean this thing a bit up.

I2C Basics

The I2C on Arduino uses analog pin 4 as SDA and analog pin 5 as SCL . With the Sparkfun AD7746 breakout board you need some pull up resistors for the I2C lines – So I added some 4.7k resistors to VCC (forgetting the pull up resistors is the most common source for an I2C connection not working – at least for me).

Connecting AD7746 to Arduino

The Sparkfun Ad7746 breakout board comes with two long lines to direct the sensing away from the logic and some ‘touch plate’. As you can see in the above photo you just connect the I2C pins, GND and VCC. Despite the marking ‘3.3V‘ on the breakout board the AD7746 accepts up to 5Volt as input voltage. So no special 3.3V voltage supply is needed.

The software part was a little more tricky. I used the AD7746 Arduino sketch I found and refactored it a bit. First of all I used my convenience extensions to read and write I2C registers conveniently (download the whole sketch – it is far too long to list here). Next we define our ADD7746 registers for convenience reasons:

#include <Wire.h>

//AD7746 definitions
#define I2C_ADDRESS  0x48 //0x90 shift one to the right

#define REGISTER_STATUS 0x00
#define REGISTER_CAP_DATA 0x01
#define REGISTER_VT_DATA 0x04
#define REGISTER_CAP_SETUP 0x07
#define REGISTER_VT_SETUP 0x08
#define REGISTER_EXC_SETUP 0x09
#define REGISTER_CONFIGURATION 0x0A
#define REGISTER_CAP_DAC_A 0x0B
#define REGISTER_CAP_OFFSET 0x0D
#define REGISTER_CAP_GAIN 0x0F
#define REGISTER_VOLTAGE_GAIN 0x11

#define RESET_ADDRESS 0xBF

#define CAP_ZERO 0x800000L

Now we can write a little startup routine to initialize the AD7746. It performs a reset (you never know), enables uses channel 1 for measuring capacitance and tries to calibrate the AD7746:

void setup()
{
  […]
  Serial.println("Initializing");

  Wire.beginTransmission(I2C_ADDRESS); // start i2c cycle
  Wire.send(RESET_ADDRESS); // reset the device
  Wire.endTransmission(); // ends i2c cycle

  //wait a tad for reboot
  delay(1);

  writeRegister(REGISTER_EXC_SETUP, _BV(3) | _BV(1) | _BV(0)); // EXC source A

  writeRegister(REGISTER_CAP_SETUP,_BV(7)); // cap setup reg - cap enabled

  Serial.println("Getting offset");
  offset = ((unsigned long)readInteger(REGISTER_CAP_OFFSET)) << 8;
  Serial.print("Factory offset: ");
  Serial.println(offset);

  writeRegister(0x0A, _BV(7) | _BV(6) | _BV(5) | _BV(4) | _BV(3) | _BV(2) | _BV(0));  // set configuration to calib. mode, slow sample

  //wait for calibration
  delay(10);

  displayStatus();
  Serial.print("Calibrated offset: ");
  offset = ((unsigned long)readInteger(REGISTER_CAP_OFFSET)) << 8;
  Serial.println(offset);

  […]
}

The calibration is a bit more tricky, later more on that. First we have to get an readout of the sensor:

long readValue() {
 long ret = 0;
 uint8_t data[3];

 char status = 0;
 //wait until a conversion is done
 while (!(status & (_BV(0) | _BV(2)))) {
 status= readRegister(REGISTER_STATUS);
 }

 unsigned long value =  readLong(REGISTER_CAP_DATA);

 value >>=8;
 //we have read one byte to much, now we have to get rid of it
 ret =  value;

 return ret;
}

It waits until an measurement has finished an reads the data value from the data register. The read routine returns a 32 bit long value. We just need the first three bytes, so we shift it one byte to the left. The datasheet specifies 0×800000 as null point, so we should subtract this value from readout. This was omitted here, since the datasheet also say that the value in REGISTER_CAP_OFFSET contains the real zero point after an calibration. So we just give back the value of REGISTER_CAP_OFFSET as offset. The content of REGISTER_CAP_OFFSET can be used in the external program logic (later more on that).

The AD7746 is not very nice to read from. The datasheet says, that you can write an address and then read as many bytes from the chip as you like. But not if you sent a STOP after you have written the address. But this is exactly what the Wire library does. So the readout routine (e.g. for Long values) looks a bit nasty since the address pointer is set back to 0:

unsigned long readLong(unsigned char r) {
  union {
    char data[4];
    unsigned long value;
  }
  byteMappedLong;

  byteMappedLong.value = 0L;

  Wire.beginTransmission(I2C_ADDRESS); // begin read cycle
  Wire.send(0); //pointer to first data register
  Wire.endTransmission(); // end cycle
  //the data pointer is reset anyway - so read from 0 on

  Wire.requestFrom(I2C_ADDRESS,r+4); // reads 2 bytes plus all bytes before the register

    while (!Wire.available()==r+4) {
      ; //wait
    }
  for (int i=r+3; i>=0; i--) {
    uint8_t c = Wire.receive();
    if (i<4) {
      byteMappedLong.data[i]= c;
    }
  }

  return byteMappedLong.value;

}

Now that we are getting some real values we can try to calibrate the chip. It comes factory calibrated, which is reset after each reset. First of all we try to perform an built in calibration:

void calibrate() {
  calibration = 0;

  Serial.println("Calibrating CapDAC A");

  long value = readValue();

  while (value>VALUE_UPPER_BOUND && calibration < 128) {
    calibration++;
    writeRegister(REGISTER_CAP_DAC_A, _BV(7) | calibration);
    value = readValue();
  }
  Serial.println("done");
}

After this i done we just switch over to continuous read mode, read out a value, print it with all calibration settings to the serial port:

  writeRegister(REGISTER_CAP_SETUP,_BV(7)); // cap setup reg - cap enabled

  writeRegister(REGISTER_EXC_SETUP, _BV(3)); // EXC source A

  writeRegister(REGISTER_CONFIGURATION, _BV(7) | _BV(6) | _BV(5) | _BV(4) | _BV(3) | _BV(0)); // continuous mode
void loop() // main program begins
{

  […]

  long value = readValue();
  Serial.print(offset);
  Serial.print("/");
  Serial.print((int)calibration);
  Serial.print("/");
  Serial.println(value);

  if ((valueVALUE_UPPER_BOUND)) {
    outOfRangeCount++;
  }
  if (outOfRangeCount>MAX_OUT_OF_RANGE_COUNT) {
    if (value < VALUE_LOWER_BOUND) {
      calibrate(-CALIBRATION_INCREASE);
    }
    else {
      calibrate(CALIBRATION_INCREASE);
    }
    outOfRangeCount=0;
  }

  delay(50);
}

The whole sketch contains a bit more code and can be much better understood if you got all the code – but that would be too much code for this post. So download the whole Arduino AD7746 Sketch and try it yourself.

Displaying the results

The results are displayed with an simple processing sketch, mimicking some kind of cheapo oscilloscope. It reads the values from the serial port and displays them. Anybody interested in the details of the sketch can download the whole AD7746 processing sketch.

The formula used to calculate the real capacitance is derrived by th datasheet:

capacitance = (value-calibration) * 2.44e-07-capdac*0.164;

I do not know if it completely perfect, but it gives some useful values, interestingly always smaller than 0 – despite the fact that the sensor should be calibrated to 0. For getting absolute capacitance readouts I suggest quadruple checking this to the datasheet and trying to optimize the adjustment of the sensor.

Leaving the sensor alone leads to some graphics like this:

AD7746 Screenshot 1

As you can see we there is some noise present, we could filter out, but for now we live with it. It is not very strong. Especially if you compare it with the next graph.

If you tap on the sensing wires it gets a very strong readout – just by touching the two fine lines on the board:

AD7746 Screenshot 4

This gives a much stronger readout (you can still see the noise – but that is very easy to distinct from the ‘signal’). This should be very easy to detect. If you look closer you will see that the readout does not really get back to the values just before touching the wires – It will go down slowly. So for detecting a signal it should be fairly easy to just compare the difference between two readouts and if it is bigger than some threshold it will interpreted as a ‘touch’.

If you use the touch pad, which comes with the AD7746 breakout board and touch the bare metal you get readout which is extremely strong – but that seems impractical because you would probably never you would never expose such big pads on your design, without some kind of coating.

AD7746 Screenshot 5

But if you lay a cover of paper over the sensor you get still an extremely sharp signal:

AD7746 Screenshot 6

The Bottom Line

In the end you see that the AD7746 is very interesting for creating some kind of hidden input elements. But it is some dificult part. The read routines are not really compatible with the wire library. Combing all the different possibilities to adjust the sensor and the complexity of calculationg the real value (the datasheet is a bit hard to understand on that topic) makes it very difficutl to tune the sensor optimally. But I hope that this material gives enough hints to use this sensor for a project. But nevertheless there are perhaps easier capacitive input sensors out there.

You can find the full source code over at github or directly download it from github.

{ 37 comments… read them below or add one }

yair November 16, 2009 at 10:22

thanks for this, found it via the comments @ sparkfun.
really enlightining stuff. im using the getto way (http://www.arduino.cc/playground/Main/CapSense) and it works for my purpose.

Reply

Yman December 8, 2009 at 15:28

Thanks for this! It’s just what I’ve been looking for to help me get these sensors working with the Arduino. Keep up the good work

Reply

jens August 19, 2010 at 17:58

thanks a lot for the tutorial and code!

any way to daisy-chain these boards? as far as i understood i2c, it is a bus-style protocol…

Reply

Marcus August 19, 2010 at 18:56

Unfortunately not.
Yes, I2C is a bus and there can be many I2C devices on a bus. But two devices must not share the same address. As far as I have seen it the AD7746 does not offer the possibility to change the I2C address of the device. Therefor you can only have one AD7746 on one bus. There are other devices with similar function, which have mor inputs. Check the Analog Website or Atmel – other vendors have capacitive Input devices as well.

Reply

jens August 19, 2010 at 21:16

I thought so. *sigh*
Thanks for your answer, i’ll keep searching!
Cheers!

Marcus August 24, 2010 at 12:56

I just had a look at the Freescale MPR121 – looks good. Perhaps that chip has enough channels for you ;)
The basic measurements and interpretation as propose din this post also applies to that chip.

Reply

Mark Harrison November 11, 2010 at 01:32

I think I missing a library or something. I’m using a Arduino 0021 IDE and an Uno. When I tried to compile code and I get “‘writeRegister’ was not declared in this scope” and it high-lights the first listing of writeRegister

Reply

Marcus November 11, 2010 at 08:48

Hi, you are not missing anything. The code examples in this post are just some bits of the code. If you download the complete sketch you will find the writeRegisterRoutine.

Reply

Michael November 29, 2010 at 06:12

Hello,

I’m doing a senior design project that is investigating the use of capacitive sensing.

The picture you have cuts off the rest of the sparkfun C-D converter. Are you using an excitation source? If so, how are you using it? As in, what it’s connected to. Because I noticed the AD7746 says use of the excitation source is optional.

Lastly, are you using the included capacitive sensor plate on the AD7746 chip or are you using your own sensor?

/BME major currently lost in a EE world

Reply

Marcus November 29, 2010 at 08:28

Hi,

it is the vanilla breakout board from Sparkfun. There is no external excitor source. As sensors I just used the pins on the board – no special sensor.
Does this help you?

Reply

Michael December 1, 2010 at 01:00

Yes, it does. Thanks.

Last question. I ordered 2 of these chips in order to get 2 sensors. I noticed in your code, you only initialized one of the sensors. If I wanted to take advantage of both of the channels, how should I modify your code to initialize both capacitance channels?

I’m sorry if these questions might seem basic, EE isn’t my specialty but I’m trying very hard to understand it.

Thanks!

Marcus December 1, 2010 at 11:17

Basically you have to implement my Arduino code for each channel. But this code does not very much more than outputting the values.
Based on my observations you have to implement a detection for your purpose. And this for each chip for each channel.

Reply

Michael December 2, 2010 at 03:26

Sorry to bother you even more, but you’ve been helpful so far.

I got the chip working, and it reads off an capacitance. Unfortunately, I can’t transfer the capacitance readings to the graph.

I know you put the Arduino sketch for that, but it doesn’t seem to be C-code, but in Java. How did you get the sketch for the code display to run?

Thanks again.

Reply

Marcus December 2, 2010 at 13:08

There is no simple answer to that.
You have to extend the Processing sketch to show more than one graph.

Ryan July 7, 2011 at 21:14

Can anyone provide me with a little more detail about implementing both channels provided on the breakout board? what addresses do i need to change for the second channel?

Joel April 1, 2011 at 02:30

In the downloaded code I think I found a typo in the defines:

//#define REGISTER_CAP_DAC_B 0x0B
#define REGISTER_CAP_DAC_B 0x0C

Reply

Marcus April 1, 2011 at 07:01

You are right 0x0B is CAP DAC A

Reply

Noah April 22, 2011 at 03:47

Hey Marcus, Thanks for putting this up it’s been an amazing reference! I have a quick question though, I realize the CAPDAC’s are used for adding or subtracting extra capacitance to shift the measurement capability. What I don’t understand is when you’re using CAP DAC A (the positive one), do you ADD or SUBTRACT out that corresponding capacitance (164 fF/bit). If you subtract out that corresponding capacitance then it seems kind of pointless to set anything above 4pF since setting it to 4pF gives you the full range. The reason I’m asking is because i’m worried my capacitance will be above 4pF and I’m hoping CAPDAC A is a way to be able to measure larger capacitances.

Reply

Noah April 22, 2011 at 04:02

Also I was wondering where you get the 2.44E-7 in your equation (which I assume is the Farads/bit). I’m kind of new to this but if I take 8pF and divide by the full range 0xFFFFFF, I get 0.59 aF/bit. Where am I going wrong?!?!?

Reply

Marcus April 22, 2011 at 13:16

If I remember correctly I derrived it from the datasheet:

The 0×000000 code represents negative full scale (–4.096 pF), the 0×800000 code represents zero scale (0 pF), and the 0xFFFFFF code represents positive full scale (+4.096 pF).

But it is always possible that I miscalcuated it. Will check it as soon as I got time to do so.

Noah April 25, 2011 at 19:48

Hey Marcus, I think your final equation is a little off. I think the CAPDAC’s job is to shift the 0 point at which the CDC is measuring by subtracting out some capacitance to get it within +-4pF. So you will need to add this capacitance back in and the final equation should look something like this:

Capacitance = (Data*4.88E-7 – 4) – (Offset*3.05E-5 – 1) + (CAPDAC*0.133)

I don’t think you can just subtract out the offset since each significant bit in the Offset register is weighed more than the ones in CapData registers.

Thanks for the tutorial! I couldn’t have gotten as far as I did without your help!

Reply

Marcus April 26, 2011 at 14:43

Was quite a time since I did this and I remeber that it was somewhat complicated. I have not checked it, but your explanation makes a lot of sense.
Thanks for your corrections. I will incorporate them as soon as I got some time.

Reply

Roald June 1, 2011 at 22:54

Hello, I am quite interested in capacitive sensing to build a 3D position sensing in the 0 – 50 cm range. Has anyone tried this chip or the Freescale MPR121QR2 in such a touchless fashion ?

Reply

Roald June 6, 2011 at 15:04

I am trying to use the Sparkfun DD7746 breakout card with the program from your blog. However I cannot get any data from the sensor. I read this on my terminal window :
Init: 0
Initializing
Getting offset
Factory offset: 0
… and nothing more.

If I use the program on which yours is based, I get always the same number : -2163.

Could you please give me a schematics of the circuit, it would help me to check the cabling. I made the connections according to the text and the photo from the blog’s post but a schematics would ensure there’s no mistake.

Also, as your program was written two years ago and I am using a recent Uno card, do you think something should be modified?

Thank you in advance.

Reply

Marcus June 6, 2011 at 15:07

Your situation sounds very much like you have forgotten the pull up resistors on SCL and SDA? Because from the values you are getting it is most likely that the communication is not working at all.

Hope it was the cause

Marcus

Reply

pjc July 1, 2011 at 00:05

Mi Marcus! Thanks for the hard work on this:)
I’ve the same issue with an Uno as Roald, but I’ve verified that my pullups are in place, and I see no pulses on sclk/sda on the scope. When executing the program, it prints out the same as Roald. I’ve isolated the problem. It never returns from

void displayStatus() {
[...]
readRegisters(0,18,data);

And inside readRegisters, although it gets “18″ bytes to read (how’s that possible if the clocks don’t seem to be running??), in particular it never returns from the wait here:
while (i while(!Wire.available()) {
// waiting
}

Any thoughts?

pjc July 1, 2011 at 00:34

Marcus, I’ve solved the problem, thank you though, so please delete the post. User error as usual.

Roald June 6, 2011 at 18:04

I can’t get anything from the board despite the resistors. I hope it’s not broken.

Reply

Marcus June 6, 2011 at 18:13

You can add debug code to the readRegister() methods to see if any value other than 0 comes over.
But it is hard to say if an I2C device is working from a distance ;)

Reply

Roald June 6, 2011 at 20:21

I’ll check that. Thanks !

Reply

Roald June 8, 2011 at 10:19

I have solved the problem. I wasn’t getting anything because the breakout board wasn’t soldered to a pin header, the header was simply inserted in the board’s holes. Now it’s soldered and it’s working. I just need a broader breadboard to connect both sides easily.

Reply

John May 11, 2012 at 03:31

Hi Marcus,

I’ve tried to implement the sketches that you have made available, however it seems that the arduino driver software and libraries have been updated since you made this guide. When I try to compile the code for Arduino V1.0, I get several syntax errors. Furthermore, after making the suggested modifications, I only get 3 lines in my serial terminal, and the arduino tx/rx lights don’t blink at all.

Could you please tell me what driver version you were using when you created the sketches, so that I could try to upload that driver version to my Arduino Uno and try out your sketches.

Thanks,
John

Reply

ANTONY July 27, 2012 at 06:33

I’ve the problem when I upload the code to my arduino.
Could you describe me step by step how to connect the arduino and the AD7746 ?
So my AD7746 can read the capasitance and transfer the capacitance readings to the graph.
thanks,
Antony

Reply

Pedro April 24, 2013 at 17:50

Hi,
Why did you use 0×48 instead of 0×90 for i2c address?

Reply

Marcus July 1, 2011 at 09:12

Could you describe what your problem is?
Perhaps other make the same mistake ;)
But if the wire.available is stuck it is a hint to a HW problem.

Reply

HIly October 23, 2012 at 18:19

Hi

I am not using the board but I have my own sensors connected to AD7746 and I want to read it’s output. I’m a beginner in coding so I am going to use your codes in my microC. BUt I couldn’t find any sketch about how I should connect AD7746 to MIcro (the pin connections) Can you help me with that?

Reply

Marcus October 23, 2012 at 18:36

Hi,

you can easily check this using the ATMEL Atmega32 (or whatever your microcontroller is) datasheet (your are looking for I2C and SCL and SDA) or by studying the Ardiuno schematics.

Reply

Leave a Comment

{ 1 trackback }