Spoon Organ


Everyone likes Spoon Organ!

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;
    }
}
This entry was posted in Ridiculous, tech. Bookmark the permalink.

8 Responses to Spoon Organ

  1. Mark says:

    I like it, can you bring it with you to Detroit.

  2. Pingback: El órgano cuchara

  3. Pingback: Spoon Organ | Lega Nerd

  4. Robin J. says:

    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. :)

    • mahto says:

      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 :-)

      • Robin J. says:

        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.

  5. 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

  6. Akiba says:

    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 :)

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>