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

[Atmega32][C]Odtwarzanie PCM przez PWM. Częstotliwości.

grandcapucin 27 Lis 2010 22:19 5729 37
  • #1 8797848
    grandcapucin
    Poziom 10  
    Cześć,

    robię większy projekt w którym chciałbym użyć zapisanych w pamięci FLASH nagrań. W związku z tym, że jeden z największych dystrybutorów elektroniki w Polsce bardzo zwleka z wysłaniem mi układu AT45DB to zacząłem montować układ bez niej, co i tak przysporzyło mi kłopotu. Celem jest jakakolwiek słyszalna jakość dźwięku.

    Plan jest prosty:
    1. Nagrywam dźwięk(ok. 1sek słowo "tak") o paśmie do 4kHz, za pomocą programu Audiocity (może jest jakiś lepszy) w formacie 8kHz MONO 16bit.
    2. Powyższy plik eksportuję jako AU 8bit u-law, który zamieniam na format Hex.
    3. Załączam go w programie jako plik nagłówkowy:
    
    unsigned char data[] = {
    0x2E,0x73,0x6E,0x64,0x00,0x00,0x00,0x18,0x00,0x00,0x1A,0x51,0x00,0x00,0x00,0x01,
    ...
    0x00,0x00,0x3D,0x46,0x00,0x00,0x00,0x01,0x7F};

    4. PWM ustawiam na poprawnie fazowy, z prescalerem = 1, czyli wyjściowa częstotliwość PWM=(8MHz/(2*2^8))=15 686 Hz Co jest przynajmniej 2 razy większe od wejściowych 4 Khz, więc aliasingu nie powinno być. W programie próbuję go odtwarzać w ten sposób:
    #define F_CPU 8000000UL //CPU
    #include "stdio.h"
    #include "stdlib.h"
    
    #include <avr/io.h>   //register
    #include <util\delay.h>  //delays
    #include <avr/interrupt.h>
    
    #include "sound.h"
    
    
    
    int main(void)
    {
    int i;
    DDRD = (1<<PD5);
    TCCR1A = 0x81; // 8 bit PWM, uzywjąc COM1A
    TCCR1B = 0x01; // preskaler licznika = 1
    OCR1AH = 0x00; // BOTTOM = 0
    OCR1AL = 0xFF; // TOP= 1
    
    OCR1A = data[0];
    
    
    	while(1){
    		for(i=0;i<6761;i++) //6761 próbek w tabeli data []
    		{
    		OCR1A = data[i];
    
    
    			if (i==6761) i=0;
    		}
    
    	}
    }

    5. Efektem na nóżce uC (nie mam oscyloskopu) jest wahające się napięcie. W odstępie 300ms wyświetla się (4,20 4,19 i 3,84 i od początku) czy coś podobnego.
    6. Zbudowany przeze mnie wzmacniacz jest oparty na filtrze dolnoprzepustowym na układzie LM358 ok.4kHz (szerokość pasma próbkowanego) i zwykłym wzmacniaczu TBA820M z założenia ma on sterować 8Ω słuchawkami. Niestety po podłączeniu 0,2W głośnika nie ma żadnego dźwięku. Całość zasilam 12V ładowarką po stabilizacji, więc wydaje mi się, że natężenie dla układu jest w porządku.

    Moje pytania:
    Ad.1 i 2 czy dobrze nagrywam,przechowuję i konwertuję dźwięk dla moich potrzeb? Może jest jakieś lepsze rozwiązanie? Inny format? Martwi mnie zapis w Audiocity 16bitowy i jego późniejsza konwersja do 8-bitowego AU. Może lepszy AIFF 8bit PCM. Czy to duża różnica przy odtwarzaniu?
    Ad.3 widziałem jeszcze taki zapis:
    const unsigned char data[] ={...

    ale nie wiem czy to coś zmienia?
    W jakimsprzykładzie z zapisem nutowym widziałem jeszcze taki zapis:
    const unsigned char data[] PROGMEM={...

    Ad.4 Wydaje mi się, że problem leży w synchronizacji, ale jak na laika C przystało nie bardzo wiem gdzie. Czy da się to jakoś właśnie zsynchronizować używając przerwań?
    Ad.5 i Ad.6 Tutaj jest chyba ok, ale mogę się mylić, bo płytki robiłem sam po raz pierwszy. Na wyjściu układu do słuchawek mam stałe napięcie 0,59V.

    Z góry dziękuję za wskazówki.

    ps
    To mój pierwszy post, ale czytam forum od dłuższego czasu, mam nadzieję, że wypełniłem pkt. regulaminu :D
  • #2 8797858
    Konto nie istnieje
    Poziom 1  
  • #3 8797868
    grandcapucin
    Poziom 10  
    Jaki jest więc najlepszy sposób na okiełznanie tych przerwań? Czy mógłbyś powiedzieć mi jak wyglądałby algorytm przerwań w moim przypadku?
  • #4 8798081
    Konto nie istnieje
    Poziom 1  
  • #6 8799223
    grandcapucin
    Poziom 10  
    Dzięki atom i ZbeeGin,

    Nie będę kopiował Twojego programu, bo go po prostu do końca nie rozumiem. Pisałem wcześniej programy w C, ale nie dla uC. Dlatego powoli poznaję wszystkie rejestry i możliwości jakie niosą ze sobą te układy scalone.

    Ale zaczynam tak wzorując się na Twoim kodzie:
       // konfiguracja licznika
       // generuj przerwania o częstotliwości 8kHz (125us)
       //
       OCR0 = 125;
       TCCR0 = (1<<WGM00)|(1<<CS01);
       TIMSK = (1<<OCIE0);
               // poprawny fazowo 8bit | prescaler 8 | 125*1us | interrupts enable 

    dzięki ustawieniu TIMSK mogę teraz przerywać w innym miejscu w programie? A czy chodzi właśnie o to, żebym miał dokładnie taki sam PWM w tym liczniku?

    To fajnie, a teraz jak to zastosować, czy tak dobrze kombinuję?
    To jak rozumiem włącza przerwania:

    _enable_interrupt();
    a czy to jest to samo i może być używane zamiennie:

    Dokładam do kodu część z ustawieniami przerwań:
     MCUCR = (1<<ISC10)|(1<<ISC00);
               //zmiana przy każdym narastającym zboczu
       GICR  = (1<<INT0)|(1<<INT1)|(1<<INT2);
               // INT0/INT1/INT2 włączone 

    To powyższe zrozumiałem, ale zastanawiam się, czy nie ma innej opcji.

    Da się do tego użyć flag? Bo rozumiem, że jakbym chciał zaadaptować Twój kod, to muszę wprowadzić na jedną z nóg uC (odpowiadającą za przerwania) połączenie z wyjściem mojego PWM?[/code]
  • #7 8799361
    Konto nie istnieje
    Poziom 1  
  • #8 8799399
    gaskoin
    Poziom 38  
    Naprawdę, polecam ściągnąć datasheet. Jest to trochę lepsze rozwiązanie niż wpisywanie na ślepo byle gdzie i byle jakich rejestrów.
  • #9 8801900
    grandcapucin
    Poziom 10  
    @A-T-O-M: Dzięki, Twoje wskazówki fajnie mnie naprowadziły.
    @gaskoin: Nie wiem skąd pomysł, że nie posiłkowałem się datasheetem? Przecież przeczytałem regulamin forum. Dodam, że nie tyko tym, który wysłałeś ale też innymi np: Setup for AVR Timers (może się komuś przyda).

    Wracając do tematu, to teraz taki kod:
    uint8_t  volatile i = 0;
    
    void setup (void)
    {
    DDRD = (1<<PD5);
    TCCR1A = 0x81; 		// 8 bit PWM, uzywjąc COM1A
    TCCR1B = 0x01; 		// preskaler licznika = 1
    TIMSK=(1<<OCIE1A); 	//włącz przerwania
    OCR1AH = 0x00; 		// BOTTOM = 0
    OCR1AL = 0xFF; 		// TOP= 1
    
    OCR1A = data[0];
    				//włącz przerwania
    }
    
    ISR(TIMER1_CAPT_vect)
    {
    	if(i<6760)
    	{
    //		cli();				// wstrzymuję timer żeby wpisać
    		OCR1A = data[i]; 	// przepisuję wartość do rejestru
    		TCNT1 = 0;			// zerowanie Timer1
    		i++;				//inkrementacja
    //		sei(); 				// uruchamiam zliczania Timer1
    	}
    	else
    	{
    		i=0;
    	}
    }
    
    int main(void)
    {
    	
    	setup();
    	sei();
    	while(1);
    	return 0;
    }
    


    Uzyskuję teraz piękne równe piszczenie(przed filtrem), ale tylko jednej wartości i to pierwszej próbki. Może coś nie tak ze zmienną "i" zrobiłem? Pierwszy raz używam ISR, może w definiowaniu funkcji jest jakiś błąd?

    A nie wiem czy potrzebne w funkcji przerwania cli() i sei()?
  • #10 8801936
    Konto nie istnieje
    Poziom 1  
  • #11 8802404
    grandcapucin
    Poziom 10  
    Dzięki za uwagę, niestety nie wiele to zmieniło jeżeli chodzi o efekt wyjściowy. A w symulatorze jak próbuję się dostać do tej zmiennej "i" żeby patrzeć jak się inkrementuje to mi wyskakuje błąd lokalizacji. Gdzie tu szukać jeszcze błędów? Format zapisu?

    Zmieniłem też to:
    const unsigned char data[] = {
    0x2E,0x73,0x6E...};
  • #13 8803234
    grandcapucin
    Poziom 10  
    @A-T-O-M za pomoc dzięki. Bo już jest dźwięk.

    Zmieniłem tylko to:
    #include <avr/pgmspace.h>	//odczyt z pamięci

    Poniżej zamieniłem na COMPA. Jakaś intuicja chyba.
    ISR(TIMER1_COMPA_vect)


    A teraz pytanie o to kiedy używać PROGMEM "tutaj":
    
    const unsigned char data[] TUTAJ= {
    0x2E,0x73,0x6E,0x64,0x00,0x00,


    Bo program ruszył bez tego? Chyba ma to znaczenie przy optymalizacji kodu?
  • #14 8821275
    grandcapucin
    Poziom 10  
    Mam kolejną niejasność. Chciałbym żeby PWM był odtwrzany tylko tyle razy ile mam próbek dźwięku. Mam do tego taką funkcję:
    ISR(TIMER1_COMPA_vect)
    {	
    
    	
    			OCR1A = pgm_read_byte(&data[odz++]);  	// przepisuję wartość do rejestru
        		sumz++;								//dodatkowa zmienna pokazująca sumę
    	if(sumz<probekz)
    		{
    			sumz=sumo;
    			probekz=probeko;    //przypisanie wartości 2-go odtworzenia
    			odz=odo;
    		}
    }


    zmienne:
    
    volatile int probeko; //ilosc probek 1
    volatile int probekz; //ilosc probek 2
    volatile int odo; //wczytywanie z tablicy od1
    volatile int odz; //wczytywanie z tablicy od2
    volatile int sumo; //pomocnicze do zliczania
    volatile int sumz; //pomocnicze do zliczania
    


    Dlatego dwie, że chcę odtwarzać jeden komunikat po drugim. Jak zrobić, żeby mój PWM zadziałał tylko tyle co
    probeko+probekz
    jeżeli w programie głównym chę go wywołać tak jak poniżej:
    do{
    			TIMSK |=(1<<OCIE1A);		//odtworzenie nagrania
    		}
    		while((probeko+probekz<????))

    jak w miejscu ???? wstawię jakąś dodatkową zmienną
    volatile int k
    , którą będę inkrementował wewnątrz funkcji przerwania to mi to zadziała i wywoływanie funkcji przerwania się wyłączy gdy w while znajdzie się fałsz?
  • #15 8821422
    Konto nie istnieje
    Poziom 1  
  • #16 8821837
    grandcapucin
    Poziom 10  
    Tylko, że podmieniane wartości nie są stałe. O to chodzi, że z dostępnych komunikatów w tablicy chcę odtworzyć dwa dowolne, których zmienne są ustalane podczas pracy programu. A tobie chodzi o to, że Dlugosc_drugiego_komunikatu jest stała, tak? Dlatego tak kombinuję, bo chcę skrócić maksymalnie funkcję z przerwania, żeby się nie przepełniła.
    Cytat:
    
    ISR(TIMER1_COMPA_vect)
    {
        OCR1A = pgm_read_byte(*odz++);     // przepisuję wartość do rejestru
        sumz--;
    
        if (Pierwszy_komunkat == 0)
            {
            if (sumz == 0)
                {
                sumz = Dlugosc_drugiego_komunikatu;
                odz = Adres_poczatku_bufora_drugiego_komunikatu;
                Pierwszy_komunkat = 1;
                }
            }
        else
            {
            if (sumz == 0)
                {
                TCCR1A = 0x00;  //Zatrzymanie Timera.
                }
            }
    }

    a gdybym się mylił, to jakbym chciał zastosować wskaźnik to powinno być:
    W mainie:
    
    void main (void)
    {
    int *odz;
    
    // jakieś tam instrukcje
    //a teraz chcę wywołać dźwięk od powiedzmy 8000 próbki do 10000(długość 2000),a po niej kolejną o 15000 do 18000 (długość 3000)
    *odz=8000;
    sumz=2000;
    odo=15000;
    sumo=3000;
    
             TIMSK |=(1<<OCIE1A);      //odtworzenie nagrania
    
    // kolejne instrukcje do wykonania
    }
    

    a czy nie lepiej tak wyłączyć działanie PWM:
    TIMSK &= ~(1<<OCIE1A)

    bo Twoja propozycja wygląda dosyć brutalnie, nie powiem. Bo jak wszystko się znajduje w nieskończonej pętli for przed którą stoi konfiguracja PWM, to mi się chyba już nie włączy..., czy wyrzucić to z fora i dodać na końcu
    while(1);
    return 0;
    na końcu?
  • #17 8821864
    Konto nie istnieje
    Poziom 1  
  • #18 8821905
    grandcapucin
    Poziom 10  
    @atomie, a co z tym wyłączaniem PWM? Czy na pytania oczywiste nie odpisujesz? :D
    Może ktoś inny wie, bo na to drugie nie odpiszesz, bo jest oczywiste?
  • #19 8822015
    Konto nie istnieje
    Poziom 1  
  • #20 8822352
    grandcapucin
    Poziom 10  
    @A-T-O-M, dzięki po raz kolejny.

    Ale, że sam sobie nie ufam to wkleję ostatecznie, czy o to Ci chodziło:

    
    
    volatile int Pierwszy_komunikat=0; // domyślnie "0"
    
    
    //zmienne jak wcześniej
    //main jak wcześniej
    
    ISR(TIMER1_COMPA_vect)
    {	
    
       	
        OCR1A = pgm_read_byte(*odz++);     // przepisuję wartość do rejestru
        sumz--;
    
        if (Pierwszy_komunikat == 0)
            {
            if (sumz == 0)
                {
                sumz = sumo;
                *odz = &odo;
                Pierwszy_komunikat = 1;
                }
            }
        else
            {
            if (sumz == 0)
                {
                	TIMSK &= ~(1<<OCIE1A);  // zatrzymanie Timera.
                }
            } 	
    }


    ostatni wątpliwość, czy przed

    obejdzie się bez "&"

    bo rozumiem, że wszystko zahula jak wpiszę zmienne nie tak jak poprzednio tylko o tak:

    *odz=&data[8000];
    odo=&data[15000] 


    tylko czy uC bez problemu się domyśli, że mi chodzi o wartość z tabeli z pliku nagłówkowego? Wystarczy do tego funkcja pgm_read_byte(); I jakiego typu teraz musi być odo wystarczy int?
    Ale się porobiło.
  • #21 8822467
    Konto nie istnieje
    Poziom 1  
  • #22 8824942
    regrom
    Poziom 16  
    grandcapucin napisał:

    A teraz pytanie o to kiedy używać PROGMEM "tutaj":
    
    const unsigned char data[] TUTAJ= {
    0x2E,0x73,0x6E,0x64,0x00,0x00,


    Bo program ruszył bez tego? Chyba ma to znaczenie przy optymalizacji kodu?


    Mam nadzieje że używasz PROGMEM, to słówko wywołuje to że twoja tablica zostanie zapisana w pamięci programu czyli we flashu, a nie gdzieś w RAMie.

    Najłatwiej to sprawdzić po kompilacji patrząc na logi:
    [Atmega32][C]Odtwarzanie PCM przez PWM. Częstotliwości.

    Czytając ten temat natknęło mnie parę pytań:

    Napisałeś że ustawiasz PWM na:
    Cytat:
    czyli wyjściowa częstotliwość PWM=(8MHz/(2*2^8))=15 686 Hz
    .

    i teraz zakręciłem się w tym sposobie odtwarzania, skoro ten timer chodzi w trybie PWM, to przerwanie od porównania jest wywoływane kiedy TCNT1 zrówna się z OCR1A - dobrze myślę?

    Czyli zmieniając OCR1A zmieniamy częstotliwość samego przerwania i zarazem wypełnienie PWMa, a sam PWM wciąż chodzi na 15kHz?

    i teraz czy zmienna częstotliwość zmian wypełnienia wynika z tego formatu w jakim zapisałeś dźwięk - uLaw?

    Pozdrawiam i dziękuje z góry za wyjaśnienie.
  • #23 8825106
    Konto nie istnieje
    Poziom 1  
  • #24 8826637
    regrom
    Poziom 16  
    Eee, szczerze to jeszcze bardziej mi zagmatwałeś sprawę..

    Możesz takim razie na chłopski rozum wytłumaczyć, kiedy to przerwanie od porównania się wykonuje i z jaką częstotliwością, oraz z jaką częstotliwością są podawane w tym przypadku próbki dźwięku na wypełnienie?

    W tym trybie(phase correct) TCNT1 jest zwiększane od 0 do TOP a potem zmniejszane od TOP do 0. I to zliczenia z w górę,a następnie w dół idzie z częstotliwością(w tym przypadku) 8Mhz(co 0,125us) dobrze myślę?

    Najlepiej jakbyś mi to rozjaśnił krok po kroku, będę wdzięczny.

    Bo ja w tej chwili widzę to tak:
    Na starcie TCNT1 = 0; na nóżce OCR1A mamy 0;
    Zaczynamy zliczać w górę do TOP(255)
    Timer zrówna się z OCR1(np 127) -> na nóżce OCR1A - mamy 1
    Timer zliczył do 255 i zaczyna zliczać do 0.
    Zrówna się przy liczenie w dół z OCR1A -> 0 na wyjściu.
    Zliczy do 0 i znów zaczyna liczyć w górę..

    i gdzie tu to przerwanie i przepisanie wartości OCR1A...?
  • #25 8826764
    Konto nie istnieje
    Poziom 1  
  • #26 8826866
    regrom
    Poziom 16  
    No kurde, cały czas próbuje się o to dowiedzieć, mam wrażenie że odpowiadasz ale na około

    ale już mamy coś wyjaśnione.

    Czyli na przykładzie: na początek OCR1A = 100;
    1) TNCT1 zwiększa się o 1 z co 0,125 us :?:
    2) doliczyliśmy do 100 - bach! przerwanie! (wpisujemy OCR1A np 50)
    3)Doliczyliśmy do 255, zaczynamy liczyć w dół.
    4)Zliczyliśmy w dół do 100 - w tym przypadku też TCNT1 = OCR1A, to tu też jest przerwanie czy nie?
    5) TCNT1 =0 znów liczymy w górę
    6) następne przerwanie wystąpi już przy TCNT1 = 50

    Tak to działa :?:

    Więc kurcze jak, mamy dźwięk w PCM z częstotliwością 8kHz, to to wypełnienie powinno być zmieniane z tą częstotliwością, a tu jest co 512 cykli timera?
  • #27 8826943
    Konto nie istnieje
    Poziom 1  
  • #28 8826974
    regrom
    Poziom 16  
    No w końcu dziękuje ze jasną i klarowną odpowiedź! :D

    Bo mnie to tutaj strasznie dziwi, ten sposób odtwarzania,myślałem że to jakaś sztuczka.
    Przecież częstotliwość PWMa nie powinna tu nic mieć do częstotliwości wrzucania próbek na wypełnienie, tzn ta nośna powinna być większa od częstotliwości PCMa żeby ją sobie odfiltrować za pomocą filtru DAC.
  • #29 8827119
    Konto nie istnieje
    Poziom 1  
  • #30 8838048
    grandcapucin
    Poziom 10  
    Hej, chłopaki tak właśnie robiłem jak mówicie tzn. Nagrywałem dźwięk z częstotliwością 15 686kHz, gdzieś na początku był błąd w opisie. Problem polegał na tym, że na początku chciałem użyć 8kHz, żeby więcej upakować w pamięci Flash uC. Dlatego teraz mam problem z filtrem, który zrobiłem...

    @regrom, zapisuję oczywiście za pomocą PROGMEM-a gdzieś w końcu doszukałem się jak to działa.
    A co do PWM wygląda to dokładnie tak jak opisałeś, poniżej rycinka:
    [Atmega32][C]Odtwarzanie PCM przez PWM. Częstotliwości.

    @A-T-O-M, co do tego:
    Cytat:

    Faktycznie lepiej wyłączać to tak jak napisałeś bo dzięki temu po zakończeniu odtwarzania PWM będzie nadal chodził i generował napięcie w okolicach 1/2 VCC.


    jeżeli chodzi o zmniejszeniu napięcia wystarczyłoby wpisywać gdzieś przed przerwaniem:

    OCR1AH = 0x00; 		// 0000 0000	BOTTOM = 0
    OCR1AL = 0xFF; 		// 1111 1111	TOP = 255
    
    //przerwanie
    //końcówka instrukcji przerwania tam gdzie wyłączenie:
    OCR1AH = 0x00; 		// 0000 0000	BOTTOM = 0
    OCR1AL = 0x00; 		// 0000 0000	TOP = 0
    
    // i znowu przed przerwaniem
    OCR1AL = 0xFF; 		// 1111 1111	TOP = 255
    


    Zadziała to jeżeli chodzi o obniżenie napięcia?
REKLAMA