2014/04/19

Easter Egg the AVR way

For this Easter, I had something in my mind. A playing egg. A quick project to make an easter egg play a short tune.

To make that, I took an ATTiny10 microprocessor, a really tiny piece of programmable silicon. A small speaker I got some years ago probably from a defunct digital camera. Then, only a 3V CR2016 battery and two resistors were needed. And of course the egg.

Schematic of the Eggplayer.

I programmed the internal Timer/Counter0 on the ATTiny to change the frequency in such a way that it would play a note. To play a tune, I downloaded a midi file, a midi editor and extracted the chorus notes and put them into the ATTiny. It only has 1 kB of memory and each note takes 1 byte: 3 bits for length (1/32, to 4/1, can be changed) and 5 bits for frequency (nearly 3 octaves starting at C5 but it can be changed almost arbitrarily). Also one byte is needed for the BPM of the tune to correctly time the note duration. About half the memory is left for the notes, the other half is the program.

Circuit with CR2016 battery.

So I got a tune-playing ATTiny, powered by 3V battery. To trigger the tune, I used a phototransistor. When the light strikes the phototransistor, it conducts and changes the level at PB0 of the ATTiny. This wakes up the microprocessor from sleep mode and plays the tune. After that, it returns to sleep.
I attached the phototransistor on the top of the egg and put the circuit into an IKEA Egg cup. To block the light (and protect the circuit from NSA), I made a tinfoil hat for the egg (see the video). From cardboard, tinfoil and some hot-melt. 
Now when you remove the hat, the ambient light strikes the phototransistor, it conducts and changes level on the PB0 port. The tune plays and then stops until the next level change.

Etched face and the phototransistor, secured by some Kapton.

But to add some geekiness to this setup, I also tinkered with the egg: I am not graphically-talented but I am a chemist and do have some FeCl3 lying around. I was wondering if I could use it to etch a drawing. So I drawed a face on a hard-boiled egg with Tipp-Ex Correction Fluid and then immersed the egg in a 10 wt.% solution of FeCl3 for 10 minutes. It turned out it got etched a bit and after removing the correction fluid I got my drawing on the egg. A simple face and a simple circuit.

One egg, one cup. Circuit schematic is etched on the back of the egg.

If you are interested in the code, below is what I typed in Atmel Studio 6.2 into a single c file. The notes are only defined from C5 to C6 but this can be expanded with all 32 available notes. Watch the video before reading the code :-)



#include <avr/io.h>
#include <avr/interrupt.h>

#define CLK 1000000UL

#define C5period 478
#define C5speriod 451
#define D5period 426
#define D5speriod 402
#define E5period 379
#define F5period 358
#define F5speriod 338
#define G5period 319
#define G5speriod 301
#define A5period 284
#define A5speriod 268
#define B5period 253
#define C6period 239

#define PAUSE 0
#define C5 1
#define C5s 2
#define D5 3
#define D5s 4
#define E5 5
#define F5 6
#define F5s 7
#define G5 8
#define G5s 9
#define A5 10
#define A5s 11
#define B5 12
#define C6 13

#define BEAT32 0
#define BEAT16 32
#define BEAT8 64
#define BEAT4 96
#define BEAT2 128
#define BEAT1 160
#define BEAT0_50 192
#define BEAT0_25 224

#define BEAT32duration 66

void setup(void);
void delayms(uint16_t);
void play(uint8_t);
void setCounter(uint16_t);
void rickRoll(void);
void powerDown(void);

int main(void)
{
    setup();
    while(1)
    {
        powerDown();
        rickRoll();
    }
}

void setup(void)
{
    // enable global interrupts
    sei();
   
    // main clock
    CCP = 0xD8; // configuration change protection
    CLKMSR = 0; // internal 8 MHz oscillator
    CCP = 0xD8; // configuration change protection
    CLKPSR = 0b11; // 8x clock division
   
    // timer/counter0
    TCCR0A = (1 << COM0B0); // toggle on compare match
    TCCR0B = (1 << WGM02); // CTC mode
    TCCR0B |= (1 << CS00); // prescaler 1
   
    // i/o
    DDRB |= (1 << DDB1); // OCR0B port output
    PUEB |= (1 << PUEB2) | (1 << PUEB3); // pull-up on unused pins
   
    // interrupt
    PCICR = 1; // enable pin interrupt
    PCMSK = (1 << PCINT0); // pin 0 is active
}

