banner

Tuesday, February 28, 2012

TV remote controller 160KHz High Quality Stereo MMC WAV player using ATMEGA32



(updated the complete source code + makefile + hex + asm + .out files on bitbucket repository) 
link is provided below the source code
Introduction:
This is my first AVR based hobby project and the most successful one compared to my all previous stuff. I am 100% satisfied with this work.. Few months ago, I tried to make a wav player using a PIC16F877A. It worked anyway, but the audio quality was not so good for higher sampling rate because that chip doens't have enough ram and thus I couldn't implement a good data buffer. But when I bought an atmega32 microcontroller, the first thing came to my mind is to make a good wav player...Now, I have completed my work and the audio quality is really amazing...

 NOW I can say that, my wav player IS ABLE TO PLAY 8 BIT MONO/STEREO with maximum bitrate of 1300kbps for mono and 1600kbps for stereo ... ie it can play an 8 bit mono wav of sampling frequency upto 160KHz and stereo upto 96KHz  without any noise or trouble!!!!! (at OSC 16.450MHz).


Note:
[The full range of human hearing is between 20 Hz and 20 kHz. The minimum sampling rate that satisfies the sampling theorem for this full bandwidth is 40 kHz. The 44.1 kHz sampling rate used for Compact Disc was chosen for this and other technical reasons.   High-end audio equipment, such as in SACD or DVD-Audio players or studio equipment, can reach as high as 192 kHz.]

 Now I believe this is a perfect 8 bit stereo wav player. Because I compared the same converted files played in my PC and in my player, but I couldn't find any difference in the playback quality. You can see it on the video (it is taken through my mobile phone, so the recorded audio may not be as good as the real one). Any way,  since the PWM have some limitations, I also tried the R2R ladder DAC... But I couldn't find any more improvement because it is already at it's top quality... Also, I think since I converted MP3 to WAV, so even if I increase the sampling rate in the conversion process, just for testing the maximum capacity of the player, but that couldn't help me to find the difference of PWM and R2R because the original audio is having a sampling rate of only 44.1KHz... So I am not going for an R2R dac and instead I am using the PWM itself...
Another interesting feature of my player is that, it could be controlled with a Philips TV remote (RC5 protocol). Also I had implemented few functions on the remote control, ie NEXT SONG, PREVIOUS SONG,  PAUSE, PLAY, FORWARD 15 SECONDS, and some funny effects like PLAY SPEED INCREMENT and DECREMENT. Also I am still trying to include more features on the remote... The player is now a beta_version ;-)....

Also, I didn't used any external FAT libraries for the low level MMC accessing, instead I had written my own code for that... So you may not be able to find any standard 'C' FILE operations on my source code....

Updated photo after testing an R-2R lader DAC... [01/03/2012]
New code and circuit diagram for wav stereo player with R2R DAC will be updated soon....
My R2R DAC...


 (Pictures of the mmc module taken through a high resolution camera :-))


 Features:
  • TV remote control to control the player remotely.
  • Hi quality audio output
  • Maximum bit rate supported - 144 kilobytes/second.
  • Stereo support
  • Automatic repeat from the top after all the songs are played.
  • Additional bitrate adjustment on remote ( << , default, >> ).
  • Forwarding (fwd) option while playing. (seconds could be set on a macro).
(more features will be added on next update)

A brief explanation about the working:

 MMC card initialization, reading, writing and interfacing are already explained on my previous post http://vinodstanur.blogspot.com/2011/07/attempt-to-access-memory-card-mmc-using.html , so I think no need to repeat in again. Any way, in short words, MMC/SD cards are to be initialized by proper commands and it enters to working mode (SPI mode) only if it is initialized successfully.
