Skocz do zawartości

STM32 Nucleo-G491RE + PMOD I2S2 niepoprawny odczyt wartości dźwięku stereo 24b/48kHz


Pomocna odpowiedź

Dzień dobry, posiadam pewien problem. Najpierw wtajemniczę w konfigurację projektu, a później opiszę szczegółowo problem. Płytka to STM32 Nucleo-G491RE, do której dołączony jest moduł Digilnet Pmod I2S2 - 24 bitowy przetwornik ADC oraz DAC z dużą prędkością próbkowania. Do odczytu danych wykorzystałem interfejs SAI z konfiguracją jak w załączniku pierwszym.

obraz_2023-03-09_121055051.thumb.png.164c3cf0375232fba19988ea190cce97.png

Oczywiście DMA zostało skonfigurowane w taki sposób aby miało wysoki priorytet i działało w pętli. Minusem jest to, że maksymalna wartość odbieranych danych to word - 16b, natomiast dane wysyłane są w ramkach 32b o wartościach 24b przez wcześniej wspomniany moduł. Do wysyłania danych zaprzęgnąłem I2S2 o konfiguracji jak poniżej, z tak samo skonfigurowanym DMA jak przy odbieraniu.

obraz_2023-03-09_121311738.thumb.png.b2b242b1ff11ab8c3b9bb4b3700709b5.png

Zmienne do przechowywania danych to:

int32_t rxBuf[2];
int32_t txBuf[2];

Inicjacja DMA:

HAL_SAI_Receive_DMA(&hsai_BlockA1, rxBuf, 2);
HAL_I2S_Transmit_DMA(&hi2s2, txBuf, 2); 

W pętli while(1) nie jest wykonywany żaden kod - wszystko jest na przerwaniu. Zrobiłem to dosyć nieprofesjonalnie - odczytana wartość z płytki pmod zawiera bit znaku więc musiałem zamienić otrzymywane dane z typu uint na int - HAL_SAI_Receive_DMA odbiera uint. Przerwanie wygląda następująco:

void HAL_SAI_RxCpltCallback(SAI_HandleTypeDef *hsai_BlockA1){
    for (int i = 0; i < 2; i++) {
            int32_t sample = rxBuf;
            int32_t processed_sample = 0;
            if (sample < 16760439 && sample > 16777) {
                if (sample < 8388608) {
                    processed_sample = sample;
                } else {
                    processed_sample = 0 - (16777216 - sample);
                }
            }
            txBuf = processed_sample;
        }
}

Do momentu gdy ściszam głośność analogowo, że się tak wyrażę - poprzez napięcia na wyjściu źródła dźwięku wszystko działa dobrze - do pewnego momentu wartości się zmniejszają a po przekroczeniu wyrażenia if zostają wyzerowane. Źródłem dźwięku jest laptop z uruchomionym generatorem dźwięku w przeglądarce - sygnałem jest sinusoida o częstotliwości 1 - 20 kHz. Problem występuje gdy staram się zmniejszyć głośność poszczególnego kanału - lewy bądź prawy. Wartości zaczynają się zmniejszać dopóki nie osiągnę około 35%. Wówczas do zmiennej txBuf przypisywana jest wartość 0. Zmniejszenie poniżej tych 35% powoduje nagły wzrost wartości w tej zmiennej. Generator dźwięku nie jest zły - na słuchawkach dobrze słychać zmniejszenie głośności dźwięku. Poprawiłem kod aby w znacznie prostszy sposób konwertował uint do inta, ale problem dalej występuje.


 

void HAL_SAI_RxCpltCallback(SAI_HandleTypeDef *hsai_BlockA1){
    for (int i = 0; i < 2; i++) {
	int32_t sample=((int32_t)rxBuf[i] << 8) >> 8;
	if(sample < 168000 && sample > -168000){
	    txBuf[i]=0;
	}else{
	    txBuf[i]=sample;
	}
    }
}

 

Edytowano przez DeadGeneratio
Link do komentarza
Share on other sites

Bo funkcja receive_DMA i tak wymaga uinta. Na moje czy tego chcę czy nie, w incie i tak zostanie wrzucona wartość uint, czemu tak jest, nie wiem. Wiem, że odbieram faktycznie dane w zakresie 0 - 2^24.

Link do komentarza
Share on other sites

(edytowany)

Jeśli wymaga uinta, to dlaczego:

int32_t rxBuf[2];

? A w pętli for to pobiera automatycznie, gdy i = 0 oraz i = 1 z rxBuf[0] i rxBuf[1]? Bit znaku jest na 24-tym bicie?

Jeśli bit znaku jest na 24 miejscu (cała liczba) i 8 najbardziej znaczących bitów to zawsze zera (?) to nie lepiej zamienić te zera na jedynki, gdy liczba ujemna i po prostu przypisać intowi uinta? Pomijam pierwsze zdanie tego posta, co wskazałem, wydaje mi się dziwne i też wymagające zmiany.

PS. Jak zawsze zera na początku, to nie będzie nigdy ujemna 😄 Przesunąć w prawo o 16 i wrzucić do int8_t i jak to będzie ujemne to uzupełnić jedynkami zera.

