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; } } }
Take a look at this for some help with a simple approach to the math: http://asuwlink.uwyo.edu/~hamann/TrilatShow.pdf
ReplyDelete@JesseL: Thanks for the link.. I had seen that article before, but it seemed to me that it needed the distance/signal travel time from each sensor to the source to be known absolutely, rather the relative to the time the first signal arrives at any sensor (which is what I will have). I will read through it again and see if I got it wrong
ReplyDeleteIt looks like a very complicated project to build.
ReplyDeleteCan you send me a larger picture? This is a nice project, i will do a Self test with the pieco Sensor. Many thanks and regards
ReplyDeleteHere is my Adresse: raiffeisenstr38@gmail.com
ReplyDeleteThank you