So, here I used a 16x2 lcd to display many things. At first, it displays error message (if any) while trying to initialize the mmc. When it is initialized successfully, it shows MMC INITIALIZED message on lcd..
Now,  the next step is to check the boot sector of the MMC card (sector 0) to check if it contains a FAT16 file system. For this, we need to read the sector 0 of MMC card to a buffer. Here I used a 512 byte buffer. From the boot sector data, we could see what file system is there in the MMC card. My code is only for FAT16, so if I found it is not FAT16, then it display an error ie NOT A FAT16. If it found FAT16, then it reads few more data from the buffer and calculates sector number for the data start, fat start and root directory start. Also it detects the sectors per clusters. Each sector is 512 bytes. These four data is required for the further activities on MMC/SD card. So those are stored as global variables.
      Now the next step is to get into the root directory. (MMC commands and the sector reading is covered in my previous post as linked above). Now we need to load the first sector of root directory into the 512 byte buffer. Then, each root directory entry is of 32 byte (in general for 8.3 file name format). Each entry contains details of a file or folder in the root directory. From there we can read the file name, file attribute, actual file starting address (cluster address) and many more... We are interested in the file name extension (WAV), the file attribute and the file starting cluster address. So we compare the extension with the string "WAV" and if it matches, then we return the cluster address of the wav file.

      Now we could read the first cluster (a group of sectors, size depends on the size of MMC/SD). We could find the sector address from the cluster number using as equation.(u can see that on my code). Now after reading and playing all the sectors in the first cluster, (playing the data will be explained after this) then we need to find the next cluster number of the same file. A file may not be distributed on the memory as one section. Instead, it can be splitted into parts and placed here and there to utilize the free memory effectively.... (actually this happens only when there are some deleted files and we add new files to the MMC/SD). So we can't predict that the next cluster of the file will be successive numbers... But all the cluster order for each file is perfectly tracked on a linked list called the FAT...(File Allocation Table). Each cluster number have a unique position on the FAT. We already calculated the FAT starting address. From the FAT, we could get the next cluster number of the file. Since it is a linked list, the 16 bit data present on the 1st cluster number position will be the second cluster number. Now after reading the second cluster, we check which is the third cluster by checking the data on the location of second cluster position,,,This continues until we read a 0xffff from a location on FAT... This denotes the end cluster for the particular file..
             We have first cluster number and we calculated the sector starting address of the particular cluster number. Now we read the data from first sector  of the file and from there we could get the bitrate, sample rate, number of channel (stereo, mono) and many more.. We take the bit rate and use it to set the timer interrupt frequency... Now the timer interrupt is generated according to the bitrate and channel number. Now on each timer interrupt, an 8 bit data is introduced to the OCR register of Timer PWM module. Accordingly it generate PWM signal in background without any CPU resource. This PWM signal could be easily demodulated with an  RC filter. If the capacitor value increases or resistor value decreases ,then it will affect the audio quality ie it may filter out some higher frequency components of the audio and may feel it like hearing some thing from an AM MW radio..:-)...So care must be given while choosing RC..
             Now, we know, if we use a single buffer for both playing data and collecting data, then there will be a small contuinity problem while the song is played.... So, it will be really irritating if we are hearing our fav music like that... Thus, here I had implemented two special 512 bytes buffer for audio data only. This buffer is filled and played by a special technique.. ie when one buffer is playing (used inside timer interrupt) , the other buffer will be filling.,, This buffers will be exchanged alternatively... By this technique, I could obtain a pure uninterrupted high quality audio .... Thats all about the working ..............;-)

Now, decoding the TV remote is a simple process, ie using a timer interrupt, we sample the incoming signal on each 1778 us. To sample the data at mid point of the first half of the manchester code, I made a small delay of about 400ms from the time zero (ie the time when the start bit is detected)...After that the timer is activated to generate interrupt flag on each 1778us. So Now u can check my ISR(INT2_vect) code to see how RC5 is decoded. Also, check my previous code about an RC5 decoder which shows the structure of RC5 code. Other wise a simple google image search for "RC5 structure" will show the required data....

I had implemented RC5 decoding on the same Atmega32. But I think it is not a good method. Becuase, it will be inside the Timer ISR for more time while playing the file. So, most time, RC5 external interrupt will be triggered when the processor is handling the Timer interrupt. Then the external interrupt will be handled only after that and this results an invalid start bit detection inside the rc5 interrupt handler and that will be treated as an invalid RC5 signal. So if we are lucky enough, the first keypress itself will do the job, else we need to press the key for a while or retry after key release... But probably, it will work with in 1 or 2 keypress... So at present I think, the best method is to decode the RC5 outside the atmega32 and send the value via a serial interface for a better performance..
Circuit Diagram[wav player with PWM output]:


Wave structure:

       The WAVE file format is a subset of Microsoft's RIFF specification for the storage of multimedia files. A RIFF file starts out with a file header followed by a sequence of data chunks. A WAVE file is often just a RIFF file with a single "WAVE" chunk which consists of two sub-chunks -- a "fmt " chunk specifying the data format and a "data" chunk containing the actual sample data.
































As an example, here are the opening 72 bytes of a WAVE file with bytes shown as hexadecimal numbers:
 
