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







Bravo Pat,
J’aime le résultat!!! Et ton choix de l’arbre pour le dessin.
Cool !
Dominick
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