Sunday 3 January 2010

Make your own annoying musical greetings card!

I love the tiny 8 pin PICs from Microchip.. an entire computer in a package the size of a fingernail that costs pennies and can be programmed from your PC using just C and run on a watch battery. They're great.. but its taken me a while to find a use for one.

This was a quick and silly project to play a tune on a piezo sounder. Hopefully the comments in the source code included below are enough to work out whats going on.. this was actually a great project to work out how to use timers and interrupts on PICs, which I'd not done before. There were a few little hoops to jump through to fit the melody data into the tiny EEPROM space (128 bytes) of the 12F629.



I used SourceBoost C and PICkit 2 USB programmer.


#include <system.h>

// config word; internal oscillator, watchdog and master clear are off
#pragma DATA _CONFIG, _INTRC_OSC_NOCLKOUT & _WDT_OFF & _MCLRE_OFF

//Set clock frequency
#pragma CLOCK_FREQ 4000000

// This is the tune data... high nybble of each byte is the relative duration (1-16) and the
// low nybble is the note (1-16) or a break (0). Note numbers are mapped to frequencies in
// code. A melody can use only 15 different notes in total and the total number of bytes that
// can be stored in EEPROM on a PIC12F629 is 128. There is a null terminator at the end of the
// melody data
#pragma DATA _EEPROM,
0x44, 0x20, 0x44, 0x10, 0x18, 0x16, 0x10, 0x14, 0x16, 0x10, 0x18, 0x1b, 0x10, 0x18, 0x16, 0x10,
0x14, 0x41, 0x20, 0x41, 0x10, 0x15, 0x13, 0x10, 0x11, 0x13, 0x10, 0x15, 0x17, 0x10, 0x15, 0x13,
0x10, 0x11, 0x44, 0x20, 0x44, 0x10, 0x18, 0x16, 0x10, 0x14, 0x16, 0x10, 0x18, 0x1b, 0x10, 0x18,
0x16, 0x10, 0x24, 0x10, 0x1b, 0x1a, 0x10, 0x19, 0x18, 0x10, 0x17, 0x16, 0x10, 0x15, 0x14, 0x20,
0x16, 0x20, 0x14, 0x10, 0x14, 0x16, 0x10, 0x17, 0x48, 0x20, 0x48, 0x10, 0x18, 0x19, 0x10, 0x1a,
0x1b, 0x10, 0x1a, 0x19, 0x10, 0x18, 0x17, 0x10, 0x16, 0x15, 0x10, 0x16, 0x17, 0x10, 0x16, 0x15,
0x10, 0x11, 0x12, 0x10, 0x13, 0x44, 0x20, 0x44, 0x20, 0x44, 0x20, 0x44, 0x20, 0x24, 0x1b, 0x1a,
0x10, 0x19, 0x18, 0x10, 0x17, 0x16, 0x10, 0x15, 0x34, 0x36, 0x44, 0x40, 0x00

typedef unsigned char byte;

// info used by the interrupt handler
byte next_tmr1h = 0;
byte next_tmr1l = 0;
byte wave = 0;

// interrupt handler called when the timer1 overflows
void interrupt( void )
{
// check if this is timer1 overflow event
if( pir1 & (1 << TMR1IF) )
{
// set up the timer 1 counters so they will overflow
// again after the appropriate time delay
tmr1h = next_tmr1h;
tmr1l = next_tmr1l;

// toggle pin state GPIO5, which drives the piezo sounder
wave=!wave;
gpio = wave? 0b100000 : 0b000000;

//clear timer 1 interrupt bit
clear_bit( pir1, TMR1IF );
}
}

void main( void )
{
// configure timer1 for interrupts / no prescaler
t1con=0;
intcon.6=1;
intcon.7=1;
pie1.0=1;
clear_bit( pir1, TMR1IF ); //clear timer 1 interrupt bit

// set up IO pins
trisio = 0;
gpio=0;

// loop forever
for(;;)
{
byte addr = 0;
byte data = 0;

// loop through the tune
for(;;)
{
// read byte from EEPROM
eeadr = addr;
eecon1.0 = 1;
data = eedata;
++addr;

// a zero byte indicates end of the tune
if(!data || addr > 0x7f)
break;

// extract the note number from the low nybble
byte note = data & 0x0f;

// play a note?
if(note)
{
// lookup corresponding frequency
long freq = 0;
switch(note)
{
case 1: freq=196; break; // G
case 2: freq=220; break; // A
case 3: freq=247; break; // B
case 4: freq=262; break; // C
case 5: freq=294; break; // D
case 6: freq=330; break; // E
case 7: freq=349; break; // F
case 8: freq=392; break; // G
case 9: freq=440; break; // A
case 10: freq=494; break; // B
case 11: freq=524; break; // C
}

// convert this into the correct timer count values
// Internal clock is 4MHz and Timer1 counts at 1/4
// of this frequency (1MHz). We need to call the
// interrupt handler at double the pitch frequency so
// that we can generate the 2 phases of the square wave
// pulse. What we calculate here is the initial 16 bit
// Timer1 value that will overflow (at 0xffff) after the
// appropriate period of time.
long l = (0xffff - (500000L/freq));
next_tmr1h = l >> 8;
next_tmr1l = l&0xff;
tmr1h = next_tmr1h;
tmr1l = next_tmr1l;
wave = 0;

// enable the timer (start sound)
t1con.0=1;
}
else
{
// disable the timer (stop sound)
t1con.0=0;
}

// duration is in top 4 bits. We'll just
// us an empty "for" loop to provide a delay
int dur = 70*(data >> 4);
for(int p=0;p<dur;++p)
{
// empty for loop for delay
for(byte q=1;q;++q);
}

}
}
}

1 comment:

  1. I know how to program in C but I don't understand the whole importing process onto the PIC >.<

    ReplyDelete