52 49 46 46 24 08 00 00 57 41 56 45 66 6d 74 20 10 00 00 00 01 00 02 00 
22 56 00 00 88 58 01 00 04 00 10 00 64 61 74 61 00 08 00 00 00 00 00 00 
24 17 1e f3 3c 13 3c 14 16 f9 18 f9 34 e7 23 a6 3c f2 24 f2 11 ce 1a 0d  


Source code[for WAV player with PWM output]: 
/*
TV REMOTE CONTROLLED HIGH QUALITY MMC WAV PLAYER USING ATMEGA32

By Vinod S <vinodstanur@gmail.com> <http://blog.vinu.co.in>

First release Date: 25/02/2012
Last update Date:   02/03/2012 
       [improved maximum bitrate support to 1600kbps for stereo and 1300 for mono]

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

*/

#include<avr/io.h>
#define F_CPU 16450000
#include <util/delay.h>
#include <avr/interrupt.h>
#include <string.h>

#define SECONDS_TO_FORWARD 15
#define RS PD6
#define EN PD5
#define LCD_NIBBLE PORTC
#define LINE1 cmd(0x80)
#define LINE2 cmd(0xc0)
#define LEFT_SWITCH  PIND&(1<<2)
#define RIGHT_SWITCH PIND&(1<<3)
#define SWITCH_EVENT PIND&((1<<3)|(1<<2))
#define CS 4
#define RC5_LED PB0

unsigned char readdata;
unsigned int count;
unsigned long int arg = 0;
unsigned char mmc_buf[512];
unsigned char mmc_buf0[512];
unsigned char mmc_buf1[512];
unsigned int fat_start, dir_start, data_start;
unsigned char sect_per_clust;
volatile unsigned char BUF1_EMPTY, BUF0_EMPTY = 0;
unsigned long int OCR1A_BACKUP;
register int ISR_i asm("r2");
register char STEREO asm("r4");
register char TOGGLE_BUFFER asm("r5");
unsigned long int bitrate;
unsigned int RC5_DATA;
char RC5_FLAG;
unsigned int OCR1A_ADJUST;
unsigned int STARTING_CLUSTER;

void LCD_STROBE(void);
void data(unsigned char);
void cmd(unsigned char);
void clear(void);
void lcd_init();
void string(char *, char);
void spi_init();
void spi_write(char);
unsigned char spi_read();
void command(unsigned char, unsigned long int, unsigned char);
char mmc_init();
void mmc_read_sector(unsigned long int);
void fat16_init();
void print_num(unsigned long int, char);
unsigned int scan_root_dir(unsigned char *,char [], char);
void play_cluster(unsigned int);
unsigned int find_next_cluster(unsigned int);
void pwm_init();
void mmc_read_double_buffer(unsigned long int, unsigned char []);
void timer1_init();
char check_bitrate_and_stereo(unsigned int);
void INT2_init();
unsigned int forward_seconds(unsigned int);
void pull_up_enable();
void init_RC5_valid_indicator_LED();
int main()
{
    unsigned char fname[12];
    unsigned int cluster;
    char NEXT_OR_PREVIOUS = 1;
    
    _delay_ms(50);
    spi_init();
    lcd_init();
    pull_up_enable();
    while(mmc_init());
    pwm_init();
    fat16_init();
    timer1_init();
    INT2_init();
    init_RC5_valid_indicator_LED();
    while(1) {
        while((cluster = scan_root_dir("WAV", fname, NEXT_OR_PREVIOUS)) == 0) {
            NEXT_OR_PREVIOUS = 1;
        }
        NEXT_OR_PREVIOUS = 1;
        LINE1;
        clear();
        string(fname,1);
        if(!check_bitrate_and_stereo(cluster)) {
            TOGGLE_BUFFER = 0;
            BUF0_EMPTY = 1;
            ISR_i = 0;
            sei();
            while(cluster != 0xffff) {
                play_cluster(cluster);
                cluster = find_next_cluster(cluster);
                if(SWITCH_EVENT) {
                    if(RIGHT_SWITCH) {NEXT_OR_PREVIOUS = 1;break;};
                    if(LEFT_SWITCH) {NEXT_OR_PREVIOUS = 0; break;};
                }
                if(RC5_FLAG) {
                    RC5_FLAG = 0;
                    if(RC5_DATA == 32) {NEXT_OR_PREVIOUS = 1;break;};
                    if(RC5_DATA == 33) {NEXT_OR_PREVIOUS = 0;break;};
                    if(RC5_DATA == 1) {OCR1A = (OCR1A_ADJUST += OCR1A_BACKUP/20);};
                    if(RC5_DATA == 3) {OCR1A = (OCR1A_ADJUST -= OCR1A_BACKUP/20);};
                    if(RC5_DATA == 2) {OCR1A = (OCR1A_ADJUST = OCR1A_BACKUP);};
                    if(RC5_DATA == 6) {cluster = forward_seconds(cluster);}
                    if(RC5_DATA == 5) {cluster = STARTING_CLUSTER;}
                }
            }
            cli();
            clear();
            _delay_ms(100);
        }
    }
    return 0;
}

