I thought it was about time to give an update on the MIDI ping pong table sensing stuff. I've spent quite a lot of time on it, been through a few iterations of the hardware, and learned a lot! I started this as a total newbie as far as analog electronics go (Op-amps etc) and now I think I have at least grasped a few basic techniques. I'd like to try to share the lessons I learned, but I'll warn you now that you'll need to be quite interested in this stuff to get through this post :o)
Going back to the start, my first sensing attempt was to use piezo sensors and an unbuffered inverter (4069) as an amplifier (an idea picked up from Nicholas Collins' book 'Handmade Electronics Music'). I fed the amplified signal into a Darlington transistor which I hoped would give be the logic "hard edge" signal I needed for my timing code. The result was very sensitive, however there were a few things going on that I didn't understand (sometimes the output of the transistor seemed to oscillate without any input). At the time I seemed to be picking up a lot of electrical noise and became convinced the amplifier circuit needed to be closer to the piezo sensor.
My second iteration used a proper op-amp (An LM358 configured for single supply) which was placed close to the piezo sensor. The circuit was still very sensitive but the "noise" problems thankfully seemed to have gone away. I tried feeding the output of the op-amp to a digital input pin on a PIC but I was finding that the when a pulse was detected the digital input would trigger not just once, but many times over microsecond timescales and this would cause problems for the code (e.g. pin change interrupt fires but the pin state does not seem to have changed)
At the time I didn't fully understand the reason for the digital input state flipping about like this and could only assume it was some kind of high frequency harmonic component of the actual vibration being picked up (now I know better... see below). However I needed to do something about it so that my microcontroller code would have a decent length pulse it could detect reliably. I added a 555 circuit configured as a "retriggerable monostable" (this was something I read about online). Basically once the first ON pulse come into the 555, no matter how fleeting, the 555 will hold the output HIGH for a controllable timed period, so all the spurious follow-on pulses are masked and we don't need to care about them. The output became nice and clear for the interrupt pins on the MCU and worked reliably.
So by now I had an LM358 and a 555, plus a bunch of caps, diodes and resistors, that I wanted located right up close to the piezo sensor. I decided to make the circular surface mount boards shown in a previous blog post. I suspected at the time they might be a bit over-engineered, but as long as they worked I didn't care.
Then I started focusing on the triangulation code and noticed occasional inconsistencies in the sensor timing readings, which I became convinced were due to the differential input levels at which each sensor fired. This makes sense because the "front" of the sound rippling out over a table top is not a hard vertical edge, but rather a slope, with the vibrational displacement ramping up from zero to to its maximum as the wave passes under the sensor. The level at which the sensor "triggers" will depend on its own sensitivity level, but not all 4 sensors will have exactly the same trigger levels. Now, since the speed of sound in dense wood is very high, a small difference in trigger level might translate to a big difference in the calculated position. Damn!
I was helped here by Matt Waterman, who got in touch with me via my blog and suggested using zero crossing detection as a solution to this. My understanding here is that once the initial wave front passes the sensor and triggers it, the vibrational displacement will reach a positive peak, then fall down to to zero, then reach a negative trough before repeating. At some point the displacement it crosses the "zero" value, and this is a distinct point in time which can be detected without the same sensitivity difference issues (since we're comparing positive level with negative level, not two different positive levels, this can be detected more accurately). Since the positive wave front should have the same front-to-back width across all the sensors, the zero-crossing time measurement should be just as suitable for my purposes as a hypothetical perfect measurement of the arrival time of the wave front (becaise we're working with relative time of arrival at the sensors, not the absolute times of arrival)
To work with zero crossing I had to remove the 555 from the circuit (since this masked all the input after the initial wave front triggers the sensor, including the initial zero crossing point). I added an LM339 comparator configured for zero crossing detection (based again on internet research), so the Arduino needed to receive digital inputs from the initial pulse (The Op-amp output) and the zero crossing pulse (the Comparator output). The first zero crossing trigger after the wave front trigger would be the point in time we'd be interested in. With the 555 gone, I was of course back with the issues of the crazy pulse trains that it had been hiding from me... Double Damn!
After more headscratching, Googling, breadboarding and staring at my old CRT oscilloscope and a USB logic analyzer, I tried putting a Schmitt trigger inverter IC in there and Hey Presto!... a perfect clean pulse.
Now if I'd done this before I would have reached for the Schmitt trigger IC right at the start, but this is all a learning curve, and this was one of the most useful things I learned: When forcing an analog level to a digital level use a Schmitt Trigger! All I'd ever used them for was to make oscillators (like Ray Wilson's excellent WSG noisebox)
So how does the Schmitt trigger IC help? Well a trace I got from my USB logic analyzer showed what was going on... Think about an analog sine wave being forced to a digital pulse wave. Now, if you simply connected the analog level to a digital pin you'd expect that above a certain threshold the digital level is HIGH and below the threshold the level is LOW right? This is mostly true, but the problem is that round about the threshold level, on the up or down slope, the digital value goes crazy... it flips *really* fast between HIGH and LOW because its in a "grey area" - it really doesn't know if it should be HIGH or LOW and tiny fluctuations and noise push it one way and the other. This goes on until the input voltage changes enough to get out of this fuzzy "no-mans land" range and we get a stable HIGH or LOW
However, stick a Schmitt trigger in there and there now there are TWO thresholds - one for the rising level and one for the falling level. In the no-mans land between the two, the output of the Schmitt trigger does not change. The result is a nice clean LOW - HIGH - LOW transition. The grey area is gone.
With a Schmitt trigger, the zero crossing worked! And it worked even with a long wire between the Piezo sensor and the op-amp, so I could put all the electronics on a single board and for all sensors. Not only that, but the direct Schmitt trigger output was now working just as well for timing as the zero crossing comparator output! Removing the comparator now simplifies the whole thing and halves the number of digital inputs needed to read the sensors. Result!
So, after that long journey I learned quite a lot. If you managed to read this far I hope it helps you too. I would also say never underestimate the value of an oscilloscope - even a battered old CRT one like mine. For digital stuff a cheap USB storage logic analyser (I used a SCANLOGIC built from a 40 Euro kit) is also invaluable for seeing whats going on in that crazy world of the microsecond timescale.
So, my design now reads 8 piezo sensors (4 for each corner on side of a ping pong table). The sensors are simply wired with screened cable to a central amp box. This has 4 LM358 dual op amps (one channel per sensor) which are now on a 5V dual supply (from a nice board from Futurlec that generates a stable and noise-free bipolar supply from a single input supply). The op amp outputs go through a pair of 74HC14 hex Schmitt trigger inverters and from there to digital inputs of an Arduino Nano.
The Nano uses interrupt on pin change to simply flag the change of pin state, so that a flag can be polled instead of the port input (in case there is a rapid change and a signal is missed). There are two state machines running timing impulses from each side of the table, and the conversion of time-of-arrival information to coordinates is done using the fast approximation approach I described before.
The Nano drives a set of diagnostic LEDs - 8 green LEDs representing the sensors and 2 red LEDs which light when there is a misread (not all sensors firing together on that side of table). The coordinate is mapped into an 8x8 grid and sent out as MIDI using the following encoding method:
MIDI note = 16 * row + col
where row and col are 0..7
The two sides of the table are split into two different MIDI channels. These are to be fed into a second Arduino which does some processing of the raw notes to create a more interesting set of MIDI notes and controllers based on some "intelligence" about gameplay (e.g a "rally" is made up of notes alternating between 2 sides of the table and finishes when there are 2 bounces on the same side or no bounce for a timeout period). The MIDI output from the second Arduino will be used to drive a synthesizer to create the eventual sound of the game.
The note mapping scheme described above is (deliberately) the same as that used in the Novation Lanunchpad. Using MIDI-OX to route MIDI inputs to outputs, this means I can use a pair of Launchpads as a handy display to indicate the detected posiiton of a bounce. It also allows me to use a pair of Launchpads in place of the table while testing the MIDI engine on the second Arduino. I can just alternate pressing buttons on each Launchpad and it things there is a rally going on!