Spoon Organ is an instrument that I created to show at the Make Tokyo Meeting 06 this past weekend. The user can play musical tunes simply by touching a row of spoons sitting on a table, with a fork added in for good measure. A microcontroller is used to detect changes in capacitance caused by a finger pressing against the metal, which are then sent to a computer using the MIDI protocol.
The project was conceived of and created while on Hackers on a Plane 4, using spare parts from the Make It Last Build Series, cables and connectors purchased in Akihabara (thanks, Akiba!), and soldering work completed at Tokyo Hackerspace. Here is a short video of it running:
Read on for a schematic and source code.
The schematic for the project is pictured above. I’m using the CTMU peripheral on the PIC controller to do the sensing; it’s pretty amazing and requires no extra parts to use any of ADC input pins as a touch sensor. I’m only using every other sensor, because early tests showed that there would be interference between the channels when used on a solderless breadboard. The spoons are connected to the board using shielded cable (nothing fancy, just some power supply cable), which I soldered to alligator clips on one end and breadboarding plugs (ok, just bits off the ends of resistors) to the other. The shield is grounded on the microcontroller side, and left unconnected on the other.
Here is the source code for the project (please note that it is a quick hack from another project, and includes lots of useless code):
///////////////////////////////////////////////////////////////////////////// // Include files // These lines allow us to use code routines (libraries) from other files, // so that we don't have to write everything by ourselves. ///////////////////////////////////////////////////////////////////////////// #include <p18lf25k22.h> // This file includes definitions for all of the // registers on our chip #include <stdio.h> // For the sprintf() function #define __18LF25K80 #include <i2c.h> ///////////////////////////////////////////////////////////////////////////// // Pragma statements // These lines tell the microcontroller what configuration to use when it // when it turns on. The most important part for now is to tell the // microcontroller what to use for a clock input. ///////////////////////////////////////////////////////////////////////////// // Use the internal oscillator as a clock #pragma config FOSC = INTIO67 // Use internal clock, don't output //#pragma config FOSC = INTIO7 // Use internal clock, output on RA6 // Configure it to run at 16 MHz #pragma config PLLCFG = OFF // Allow the program to turn on the watchdog timer. The watchdog is a // a special feature of the processor, that runs separately from the main // program and can be used to wake up the processor after a certain amount of time. #pragma config WDTEN = OFF // Set the watchdog timer prescaler to 1. #pragma config WDTPS = 256 // Fix a compiler bug #undef INTCON #undef INTCONbits ///////////////////////////////////////////////////////////////////////////// // Function declarations // Declare any user functions that you want to use here ///////////////////////////////////////////////////////////////////////////// void main (void); void setup(void); void loop(void); void receive_interrupt(void); unsigned char receiveCharacter = 0; // Stores the last character that was // received by the serial port unsigned char stayAwakeCount = 0; // 5 second countdown to keep the // chip from sleeping unsigned char measureSwitch(unsigned char channel); #define PINCOUNT 8 int lastState[PINCOUNT]; unsigned char offCount[PINCOUNT]; // Pins: // 2 4 7 14 21 23 25 15 unsigned char inputADC[PINCOUNT] = { 0, 2, 4,15,12, 8,11,16}; unsigned char inputPorts[PINCOUNT] = { 0, 0, 0, 2, 1, 1, 1, 2}; unsigned char inputPins[PINCOUNT] = { 0, 2, 5, 3, 0, 2, 4, 4}; unsigned char noteChannels[PINCOUNT] = { 0, 0, 0, 0, 0, 0, 0, 0}; //unsigned char noteKeys[PINCOUNT] = {49,51,54,56,58,61,63}; unsigned char noteKeys[PINCOUNT] = {49,51,53,54,56,58,60,61}; int Vread[PINCOUNT]; #define PRESSED 0 #define RELEASED 1 #define NOCHANGE 2 ///////////////////////////////////////////////////////////////////////////// // Function definitions // Define any user functions that you want to use here ///////////////////////////////////////////////////////////////////////////// // Configure the high interrupt to call the high_isr() function // From MPLAB C18 C Compiler Users Guide, Page 29 #pragma code low_vector=0x08 void low_interrupt (void) { _asm GOTO receive_interrupt _endasm } #pragma code /* return to the default code section */ // The main() function is where the program 'starts' when the microcontroller // is turned on. For this project, we will use it to call setup() and loop() // functions, then use those similar to an Arduino sketch. void main ( void ) { // Call the setup function once to put everything in order setup(); // Then call the loop() function over and over while (1) { loop(); } } // Function to write a character string to the serial port, from: // C:\MCC18\src\pmc_common\USART\u1puts.c void puts1USART( char *data ) { do { // Transmit a byte while(!TXSTA1bits.TRMT); TXREG1 = *data; // Write the data byte to the USART2 } while( *data++ ); } // Map function, from: // http://www.arduino.cc/en/Reference/Map long map(long x, long in_min, long in_max, long out_min, long out_max) { return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; } // Function to write a single byte to a device on the i2c bus void i2cWriteByte( char byte ) { // Send the first byte of address, and wait for ack SSPBUF = byte; // Wait for idle while ( ( SSPCON2 & 0x1F ) || ( SSPSTATbits.R_W ) ) continue; } // This function is called once, when the microcontroller is turned on. void setup( void ) { int i; // Processor configuration // Configure the oscillator to run at 16 MHz OSCCONbits.IRCF = 111; // 16 MHz // OSCCONbits.IRCF = 101; // 4 MHz ANSELA = 0; TRISA = 0; PORTA = 0; ANSELB = 0; TRISB = 0; PORTB = 0; ANSELC = 0; TRISC = 0; PORTC = 0; // ADC configuration ADCON2bits.ADFM=1; // Right Justified // Set the speed that the ADC should capture data // ADCON2bits.ACQT=0b110; // Acquisition time // ADCON2bits.ADCS=0b010; // Fosc/8 (?) ADCON2bits.ACQT=0b111; // Acquisition time ADCON2bits.ADCS=0b010; // Fosc/32 ADCON0bits.ADON = 1; // Turn the ADC on // Serial port configuration TRISCbits.TRISC6 = 0; // Make TX pin an output TRISCbits.TRISC7 = 1; // and RX pin an input ANSELCbits.ANSC7 = 0; // Specifically, an analog input // Configure the serial port to run at 9600 baud // (see manual, page 275) // for 16 MHz clock SPBRG1 = 25; TXSTA1bits.BRGH = 0; // Baud rate select BAUD1CONbits.BRG16 = 0; // Turn on the serial port RCSTA1bits.CREN = 1; // Enable receive mode on the serial port TXSTA1bits.TXEN = 1; // Enable transmitter RCSTA1bits.SPEN = 1; // Enable receiver // CTMU configuration CTMUCONH = 0x00; //make sure CTMU is disabled CTMUCONL = 0x90; //CTMU continues to run when emulator is stopped,CTMU continues //to run in idle mode,Time Generation mode disabled, Edges are blocked //No edge sequence order, Analog current source not grounded, trigger //output disabled, Edge2 polarity = positive level, Edge2 source = //source 0, Edge1 polarity = positive level, Edge1 source = source 0, CTMUICONbits.IRNG = 0x01; // .55uA current source // CTMUICONbits.ITRIM = 0b111111; // half power // Configure the pins we are going to use for(i = 0; i < PINCOUNT; i++) { lastState[i] = 0; offCount[i] = 0; switch(inputPorts[i]) { case 0: // A ANSELA = ANSELA | 1<<inputPins[i]; // TRISA = TRISA | 1<<inputPins[i]; break; case 1: // B ANSELB = ANSELB | 1<<inputPins[i]; // TRISB = TRISB | 1<<inputPins[i]; break; case 2: // C ANSELC = ANSELC | 1<<inputPins[i]; // TRISC = TRISC | 1<<inputPins[i]; break; } } } unsigned int logCount = 0; // Number of samples that the logger has acquired int logInterval = 10; // Number of seconds between measurements char logging = 0; // Specifies whether we are actively logging or not int intervalCounter = 0; // This function is called repeatedly int in; char buffer[100]; void loop(void) { unsigned char i; for( i = 0; i < PINCOUNT; i++) { //i = 1; in = measureSwitch(i); if (in != RELEASED) { offCount[i] = 0; } if (in == PRESSED && in != lastState[i]) { lastState[i] = in; // Note on buffer[0] = 0x90 + noteChannels[i]; buffer[1] = noteKeys[i]; buffer[2] = 127; buffer[3] = 0x00; puts1USART(buffer); // sprintf(buffer,(const rom far char *)"TOUCH_ON channel=%hu value=%i\r\n\x00", i, Vread[i]); // puts1USART(buffer); } else if (in == RELEASED && in != lastState[i]) { if (offCount[i] < 3) { offCount[i]++; } else { lastState[i] = in; offCount[i] = 0; // Note off buffer[0] = 0x80 + noteChannels[i]; buffer[1] = noteKeys[i]; buffer[2] = 127; buffer[3] = 0x00; puts1USART(buffer); } // sprintf(buffer,(const rom far char *)"TOUCH_OFF channel=%hu value=%i\r\n\x00", i, Vread[i]); // puts1USART(buffer); } } } // This function is called whenever a high interrupt occurs. For the // datalogger project, this only happens when the EUSART (serial port) // receives a character. #pragma interruptlow receive_interrupt void receive_interrupt (void) { // Read the character in from the serial module. If the serial port just // woke up the microcontroller, this will be garbage. receiveCharacter = RCREG; // If we just woke up from sleeping, discard the first character because // it is probably corrupted if (stayAwakeCount == 0) { receiveCharacter = 0; } // Start a counter to wait 5 seconds before sleeping, in order to give // the user time to send a serial command stayAwakeCount = 5; } // PIC 18lf22 datasheet, page 326 #define COUNT 150 //@ 16MHz = 125uS. #define DELAY for(i=0;i<COUNT;i++) {} /* #define OPENSW 1000 //Un-pressed switch value #define TRIP 200 //Difference between pressed //and un-pressed switch #define HYST 65 //amount to change */ #define TRIPOFF 1000 // #define TRIPON 800 //from pressed to un-pressed unsigned char measureSwitch(unsigned char channel) { int i, j; switch(inputPorts[channel]) { case 0: // A TRISA = TRISA | 1<<inputPins[channel]; break; case 1: // B TRISB = TRISB | 1<<inputPins[channel]; break; case 2: // C TRISC = TRISC | 1<<inputPins[channel]; break; } ADCON0bits.CHS = inputADC[channel]; // ADCON0bits.ADON = 1; // Turn the ADC on DELAY; //assume CTMU and A/D have been setup correctly //see Example 25-1 for CTMU & A/D setup CTMUCONHbits.CTMUEN = 1; // Enable the CTMU CTMUCONLbits.EDG1STAT = 0; // Set Edge status bits to zero CTMUCONLbits.EDG2STAT = 0; CTMUCONHbits.IDISSEN = 1; //drain charge on the circuit DELAY; DELAY; CTMUCONHbits.IDISSEN = 0; //end drain of circuit CTMUCONLbits.EDG1STAT = 1; //Begin charging the circuit //using CTMU current source DELAY; //wait for 125us CTMUCONLbits.EDG1STAT = 0; //Stop charging circuit PIR1bits.ADIF = 0; // Make sure adc interrupts are disabled ADCON0bits.GO=1; //and begin A/D conv. while(!PIR1bits.ADIF); //Wait for A/D convert complete Vread[channel] = ADRES; //Get the value from the A/D CTMUCONHbits.CTMUEN = 0; // Disable the CTMU switch(inputPorts[channel]) { case 0: // A TRISA = 0; break; case 1: // B TRISB = 0; break; case 2: // C TRISC = 0; break; } if(Vread[channel] < TRIPON) { // sprintf(buffer,(const rom far char *)"TOUCH_ON channel=%hu value=%i\r\n\x00", channel, Vread[channel]); // puts1USART(buffer); return PRESSED; } else if(Vread[channel] > TRIPOFF ) { // sprintf(buffer,(const rom far char *)"TOUCH_OFF channel=%hu value=%i\r\n\x00", channel, Vread[channel]); // puts1USART(buffer); return RELEASED; } else { // sprintf(buffer,(const rom far char *)"NO_CHANGE channel=%hu value=%i\r\n\x00", channel, Vread[channel]); // puts1USART(buffer); return NOCHANGE; } } |
I like it, can you bring it with you to Detroit.
Pingback: El órgano cuchara
Pingback: Spoon Organ | Lega Nerd
That is weird but cool. I like it! And those little kids are cute. Does that mean that you traveled to Tokyo??? Because if you did that is doubly cool.
Thanks! Yeah, I just got back from a 18 day trip to Japan, and we stayed in Tokyo and Osaka (and visited a bunch of other places as well). I made up the spoon organ while on the trip
You made it up while traveling?! Okay I think that increases your coolness level by another degree. How was your trip and what did you get to do? Lee thinks it would be neat to go to Japan.
Pingback: Spoon Organ, Modulate Holiday Gift Sound Pack, Interview: Music Production Guru and Violinist Laura Escudé, Free Korg Legacy MS-20 Patches, Jethroe Dub DJ FX, Free DIY Techno/Minimal
I can attest to the fact that cibomahto was in Tokyo and did use cheap silverware for his organ. Wait, that sounds kind of strange…
Anyways, it was fun hanging out with you and hearing about the goings-on in the Make community in the US. I feel sad that I’m missing everything over there.
And for the spoon organ (+1 fork), it was a big hit at the Make JP event and people were trying to play songs on it. I’m hoping the next version will have spatulas for the organ pedals