unsigned int forward_seconds(unsigned int cluster)
{
    cli();
    unsigned long int clusters_to_forward;
    clusters_to_forward = ((bitrate / 512) * SECONDS_TO_FORWARD) / sect_per_clust;
    while(clusters_to_forward) {
        cluster = find_next_cluster(cluster);
        if(cluster == 0xffff) break;
        clusters_to_forward--;
    }
    sei();
    return cluster;
}
void INT2_init()
{
    DDRB &= ~(1<<PB2);
    GICR   |=(1<<INT2);
    sei();
}

ISR (INT2_vect)
{
    char i = 0;
    //TIMSK &= ~(1 << OCIE1A);
    RC5_DATA = 0;
    OCR1A = ((double)F_CPU/1000000)*1778;
    _delay_us(350);
    TCNT1 = 0;
    for(i = 0; i < 13; i++) {
        while(!(TIFR & (1 << OCF1A)));
        TIFR |= 1<<OCF1A;
        RC5_DATA <<= 1;
        if(PINB & (1<<PB2)) {
            _delay_us(10);
            if(PINB & (1<<PB2)) {
                _delay_us(10);
                if(PINB & (1<<PB2)) {
                    RC5_DATA++;
                }
            }
        }
    }
    if((RC5_DATA & 0b1111101100000000) != 0b0000001100000000) {
        OCR1A = OCR1A_ADJUST;
        return;
    }
    PORTB |= (1<<RC5_LED);
    RC5_DATA &= 0b111111;
    RC5_FLAG = 1;
    OCR1A = OCR1A_ADJUST;
    TIMSK |= (1 << OCIE1A);
    _delay_ms(500);
    PORTB &= ~(1<<RC5_LED);
    GIFR |= (1<<INTF2);
}

char check_bitrate_and_stereo(unsigned int cluster)
{
    int i;
    mmc_read_sector(((unsigned long int)(cluster -2) * sect_per_clust) + data_start);
    if(mmc_buf[34] != 8) return 1;
    for (i = 31; i > 27; i--) {
        bitrate <<= 8;
        bitrate |= mmc_buf[i];
    }
    STEREO = mmc_buf[22] - 1;
    print_num(bitrate,2);
    OCR1A_BACKUP = ((F_CPU *(STEREO + 1))/bitrate);
    OCR1A = OCR1A_ADJUST = OCR1A_BACKUP;
    return 0;
}

unsigned int find_next_cluster(unsigned int cluster)
{
    unsigned int cluster_index_in_buff = (2 * (cluster % 256));
    mmc_read_sector(fat_start + cluster/256);
    return  ((mmc_buf[cluster_index_in_buff + 1] << 8) + mmc_buf[cluster_index_in_buff]);
}



void mmc_read_double_buffer(unsigned long int sector, unsigned char a[])
{
    int i;
    sector *= 512;
    command(17, sector, 0xff);
    while (spi_read() != 0);
    while (spi_read() != 0xfe);
    for(i = 0; i < 512; i++)
    a[i] = spi_read();
    spi_write(0xff);
    spi_write(0xff);
}

void play_cluster(unsigned int cluster)
{
    unsigned long int sector;
    int i, j;
    sector = ((unsigned long int)(cluster -2) * sect_per_clust);
    sector += data_start;
    for(i = 0; i < sect_per_clust; i++) {
        while((!BUF1_EMPTY) && (!BUF0_EMPTY));
        if(BUF0_EMPTY) {
            mmc_read_double_buffer(sector, mmc_buf0);
            BUF0_EMPTY = 0;
            } else if(BUF1_EMPTY) {
        mmc_read_double_buffer(sector, mmc_buf1); BUF1_EMPTY = 0;}
        sector += 1;
    }
}

