Introduction:

This project began in 2008 after reading Programming and Customizing the Basic Stamp Computer by Scott Edwards. This book, although somewhat dated, is one of the best introductory level texts on the subject of robotics and microcontroller programming that I’ve found. The “Sonar Theremin” project described on this site is an adaptation of the “Sonamin” project found in chapter 14 of the Stamp book. Our adaptations include code and circuit modifications for PIC microcontrollers, commercial sonar, and custom sound effects. The original design, however, is worth reviewing and includes instructions for building your own sonar sensor using transducers and an op-amp. We spent a lot of time trying to build our own sonar sensor using the Scott Edwards example and many others but were never able to reproduce the capabilities of existing commercial models in terms of both cost and performance. This I think is a growing trend in electronics, and technology in general, that those with a do-it-yourself attitude find difficult to embrace. It is more often than not easier and more effective to purchase a special-function, mass-produced, commercial component than to design something yourself. In a way, this liberates us from the tedium of low level design and empowers us to build with greater complexity. The result of this, however, is that it’s becoming increasingly easier to build something than to understand it. More than anything, this project is intended for those do-it-yourself hackers out there who want to build AND understand. If that’s you then read on; if not, see you in the apocalypse. 🙂

Step 1: Build the Prototype

The first step to any electronics project is to build a prototype. This will help you see what’s going on in the circuitry and force you to address any gaps in your understanding. You’ll need all of the parts identified in the parts list as well as a breadboard and some wiring. I like to use the big fat solderless breadboards and reinforced jumper wires from Jameco. The reinforced jumper wires are available in 8 inch, 4 inch and 2 inch segments. You’ll probably want some of each, but I’ve found the 2 inch lengths to be the most useful. You may also want to grab a couple 18 pin ZIF sockets (zero insertion force). These will allow you to easily move the microcontrollers in and out of the circuit as you’re prototyping without breaking any pins. Also, a few alligator clip test leads are nice to have for wiring up the bulkier components like potentiometers, power jacks and batteries. Obviously your budget will determine how many of these accessory components you can afford. All you really need is a medium size breadboard and some jumper wires.

Once you’ve assembled all of the necessary components, wire it all together according to the circuit schematic below. Be very careful about how you wire the power and ground connections. Do not reverse them by mistake and do not run more than 5 volts to the microcontollers or the sonar unit. Use a multimeter to test the voltages if you are unsure. Nero and I blew out a sonar unit once by driving it with 12 volts instead of 5. They are not exactly cheap.

[singlepic id=58 w=320 h=240]

When you’re done, you should have a working prototype that looks similar to the following.

[nggallery id=6]

Step 2: Program the Microcontrollers

Once you’ve built the prototype it’s time to program the microcontrollers. To program a microcontroller, you need a full development environment which generally consists of the following elements. Since we are using PIC16F628A microcontrollers, we will need to be sure that all of these elements are compatible.

  1. Source Code Editor / IDE – A Source Code Editor or IDE (Integrated Development Environment) is a collection of tools that allow you to write and edit source code. I used MicroCode Studio for this project, which is free. There are many others options, but your choice will largely be determined by the programming language and compiler you want to use. Theoretically, you could use something as simple as notepad to write/edit your code but I don’t recommend it.
  2. Compiler – A compiler is a program that converts human readable source code into machine readable hex code that can run on a microcontoller. Many Source Code Editors / IDE’s are distributed with a compiler and automate the process of using it. Several compilers are available for PIC microcontollers and many are free. I used PICBasic PRO for this project, which integrates with MicroCode Studio and is relatively cheap (but not free). Since the code for this project is written in PICBasic PRO, you’re kind of stuck with using the PICBasic PRO compiler unless you want to do some conversion.
  3. Programmer – A programmer is a hardware device that allows you to transfer or “program” your compiled code onto the microcontroller. I use a PIC-PG2C (first image below). This is one of the most economic programmers and works very well. All you need is a serial port and cable. (Note that this programmer may not work with some laptops due to the lower voltage of the serial connection.)
  4. Programmer Driver – A programmer driver is a piece of software that communicates with the programmer hardware in order to transfer compiled source code from a PC to a microcontroller. Sometimes this software is built into the Source Control Editor / IDE, but in many cases, especially for third party programmers, the driver is a separate utility. I use ICProg which is a free separate utility.

