DeadGeneratio Napisano Maj 23, 2023 Udostępnij Napisano Maj 23, 2023 Dzień dobry. Mam kilka pytań, może ktoś ma dobre poradniki, pomysły bądź książki dotyczące optymalizacji kodu. To co aktualnie napisałem to lepianka z brązowej mazi trzymająca się na słomie, aczkolwiek działa. Zacznę od samego ADC. Docelowo wykorzystam szesnaście kanałów a więc wszystkie jakie są dostępne w ADC - płytka to stm32 F407 DISC 1. Celem jest budowa korektora graficznego opartego na cyfrowych filtrach. Siedem potencjometrów będzie kontrolowało poszczególne częstotliwości dla lewego kanału dźwięku, kolejne siedem tak samo z prawym, piętnasty kanał będzie odpowiadał za balas lewo - prawo, i ostatni, szesnasty za kontrolę głośności. Aktualnie kod pobiera dane w trybie DMA, jednak niepokoi mnie to, że pomimo braku ruchu gałką potencjometru i tak wykonywane są kolejne przerwania w funkcji HAL_ADC_ConvCpltCallback. Jest to oczywiste - następuje wykonanie tej funkcji za każdym razem, gdy zostanie zapełniony wcześniej wskazany bufor. Tutaj pierwsze pytanie - czy jest jakaś opcja aby załączyć takie przerwanie dopiero w momencie wykrycia zmiany wartości na jakimkolwiek kanale, czy też raczej iść w stronę dodania dodatkowego przycisku na korektorze w stylu "aktualizuj wartość filtrów". Niemniej, i tak trzeba wyłączyć taką funkcję do czasu wykrycia zmiany przycisku bądź dźwigni. Preferowałbym zostanie przy DMA z racji na brak udziału procesora w zapisywaniu tych wartości do pamięci - będę potrzebował spory zapas mocy na późniejsze cyfrowe przetwarzanie dźwięku. Drugie pytanie - jak przekazać tablicę do funkcji od razu w całości zamiast dzielenia na poszczególne kawałki. Zamiast w przerwaniu przykładowo w pętli for wywoływać funkcję kilkunastokrotnie, od razu przekazać macierz, i uzyskać wynik w postaci całej macierzy za jednym zamachem. Przykład tego kodu, z którego oczywiście nie jestem dumny, ale pozwolił sprawdzić podstawowe działanie na dwóch potencjometrach poniżej: /* USER CODE BEGIN 4 */ uint8_t checkPotentiometer(uint8_t temp){ int j = 0; if(temp >=0 && temp < 11){ j = 0; }else if(temp >=11 && temp < 22){ j = 20; }else if(temp >=22 && temp < 33){ j = 40; }else if(temp >=33 && temp < 44){ j = 60; }else if(temp >=44 && temp < 55){ j = 80; }else if(temp >=55 && temp < 64){ j = 100; } return j; } void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) { Filter30Hz = checkPotentiometer(l_potentiometer[0]); HAL_I2C_Mem_Read(&hi2c1, FirstEEPROM, Filter30Hz, 0xFF, &checkSum[0], sizeof(checkSum[0]), HAL_MAX_DELAY); HAL_I2C_Mem_Read(&hi2c1, FirstEEPROM, Filter30Hz+4, 0xFF, &checkSum[1], sizeof(checkSum[1]), HAL_MAX_DELAY); HAL_I2C_Mem_Read(&hi2c1, FirstEEPROM, Filter30Hz+8, 0xFF, &checkSum[2], sizeof(checkSum[2]), HAL_MAX_DELAY); HAL_I2C_Mem_Read(&hi2c1, FirstEEPROM, Filter30Hz+12, 0xFF, &checkSum[3], sizeof(checkSum[3]), HAL_MAX_DELAY); HAL_I2C_Mem_Read(&hi2c1, FirstEEPROM, Filter30Hz+16, 0xFF, &checkSum[4], sizeof(checkSum[4]), HAL_MAX_DELAY); Filter60Hz = checkPotentiometer(l_potentiometer[1]); HAL_I2C_Mem_Read(&hi2c1, FirstEEPROM, Filter30Hz, 0xFF, &checkSum[5], sizeof(checkSum[5]), HAL_MAX_DELAY); HAL_I2C_Mem_Read(&hi2c1, FirstEEPROM, Filter30Hz+124, 0xFF, &checkSum[6], sizeof(checkSum[6]), HAL_MAX_DELAY); HAL_I2C_Mem_Read(&hi2c1, FirstEEPROM, Filter30Hz+128, 0xFF, &checkSum[7], sizeof(checkSum[7]), HAL_MAX_DELAY); HAL_I2C_Mem_Read(&hi2c1, FirstEEPROM, Filter30Hz+132, 0xFF, &checkSum[8], sizeof(checkSum[8]), HAL_MAX_DELAY); HAL_I2C_Mem_Read(&hi2c1, FirstEEPROM, Filter30Hz+136, 0xFF, &checkSum[9], sizeof(checkSum[9]), HAL_MAX_DELAY); } liczbaPrzerwan++; } /* USER CODE END 4 */ Liczba przerwań pozwoliła mi na sprawdzenie ile razy jest wykonywany ten kod - wychodzi na to, że w ciągu sekundy około 1000 razy. Niepotrzebne marnowanie zasobów mikrokontrolera. Do głowy przychodzi mi pomysł wyzwalania ADC poprzez Timer ustawiony przykładowo na sekundowe odliczanie, aczkolwiek to i tak marnowanie energii na robienie tego samego, gdy urządzenie będzie stało w miejscu przez godziny bez zmiany parametrów. Link do komentarza Share on other sites More sharing options...
DeadGeneratio Maj 24, 2023 Autor tematu Udostępnij Maj 24, 2023 (edytowany) Mały update: poprawiłem znacząco obsługę ADC + DMA - teraz działa na podstawie wyzwalania z wewnętrznego timera. Póki co, działa konwersja wartości uzyskanych z kanałów na określoną przez mnie liczbę, sporym ułatwieniem jest to, że kod napisałem w taki sposób, aby można było wykonać za jednym zamachem n-przekształceń, zamiast ciągle odwoływać się w funkcji do poszczególnych wartości z przerwania. Aktualnie kod wykonuje się co sekundę, w międzyczasie DMA jest nieaktywne, nie są wykorzystywane żadne zasoby CPU. Jeśli coś można tu poprawić, proszę o komentarz. Kod i screeny poniżej: /* USER CODE BEGIN PD */ #define n_channels 2 // definiowanie liczby wykorzystywanych kanałów /* USER CODE END PD */ /* USER CODE BEGIN PV */ uint8_t adc_buffer[n_channels]; uint8_t value[n_channels]; uint32_t interrupt_val = 0; /* USER CODE END PV */ /* USER CODE BEGIN 2 */ HAL_ADC_Start_DMA(&hadc3, adc_buffer, n_channels); HAL_TIM_Base_Start_IT(&htim2); /* USER CODE END 2 */ /* USER CODE BEGIN 4 */ void convertADCdata(){ for(int i=0; i<=sizeof(adc_buffer)-1;i++){ uint8_t statement = 1; uint8_t step = 1; do{ if(adc_buffer[i] > 6 * step){ // 6 <- mnożnik umożliwiający zmianę zakresu przetworzonej wartości, tutaj 0-11 step++; }else{ value[i] = step; statement = 0; } }while(statement != 0); } } /* void updateFilters(){ } */ void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc){ convertADCdata(); //updateFilters(); interrupt_val++; } /* USER CODE END 4 */ Edytowano Maj 24, 2023 przez DeadGeneratio Link do komentarza Share on other sites More sharing options...
Gieneq Maj 25, 2023 Udostępnij Maj 25, 2023 @DeadGeneratio a może zmniejsz częstotliwość taktowania ADC, ustaw tak żeby liczba cykli potrzebna do spróbkowania przetwornikiem wyszła np 1kHz. daj do DMA 16 elementową tablicę, circular mode. Jak zapamięta wszystko to dostaniesz przerwanie - jedno co 16ms. Jeżeli chcesz filtrację to możliwe że masz jakieś sprzętowe opcje. Może daj bufor 16*liczba_próbek_do_filtracji np 8 -> 16*8 i daj to całe na DMA. Wtedy jeszcze rzadziej dostaniesz przerwanie a jak już przyjdzie to możesz sobie uśrednić te pomiary w przerwaniu i głównym kodzie mieć już tablicę tylko 16 przefiltrowanych wartości. Dnia 23.05.2023 o 17:13, DeadGeneratio napisał: Drugie pytanie - jak przekazać tablicę do funkcji od razu w całości zamiast dzielenia na poszczególne kawałk Tablica jest wskaźnikiem, przekaż wskaźnik do funkcji i rozmiar. Rozmiar albo jako stała/define dla całego kodu, albo jako kolejny argument. Obejrzyj jak wygląda funkcja np. memcpy 2 Link do komentarza Share on other sites More sharing options...
DeadGeneratio Maj 25, 2023 Autor tematu Udostępnij Maj 25, 2023 Wydaje mi się, że nie zerknąłeś na mój komentarz poniżej. Czy teraz nie jest to jeszcze lepiej wykonywane aniżeli zmniejszenie taktowania ADC? Dzięki temu, że jest uruchamiany co jakiś czas, nie są marnowane zasoby, a nie zmieniając taktowania pomiary są wykonywane szybciej. Nie jestem pewien czy dobrze myślę, ale gdybym ustawił faktycznie przerwanie co 16 ms to kod mógł by się zaczął krzaczyć, będę musiał zaprzęgnąć I2S z zegarem 96kHz + master clock nie pamiętam ile, ale kilka razy więcej. Będzie to działało na przerwaniach więc nie jestem pewien czy to dobry pomysł czekać tak długo na ADC, w momencie, gdy dane do I2S muszą być przekazywane w sposób ciągły. Link do komentarza Share on other sites More sharing options...
Polecacz 101 Zarejestruj się lub zaloguj, aby ukryć tę reklamę. Zarejestruj się lub zaloguj, aby ukryć tę reklamę. 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
Gieneq Maj 26, 2023 Udostępnij Maj 26, 2023 (edytowany) @DeadGeneratio może trochę zbyt pobieżnie obejrzałem. Przeczytałem jeszcze raz. Wracając do pierwszego wpisu, ja bym się nie przejmował że ADC odczytuje niezmieniające się napiecia na potencjometrach - skąd ADC ma wiedzieć że coś się zmieniło jak nie sprawdził. 15 kanałów to 15 konwersji jedną jednostką przetwornika. Ale ok, możesz je przesłuchiwać z zadaną częstotliwością wyzwalając DMA timerem, to będzie super. Daj wtedy max częstotliwość na ADC. Tylko uwaga! Dobierz liczbę cykli próbkowania do impedancji na każdym z kanałów, załóż najgorszy wariant. Im większa impedacja tym więcej cykli zegara na spróbkowanie. Poczytaj jak działa przetwornik SAR w stmkach. Patrzę na twój kod z drugiego wpisu, widzę że masz jakiś timer i adc. Masz funkcję callback od zakończenia konwersji ADC. Nie jestem ekspertem, ale przyszło mi trochę pracować w STMach. Załóżmy że chcesz pomiary co 10ms i pomiar 15 wartości jest nieporównywalnie krótszy w stosunku do tych 10ms. Jakbym miał to zrobić to bym zrobił tak: bufor 15 wartości, timer basic odpalony z przerwaniem "start_base_timer_it" w funkcji obsługi przepełnienia timera dajesz wyzwolenie DMA z argumentem na bufor 15 wartosci dma single perip->memory, pewnie 16bit danych. w adc ustaw te 15 kanałów "rank 1,2,3..." przerwanie od timera wyższy priorytet od tego od dma obsłuż callback od dma, nie od ADC. W tej funkcji przekonwertuj dane - zajmie to stosunkowo malo czasu. Żeby nie miec ifa możesz dać strukturę z unią: typedef struct measurements_t { union { uint16_t buffer[15]; struct { uint16_t left[7]; uint16_t right[7]; uint16_t balance; }; }; } measurements_t; union to współdzielenie pamięci. Pamiętaj tylko o problemie indianowości. A i zapoznaj się z 19 stroną https://www.st.com/resource/en/datasheet/stm32f407ig.pdf tu masz jaki kanał DMA łączy się z ADC i dalej z pamięcią. W niektórych STMkach niekażda pamięć łączy się z peryferiami. Edytowano Maj 26, 2023 przez Gieneq 2 Link do komentarza Share on other sites More sharing options...
kaworu Maj 27, 2023 Udostępnij Maj 27, 2023 Dnia 24.05.2023 o 22:22, DeadGeneratio napisał: Mały update: poprawiłem znacząco obsługę ADC + DMA - ... A w ogóle jest Ci do czegoś potrzebna informacja o zakończeniu konwersji? Bo na mój gust można w ogóle wyłączyć przerwania z ADC/DMA, niech sobie działa w tle. I po prostu wyliczaj filtry na bazie aktualnych wskazań potencjometrów - ciągle. Druga sprawa, ten straszny "elseIf" w checkPotentiometer, da się uprościć do: uint8_t checkPotentiometer(uint8_t temp){ return ((temp * 5) / 55) * 20; } 2 Link do komentarza Share on other sites More sharing options...
_LM_ Maj 27, 2023 Udostępnij Maj 27, 2023 35 minut temu, kaworu napisał: Druga sprawa, ten straszny "elseIf" w checkPotentiometer, da się uprościć do Z poprawką na wartość z poza zakresu, kiedy input > 63 2 Link do komentarza Share on other sites More sharing options...
DeadGeneratio Maj 28, 2023 Autor tematu Udostępnij Maj 28, 2023 (edytowany) Dnia 26.05.2023 o 08:49, Gieneq napisał: typedef struct measurements_t { union { uint16_t buffer[15]; struct { uint16_t left[7]; uint16_t right[7]; uint16_t balance; }; }; } measurements_t; Zaraz poszukam dobrych źródeł na temat struktur i przeczytam datasheet który podesłałeś, ale czy przypadkiem nie wchodzę już w programowanie obiektowe, które jest można powiedzieć perełką języka C++? Ponoć w zwykłym C nie istnieje pojęcie obiektów, struktur i klas. Poprawka, przeczytałem, że istnieją struktury w zwykłym C, ale klasy już nie. Stąd moje wcześniejsze pytanie. Natomiast odnośnie tego strasznego if else, poprawiłem już ten kod - w drugim komentarzu checkPotentiometer() został zamieniony na convertADCdata() Edytowano Maj 28, 2023 przez DeadGeneratio Link do komentarza Share on other sites More sharing options...
Pomocna odpowiedź
Bądź aktywny - zaloguj się lub utwórz konto!
Tylko zarejestrowani użytkownicy mogą komentować zawartość tej strony
Utwórz konto w ~20 sekund!
Zarejestruj nowe konto, to proste!
Zarejestruj się »Zaloguj się
Posiadasz własne konto? Użyj go!
Zaloguj się »