ISR (TIMER1_COMPA_vect)
{
    if(STEREO) {
        if(TOGGLE_BUFFER == 1) {
            OCR0 =  mmc_buf0[ISR_i++];
            OCR2 =  mmc_buf0[ISR_i++];
            }else{
            OCR0 =  mmc_buf1[ISR_i++];
            OCR2 = mmc_buf1[ISR_i++];
        }
        if(ISR_i == 512) {
            if(TOGGLE_BUFFER)
            BUF0_EMPTY = 1;
            else
            BUF1_EMPTY = 1;
            TOGGLE_BUFFER ^= 1;
            ISR_i = 0;
        }
        } else {
        if(TOGGLE_BUFFER == 1)
        OCR0 = OCR2 = mmc_buf0[ISR_i++];
        else
        OCR0 = OCR2 = mmc_buf1[ISR_i++];
        if(ISR_i == 512) {
            if(TOGGLE_BUFFER)
            BUF0_EMPTY = 1;
            else
            BUF1_EMPTY = 1;
            TOGGLE_BUFFER ^= 1;
            ISR_i = 0;
        }
    }
}

void timer1_init()
{
    TCCR1B |= (1 << WGM12)|(1 << CS10);
    TCNT1 = 0;
    OCR1A = 10000;
    TIMSK |= (1 << OCIE1A);
}

unsigned int scan_root_dir(unsigned char *FILE_EXTENSION, char FNAME[], char UP_DOWN)
{
    while(1) {
        unsigned int i;
        static unsigned char read_end = 0;
        static int base_count = -32, sect_plus = 0;
        if(UP_DOWN == 1) {
            base_count += 32;
            if(base_count == 512) {base_count = 0; sect_plus += 1;};
            } else {
            base_count -= 32;
            if(base_count == -32) {base_count = (512 - 32); sect_plus -= 1;}
            if(sect_plus < 0) {sect_plus = 0; base_count = 0;}
        }
        while(1) {
            mmc_read_sector(dir_start + sect_plus);
            while(base_count < 512) {
                if(mmc_buf[base_count] == 0) { read_end = 1; break;}
                if ((mmc_buf[1] != 0) && (mmc_buf[base_count + 2] != 0) && (mmc_buf[base_count] != 0xe5) && (mmc_buf[base_count] != 0x00) && ((mmc_buf[base_count + 11] & 0b00011110) == 0) && (strncmp(mmc_buf + base_count + 8, FILE_EXTENSION, 3) == 0)) {
                    for(i = 0; i < 11; i++)
                    FNAME[i] = mmc_buf[base_count + i];
                    FNAME[11] = 0;
                    return (STARTING_CLUSTER = (unsigned int)((mmc_buf[27 + base_count] << 8) + mmc_buf[26 + base_count]));
                }
                if(UP_DOWN) base_count += 32;
                else base_count -= 32;
            }
            base_count = 0;
            sect_plus++;
            if(read_end) { base_count = -32; sect_plus = 0; read_end = 0; return 0;}
        }
    }
}

void print_num(unsigned long int i, char line)
{
    char u = 0;
    unsigned char lcd_buf[16];
    if(line == 1) cmd(0x80);
    else cmd(0xc0);
    while(i) {
        lcd_buf[u++] = (i % 10 + '0');
        i /= 10;
    }
    while(u) data(lcd_buf[--u]);
}

void fat16_init()            //BOOT SECTOR SCANNING//
{
    mmc_read_sector(0);
    clear();
    LINE1;
    if((mmc_buf[0x36] == 'F') && (mmc_buf[0x39] == '1') && (mmc_buf[0x3a] == '6'))
    string("FAT16 DETECTED",1);
    else {
        string("NOT A FAT16",1);
        while(1);
    }
    _delay_ms(500);
    fat_start = mmc_buf[0x0e];
    dir_start = (fat_start + (((mmc_buf[0x17] << 8) + mmc_buf[0x16]) * 2));
    data_start = (dir_start + ((((mmc_buf[0x12] << 8) + (mmc_buf[0x11])) * 32) / 512));
    sect_per_clust = mmc_buf[0x0d];
}

void mmc_read_sector(unsigned long int sector)
{
    int i;
    
    sector *= 512;
    command(17, sector, 0xff);
    while (spi_read() != 0);
    while (spi_read() != 0xfe);
    for(i = 0; i < 512; i++)
    mmc_buf[i] = spi_read();
    spi_write(0xff);
    spi_write(0xff);
}

