singing birds

10 watts amplifier with built-in 16-bit / 48kHz RIFF-WAVE player (music on a SD-CARD). 2 servos for controlling the beaks. I did this project mostly to learn stuff. The result is a bit stupid.

VIDEO:

PHOTOS:

Schematic & Board (Kicad)
How to filter PWM for music

sd player source code

/*---------------------------------------------------------------*/
/* 8-pin SD audio player R0.02                     (C)ChaN, 2009 */
/*---------------------------------------------------------------*/

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "diskio.h"
#include "pff.h"

char strbuff[10];
unsigned int position = 0;
unsigned long count = 0;
unsigned int nbwave = 0;

#define FCC(c1,c2,c3,c4)	(((DWORD)c4<<24)+((DWORD)c3<<16)+((WORD)c2<<8)+(BYTE)c1)	/* FourCC */


/*---------------------------------------------------------*/
/* Work Area                                               */
/*---------------------------------------------------------*/

volatile BYTE FifoRi, FifoWi, FifoCt;	/* FIFO controls */

BYTE Buff[256];		/* Wave output FIFO */

FATFS fs;			/* File system object */
DIR dir;			/* Directory object */
FILINFO fno;		/* File information */

WORD rb;			/* Return value. Put this here to avoid bugs of avr-gcc */



EMPTY_INTERRUPT(WDT_vect);


/*---------------------------------------------------------*/

static
DWORD load_header (void)	/* 0:Invalid format, 1:I/O error, >1:Number of samples */
{
	DWORD fcc, sz;
	UINT i;
	FRESULT res;


	res = pf_read(Buff, 256, &rb);		/* Load file header (256 bytes) */
	if (res) return 1;

	if (rb != 256 || LD_DWORD(Buff+8) != FCC('W','A','V','E')) return 0;

	i = 12;
	while (i < 200) {
		fcc = LD_DWORD(&Buff[i]);	/* FCC */
		sz = LD_DWORD(&Buff[i+4]);	/* Chunk size */
		i += 8;
		switch (fcc) {

		case FCC('f','m','t',' ') :		/* 'fmt ' chunk */
			if (sz > 100 || sz < 16)				/* Check chunk size */
				return 0;
			if (Buff[i+0] != 1)						/* Check coding type (1) */
				return 0;
			if (Buff[i+2] != 1 && Buff[i+2] != 2)	/* Check channels (1/2) */
				return 0;
			GPIOR0 = Buff[i+2];		/* Channel flag */
			if (Buff[i+14] != 8 && Buff[i+14] != 16)	/* Check resolution (8/16) */
				return 0;
			GPIOR0 |= Buff[i+14];	/* Resolution flag */
			OCR0A = (BYTE)(F_CPU / 8 / LD_WORD(&Buff[i+4])) - 1;	/* Sampling freq */
			break;

		case FCC('f','a','c','t') :		/* 'fact' chunk (skip) */
			break;

		case FCC('d','a','t','a') :		/* 'data' chunk (start to play) */
			fs.fptr = i;
			return sz;

		default :						/* Unknown chunk (error) */
			return 0;
		}
		i += sz;
	}

	return 0;
}



static
UINT play (
	const char *fn
)
{
	DWORD sz;
	FRESULT res;
	BYTE sw;
	WORD btr;


	if ((res = pf_open(fn)) == FR_OK) {
		sz = load_header();			/* Load file header */
		if (sz < 256) return (UINT)sz;

		if (!TCCR1) {				/* Enable audio out if not enabled */
			PLLCSR = 0b00000110;	/* Select PLL clock for TC1.ck */
			GTCCR =  0b01100000;	/* Enable TC1.OCB as PWM out (L-ch) */
			OCR1B = 128; OCR1A = 128;
			TCCR1 = STEREO ? 0b01100001 : 0b00000001;	/* Start TC1 with TC1.OCA is enabled as PWM out (R-ch) */
			TCCR0A = 0b00000010;	/* Enable TC0.ck = 2MHz as interval timer */
			TCCR0B = 0b00000010;
			TIMSK = _BV(OCIE0A);
		}

		FifoCt = 0; FifoRi = 0; FifoWi = 0;		/* Reset FIFO */
		pf_read(0, 512 - fs.fptr, &rb);			/* Snip sector unaligned part */
		sz -= rb;

		sw = 1;		/* Button status flag */
		do {
			/* Forward audio data */
			btr = (sz > 1024) ? 1024 : (WORD)sz;
			res = pf_read(0, btr, &rb);
			if (res != FR_OK || btr != rb) break;
			sz -= rb;
			/* Check button down and break on button down */
			sw <<= 1;
			//if (bit_is_clear(PINB, 0) && ++sw == 1) break;
			if (bit_is_clear(PINB, 0) && ++sw == 1) {
				
				count = 0;
				
				do { 
					count++;
					wdt_reset();
				} while (bit_is_clear(PINB, 0));
				
				if(count > 500000) {
					position--;
					position--;
				}
				
				break;
				
			}
			wdt_reset();
		} while (rb == 1024);	/* Repeat until all data read */
	}

	while (FifoCt) ;			/* Wait for FIFO empty */
	OCR1A = 128; OCR1B = 128;

	return res;
}

long smallrandom(long howbig)
{
  if (howbig == 0) {
    return 0;
  }
  return random() % howbig;
}

long rangerandom(long howsmall, long howbig)
{
  if (howsmall >= howbig) {
    return howsmall;
  }
  long diff = howbig - howsmall;
  return smallrandom(diff) + howsmall;
}