void delayms(uint16_t ms)
{   
    uint16_t i;   
    uint8_t j;   
    for (i = 0; i < ms; i++)
    {
        for (j = 0; j < (CLK / 4000); j++)
        {
            asm("NOP");
        }
    }   
}

void play(uint8_t durationAndNote)
{
    switch (durationAndNote & 0x1F)
    {
    case PAUSE:
        setCounter(PAUSE);
        break;
    case C5:
        setCounter(C5period);
        break;
    case C5s:       
        setCounter(C5speriod);
        break;
    case D5:
        setCounter(D5period);
        break;       
    case D5s:
        setCounter(D5speriod);
        break;
    case E5:
        setCounter(E5period);
        break;
    case F5:
        setCounter(F5period);
        break;
    case F5s:
        setCounter(F5speriod);
        break;
    case G5:
        setCounter(G5period);
        break;
    case G5s:
        setCounter(G5speriod);
        break;       
    case A5:
        setCounter(A5period);
        break;
    case A5s:
        setCounter(A5speriod);
        break;
    case B5:
        setCounter(B5period);
        break;
    case C6:
        setCounter(C6period);
        break;
    }
    delayms(BEAT32duration << (durationAndNote >> 5));   
    setCounter(PAUSE);
    delayms(5);   
}

void setCounter(uint16_t val)
{
    OCR0AH = (val >> 8);
    OCR0AL = (val & 0xFF);
}

void rickRoll(void)
{   
    play(BEAT16 | C5);
    play(BEAT16 | D5);
    play(BEAT16 | F5);
    play(BEAT16 | D5);

    play(BEAT8 | A5);
    play(BEAT8 | A5);
    play(BEAT4 | G5);
    play(BEAT8 | PAUSE);
    play(BEAT16 | C5);
    play(BEAT16 | D5);
    play(BEAT16 | F5);
    play(BEAT16 | D5);

    play(BEAT8 | G5);
    play(BEAT8 | G5);
    play(BEAT8 | F5);
    play(BEAT16 | E5);
    play(BEAT8 | D5);
    play(BEAT16 | C5);
    play(BEAT16 | D5);
    play(BEAT16 | F5);
    play(BEAT16 | D5);
   
    play(BEAT4 | F5);
    play(BEAT8 | G5);
    play(BEAT8 | E5);
    play(BEAT16 | D5);
    play(BEAT4 | C5);
    play(BEAT8 | C5);
   
    play(BEAT8 | G5);
    play(BEAT8 | F5);
    play(BEAT4 | F5);
    play(BEAT4 | PAUSE);
    play(BEAT16 | C5);
    play(BEAT16 | D5);
    play(BEAT16 | F5);
    play(BEAT16 | D5);
   
    play(BEAT4 | A5);
    play(BEAT8 | A5);
    play(BEAT4 | G5);
    play(BEAT8 | PAUSE);
    play(BEAT16 | C5);
    play(BEAT16 | D5);
    play(BEAT16 | F5);
    play(BEAT16 | D5);
   
    play(BEAT4 | C6);
    play(BEAT8 | F5);
    play(BEAT8 | F5);
    play(BEAT16 | E5);
    play(BEAT8 | D5);
    play(BEAT16 | C5);
    play(BEAT16 | D5);
    play(BEAT16 | F5);
    play(BEAT16 | D5);
   
    play(BEAT4 | F5);
    play(BEAT8 | G5);
    play(BEAT8 | D5);
    play(BEAT8 | E5);
    play(BEAT16 | D5);
    play(BEAT4 | C5);
    play(BEAT8 | C5);
   
    play(BEAT4 | G5);
    play(BEAT4 | F5);
    play(BEAT2 | PAUSE);
}

void powerDown(void)
{
    DDRB &= !(1 << DDB1); // OCR0B port disable
    SMCR = (1 << SM1); // power-down mode
    SMCR |= (1 << SE); // sleep enable
    PRR = (1 << PRADC); // shut down adc
    asm("SLEEP");
}

ISR(PCINT0_vect)
{   
    SMCR &= !(1 << SE); // sleep disable
    DDRB |= (1 << DDB1); // OCR0B port output
}