logo elektroda
logo elektroda
X
logo elektroda
Adblock/uBlockOrigin/AdGuard mogą powodować znikanie niektórych postów z powodu nowej reguły.

[ATMEGA16][C] Dżwięk z PWM

rafał 27 Wrz 2008 19:03 4318 14
  • #1 5576919
    rafał
    Poziom 22  
    Witam

    Wraz z kolega budujemy małe urządzenie którego zadaniem ma być odtworzenie dźwięku z karty SD za pomocą PWM. Niestety poza otrzymaniem ciągłego 4kHz albo różnych innych nie mam więcej efektów. Projekt jest rozwinięciem Attiny2313 Audio z PWM. Poniżej kod programu.
    
    #include <avr/io.h>
    #include <avr/interrupt.h>
    #include <stdio.h>
    #include <inttypes.h>
    #include <avr/iom16.h>
    #include <util/delay.h>
    
    #define SPIDI	6	// Port B bit 6 (pin7): data in (data from MMC)
    #define SPIDO	5	// Port B bit 5 (pin6): data out (data to MMC)
    #define SPICLK	7	// Port B bit 7 (pin8): clock
    #define SPICS	4	// Port B bit 4 (pin5: chip select for MMC
    
    
    void timer_init()
    {
     TCCR1A = (1<<COM1A1) | (1<<COM1A0) | (1<<COM1B1) | (1<<COM1B0) | (0<<FOC1A) | (0<<FOC1B) | (1<<WGM10) | (1<<WGM11);
     TCCR1B = (0<<WGM13) | (1<<WGM12) | (1<<CS12) | (1<<CS11) | (1<<CS10);
     TIMSK  = (1<<OCIE1A);
     DDRD = 0xFF;
     PORTD = 0x00;
     OCR1A = 1500;
     OCR1B = 0x00;
    }
    
    void SPIinit(void) {
    	DDRB &= ~(1 << SPIDI);	// set port B SPI data input to input
    	DDRB |= (1 << SPICLK);	// set port B SPI clock to output
    	DDRB |= (1 << SPIDO);	// set port B SPI data out to output 
    	DDRB |= (1 << SPICS);	// set port B SPI chip select to output
    	SPCR = (1 << SPE) | (1 << MSTR) | (0 << SPR1) | (0 << SPR0);
    	PORTB &= ~(1 << SPICS);	// set chip select to low (MMC is selected)
    }
    
    char SPI(char d) {  // send character over SPI
    	char received = 0;
    	SPDR = d;
    	while(!(SPSR & (1<<SPIF)));
    	received = SPDR;
    	return (received);
    }
    
    
    char Command(char cmd, uint32_t adr )
    {	
    	SPI(0xFF);
    	SPI(cmd);
    	SPI((uint8_t)(adr >> 24));
    	SPI((uint8_t)(adr >> 16));
    	SPI((uint8_t)(adr >> 8));
    	SPI((uint8_t)(adr));
    	SPI(0xFF);	
    	SPI(0xFF);
    	return SPI(0xFF);
    }
    
    int MMC_Init(void) { // init SPI
    	char i;
    	PORTB |= (1 << SPICS);
    	PORTB |= (1 << SPIDI);
    	
    	// start MMC in SPI mode
    	for(i=0; i < 10; i++) SPI(0xFF);
    	PORTB &= ~(1 << SPICS);
    
    	if (Command(0x40,0) != 1) goto mmcerror; // reset MMC
    
    st: // if there is no MMC, prg. loops here
    	if (Command(0x41,0) !=0) goto st;
    	return 1;
    mmcerror:
    	return 0;
    }
    
    
    
    
    uint16_t sendmmc(uint32_t sector)//, uint16_t stop) { // send 512 bytes from the MMC via the serial port
    	{
    	int i;
    	// 512 byte-read-mode 
    	uint8_t ix;
    	char r1 =  Command(0x51,sector);
    
    	for (ix = 0; ix < 50000; ix++) {
    		if (r1 == (char)0x00) break;
    		r1 = SPI(0xFF);
    	}
    
    	if (r1 != (char)0x00) {
    		return 1;
    	}
    
    	while(SPI(0xFF) != (char)0xFE);
    
    	for(i=0; i < 512; i++) 
    	{
    	     while(!(TIFR & (1<<OCF1A)));
    		 OCR1A = 1500;
    		 OCR1B = SPI(0xFF);	
     	}
    
    
    
    	SPI(0xFF);
    	SPI(0xFF);
    
    	return 0;
    }
    
    int main(void) 
    {
    
     //init();
     timer_init();
     SPIinit();
    			
     if (MMC_Init() == 1)
     {
    
      DDRD |= (1<<PD6);
      PORTD |= (1<<PD6);
    	 
      sei(); // enable interrupts
    	
      for (int sect = 0; sect < 62760; sect++)
      {
       sendmmc(sect);
      }
     }
    
      
    
     while (1) 
     {
    	PORTD &= ~(1<<PD7);
    	_delay_ms(500);
    	PORTD |= (1<<PD7);
    	_delay_ms(500);	
     }
    }


    Prosze w przyszłości , stosować znaczniki [code] i nie używać w tytule "zakazanych" wyrazów - regulamin p.11.1
    [zumek]
  • #2 5578263
    Freddy
    Poziom 43  
    PWM jak sama nazwa wskazuje, to modulacja szerokości impulsu, a nie jego częstotliwości.
  • #3 5578489
    ZbeeGin
    Poziom 39  
    Freddy napisał:
    PWM jak sama nazwa wskazuje, to modulacja szerokości impulsu, a nie jego częstotliwości.

    Kolega właśnie to robi. W tym fragmencie.
    for(i=0; i < 512; i++) 
       { 
            while(!(TIFR & (1<<OCF1A))); 
           OCR1A = 1500; 
           OCR1B = SPI(0xFF);    
        }

    Do OCR1B wpisywane są bajty, które to sterują wypełnieniem przebiegu - co przekłada się po filtracji na napięcie.

    To co mi się najbardziej nie podoba to ustawienie preskalera Timer1:
      TCCR1B = (0<<WGM13) | (1<<WGM12) | (1<<CS12) | (1<<CS11) | (1<<CS10); 

    Czy rzeczywiście taktujesz licznik z zewnętrznego źródła? Jaka jest częstotliwość tego przebiegu?

    Do odtwarzania polecam ustawić prescaler na 1 (taktowanie wewnętrzne) i nie używać kanału A PWM-a jako układu synchronicznego wpisywania próbek w równych odstępach czasu. Jak już to skorzystać z przerwań drugiego licznika.

    Dodatkowo próbki audio są zwykle zapisywane w postaci liczb signed i trzeba przeliczyć je na unsigned, a potem wysyłać do rejestru OCR.

    Ponadto próbki są 8 bitowe, a jak widać włączyłeś FastPWM (bardzo dobrze!) ale pracujący w rozdzielczości 10bitów - a to błąd.
  • #4 5578994
    rafał
    Poziom 22  
    Witam po poprawieniu kodu wciąż brak dźwięku. Na oscyloskopie pokazuje ze jest to sygnał wypełniony w 10% dla licznika 1B i w 100% dla licznika 1A. I f~46kHz . Poniżej testowy kod. Kwarc 12 MHz.

    
    
    #include <avr/io.h>
    #include <avr/interrupt.h>
    #include <stdio.h>
    #include <inttypes.h>
    #include <avr/iom16.h>
    #include <util/delay.h>
    
    
    unsigned char test[512] = 
    {
    128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 
    128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 127, 129, 128, 127, 133, 
    117, 109, 125, 121, 116, 132, 140, 126, 114, 114, 116, 120, 114, 93, 73, 66, 76, 116, 142, 129, 
    128, 129, 120, 119, 118, 104, 87, 123, 181, 194, 196, 198, 189, 176, 160, 162, 172, 164, 164, 183, 
    197, 188, 168, 167, 170, 165, 185, 209, 206, 196, 196, 199, 185, 162, 156, 167, 176, 173, 170, 166, 
    151, 142, 140, 134, 130, 127, 113, 86, 67, 66, 69, 75, 73, 75, 86, 90, 91, 84, 65, 48, 
    41, 30, 26, 56, 91, 88, 72, 70, 73, 82, 89, 73, 57, 60, 74, 89, 92, 77, 63, 60, 
    53, 47, 56, 64, 63, 61, 56, 54, 52, 36, 16, 22, 51, 66, 67, 70, 76, 88, 99, 92, 
    77, 74, 85, 100, 106, 97, 83, 85, 96, 108, 133, 160, 164, 144, 113, 96, 91, 82, 74, 76, 
    89, 97, 97, 97, 82, 54, 40, 41, 41, 43, 56, 74, 78, 64, 55, 64, 72, 72, 84, 102, 
    108, 116, 126, 127, 124, 127, 134, 134, 138, 148, 152, 156, 164, 165, 169, 171, 160, 156, 157, 152, 
    151, 145, 133, 136, 153, 166, 165, 163, 165, 161, 156, 158, 155, 147, 148, 160, 185, 209, 215, 220, 
    220, 204, 200, 208, 205, 200, 202, 209, 214, 213, 205, 198, 194, 194, 203, 219, 231, 235, 230, 219, 
    200, 184, 177, 170, 170, 177, 172, 164, 163, 158, 156, 160, 163, 161, 142, 116, 103, 96, 89, 93, 
    101, 105, 111, 116, 120, 110, 89, 80, 78, 75, 73, 80, 93, 91, 77, 69, 70, 77, 91, 98, 
    89, 87, 93, 95, 95, 94, 97, 96, 91, 94, 99, 100, 101, 95, 83, 78, 79, 71, 56, 41 
    };
    
    unsigned char znacznik_przerwania_timera = 0;
    
    #define SPIDI	6	// Port B bit 6 (pin7): data in (data from MMC)
    #define SPIDO	5	// Port B bit 5 (pin6): data out (data to MMC)
    #define SPICLK	7	// Port B bit 7 (pin8): clock
    #define SPICS	4	// Port B bit 4 (pin5: chip select for MMC
    
    
    
    void timer_init()
    {
    
    
    
     TCCR1A = (1<<COM1A1) | (1<<COM1A0) | (1<<COM1B1) | (1<<COM1B0) | (0<<FOC1A) | (0<<FOC1B) | (1<<WGM10) | (0<<WGM11);
     TCCR1B = (0<<WGM13) | (1<<WGM12) | (0<<CS12) | (0<<CS11) | (1<<CS10);
     TIMSK  = (1<<OCIE1B);
     DDRD = 0xFF;
     PORTD = 0x00;
     OCR1A = 0x00;
     OCR1B = 0x5DC;
    }
    
    ISR (TIM1_COMPB)
    {
     TIFR = 0x00;
     OCR1B = 0x5DC;
     znacznik_przerwania_timera = 1;
    }
    
    void SPIinit(void) {
    	DDRB &= ~(1 << SPIDI);	// set port B SPI data input to input
    	DDRB |= (1 << SPICLK);	// set port B SPI clock to output
    	DDRB |= (1 << SPIDO);	// set port B SPI data out to output 
    	DDRB |= (1 << SPICS);	// set port B SPI chip select to output
    	SPCR = (1 << SPE) | (1 << MSTR) | (1 << SPR1) | (1 << SPR0);
    	PORTB &= ~(1 << SPICS);	// set chip select to low (MMC is selected)
    }
    
    char SPI(char d) {  // send character over SPI
    	char received = 0;
    	SPDR = d;
    	while(!(SPSR & (1<<SPIF)));
    	received = SPDR;
    	return (received);
    }
    
    
    char Command(char cmd, uint32_t adr )
    {	
    	SPI(0xFF);
    	SPI(cmd);
    	SPI((uint8_t)(adr >> 24));
    	SPI((uint8_t)(adr >> 16));
    	SPI((uint8_t)(adr >> 8));
    	SPI((uint8_t)(adr));
    	SPI(0xFF);	
    	SPI(0xFF);
    	return SPI(0xFF);
    }
    
    int MMC_Init(void) { // init SPI
    	char i;
    	PORTB |= (1 << SPICS);
    	PORTB |= (1 << SPIDI);
    	
    	// start MMC in SPI mode
    	for(i=0; i < 10; i++) SPI(0xFF);
    	PORTB &= ~(1 << SPICS);
    
    	if (Command(0x40,0) != 1) goto mmcerror; // reset MMC
    
    st: // if there is no MMC, prg. loops here
    	if (Command(0x41,0) !=0) goto st;
    	return 1;
    mmcerror:
    	return 0;
    }
    
    
    
    
    uint16_t sendmmc(uint32_t sector)//, uint16_t stop) { // send 512 bytes from the MMC via the serial port
    	{
    	int i;
    	// 512 byte-read-mode 
    	uint8_t ix;
    	char r1 =  Command(0x51,sector);
    
    	for (ix = 0; ix < 50000; ix++) {
    		if (r1 == (char)0x00) break;
    		r1 = SPI(0xFF);
    	}
    
    	if (r1 != (char)0x00) {
    		return 1;
    	}
    
    	while(SPI(0xFF) != (char)0xFE);
    
    
    	for(i=0; i < 512; i++) 
    	{
    //	     while(!(TIFR & (1<<OCF1B)));
    		 znacznik_przerwania_timera = 0;
    		 OCR1A = test[i];//SPI(0xFF);
    		 OCR1B = 0x5DC;	
     		 while(znacznik_przerwania_timera!=1);
     	}
    
    
    
    	SPI(0xFF);
    	SPI(0xFF);
    
    	return 0;
    }
    
    int main(void) 
    {
    
     //init();
     timer_init();
     SPIinit();
    			
     if (MMC_Init() == 1)
     {
    
      //DDRD |= (1<<PD6);0x|= (1<<PD6);
      PORTD= 0x40;
    	 
      sei(); // enable interrupts
    	
      for (int sect = 0; sect < 62760; sect++)
      {
       sendmmc(sect);
      }
     }
    
      
    
     while (1) 
     {
    	PORTD &= ~(1<<PD7);
    	_delay_ms(500);
    	PORTD |= (1<<PD7);
    	_delay_ms(500);	
     }
    }
    
    
  • #5 5579550
    ZbeeGin
    Poziom 39  
    rafał napisał:
    Witam po poprawieniu kodu wciąż brak dźwięku.
    ISR (TIM1_COMPB)
    {
     TIFR = 0x00;
     OCR1B = 0x5DC;
     znacznik_przerwania_timera = 1;
    }

    Tak to się znaczników przerwania w rejestrze TIFR nie kasuje. Trzeba bit zapisać wysoką wartością logiczną jak już. Znacznik sam się kasuje po przyjęciu przerwania.
    Ponadto kompilator (avr-gcc 4.3.0) zgłasza, że taka nazwa źródła przerwań może być błędna.

    --------
    U mnie Twój program zaczął dopiero działać w symulatorze jakoś sensownie przy ustawieniu braku optymalizacji kodu. Przy innych opcjach procedura sendmmc() nie była wogóle wywoływana, a program utykał w jakiejś bliżej nie zidentyfikowanej pustej pętli.
    Do celów testowych wykorzystałem taki oto kod:

    #include <avr/io.h> 
    #include <avr/interrupt.h> 
    #include <stdio.h> 
    #include <inttypes.h> 
    // #include <avr/iom16.h> 
    #include <util/delay.h> 
    
    
    unsigned char test[512] = 
    { 
      128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 
      128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 127, 129, 128, 127, 133, 
      117, 109, 125, 121, 116, 132, 140, 126, 114, 114, 116, 120, 114, 93, 73, 66, 76, 116, 142, 129, 
      128, 129, 120, 119, 118, 104, 87, 123, 181, 194, 196, 198, 189, 176, 160, 162, 172, 164, 164, 183, 
      197, 188, 168, 167, 170, 165, 185, 209, 206, 196, 196, 199, 185, 162, 156, 167, 176, 173, 170, 166, 
      151, 142, 140, 134, 130, 127, 113, 86, 67, 66, 69, 75, 73, 75, 86, 90, 91, 84, 65, 48, 
      41, 30, 26, 56, 91, 88, 72, 70, 73, 82, 89, 73, 57, 60, 74, 89, 92, 77, 63, 60, 
      53, 47, 56, 64, 63, 61, 56, 54, 52, 36, 16, 22, 51, 66, 67, 70, 76, 88, 99, 92, 
      77, 74, 85, 100, 106, 97, 83, 85, 96, 108, 133, 160, 164, 144, 113, 96, 91, 82, 74, 76, 
      89, 97, 97, 97, 82, 54, 40, 41, 41, 43, 56, 74, 78, 64, 55, 64, 72, 72, 84, 102, 
      108, 116, 126, 127, 124, 127, 134, 134, 138, 148, 152, 156, 164, 165, 169, 171, 160, 156, 157, 152, 
      151, 145, 133, 136, 153, 166, 165, 163, 165, 161, 156, 158, 155, 147, 148, 160, 185, 209, 215, 220, 
      220, 204, 200, 208, 205, 200, 202, 209, 214, 213, 205, 198, 194, 194, 203, 219, 231, 235, 230, 219, 
      200, 184, 177, 170, 170, 177, 172, 164, 163, 158, 156, 160, 163, 161, 142, 116, 103, 96, 89, 93, 
      101, 105, 111, 116, 120, 110, 89, 80, 78, 75, 73, 80, 93, 91, 77, 69, 70, 77, 91, 98, 
      89, 87, 93, 95, 95, 94, 97, 96, 91, 94, 99, 100, 101, 95, 83, 78, 79, 71, 56, 41 
    }; 
    
    unsigned char znacznik_przerwania_timera = 0; 
    
    #define SPIDI   6   // Port B bit 6 (pin7): data in (data from MMC) 
    #define SPIDO   5   // Port B bit 5 (pin6): data out (data to MMC) 
    #define SPICLK  7   // Port B bit 7 (pin8): clock 
    #define SPICS   4   // Port B bit 4 (pin5: chip select for MMC 
    
    
    
    void timer_init(void) 
    { 
      TCCR1A = (1<<COM1A1) | (1<<COM1A0) | (1<<COM1B1) | (1<<COM1B0) | (0<<FOC1A) | (0<<FOC1B) | (1<<WGM10) | (0<<WGM11); 
      TCCR1B =  (0<<WGM13) |  (1<<WGM12) |   (0<<CS12) |   (0<<CS11) |  (1<<CS10); 
      TIMSK  = (1<<OCIE1B); 
      DDRD   =  0xFF; 
      PORTD  =  0x00; 
      OCR1A  =  0x00; 
      OCR1B  = 0x5DC; 
    } 
    
    ISR(TIMER1_COMPB_vect) 
    { 
      OCR1B = 0x5DC; 
      znacznik_przerwania_timera = 1; 
    } 
    
    void SPIinit(void) 
    { 
       DDRB &= ~(1 << SPIDI);     // set port B SPI data input to input 
       DDRB |= (1 << SPICLK);     // set port B SPI clock to output 
       DDRB |= (1 << SPIDO);      // set port B SPI data out to output 
       DDRB |= (1 << SPICS);      // set port B SPI chip select to output 
       SPCR = (1 << SPE) | (1 << MSTR) | (1 << SPR1) | (1 << SPR0); 
       PORTB &= ~(1 << SPICS);    // set chip select to low (MMC is selected) 
    } 
    
    char SPI(char d) 
    {  // send character over SPI 
       char received = 0; 
       
       SPDR = d; 
       while(!(SPSR & (1<<SPIF))); 
       received = SPDR; 
       return (received); 
    } 
    
    uint16_t sendmmc(uint32_t sector) //, uint16_t stop) { // send 512 bytes from the MMC via the serial port 
       { 
       int i; 
    
       for(i = 0; i < 512; i++) 
       { 
           znacznik_przerwania_timera = 0; 
           OCR1A = test[i]; //SPI(0xFF); 
           OCR1B = 0x5DC;    
           while(znacznik_przerwania_timera != 1); 
       } 
       return 0; 
    } 
    
    
    int main(void) 
    { 
      timer_init(); 
      SPIinit(); 
    
        sei();    // enable interrupts 
        for (int sect = 0; sect < 62760; sect++) 
        { 
          sendmmc(sect); 
        } 
    
      while(1) 
      { 
        PORTD &= ~(1<<PD7); 
        _delay_ms(500); 
        PORTD |=  (1<<PD7); 
        _delay_ms(500);    
      }
    }
    


    Dalej jednak proponuję skorzystać z dwóch odrębnych liczników:
    1. Timer0 - generuje audio
    2. Timer1 - podaje próbki w równych odstępach czasu.
    Inaczej nic sensownego nie usłyszysz.
  • #6 5641910
    szeryf.rm
    Poziom 22  
    Z optymalizacją nie działa bo jak zwykle brakuje volatile przy znacznik_przerwania_timera. Być może jeszcze coś jest nie tak, ale tylko to zauważyłem.
  • #7 5642050
    rafał
    Poziom 22  
    Witam

    Już udało nam się wyłapać błędy. Jutro postaram się wrzucić poprawki. Układ działa tak jak założyliśmy. Na ta chwilę powstaje wersja z 16Mbit pamięcią flash AT45DB161.
  • #8 5665837
    mreq
    Poziom 21  
    Nie prościej było dać DACa ? Nawet covoxowego R-2R
  • #9 5665938
    rafał
    Poziom 22  
    Prościej może i prościej .... Ale na pewno nie taniej... A układ ma być możliwie najtańszy jak tylko się da.
  • #10 5666394
    GienekS
    Poziom 32  
    No faktycznie, drabinka R-2R na 8 bitach Cię zrujnuje finansowo.
  • #11 5667489
    rafał
    Poziom 22  
    Ech .. może trzeba by to przemyśleć.:D
  • #13 6306731
    Syrek
    Poziom 12  
    Czołem!
    W jakim programie można stworzyć próbeczki, które możemy wrzucić do pamięci programu naszego procka?
  • #14 6310267
    ZbeeGin
    Poziom 39  
    Kolega jest strasznie niecierpliwy. Jesteśmy na Forum a nie na wyścigach.

    Jakby kolega ruszył jednak porządnie głową to znalazłby pierwszy lepszy program do edycji i nagrywania dźwięku by takie próbki nagrać, i zapisać w formacie RAW; a potem samodzielnie za pomocą QBasic-a (lub innego narzędzia programistycznego) napisał program który binarne dane z pliku zapisuje w innym pliku ale już w postaci tekstu, strawnego przez kompilator.
  • #15 6310933
    Syrek
    Poziom 12  
    Pewnie, że ruszyłem główką. Wyszperałem jakiś programik, który konwertuje wav-a do raw-a. Pod Linuxem jest tego trochę. Myślałem może, że jest jakieś gotowe narzędzie, którym to przerabiacie i jest od razu tablica char-ów - w wolnych chwilach pokoduję sobie takie coś:).

    PS. Przepraszam za niecierpliwość:) ale wystarczy spojrzeć na moją aktywność na forum aby stwierdzić, że więcej czytam niż piszę. No i uwielbiam charakterystyczny styl na forach (czyli: Ja ekspert -> Ty laik, więc siedź cicho i czytaj co do Ciebie piszę :-P)

    Temat już dawno nieaktualny ale nikt nie udzielił mi konkretnej odpowiedzi więc odpowiem sobie teraz sam aby ktoś też mógł z tego czasem skorzystać:). Do stworzenia próbek wystarczy jeden program - dowolny HexEditor etc. Otwieramy jakiegoś wav-a i eksportujemy do tablicy unsigned char. Dziękuję wszystkim za pomoc.
REKLAMA