Edytowano przez matsobdev
Link do komentarza
Share on other sites

Zarejestruj się lub zaloguj, aby ukryć tę reklamę.
Zarejestruj się lub zaloguj, aby ukryć tę reklamę.

jlcpcb.jpg

jlcpcb.jpg

Produkcja i montaż PCB - wybierz sprawdzone PCBWay!
   • Darmowe płytki dla studentów i projektów non-profit
   • Tylko 5$ za 10 prototypów PCB w 24 godziny
   • Usługa projektowania PCB na zlecenie
   • Montaż PCB od 30$ + bezpłatna dostawa i szablony
   • Darmowe narzędzie do podglądu plików Gerber
Zobacz również » Film z fabryki PCBWay

Jeśli odbieram wartość 2^24, to właściwie nie ma znaczenia w tym momencie czy będzie to zmienna typu int32_t czy uint32_t. W obu zmiennych otrzymywana wartość się zmieści. Odnośnie pętli for - działa ona na przerwaniu. DMA jest ustawione w trybie circual, czy w momencie wykonywania przerwania DMA dalej zapełnia bufor - nie wiem. Ale wydaje mi się, że prędkość wykonywania tego przerwania jest na tyle szybka, że nie ma wpływu to na odbierany sygnał - otrzymuję zaledwie 48k próbek na sekundę. Odnośnie I2S - właśnie tu jest problem - nie wiem gdzie znajduje się bit znaku. Założyłem, że na początku bądź na końcu ciągu danych.

Link do komentarza
Share on other sites

(edytowany)

Czyli to po swojemu to wysyła wartość bezwzględna (swoją droga to będzie 0 do 2^23 i 0 do 2^23 - 1) plus znak gdzieś? Nie wiem, datasheet układu co mówi (tam są chyba dwa oddzielne)? Jeśli to nic nie rozjaśni, to ja bym surowe dane przeanalizował - bez żadnej konwersji (nie ma siły, będą ujemne i dodatnie, w hexedytorze wyłuskać - tylko to pewnie stereo będzie przeplecione lewy-prawy-lewy-prawy...,  potem w Excelu, czy gdzie tam szesnastkowe przerobić wsadowo na zapis bnarny), można do Audacity takie zaimportować, tylko tam trzeba wiedzieć gdzie jest znak i... jest 32 bit, jest 24 bit, tylko np. 32 ze znakiem to bity znaku standardowo tam, gdzie powinny być.

Strzelam, że to będą 3 bajty, little-endian, choć to wiadomo. Można najwyższy bajt z 32 bitowego słowa usunąć w Pythonie np. i zaimportować do Audacity. Little-endian albo big-endian Jeśli będzie coś sensownego słychać, potwierdzone. Równie dobrze mogą to być maksymalnie 24 bitowe wartości w 32 bitowej zmiennej wysyłane. Datasheet.

Taki zapis:

2134200620_Screenshot2023-03-10at09-49-05STM32Nucleo-G491REPMODI2S2niepoprawnyodczytwartocidwikustereo24b_48kHz.thumb.png.42c6697970c22155e421316f4668c23a.png

nie pomaga 😄

Edytowano przez matsobdev
Link do komentarza
Share on other sites

Dlaczego surowe dane mają mieć ujemne wartości. Surowe dane to ciąg bitów, przyjmujących wartość logiczną zero bądź jeden. Dopiero po konwersji otrzymywane są dane poszczególnych kanałów, a potem analizując dane można znaleźć bit znaku, który dopiero wtedy umożliwi identyfikację wartości sygnału. Nie mam oscyloskopu ani miernika.

Link do komentarza
Share on other sites

(edytowany)

No tak, minus to tylko koncepcja, tutaj realizowana przez bit znaku, ale widząc dane np:

uint16_t = 0b1111111111111110;

to można powiedzieć, że to -2. Tylko nie wiedząc, gdzie jest znak, to jak można to przerobić? Takiego przeszukania tutaj potrzeba (moim zdaniem).

int32_t = 0b11111111111111111111010010111010;

w Twoim przypadku coś by oznaczało jak i:

int32_t = 0b00000000000000000001010010111010;

na przykład. A może też być tak:

int32_t = 0b00000000000000000001010010111010;
            ppppppppzlllllllllllllllllllllll

p - "puste",

z - znak,

l - liczba, wartość bezwzględna (może też być znak, jeśli nieduża ujemna liczba).

Wystarczy zapis do pamięci i odczyt serialem na przykład jakiejś sekundy, dwóch. Tutaj nie będzie potrzeba więcej, niż wartości jednego kanału.

Jak takie dane by się pojawiały:

int32_t = 0b00000000111111111101010010111010;
            ppppppppzlllllllllllllllllllllll

to już można coś wywnioskować.

Edytowano przez matsobdev
Link do komentarza
Share on other sites

Nie będzie zawsze dodatnie. Otrzymuje tam zarówno dodatnie jak i ujemne wartości, na podglądzie jak wysyłałem dane uartem tworzyła się ładna symetryczna pod względem amplitudy sinusoida

Link do komentarza
Share on other sites

