Theremin as a Capacitive Sensing Device

</p> <p>


Here we show a little Theremin module which plugs onto a Arduino Board that gives out the tune to a speaker or puts out the tune as control signal like MIDI, Servo  etc. We were using this device not only as a musical instrument, various kinds of proximity sensors, pointing devices or as interface in combination with Processing, Max or Pd have been build with this technique.


How it Works

The Theremin Module itself is as little LC type Radio frequency oscillator which generates a radio wave and  gives out the frequency signal to the Arduino board.
An Antenna connected to the LC resonator provides the Theremin effect when a person or some conductive material is placed next to the antenna.
This leads to a slight frequency deviation of the oscillator frequency which is registered by the Arduino software.
The Arduino itself acts in this case as a accurate frequency meter which transforms this frequency deviation into sound or control signals. Since you will find numerous articles in the Web describing the principle of a Theremin we leave here only that brief description.


Video

http://interface.khm.de/wp-content/uploads/2008/12/theremin.flv


The Oscillator

If you have a look at the Oscillator schematic you see the components L1 and C1 that are forming a resonator responsible for the oscillator frequency of about 4.1 Mhz. The Gates IC1A and IC1B are building an analog amplifier block which is rising the oscillator signal to be fed back to the resonator. Capacitor C3 put an amount of energy back to the input as positive feedback so that an steady oscillation occurs. The Gates D and C are conditioning the wave into a digital logic level squarewave signal for the Arduino board

Care must be taken in the choice of the parts L1 and C1. C1 must be a ceramic capacitor NP0 type (zero temperature coefficient) and L1 a high Q inductor suitable for high frequency such as a Neosid 00612299 but for the first test a standard 10uH Choke will do also the job. Because the oscillator frequency tends to drift  by ambient temperature changes the circuit should be capsuled in a little plastic box. The antenna length should be not longer than about one meter, good results can be obtained with a ring of 1,5mm steel wire which provides also a good mechanical stability to avoid swinging or vibration. Most of the drift or other unwanted side effects are compensated by the software.
It must be noticed that also that all metal parts like usb cable, connected speaker, even the connected computer are loaded by the RF signal and tend to be “hot” so that they work as antenna as well.


TTL LC-type Oscillator schematic


</p> <p>



Oscillator Breadboard Layout

</p> <p>


The Software

To measure the oscillator frequency the output is connected to digitalinput 5 of the Arduino Board. This pin has an alternate function as an input of the ATMEGA internal hardware Counter1 unit. If configured by software, every time a level change is present on that pin the counter is advanced by one. That happens in our case about 4 million times a second. The hardware Timer2 unit is used as a timebase to start and stop counting in a interval of exactly 1/10 second. After that period the counter value reads about 410.000 so we yield a measurement resolution of  10Hz which is what we need to see the slight frequency deviation caused by the Theremin effect. Since the Counter1 unit is only a 16 Bit counter several overflows occur during the measurement. These overflows are counted in the timebase section to be assembled after the measurement period to a long int result. The whole timing is done in an interrupt function which is invoked by the timer2 all 2 milliseconds.
If the end of a timebase period is reached a flag as a global variable is set to signal the main function the presence of a new value. Since only the frequency differences not the absolute value are of interest  for us, the consecutive values are substracted from the first frequency value after startup. An autocalibration to eliminate long term frequency drift is performed after a number of measurement cylces when the deviation is smaller then a certain threshold value. The threshold value and the number of cycles to calibrate has to be customized to the application.
Please refer to the ATMEL Atmega168 pdf to learn about the details of the timer and counter sections.


</p> <p>


</p> <p>


</p> <p>


</p> <p>


Source Code

/* Theremin Test
 *
 * Therremin with TTL Oscillator 4MHz
 * Timer1 for freauency measurement
 * Timer2 for gate time
 * connect Oscillator on digital pin 5
 * connect Speaker with 1K Resistor in series on pin 8

 * KHM 2008 /  Martin Nawrath
 * Kunsthochschule fuer Medien Koeln
 * Academy of Media Arts Cologne

 */
#include <Stdio.h>
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))

//! Macro that clears all Timer/Counter1 interrupt flags.
#define CLEAR_ALL_TIMER1_INT_FLAGS    (TIFR1 = TIFR1)

int pinLed = 13;                 // LED connected to digital pin 13
int pinFreq = 5;

void setup()
{
  pinMode(pinLed, OUTPUT);      // sets the digital pin as output
  pinMode(pinFreq, INPUT);
  pinMode(8, OUTPUT);

  Serial.begin(57600);        // connect to the serial port

  // hardware counter setup ( refer atmega168.pdf chapter 16-bit counter1)
  TCCR1A=0;                   // reset timer/counter1 control register A
  TCCR1B=0;                   // reset timer/counter1 control register A
  TCNT1=0;                    // counter value = 0
  // set timer/counter1 hardware as counter , counts events on pin T1 ( arduino pin 5)
  // normal mode, wgm10 .. wgm13 = 0
  sbi (TCCR1B ,CS10);         // External clock source on T1 pin. Clock on rising edge.
  sbi (TCCR1B ,CS11);
  sbi (TCCR1B ,CS12);

  // timer2 setup / is used for frequency measurement gatetime generation
  // timer 2 presaler set to 256 / timer 2 clock = 16Mhz / 256 = 62500 Hz
  cbi (TCCR2B ,CS20);
  sbi (TCCR2B ,CS21);
  sbi (TCCR2B ,CS22);

  //set timer2 to CTC Mode
  cbi (TCCR2A ,WGM20);
  sbi (TCCR2A ,WGM21);
  cbi (TCCR2B ,WGM22);
  OCR2A = 124;                  // CTC at top of OCR2A / timer2 interrupt when coun value reaches OCR2A value

  // interrupt control

  sbi (TIMSK2,OCIE2A);          // enable Timer2 Interrupt

}

