Elektroda.pl
Elektroda.pl
X
Please add exception to AdBlock for elektroda.pl.
If you watch the ads, you support portal and users.

Mały odtwarzacz MP3,AAC,MP4 na AT91SAM7S256

Tytus Kosiarski 14 May 2010 22:30 37993 51
Nazwa.pl
  • Mały odtwarzacz MP3,AAC,MP4 na AT91SAM7S256


    Witam wszystkich :)
    Chciałbym zaprezentować wykonany i uruchomiony przeze mnie mały odtwarzacz MP3. Wykonałem go w ramach poznawania możliwości mikrokontrolera AT91SAM7S256 i nauki programowania w języku C.
    Dane techniczne odtwarzacza:
    1. obsługa kart SD (uruchamiałem na karcie SDHC 8GB) z systemem plików FAT32
    2. odczytywanie długich nazw plików i podkatalogów (obsługiwane nazwy do 80 znaków)
    3. możliwość wymiany dowolnych plików z komputerem poprzez USB (odtwarzacz widziany przez komputer jako Mass Storage Device). Testowane pod Win2000 i WinXP
    4. zasilanie z 1 akumulatora NiMH AA/R6
    5. czas pracy przy zasilaniu z akumulatora: ok.10...11h (z akumulatora 2450mAh)
    6. możliwość ładowania akumulatora poprzez USB lub ładowarką 5V / 500mA (np. odpowiednio dostosowaną do tego ładowarką od telefonu Sony Ericsson T280i, którą akurat mam)
    7. możliwość programowania i debugowania poprzez złącze "mini" JTAG. "Mini", bo wykorzystane są tylko sygnały SRST, TDO, TCK, TDI, TMS oraz GND i Vcc, co pozwoliło mi zastosować listwę goldpin 2x5 pin, widoczną na zdjęciach wnętrza odtwarzacza (normalnie jest to 2x10 pin)
    8. interfejs użytkownika: LCD 2x16 znaków, 5 podświetlanych przycisków (4 sterujące i 1 włączający zasilanie) oraz 2 LED
    9. odtwarzanie plików MP3 VBR i CBR, 128...320kb/s, fs=44,1kHz i 48kHz
    10. wymiary odtwarzacza (Długość x Szerokość x Grubość) 84x60x22mm
    11. masa 105g

    Odtwarzacz do dekodowania MP3 używa biblioteki dekodera MP3 z Atmel'owego projektu "AT91SAM Internet Radio" (PDF i kod źródłowy na www.atmel.com). Biblioteka dekodera MP3 jest z Helix Community, ale dopiero skompilowała mi się biblioteka z Atmel'owego projektu. Obsługę USB zrobiłem na podstawie pliku CDC_ENUMERATE.C z archiwum at91sam7s_usart_usb_20080916.zip (przejrzyście i zrozumiale według mnie napisana funkcja obsługi USB w tym pliku). Niestety, nie pamiętam już, z jakiej www ściągnąłem to archiwum, toteż dołączam go do dokumentacji odtwarzacza.

    Działanie odtwarzacza jest następujące: po włączeniu zasilania, przez 2 sekundy na LCD wyświetlana jest nazwa partycji (Volume Label). W przypadku karty SD będzie to prawdopodobnie nazwa producenta karty (u mnie tak jest). Po upływie tych 2 sekund, gdy nie jest dołączone USB, na LCD pojawia się lista z zawartością głównego katalogu karty. Jedyne sortowanie, jakie zaimplementowałem w programie odtwarzacza, to wyświetlanie najpierw nazw podkatalogów, a poniżej nich nazw plików. Toteż podkatalogi są wyświetlane na tej liście w takiej kolejności, w jakiej są zapisane na karcie. To samo dotyczy plików. Przewijanie listy odbywa się za pomocą przycisków przewijania/regulacji głośności w górę i w dół. Zawsze tylko pierwsza pozycja na tej liście jest aktywna, t.zn. wybierana jest przyciskiem odtwarzaj/wejdź do/wyjdź z podkatalogu. Czyli gdy na tej pozycji jest nazwa podkatalogu, to wciśnięcie tego przycisku spowoduje wejście do tego podkatalogu. Po wejściu do podkatalogu znów wyświetlana jest lista z zawartością podkatalogu (inne podkatalogi oraz pliki) oraz dochodzi jeszcze wyświetlanie ". [RootDir]" oraz ".. [UpDir]". Wybranie ". [RootDir]" powoduje wyjście do głównego katalogu karty (niezależnie od tego, jak głęboko się weszło w podkatalogi), wybranie ".. [UpDir]" powoduje wyjście do nadrzędnego podkatalogu w stosunku do tego, z którego się wyszło. Przed nazwą podkatalogu wyświetlany jest mały prostokąt (widoczny na zdjęciach), co pozwala odróżnić nazwę podkatalogu od nazwy pliku.
    Gdy natomiast na tej pozycji wyświetlana jest nazwa pliku, to wciśnięcie tego przycisku spowoduje jego odtwarzanie. Sposób poruszania się po tej liście przypomina trochę sposób poruszania się w Windows Commander, co było takim moim zamiarem. Odtwarzane są wszystkie pliki w wybranym podkatalogu, począwszy od wybranego pliku do końca listy. Podczas odtwarzania na LCD jest wyświetlana długa nazwa odtwarzanego pliku, bieżący czas utworu, całkowity czas trwania utworu oraz bitrate. By zmieścić taką ilość informacji na 16-znakowym LCD, zastosowałem samoczynne przewijanie tekstu podczas odtwarzania. Wykorzystałem możliwość wyświetlacza - przesuwania całego tekstu o 1 znak w lewo. Wystarczy cyklicznie, podczas odtwarzania, wysyłać komendę przesuwu o 1 znak wyświetlanego tekstu i otrzyma się taki efekt. Efekt ten jest pokazany na filmie (przepraszam za nienajlepszą jakość filmu - aparat trzymany w ręku) Po zakończeniu odtwarzania odtwarzacz powraca do wyświetlania listy podkatalogów i plików w aktualnie wybranym podkatalogu.
    Funkcje oszczędzające energię w akumulatorku:
    1. wygaszanie podświetlenia LCD i przycisków po 20sek. od momentu wykrycia braku aktywności przycisków. Oczywiście, naciśnięcie któregoś z 4 przycisków sterujących powoduje max rozjaśnienie podświetlenia.
    2. Całkowite wyłączanie odtwarzacza, gdy ten przez 2 minuty wyświetla tylko listę na LCD, a nie odtwarza
    3. W przypadku braku karty SD w slocie przy włączeniu odtwarzacza, wyświetlenie przez kilka sekund stosownego komunikatu na LCD i odtworzenie dźwięku ostrzegawczego, po czym całkowite wyłączenie odtwarzacza. To samo jest w przypadku wyjęcia karty podczas odtwarzania muzy
    4. Zastosowanie najniższej możliwej częstotliwości taktowania mikrokontrolera: 45,1584MHz przy odtwarzaniu plików z fs=44,1kHz i 49,152MHz przy odtwarzaniu plików z fs=48kHz. Dla tych częstotliwości uzyskałem płynne odtwarzanie bez trzasków, przy jednoczesnej programowej obsłudze innych rzeczy.
    Dodałem również pomiar napięcia akumulatora podczas samodzielnej pracy odtwarzacza (bez współpracy z komputerem). Napięcie akumulatorka mierzone jest przez przetwornik A/D wewnątrz mikrokontrolera. Gdy napięcie akumulatorka spadnie do 1V, to odtwarzany jest dźwięk ostrzegawczy, wyświetlany odpowiedni komunikat na LCD, po czym odtwarzacz wyłącza się.
    Schemat ideowy i projekt PCB zrobiłem w Protelu 99.
    Program odtwarzacza napisałem w języku C w środowisku Rowley Crossworks ver.1.7 i zaprogramowałem mikrokontroler przez JTAG przy użyciu Macraigor Wiggler. Podczas kompilowania trzeba ustawić opcje:
    Optimize for Size, bez tego kod wykonuje się zbyt wolno, co objawia się trzeszczeniem podczas odtwarzania.
    Heap size na rozmiar 23918 bajtów, by zaalokować zmienne używane przez dekoder MP3
    Stack size (IRQ mode) na wartosc 384 bajtów
    Stack size (User/System mode) na wartość 1024 bajty.
    Użycie pamięci przez program: 93kB pamięci programu Flash i 36kB RAM. Żadnych funkcji nie przenosiłem do RAM, wszystkie są we Flash-u.
    Koszty - niestety niemałe. PCB 60zł, części elektroniczne i obudowa (z TME) 280zł. Drobnicę SMD (kondensatory, rezystory, tranzystory) miałem w zapasach. Ale moim założeniem podczas prac nad odtwarzaczem było czegoś się nauczyć, a nie konkurować z producentami mp-trójek.
    Elektronika odtwarzacza i akumulatorek umieszczone są w obudowie KM-22 - stąd takie wymiary odtwarzacza, jak w danych technicznych.
    Etapy prac nad odtwarzaczem (poważniejsze problemy i sposoby rozwiązania ich) są tu: https://www.elektroda.pl/rtvforum/topic1413095.html
    Kod źródłowy napisany przeze mnie można dowolnie modyfikować. Proszę tylko o powiadomienie mnie, co zostało zmienione i w którym miejscu w programie i jakie dało to usprawnienie :) Oczywiście nadal obowiązują warunki licencyjne na kod dekodera MP3 i kod obsługi USB.
    Pozdrawiam, KT_priv

    Mały odtwarzacz MP3,AAC,MP4 na AT91SAM7S256 Mały odtwarzacz MP3,AAC,MP4 na AT91SAM7S256 Mały odtwarzacz MP3,AAC,MP4 na AT91SAM7S256 Mały odtwarzacz MP3,AAC,MP4 na AT91SAM7S256 Mały odtwarzacz MP3,AAC,MP4 na AT91SAM7S256



    Cool? Ranking DIY
    About Author
    Tytus Kosiarski
    Level 15  
    Offline 
    Has specialization in: programowanie mikrokontrolerów
    Tytus Kosiarski wrote 238 posts with rating 396, helped 10 times. Been with us since 2007 year.
  • Nazwa.pl
  • #2
    LnxTx
    Level 12  
    Czy dodanie obsługi AAC, Ogg czy FLAC było by trudnym zadaniem?
  • #3
    Urgon
    Level 38  
    AVE...

    Trzy rzeczy, które bym koniecznie zmienił na Twoim miejscu:
    - po rozpoczęciu odtwarzania odtwarzać powinien kolejne utwory;
    - oddzielny przycisk do wchodzenia i wychodzenia z menu;
    - dodatkowe tryby odtwarzania zmieniane przyciskiem: pojedynczy utwór w kółko, wszystkie utwory w katalogu w kółko, wszystkie utwory na karcie w kółko; losowy utwór w katalogu, losowy utwór na karcie. Domyślnie odtwarzanie katalogu w kółko.

    Dwie, które bym zmienił, ale nieobowiązkowo:
    - ładniejsza obudowa(barwiona pleksa byłaby świetna);
    - graficzny wyświetlacz, np. od komórki, sterowany bezpośrednio przez mikrokontroler lub z własnym sterownikiem(kilka miesięcy temu ktoś prezentował taki układ).

    Moim zdaniem bardzo fajny i wartościowy projekt...
  • #4
    Tytus Kosiarski
    Level 15  
    Witam
    Nie wiem, nie myślałem nad tym. Ale nie wydaje mi się, żeby to było trudne, byłaby to tylko kwestia zastąpienia bibliotek dekodera MP3 bibliotekami dekodującymi wspomniane formaty. Lub ewentualnie dodania tych bibliotek do obecnego programu z dekoderem MP3, by rozszerzyć możliwości odtwarzacza. Pytanie tylko, czy starczy na to RAM-u? Ale... wpisałem w Googlach "dekoder aac na arm". Pierwszy link: PRD10-GENC-001288-4-0.PDF z którego wynika, że chyba by starczyło RAM-u. Trza poszukać w Sieci odpowiednich bibliotek dekoderów. Pozdrawiam, KT_priv

    Dodano po 11 [minuty]:

    Do Urgon: Dzięki :) Odpowiadam:
    1. To jest. Napisałem( może niejasno, to wyjaśniam) odtwarzacz gra kolejno wszystkie pliki od wybranego przyciskiem Play, do końca listy. Jeśli będzie wybrany pierwszy plik na liście, to od pierwszego, drugi, trzeci,... Jeśli będzie wybrany trzeci plik, to od trzeciego, czwarty,... aż do końca.
    2. i 3. Do pomyślenia, może w jakiejś wolnej chwili...
    4. Można by, ale ja bym musiał raczej komuś zlecić zrobienie takiej obudowy
    5. Zabieram się już za obsługę graficznego LCD tym razem na STM32 Butterfly. Może za jakiś czas powstanie kolejna,bogatsza wersja odtwarzacza...

    Mam pytanie: Jak zdjąć opłatę za plik? Czy jeszcze raz to samo wysłać, ale z wyłączeniem punktów? Szybko wysłałem i nie zauważyłem odpowiedniej opcji.
    Pozdrawiam, KT_priv

    Dodano po 1 [godziny] 5 [minuty]:

    OK, widzę, że punkty za dokumentację są już usunięte.
  • #5
    Błażej 992
    Level 11  
    Naprawdę bardzo fajne, pomysłowe urządzonko ;)

    Mam pytanie: Komu zleciłeś wykonanie płytki ? Bo z tego co widzę to chyba nie jest do końca wykonana przez Ciebie, choc oczywiście mogę się mylic :)

    Pozdrawiam.
  • Nazwa.pl
  • #6
    g44
    Level 11  
    Hej
    Bardzo dobry pomysł, bardzo dobrze, że takie rzeczy są robione. Ten rozmiar obudowy jest bardzo poręczny dla użytkownika, ale i stawia pewne wyzwania przy upchnięciu wszystkiego :)
    Czy można prosić o schemat w pliku graficznym dla nie-posiadających Protela? Albo o listę części chociaż.

    Pozdrawiam
    g44
  • #7
    Tytus Kosiarski
    Level 15  
    Witam wszystkich :) Dzięki :)

    Wykonanie PCB zleciłem w Satland Prototype ( www.prototypy.com.pl )

    Dołączam schemat odtwarzacza w pliku MS Word.
    Pozdrawiam, KT_priv
  • #8
    chaka
    VIP Meritorious for electroda.pl
    A co z jakością dźwięku? Jakie stosowałeś przetworniki DA? Jak realizowana jest regulacja głośności?
  • #9
    Tytus Kosiarski
    Level 15  
    Jakość dźwięku słuchowo jest dobra. Jakość CD może byłaby tu nadużyciem(chociaż, kto wie, nie mam audiofilskiego słuchu), ale gra to jak dobre radio FM. Porównywalnie z mp-trójką na STA013 i CS4334, którą robiłem ok. 1,5 roku temu. Zastosowałem w tym odtwarzaczu DAC UDA1330ATS (do kupienia tu: www.seguro.pl/sklep/?zobacz=4301&producent= ). Do regulacji głośności wykorzystałem możliwość regulacji poziomu sygnału wyjściowego w UDA1330 za pośrednictwem jego interfejsu L3 (z PDF-a tego scalaka). Taki interfejs zrealizowałem programowo w ARM wykorzystując przerwania od timera TC1.
    Poniżej kod realizujący programowy interfejs L3:

    void compare_tc1_irq_handler(void) __attribute__ ((interrupt ("TC1")));
    void compare_tc1_irq_handler(void)   //procedura obsługująca przerwanie pochodzące od zrównania się zawartości TC1 z zawartością rejestru TC1_RC
    {
     unsigned int dummy;
     dummy=*AT91C_AIC_IVR; //odczyt z rejestru IVR,sygnalizuje, że rozpoczęłą sie obsługa przerwania 
     dummy=*AT91C_TC1_SR;  //odczyt z rejestru statusu TC1.Musi być ten odczyt,by możliwe było przyjmowanie dalszych przerwań od TC1
                          //gdy tego odczytu nie było, to przerwanie przyjmowane i wykonywane było tylko raz
     if ( (AT91F_PIO_GetInput(AT91C_BASE_PIOA) & L3CLK ) == L3CLK )  //realizacja sygnału zegarowego na wejściu L3CLK przetwornika UDA1330
     { //teraz przygotowanie nowego bitu z dana_do_wyslania do wystawienia na linię L3DATA
       AT91F_PIO_ClearOutput( AT91C_BASE_PIOA, L3CLK ); //jeśli było L3CLK = H ,to teraz wyzerowanie L3CLK = L (wytworzenie zbocza opadającego na L3CLK)
       if ((dana_do_wyslania & 0x01) == 0) //gdy najmłodszy bit danej do wysłania == 0,to
           AT91F_PIO_ClearOutput( AT91C_BASE_PIOA, L3DATA); //wyzerowanie linii L3DATA
       else //w przeciwnym razie, gdy najmłodszy bit danej do wysłania <> 0, to
           AT91F_PIO_SetOutput( AT91C_BASE_PIOA, L3DATA); //ustawienie linii L3DATA.W efekcie stan linii L3DATA będzie podążać za stanem najmłodszego bitu danej do wysłania
       dana_do_wyslania = dana_do_wyslania >> 1;
     }
     else
     {
       AT91F_PIO_SetOutput( AT91C_BASE_PIOA, L3CLK );  //w przeciwnym razie, gdy było L3CLK = L, to teraz ustawienie L3CLK = H (wytworzenie zbocza narastającego na L3CLK (zapisującego bit do DAC))
       licznik_wyslanych_bitow++; //zwiększenie o 1 licznika wysłanych bitów, bo poprzez wytworzenie zbocza narastającego na L3CLK dany bit już został wysłany do DAC
     }
     if ((licznik_wyslanych_bitow == 8) & (AT91F_PIO_GetInput(AT91C_BASE_PIOA) & L3CLK ) == L3CLK) dana_juz_wyslana = true;
     //gdy już liczba wysłanych bitów = 8 i poziom na L3CLK = H (już ostatnie zbocze narastające zostało wytworzone), to ustawienie dana_juz_wyslana = true
     *AT91C_AIC_EOICR = 0; //zapis do rejestru EOICR sygnalizuje, że zakończyła się obsługa bieżącego przerwania 
    }


    Sama regulacja głośności przyciskami Scroll/Volume Up / Down odbywa się poprzez zmianę wartości elementu nastawa_glosnosci:
    unsigned char konfig_glosnosci[] = {0x14,               //0b00010100 - adres UDA1330 i wybrany rejestr danych (2 najmłodsze bity = 00)
                                         nastawa_glosnosci / 2}; //dana wpisywana do rejestru danych (wartość nastawy głośności jest dzielona przez 2, by uzyskać
                                                                 //zmniejszenie szybkości regulacji głośności w górę i w dół )


    i później wysyłanie tablicy konfig_glosnosci do DAC podczas naciskania w.w. przycisków. Odbywa się to w funkcji volume_control w pliku main.c
  • #10
    galgann
    Level 16  
    Mam pytanko. Jaki uC można oprócz tego zastosować? Możliwe jest zrobienie czegoś podobnego na atmega8?
  • #11
    Urgon
    Level 38  
    AVE...

    Nie, gdyż Atmega8 nie jest w stanie udźwignąć tak dużego i skomplikowanego programu. Ponadto jest mikrokontrolerem ośmiobitowym, a ARMy są trzydziestodwubitowe...
  • #12
    Tytus Kosiarski
    Level 15  
    Witam
    Myślę, że dowolny ARM7 ze przynajmniej 128kB Flash i 48kB RAM (by zmieścił się program) mógłby być (np. LPC2148). PDF, w którym przedstawiono budowę odtwarzacza MP3 na tym mikrokontrolerze (ale z wykorzystaniem biblioteki libmad), jest tu: http://www.nxp.com/documents/application_note/AN10583.pdf U mnie ARM chodzi z ustawionym 0WS dla takich częstotliwości, o jakich mówiłem na początku tego wątku. Producent podaje, że 0WS może być dla fclk_max 30MHz, ale u mnie ten mikrokontroler chodzi stabilnie na takich częstotliwościach. Gdyby jednak wybrany mikrokontroler wymagałby ustawienia 1WS dla takiej fclk, to wtedy niestety, trzeba kombinować z programem, by uzyskać na tyle szybkie wykonywanie kodu, by odtwarzany dźwięk był bez trzasków i przerw.
    Niestety, Atmega8 nie nadaje się do budowy takiego odtwarzacza. Za mało Flash-a, RAM-u i 8-bit architektura. Owszem, do sterowania jakimś sprzętowym dekoderem MP3 (np. STA013, VS1001) to jak najbardziej. Temat taki do poszukania na Sieci, na elektrodzie również. Sam robiłem taki odtwarzacz MP3, gdzie STA013 był sterowany ale przez Atmega162, gdyż musiałem podpiąć jeszcze zewnętrzną RAM 62256.
    Pozdrawiam, KT_priv
  • #13
    WWektor
    Level 12  
    Witam :) Bardzo fajna MP3ka. Od jakiegoś czasu projektuje coś podobnego też na UDA1330 i ARM7 ATMELa, tylko bardziej z nastawieniem na narzędzie pracy (wyprowadzone interfejsy itp) mp3 to tylko dodatek. Chciałem się spytać jak jest z poziomem sygnału z UDA1330? Czy bezpośrednio można podpiąć słuchawki czy trzeba stosować wzmacniacz? Widziałem na Twoim schemacie bezpośrednio wyjście z DACa, czy to się sprawdza?
    Pozdrawiam :) Mam nadzieje, że już niedługo podzielę się swoją konstrukcją :)
  • #14
    Tytus Kosiarski
    Level 15  
    Witam
    Głośność jest duża przy ustawieniu max głośności. Powiedzmy, jak głos ze słuchawki tel. kom. W tej chwili słuchawki odtwarzacza leżą na stole, głośność mam ustawioną na ok. 2/3 max i jeszcze je słyszę. Oczywiście w hałaśliwym otoczeniu może to jeszcze nie wystarczyć.
    W pierwszej wersji miałem jeszcze wzmacniacz TDA7050 ze swoich zapasów, ale miałem duże zakłócenia. Toteż stwierdziłem, że go nie będzie, tym bardziej, że głośność przy bezpośrednim podpięciu słuchawek do DAC-a mi też odpowiadała.
    Również pozdrawiam :)
    KT_priv

    PS:Wprowadzam jeszcze poprawki w kodzie (w main.c) - wyeliminowanie krótkiego zaniku dźwięku przy wychodzeniu z menu regulacji głośności. Jak już będę pewny poprawnego działania kodu, to wystawię go na elektrodę.
  • #15
    symndz
    Level 16  
    Tytus Kosiarski wrote:
    Wykonałem go w ramach poznawania możliwości mikrokontrolera AT91SAM7S256 i nauki programowania w języku C.

    a masz protopyp?
  • #16
    Tytus Kosiarski
    Level 15  
    Prototyp, czyli co? Układ uruchomieniowy tego mikrokontrolera i działający odtwarzacz na tym układzie? Jeśli o to chodzi, to mam, jeszcze nie zdemontowałem pajęczyny kabelków w tym układzie.
  • #17
    symndz
    Level 16  
    Tytus Kosiarski wrote:
    jeszcze nie zdemontowałem pajęczyny kabelków w tym układzie.

    dokladnie to nazywam prototypem :)

    Dodano po 20 [minuty]:

    Tytus Kosiarski wrote:
    "dekoder aac na arm"

    to malo lepiej to uzyj .. http://code.google.com/p/opencore-aacdec/
  • #18
    Tytus Kosiarski
    Level 15  
    Witam
    Dzięki za link. Spróbuję odpalić ten kod w prototypie w wolnej chwili. Na razie jestem na etapie zgrywania plików z tego linku.
    Pozdrawiam,KT_priv
  • #19
    shg
    Level 35  
    Jak rozwiązałeś zmianę częstotliwości próbkowania?
    Masz dokładne 48 kHz, czy dokładne 44,1 kHz, czy też może obie?
  • #20
    Tytus Kosiarski
    Level 15  
    Witam
    Zmianę fs uzyskuję poprzez zmianę częstotliwości zegara mikrokontrolera przed rozpoczęciem odtwarzania pliku z fs=48kHz. Poniżej fragment kodu, który mi to realizuje:

            if (! (configured)) // gdy była to dopiero pierwsza poprawnie zdekodowana ramka (wtedy configured == false), to najpierw przed wysłaniem danych do portu SSC
            { //skonfigurowanie interfejsu SSC
              sampling_freq = mp3FrameInfo.samprate; //odczytanie częstotliwości próbkowania
              AT91F_PMC_EnablePeriphClock( AT91C_BASE_PMC, 1 << AT91C_ID_SSC ) ;  //enable the clock of the SSC
              if (sampling_freq == 48000) 
              { //gdy częstotliwość próbkowania odczytana z odtwarzanego pliku = 48kHz, to poniżej takie ustawienie clk mikrokontrolera, by przy jeszcze 0WS fsample było jak najbliższe 48kHz (co wpływa na szybkość odtwarzania)
                AT91F_CKGR_CfgPLLReg(AT91C_BASE_CKGR, AT91C_CKGR_USBDIV_0 | (256 << 16) | AT91C_CKGR_OUT_0 | AT91C_CKGR_PLLCOUNT | 100); //mikrokontroler taktowany jest zegarem (18,432 * (256+1) / 100) ~= 47,4MHz (dla takiej fclk mikrokontroler pracuje jeszcze stabilnie (bez zwisów))
             // AT91F_CKGR_CfgPLLReg(AT91C_BASE_CKGR, AT91C_CKGR_USBDIV_0 | (7 << 16) | AT91C_CKGR_OUT_0 | AT91C_CKGR_PLLCOUNT | 3); //mikrokontroler taktowany jest zegarem (18,432 * (7+1) / 3) = 49,152MHz (taka powinna być fclk mikrokontrolera)
                while (! (AT91F_PMC_IsStatusSet(AT91C_BASE_PMC, AT91C_PMC_LOCK))){}; //w pętli while odczekanie na ustabilizowanie się pętli PLL
                AT91F_SSC_Configure( AT91C_BASE_SSC, (47370240), (2*16*46260), 0, 0, AT91C_I2S_ASY_MASTER_TX_SETTING(16, 2), AT91C_I2S_ASY_TX_FRAME_SETTING(16, 2)); //(dla zegara 47,4MHz; fsample = fclk/1024 ~= 46,3kHz i jeszcze 0WS)
            //  AT91F_SSC_Configure( AT91C_BASE_SSC, (49152000), (2*16*48000), 0, 0, AT91C_I2S_ASY_MASTER_TX_SETTING(16, 2), AT91C_I2S_ASY_TX_FRAME_SETTING(16, 2)); //(dla zegara 49,152MHz; fsample = fclk/1024 = 48kHz i jeszcze 0WS)
                config_TC0(24000); //i ustawienie TC0,by generował przerwanie co 500ms dla takiej częstotliwości zegara
              }
              else { //w przeciwnym razie ustawienie zegara mikrokontrolera dla domyślnej wartości fsample=44,1kHz
                     AT91F_CKGR_CfgPLLReg(AT91C_BASE_CKGR, AT91C_CKGR_USBDIV_0 | (48 << 16) | AT91C_CKGR_OUT_0 | AT91C_CKGR_PLLCOUNT | 20); //mikrokontroler taktowany jest zegarem (18,432 * (48+1) / 20) = 45,1584MHz; fsample=44,1kHz
                     while (! (AT91F_PMC_IsStatusSet(AT91C_BASE_PMC, AT91C_PMC_LOCK))){}; //w pętli while odczekanie na ustabilizowanie się pętli PLL
                     AT91F_SSC_Configure( AT91C_BASE_SSC, (45158400), (2*16*sampling_freq), 0, 0, AT91C_I2S_ASY_MASTER_TX_SETTING(16, 2), AT91C_I2S_ASY_TX_FRAME_SETTING(16, 2)); //(dla zegara 45,1584MHz; fsample=44,1kHz)
                     config_TC0(22050); //i ustawienie TC0,by generował przerwanie co 500ms dla takiej częstotliwości zegara
                   }
              AT91F_SSC_EnableTx( AT91C_BASE_SSC);  //Enable the TX transmitter
              out_Samples = mp3FrameInfo.outputSamps;
              AT91F_SSC_SendFrame( AT91C_BASE_SSC, (char*)outBuf, out_Samples, (char*)outBuf, out_Samples); //wysłanie do portu SSC poprzez kanał DMA danych dla przetwornika D/A i wypełnienie drugiego bufora DMA
              configured = true;  //interfejs SSC jest już skonfigurowany
            }


    Niestety, w czasie wielu prób okazało się, że dla fclk = 49,152MHz i 0WS mikrokontroler już nie pracował stabilnie - czasem udawało się odtworzyć pliki z fs=48kHz, czasem nie (zawieszenie mikrokontrolera - tylko reset pomagał). Ustawienie 1WS powodowało trzaski podczas odtwarzania. Musiałem zmniejszyć fclk do ok. 47,4MHz, co dało mi fs=46240Hz. Ale, według mnie, szybkość odtwarzania nie uległa słyszalnemu spowolnieniu :)
    Aby uzyskać dokładnie fs=48kHz i odtwarzanie bez trzasków, to fclk mikrokontrolera musi być 73,728MHz i nastawione 1WS. Wtedy DAC trzeba tak skonfigurować, by pracował przy zegarze systemowym = 384*48000 = 18,432MHz na pinie SYSCLK (6). A taką częstotliwość na tym pinie będzie, po podziale fclk mikrokontrolera przez 4, stąd taka (duża) fclk mikrokontrolera. Obecnie mam fs = 44,1kHz; fs ~= 48kHz (dokładnie 46,24kHz); częstotliwość zegara systemowego DAC-a równą 256 * fs i tak DAC jest skonfigurowany.
    Pozdrawiam, KT_priv
  • #21
    Tytus Kosiarski
    Level 15  
    Witam wszystkich:)
    OK, wprowadziłem kilka usprawnień w programie odtwarzacza MP3. Oto one:
    1. Zmniejszenie trzasku /stuku w słuchawkach po zakończeniu regulacji głośności. Trzask ten był powodowany odświeżeniem zawartości LCD (czyli ponownym wyświetleniem nazwy odtwarzanego pliku i jego podstawowych parametrów) po wyjściu z regulacji głośności. Podczas tego odświeżania przerywane było wysyłanie danych do DAC, stąd był ten trzask. Uzyskałem to poprzez jednokrotne przypisanie
     *AT91C_RTTC_RTMR = 1; //ustawienie preskalera RTT w celu przyspieszenia przyjmowania przerwań od RTT, by uzyskać szybsze wystawianie znaków na LCD
    
    przed rozpoczęciem pętli dekodującej ramki MP3 w funkcji play_mp3_file w pliku main.c. Jednokrotne, bo nigdzie więcej w pętli dekodującej MP3 nie występuje to przypisanie (w pierwszej wersji programu odtwarzacza występowało). Dodatkowo przez to pozbyłem się krótkich, nieregularnie występujących przerw podczas odtwarzania (wyglądało to tak, jakby zawieszanie się mikrokontrolera). Domyślam się, że powodem tych przerw była zmiana zawartości rejestru RTT_MR podczas obsługi przerwania od RTT. Przywrócenie oryginalnej nastawy RTT_MR odbywa się poprzez wywołanie funkcji:
    config_RTT();  //ponowne ustawienie preskalera RTT,by przerwanie od niego następowało co 1ms przy fclk=32768Hz

    po wyjściu z pętli dekodującej ramki MP3. Samo ciało funkcji config_RTT() jest w pliku IO_init.c.
    Również skróciłem czas wyświetlenia na LCD podstawowych parametrów odtwarzanego pliku poprzez wyświetlanie tylko nazw tych parametrów (pominięte są liczby):
          wyswietl_tekst_na_LCD(0xC0,"Time:  ");
          wyswietl_tekst_na_LCD(0xCB,";total:");
          wyswietl_tekst_na_LCD(0xD8,";bitrate=");
          wyswietl_tekst_na_LCD(0xE4,"kbs");

    w funkcji volume_control w pliku main.c. Liczbowe wartości tych parametrów są aktualizowane i wyświetlane podczas każdorazowego obiegu pętli dekodującej MP3.

    2. Usunięcie trzasku pojawiającego się podczas zakończenia odtwarzania jednego pliku i rozpoczęcia odtwarzania następnego. To zjawisko występowało przy niektórych odtwarzanych plikach. Pomogło na to zmniejszanie wartości zmiennej bytesLeft, gdy funkcji MP3FindSyncWord nie udało się znaleźć bajtów rozpoczynających nagłówek ramki MP3 w tablica_bajtow_danych przechowującej sektor odczytany z karty SD. Ilustruje to poniższy kod:
          offset = MP3FindSyncWord(readPtr, (sizeof(tablica_bajtow_danych) - (readPtr - tablica_bajtow_danych))); //poszukiwanie nagłówka ramki MP3 w tablica_bajtow_danych począwszy od elementu wskazywanego wskaźnikiem readPtr; przeszukiwane będzie jeszcze (sizeof(tablica_bajtow_danych) - (readPtr - tablica_bajtow_danych)) pozostałych bajtów w tablica_bajtow_danych
          if (offset < 0) //gdy została przepatrzona cała tablica_bajtow_danych i nie udało się w niej znaleść nagłówka ramki MP3, to poniżej
          {
            bytesLeft = bytesLeft - (sizeof(tablica_bajtow_danych) - (readPtr - tablica_bajtow_danych)); //zmniejszenie bytesLeft (liczby pozostałych bajtów pliku) o liczbę przepatrzonych już bajtów, wśród których nie było nagłówka ramki MP3
            result = read_sector_from_current_or_next_cluster(numer_wpisu_poczatku_listy);  //wypełnienie tablicy_bajtow_danych nową porcją danych z karty SD   
            if ((result == 0xFF) | (result == 0)) //gdy koniec pliku (wtedy result == 0xFF) lub błąd odczytu karty (wtedy result == 0), to:
                 outOfData = 1; //ustawienie zmiennej outOfData, by można było wyjść z pętli dekodującej MP3
            else readPtr = tablica_bajtow_danych;  //w przeciwnym razie nowe dane z karty SD są poprawnie odczytane i teraz wskaźnik readPtr wskazuje pierwszy element tablicy_bajtow_danych z nowymi danymi
            continue;  //i skok do instrukcji while (!outOfData) - końca pętli dekodującej ramki MP3
          }
    

    Zmiana ta jest w funkcji play_mp3_file w pliku main.c

    3. Zaimplementowanie komend: CMD25 (Write multiple block) i CMD18 (Read multiple block) w celu przyspieszenia transferu plików pomiędzy kompem i odtwarzaczem, gdy ten jest dołaczony do kompa przez USB. Powiem szczerze, liczyłem na znaczne zwiększenie prędkości transferu po tym zabiegu, niestety przeliczyłem się :( Zyskałem ok. 4kB/s i obecnie mam ok. 200kB/s przy przesyle plików z odtwarzacza do kompa i ok. 75kB/s w odwrotnym kierunku.
    Funkcje obsługujące komendy CMD25 i CMD18, to odpowiednio write_multiple_sectors i read_multiple_sectors, obydwie umieszczone w pliku SD_card_functions.c. Funkcje te wywoływane są z poziomu funkcji odpowiednio: Write_To_Card i Read_From_Card, umieszczonych w pliku SCSI_commands.c i obsługujących transmisję danych przez USB. Jeśli ktoś miałby jeszcze jakiś pomysł odnośnie przyspieszenia transferu plików (szczególnie podczas kopiowania plików z kompa do odtwarzacza), to chętnie, chętnie... :)

    4. Uruchomienie możliwości przewijania w poziomie długich nazw plików i podkatalogów nie mieszczących się w całości na 16-znakowym LCD podczas wyświetlania listy plików i podkatalogów na nim. Zaprezentowane jest to na filmie. Ta możliwość jest zrealizowana w funkcji scroll_horizontal_long_names umieszczonej w pliku main.c. Funkcja ta jest wywoływana cyklicznie podczas obiegu głównej pętli programu umieszczonej w funkcji main() [wtedy tylko wyświetlana jest lista plików i podkatalogów], natomiast przesuw długiej nazwy pliku lub podkatalogu o 1 znak w lewo w tej funkcji odbywa się wtedy, gdy ustawiona jest zmienna tick (czyli co każde 500ms)

    5. Usunięcie wyświetlania przez 2sek nazwy producenta karty SD podczas wyjścia z podkatalogu do katalogu głównego karty. Nazwa ta wyświetlana jest tylko przy włączeniu zasilania odtwarzacza lub po odłączeniu USB od odtwarzacza, potem już nie. Uzyskałem to sprawdzając stan zmiennej power_on_or_USB_connected przed wyświetleniem tej nazwy na LCD w funkcji read_dir w pliku File_system_functions.c.

    6. Rozszerzenie możliwości wykrywania obecności karty SD w slocie i odpowiednia reakcja odtwarzacza w przypadku braku tejże również podczas wchodzenia do podkatalogu i przy wychodzeniu z niego do katalogu nadrzędnego (pierwsza wersja programu miała tą możliwość tylko podczas odtwarzania pliku i po włączeniu zasilania odtwarzacza. Próba wejścia do podkatalogu / wyjścia z podkatalogu przy wyjętej karcie SD kończyła się zawieszeniem odtwarzacza). Uzyskałem to poprzez sprawdzanie rezultatu odczytu zawartości karty przez funkcję read_dir po wykryciu naciśnięcia przycisku Play/Enter/Exit SubDir w funkcji main w pliku main.c. Poniżej kod, który realizuje mi tą możliwość:
        if (! (tablica_wpisow.is_directory)) //gdy wybrany wpis jest wpisem pliku,to
         {
           ... //tu realizacja pętli odtwarzającej wszystkie pliki w aktywnym podkatalogu począwszy od wybranego pliku aż do końca listy plików w aktywnym podkatalogu 
         }  
         else //w przeciwnym razie wybrany z listy wpis jest wpisem podkatalogu i wtedy:
         {
           unsigned int first_cluster_number = tablica_wpisow.first_cluster_nbr;  //numer pierwszego klastra zajętego przez wybrany z listy podkatalog
           liczba_wpisow = read_dir(rodzaj_karty, first_cluster_number, start_address); //odczytanie zawartości wybranego z listy podkatalogu, w zmiennej liczba_wpisow jest ilość plików i podkatalogów w wybranym podkatalogu
           if (liczba_wpisow == 0xFF) //gdy liczba_wpisow == 0xFF, to jest to błąd odczytu zawartości karty w powyższej funkcji read_dir, i wtedy:
           {
             AT91F_SPI_Disable( AT91C_BASE_SPI);  //wyłączenie SPI
             init_LCD(); //inicjacja wyświetlacza LCD         
             wyswietl_tekst_na_LCD(0x80,"No SDcard!Insert");  //tekst wyświetlany w pierwszym wierszu LCD
             wyswietl_tekst_na_LCD(0xC0,"SD with FAT32"); //tekst wyświetlany w drugim wierszu LCD
             play_mp3_sound(sound1, size1); //odtworzenie MP3 z dźwiękiem ostrzegawczym o źle włożonej karcie SD
             play_mp3_sound(sound1, size1); //odtworzenie MP3 z dźwiękiem ostrzegawczym o źle włożonej karcie SD
             wait(2000);
             shutdown_player(240); //wyłączenie odtwarzacza     
           }
           numer_wpisu_poczatku_listy = list_files_directories(1/*=numer_wpisu_poczatku_listy*/, liczba_wpisow, 2, 16); //teraz wylistowanie na LCD 2*16 nazw plików i podkatalogów począwszy od pierwszej pozycji zapamiętanej w zmiennej tablica_wpisow
                                                               // w zmiennej numer_wpisu_poczatku_listy jest numer wpisu pliku lub podkatalogu, którego nazwa jest wyswietlana w pierwszym wierszu LCD
           opoznienie_przewijania = 0; //wyzerowanie tej zmiennej,by rozpocząć przewijanie w poziomie długich nazw plików i podkatalogów podczas wyświetlania listy dopiero po osiągnięciu przez tą zmienną wartości czasu opóźnienia
         }//koniec warunku if (! (tablica_wpisow.is_directory)) spr.czy wybrany wpis jest wpisem pliku
    

    Dodatkowo, aby powyższy kod działał poprawnie, musiałem dołożyć jeszcze jeden rezystor 15k między gałąź zasilania +3,3V i linię portu PA12 (linia MISO mikrokontrolera). W moim odtwarzaczu rezystor ten jest w obudowie 0603 (SMD) i na PCB odtwarzacza wlutowałem go pomiędzy plusowy pad kondensatora C26 i pin 2 scalaka U5 (przy odrobinie zręczności da się go ładnie wlutować :) )
    Dołączam uaktualniony kod źródłowy programu, poprawiony schemat ideowy odtwarzacza oraz aktualne wsady do mikrokontrolera - doszły do mnie głosy, że część osób ma problemy z wygenerowaniem odpowiednich plików wynikowych z wykorzystaniem mojego kodu źródłowego.
    Pozdrawiam, KT_priv


  • #22
    mrh
    Level 18  
    a która część kodu odpowiada za znajdowanie pliku mp3 ???
  • #23
    Tytus Kosiarski
    Level 15  
    Czy chodzi Ci o wynajdywanie tylko plików MP3 na karcie i wyświetlanie ich na LCD? Jeśli tak, to żadna, bo nie implementowałem w kodzie funkcji filtracji plików MP3 spośród wszystkich plików na SD. Jest to na razie TYLKO odtwarzacz mp-trójek, toteż założyłem że na SD powinny być tylko pliki MP3. Niemniej jednak, ta mp-trójka funkcjonuje jako Mass Storage Device i możliwe jest wgrywanie na kartę SD z kompa dowolnych plików, które po wgraniu pojawią się na LCD podczas wyświetlania na nim listy plików i podkatalogów. Co więcej, można takie pliki wybrać do "odtwarzania" - skutkuje to niekiedy fajnymi efektami dźwiękowymi :D i tyle.
    Jeśli nie udzieliłem satysfakcjonującej Ciebie odpowiedzi, to proszę o doprecyzowanie pytania.
    Pozdrawiam, KT_priv
  • #24
    mrh
    Level 18  
    Chodzi mi o to, skąd odtwarzacz wie że to jest początek pliku mp3 ??
  • #25
    Tytus Kosiarski
    Level 15  
    Po wybraniu pliku do odtwarzania (realizuje to poniższy fragment kodu umieszczony w głównej pętli programu w pliku main.c):
       if ((! (AT91F_PIO_GetInput(AT91C_BASE_PIOA) & SW2)) & (~ play_key_pressed)) //gdy dopiero teraz naciśnięty przycisk SW2 - Play (czyli jeszcze w tym miejscu play_key_pressed == false),to:
       { 
         current_time = 0; //wyzerowanie bieżącego czasu, by prawidłowo ściemnić podświetlanie LCD podczas odtwarzania pliku
         dimming_LCD_backlight(current_time, aktualizacja_LCD_odtwarzanie); //ściemnienie podświetlania LCD po 10sek od momentu wejścia do podkatalogu
         play_key_pressed = true; //to zmiana stanu play_key_pressed.Potrzebne,by uniknąć sytuacji naprzemiennego wchodzenia do podkatalogu i wychodzenia z niego w przypadku przytrzymania przycisku Play
         wait(300);//i odczekanie na zanik drgań zestyków przycisku Play
         start_address = play_file_enter_subdir(numer_wpisu_poczatku_listy); //wywołanie funkcji play_file_enter_subdir,która zwraca adres pierwszego sektora karty SD zajętego przez wybrany plik lub podkatalog
         if (! (tablica_wpisow.is_directory)) //gdy wybrany wpis jest wpisem pliku,to
         {
           unsigned char licznik_odtworzonych_plikow = numer_wpisu_poczatku_listy; //zainicjowanie licznika odtworzonych plików
           do //poniższa pętla odtwarza wszystkie pliki począwszy od wybranego wartością numer_wpisu_poczatku_listy pliku do końca listy plików
           {
             play_mp3_file(licznik_odtworzonych_plikow); //wywołanie funkcji odtwarzającej wybrany plik.Funkcja ta korzysta również ze zmiennej start_address
    


    odtwarzacz przechodzi do realizacji funkcji play_mp3_file. W tej funkcji tuż po deklaracji użytych w niej zmiennych jest:
      result = read_sector(start_address + licznik, rodzaj_karty); //odczytanie nowych danych z pierwszego sektora karty SD należącego do klastra zajętego przez odtwarzany plik
      readPtr = tablica_bajtow_danych;  //teraz wskaźnik readPtr wskazuje pierwszy element tablicy_bajtow_danych

    W efekcie zmienna tablica_bajtow_danych jest wypełniona pierwszą 512-bajtową paczką danych odczytanych z wybranego pliku, a wskaźnik readPtr wskazuje pierwszy element tej tablicy. Następnie w pętli do...while dekodującej kolejne ramki MP3, po uprzednim wykonaniu funkcji odświeżających zawartość LCD i regulujących głośność, jest sprawdzenie warunku:
        if (((readPtr - tablica_bajtow_danych) + 4) > sizeof(tablica_bajtow_danych)) //spr.czy nagłówki kolejnych ramek mieszczą się w całości w tablicy_bajtow_danych. Gdy nie mieszczą
        {  //się,to oznacza, że fragment kolejnego nagłówka ramki MP3 jest na końcu tablicy_bajtow_danych i wtedy poniżej
    ...
        }
            else //w przeciwnym razie nagłówki kolejnych ramek MP3 mieszczą się w całości w tablicy_bajtow_danych i wtedy
        {
          offset = MP3FindSyncWord(readPtr, (sizeof(tablica_bajtow_danych) - (readPtr - tablica_bajtow_danych))); //poszukiwanie nagłówka ramki MP3 w tablica_bajtow_danych począwszy od elementu wskazywanego wskaźnikiem readPtr; przeszukiwane będzie jeszcze (sizeof(tablica_bajtow_danych) - (readPtr - tablica_bajtow_danych)) pozostałych bajtów w tablica_bajtow_danych
    
    

    Jako, że zaczynamy odtwarzanie od początku, to najpierw wykonają się instrukcje po słowie kluczowym else. A tu jest funkcja MP3FindSyncWord, której zadaniem jest znalezienie następującego ciągu binarnego 11111111 111 (czyli hex: 0xFF 0xEy, gdzie y to kolejne 5 bitów w tym drugim bajcie) w tej zmiennej tablica_bajtow_danych. Samo ciało tej funkcji (trochę zmodyfikowane przeze mnie - szczegóły w komentarzach) jest w pliku mp3dec.c . Taki ciąg binarny jest słowem synchronizującym początek każdego nagłówka każdej kolejnej ramki MP3. Funkcja ta zwraca wartość offset. Offset ten jest sprawdzany następnie przez dalszą część tego warunku:
          if (offset < 0) //gdy została przepatrzona cała tablica_bajtow_danych i nie udało się w niej znaleść nagłówka ramki MP3, to poniżej
          {
            bytesLeft = bytesLeft - (sizeof(tablica_bajtow_danych) - (readPtr - tablica_bajtow_danych)); //zmniejszenie bytesLeft (liczby pozostałych bajtów pliku) o liczbę przepatrzonych już bajtów, wśród których nie było nagłówka ramki MP3
            result = read_sector_from_current_or_next_cluster(numer_wpisu_poczatku_listy);  //wypełnienie tablicy_bajtow_danych nową porcją danych z karty SD   
            if ((result == 0xFF) | (result == 0)) //gdy koniec pliku (wtedy result == 0xFF) lub błąd odczytu karty (wtedy result == 0), to:
                 outOfData = 1; //ustawienie zmiennej outOfData, by można było wyjść z pętli dekodującej MP3
            else readPtr = tablica_bajtow_danych;  //w przeciwnym razie nowe dane z karty SD są poprawnie odczytane i teraz wskaźnik readPtr wskazuje pierwszy element tablicy_bajtow_danych z nowymi danymi
            continue;  //i skok do instrukcji while (!outOfData) - końca pętli dekodującej ramki MP3
          }
          readPtr += offset;  //w przeciwnym razie udało się znaleść nagłówek i wtedy zwiększenie readPtr, tak, by wskazywał na pierwszy bajt tego nagłówka 
          err = MP3GetNextFrameInfo(hMP3Decoder, &mp3FrameInfo, readPtr);  //pobranie informacji o ramce MP3 na podstawie znalezionego wyżej nagłówka
          if (err) {  //gdy nie uda się pobrać informacji (to nie był nagłówek, wtedy err <> 0)
                     readPtr = readPtr + 1; //to zwiększenie o 1 wskaźnika readPtr, by dalsze poszukiwanie nowego nagłówka rozpoczęło się od następnego bajtu po tym mylnie zakwalifikowanym jako początek nagłówka ramki MP3
                     continue; //i skok do instrukcji while (!outOfData) - końca pętli dekodującej ramki MP3
                   }
          //w przeciwnym razie udało się pobrać informacje o kolejnej ramce MP3 i wtedy poniżej obliczenie rozmiaru tej ramki (rozmiar ramki zależy również od wersji MPEG)

    i gdy jest on < 0, to oznacza, że w bieżącej zawartości tablica_bajtow_danych nie udało się znaleźć takiego ciągu binarnego. Wtedy (nadal w tym warunku) następuje wywołanie funkcji read_sector_from_current_or_next_cluster (w pliku main.c), która wypełnia zmienną tablica_bajtow_danych nową 512-bajtową porcją danych z pliku i cały cykl poszukiwania słowa synchronizującego w pętli do...while dekodującej ramki MP3 rozpoczyna się od początku. Natomiast, gdy udało się w bieżącej zawartości tablica_bajtow_danych znaleźć taki ciąg binarny (wtedy offset > = 0), to najpierw następuje zwiększenie wskaźnika readPtr o wartość offset i następnie wywoływana jest funkcja MP3GetNextFrameInfo (również z mp3dec.c), która sprawdza, czy rzeczywiście jest to nagłówek kolejnej ramki MP3. Funkcja ta zwraca wartość err = 0,gdy jest to słowo synchronizujące kolejny nagłówek ramki MP3 i wtedy realizowane są dalsze instrukcje (m.in. obliczenie zmiennej Frame_length - długość ramki MP3), w przeciwnym razie err<>0 i wtedy tylko zwiększenie wskaźnika readPtr o 1 i powrót na początek pętli do...while dekodującej ramki MP3.

    Mam nadzieję, że w miarę wyjaśniłem. Komentarze w kodzie też powinny być pomocne w zrozumieniu działania fragmentów kodu.
    Pozdrawiam, KT_priv
  • #26
    Tytus Kosiarski
    Level 15  
    Witam ponownie po dłuższej przerwie. W międzyczasie usunąłem jeszcze jeden zauważony błąd powodujący zawieszenie się odtwarzacza, a przedtem wyświetlenie śmieci na LCD. Błąd ten pojawiał się, gdy do odtwarzacza włożyło się nowo zakupioną lub pustą po formacie kartę SD. Błąd ten usunąłem modyfikując fragment funkcji main, jak poniżej:
    
    if (! (AT91F_PIO_GetInput(AT91C_BASE_PIOA) & AT91C_PIO_PA30))
       {    //gdy PA30 = L, to odtwarzacz NIE jest dołączony do portu USB w kompie i wtedy:
         liczba_wpisow = read_dir(rodzaj_karty, opis_partycji.first_cluster_num, opis_partycji.first_data_sector_address); //odczytanie zawartości głównego katalogu na karcie SD, w zmiennej liczba_wpisow jest ilość plików i podkatalogów w katalogu głównym na karcie SD
         if (liczba_wpisow == 0) //gdy na karcie SD nie ma plików i podkatalogów (karta była formatowana), to:
         { 
           AT91F_SPI_Disable( AT91C_BASE_SPI);  //wyłączenie SPI
           wyswietl_tekst_na_LCD(0x80,"No directories/");  //tekst wyświetlany w pierwszym wierszu LCD
           wyswietl_tekst_na_LCD(0xC0,"files on SD Card"); //tekst wyświetlany w drugim wierszu LCD
           play_mp3_sound(sound1, size1); //odtworzenie MP3 z dźwiękiem ostrzegawczym o źle włożonej karcie SD
           play_mp3_sound(sound1, size1); //odtworzenie MP3 z dźwiękiem ostrzegawczym o źle włożonej karcie SD
           wait(2000);
           shutdown_player(240); //wyłączenie odtwarzacza     
         }
         numer_wpisu_poczatku_listy = list_files_directories(1/*=numer_wpisu_poczatku_listy*/, liczba_wpisow, 2, 16); //teraz wylistowanie na LCD 2*16 nazw plików i podkatalogów począwszy od pierwszej pozycji zapamiętanej w zmiennej tablica_wpisow. W zmiennej numer_wpisu_poczatku_listy jest numer wpisu pliku lub podkatalogu, którego nazwa jest wyswietlana w pierwszym wierszu LCD
       }

    Powyższy fragment sprawdza, czy nie jest dołączony USB do odtwarzacza. Gdy nie jest, to odczytywana jest zawartość głównego katalogu karty. Realizuje to funkcja read_dir, która zwraca ilość plików i podkatalogów w głównym katalogu karty. Ta ilość przypisana jest do zmiennej liczba_wpisow. W przypadku, gdy karta była pusta, zmienna ta miała wartość 0. Przed poprawką przekazanie takiej wartości do następnej wywoływanej funkcji list_files_directories powodowało wyżej opisany błąd. Teraz taka sytuacja powoduje wyłączenie odtwarzacza (bo nie ma czego odtwarzać) po wyświetleniu stosownego komunikatu. Oczywiście po dołączeniu USB powyższy fragment jest pomijany, gdy odtwarzacz pracuje jako Mass Storage Device, stąd sprawdzenie warunku, czy dołączony USB.

    Następną poprawką jest pomijanie nieistniejącej nazwy karty (volume label). Realizuje mi to następujący fragment funkcji read_dir (z pliku File_system_functions.c):
    
    switch (tablica_bajtow_danych[11 + (32 * licznik_paczek)]) //sprawdzanie  11.bajtu w każdej paczce
    {
      case 0x08:  //analizowana paczka zawiera nazwę partycji
        if (power_on_or_USB_connected) //wyświetlenie tej nazwy tylko po właczeniu zasilania lub po odłączeniu USB
        {
          configure_LCD(0x01); //wyczyszczenie wyświetlacza LCD (Display Clear)
          wait(3);
          for (licznik_petli = 0; licznik_petli < 11; licznik_petli++) //pętla wystawiająca znaki etykiety karty SD na wyświetlaczu LCD
          {
            if (tablica_bajtow_danych[licznik_petli + (32 * licznik_paczek)] < 0x80) //spr,czy jest to 7-bitowy kod ASCII. Gdy tak,to: 
               wyswietl_znak_ASCII_na_LCD(0x82 + licznik_petli, tablica_bajtow_danych[licznik_petli + (32 * licznik_paczek)]);//wystawianie na LCD 7-bit ASCII znaków etykiety karty SD
            else //w przeciwnym razie może być to kod polskiej litery (w 8-bit ASCII) i wtedy
            { 
              if (tablica_bajtow_danych[licznik_petli + (32 * licznik_paczek)] == 0xE5) break; //gdy jest skasowana etykieta karty SD, to opuszczenie pętli wystawiającej znaki etykiety karty na LCD
              else //w przeciwnym razie jest to kod polskiej litery i poniżej wyświetlenie tej polskiej litery na LCD
              {
                wyswietl_polski_znak_na_LCD(0x82 + licznik_petli, (adres_CGRAM << 3), tablica_bajtow_danych[licznik_petli + (32 * licznik_paczek)]);
                adres_CGRAM++;  //zwiększenie adresu CGRAM, bo jedna polska litera została już wyświetlona
              }
            }
          }
          if (tablica_bajtow_danych[licznik_petli + (32 * licznik_paczek)] != 0xE5) //gdy jest obecna etykieta karty SD, to wyświetlenie jej przez 2 sekundy
          {                                                                         //razem z wyświetleniem napięcia baterii
            wyswietl_tekst_na_LCD(0xC0,"Vbatt=");
            AT91F_ADC_StartConversion(AT91C_BASE_ADC);  //uruchomienie przetwornika A/C
            while (! (AT91C_BASE_ADC->ADC_SR & AT91C_ADC_EOC5));  //i po odczekaniu, gdy Vbat jest już przetworzone na wartość cyfrową
            converted_Vbat = AT91F_ADC_GetConvertedDataCH5(AT91C_BASE_ADC); //to odczytanie cyfrowej wartości Vbat
            Vbat = ((2500 * converted_Vbat) + 14625) / 1979; //obliczenie rzeczywistego napięcia baterii na podstawie wartości odczytanej z ADC
            itoa(Vbat,txt_Vbat,10); //konwersja liczbowej wartości napięcia baterii na postać łańcuchową
            if (Vbat < 100) //gdy napięcie baterii < 1V to odpowiedni format łańcucha txt_Vbat, tak, by poprawnie były wyświetlane wartości napięcia < 1V
            {                
              txt_Vbat[2] = txt_Vbat[1];//przeniesienie części ułamkowej napięcia baterii na odpowiednie pozycje w łańcuchu txt_Vbat
              txt_Vbat[1] = txt_Vbat[0];
              txt_Vbat[0] = 0x30; //przed przecinkiem będzie wyświetlana liczba 0
            }
            wyswietl_znak_ASCII_na_LCD(0xC6,txt_Vbat[0]);
            wyswietl_znak_ASCII_na_LCD(0xC7,',');
            wyswietl_znak_ASCII_na_LCD(0xC8,txt_Vbat[1]);
            wyswietl_znak_ASCII_na_LCD(0xC9,txt_Vbat[2]);
            wyswietl_znak_ASCII_na_LCD(0xCA,'V');
            wait(2000);  
          }
        }
        break;
    

    Działanie: po formatowaniu karty wszytkie wpisy plików i podkatalogów w tablicy FAT są zerowane, toteż siłą rzeczy każdy 11.bajt w poszczególnych 32-bajtowych wpisach plików i podkatalogów w FAT równy jest 0 i wtedy nie będą wykonywane instrukcje po linii case 0x08. Natomiast, gdy istniała wcześniej nazwa karty, ale została ona skasowana, to na jej wyświetlenie nie pozwoli instrukcja:
    
    if (tablica_bajtow_danych[licznik_petli + (32 * licznik_paczek)] == 0xE5) break; //gdy jest skasowana etykieta karty SD, to opuszczenie pętli wystawiającej znaki etykiety karty na LCD
    

    gdyż skasowanie nazwy karty, tak jak skasowanie pliku, odbywa się poprzez zastąpienie pierwszego znaku nazwy kodem 0xE5.
    Przy okazji niejako uzyskałem możliwość stosowania polskich znaków w nazwie karty, gdyż pętla wystawiająca znaki nazwy na LCD sprawdza kod każdego znaku nazwy (poczynając od pierwszego znaku) i gdy kod ten > 0x80, to wpierw dalej sprawdza, czy jest to czasem kod 0xE5. Gdy nie jest (wtedy to jest kod polskiej litery w standardzie CP852, DOS Latin II), to następuje wystawienie odpowiadającej polskiej litery na LCD podczas wywołania funkcji wyswietl_polski_znak_na_LCD (plik polish_characters.c).

    Dorobiłem również taki "feature" :) w postaci pomiaru i wyświetlenia napięcia baterii podczas pokazywania nazwy karty, co widać na zaprezentowanym wyżej fragmencie kodu, oraz podczas odtwarzania pliku, gdy naciśnie się jednocześnie przyciski Volume UP i Volume DOWN. Fragment kodu realizujący mi tą właściwość umieszczony jest w funkcji play_mp3_file w pliku main.c i pokazany jest poniżej:
    
        if (! ( (AT91F_PIO_GetInput(AT91C_BASE_PIOA) & SW1) | ((AT91F_PIO_GetInput(AT91C_BASE_PIOA) & SW0) << 1) ))//gdy naciśnięte naraz przyciski SW1 i SW0(=L) (wtedy odczyt napięcia baterii),to:
        {
          if (! (show_battery_voltage)) //gdy wyświetlenie napięcia jest pierwszy raz po naciśnięciu przycisków SW1 i SW0
          {
            aktualizacja_LCD_odtwarzanie = false; //to wyzerowanie zmiennych aktualizacja_LCD_odtwarzanie i aktualizacja_LCD_glosnosc w celu 
            aktualizacja_LCD_glosnosc = false; //prawidłowego działania funkcji volume_control po zwolnieniu jednego z tych przycisków lub dwóch naraz
            configure_LCD(0x01); //wyczyszczenie wyświetlacza LCD (Display Clear)
            wait(15);
            configure_LCD(0x02); //dana konfigurująca wyświetlacz LCD(Cursor/Display Home) 
            wait(15);
            wyswietl_tekst_na_LCD(0x81,"Ubattery=");
            wyswietl_znak_ASCII_na_LCD(0x8B,',');
            wyswietl_znak_ASCII_na_LCD(0x8E,'V');
          }
          show_battery_voltage = true;  //i ustawienie zmiennej show_battery_voltage, by przytrzymanie naciśniętych przycisków SW1 i SW0 nie powtarzało powyższych instrukcji
          AT91F_ADC_StartConversion(AT91C_BASE_ADC);  //uruchomienie przetwornika A/C
          while (! (AT91C_BASE_ADC->ADC_SR & AT91C_ADC_EOC5));  //i po odczekaniu, gdy Vbat jest już przetworzone na wartość cyfrową
          converted_Vbat = AT91F_ADC_GetConvertedDataCH5(AT91C_BASE_ADC); //to odczytanie cyfrowej wartości Vbat
          Vbat = ((2500 * converted_Vbat) + 14625) / 1979; //obliczenie rzeczywistego napięcia baterii na podstawie wartości odczytanej z ADC
          itoa(Vbat,txt_Vbat,10); //konwersja liczbowej wartości napięcia baterii na postać łańcuchową
          wyswietl_znak_ASCII_na_LCD(0x8A,txt_Vbat[0]); //i wyświetlenie tej wartości na LCD
          wyswietl_znak_ASCII_na_LCD(0x8C,txt_Vbat[1]);
          wyswietl_znak_ASCII_na_LCD(0x8D,txt_Vbat[2]);
        }
        else //w przeciwnym razie :
        {
          show_battery_voltage = false; //wyzerowanie zmiennej show_battery_voltage,by prawidłowo pokazać wartość napięcia baterii gdy znów będą naciśnięte przyciski SW0 i SW1
          volume_control(numer_wpisu_poczatku_listy); //oraz wywoływana jesrt procedura nastawiająca głośność odtwarzania
        }
    

    Zastosowanie dodatkowej zmiennej show_battery_voltage było potrzebne, by tylko raz po naciśnięciu tych przycisków była wyświetlana informacja na LCD o pomiarze napięcia baterii. Natomiast non stop podczas naciskania tych przycisków odczytywana i prezentowana na LCD jest liczbowa wartość napięcia baterii.

    Dodatkowo wprowadziłem możliwość formatowania karty SD, gdy odtwarzacz współpracuje z kompem jako Mass Storage Device. Sprowadziło się to do zmodyfikowania instrukcji wyboru:
    
    switch (packet_CBW.CBWCB_Table[0])  //pole Operation Code w ramce SCSI
    

    umieszczonej w pętli obsługującej transmisję USB w funkcji main, do której dodałem:
    
     case 0x2F: //komenda Verify 
                    nbr_of_data = 0;
                    Send_CSW_packet(0, packet_CBW.CBWCB_Table[0], nbr_of_data); //teraz wypełnienie zmiennej tablica_bajtow_danych danymi z pakietu CSW i wysłanie jej do hosta
                    break;
    


    Jeszcze odnośnie plików MP3 z fsample=48kHz: podczas odtwarzania takich plików zdecydowałem się na taktowanie ARM-a zegarem 73,728MHz i ustawionym 1WS. Ta zmiana powoduje mi wzrost prądu pobieranego z akumulatora do 200mA (dla plików z fsample=44,1kHz i taktowaniu ARM-a zegarem 45,1584MHz, 0WS, prąd ten jest 160mA), ale w zamian mam poprawne i stabilne (bez zawieszeń) odtwarzanie plików z tym fs. Poniżej fragment kodu (w funkcji play_mp3_file z pliku main.c), w którym wybieram fclk ARM-a:
    
    MP3GetLastFrameInfo(hMP3Decoder, &mp3FrameInfo); //uzyskanie informacji o zdekodowanej ramce MP3
    if (! (configured)) // gdy była to dopiero pierwsza poprawnie zdekodowana ramka, to najpierw przed wysłaniem danych do portu SSC
    { //skonfigurowanie interfejsu SSC
      sampling_freq = mp3FrameInfo.samprate;
      if (sampling_freq == 48000) 
      { //gdy częstotliwość próbkowania odczytana z odtwarzanego pliku = 48kHz, to poniżej ustawienie clk ARM-a na wartość 73,728MHz i ustawienie cyklu 1WS
        *AT91C_RTTC_RTMR = 3; //ustawienie preskalera RTT w celu przyspieszenia przyjmowania przerwań od RTT, by uzyskać szybsze wystawianie znaków na LCD
        AT91F_MC_EFC_CfgModeReg( AT91C_BASE_MC, (AT91C_MC_FMCN | AT91C_MC_FWS_1FWS)); //ustawienie 1 cyklu WaitState
        AT91F_CKGR_CfgPLLReg(AT91C_BASE_CKGR, AT91C_CKGR_USBDIV_0 | (3 << 16) | AT91C_CKGR_OUT_0 | AT91C_CKGR_PLLCOUNT | 1); //mikrokontroler taktowany jest zegarem (18,432 * (3+1) / 1) = 73,728MHz
        while (! (AT91F_PMC_IsStatusSet(AT91C_BASE_PMC, AT91C_PMC_LOCK))){}; //w pętli while odczekanie na ustabilizowanie się pętli PLL
        AT91F_SSC_Configure( AT91C_BASE_SSC, (73728000), (2*16*48000), 0, 0, AT91C_I2S_ASY_MASTER_TX_SETTING(16, 2), AT91C_I2S_ASY_TX_FRAME_SETTING(16, 2)); //(dla zegara 73,728MHz; fsample =  48kHz)
        config_TC0(36000); //i ustawienie TC0,by generował przerwanie co 500ms dla częstotliwości zegara = 73,728MHz 
        *AT91C_TC1_RC = 8; //oraz ustawienie TC1 tak,by prawidłowo działał interfejs L3 w UDA1330 dla częstotliwości zegara = 73,728MHz
      }
      else  //w przeciwnym razie ustawienie zegara mikrokontrolera dla domyślnej wartości fsample=44,1kHz
      {
        AT91F_CKGR_CfgPLLReg(AT91C_BASE_CKGR, AT91C_CKGR_USBDIV_0 | (48 << 16) | AT91C_CKGR_OUT_0 | AT91C_CKGR_PLLCOUNT | 20); //mikrokontroler taktowany jest zegarem (18,432 * (48+1) / 20) = 45,1584MHz; fsample=44,1kHz
        while (! (AT91F_PMC_IsStatusSet(AT91C_BASE_PMC, AT91C_PMC_LOCK))){}; //w pętli while odczekanie na ustabilizowanie się pętli PLL
        AT91F_SSC_Configure( AT91C_BASE_SSC, (45158400), (2*16*sampling_freq), 0, 0, AT91C_I2S_ASY_MASTER_TX_SETTING(16, 2), AT91C_I2S_ASY_TX_FRAME_SETTING(16, 2)); //(dla zegara 45,1584MHz; fsample=44,1kHz)
        config_TC0(22050); //i ustawienie TC0,by generował przerwanie co 500ms dla częstotliwości zegara = 45,1584MHz
        *AT91C_TC1_RC = 4; //oraz ustawienie TC1 tak,by prawidłowo działał interfejs L3 w UDA1330 dla częstotliwości zegara = 45,1584MHz
      }
      AT91F_SSC_EnableTx( AT91C_BASE_SSC);  //Enable the TX transmitter
      out_Samples = mp3FrameInfo.outputSamps;
      AT91F_SSC_SendFrame( AT91C_BASE_SSC, (char*)outBuf, out_Samples, (char*)outBuf, out_Samples); //wysłanie do portu SSC poprzez kanał DMA danych dla przetwornika D/A i wypełnienie drugiego bufora DMA
      AT91F_PMC_EnablePeriphClock( AT91C_BASE_PMC, 1 << AT91C_ID_SSC ) ;  //enable the clock of the SSC
      configured = true;  //interfejs SSC jest już skonfigurowany
    }
    


    I jeszcze najważniejsza rzecz: udało mi się znacząco zwiększyć prędkość transferu plików podczas zapisywania i odczytywania plików przez komputer, gdy odtwarzacz jest połączony z nim przez USB. Obecnie mam ok. 350kB/s przy zapisie plików na kartę SD i ok. 400kB/s przy odczycie plików z niej (w porównaniu z poprzednimi odpowiednio 75kB/s i 200kB/s). Osiągnąłem to poprzez poważną przeróbkę funkcji: Write_To_Card i Read_From_Card, umieszczonych w pliku SCSI_commands.c i obsługujących transmisję danych przez USB. Poniżej te funkcje:
    
    /*funkcja wkładająca do tablicy bajtów danych w odpowiedzi na komendę Read odczytany blok danych z karty SD i później 
    wysyłająca tą tablicę do hosta USB.
    Argumentami funkcji sa:
     1. ilość bloków, które mają być odesłane hostowi
     2. adres bloku, od którego zaczyna się odczyt karty SD
    Zwracaną wartością jest liczba bajtów odesłanych hostowi w odpowiedzi na komendę Read = ilość bloków * 512 */
    unsigned int Read_From_Card(unsigned short length, unsigned int LBA)
    {
      signed short licznik_blokow = length;
      if (length == 0) AT91F_USB_SendZlp(pCDC, AT91C_EP_IN); //wysłanie Zero Length Packet do hosta USB, gdy ten wysłał komendę Read,ale bez podania ilości bloków do odczytu
      else
      {
        if (length > 1) //gdy ilość bloków,które mają być odczytane z karty, jest większa od 1 
        {       //to poniżej realizacja odczytu wielu bloków z karty SD
            unsigned char *dane_z_karty_SD = malloc(32768); //zarezerwowanie w heap obszaru na tablicę o rozmiarze 32kB przechowującą dane odczytywane z karty
            // if (dane_z_karty_SD == 0) goto SINGLE_BLOCK; //(gdyby było potrzebne)gdy nie udało się zaalokować RAM-u na tablicę dane_z_karty_SD,to odczyt pojedynczych bloków
            AT91F_SPI_CfgCs( AT91C_BASE_SPI, SD_Card_Access, (0x0 << 24) | (0x0 << 16) | (AT91C_SPI_SCBR - 0xFC00) | AT91C_SPI_BITS_8 | !(AT91C_SPI_CSAAT) | !(AT91C_SPI_NCPHA) | (AT91C_SPI_CPOL) ); //ust. SPI mode 3 (AT91C_SPI_CPOL) | !(AT91C_SPI_NCPHA), 8 bitów danych, bez opóźnień między zegarem i danymi, bez opóźnień pomiędzy poszczególnymi danymi(potrzebne dla kart SD), częstotliwość zegara = MainCLK / (0xFF - 0xFC) 
            while (licznik_blokow > 0) //gdy jeszcze są do odczytania dane z karty, wtedy licznik_blokow > 0 i poniższa pętla się wykonuje
            {
              AT91F_MC_EFC_CfgModeReg( AT91C_BASE_MC, (AT91C_MC_FMCN | AT91C_MC_FWS_3FWS)); //ustawienie 3 cykli Wait States przed odczytem dnych z karty (zegar MainCLK = 96MHz)
              read_multiple_sectors(dane_z_karty_SD, (licznik_blokow > 0x40 ? 0x40 : licznik_blokow), LBA, (CSD.CSD_Structure ? true : false));//odczytanie (64 lub licznik_blokow) bloków począwszy od adresu w LBA; gdy zwykła karta SD (CSD_Structure = 0),to false, w przeciwnym razie true  
              AT91F_MC_EFC_CfgModeReg( AT91C_BASE_MC, (AT91C_MC_FMCN | AT91C_MC_FWS_1FWS)); //ustawienie 1 cyklu WS przed wysłaniem odczytanych danych do kompa przez USB
              AT91F_UDP_Write(&pCDC, dane_z_karty_SD, (512 * (licznik_blokow > 0x40 ? 0x40 : licznik_blokow))); //wysłanie wypełnionej tablicy dane_z_karty_SD do hosta USB
              LBA = LBA + (licznik_blokow > 0x40 ? 0x40 : licznik_blokow); //zwiększenie adresu LBA o liczbę odczytanych już bloków
              licznik_blokow = licznik_blokow - 0x40; //i zmniejszenie licznika bloków; gdy ta wartość będzie <= 0, t.zn. już wszystkie bloki zostały odczytane z karty
            }
            free(dane_z_karty_SD);
        }
        else 
    //SINGLE_BLOCK:  
        {
           AT91F_SPI_CfgCs( AT91C_BASE_SPI, SD_Card_Access, (0x0 << 24) | (0x0 << 16) | (AT91C_SPI_SCBR - 0xFC00) | AT91C_SPI_BITS_8 | !(AT91C_SPI_CSAAT) | !(AT91C_SPI_NCPHA) | (AT91C_SPI_CPOL) ); //ust. SPI mode 3 (AT91C_SPI_CPOL) | !(AT91C_SPI_NCPHA), 8 bitów danych, bez opóźnień między zegarem i danymi, bez opóźnień pomiędzy poszczególnymi danymi(potrzebne dla kart SD), częstotliwość zegara = MainCLK / (0xFF - 0xFC) 
           while (licznik_blokow) //w przeciwnym razie odczyt pojedynczych bloków z karty 
           {
             AT91F_MC_EFC_CfgModeReg( AT91C_BASE_MC, (AT91C_MC_FMCN | AT91C_MC_FWS_3FWS)); //ustawienie 3 cykli Wait States przed odczytem dnych z karty (zegar MainCLK = 96MHz)
             read_sector(LBA,(CSD.CSD_Structure ? true : false));//odczytanie bloku o adresie w LBA; gdy zwykła karta SD (CSD_Structure = 0),to false, w przeciwnym razie true
             AT91F_MC_EFC_CfgModeReg( AT91C_BASE_MC, (AT91C_MC_FMCN | AT91C_MC_FWS_1FWS)); //ustawienie 1 cyklu WS przed wysłaniem odczytanych danych do kompa przez USB
             AT91F_UDP_Write(&pCDC, tablica_bajtow_danych, 512); //wysłanie wypełnionej tablicy bajtów danych do hosta USB
             LBA++; //adres następnego bloku do odczytu
             licznik_blokow--; //zmniejszenie o 1 licznika bloków, bo jeden odczytany blok został już wysłany do hosta USB
           }
        }
      }
      return (length * pow(2, CSD.Block_Length));
    }
    
    
    
    /*funkcja odczytująca bloki danych z hosta USB i zapisująca odczytane bloki na kartę SD
    Argumentami funkcji sa:
     1. ilość bloków, które mają być odebrane od hosta
     2. adres bloku, od którego zaczyna się zapis danych na kartę SD
    Zwracaną wartością jest liczba bajtów odebranych od hosta w odpowiedzi na komendę Write = ilość bloków * 512 */
    unsigned int Write_To_Card(unsigned short length, unsigned int LBA)
    { 
      signed short liczba_pozostalych_blokow = length; //liczba pozostałych bloków, które muszą jeszcze być zapisane na kartę SD
      unsigned char *dane_na_karte_SD = malloc(32768); //zarezerwowanie w heap obszaru na tablicę o rozmiarze 32kB przechowującą dane przeznaczone do zapisu na kartę SD
      AT91F_SPI_CfgCs( AT91C_BASE_SPI, SD_Card_Access, (0x0 << 24) | (0x0 << 16) | (AT91C_SPI_SCBR - 0xFB00) | AT91C_SPI_BITS_8 | !(AT91C_SPI_CSAAT) | !(AT91C_SPI_NCPHA) | (AT91C_SPI_CPOL) ); //ust. SPI mode 3 (AT91C_SPI_CPOL) | !(AT91C_SPI_NCPHA), 8 bitów danych, bez opóźnień między zegarem i danymi, bez opóźnień pomiędzy poszczególnymi danymi(potrzebne dla kart SD), częstotliwość zegara = MainCLK / (0xFF - 0xFB)
        while (liczba_pozostalych_blokow > 0) //gdy jeszcze są do zapisania dane na kartę, wtedy liczba_pozostalych_blokow > 0 i poniższa pętla się wykonuje
        {
          AT91F_UDP_Read(&pCDC, dane_na_karte_SD, (512 * (liczba_pozostalych_blokow > 0x40 ? 0x40 : liczba_pozostalych_blokow))); //wypełnienie tablicy dane_na_karte_SD danymi odebranymi z hosta USB
          AT91F_MC_EFC_CfgModeReg( AT91C_BASE_MC, (AT91C_MC_FMCN | AT91C_MC_FWS_2FWS)); //ustawienie 2 cykli Wait States przed zapisem danych na kartę (zegar MainCLK = 96MHz)
          write_multiple_sectors(dane_na_karte_SD, (liczba_pozostalych_blokow > 0x40 ? 0x40 : liczba_pozostalych_blokow), LBA, (CSD.CSD_Structure ? true : false));//zapis na kartę (liczba_pozostalych_blokow) bloków począwszy od adresu w LBA; gdy zwykła karta SD (CSD_Structure = 0),to false, w przeciwnym razie true  
          AT91F_MC_EFC_CfgModeReg( AT91C_BASE_MC, (AT91C_MC_FMCN | AT91C_MC_FWS_1FWS)); //ustawienie 1 cyklu WS przed przesłaniem przez USB danych z kompa do zapisu na kartę
          LBA = LBA + (liczba_pozostalych_blokow > 0x40 ? 0x40 : liczba_pozostalych_blokow); //zwiększenie adresu LBA o liczbę zapisanych już bloków
          liczba_pozostalych_blokow = liczba_pozostalych_blokow - 0x40; //i zmniejszenie liczba_pozostalych_blokow; gdy ta wartość będzie <= 0, t.zn. już wszystkie bloki zostały zapisane na kartę
        }
        free(dane_na_karte_SD);
        return (length * pow(2, CSD.Block_Length));
    }
    

    Poprzednie, błędne wersje tych funkcji wynikały z mojego błędnego założenia, że komputer będzie zawsze żądać odczytu lub zapisu ilości bloków, które tworzą 1 cluster. Okazało się być to nieprawdą (co wyszło podczas debugowaia), stąd nowa postać powyższych funkcji. Bufory: dane_na_karte_SD i dane_z_karty_SD zaalokowałem w odpowiednio powiększonym obszarze pamięci przeznaczonej dla zmiennych dekodera MP3, który podczas współpracy odtwarzacza z komputerem nie był wykorzystywany. Stąd we właściwościach projektu musiałem zwiększyć Heap Size do 33000B (wobec dotychczasowych 23918B).
    Pozdrawiam, KT_priv
  • #27
    mrh
    Level 18  
    Tytus Kosiarski, mam pytanie dotyczącej Twojej funkcji play_mp3_file, mianowicie masz może tę funkcję w uboższej wersji tzn. bez lcd itp. Bo Twoja jest bardzo rozbudowana. Chodzi mi o samo dekodowanie mp3 i wysłanie do DAC.
  • #28
    Tytus Kosiarski
    Level 15  
    Witam
    Jest w załączniku. Drobna uwaga: całe dekodowanie zrealizowałem w funkcji main w pliku main.c. Po skompilowaniu i wgraniu tego do ARM-a z dołączonym DAC-em, jak na schemacie ideowym, ten powinien od razu odtwarzać kawałek MP3 zapisany w pamięci Flash.
    Pozdrawiam, KT_priv
  • #29
    Tytus Kosiarski
    Level 15  
    Witam ponownie. Wprowadziłem jeszcze kilka usprawnień w programie odtwarzacza po ponad trzymiesięcznym, intensywnym jego eksploatowaniu. Podyktowane to było faktem zakupu kolejnej karty SD (SDHC 8GB Kingston, niby Class 4, w czarnej obudowie), która, jak się później okazało, miała bardzo długi czas oczekiwania na wystawienie pierwszego bajtu poprzedzającego 512-bajtową paczkę danych. Ilustruje to poniższy fragment kodu:
          
    attempt = 0;
    do  //w poniższej pętli do...while oczekiwanie na pojawienie się pierwszego bajtu <> 0xFF poprzedzającego 512-bajtową paczkę danych. 
    {    //Gdy są same 0xFF, to karta SD jeszcze nie wystawiła danych po wysłaniu odpowiedzi i wtedy poniższa pętla ciągle się wykonuje
       AT91F_SPI_PutChar(AT91C_BASE_SPI, 0xFF, SD_Card_Access); //wystawianie 0xFF na MOSI, by utrzymać aktywność CLK
       odebrany_bajt = AT91F_SPI_GetChar( AT91C_BASE_SPI);  //odczytywanie dalszych bajtów danych z karty 
       if ( !(AT91F_PIO_GetInput(AT91C_BASE_PIOA) & AT91C_PIO_PA30)) //gdy NIE jest dołączone USB,to
       {
         attempt++;
         if (attempt > 3500) return read_write_OK = false; //gdy liczba prób odczytania danych z karty > 3500, to wyjście ze zwrotem wartości read_write_OK = false
       }
    }
    while (odebrany_bajt == 0xFF);  //ta pętla wykonuje się tak długo, jak długo odebrany_bajt == 0xFF


    Fragment ten wywoływany jest kilkakrotnie w funkcji sd_get_response_and_read_write_data w pliku SD_Card_functions.c
    W poprzednich wersjach programu ta pętla kończyła się, gdy attempt > 1000 (w przypadku gdy non stop odebrany_bajt == 0xFF). Dla tego, konkretnego egzemplarza karty SD pozostawienie dotychczasowej wartości, z którą była porównywana zmienna attempt, skutkowało błędami w odczycie drzewa plików i podkatalogów umieszczonych na karcie po włączeniu odtwarzacza oraz wyłączeniem odtwarzacza (z komunikatem o braku karty SD) podczas prób odtworzenia plików MP3 z tej karty. Wymiana tej karty na inną w sklepie nie wchodziła w grę, bo karta jest formalnie na chodzie, w laptopie funkcjonuje prawidłowo. Pozostawało zatem dostosowanie programu odtwarzacza tak, by ten egzemparz karty prawidłowo współpracował z odtwarzaczem.

    Na pierwszy ogień poszło zwiększenie wartości, z którą jest porównywana zmienna attempt (w powyższym fragmencie kodu). Zabieg ten spowodował poprawne odczytywanie drzew plików i podkatalogów na tej karcie oraz poprawne odczytywanie zawartości odtwarzanego pliku MP3. Niestety, okazało się, że tak długi czas oczekiwania na dane z karty powoduje przedwczesne opróżnianie bufora nadawczego SSC podczas odtwarzania plików VBR i plików CBR z bitrate > 160kb/s, co powodowało trzaski podczas odtwarzania takich plików.
    Postanowiłem przyjrzeć się funkcji play_mp3_file w pliku main.c. W tej funkcji oprócz czasochłonnego dekodowania strumienia MP3 odbywa się odczyt karty SD w celu dostarczania nowych danych do dekodowania, odbywa się również regulacja głośności, wyświetlanie informacji na LCD i pomiar napięcia baterii. Pomny doświadczeń nabytych podczas wcześniejszych prób implementacji odczytu na raz wielu sektorów z karty, postanowiłem również zaimplementować komendę ReadMultipleSector podczas odtwarzania plików. Poprzednio ta komenda działała tylko przy współpracy odtwarzacza z kompem, przy odtwarzaniu plików odbywał się odczyt pojedynczych sektorów z karty. Osiągnąłem to poprzez umieszczenie fragmentu:

      result = read_multiple_sectors(tablica_bajtow_danych, 2, start_address + licznik, rodzaj_karty); //odczytanie nowych danych z 2 pierwszych sektorów karty SD należących do klastra zajętego przez odtwarzany plik
      licznik = licznik + 2; //zwiększenie licznika odczytanych sektorów, bo powyżej zostały już odczytane 2 sektory; 
    

    na początku funkcji play_mp3_file i wewnątrz funkcji read_sector_from_current_or_next_cluster (plik main.c). Oczywiście musiałem też odpowiednio dostosować ciało wywoływanej funkcji read_multiple_sector tak, by ta prawidłowo współpracowała z resztą programu odtwarzacza podczas grania muzy (bo wcześniej ta funkcja była wykorzystywana tylko podczas współpracy odtwarzacza z kompem). Najlepsze efekty podczas odtwarzania uzyskiwałem przy odczycie naraz 2 sektorów, co pozwoliło mi już na płynne odtwarzanie plików z bitrate <= 192kb/s. Niestety, nadal pozostawał problem z plikami z wyższym bitrate.
    Aby rozwiązać ten problem, spróbowałem zwiększyć częstotliwość zegara mikrokontrolera do 67,7376MHz (4 * fsysclk UDA1330 = 4 * 384 * 44100). W efekcie uzyskałem poprawne odtwarzanie plików <=256kb/s. Pozostało jeszcze 320kb/s. W pierwszej wersji przeróbek następną wartością zegara mikrokontrolera było 90,3168MHz (4 * 512 * 44100). W efekcie uzyskałem poprawne odtwarzanie wszystkich plików MP3. Jednakże nie podobała mi się tak duża wartość zegara mikrokontrolera. Pamiętając o moich wcześniejszych próbach zmniejszania fsample bez słyszalnego spowolnienia tempa odtwarzania utworu, próbowałem zmniejszać fsample z krokiem 300Hz (z oryginalnej wartości 44100Hz) poprzez zmniejszanie fclk mikrokontrolera, pamiętając o zachowaniu zależności fclk = 4 * mnożnik_UDA1330 * fsample; gdzie mnożnik_UDA1330 może tylko przyjąć jedną z wartości: 256, 384, 512 (tak sądziłem po lekturze PDF-a tego układu). Przyjąłem wartość mnożnik_UDA1330 = 512. Postępując w taki sposób doszedłem do wartości fsample = 42900Hz (tempo odtwarzania było jeszcze akceptowalne). Dałoby to zegar mikrokontrolera ~= 87,8MHz (4* 512 * 42900). Trochę lepiej, ale jeszcze nie to... Ale, odwróćmy tok myślenia: zależność
    (1) fclk = 4 * mnożnik_UDA1330 * fsample

    można przekształcić do postaci:
    (2) fclk / 4 = mnożnik_UDA1330 * fsample

    A właśnie iloczyn
    (3) mnożnik_UDA1330 * fsample = fsysclk UDA1330

    Czyli zmniejszając fclk zmniejszamy fsysclk UDA1330 na pinie 6. tego scalaka. Czyli zobaczmy, ile będzie wynosił mnożnik_UDA1330, dla fsample równe jednak 44100Hz i zegara mikrokontrolera równego 87,8592MHz:
    (4) mnożnik_UDA1330 = 87859200 / (4 * 44100) ~= 498

    Czyli zegar fsysclk UDA1330 na pinie 6. tego scalaka niekoniecznie musi mieć wartość 512 * fsample; mnożnik_UDA1330 może mieć mniejszą wartość bez zauważalnego wpływu na jakość odtwarzania. Toteż zegar mikrokontrolera dobierałem tak, aby uzyskać możliwie najmniejszą jego wartość dla plików z bitrate = 320kb/s, ale jednocześnie żeby wartość zegara dzieliła się bez reszty przez 2048 (by uzyskać generację przerwania od TC0 co dokładnie 500ms) i jednocześnie wartość mnożnika_UDA1330 była również liczbą całkowitą. Po uwzględnieniu tych założeń wybrałem zegar mikrokontrolera równy 78,4896MHz i przyjąłem fsample = 43800Hz. I taki zegar na razie zostawiłem przy odtwarzaniu plików z bitrate 320kb/s.
    Pozostało jeszcze zrobić w programie automatykę wybierającą odpowiedni zegar zależnie od wartości bitrate odtwarzanego pliku. Wykorzystałem do tego fakt obecności pętli oczekującej na wysłanie do UDA1330 uprzednio dostarczonych danych. Przedstawia to poniższy kod:

    
    unsigned char Vbat_not_converted = true;
    wait_counter++; //zwiększenie o 1 zmiennej wait_counter przed wejściem w poniższą pętlę
    while (! (AT91C_BASE_SSC->SSC_SR & AT91C_SSC_ENDTX)) //odczekanie w pętli, aż wszystkie poprzednie dane z portu SSC pójdą do przetwornika D/A
    {
      if (Vbat_not_converted) //podczas oczekiwania w tej pętli:
      {
         wait_counter--; //i zmniejszenie o 1 tej zmiennej podczas jednokrotnego wykonania tej pętli. W efekcie ,gdy mikrokontroler dostarcza dane do DAC w sposób ciągły (bez przerw),to ta zmienna == 0
         AT91F_ADC_StartConversion (AT91C_BASE_ADC);  //uruchomienie przetwornika A/C
         while (! (AT91C_BASE_ADC->ADC_SR & AT91C_ADC_EOC5));  //i po odczekaniu, gdy Vbat jest już przetworzone na wartość cyfrową
         converted_Vbat = AT91F_ADC_GetConvertedDataCH5 (AT91C_BASE_ADC); //to odczytanie cyfrowej wartości Vbat
         Vbat_not_converted = false; //i odp.ustawienie zmiennej Vbat_not_converted, by podczas dalszego wykonywania się pętli while (! (AT91C_BASE_SSC->SSC_SR & AT91C_SSC_ENDTX)) już nie przetwarzać Vbat
      }
    }
    if ((wait_counter > 2) & (clock_selector < 2)) //gdy mikrokontoler 2 razy nie zdąży dostarczyć danych dla DAC dla fclk mikrokontrolera wybranej zmienną clock_selector(przyczyną najczęściej zbyt długi odczyt z karty) i gdy clock_selector nie osiągnął jeszcze max wartości (==2)
    {
       SSC_configured = false; //to ustawienie zmiennej SSC_configured = false, by móc ponownie skonfigurować zegar mikrokontrolera przy następnym obiegu pętli dekodującej MP3
       clock_selector++; //i zwiększenie clock_selector, by wybrać większą częstotliwość fclk mikrokontrolera. Zwiększanie tej zmiennej trwa tak długo, aż clock_selector == 2
    }


    Natomiast zmiana w powyższym kodzie wartości zmiennych SSC_configured i clock_selector powoduje przy następnym obiegu pętli dekodującej MP3 ponowne wykonanie kodu:

           
    if (! (SSC_configured)) // gdy była to dopiero pierwsza poprawnie zdekodowana ramka lub wystąpiła zmiana wartości clock_selector, to najpierw przed wysłaniem danych do portu SSC
    { //skonfigurowanie interfejsu SSC
       sampling_freq = mp3FrameInfo.samprate;
       switch (sampling_freq)
       {
           ...
       }
       AT91F_SSC_EnableTx( AT91C_BASE_SSC);  //Enable the TX transmitter
       out_Samples = mp3FrameInfo.outputSamps;
       AT91F_SSC_SendFrame( AT91C_BASE_SSC, (char*)outBuf, out_Samples, (char*)outBuf, out_Samples); //wysłanie do portu SSC poprzez kanał DMA danych dla przetwornika D/A i wypełnienie drugiego bufora DMA
       AT91F_PMC_EnablePeriphClock( AT91C_BASE_PMC, 1 << AT91C_ID_SSC ) ;  //enable the clock of the SSC
       SSC_configured = true;  //interfejs SSC jest już skonfigurowany
       wait_counter = 0; //i wyzerowanie zmiennej wait_counter po wyborze zegara mikrokontrolera
    }
    

    gdzie wykonanie instrukcji wewnątrz switch (sampling_freq) odpowiednio ustawia zegar mikrokontrolera i konfiguruje interfejs SSC. Przy okazji dorobiłem też w powyższym kodzie konfigurującym interfejs SSC możliwość odtwarzania plików z fsample = 32kHz (trafił mi się jeden taki plik).

    Doszły do mnie uwagi, że nie każdy egzemplarz mikrokontrolera AT91SAM7S256 pracuje stabilnie z zegarem 45,1584MHz przy 0WS (podstawowy zegar mikrokontrolera przy odtwarzaniu plików 44,1kHz) Dlatego dołączam dwa wsady do mikrokontrolera: 1. domyślny, z początkowym zegarem 45,1584MHz (podkatalog wsady\zegar_45MHz); 2. z początkowym zegarem 67,7376MHz (podkatalog wsady\zegar_67MHz).

    Pozdrawiam, KT
  • #30
    Tytus Kosiarski
    Level 15  
    Witam ponownie
    Dołożyłem już możliwość odtwarzania plików AAC w programie tego odtwarzacza. Obecnie odtwarzacz oprócz dotychczasowego odtwarzania plików MP3 może również odtwarzać pliki AAC VBR, tylko Low Complexity standardu MPEG-2 i MPEG-4. Do tego celu wykorzystałem bibliotekę dekodera AAC z tego projektu: http://embdev.net/articles/ARM_MP3/AAC_Player . Kod tej biblioteki musiałem trochę przerobić, by dostosować ją do mojego odtwarzacza. Zmiany objęły:
    1. wyliczanie wartości bitrate każdej ramki AAC. Realizuje to linia:
    Code: c
    Log in, to see the code

    umieszczona w funkcji UnpackADTSHeader w pliku filefmt.c. Oryginalnie do zmiennej aacDecInfo->bitRate była podstawiana wartość 0.
    2. Usunięcie z tej biblioteki plików umożliwiających dekodowanie strumienia AAC-SBR. Powodem było brak wystarczającej ilości RAM w mikrokontrolerze. O ile sam dekoder plików AAC potrzebował ok.21kB heap + 3kB stack, to na dekodowanie strumienia AAC-SBR potrzeba było znacznie więcej jak 64kB heap, by funkcja AACInitDecoder zwróciła wartość różną od 0.
    3. dołożenie jeszcze jednego argumentu do funkcji AACDecode, a mianowicie adresu zmiennej nFrames. W ciele tej funkcji pod ten adres jest wpisywana wartość aacDecInfo->frameCount po każdorazowym, poprawnym zdekodowaniu ramki AAC.

    Mając już zaimplementowany dekoder AAC, pokusiłem się jeszcze o dorobienie możliwości odtwarzania plików MP4 i M4A, przy czym z plików MP4 odtwarzam tylko ścieżkę dźwiękową. Budowa tych plików jest zupełnie inna. Pliki te są zbudowane z różnych segmentów danych, zwanych atomami. Do uzyskania informacji o sposobie kodowania dźwięku, ekstrakcji próbek dźwięku z pliku MP4 czy też M4A potrzebne mi były następujące atomy:
    1. atom "mdat". Tu potrzebna mi była informacja o ilości atomów "mdat", ich rozmiarów oraz położeniu tych atomów w odtwarzanym pliku MP4. Na podstawie tej informacji mogłem określić zgrubnie położenie innych potrzebnych atomów w pliku (czy są przed, czy za atomem / atomami "mdat". Dlatego poszukiwanie tego atomu / tych atomów zrealizowałem zaraz na początku funkcji play_mp4_file (plik play_files.c)
    2. atomy: "mdia", "mdhd", "mp4a", "esds". Znajomość offsetu atomu "mdia" potrzebna mi była do przyspieszenia wyszukiwania pozostałych trzech atomów w pliku. Atom "mdhd" przechowuje informacje o wersji strumienia audio, czasie jego utworzenia, czasie trwania ścieżki dźwiękowej oraz częstotliwości próbkowania. Następny atom "mp4a" jest znacznikiem, że dane w tych atomach dotyczą ścieżki dźwiękowej. Zawiera też informację o częstotliwości próbkowania audio oraz o długości (w bitach) próbek audio - najczęściej 16-bit. Następnym atomem jest "esds" który zawiera informację o typie ścieżki audio - najczęściej będzie to strumień MPEG-4. Dalsze bajty tego atomu precyzują typ ścieżki audio - najczęściej AAC LC, dlatego można było wykorzystać ten dekoder AAC do odtwarzania plików MP4. Kolejne bajty tego atomu określają również bitrate, indeks częstotliwości próbkowania oraz ilość kanałów audio - najczęściej 2, choć ścieżki dźwiękowe niektórych filmów w pliku MP4 są monofoniczne (ilość kanałów audio - 1). Wszystkie te informacje są potrzebne do wypełnienia pól struktury audio_stream_info i w dalszej kolejności poprawnego skonfigurowania dekodera AAC. Wyszukiwaniem tych atomów zajmuje się funkcja get_audio_stream_info w pliku mp4dec.c. Również ta funkcja przy okazji znajduje offsety i rozmiary atomów "stco", "stsc" i "stsz".
    3. atomy: "stco", "stsc", "stsz". Atomy te są niezbędne do ekstrakcji strumienia audio z pliku MP4. Po znalezieniu ich offsetów i rozmiarów, dane z tych atomów są użyte do wypełnienia tablic stco_atom_table, stsc_atom_table i stsz_atom_table jeszcze przed wejściem w pętlę odtwarzającą wybrany plik w funkcji play_mp4_file w pliku play_files.c. Przy czym z uwagi na znaczne niekiedy rozmiary tych atomów (zależnie od konkretnego pliku), konieczne było wypełnianie tych tablic kolejnymi fragmentami danych z odpowiednich atomów odczytywanymi z karty SD "w locie" podczas wykonywania się pętli odtwarzającej wybrany plik.
    W efekcie uzyskałem odtwarzanie strumienia audio z plików MP4 z bitrate do 128kb/s, 44,1kHz i 48kHz. Wyższych bitrate'ów nie próbowałem. Dołączam kod źródłowy i wsady do mikrokontrolera (plik BIN i Intel HEX).
    Pozdrawiam, KT