(edytowany)

Ciekawe dlaczego mój wpis został usunięty... chciałem tylko usłyszeć dlaczego niby zmienna sample będzie zawsze dodatnia. Przykładowo jeśli rxBuf = 0x800001.

Edytowano przez Elvis
Link do komentarza
Share on other sites

(edytowany)

A babola musiałem strzelić, dlatego. Najwidoczniej inty jedynkami są w prawo dopełniane, jak wskoczy na minus? Muszę się douczyć.

@DeadGeneratio Zmiana, w tej uproszczonej funkcji:

rxBuf --> rxBuf[i]

poprawiła zmianę głośności kanałów osobno?

Edytowano przez matsobdev
Link do komentarza
Share on other sites

Na moje ramka będzie wyglądała bardziej jak: 32 bity -> z d d d d d d d d d d d d p p p z d d d d d d d d d d d d p p p

z - znak, d - dane, p - puste ewentualnie bit synchronizacji. W ramce 32 bity, 24 bity są danymi, więc na kanał przypada 12 bitów. Ale właściwie i tak nie wiadomo, bo może to być wartość 11 bitowa z znakiem - czyli 12 bitów, a może mieć 12 bitów, a z znakiem 13 bitów. Nie za bardzo rozumiem uproszczenia rxBuf do rxBuf.

Link do komentarza
Share on other sites

(edytowany)

Chodziło mi o to, że wcześniej nie było indeksu, z którego miejsca pobierane są dane z rxBuf, pętla zrobi dwa pełne kółka. Nie jest to, żeby lewy i prawy kanał zebrać? Ja tam nie wiem, pytam się tylko. A to nie jest 24 bitowa rozdzielczość jednej próbki? CD Audio ma np. 16, zatem 32 bity prawy i lewy razem. Tanie to nie jest, to ja domyślam się, że to jedna próbka z rozdzielczością 24 bitów, choć to tylko moje zdanie. Przy 12 bitach w jednej ramce oba kanały to pewnie Twoja funkcja by nie działała i wartości były plus/minus około 2000.

Układ Cirrus CS5343/4, datasheet strona 14. Nie wiem, czy dobrze czytam, odczyt na narastającym zboczu zegara. Najpierw idą 24 bity (23 - 0), potem jeszcze zegar daje, a idą zera. Teraz to chyba tylko specyfikacja I2S, żeby być pewnym.

Edytowano przez matsobdev
Link do komentarza
Share on other sites

(edytowany)

Wygląda na to, że bit znaku będzie jako LSB. Ustawiłem odbieranie danych jako 32 bity w 32 bitowej ramce aby mieć pewność, że zbiorę wszystkie dane i takie coś otrzymałem dla nie podłączonego przewodu jack do ADC.

obraz_2023-03-13_181513555.thumb.png.7695c7408a1d49f31c396ec1b5b7af03.png

Natomiast dla puszczonego sygnału prostokątnego o częstotliwości 1Hz, przy zbieraniu próbki 5Hz, czasami śmieciowo w buforze się ułożyło i musiałem niektóre próbki usunąć, więc dane te nie są idealnie co 0,2s otrzymuję takie coś:

obraz_2023-03-13_181529827.thumb.png.78aecd4247fcdfeea43ee543c4d1c364.png

Dla tego samego sygnału puszczonego tylko na kanale lewym otrzymuję coś takiego:

obraz_2023-03-13_181558261.thumb.png.9fc6fc6f6a78e5235b522ff28e45ade2.png

Natomiast dla prawego:

image.thumb.png.249dae979fba7654cd8377c3fd03642a.png

Jest to dosyć ciekawe - jeśli ramka zawiera tylko jeden kanał, a kolejna miała by zawierać kolejne dane z drugiego kanału, to w pewnym momencie albo dla trzeciego albo czwartego screenshota powinno się ukazać mniej więcej to samo co w pierwszym screenshocie. Oczywiście widoczne są 24 bity, pozostałe 8 to zera. Więc ustawienie odbierania 24 bitów w ramce 32 bitowej jest w pełni poprawne.

Edytowano przez DeadGeneratio
Link do komentarza
Share on other sites

Dołącz do dyskusji, napisz odpowiedź!

Jeśli masz już konto to zaloguj się teraz, aby opublikować wiadomość jako Ty. Możesz też napisać teraz i zarejestrować się później.
Uwaga: wgrywanie zdjęć i załączników dostępne jest po zalogowaniu!

Anonim
Dołącz do dyskusji! Kliknij i zacznij pisać...

×   Wklejony jako tekst z formatowaniem.   Przywróć formatowanie

  Dozwolonych jest tylko 75 emoji.

×   Twój link będzie automatycznie osadzony.   Wyświetlać jako link

×   Twoja poprzednia zawartość została przywrócona.   Wyczyść edytor

×   Nie możesz wkleić zdjęć bezpośrednio. Prześlij lub wstaw obrazy z adresu URL.

×
×
  • Utwórz nowe...

Ważne informacje

Ta strona używa ciasteczek (cookies), dzięki którym może działać lepiej. Więcej na ten temat znajdziesz w Polityce Prywatności.