char mmc_init()
{
    int u = 0;
    
    PORTB |= 1<<CS;
    for (u = 0; u < 50; u++) {
        spi_write(0xff);
    }
    PORTB &= ~(1<<CS);
    _delay_ms(1);
    command(0, 0, 0x95);
    count = 0;
    while ((spi_read() != 1) && (count < 1000))
    count++;
    if (count >= 1000) {
        string("CARD ERROR-CMD0 ",1);
        _delay_ms(500);
        return 1;
    }
    command(1, 0, 0xff);
    count = 0;
    while ((spi_read() != 0) && (count < 1000)) {
        command(1, 0, 0xff);
        count++;
    }
    if (count >= 1000) {
        string("CARD ERROR-CMD1 ",1);
        _delay_ms(500);
        return 1;
    }
    command(16, 512, 0xff);
    count = 0;
    while ((spi_read() != 0) && (count < 1000))
    count++;
    if (count >= 1000) {
        string("CARD ERROR-CMD16",1);
        _delay_ms(500);
        return 1;
    }
    string("MMC INITIALIZED!",1);
    _delay_ms(500);
    SPCR &= ~(1<<SPR1); //increase SPI clock from f/32 to f/2
    return 0;
}


void command(unsigned char command, unsigned long int fourbyte_arg, unsigned char CRCbits)
{
    spi_write(0xff);
    spi_write(0b01000000 | command);
    spi_write((unsigned char) (fourbyte_arg >> 24));
    spi_write((unsigned char) (fourbyte_arg >> 16));
    spi_write((unsigned char) (fourbyte_arg >> 8));
    spi_write((unsigned char) fourbyte_arg);
    spi_write(CRCbits);
    spi_read();
}

unsigned char spi_read()
{
    SPDR = 0xff;
    while(!(SPSR & (1<<SPIF)));
    return SPDR;
}

void spi_write(char cData)
{
    SPDR = cData;
    while(!(SPSR & (1<<SPIF)));
}

void spi_init()
{
    DDRB |= (1<<5)|(1<<7)|(1<<4);
    SPCR = (1<<SPE)|(1<<MSTR)|(1<<CPOL)|(1<<CPHA)|(1<<SPR1);
    SPSR = 1;
}

void LCD_STROBE(void)
{
    PORTD |= (1 << EN);
    _delay_us(1);
    PORTD &= ~(1 << EN);
}

void data(unsigned char c)
{
    PORTD |= (1 << RS);
    _delay_us(50);
    LCD_NIBBLE = (c >> 4);
    LCD_STROBE();
    LCD_NIBBLE = (c);
    LCD_STROBE();
}

void cmd(unsigned char c)
{
    PORTD &= ~(1 << RS);
    _delay_us(50);
    LCD_NIBBLE = (c >> 4);
    LCD_STROBE();
    LCD_NIBBLE = (c);
    LCD_STROBE();
}

void clear(void)
{
    cmd(0x01);
    _delay_ms(5);
}

void lcd_init()
{
    DDRC = 0x0f;
    DDRD |= (1 << RS)|(1 << EN);
    _delay_ms(15);
    cmd(0x30);
    _delay_ms(1);
    cmd(0x30);
    _delay_us(100);
    cmd(0x30);
    cmd(0x28);
    cmd(0x28);
    cmd(0x0c);
    clear();
    cmd(0x6);
}

void string(char *p, char line)
{
    if(line == 1) LINE1;
    else LINE2;
    while(*p) data(*p++);
}

void pwm_init()
{
    TCCR0|=(1<<WGM00)|(1<<WGM01)|(1<<COM01)|(1<<CS00);
    TCCR2|=(1<<WGM20)|(1<<WGM21)|(1<<COM21)|(1<<CS20);
    DDRB|=(1<<PB3);
    DDRD|=(1<<PD7);
}

void pull_up_enable()
{
    PORTB |= (1<<PB2);
}
void init_RC5_valid_indicator_LED()
{
    DDRB |= (1<<RC5_LED);
}

download source code + hex + asm + object + makefile from below link
repo

Screen shots:





Development tools:
I am using avr-gcc in linux.
We need avr-gcc, binutils-avr & avr-libc to be installed before trying to build the hex file. These are available via synaptic package manager.
Also, I am using avrdude package to burn the avr, hardware used is usbasp. So the makefile is made according to that... We can also use a simple parallel port burning circuit but in my case I don't have a parallel port in my lap, so I stick with the usbasp. It contains an atmega8 microcontroller programmed with the hex available at usbasp homepage.