volatile byte i_tics;
volatile byte f_ready ;
volatile byte mlt ;
unsigned int ww;

int cal;
int cal_max;

char st1[32];
long freq_in;
long freq_zero;
long freq_cal;

unsigned int dds;
int tune;

int cnt=0;

void loop()
{
  cnt++;
  // add=analogRead(0);

  f_meter_start();

  tune=tune+1;
  while (f_ready==0) {            // wait for period length end (100ms) by interrupt
    PORTB=((dds+=tune) >> 15);    // kind of DDS tonegenerator / connect speaker to portb.0 = arduino pin8
  }
 tune = freq_in-freq_zero;
 // use the tune value here for your own purposes like control of servos, midi etc.

  // startup
  if (cnt==10) {
    freq_zero=freq_in;
    freq_cal=freq_in;
    cal_max=0;
    Serial.print("** START **");
  }

  // autocalibration
  if (cnt % 20 == 0) {   // try autocalibrate after n cycles
    Serial.print("*");
    if (cal_max <= 2) {
      freq_zero=freq_in;
      Serial.print(" calibration");
    }
    freq_cal=freq_in;
    cal_max=0;
    Serial.println("");
  }
  cal = freq_in-freq_cal;
  if ( cal < 0) cal*=-1;  // absolute value
  if (cal > cal_max) cal_max=cal;

  digitalWrite(pinLed,1);  // let LED blink
  Serial.print(cnt);
  Serial.print("  "); 

  if ( tune < 0) tune*=-1;  // absolute value
   sprintf(st1, " %04d",tune);
  Serial.print(st1);
  Serial.print("  "); 

  Serial.print(freq_in);
  Serial.print("  ");
/*
  Serial.print(freq_zero);
  Serial.print("  ");
  Serial.print(cal_max);
*/
  Serial.println("");
  digitalWrite(pinLed,0);

}
//******************************************************************
void f_meter_start() {
  f_ready=0;                      // reset period measure flag
  i_tics=0;                        // reset interrupt counter
  sbi (GTCCR,PSRASY);              // reset presacler counting
  TCNT2=0;                         // timer2=0
  TCNT1=0;                         // Counter1 = 0
  cbi (TIMSK0,TOIE0);              // dissable Timer0 again // millis and delay
  sbi (TIMSK2,OCIE2A);             // enable Timer2 Interrupt
  TCCR1B = TCCR1B | 7;             //  Counter Clock source = pin T1 , start counting now
}

//******************************************************************
// Timer2 Interrupt Service is invoked by hardware Timer2 every 2ms = 500 Hz
//  16Mhz / 256 / 125 / 500 Hz
// here the gatetime generation for freq. measurement takes place: 

ISR(TIMER2_COMPA_vect) {

  if (i_tics==50) {         // multiple 2ms = gate time = 100 ms
                            // end of gate time, measurement ready
    TCCR1B = TCCR1B & ~7;   // Gate Off  / Counter T1 stopped
    cbi (TIMSK2,OCIE2A);    // disable Timer2 Interrupt
    sbi (TIMSK0,TOIE0);     // ensable Timer0 again // millis and delay
    f_ready=1;              // set global flag for end count period

                            // calculate now frequeny value
    freq_in=0x10000 * mlt;  // mukt #ovverflows by 65636
    freq_in += TCNT1;       // add counter1 value
    mlt=0;

  }
  i_tics++;                 // count number of interrupt events
  if (TIFR1 & 1) {          // if Timer/Counter 1 overflow flag
    mlt++;                  // count number of Counter1 overflows
    sbi(TIFR1,TOV1);        // clear Timer/Counter 1 overflow flag
  }

}


Files

theremin.zip


Links

http://www.schuelke.org
http://www.freyahattenberger.de/works%20leasstsich2.html

http://netzspannung.org/cat/servlet/CatServlet?cmd=netzkollektor&subCommand=showEntry&forward=&entryId=87369&version=print&print=all&lang=de

http://www.khm.de/en/project/paview/2446/paitems/60/pasearch/theremin/

http://www.khm.de/en/project/paview/1376/
http://www.khm.de/~alfred/state/photos.html

http://mars.imk.fraunhofer.de/imk_web-pre2000/images/mars/files/graphicon99.pdf



A PIC-based Theremin and a dual AVR Theremin

<br />



Forum

further questions to this topic can be discussed here:

http://arduino.cc/forum/index.php/topic,65991.0.html


Contact

Martin Nawrath, nawrath@khm.de