int main (void)
{
	PORTB = 0b111011;		/* Initialize port: - - H H H L H P */
	DDRB  = 0b111110;
	
	sei();
	
	//RANDOM (SEED)
	unsigned long seednumber = 1;
	unsigned char i;
	ADCSRA |= (1 << ADPS2) | (1 << ADPS1) | (0 << ADPS0);  //ADC Prescalar set to 64 - 125kHz@8MHz
	ADMUX |= (1 << ADLAR);
	ADCSRA |= (1 << ADEN);  // Enable ADC
    for(i = 0; i < 100; i++) {
		wdt_reset();
		ADCSRA |= (1 << ADSC);  // Start A2D Conversions
		while (ADCSRA & (1 << ADSC)); 
		seednumber = seednumber + ADCH;
	}
	ADCSRA |= (0 << ADATE);
	ADCSRA |= (0 << ADEN);  // Disable ADC
    ADCSRA |= (0 << ADSC);  // Stop A2D Conversions
	
	MCUSR = 0;
	WDTCR = _BV(WDE) | 0b110;	/* Enable WDT reset in timeout of 1s */

	BYTE res;
	//mount
	do {
		res = pf_mount(&fs);
		wdt_reset();
		seednumber++;
	} while (res != FR_OK);
	
	Buff[0] = 0;
	do {
		res = pf_opendir(&dir, (char*)Buff);
		wdt_reset();
		seednumber++;
	} while (res != FR_OK);
	
	//nb files
	for (;;) {
		res = pf_readdir(&dir, &fno);
		if (res != FR_OK || fno.fname[0] == 0) break;
		wdt_reset();
		nbwave++;
		seednumber++;
	}
	
	srandom(seednumber);
	//start with a random position
	position = rangerandom(1, nbwave + 1);

	for (;;) {
		
		//limit
		if(position > (nbwave + 1)) {
			position = 1;
		} else if(position < 1) {
			position = 1;
		}
		
		//play
		sprintf(strbuff,"%d.wav", position);
		play(strbuff);
		
		//increment song
		position++;

	}
}

servo source code

#include 
#include 
#include 
#include 
#include  

#define F_CPU 8000000
#define nop()  __asm__ __volatile__("nop")

void wait100us(void) {
		unsigned char i;
		for(i = 0; i < 100; i++) {
			nop();
		}
}

void Delay_ms(unsigned int t)

  {
   unsigned int i, aika;
   aika = 140;

   while(t--)
    for(i = 0; i < aika; i++)

       if(t==0) return;

  } 

void adcinit (void) {

	    // PRESCALER 128
	    ADCSRA |= (1 << ADPS2) | (1 << ADPS1) | (0 << ADPS0);

	    // AUTO TRIGGER ENABLE
	    ADCSRA |= (1 << ADATE);

	    // VCC ref
	    ADMUX |= (0 << REFS1) | (0 << REFS0);

	    // RIGHT ADJUST
	    ADCSRB |= (0 << ADLAR);

	    // CHANNEL 0
	    ADMUX |= (0 << MUX5) | (0 << MUX4) | (0 << MUX3) | (0 << MUX2) | (0 << MUX1) | (1 << MUX0);

	    // FREE RUNNING MODE
	    ADCSRB |= (0 << ADTS2) | (0 << ADTS1) | (0 << ADTS0);

	    // ENABLE ADC
	    ADCSRA |= (1 << ADEN);

		// PORT A: INPUT
		DDRA = 0x00;
		PORTA = 0x00;


}

void pwminit (void) {
		// disable PWM while configuring it
		TCCR1A = 0;

		// used for TOP, makes for 50 hz PWM = (8,000,000 / (8 * 50)) - 1
		ICR1 = 19999;

		// servo pins outputs
		DDRA = _BV(PA5) | _BV(PA6);

		// set up counter 1 (16 bit) to act as a dual channel PWM generator
		// comparator mode = non-inverting, use ICR1 as TOP and have a prescale of 8.
		TCCR1A = _BV(COM1A1) | _BV(COM1B1) | _BV(WGM11);
		TCCR1B = _BV(WGM13) | _BV(WGM12) | _BV(CS11);
}

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;
}

int main() {

	adcinit();
	pwminit();
    sei();

    // START A2D
    ADCSRA |= (1 << ADSC);

	while(1) {
		
		//700 to 2400
		//but here we need only 50 degress
		OCR1A = map(ADC, 0, 1023, 700, 1100);
		ADMUX |= (1 << MUX1);
		ADMUX &= ~(1 << MUX0);
		Delay_ms(2);
		OCR1B = map(ADC, 0, 1023, 700, 1100);
		ADMUX |= (1 << MUX0);
		ADMUX &= ~(1 << MUX1);
		Delay_ms(2);
	}
    return(0);
}

13

04 2010

0 Comments Add Yours ↓

  1. Dominick #
    1

    Bravo Pat,
    J’aime le résultat!!! Et ton choix de l’arbre pour le dessin.

    Cool !

    Dominick

  2. 2

    Wow! Super. Très bon résultat, je trouve. Un VU-Meter en bec d’oiseau. :) En fait, j’ai eu exactement la même idée il y a 2-3 ans, mais je ne l’ai jamais fait! (moi, je voulais en faire une marionnette) Le faire, c’est autre chose ! Bravo à toi et ta persévérance. :)

    Alex



Your Comment