Once you have installed and configured each of the above elements, programming the microcontroller is easy. Just load the source code into your chosen editor / IDE and select the compile option. Once the code is compiled, load the machine code file into your chosen programmer driver and select the program option. In MicroCode Studio, the entire compile and program process is automated and can be initiated with a single button click. This is a very nice feature to have in a development environment, but getting there can be tricky and will probably require a bit of time and research. Note that when you perform the program operation, you will need to set the fuses as follows.

If you want to compile the source code yourself, it is available below. Note that there are two programs, one for each microcontroller. Specifically, sonar_theremin_master.pbp is for the master microcontroller (U1 in the circuit schematic) and sonar_theremin_slave.pbp is for the slave microcontroller (U2) in the circuit schematic. If you don’t have access to a PICBasic PRO compiler, you can just skip the compile step and download the master and slave machine hex code. You can use these files with a programmer driver to program the microcontrollers directly.

If you’re interested in understanding how the master and slave programs work, please read through the documentation in the code. Almost every statement is explained in detail. In general, these programs perform the following functionality.

sonar_theremin_master.pbp:

  1. Outputs a series of startup tones to the audio channel using hardware pulse width modulation.
  2. Sends a ping command to the sonar unit.
  3. Measures the elapsed time between the ping and the ping echo.
  4. Calculates an audio frequency that is proportional to the ping time.
  5. Outputs the calculated frequency using hardware pulse width modulation.
  6. Sends a command to the slave instructing it to output a frequency exactly one octave below the calculated frequency from step 4 above.
  7. Repeat beginning at step 2 above.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
'****************************************************************
'* Name : sonar_theremin_master.pbp *
'* Author : Odysseus, Hemingway, Nero *
'* Notice : Copyright (c) 2009, Section9.org *
'* Date : 9/30/2010 *
'* Version : 1.3 *
'****************************************************************

' Define oscillator speed.
DEFINE OSC 4

' Define program symbols.
symbol sonarPin = PortB.6 ' Sonar signal pin.
symbol TxPin = PortB.7 ' Serial communication transmission pin.

' Define program constants.
lA con 440 ' Frequency for low A tone.
lAs Con 466 ' Frequency for low A sharp tone.
lB con 494 ' Frequency for low B tone.
lC con 523 ' Frequency for low C tone.
lCs con 554 ' Frequency for low C sharp tone.
lD con 587 ' Frequency for low D tone.
lDs con 624 ' Frequency for low D sharp tone.
lE con 659 ' Frequency for low E tone.
lF con 698 ' Frequency for low F tone.
lFs con 740 ' Frequency for low F sharp tone.
lG con 784 ' Frequency for low G tone.
lGs con 831 ' Frequency for low G sharp tone.
A con 880 ' Frequency for A tone.
As con 932 ' Frequency for A sharp tone.
B con 988 ' Frequency for B tone.
C con 1046 ' Frequency for C tone.
Cs con 1108 ' Frequency for C sharp tone.
D con 1174 ' Frequency for D tone.
Ds con 1249 ' Frequency for D sharp tone.
E con 1318 ' Frequency for E tone.
F con 1396 ' Frequency for F tone.
Fs con 1480 ' Frequency for F sharp tone.
G con 1568 ' Frequency for G tone.
Gs con 1662 ' Frequency for G sharp tone.
hA con 1762 ' Frequency for high A tone.
hAs con 1873 ' Frequency for high A sharp tone.
hB con 1976 ' Frequency for high B tone.
hC con 2094 ' Frequency for high C tone.
hCs con 2218 ' Frequency for high C sharp tone.
hD con 2350 ' Frequency for high D tone.
hDs con 2489 ' Frequency for high D sharp tone.
hE con 2636 ' Frequency for high E tone.
hF con 2794 ' Frequency for high F tone.
hFs con 2960 ' Frequency for high F sharp tone.
hG con 3136 ' Frequency for high G tone.
hGs con 3322 ' Frequency for high G sharp tone.
SerCommSoT con "*" ' Serial communication start of transmission code.
SerCommMode CON 6 ' Serial communication mode code.

