In my ongoing series about cool sensors Interactive Matter presents the AD7746 capacity sensor:
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:
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:
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.
But if you lay a cover of paper over the sensor you get still an extremely sharp signal:
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.






{ 36 comments… read them below or add one }
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.
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
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…
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.
I thought so. *sigh*
Thanks for your answer, i’ll keep searching!
Cheers!
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.
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
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.
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
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?
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!
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.
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.
There is no simple answer to that.
You have to extend the Processing sketch to show more than one graph.
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?
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
You are right 0x0B is CAP DAC A
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.
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?!?!?
If I remember correctly I derrived it from the datasheet:
But it is always possible that I miscalcuated it. Will check it as soon as I got time to do so.
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!
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.
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 ?
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.
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
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(!Wire.available()) {
while (i
// waiting
}
Any thoughts?
Marcus, I’ve solved the problem, thank you though, so please delete the post. User error as usual.
I can’t get anything from the board despite the resistors. I hope it’s not broken.
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 ;)
I’ll check that. Thanks !
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.
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
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
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.
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?
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.
{ 1 trackback }