Elektroda.pl
Elektroda.pl
X
Proszę, dodaj wyjątek dla www.elektroda.pl do Adblock.
Dzięki temu, że oglądasz reklamy, wspierasz portal i użytkowników.

[ATMEGA][C] Jak odtworzyć plik WAVE?

13 Sty 2010 20:25 6946 34
  • Poziom 11  
    Witam.
    Próbuje zrozumieć filozofie odtwarzania plików WAVE.
    Czy odtwarza je się tak że co określony czas próbkowania na jedno z wejście głośnika podaje się odpowiednie napięcie zapisane w polach danych (powiedzmy od 5v do 0v czyli 5/rozdzielczość i potem razy dane) ?
    Oczywiście potrzebna jest informacja ile kanałów jest zapisanych.
    Czy dobrze to rozumie i czy do odtwarzanie potrzebna jest jeszcze jakaś informacja z nagłówka?
    Widziałem też ze jest pole kompresji ale ja chyba nie będę w najbliższym czasie bawił się tym, wiem że wartość jeden w tym polu oznacza brak kompresji.
    Układem odtwarzającym będzie ATmega a program napisze w C.
  • Poziom 11  
    No tak do takich informacji dotarłem, tylko nie wiem czy mój tok rozumowania jest dobry ?
  • Poziom 11  
    Udało mi się odtworzyć dźwięk zapisany w tym formacie, ale jakość nie najlepsza. Próbowałem coś z tym zrobić kombinowałem z PWM ale ciągle słychać szumy, w związku z tym chyba trzeba zastosować jakiś przetwornik DAC. Co nadaje się do takiego zastosowania ? R-2R czy może jakiś układ scalony ? Jeszcze nie miałem okazji bawić się jakikolwiek przetwornikiem cyfrowo analogowym dlatego proszę o pomoc.
  • Poziom 11  
    Cyba wole zostać przy C. Tak więc co kupić jakąś układ czy tą drabinkę zrobić?
  • Poziom 43  
    Nikt nie mówi że to musi być BASCOM. Chodzi o sposoby które polepszają jakość dźwięku.
    Pokaż swój kod. Pewnie masz duży Jitter i stąd szum. U nas jakość dźwięk jest wręcz krystaliczny a to wszystko na PWM i to nawet bez filtru RC.
  • Poziom 11  
    Timer jest chyba buforowany czyli od razu nie ustawia danej wartości tylko po dojściu do jednego z krańców(liczy w górę a potem w dół) czy tak?
    16000000/44000=363,(63).
    Czyli 363 takty zegara pomiędzy kolejnymi przerwaniami.
    Czy dobrze to rozumiem?
    Czy jest możliwość żeby PWM zliczał od 0 do zadanej wartości a potem się zerował?
    W moim kodzie odtwarzane są pierwsze 255 komórek ze zmiennej sector a w tym samym czasie do zmiennej sectorb przepisywana jest druga połowa zmiennej sector,
    po przekroczeniu 255 czytaną zmienną staje się sectorb a do zmiennej sector wpisywane są nowe wartości z karaty SD.

    Code:


    unsigned char sector[512],sectorb[256];//zmienne globalne

    Code:


    TCCR2 |= (1 << WGM21); // Ustawia timer w tryb CTC
    OCR2 = 5; // Ustawia wartość pożądaną
    TCCR2 |= (1 << CS22); // Ustawia timer z preskalerem Fcpu/64
    TIMSK |= (1 << OCIE2); // Zezwolenie na przerwania dla CTC
    sei(); // Zezwolenie globalne na przerwania


    TCCR1A|= (1 << COM1A1)|(1 << COM1B1)|(1 << WGM10)|(1 << WGM11);//0xA3 10 bit PWM
    TCCR1B|=(1 << WGM12)|(1 << CS10);//0x09;
    TCNT1=0x00;
    DDRD = 255;
    while(1){}


    Code:

    void NowySector(void){
          
          razP=0;
          adresDzwi=adresDzwi+512;
          SD_READ(adresDzwi);
    }



    void PrzZjDd(void){
    unsigned int iv;
          for(iv=0;iv<256;iv++){
             sectorb[iv]=sector[iv+256];
          }
    }


    ISR(TIMER2_COMP_vect)
    {

     

     
    if ( pwmI < 256) {
           OCR1A = sector[pwmI+1];
       }
          


       if ( pwmI >= 256) {
       
           OCR1A = sectorb[(pwmI+1)-256];
       

       }


       if ( pwmI == 256) {
          NowySector();   
       }


    pwmI+=2;
       if ( pwmI >= 511) {
          pwmI=0;
          PrzZjDd();
       }


    }
  • Poziom 30  
    Po mojemu to możesz mieć problem z formatem danych - sprawdź, czy poprawnie interpretujesz kolejne próbki. To raczej nie jest kwestia przetwornika D/A.
  • Poziom 43  
    No Timer jest "buforowany" ale nie o to mi chodziło. Chodziło mi o buforowanie większej ilości danych. Ale to masz skoro masz dwie tablice.
    Czy mi się zdaje czy w przerwaniu odczytujesz sektory?
  • Poziom 11  
    Format jest dobry bo słychać to co byś powinno. A z tym przepisywaniem to wcześniej miałem to w głównej funkcji ale później to przeniosłem tak jak teraz bo nie działało tak jak chciałem, ale chyba zile to wymyśliłem bo w tej sytuacji, jeśli podczas przepisywania albo wczytywania wystąpi przerwanie to nie zostanie obsłużone. A jak to jest z tym PWM? atom1477 powiedz mi, jak działa ten wasz PWM. Z tego co widziałem to jest 8bitowy i pracuje w trybie Clear Up czyli tym odwróconym. Ale nie bardzo rozumiem co tam na dole się z nim dzieje?
  • Poziom 43  
    PWM działa tak samo jak u Ciebie. A to czy jest Clear Up czy Clear Down nie ma tutaj żadnego znaczenia. Czyli nie w tym jest błąd.
    Szczerze mówiąc nie bardzo rozumiem Twój kod. Co masz w funkcji main?
    Możesz jakoś nagrać ten dźwięk i go tutaj wrzucić?
    Mi się wydaje że po prostu co sektor masz przerwę bo wywołujesz funkcję odczytującą nowy sektor.
  • Poziom 11  
    Ja wymyśliłem to tak, mam zmienną sector[512] i do tej zmiennej wpisuje sektor X z karty SD potem konfiguruje timery, Timer 1 jako PWM a Timer 2 wytwarza przerwania w częstotliwości 44000 Hz, co kolejne przerwanie wpisywana jest wartość ze zmiennej sector[pwmI] do OCR1A i zwiększane jest o 2 pwmI bo (bo próbka ma 16 bitów, mniej znaczący bajt omijam), i tak w koło gdy wartość pwmI wyniesie 256(do zmiennej sector[512] wpisywany jest sektor X+512 z karty SD) to od tego momentu odczytywana jest wartości z drugiej tablicy sectorb[] (na początku jest pusta dopiero po pierwszym wyzerowaniu zmiennej pwmI zapisywana jest do niej tylna części tablicy sector[], czyli sectorb[od 0 do 255]=sector[od 256 do 511]). Gdy pwmI będzie miała 512 zostanie wyzerowana i rozpączętete zostanie odtwarzanie świeżego sektora a do tej mniejszej zmiennej zostanie przepisana tylna cześć większej(sectorb[od 0 do 255]=sector[od 256 do 511]). Ten kod który podałem rzeczywiście blokuje przerwania, funkcje NowySector() i PrzZjDd() powinny byś wywoływane z funkcji main. W tym waszym kodzie pracuje jeden zegar a u mnie dwa, jak kontrolujecie częstotliwość czy wartości do PWM wpisywana jest co 512 taktów zegara(od 0 do 255 i od 255 do 0) czy tak ?
  • Poziom 43  
    U nas odczyt jest w programie głównym.
    A odtwarzanie w przerwaniu.
    Co dwa przerwania dokładnie. U nas nie kontrolujemy częstotliwości PWMa. Ustawiliśmy na stałe i tyle. Ale to narzuca ograniczenie na pliki bo wszystkie muszą mieć próbkowanie 16kHz bo PWM pocina na 32kHz.

    Powinieneś zrobić jakieś buforowanie.
    SD_READ to jest jakaś standardowa funkcja z jakiejś biblioteki czy sam ją pisałeś?
    Fajnie by było jak by dało radę zmusić ją do odczytywania mniejszych ilości danych niz 512B. Ale tak też będzie działało tylko program trzeba będzie trochę inaczej napisać.

    Dodano po 11 [minuty]:

    Czekaj czekaj, u Ciebie jeden Timer robi za PWM a drugi za kontroler częstotliwości próbkowania? Powiedzmy PWM pracuje ciągle na 100kHz ale tym drugim Timerem ustawiasz 44100, 38000, 32000 albo 16000Hz?
    Jak tak to masz ogromny Jitter.
    Powiedzmy ustawiasz 44100Hz.
    100kHz/44,1kHz = 2,26.
    Czyli nie jest to pełna wielokrotność i dane do PWMa będą trafiały w na różne okresy. Jedna próbka trafi tam na dwa okresy PWMa a inna na 3 okresy. Tych co trafia na 2 okresy będzie trochę więcej, tak że średnia wyniesie właśnie te 2,26.
    (3-2)/(3+2) = 20%.
    Czyli masz Jitter aż 20%. Najgorszy sprzęt audio jaki może być ma zniekształcenia może 1…10%, a Ty masz 20%.
    Niestety Timer PWM musi pracować na częstotliwości dokładnie równej częstotliwości próbkowania albo na jej pełnej wielokrotności.
    A jak się tego nie da uzyskać to trzeba odtwarzać plik wolniej albo szybciej. A i jak to jest be to trzeba drabinkę R-2R która już PWMem nie jest i co za tym idzie nie wymaga żadnej zgodności częstotliwości.
  • Poziom 11  
    No właśnie dokładnie tak, wcześniej miałem kilka kombinacji, próbowałem resetować ten PWM no ale to też było kiepskie bo takty się marnowały. Ja chciałbym odtwarzać z rożną częstotliwością dlatego nastawałem na te DAC. Będzie to na pewno wygodniejsze i z dużą dokładnością, tylko nie wiem co będzie lepsze drabinka czy scalka ? A SD_READ jest z programu znalezionego w internecie, w każdym razie można tą funkcje modyfikować. Myślę że ten pomysł z jedna większa i jedną mniejszą tablicą jest dobry bo z jednej dane są pobierane a do drugiej zapisywane, i tylko raz odczytuje dany sektor, co skraca czas bo nie odczytuje dwa razy tego samego sektora z karty.
  • Pomocny post
    Poziom 43  
    Ale ja nie mówię żeby odczytywać dwa razy ten sam sektor, tylko żeby zmodyfikować funkcję odczytującą aby odczytywała mniejszą ilość danych.
    Ja bym zrobił tak, że była by jedna tablica 512B, i jak odtwarzanie przekroczy 256, to odpalana jest funkcja czytająca sektor. I wysyła ona żądanie odebrania 512-tu bajtów, oraz zaczyna odczytywać, ale odczytuje tylko 256B. Później czeka aż odtwarzanie przewinie się do 0 i tedy zabiera się za odczytywanie kolejnych 256B i po tym odsyła potwierdzenie odebrania 512B.
    I zaczyna oczekiwać na możliwość odebrania kolejnych danych.
    Wygodniej było by to rozbić na dwie funkcje:
    Odczyt_256_1: gdzie wysyłana była by komenda i odczytywane 256B do pierwszej połówki bufora.
    Odczyt_256_2: gdzie odczytywane było by pozostałe 256B do drugiej połówki bufora i odczyt był by zakańczany wysłaniem potwierdzenia do karty.

    Coś takiego:
    Code:

    main
    {
        Addr = 0;

        while (Buf_pos < 256);
        Odczytaj_256_1(Addr);
        while (Buf_pos > 100);
        Odczytaj_256_2(Addr++);
    }



    ISR(TIMER2_COMP_vect)
    {
        OCR1A = Buffer[Buf_pos];
        //PortB = Buffer[Buf_pos];      //PWM albo to
        Buf_pos++;
        Buf_pos &= 0x01FF;
    }


    Dodano po 7 [minuty]:

    Acha. I wystarczy Ci drabinka R-2R.
  • Poziom 11  
    Aha czyli przytrzymać odczyt ;) o tym nie pomyślałem.
  • Poziom 43  
    No właśnie - przytrzymać. Czy raczej wstrzymać.
    U nas też byśmy tak zrobili ale BASCOM na to nie pozwalał. Więc zrobiliśmy inaczej, ale podobnie. Odczytujemy partiami tak aby zawsze być tuż za buforem odczytu. Gdy kolejna próbka zostanie odtworzona to na jej miejsce jest odczytywana nowa z karty SD. U Ciebie też będzie partiami, tyle że bardzo dużymi bo po aż 256B.
  • Poziom 11  
    Zrobiłem tą drabinkę, no słychać tylko szum też słychać. Wydaje mi sie że to przez to że niema przeciwieństwa czyli:
    Code:
    PORTD=100;
    
    PORTA=~PORTD;

    Czy mam racje ?
  • Poziom 43  
    Nie czaję. Drabinkę masz na PortA czy na PortD?
  • Poziom 11  
    Na port D.
    Chodzi oto żeby na jedną konikówce głośnika podać coś a na druga przeciwieństwo.
    Czyli dać drugą drabinkę.
  • Poziom 43  
    Ale po co? przy sterowaniu PWM mogło by to zwiększyć moc, ale tutaj i tak musisz mieć wzmacniacz (analogowy) więc po co Ci to. Wystarczy Ci jeden port. Gorzej że raczej musi być cały (całę 8 bitów).

    Dodano po 6 [minuty]:

    Dobra, ale sprawdzasz to na takim samym kodzie? To znaczy na prawie takim samym (w jednym OCR1A a w drugim PortD)?
    Jak masz szum to masz coś nie tak z drabinką. Najprędzej to pomyliłeś MSB z LSB, czyli zrobiłeś motylka na porcie. Wtedy właśnie taki efekt jest.
  • Poziom 11  
    No sprawdziłem już i nic ten drugi port nie pomógł. Natomiast trafiłem na coś dziwnego podczas testowania programu w AVR studio, mianowicie w przerwaniu od timera do zmiennej wpisuje 1 po powrocie do programu głównego gdy testuje tą zmienną czy jest różna od 0 okazuje się że nie jest gdy najadę kursorem na tą zmienną pisze ze ma wartości 1, próbowałem też tą zmienną wpisać do PORTD ale też okazuje się że jest pusto. Czy to może być wina kodu ?
  • Pomocny post
    Poziom 43  
    No w pewnym sensie tak. Ale kod sam się nie napisał.
    Potaż ten kod. Ale cały.
  • Poziom 11  
    Nawet próbowałem przenieść wpisywanie poza przerwanie do innej funkcji ale nie pomogło.
    Chodzi o zmienne secDP secPP.
    Kod w całości jest bardzo długi bo zawiera nie tylko to ale jeśli chcesz go zobaczyć w całości, powiedz to go wstawię.
    Nadole jest kod odpowiedzialny za granie.

    Code:


    //#### CONFIG ####

    #define F_CPU 16000000UL  // 8 MHz

    #define SPIPORT PORTC
    #define SPIDDR DDRC
    #define CS 0
    #define CLK 7
    #define SDA 5
    #define RESET 1
    #define SS 4


    #define USR UCSRA
    #define UCR UCSRB
    #define UBRR UBRRL
    #define BAUD_RATE 38400
    #define PWMout OCR1A
    #define PWMout2 OCR1B

    #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







    //#define MODE565

    //#################

    #include <avr/io.h>


    #include <avr/io.h>
    #include <avr/delay.h>
    #include <avr/pgmspace.h>
    #include <avr/interrupt.h>


    #include <avr/interrupt.h>
    #include <avr/signal.h>
    #include <inttypes.h>
    #include <avr/iom16.h>





    #define cbi(reg, bit) (reg&=~(1<<bit))
    #define sbi(reg, bit) (reg|= (1<<bit))

     
    #define   C0S PORTB &= ~(1 << SPICS);
    #define   C1S PORTB |= (1 << SPICS);


    #define RD7 PORTD &= ~(1 << 7);
    #define RD6 PORTD &= ~(1 << 6);

    #define SD7 PORTD |= (1 << 7);
    #define SD6 PORTD |= (1 << 6);

    #define CS0 cbi(SPIPORT,CS);
    #define CS1 sbi(SPIPORT,CS);
    #define CLK0 cbi(SPIPORT,CLK);
    #define CLK1 sbi(SPIPORT,CLK);
    #define SDA0 cbi(SPIPORT,SDA);
    #define SDA1 sbi(SPIPORT,SDA);
    #define RESET0 cbi(SPIPORT,RESET);
    #define RESET1 sbi(SPIPORT,RESET);













    #define byte unsigned char
    byte n=0;
    byte s1,s2;
    byte r,g,b;

    unsigned int pwmI;
    int secPP;
    int secDP;
    int idrG;

    unsigned char sector[512];//,sectorb[256]
    char bufo;
    uint32_t ostatniAdres;
    unsigned long long int adresFont=0,adresDzwi=0;


    void spi_init(void);
    void spi_zapisz_bajt(unsigned char dana);

    void znak(unsigned char c,unsigned char x,unsigned char y); 
    void sendCMD(byte cmd);
    void sendData(byte cmd);

    void setPixel(byte r,byte g,byte b);

    void spi_stop(void);
    void spi_start(void);
    void shiftBits(byte b);
    void tekst(char *pString,char x,char y);
    void waitms(int ms) {
      int i;
      for (i=0;i<ms;i++) _delay_ms(1);
    }


    void secDPF(void){
    secDP=1;
    }
    void secPPF(void){
    secPP=1;
    }


    ISR(TIMER1_COMPA_vect){

       PORTD = sector[pwmI+1];

       pwmI+=2;
       if ( pwmI > 512) {
          pwmI=0;
          secDPF();
          }

       if ( pwmI == 256) {
          secPPF();
          }


    }



    //=======================================================================
    int main (void) {






    DDRD=0b11111111;


    TCCR1B |= (1 << WGM12); // Ustawia timer w tryb CTC
    OCR1A = 725; //363 Ustawia wartość pożądaną na 1Hz dla preskalera 64
    TCCR1B |= (1 << CS10); // | (1 << CS11)) Ustawia timer z preskalerem Fcpu/64
    TIMSK |= (1 << OCIE1A); // Zezwolenie na przerwania dla CTC
    sei(); // Zezwolenie globalne na przerwania


       



    while(1)
    {

       if (secPP != 0) {
          adresDzwi=adresDzwi+512;
       //   SD_READP1(adresDzwi);
          secPP=0;
          SPDR=0;
       }



       if (secDP != 0) {

          secDP=0;
       }


    }
    }
  • Pomocny post
    Poziom 43  
    Było 10000000000000000000 razy:
    Code:

    volatile int secPP;
    volatile int secDP;
  • Poziom 11  
    Dziękuje. Pierwszy raz się z tym spotykam. Zabieram się za dalszą zabawę.

    Dodano po 1 [godziny] 47 [minuty]:

    Ok działa jakość prawie dobra na pewno lepiej nisz przedtem, ale chyba che więcej.
    Odnośnie drugiego port.
    Wydaje mi się że dziwek zapisany w wav nie jest zapisany w przedziale od 0 w górę, tylko od -x 0 +x.
    I chyba przy wykorzystaniu drugiego portu będzie można uzyskać coś takiego.
    Tak jak na zdjęciu cicho jest na środku.
    Chyba że się mylę ?
    [ATMEGA][C] Jak odtworzyć plik WAVE?
  • Poziom 43  
    No tak. Masz 16bitów signed. Ale jak widziałem dekodujesz to (choć dość dziwnie).
    Ale drugi port nie ma tutaj nic do rzeczy. Przed podaniem tego na port czy też na dwa porty i tak będziesz to musiał zdekodować.
  • Poziom 11  
    Wydaje mi się że ot jest tak.
    Gdy jest wartość 128 to daje tyle na jeden port i przeciwieństwo na drugi czyli 127 i różnicy prawie niema a gdy na jeden port podaje 1 to na drugi port przeciwieństwo czyli 254, to pomiędzy tymi wartościami jest spora różnica. Tak mi się wydaje, i to chyba będzie to dekodowanie. Tak to ma być? A jak nie tak to jak.