' Define program variables.
tone var byte ' Variable to store the tone number.
TxWord var word ' Variable to store the serial transmission data.
wDur var word ' Variable to store the tone duration.
wFreq var word ' Variable to store the tone frequency.
wPingTime var word ' Variable to store the ping time in 10 us increments.
w32 var word ' Variable for use in 32 bit calculations.
x var byte ' Variable for loop index.

gosub startup

main:

gosub getPingTime
gosub calcFreqFromPingTime
gosub doTones

goto main

' Play a little startup tune.
startup:

for x = 1 to 2
wDur = 100
wFreq = la
gosub doTones
pause wDur
wFreq = lE
gosub doTones
pause wDur
wFreq = A
gosub doTones
pause wDur
wFreq = hE
gosub doTones
pause wDur
wFreq = le
gosub doTones
pause wDur
wFreq = a
gosub doTones
pause wDur
wFreq = hE
gosub doTones
pause wDur
wFreq = le
gosub doTones
pause wDur
wFreq = A
gosub doTones
pause wDur
wFreq = hE
gosub doTones
pause wDur
next x

wFreq = A
gosub doTones
pause wDur
wFreq = hA
gosub doTones
pause wDur * 5

return

' Generate a sonar ping and measure the return time in 10 us units.
getPingTime:

pulsout sonarPin, 5
pulsin sonarPin, 1, wPingTime
return

' Calculate a frequency based on the value of wPingTime.
calcFreqFromPingTime:

' For this application, we want to generate frequencies at
' distances between approximately 6 and 36 inches from our
' sonar unit. This range corresponds to wPingTime values of
' ~90 to ~530 units or so. We want to play our lowest tone
' at 90 wPingTime units and our highest tone at 530 units,
' with intermediate tones distributed evenly along this range.
' No tones should be played for values outside of this range.

' We have 35 tones to play within a wPingTime range of 440
' units (530-90=440). If we divide this range evenly between
' our 35 tones, we get 12.57 wPingTime units per tone
' (440/35=12.57). This value represents our tone interval. In
' other words, for every 12.57 wPingTime units, our tone
' changes by 1.
'
' Therefore, to calculate the tone to play, given a wPingTime
' value, we just need to use the following equation.
'
' tone = (wPingTime - 90) / 12.57
'
' However, PicBasic Pro is incapable of floating point
' division so we need to scale our terms up by 100 in order
' to gain the necessary precision. The equation becomes...
'
' tone = (wPingTime - 90) * 100 / 1257
'
' In some cases, this scaling will result in intermediate
' values that exceed 16 bits. So in order to avoid data loss,
' we need to use the DIV32 operator for the division part
' of the equation. The final result is the equation below.

w32 = (wPingTime - 90) * 100
tone = div32 1257

' Send debug info to LCD.
SEROUT2 PortB.5, 16468, [254, 1, 254, 128] ' Clear LCD screen and reset cursor.
serout2 PortB.5, 16468, ["Ping: ", dec wPingTime] ' Output wPingTime value.
Serout2 PortB.5, 16468, [254, 192] ' Move cursor to next line.
SEROUT2 PortB.5, 16468, ["Tone: ", Dec tone] ' Output tone value.

' Set wFreq according to the calculated tone value.
select case tone
case 0
wFreq = lA
case 1
wFreq = lAS
case 2
wFreq = lB
case 3
wFreq = lC
case 4
wFreq = lCS
case 5
wFreq = lD
case 6
wFreq = lDS
case 7
wFreq = lE
case 8
wFreq = lF
case 9
wFreq = lFS
case 10
wFreq = lG
case 11
wFreq = lGS
case 12
wFreq = A
case 13
wFreq = As
case 14
wFreq = B
case 15
wFreq = C
case 16
wFreq = Cs
case 17
wFreq = D
case 18
wFreq = Ds
case 19
wFreq = E
case 20
wFreq = F
case 21
wFreq = Fs
case 22
wFreq = G
case 23
wFreq = Gs
case 24
wFreq = hA
case 25
wFreq = hAs
case 26
wFreq = hB
case 27
wFreq = hC
case 28
wFreq = hCs
case 29
wFreq = hD
case 30
wFreq = hE
case 31
wFreq = hF
case 32
wFreq = hFs
case 33
wFreq = hG
case 34
wFreq = hGs
case else
wFreq = 0
end select