here are the photos of my usbasp programmer, made according to the circuit diagram and firmware provided at the usbasp home page.



You could download the repo from the my bitbucket link.. (posted just below the source code).

How to make?
cd into the directory where the Makefile and c file is moved.
just need to type "make" to build the hex

How to burn it using usbasp?
Just need to type "sudo make burn_hex"

How to burn fuse bits?
Just need to type "sudo make burn_fuse"

How to clear the build?
Just need to type "make clean"

How to build & burn it without using Makefile?
avr-gcc -mmcu=atmega32 -O2 main.c
avr-objcopy -j .text -j .data -O ihex a.out a.hex
sudo avrdude -c usbasp -p m32 -U flash:w:a.hex:i
sudo avrdude -c usbasp -p m32 -U lfuse:w:0xef:m

Finished......;-)
Fuse bits: LFUSE = 0b11101111


UPDATES:

[2/3/2012]
Updated source code: 
1> Modified the frequently used normal global variables INT_i ,TOGGLE_BUFFER and STEREO to register variables. This increased the maximum bitrate support since the access time of these variables are reduced inside timer ISR...
2>    Modified the MMC initialization function to return 1 if not success and 0 if success. Then mmc initialization function is called in a while loop until it return 0. Now, if no MMC is inserted, it waits for MMC and start playing automatically when we insert an MMC with FAT16 and 8bit wav files :-)
3> Also I modified the speed optimization to -O2 and this really improved the capacity...


If needed, we can try to UN-LOOP the for(i=0;i<512,i++) a[i]=spi_read(); and this will improve the quality little more. But any way, since that improvement is very small and not able to measure the difference, so I am not updating it....

39 comments:

mm167 said...

very nice!!

Goebish said...

Nice project, what are the fuse settings used ?

Vinod.S said...

fuse bits are now added at the bottom of the post...

CircuitsDIY said...

So, you're running Atmega32 at 16.450Mhz. Isn't that overclocking for the device as it's rated for 16Mhz max.

Vinod.S said...

Yes it is overclocking...

But it doesn't makes any problem any way because this .450MHz is nothing compared to the 16MHz.
Also I don't have a 16MHz crystal with me....

Ca$h said...

Hey,
This is indeed a very nice project. However, I've a very question, which may be pretty off-topic.

How do you set the fuse bits with the usbasp programmer? I've bought 2X usbasp programmer built out from the same source as you but both doesn't detects the chip at all when the chip is working at 1Mhz internal clock.
I've to just visit my college and set the fuse to use external crystal using universal programmer. Later on, the usbasp programmer works like charm.

Is your usbasp programmer capable of setting fuse of BRAND NEW ATMEGA's?
If so, could you please share the firmware & circuit on how YOURS in exactly made?

Thanks.

Vinod.S said...

ACTUALLY in UABASP programmer, u need to short two wires (as specified in the usbasp circuit diagram) to make it work with low frequency clock...

Then u can set the fuse bits to external xtal etc....

Vinod.S said...

sorry its not UABASP, but USBASP ;-) (typing error)

Ca$h said...

Crap, I see the slow sck now in the circuit diagram..:$
Many thanks for guiding..:D

Goebish said...

Hi, there's something I don't understand:
Why do #define F_CPU 12000000 but use a 16.450MHz crystal ?
If I want to use a 16MHz crystal, should I #define F_CPU to 11671732 ?

Vinod.S said...

No it is a mistake, I forgot to update it..
Now I just modified it...

Goebish said...

Ok, thank you, I'm trying this circuit but don't have 16.450MHz crystals handy ... Well, I know my ATmega32A can support up to 32MHz anyway ;)

Goebish said...

Do not ground D0-D3 on the LCD, or it won't work, don't ask me why I know ;)

Vinod.S said...

I never used to ground it in practical... But I don't think it will make problem if we ground it....;-)

Goebish said...

Mine didn't work with D0-D3 grounded, took a few hours to figure out why it wouldn't initialize.
Now I got another problem, it keeps saying my SDCARD is not FAT16, but it is... Well, going to debug that...

Vinod.S said...

In my code, it assumes that the card doesn't have MBR and no partitions.... It is expecting boot sector at sector 0....

Also, hope U have done the required modicication to support it with SD...

Goebish said...

My bad, I forgot to read the beginning of the post. My SDCARD initialize without problem, but I understand why it says it's not FAT16 now, because I've a MBR at sector 0.
Going to try to modify the program to handle that, shouldn't be too hard and it will add a bit of fun :)
Thank you for the explanation !

