So we were sitting in our professor’s cabin trying to decide on what project to make for the Design lab. After going through umpteen number project videos; right from a Robotic Arm to a Remote-controlled Home Automation, we came across the Laser Harp where by changing the position of obstruction in the path of a laser, we can generate music. That didn’t seems interesting enough though. How about DJing? That’s where a Theremin comes in. To read more on theremins, here’s a link: How a Theremin works
Our idea is to reconstruct a theremin using Raspberry Pi, Arduinos and sensors like Gyroscope, Accelerometers, Ultrasonic Distance Sensors that generates sine waves of varying frequencies dependent on the movement of hands(which have sensors attached). So let’s get started with the setup of Raspberry Pi.
The Pi Setup
A mini computer that can run most processes and widely used in DIY projects; Raspberry Pi is a series of small single board computers. To setup an RPi, we first need to download an OS, either Raspbian(officially supported OS) or NOOBS(easy installer). The detailed process has been explained here: Introduction – Raspberry Pi tutorial
We have used the Raspbian in our project. However, the file was corrupted which led to the wifi drivers not being installed. This situation doesn’t allow us to use the SSH protocol for remote login to the RPi. An easy way to resurrect this problem is to use a USB Wifi Adapter or just reinstall the OS(which is tedious). Another way of making the process easy is to use Etcher, which is a software used for easy and organised installation.
Once the OS is successfully installed, connect the pi to your computer’s hotspot which will enable you to access the pi. We used a projector directly by connecting it to the HDMI port before the Wifi drivers were installed. We need to first enable SSH as it is disabled by default since it allows anyone with the default username and password to connect to your pi. The following code installs the VNC server to project the RPi desktop screen on your desktop and also specify the dimensions. (you are basically using the remote desktop).
>> iwconfig helps check the netwok status
The time on the RPi also needs to be synced so as to update the current version of the software(in case of RPi 3) or any previous software(for older versions)
The Raspberry Pi is all set!
Generating Audio Signals
Initially, we generated audio signals on the computer using the PyAudio library. On installing the corresponding libraries on the Raspberry Pi, the code was rerun on the RPi.
import numpy as np import pyaudio p = pyaudio.PyAudio() volume = input("Enter the volume (0.1 to 1): ") fs = 44100 #Sampling Rate duration = 5.0 #Duration of Sound in Seconds f = input("Enter the frequency (201 - 17999): ") #Frequency samples = (np.sin(2*np.pi*np.arange(fs*duration)*f/fs)).astype(np.float32) #Generating Samples stream = p.open(format=pyaudio.paFloat32, channels=1, rate=fs, output=True) stream.write(volume*samples) stream.stop_stream() stream.close() p.terminate()
This code lead to white noise while using the 3.5mm audio jack. This was tackled by reimplementing the original PWM-based 11-bit audio @48kHz as 7-bit 2nd-order Sigma-Delta modulated at 781.25kHz. The effective noise floor with this scheme approximates that of CD-quality audio DACs. The rough edges and bugs can be taken care of by running sudo rpi-update and adding the following line in /boot/config.txt: audio_pwm_mode=2
We also need to run amixer cset numid=3 1 before using audio for the first time to switch the audio output path to audio jack as HDMI is the default.
The Ping Sensor(Ultrasonic Distance Sensor HC SR-04):
The code used for the ultrasonic distance sensor is given below:
#Libraries import RPi.GPIO as GPIO import time #GPIO Mode (BOARD / BCM) GPIO.setmode(GPIO.BCM) #set GPIO Pins GPIO_TRIGGER = 18 GPIO_ECHO = 24 #set GPIO direction (IN / OUT) GPIO.setup(GPIO_TRIGGER, GPIO.OUT) GPIO.setup(GPIO_ECHO, GPIO.IN) def distance(): # set Trigger to HIGH GPIO.output(GPIO_TRIGGER, True) # set Trigger after 0.01ms to LOW time.sleep(0.00001) GPIO.output(GPIO_TRIGGER, False) StartTime = time.time() StopTime = time.time() # save StartTime while GPIO.input(GPIO_ECHO) == 0: StartTime = time.time() # save time of arrival while GPIO.input(GPIO_ECHO) == 1: StopTime = time.time() # time difference between start and arrival TimeElapsed = StopTime - StartTime # multiply with the sonic speed (34300 cm/s) and divide by 2 distance = (TimeElapsed * 34300) / 2 return distance if __name__ == '__main__': try: while True: dist = distance() print ("Measured Distance = %.1f cm" % dist) time.sleep(1) # Reset by pressing CTRL + C except KeyboardInterrupt: print("Measurement stopped by User") GPIO.cleanup()
The connections between HC SR04 and Raspberry Pi are: VCC to Pin 2 (VCC), GND to Pin 6 (GND); TRIG to Pin 12 (GPIO18). Connect the 330Ω resistor to ECHO. On its end you connect it to Pin 18 (GPIO24) and through a 470Ω resistor you connect it also to Pin6 (GND). Direct connection between the sensor and Raspberry Pi didn’t work well as the connections were imperfect.
A voltage divider is introduced between ECHO pin and Raspberry Pi as we need to reduce the output voltage from 5V to 3.3V(GPIO pin rating for RPi) so that the GPIO pins on the RPi aren’t damaged.
The distance input by the ping sensor was coded to give the output variation in volume as distance/50 so as to get maximum range in 0.1 to 1 for volume(as per the specifications).
The MPU6050 has an embedded 3-axis MEMS gyroscope, a 3-axis MEMS accelerometer. It is very useful for some motion detecting. This small module integrate the logic level converter circuit（makes it compatible with 3.3V-5V voltage level) together with the MPU6050 sensor, you can integrate it to your project conveniently.
The connections are as follows:
We have obtained 8 types of directional features using the MPU:
1] Rotations along X, Y, Z axes (3 features)
2] Differential rotation between axes (2 features)
3] Acceleration along X, Y, Z axes (3 features)
Information on rotation shall be used to variate frequency while change in acceleration shall be used to change volume. This shall be taken care of in the integrating code.
The code for the interfacing is:
#!/usr/bin/python import smbus import math import time # Power management registers power_mgmt_1 = 0x6b power_mgmt_2 = 0x6c def read_byte(adr): return bus.read_byte_data(address, adr) def read_word(adr): high = bus.read_byte_data(address, adr) low = bus.read_byte_data(address, adr+1) val = (high << 8) + low return val def read_word_2c(adr): val = read_word(adr) if (val >= 0x8000): return -((65535 - val) + 1) else: return val def dist(a,b): return math.sqrt((a*a)+(b*b)) def get_y_rotation(x,y,z): radians = math.atan2(x, dist(y,z)) return -math.degrees(radians) def get_x_rotation(x,y,z): radians = math.atan2(y, dist(x,z)) return math.degrees(radians) bus = smbus.SMBus(1) # or bus = smbus.SMBus(1) for Revision 2 boards address = 0x68 # This is the address value read via the i2cdetect command # Now wake the 6050 up as it starts in sleep mode bus.write_byte_data(address, power_mgmt_1, 0) while 1: time.sleep(1.5) print "gyro data" print "---------" gyro_xout = read_word_2c(0x43) gyro_yout = read_word_2c(0x45) gyro_zout = read_word_2c(0x47) print "gyro_xout: ", gyro_xout, " scaled: ", (gyro_xout / 131) print "gyro_yout: ", gyro_yout, " scaled: ", (gyro_yout / 131) print "gyro_zout: ", gyro_zout, " scaled: ", (gyro_zout / 131) print print "accelerometer data" print "------------------" accel_xout = read_word_2c(0x3b) accel_yout = read_word_2c(0x3d) accel_zout = read_word_2c(0x3f) accel_xout_scaled = accel_xout / 16384.0 accel_yout_scaled = accel_yout / 16384.0 accel_zout_scaled = accel_zout / 16384.0 print "accel_xout: ", accel_xout, " scaled: ", accel_xout_scaled print "accel_yout: ", accel_yout, " scaled: ", accel_yout_scaled print "accel_zout: ", accel_zout, " scaled: ", accel_zout_scaled print "x rotation: " , get_x_rotation(accel_xout_scaled, accel_yout_scaled, accel_zout_scaled) print "y rotation: " , get_y_rotation(accel_xout_scaled, accel_yout_scaled, accel_zout_scaled)
Integrating the Peripherals:
- We decided to use the x rotation first and synchronise it with the audio output for lower frequencies.
- For a range of 0 Hz to 3000 Hz, the x rotation was scaled from -80 rad/s to +80 rad/s.
- The duration of a particular output was increased to 5s so that the change in frequency due to motion can be observed with clarity.
- The sampling frequency for the I/O was kept at the highest value( 44100 Hz ) to ensure that for a particular frequency, the signal was almost continuous. However, only the 3.5mm audio jack gives a sampled output in the duration of 5s. On switching to Bluetooth audio, a continuous output was obtained.