return

' Output the tones.
doTones:

' Drop the frequency one octave by halving it.
TxWord = wFreq / 2

' Transmit the halved frequency to the slave PIC for output.
serout TxPin, SerCommMode, [SerCommSoT, TxWord.Lowbyte, TxWord.HighByte]

' Output the frequency.
hpwm 1, 127, wFreq

return

sonar_theremin_slave.pbp:

  1. Listens for a serial communication from the master microcontroller.
  2. Outputs the frequency received from the serial communication in step 1 above using hardware pulse width modulation.
  3. Repeat beginning at step 1 above.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
'****************************************************************
'* Name : sonar_theramin_slave.pbp *
'* Author : Odysseus, Hemingway, Nero *
'* Notice : Copyright (c) 2009, Section9.org *
'* Date : 8/25/2010 *
'* Version : 1.2 *
'****************************************************************

define OSC 4

' Define program sybmols.
symbol RxPin = PortB.7 ' Serial communication recieve pin.

' Define program constants.
SerCommMode con 6 ' Serial communication mode code.
SerCommSoT Con "*" ' Serial communication start of transmission code.

' Define program variables.
RxLowByte Var Byte ' Variable to store the 1st data byte of a serial communication.
RxHighByte var byte ' Variable to store the 2nd data byte of a serial communication.
wFreq var word ' Variable to store output frequency.

Main:

' Scan RxPin for RxStart code, then read data into RxLowByte and RxHighByte.
SerIn RxPin, SerCommMode, [SerCommSoT], RxLowByte, RxHighByte

' Move the two bytes into the wFreq word variable.
wFreq.Lowbyte = RxLowByte
wFreq.Highbyte = RxHighByte

' Output the recieved frequency.
hpwm 1, 127, wFreq

goto main

Once you have successfully programmed both microcontrollers, place them back into the circuit and turn it on. You can connect an LCD unit to port B5 of the master microcontroller to see the elapsed ping time and corresponding tone (second image below). You can also connect the audio output to an oscilloscope (third image below) to see the frequency being generated. Of course, you could plug in headphones or speakers to the headphone jack as well. Be careful using headphones. The volume can be quite loud if the volume potentiometer is not adjusted correctly.

[nggallery id=7]

Step 3: Prep the Enclosure

Although there is probably a much more efficient enclosure for a project like this, I went with something I could pick up at a local Radio Shack. For this step, just grab a sharpie and mark off the desired positions for each component. The exact layout is up to you. Just be sure that everything is spaced apart far enough to allow you to solder the leads. You’ll probably want to position the headphone jack near the top of the enclosure so that it will be flush against the top inner surface. This will allow you to crazy glue it in place. Note that I drilled the hole for the potentiometer latch on the wrong side. I had to go back and re-drill it afterwards to prevent the potentiometer leads from making contact with the circuit board.

Once you’ve got all the holes drilled to size (as shown in the third image below), mount the components onto the enclosure. Remember, you’ll have to crazy glue the headphone jack. The result should look something like image four below.

At this point you can solder the enclosure components (as shown in the fifth image below) or you can just wait to do it along with the rest of the soldering. I recommend doing it at this point to get warmed up for the smaller components. Note that there are 3 wires going to the ground terminal of the switch. Getting all three cleanly soldered can be a bit tricky. Don’t be afraid to crank up the watts on the iron and lay down a lot of solder, but don’t bridge the terminals either. Also try not to melt the insulation on your wiring.

Notice that the potentiometer is wired up as a voltage divider rather than just a variable resistor. Although you can use a variable resistance in series with an audio signal to control volume, it is much easier to just divide the voltage of the audio signal across the range of the potentiometer. With an audio taper potentiometer, this technique gives you a very effective and well-balanced volume control.

[nggallery id=8]

Step 4: Prep the Perfboard

I am almost too embarrassed to describe this stage of the construction. I will admit that some of these techniques are ugly. I guess that’s why they call it hacking. The problem I ran into was that I could not find a perfboard that fit cleanly into my chosen Radio Shack enclosure. I probably should have taken the time to select compatible components, but at the time I didn’t feel like placing another online order and so decided to make the best of what I had. In the end, I was able to make it work.