Goebish said...

Got it to work, I just had to use a ugly trick: as my boot sector is located at sector #129 I just added
sector+=129;
at the beginning of mmc_read_sector()
Now going to write a real MBR parser ...

Thanks again !

Vinod.S said...

nice ..;-)

miguel-ito said...

Hi. Amazing project. Two Question. Would work on atmega8 uc? and ... I can use a 2 GB micro SD card?

Vinod.S said...

I want to update the code to support SD cards.. I will update it soon....

It will not work on atmega8. Because here I used two 512 bytes buffer for the perfect audio quality at high bit rates like 160KHz.... So the atmega8 RAM will not be enough for this.... (but u can modify it to adapt it with atmega8 but the maximum bitrate support may be less compared to this)

ROBOTRONICS with AVI said...

do you mean that your code only works for MMC and not for SD cards or micro SD cards?
Also i had a question about remote control.
Does all cheap TV remote available in market follow philips PC5 protocol? If not which of them are following the RC5 protocol? please reply me.

Anonymous said...

hello to pepole of india
hello to gandi
the value of xtl is not found at any market
what is the compiler?
pls help me
tnx alot
pls mailto rajimahdi2012@yahoo.com

Goebish said...

@Anonymous you can use another xtl, 16 or 20MHz for exemple, then you just have to modify this line:
#define F_CPU 16450000
to match your xtal frequency.

dmitry said...

Great work!
Is it real to do all that on ATmega16 ?

Hemanth Kumar said...

Hi...really Great Project...
I have one doubt...can we use 1GB Micro SD Card.?..
Please help me out.....

Vinod.S said...

@dmitry: We can make wav player using atmega16 also. But my player supports high bitrate wav files since I am using 1KB of RAM for the audio buffer itself. In atmega16 the RAM is only 1KB. So we cannot dedicate 1KB RAM and thus the maximum bitrate support will be less compared to this project... I believe so... (it depends on your code)

Vinod.S said...

Hi hemanth, the particular code is written only for MMC cards. You can easily modify the MMC initialization part to make it work with SD cards also... You can check out my project - "mounting sd card on linux using parallel port of pc". There I included SD card support along with the MMC card. Just try to modify this MMC initialization according to that.... Then it will support SD card also....:-)

Vishakha Vinayak said...

i want try it on atmega 64 kit...will it work on..and what extra peripherals are required??

Артур Гумеров said...

Please change the code so that option was to use SD cards

zola said...

will work with an 12mhz xtal ???

Idowu said...

Hello Vinod, I am working on a similar project. my project is to convert stored text into audio. I tried using 16F877A but I am only getting a beeping sound.Any suggestion on how to go about it please?

Kannu Pandiyan said...

how u got this OCR1A = ((double)F_CPU/1000000)*1778; ......, i am using 14.7456 MHZ with atmega2560 ....,??? will it work

Aurelian Caraghete said...

work with Atmega32 or 16 with 12MHz crystal, and for the sd card formatted under windows as FAT the line In function
void mmc_read_sector (int sector)
{
sector+=63; // work for my sdcards , thx goebish for this line


..........................
many thanks Vinod.S and

maghsoud abureyhani said...

hi

maghsoud abureyhani said...

hi my friend.imade your waveplayer with m32 16Mhz microsd avr studio and format with fat(default). but i cant play music.
iust lcd shows NOT A FAT16 . can you help me ? i really need your help.thanks

Goebish said...

maghsoud abureyhani, I had the same issue, read my previous (old) comments.

Βασιλης Β said...

Very nice project. The specs are very good.
Can you make it to play 2 stereo wav files or 4 mono wav files simultaneously ?

Raju said...

Hi Vinod,
It indeed an excellent project made by you. but can you customised the device a little bit? Like, can you make the device to work with a 128GB Micro SD card? Also, can you give the user to save say 50 mp3 files, and the flexibility to choose any of the 3 mp3 files to play continuously according to his wish. It would be ok, if you give him to an option to choose the files using a 8 pin DIP switch, and if need the user have to save the files in a particular type of file name, given by you. The device will play the files not randomly, but one by one continuously, until the user press a stop button. (Remote Control is Not necessary, a switch on the device will be fine. In fact remote control is not my requirement.) If "YES" is your answer then contact me in my mail id :
rajusahantc@gmail.com
Mob : 09038089813

Post a Comment

SHARE IT on FB