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 <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <avr/wdt.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#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 <avr/io.h>
#include <avr/interrupt.h>
#include <inttypes.h>
#include <avr/sleep.h>
#include <util/delay.h> 

#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

2 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