If you decide to use a perfboard and enclosure that are compatible, you can probably skip this step. If you don’t mind a little ugliness and want to use the same components I did, then grab a sharpie. First measure and mark the perfboard so that it will fit cleanly into the enclosure (image one below). Next, use a table saw or hacksaw to cut the perfboard down to size. This can be a bit tricky. Be careful not to fracture the board or strip off or break the copper strips. When you are finished your board should look like image two below and should fit cleanly into the enclosure as shown in image three below.

You may also want to cut out some routing tabs on each side of perfboard. This will allow you to route your power, ground, audio and sonar connections to the opposite side of the board. You can see these routing tabs in the images in the next section.

[nggallery id=10]

Step 5: Solder!

Alright, so you’ve made it to the soldering stage. This part is the most fun, but also the most prone to error. Just take your time and plan out the entire layout before you even begin to solder. The layout shown in image one below worked pretty well for me. If I had to do it over again, I think I would place the voltage regulator on the far left and move the PIC microcontrollers down toward the right a bit more. This would simplify the routing pattern of the sonar signal line and decrease it’s length as well. In the end all that matters is that the connections are solid and that everything fits safely into enclosure.

Notice that the power, ground and audio lines terminate with alligator clips. This is not necessary. You can certainly solder these lines directly onto their destination points; however, I found that using alligator clips makes pulling the circuit board out for maintenance or repair a whole lot easier. For example, I forgot to solder one wire while making this. When I turned it on to test it, it didn’t work. I had to pull out the board and go over several points with a multimeter before figuring out what the problem was. Being able to disconnect the board from the enclosure made troubleshooting much easier.

Also notice that the three sonar wires are connected to the sonar unit by means of a connector housing receptacle. Again, you could solder these connections directly, but being able to easily disconnect this component is very convenient. If you look closely you’ll see that the receptacle is a bit ugly. That’s because it originally was a 5 pin unit that I cut down to 3 pins. Additionally, this unit was not designed for the type/grade of wire I used with it and so I had to secure the connections with a few drops of solder. You can get a better receptacle for this connection from Jameco for only 15 cents.

Finally, you may want to pad the sonar unit with some foam or cardboard to prevent it from rattling around (image six below). I used some shipping foam that came in a box of IC’s. Don’t try to paint the foam and sonar unit black with sharpie ink like I did. It will only make an ugly mess.

When you’re finished, hook it all up inside the enclosure. The final result should look something like image seven below. Connect the power supply, plug in some headphones and turn it on. If it works, congratulations! If not, just go over your connections. Make sure everything is wired up according to the circuit schematic. Measure the ground and power rail voltages on both sides of the regulator. You should have 9-15 volts going into the regulator and 5 volts coming out. Verify that the microcontrollers are oriented correctly and that the appropriate pins are connected. Make sure that the master and slave microcontroller are seated firmly in the correct positions. Use the continuity function of a multimeter to check all connections for shorts. Also verify that your electrolytic capacitors are oriented correctly. If none of this helps and the device still does not work, you may have blown out the sonar unit or a microcontroller. Pull the suspect component out of the circuit and test it independently. Eventually you’ll figure it out, but feel free to email us with any questions.

[nggallery id=9]

Final Thoughts:

Much thanks to Nero and Hemingway for their “constructive criticism” and help in the research, design and prototyping stages. Please feel free to submit any questions or suggestions to Odysseus. There are currently no plans to improve the design of this project although there are several improvements that come to mind. Specifically, I think it would be cool to swap out the sonar unit with an IR unit. I’d also like to eliminate the master/slave architecture and use a single microcontroller to output multiple tones. Of course, the perfboard and enclosure components should be optimised and it would be nice to transform the square wave PWM output into a smoother waveform prior to the final output stage. Beyond these basic improvements, I can see a version 2.0 with a panel mounted mode switch that allows the user to dynamically apply specific filters/effects to the generated audio signal. We may try to implement some of these features some day. Who knows? I suppose it all depends on whim.

Welcome to the world of electronics!

[singlepic id=57 w=320 h=240]