Project 6: Input devices. Make a theremin. More realistically, make something that produces sound that changes when you wave your hands.

-- hardware setup --
A traditional theremin uses a solid copper antenna. I approixmated this by wrapping the adhesive copper we have around an acrylic rod.
The biggest problem I had was that it was hard to get a range on the capacitance sensing. I thus changed the hello3 board to have a smaller resistor and thus have a larger current.
However, I am limited by the fact that the microcontroller requires 5V.
This took quite a bit of trial and error, soldering an desoldering. In the end, it worked but I was not able to get a very good range, maybe only 1 inch or so.

I hooked up a 9V battery to my board because when I was using a USB-->serial hookup to try to run the GUI on my laptop I saw that the actual voltage drop was only 4.4V, and that the adapter was sapping some of the voltage away.

-- my quasi-theremin --

Here is what the final set up looked like.


Here it is in action: ... Theremin Movie ...


-- making a sound in python --
I downloaded a soundlibrary called sndobj that allows you to play a sound in python.
It was frustrating on the linux computer because it required installing libjack.
Also frustrating was the fact that there were no speakers on the computer, so I had to find a friend with speakers at the Media Lab and hook them up to the computer.
Unfortunately, even though I downloaded all the required things to run my board from my laptop, the response was very noisy and strange and I decided to just try to work on the linux computer in the 016 lab anyway.
My test sound code looks like this:
import sndobj
import time

tab = sndobj.HarmTable()
tab.SetHarm(2,2)
osc = sndobj.Oscili(tab, 100, 10000)
mod = sndobj.Oscili(tab, 200, 200)
outp = sndobj.SndRTIO(1, sndobj.SND_OUTPUT)

mod.SetFreq(200)
osc.SetFreq(300, mod)
outp.SetOutput(1, osc)

thread = sndobj.SndThread()
thread.AddObj(mod)
thread.AddObj(osc)
thread.AddObj(outp, sndobj.SNDIO_OUT)

thread.ProcOn()
time.sleep(5)
osc.SetFreq(600, mod)
time.sleep(5)
thread.ProcOff()


which basically made a tone that is an oscillation modulated by a second oscillation, and then the same tone one octave higher.
It was fun to play with sound modulation and harmonics and different waveforms. I decided that a saw-shaped waveform with 5 harmonics sounded the coolest.


-- modifying the assembly code --
I changed the assembly code from the hello3.step.asm to only sample the capacitance at the charging part of the curve. I made several changes:
-only charge the capacitor until it samples, then let it discharge
-only sample on the charging part of the curve
-sample early in the curve for higher sensitivity
-sample at the same place each time through
-only send two bytes of the data--i.e., one reading from the ADC
The resulting code is much simpler, and as I hoped, faster.
The assembly code I used is at the bottom.

-- modifying the GUI -- I used my voltmeter GUI from Project 4 as a base.
I then added the sound code and replaced the frequency argument (i.e., the pitch) with a variable dependent on the input coming from the serial port.
There was a problem synching the board with the computer. David helped me make the GUI check the framing each time to make sure it responds correctly.
There was obviously also some noise issues. When we amplified the signal to make a bigger change in the response, we amplified the noise as well. We got rid of some of this problem by doing a rolling average.
Here is a screenshot of the visual display.



The code is displayed at the end.

******************************************************************************
The assembly code:



;
; suelin.asm
; capacitance measurement
; based on Neil Gershenfeld CBA MIT 10/29/05
;
; definitions
;
.include "tn13def.inc"
.equ chargepin  = PB1 ; charging pin
.equ txpin = PB4 ; serial transmit pin
.def bitcnt = R16 ; bit counter
.def temp = R17	; temporary storage
.def txbyte = R18 ; data byte
.def delaycnt = R19 ; delay counter
.def uplo = R20 ; up low byte
.def uphi = R21 ; up hi byte
;
; start of code
;
.cseg
;puts program at beginning of memory
.org 0
rjmp reset ; jump to reset routine
;
; putchar routine
; assumes no line driver (doesn't invert bits)
;
.equ sb = 1 ;  number of stop bits
putchar:
   ldi bitcnt, 9+sb ; 1+8+sb
   com txbyte ; invert everything
   sec ; set start bit
   putchar0:
      brcc putchar1 ; if carry set
      sbi PORTB, txpin ; send a '0'
      rjmp  putchar2 ; else	
   putchar1:
      cbi PORTB, txpin ; send a '1'
      nop
   putchar2:
      rcall bitdelay ; one bit delay
      rcall  bitdelay
      lsr txbyte ; get next bit
      dec bitcnt ; if not all bits sent
      brne putchar0 ; send next
   ret ; else return
;
; serial bit delay routine
;
.equ b = 17 ; 9600 bps
bitdelay:
   ldi temp, b
   bitloop:
      dec temp
      brne bitloop
   ret
;
; routine to wait for sample to settle
;
.equ delay = 255
settle:
   ldi temp, delay
   settleloop:
      dec temp
      brne settleloop
   ret
;
; main program
;
reset:
   ldi temp, low(RAMEND) ; set stack pointer to top of RAM
   out SPL, temp ;
   ;
   ; init output pins
   ;
   sbi PORTB, txpin ; comm
   sbi DDRB, txpin ; "
   sbi PORTB, chargepin ; charging
   sbi DDRB, chargepin ; "
   ;
   ; init A/D
   ;
   cbi ADMUX, REFS0 ; use Vcc as reference
   cbi ADMUX, ADLAR ; right-adjust result
   sbi ADCSRA, ADEN ; enable A/D
   cbi ADCSRA, ADATE ; disable auto-trigger
   cbi ADCSRA, ADPS2 ; set prescaler for /2
   cbi ADCSRA, ADPS1 ; "
   cbi ADCSRA, ADPS0 ; "
   cbi ADMUX, MUX1 ; input on ADC1
   sbi ADMUX, MUX0 ; "
   ;
   ; infinite main loop
   ;
   loop:
      ;
      ; loop over delays
      ;
        
      ;changed from 254 to sample earlier
      ldi delaycnt, 5
      delayloop:
         ;
	 ; settle sample and start upward step response
	 ;
         cbi PORTB, chargepin
         rcall settle
	 mov temp, delaycnt
         sbi PORTB, chargepin
	 ;
         ; wait for delay--maybe change amount of time depending on 
charge curve
	 ;
	 addelayup:
	    dec temp
	    brne addelayup
         ;
	 ; read response
	 ;
         sbi ADCSRA, ADSC ; start conversion
         adloopup:
            sbic ADCSRA, ADSC ; loop until complete
            rjmp adloopup
	 ;
	 ; save conversion
	 ;
         ;in uplo, ADCL ; get low byte
         ;in uphi, ADCH ; get high byte
         ;
	 ;
	 ; send conversions
	 ;
         in txbyte, ADCL ; low down byte
         rcall putchar
         in txbyte, ADCH ; hi down byte
         rcall putchar
         ;mov txbyte, uplo ; low up byte
         ;rcall putchar
         ;mov txbyte, uphi ; hi up byte
         ;rcall putchar
	 ;dec delaycnt
	 ;dec delaycnt
	 ;brne delayloop
      ;
      ; send 1 2 3 4 for framing
      ;
      ldi txbyte, 1
      rcall putchar
      ldi txbyte, 2
      rcall putchar
      ldi txbyte, 3
      rcall putchar
      ldi txbyte, 4
      rcall putchar
      rjmp loop

******************************************************************************
Here is the GUI for the theremin:

#
# 
# theremin GUI
# 11.05.06
#
from Tkinter import *
import serial

import sndobj
import time

tab = sndobj.HarmTable()
tab.SetHarm(5,4)
osc = sndobj.Oscili(tab, 100, 500)
mod = sndobj.Oscili(tab, 440, 2)
outp = sndobj.SndRTIO(1, sndobj.SND_OUTPUT)

mod.SetFreq(200)
osc.SetFreq(5, mod)
outp.SetOutput(1, osc)

thread = sndobj.SndThread()
thread.AddObj(mod)
thread.AddObj(osc)
thread.AddObj(outp, sndobj.SNDIO_OUT)

thread.ProcOn()
#thread.ProcOff()


WINDOW = 800
NSAMPLES = 254
MAX = 1040
MAX_VOLTAGE = 5.0
eps = .9
saveflag = 0
index = 0
path = []
step = []
path_filt = []
step_filt = []
baseline = []

xpos = .05*WINDOW

def idle(parent,canvas):
   global index, channel, baseline, path, path_filt, step, step_filt, saveflag, xpos
   #
   # idle routine
   #
   eps = float(sfilter.get())
 #  lo_dn = ord(ser.read())
 #  hi_dn = ord(ser.read())
   #uplo = ord(ser.read())
   #uphi = ord(ser.read())
   voltage_value1 = 90
   voltage_value2 = 90
   voltage_value3 = 90
   voltage_value4 = 90
   voltage_value5 = 90
   voltage_value6 = 90
   voltage_value7 = 90
   voltage_value8 = 90
   voltage_value9 = 90
   voltage_value10 = 90
   voltage_value11 = 90
   voltage_value12 = 90
   i = 0
   x0 = 0
   x1 = 0
   x2 = 0
   x3 = 0
   x4 = 0
   x5 = 0
   keepLooping = 1
   ser.flushInput()
   while keepLooping:
     #i += 1
  	x5 = x4
	x4 = x3
	x3 = x2
	x2 = x1
   	x1 = x0
   	x0 = ord(ser.read())
   	if ((x5 == 1) & (x4 == 2) & (x3 == 3) & (x2 == 4)):
                voltage_value1 = voltage_value2
                voltage_value2 = voltage_value3
                voltage_value3 = voltage_value4
                voltage_value4 = voltage_value5
                voltage_value5 = voltage_value6
                voltage_value6 = voltage_value7
                voltage_value7 = voltage_value8
                voltage_value8 = voltage_value9
                voltage_value9 = voltage_value10
                voltage_value10 = voltage_value11
                voltage_value12 = float(x1 + (x0*256))
                voltage_value = (voltage_value1 +voltage_value2 +voltage_value3 +voltage_value4 +voltage_value5 
+voltage_value6 +voltage_value7 +voltage_value8 +voltage_value9 +voltage_value10 +voltage_value11 +voltage_value12)/12
                voltage_value = (voltage_value-100)*50
		keepLooping = 0
		print '%.3f'%voltage_value
   keepLooping = 1

 #to make the window start over
   if xpos == WINDOW:
      xpos = .05*WINDOW
      canvas.delete("dot")	

#want voltage_value to be the slope of the line
  

   #convert these two to one number
   #voltage_value = 256*uphi + uplo # this number is between 0-1023
   #convert to 0-5 volt values
   
   #taking care of invalid voltage readings from chip
   if voltage_value < 0:
      voltage_value = 0
   if voltage_value > 1023:
      voltage_value = 1023

   voltage_value_normalized = float( voltage_value / 1023.0 ) # range [0,1]
   voltage_value_volts = voltage_value_normalized * MAX_VOLTAGE # range [0,5]
   vvsn = int ( voltage_value / 50 ) # correlate voltage value to text size
   # vvsn stands for voltage value size normalized
   r=int (voltage_value_volts * 51)
   b=int (255 - r)
   g=0			
   vvcn =  "#%02x%02x%02x"% (r,g,b) 

   osc.SetFreq(20+(5**voltage_value_normalized)*50, mod)

   # vvsn stands for voltage value color normalized
   dotcolor =  "#%02x%02x%02x"% (r,g,b) 
   #time counter==xposition

   #read 1 2 3 4 again and ignore

   print "-------"
   print voltage_value, voltage_value_normalized

   #show convetred value on screen
   

   canvas.delete("voltage")
   canvas.delete("voltagestatic")
   canvas.create_text(xpos, (1-voltage_value_normalized)*WINDOW,font=("Times New Roman", vvsn),tags="voltage",fill=vvcn)
   canvas.create_text(.05*WINDOW,.05*WINDOW, font=("Times New Roman", vvsn),tags="voltagestatic", fill=vvcn)
   canvas.itemconfigure("voltage",text="%.2f [V]"%voltage_value_volts)	
   canvas.itemconfigure("voltagestatic",text="%.2f [V]"%voltage_value_volts)
 #  canvas.delete("oval")
   xpos = xpos + 1
   canvas.create_oval(xpos, (1-voltage_value_normalized)*WINDOW, xpos, (1-voltage_value_normalized)*WINDOW, width=3, 
tags="dot", fill=vvcn, outline=vvcn)
   parent.after_idle(idle,parent,canvas)

"""
   lo_dn =0
   hi_dn =0
   lo_up =0
   hi_up =0
   


   if ((lo_dn == 1) & (hi_dn == 2) & (lo_up == 3) & (hi_up == 4)):
      if (path_filt == []):
         path_filt = path
	 step_filt = step
      else:
         for i in range(len(path_filt)):
	    path_filt[i] = (1-eps)*path_filt[i] + eps*path[i]
         for i in range(len(step_filt)):
	    step_filt[i] = (1-eps)*step_filt[i] + eps*step[i]
      canvas.delete("path")
      canvas.create_line(path_filt,tag="path",width=3,fill="#00b000")
      if (baseline != []):
         canvas.delete("baseline_path")
         canvas.create_line(baseline,tag="baseline_path",width=3,fill="#b00000")
      canvas.itemconfigure("y0",text="y[0]: %.2f"%step_filt[0])
      canvas.itemconfigure("y1",text="y[1]: %.2f"%step_filt[1])
      canvas.itemconfigure("y-1",text="y[-1]: %.2f"%step_filt[-1])
      canvas.coords('x0',0,.95*WINDOW,step_filt[0],WINDOW)
      if (saveflag == 1):
         file = open(soutfile.get(),"w")
	 for i in range(len(step_filt)):
	    file.write("%f\n"%(step_filt[i]))
	 file.close()
	 print 'saved to '+soutfile.get()
         saveflag = 0
      index = 0
      path = []
      step = []
   else:
      value_up = 256*hi_up + lo_up
      value_dn = 256*hi_dn + lo_dn
      value = (value_up + (1023-value_dn))/2.0
      index += 2
      step.insert(0,value)
      path.insert(0,WINDOW-value*WINDOW/float(MAX))
      path.insert(0,WINDOW-index*WINDOW/float(NSAMPLES))
"""
   #parent.after_idle(idle,parent,canvas)


def save_data(parent):
   global saveflag
   saveflag = 1

def store_baseline(parent):
   global path_filt, baseline
   baseline = []
   for i in range(len(path_filt)):
      baseline.append(path_filt[i])

#
# open serial port
#
#ser = serial.Serial('/dev/ttyUSB0',9600)

ser = serial.Serial('/dev/ttyS0',9600)
#ser = serial.Serial('COM6',9600)
ser.setDTR()
#
# find framing
#
print "finding framing ..."
byte2 = 0
byte3 = 0
byte4 = 0
while 1:
   byte1 = byte2
   byte2 = byte3
   byte3 = byte4
   byte4 = ord(ser.read())
   if ((byte1 == 1) & (byte2 == 2) & (byte3 == 3) & (byte4 == 4)):
      print "start plotting"
      break
#
# start plotting
#
root = Tk()
root.title('hello3.step.py')
root.bind('q','exit')

canvas = Canvas(root, width=WINDOW, height=WINDOW, background='white')
canvas.create_text(.5*WINDOW,.5*WINDOW,font=("Helvetica", 24),tags="voltage",fill="#000000")
canvas.create_text(.2*WINDOW,.9*WINDOW,font=("Helvetica", 24),tags="y0",fill="#0000b0")
canvas.create_text(.5*WINDOW,.9*WINDOW,font=("Helvetica", 24),tags="y1",fill="#0000b0")
canvas.create_text(.8*WINDOW,.9*WINDOW,font=("Helvetica", 24),tags="y-1",fill="#0000b0")
canvas.create_rectangle(0,.95*WINDOW,0,WINDOW, tags='x0', fill='#b00000')
canvas.pack()



ioframe = Frame(root)
Label(ioframe, text=" filter (0-1):").pack(side="left")
sfilter = StringVar()
sfilter.set(str(eps))
Entry(ioframe, width=5, textvariable=sfilter).pack(side="left")
Label(ioframe, text=" ").pack(side="left")
basebtn = Button(ioframe, text="store baseline")
basebtn.bind('',store_baseline)
basebtn.pack(side="left")
Label(ioframe, text=" ").pack(side="left")
savebtn = Button(ioframe, text="save data")
savebtn.bind('',save_data)
savebtn.pack(side="left")
Label(ioframe, text=" output file:").pack(side="left")
soutfile = StringVar()
soutfile.set("out.dat")
Entry(ioframe, width=10, textvariable=soutfile).pack(side="left")
Label(ioframe, text=" ").pack(side="left")
quitbtn = Button(ioframe, text="quit")
quitbtn.bind('','exit')
quitbtn.pack(side="left")
ioframe.pack()

root.after(1000,idle,root,canvas)
root.mainloop()




edit