Gas station without pumps

2012 July 12

Capacitive sensing with op amps, continued

Filed under: Circuits course — gasstationwithoutpumps @ 22:23
Tags: , , , , , , ,

What more can I do (or, more importantly, have the students do with the Capacitive sensing with op amps?

The circuit that I gave in that post has several parameters.  Perhaps the students should try predicting what happens when they are adjusted.

Op amp multivibrator. The first op amp is just to set up a virtual ground halfway between the power rails. The second op amp does the oscillating (at about 8.12 kHz for these components). Circuit drawn with CircuitLab, which is not capable of simulating it.

For example, what happens if Vbias is raised or lowered? What is the effect of changing each of the resistors? How can we optimize the circuit for easy, reliable detection of touches with an Arduino pulseIn() measurement?

If we pretend that the output is a rail-to-rail square wave (ignoring the slew rate limitation), analysis is pretty easy.

V_{plus} = \frac{R_4 V_{bias} + R_5 V_{out}}{R_4+R_5}.

  • If the output is low, then V_{plus}= \frac{R_4}{R_4+R_5} V_{bias} and Vminus is higher than that, but dropping.
  • If the output is high, then V_{plus} = \frac{R_4 V_{bias} + R_5 V_{dd}}{R_4+R_5} and Vminus is lower than that, but rising.

If we define r=R_5/R_4, we can simplify further and say that Vminus swings between two threshold voltages: V_{low}= \frac{V_{bias}}{1+r}  and V_{high}= \frac{V_{bias} + r V_{dd}}{1+r}.

The discharging curve is simply V_{high} e^{-t/\tau}, where \tau= R_3 C_1, so it takes \tau \ln \left(\frac{V_{high}}{V_{low}}\right) = \tau \ln \left( \frac{V_{bias} + r V_{dd}}{V_{bias}}\right) to discharge to the other threshold.

The charging curve is V_{dd} - (V_{dd}-V_{low})e^{-t/\tau}, and so it takes \tau \ln \left(\frac{V_{dd}-V_{low}}{V_{dd}-V_{high}}\right). That is, \tau \ln \left( \frac{V_{dd}-V_{bias}+r V_{dd}}{V_{dd}-V_{bias}}\right).

If we want a symmetric waveform, we need to have V_{low}+V_{high}=V_{dd}, which in turn requires V_{bias}=V_{dd}/2. Raising Vbias makes the low part of the output waveform shorter and the high part longer. Conversely, lowering Vbias makes the low part longer and the high part shorter.

The total period is  T = \tau \ln \left( \frac{(V_{dd}-V_{bias}+r V_{dd})(V_{bias} +r V_{dd})}{(V_{dd}-V_{bias})V_{bias}}\right).  If we set Vbias to Vdd/2, then we can simplify further to 2 \tau \ln \frac{0.5+r}{0.5}.

The sum R4+R5 only affects the amount of current that the Vbias supply has to provide, but it is probably a good idea to make it fairly large, and to add a bypass capacitor to Vbias, to prevent noise coupling though the bias supply.

When using pulseIn() on the Arduino to measure the capacitance C1, we want to have a large change in the pulse duration (to avoid quantization effects from reporting the duration in μsec), which means a large value for R3. That is limited by the problem of picking up 60Hz noise if the input impedance is too high.

We can also tweak r=R5/R4 a little, to make the period a larger multiple of τ, limited by the problem of noise if we get the thresholds too close to the rails.

Since pulseIn() only measures one part of the waveform (the low part or the high part), we could also tweak the bias voltage to lengthen just that part of the waveform, for example, dropping Vbias to Vdd/4 to length the low part.  We again have to be careful not to get the threshold too close to the rails.  If we are lowering Vbias, then the threshold to watch is V_{low}=\frac{V_{bias}}{1+r}. If we fix V_{low} as low as we’re willing to make it, then should we make Vbias low or r high?  The number we are trying to maximize is the discharge time \tau \ln \left( \frac{V_{bias} + r V_{dd}}{V_{bias}}\right) = \tau \ln ( 1+ r V_{dd} V^{-1}_{bias}), with the constraint that V_{bias} \geq (1+r) V_{low}. At equality, we have to maximize r/(1+r), which is achieved by making r as large as possible.  Of course, that would mean raising Vbias, which would run us into trouble with noise at the other threshold.  In general, it seems like the symmetric waveform with Vbias=Vdd/2 gives us the best noise margins.

Next step: how do we either slow down the oscillator or clean up the waveform enough to get a good signal to feed into the Arduino?

I tried increasing both R1 and R5, while decreasing R4, as shown below.

Modified circuit for longer period. C1 is just the stray capacitance of the touch sensor, with no deliberately added capacitance.

This circuit oscillates with a frequency of 35.66 kHz (period of about 28 µsec )when the touch sensor is not touched, which is slow enough that the signal runs from rail to rail. Touching the sensor drops the frequency to 20kHz or less (period of 50 µsec or more). Since r=5.556, the expected period is 4.988 τ, or 0.4988 µsec/pF C1.  The period is consistent with a stray capacitance of 56 pF, increasing to over 100 pF when the sensor is touched.  There is a lot of jitter in the lower-frequency signal, probably due to some coupling of 60 Hz noise into the system. A 1nF capacitor for C1 gives a frequency of 1660Hz (602 µsec, which is more than the expected 500 µsec), 47nF gives 34.65 Hz (28.86 msec, not the expected 23.44 msec).  Why am I consistently 25% off?

The change of 20 µsec or more in the period (10 µsec or more in the half period) should be easily detectable with one pulseIn() measurement on the Arduino.  The following code seems to work fairly well, with a light touch causing the LED to flash (right on the edge of detection) and a firmer touch giving a steady reading.

// Capacitive sensor switch
// Thu Jul 12 21:36:42 PDT 2012 Kevin Karplus

// To use, connect the output of the op-amp oscillator to
// pin CAP_PIN on the Arduino.
// The code turns on the LED on pin 13 when a touch is sensed.

// The Arduino measures the width of one LOW pulse on pin CAP_PIN.
// The LED is turned on if the pulse width is more than min_pulse_usec

// pin that oscillator output connected to
#define CAP_PIN (2)

// time (in milliseconds) to wait after a change in state before
// testing input again
#define DEBOUNCE_MSEC (100)

static uint8_t was_on;	// stored state of LED, for detecting transition

static uint16_t min_pulse_usec=5;
// min pulse width (in  microseconds) for detecting a touch

void setup(void)
    pinMode(13, OUTPUT);
    // assuming that the touch sensor is not touched when resetting, 
    // find the maximum typical value for untouched sensor
    for (uint8_t i=0; i<10; i++)
    {   long pulse=pulseIn(CAP_PIN,LOW);
        if (pulse>min_pulse_usec)
	{    min_pulse_usec =pulse;
    min_pulse_usec += 2;	// add some room for noise

void loop(void)
    uint8_t on = pulseIn(CAP_PIN,LOW) >= min_pulse_usec;
    digitalWrite(13, on);
    if (on!=was_on)
    {   delay(DEBOUNCE_MSEC);


  1. […] Capacitive sensing with op amps, continued […]

    Pingback by Order and topics for labs « Gas station without pumps — 2012 August 16 @ 23:39 | Reply

  2. […] sensing with op amps and Capacitive sensing with op amps, continued used a rather complicated circuit to make a Schmitt-trigger oscillator out of op […]

    Pingback by Capacitive sensing with Schmitt trigger « Gas station without pumps — 2012 November 1 @ 21:03 | Reply

RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: