Sunday, January 8, 2017

Arduino capacitive proximity sensor with a square-wave signal generator

Introduction


In this post I explain a capacitive proximity / touch sensor for Arduino based on a square wave generator rather than charge time measurement as the popular CapSense library. This design results in very stable sensor readings and it also works from a battery. It is compatible with virtually every Arduino board and even with most ATtiny chips.



How it works

 

It uses the very same technique as the beautiful musical instrument, the theremin, only it operates on a much lower frequency (around ~60KHz instead of some MHz) and uses square-wave signals for the sake of easier counting. So let me paraphrase a simple explanation of its working principle from theremin.info:
By using an alternating current of suitable frequency, tones of varying pitch are easily obtainable. A small vertical rod is used as the antenna. When the instrument is in operation, electro-magnetic waves of very weak energy are generated around this rod. These waves are of a definite length and frequency. The approach of a hand, which is an electrical conductor, alters the conditions in the electro-magnetic field surrounding the antenna, changes its capacity and thus affects the frequency of the alternating current generated by the apparatus. In this manner, a kind of invisible touch is produced in the space surrounding the antenna, and, as in a cello, a finger pressing on a string produces a higher pitch as it approaches the bridge, in this case also, the pitch increases as the finger is brought nearer the antenna. 
In summary, stable, ~60KHz square-waves are generated by an RC oscillator. The capacity of an approaching hand causes a slight drop in this frequency what is detected by an Arduino based frequency counting library.

The square-wave generator

 

The sensor uses a Schmitt trigger based square-wave generator; this was the simplest suitable circuit I could find for this purpose, it consists only of three very cheap, commonly available parts: a capacitor, a resistor and a 7414 Schmitt trigger chip. The frequency of the output signal can be calculated by the following formula:  

freq = 1.2 / (R*C)

With some experiments, I found that 60Khz base frequency fits nicely my initial goal of starting to sense around ~15cm. I achieved this by choosing C = 100pF and R = 200KΩ.

To make the oscillator stable in the long term, it may make sense to use a ceramic capacitor with a low temperature drift, a so called NP0 type.

In summary, the part list and a lovely Fritzing diagram for the whole sensor:
  • C1 = 100 pF
  • R   = 200 KΩ
  • IC  = 74HC14 Hex inverting Schmitt trigger 
  • C2 = 100nF (optional, but recommended)

The software


The following code can be downloaded directly from https://github.com/domoszlai/arduino-proximity-sensor

In a perfect world, all one should do is to constantly measure the frequency of the oscillator and compare it to the expected (60KHz) frequency.  At proximity, the frequency drop is small (some hundreds Hz) and more or less linear with the distance. At touch, there is a huge, sudden, non-linear 
frequency drop. With some experimentation one can easily determine these frequencies to get an idea how to interpret an actual frequency measurement.   

However, we do not live in a perfect world:
  • The actual value of a resistor or capacitor is precise only to the extent of some tolerance (so you never get 60Khz, only if you buy high precision parts)
  • The actual value of a capacitor is dependent on the current temperature, voltage, ... (e.g. the frequency is drifting with time) 
  • The actual frequency is influenced by environmental noise (just put the antenna closer to your laptop to verify this)

To get proper, stable result, these problems must be handled. Probably there is no one, ultimate solution for these issues, rather, the solution vary from application to application. For my purposes, I found the following heuristic satisfying:

Instead of using the measured frequency directly, two lines of frequency measurements are maintained through an exponential filter. A baseline, with a huge weight (0.995), and current line with a smaller weight (0.75). Their values are initialized in the setup().

float current_line;
float baseline;

void setup()
{
  // https://github.com/domoszlai/arduino-frequency-counter
  current_line = baseline = count_frequency();
}

void loop()
{
  unsigned long f = count_frequency();

  current_line = current_line * CL_WEIGHT + (1 - CL_WEIGHT) * f;
  baseline = baseline * BL_WEIGHT + (1 - BL_WEIGHT) * f;

  ...
}

The baseline contains the base frequency, the frequency expected when proximity is not sensed. However, the baseline is not static: because of the exponential filter, it very slowly follows the changes of the current frequency measurement. This way, drifting of the base frequency is handled. It has, however, the side effect, that the system slowly adapts to proximity...

The current line also follows the changes of the current frequency measurement, but, because of the smaller weight, in a much faster pace. In this case, the exponential filter is applied only to smooth the frequency measurement.

A relative distance of the proximity can be computed easily now by proportionate the difference of the baseline and the current line to the maximal expected frequency drop:

// distance is reciprocal, bigger value indicates smaller distance
// and its range is 0-255 
int diff = max(0, base_line - current_line);
if(diff < MAX_EXPECTED_FREQ_NOISE) diff = 0;
int distance = diff * 255L / MAX_FREQUENCY_DROP;
distance = min(distance, 255);


Finally, putting everything together:


// https://github.com/domoszlai/arduino-frequency-counter
#include "frequency_counter_TC.h"

//#define DEBUG

#define MAX_FREQUENCY_DROP        500
#define MAX_EXPECTED_FREQ_NOISE   30
#define CL_WEIGHT                 0.75
#define BL_WEIGHT                 0.995

float current_line = 0;
float baseline = 0;

void setup()
{
#ifdef DEBUG
  Serial.begin(9600);
#endif

  current_line = baseline = count_frequency();
}

void loop()
{
  unsigned long f = count_frequency();

  current_line = current_line * CL_WEIGHT + (1 - CL_WEIGHT) * f;
  baseline = baseline * BL_WEIGHT + (1 - BL_WEIGHT) * f;

  // distance is reciprocal, bigger value indicates smaller distance
  // and its range is 0-255 
  int diff = max(0, baseline - current_line);
  if(diff < MAX_EXPECTED_FREQ_NOISE) diff = 0;
  int distance = diff * 255L / MAX_FREQUENCY_DROP;
  distance = min(distance, 255);

#ifdef DEBUG
  Serial.print(distance);
  Serial.print(" ");
  Serial.print(f);
  Serial.print(" ");
  Serial.print(baseline);
  Serial.print(" ");
  Serial.println(current_line);   
#endif  
}

Implementation notes

  • The oscillator is prone to the instability of the input voltage. A standard USB 5V can be quite noisy; in  my case, switching to regulated 3.3V reduced the frequency deviation by 50% the least. A voltage regulator is especially important, if it runs on a battery where voltage may drop as the load increases
  • Avoid breadboards. The parasitic capacitance of an average breadboard messes up the frequency spectrum so much, it is hard see the peaks
  • If ATtiny is used as frequency counter, it is essential to use an at least 8MHz external crystal. For a frequency counter, a stable timebase is essential; for this purpose, the internal oscillator of an ATtiny is just not good enough (this video is gold on the stability of oscillators)
  • If you want to increase sensitivity, try increase the base frequency (and the size of the antenna). In this case, you might want to try an LC oscillator instead of the RC one, as they tend to be more stable
  • It also make sense to decrease sensitivity, especially for a touch sensor. In this case 15-20Khz base frequency must be enough

 





Share:

Wednesday, January 4, 2017

Arduino frequency counter experiments

Introduction 

 

In this post, I would like to summarize my recent experiments with different frequency counting approaches using the Arduino platform. My original goal is to develop a small, capacitive proximity sensor, that is more reliable than the default charge time measurement based one (and just as importantly, works on a battery). The idea is, similarly to a theremin, to use an oscillator to generate square waves and detect small changes in the frequency caused by the proximity of a person. For this, I needed a reliable frequency counter first. My requirements were the following:
  • it should be as precise as possible for at least up to 100KHz
  • it should work on an ATtiny (even on an ATtiny13)
UPDATE: check out the proximity sensor, this library is developed for: http://dlacko.blogspot.com/2017/01/arduino-capacitive-proximity-sensor.html

 

Results


I ended up with a small library that is able to count frequency up to the MHz range on the more complex platforms (e.g. Uno, Mega), and I also got it working on most of the ATtiny ones (I tested it on a ATtiny85), but not on the ATtiny13 (technically it would be possible, but I could not fit the proximity sensor code into the 1K flash memory, so I decided not to waste more time on it). On an ATTiny, the inaccuracy of the internal oscillator may affect the measurement with a constant factor, but this is fine for my application (why would you count frequency with an ATTiny anyway?)

The library uses fixed 100ms gate time (the time period of counting pulses), which introduces some error (the pulse count is multiplied by 10 to get the frequency, thus, the last digit is always 0). Because of this, it is also worth to mention that this library does not work well for very low frequencies.

The frequency counter library is available at https://github.com/domoszlai/arduino-frequency-counter. It implements two different approaches as they both have pros and cons, and a separate one for ATTiny. Implementation details can be found in the source code and on the github page.

Approaches

 

I identified three basic frequency counting methods based on the number of  required hardware timers:

  • No timer

    This is a very naive approach, and it is not even entirely timer less as it implicitly uses Timer0 for counting time. Theses methods are usually based on one of the pulseIn(), millis()micros()functions to measure gate time or pulse width. I found them too imprecise for my purposes.
  • 1 timer
    This method uses one timer for gate time measurement and a pin-change interrupt (PCI) for counting the pulses. It is very generally usable as pin-change interrupts are available for many/most pins, but it may work well for lower frequencies only (it worked perfectly with ~60KHz in my tests). This is the method used by the ATTiny counter as the smallest ones, e.g. ATTiny85, has only one available timer (two but, Timer0 is used by the Arduino core).

    See: frequency_counter_PCI.cpp
  • 2 timers

    I found this the most reliable method that works for up to several MHz. It uses one timer for gate time measurement and a hardware timer/counter (TC) for counting the pulses. However, the hardware counter requires the usage of one specific pin, what can be very impractical in some situations. It must be the T1 pin (usually pin 5) for most boards, but T5 (pin 47) in the case of an Arduino Mega.

    See: frequency_counter_TC.cpp



Share:

Featured Post

V-plotter math: coordinate transformation with rotation compensation

The most important thing to deal with in v-plotter software is the coordinate transformation. Descartes coordinates must be converted ...

Popular Posts