Musical ping-pong tables and 2d multilateration

I’m currently trying to help out a local artist who is building an Interactive Ping-Pong Table, where each bounce of the ball generates a musical sound which depends on the position of the bounce.

I thought it should be possible to do this without drastically changing the table (i.e. without chopping the surface up) by using 3 piezo disks and an Arduino or PIC to time the arrival of the pulse at each disk and work out the position of the ball. It all sounded pretty easy, and an interesting project. Its certainly been interesting, but I’ll think twice in future before deciding something is easy before I’ve properly thought  it through :o)

I found pretty quickly that some kind of amplification is needed.. the piezo disks are pretty sensitive to a sounds close by but not so great for something the other end of a table. First of all I tried to boost the level using 4069 inverter chips (I got that idea from Nicholas Collins’ book – Handmade electronic music) since I’ve never really understood op amps and didn’t want to get into all that dual supply rubbish. In my initial circuit I used an NPN Darlington transistor on the output of the amplifier stage to generate the logic pulse.

It kind of worked, but I was finding that the MCU would hang when an interrupt-on-change interrupt was being fired by multiple sensors. I also had a problem with the output getting stuck on (I think this might have been due to supply noise, noise picked up on a long wire to the piezo, and an overly sensitive amp stage). I  think the hang thing might have been due to noisy outputs triggering a rapid train of interrupts than the poor PIC could not handle. I have an IKA Logic analyser and using this I could see a mad train of pulses coming from sound waveform, echoes, supply noise whatever… I don’t really know, but the PIC didn’t like it.

Searching about for ideas online I read about running op-amps like LM358 from a single supply, which seemed to be a better way to do things than using logic chips as amps. I also saw how a 555 monostable circuit can be used to clean up a dirty pulse by keeping an output high for a timed period as soon as the first edge of the input pulse comes in, so the train of pulses from reverberations and so on get masked by a nice clean extended output pulse… nice and friendly for MCU interrupt pins.

The resulting circuit seems to work pretty well, even though it still seems a bit complicated. Maybe it is a case of over-engineering, but I learned a lot and it does at least work pretty well. Using SMDs I can also get it on a board about the same size as the piezo disk so it can sit on top.

For some reason I thought the maths behind working out a point from timing would be easy..and it is in one dimension with 2 sensors…

However working in 2 dimensions with 3 sensors seems to be a completely different kettle of fish… the technique is called Multilateration and there have been entire research papers written about it :o) The problem is that all the timing readings you’re working with are relative… its more complicated than I  thought to get back to an actual position. Maybe I can simplify things, since my sensors will be arranged in the corners of a rectangular area and I can always calibrate them at the start by tapping the corners of the table. Or maybe some dirty trial and error approach will be good enough… Anway thats the next step… wish me luck..!

Here is the source code used in this clip

// SOURCEBOOST C
// PIC16F688
#include <system.h>
#pragma DATA _CONFIG, _MCLRE_OFF&_WDT_OFF&_INTRC_OSC_NOCLKOUT
#pragma CLOCK_FREQ 8000000

#define SENSEA 0b00010000
#define SENSEB 0b00100000
#define SENSE_MASK (SENSEA|SENSEB)

typedef unsigned char byte;

// INITIALISE SERIAL PORT FOR MIDI
void init_usart()
{
pir1.1 = 1; //TXIF transmit enable
pie1.1 = 0; //TXIE no interrupts

baudctl.4 = 0; // synchronous bit polarity
baudctl.3 = 1; // enable 16 bit brg
baudctl.1 = 0; // wake up enable off
baudctl.0 = 0; // disable auto baud detect

txsta.6 = 0; // 8 bit transmission
txsta.5 = 1; // transmit enable
txsta.4 = 0; // async mode
txsta.2 = 0; // high baudrate BRGH

rcsta.7 = 1; // serial port enable
rcsta.6 = 0; // 8 bit operation
rcsta.4 = 0; // enable receiver

spbrgh = 0; // brg high byte
spbrg = 15; // brg low byte (31250)
}

enum {
READY,
LISTENING,
TIMING,
TIMEOUT
};

byte remaining;
long timeA;
long timeB;
byte state;

void interrupt( void )
{
// check for interrupt on change
if(intcon.0) // IOCA fired
{
// are any of the signals we're waiting
// for now ready for us?
byte savePortA = porta;
byte whichSensor = savePortA & remaining;
unsigned long thisTime;
if(whichSensor)
{
if(state == LISTENING)
{
// start the timer
t1con.0 = 1;
thisTime = 0;
state = TIMING;
}
else
{
// grab the current time
thisTime = tmr1h << 8 | tmr1l;
}

// Grab times from sensors
if(!!(whichSensor & SENSEA))
timeA = thisTime;
if(!!(whichSensor & SENSEB))
timeB = thisTime;

// clear bits for the sensors we
// already have
remaining &= ~savePortA;
if(!remaining)
{
intcon.3 = 0; // ioca off
state = READY;
}
}

// clear interrupt fired flag
intcon.0 = 0;
}
}

////////////////////////////////////////////////////////////
// SEND A MIDI BYTE
void send(unsigned char c)
{
txreg = c;
while(!txsta.1);
}

////////////////////////////////////////////////////////////
// NOTE MESSAGE
void sendNote(byte channel, byte note, byte value)
{
send(0x90 | channel);
send(note&0x7f);
send(value&0x7f);
}
void main()
{
// osc control / 8MHz / internal
osccon = 0b01110001;


// comparator off
cmcon0 = 7;

// configure io
trisa = SENSE_MASK;
trisc = 0b00000000;
ansel = 0b00000000;
porta = 0b00000000;
portc = 0b00000000;

// initialise MIDI comms
init_usart();

//
t1con = 0b00000000;

// interrupt on change porta.4
ioca = SENSE_MASK;
intcon.7 = 1;
intcon.3 = 0;
intcon.0 = 0;

byte note = 0;
for(;;)
{
// Prepare to listen
timeA=0xffff;
timeB=0xffff;
remaining = SENSE_MASK;
state = LISTENING;
t1con.0 = 0; // reset the timer
tmr1h=0;
tmr1l=0;
intcon.3 = 1; // ioca on

// wait to start timing
while(LISTENING == state);

// wait to complete timing
while(TIMING == state)
{
unsigned long timeNow = tmr1h << 8 | tmr1l;
if(timeNow > 0x8000)
state = TIMEOUT;
}

if(TIMEOUT == state)
{
// ignore the interrupt if it does
// not register on all the sensors
}
else
{
long x=0;
// i know i'm getting reading of up to 5000 'cos I printed
// them to serial port... you might get something different
if(timeA > 5000)
timeA = 5000;
if(timeB > 5000)
timeB = 5000;
if(timeA)
x = 5000 + timeA;
else if(timeB)
x = 5000 - timeB;
if(x)
{
note = x/100; // is is in range 0-10000 so move this to MIDI range 0-100
sendNote(0, note, 127);
}
}

// delay (I think delay_ms function needs timer1)
int i=1000;
while(++i);
if(note)
{
sendNote(0, note, 0);
note=0;
}
}
}
This entry was posted in News. Bookmark the permalink.

Comments are closed.