Skocz do zawartości

Avr c, skrócenie obliczeń do minimum


Pomocna odpowiedź

Ten 1000 to tylko taki przykład, wzięty z resztą z Twojego kodu. To właśnie jest skutek prezentowania niecałego programu. Wszystko jedno.

Nie walcz z sqrt, tylko zacznij od początku: zbierz rzeczywiste próbki, wypisz je przez RS232 na kompie, przekonaj się, że ADC działa dobrze (przecież to jest Twój oscyloskop!), wyrysuj sobie ten przebieg na wykresie w arkuszu, sprawdź, czy sinusioda nie nasyca się na którymś wierzchołku, policz sobie kwadraty, sumę, rms. Wpuść jeszcze raz ten sam komplet próbek np. do symulatora, potwierdź prawidłowość obliczeń itd. Potem to samo na procesorze. Dopisz jakiś kod wspomagający uruchamianie: ładowanie próbek z terminala, wypisywanie wyników pośrednich. To standardowe metody uruchamiania takich rzeczy. Nie szukaj po omacku i nie pytaj nas

"Ale czy to sprawiałoby aż taki bład?"

bo to trochę kompromitujące, nie sądzisz?

Przecież to Ty jesteś odpowiedzialny za część teoretyczną. Możesz nie umieć zrobić systemu przetwarzania danych i tego właśnie się uczysz, ale liczyć chyba umiesz?

Kilka uwag:

1. Na 256 próbek masz 4 "bonusowe". Po co? (nie lepiej liczyć sumę bez tych 4?) - od razu masz błąd +/- 2% za te próbki, a w programie praktycznie nic nie musisz zmieniać żeby tego błędu nie było (jeden if i jedno dzielenie...)

2. Na końcu funkcji obsługi przerwań możesz sprawdzić czy jest ustawiona flaga do wywołania kolejnego przerwania z timera... jak tak to już wiesz, że spóźniłeś się z obliczeniami. Wystarczy to wyrzucić na jakąś diodę czy w inny sposób sygnalizować ten błąd.

Jak chcesz sprawdzić o ile się spóźniłeś wystarczy spojrzeć na wartość licznika od timera (oczywiście w razie kilkukrotnego wystąpienia timera już się o tym nie dowiesz)

3. Nie widzę kodu, ale wyslij_spi jest zrobione programowo? No to zbyt szybkie to nie będzie - stracisz spokojnie kilkaset cykli.

4. Spójrz co wyrzuca kompilator, ale sqrt ( suma_u / 256 ) jak na moje oko podzieli suma_u przez 256 jako liczbę całkowitą, więc tracisz cały ułamek - kolejny błąd w dokładności.

Powinno być sqrt ( (float)suma_u / 256.0f )

a jak już naprawdę Tobie na tym ułamku nie zależy to możesz przynajmniej porządnie zaokrąglić robiąc ( suma_u + 128 ) / 256

Ten 1000 to tylko taki przykład, wzięty z resztą z Twojego kodu. To właśnie jest skutek prezentowania niecałego programu. Wszystko jedno.

Nie walcz z sqrt, tylko zacznij od początku: zbierz rzeczywiste próbki, wypisz je przez RS232 na kompie, przekonaj się, że ADC działa dobrze (przecież to jest Twój oscyloskop!), wyrysuj sobie ten przebieg na wykresie w arkuszu, sprawdź, czy sinusioda nie nasyca się na którymś wierzchołku, policz sobie kwadraty, sumę, rms. Wpuść jeszcze raz ten sam komplet próbek np. do symulatora, potwierdź prawidłowość obliczeń itd. Potem to samo na procesorze. Dopisz jakiś kod wspomagający uruchamianie: ładowanie próbek z terminala, wypisywanie wyników pośrednich. To standardowe metody uruchamiania takich rzeczy. Nie szukaj po omacku i nie pytaj nas

"Ale czy to sprawiałoby aż taki bład?"

bo to trochę kompromitujące, nie sądzisz?

Przecież to Ty jesteś odpowiedzialny za część teoretyczną. Możesz nie umieć zrobić systemu przetwarzania danych i tego właśnie się uczysz, ale liczyć chyba umiesz?

Masz rację, szukam po omacku. Noc jest długa więc zrobie tak jak napisałeś, odezwe się po wykonaniu zadania. Pewnie z kolejnym problemem..

Kod obsługi SPI.

void init_spi(void)
{
DDRB |= (1<<MOSI)|(1<<SCK)|(1<<CS);
SPCR |= (1<<SPE) |(1<<MSTR) | (1<<SPR1) ;

}
uint8_t wyslij_spi(uint8_t bajt)
{
SPDR = bajt;
while(!(SPSR & (1<<SPIF)));
return SPDR;
}

while mam na razie pustego. Jak będzie działał poprawnie pomiar to dorzuce tam kilka linijek prostego algorytmu.

O wynikach informuje mnie timer. dodałem if ( licznik ==1000) ... i zatrzymuję timer i wysyłam do terminala co potrzebuję.

Mirek:

1. Jednak dzielenie przez 250 kosztuje znacznie więcej czasu niż przez 256. Można spróbować dobrać częstotliwość przerwań tak, by 256 próbek to była całkowita wielokrotność 20ms.

2. Dobry pomysł.

3. Nawet jeżeli nie jest programowo, to wysyłamy do ADC 3 bajty a to oznacza oczekiwanie ponad 24 okresów zegara SPI na wynik z przetwornika. Oczekiwanie w obsłudze przerwania!

4. Można dzielenie w ogóle spod pierwiastka wyrzucić: sqrt(suma_u)/16 i wyjdzie na to samo 🙂

A w sumie to szkoda, że nie można oglądać postu na który się odpowiada. Kiedyś to było jakoś lepiej..

Jak to jest dzielenie float to nie robi różnicy, ale można wykorzystać numer z pkt.4 i wtedy sensownie jest ustawić pod to okres, żeby np. w 20ms robił 32 lub 64 pomiary.

Tylko nie zapominaj o zaokrąglaniu, czyli ( (uint16_t) sqrt ( suma_u ) + 8 ) / 16

Dobrałem timer tak aby przypadło..16 próbek na okres - preskaler 256, OCR = 38. Śmiesznie mało ale jak na razie działa to najlepiej z wszytkich prób. kolejne wyniki są bardzo zbieżne.

Obrałem 256 próbek w tablicy gdyż na tyle pozwala pamięć a razrazem potęgi dwójki dzieli najszybciej.

Przy szybszym próbkowaniu było lepiej gdy dla 52 próbek na okres tablica miala 260 elementów. Dokładna liczba elementów ma jednak spore znaczenie.

W pętli głównej dokonuje porównania otrzymanych wartości z wartościami nastawczymi. Myślę sobię, że mógłbym nie dzielić wyniku przez liczbę próbek i porównywać wynik z wartością nastawczą przemnożoną przez liczbę próbek. Na jedno by wyszło a unikam dzielenia.

Ale...

kod wyjściowy:

uint16_t pomiar_u(void)

{

static uint16_t tab_u[256];

static uint8_t indeks_u;

float rms_u;

static uint64_t suma_u;

uint8_t b,c;

PORTB &=~(1<

wyslij_spi(0b00011000);

b=wyslij_spi(0x00);

c=wyslij_spi(0x00);

PORTB|=(1<

c=c;

b=b&0x1F;

indeks_u++;// if (indeks_u ==1023) indeks_u=0;

suma_u -= (uint32_t)tab_u[indeks_u]*tab_u[indeks_u];

tab_u[indeks_u] = (uint16_t)256*b+c;

suma_u += (uint32_t)tab_u[indeks_u]*tab_u[indeks_u];

rms_u= sqrt(suma_u/256);

return (rms_u);

}

Wartość rms_u do któej przekazywany był wynik powinna teraz mieć inną wielkość. Wrzucam do niej pierwiastek sumy, która to jest 64 bitowa, więc pierwiastek powinien mieć max 32 bity.

Obieram uint 32_t rms_u

a całą funkcje robię również 32 bitową, gdyż taką wartość będzie zwracać. Usuwam dzielenie i rzutuję wynik pierwiastkowania na uint32. Otrzymałem taki twór:

uint32_t pomiar_u(void)

{

static uint16_t tab_u[260];

static uint8_t indeks_u;

uint32_t rms_u;

static uint64_t suma_u;

uint8_t b,c;

PORTB &=~(1<

wyslij_spi(0b00011000);

b=wyslij_spi(0x00);

c=wyslij_spi(0x00);

PORTB|=(1<

c=c;

b=b&0x1F;

indeks_u++;// if (indeks_u ==1023) indeks_u=0;

suma_u -= (uint32_t)tab_u[indeks_u]*tab_u[indeks_u];

tab_u[indeks_u] = (uint16_t)256*b+c;

suma_u += (uint32_t)tab_u[indeks_u]*tab_u[indeks_u];

rms_u= (uint32_t) sqrt(suma_u);

return (rms_u);

}

I brak reakcji. Diody kontrolne nie świecą. Czy wniosek jest taki, że pierwiastek z 64 bitowej sumy nie zawsze musi być 32 bitowy?

1. Gdzie w kodzie masz zmienne 64bitowe???

2. Wszystko się zmieści na zmiennych 32bitowych i na pierwszy rzut oka powinno być ok.

3. Jak na końcu dzielenie robisz już jako liczby całkowite i jest to tylko przesunięcie ( / 16 zamienia się w >> 4 ) to taka instrukcja jest bardzo szybka i nawet dla liczb 32bitowych czas wywołania jest rzędu kilkunastu cykli zegara i nie ma większego sensu od tego uciekać.

Sam powrót z funkcji z liczbą 32 bitową i potem porównywanie dwóch liczb 32 bitowych a nie 16 bitowych może zajmować więcej czasu niż to "dzielenie".

4. Tak jak Marek mówił po pierwiastkowaniu nie dzielisz już przez 256, a przez 16 (256 też musi być spierwiastkowane). Jak w pętli głównej porównujesz wynik z wartościami nastawczymi pomnożonymi przez 256 to nic z tego Tobie nie wyjdzie. One musżą być * 16 ...

5. Proponowane rozwiązanie to zostać przy 16bitowym rms_u i robić:

rms_u = (uint16_t) ( ( (uint32_t) sqrt ( suma_u ) + 8 ) / 16 );

czyli faktycznie (kompilator w wersji release powinien to zrobić przy kompilacji)

rms_u = (uint16_t) ( ( (uint32_t) sqrt ( suma_u ) + 8 ) >> 4 );

Oj, Mirek, siódma to to za rano żeby analizować kod. Przynajmniej dla mnie.

1. Pytałeś o liczby 64-bitowe: suma_u jest taka.

2. No nie wiem, autor przeprowadził szybką analizę gdzieś na początku. Zsumowanie 256 kwadratów liczb 13-bitowych daje maksymalny wynik 0x3FFC00100, nie mieszczący się na 32 bitach. Daltego - jak rozumiem stosuje typ uint64_t

qwee: Pamiętaj, że kumulując dużą dokładność w liczbach 32- albo nawet 64-bitowych trzeba to robić po coś. Jeżeli za chwilę robisz (niejawną) kowersję na 24-bitowy float to możesz dużo stracić. Dostajesz wtedy duży zakres dynamiczny tych liczb (i darmowy pierwiastek z biblioteki math) ale liczba bitów znaczących jest mniejsza. Kolejne pytanie: czy to źle? Może wcale tyle nie potrzeba i nie ma się czym przejmować?

Myślę sobię, że mógłbym nie dzielić wyniku przez liczbę próbek i porównywać wynik z wartością nastawczą przemnożoną przez liczbę próbek. Na jedno by wyszło a unikam dzielenia

Mógłbyś nawet nie wyciagać pierwiastka a zamiast tego podnieść wartość nastawczą do kwadratu. Dokładnie o tym pisałem kilka dni temu przypuszczając, że robisz tak jak robisz z jakiegoś ważnego powodu, np takiego, że musisz wartości Vrms/Irms gdzieś wysłać lub potrzebujesz ich do innych obliczeń.

Wrzucam do niej pierwiastek sumy, która to jest 64 bitowa, więc pierwiastek powinien mieć max 32 bity

Oczywiście pierwiastek z liczby maksymalnie 64-bitowej będzie miał wartość mieszczącą się w maksymalnie 32 bitach, ale w szczegółach to już takie proste nie jest. Operacja sqrt(suma_u/256) wykonuje się tak:

a. najpierw 64-bitowa suma_u jest przesuwana w prawo o 8 bitów (dzielenie przez 256)

b. 64-bitowy wynik jest następnie zamieniany na float, bo tylko taki pierwiastek jest w bibliotece math czyli:

- normalizujemy 64-bity (co jest operacją bardzo paskudną bo polega na przesuwaniu w lewo i każdorazowym sprawdzaniu czy już MSB=1)

- bierzemy z nich 24 najstarsze

- uzupełniamy to cechą (wykładnikiem) o długości 8 bitów

c. liczymy pierwiastek na zmiennym przecinku

Jeżeli chcesz z tego zrobić z powrotem uint_32, to:

d. Najpierw sprawdzana jest cecha czy w ogóle jest sens robić konwersję, bo wynik może być 0x00000000 dla liczb < 1 lub 0xFFFFFFFF dla > 2^32

e. Następuje denormalizacja 24-bitowej cechy co róznież jest przesuwaniem ale przynajmniej teraz wiadomo o ile

f. Rzutujemy otrzymane 24 bity na 32-bitowy uint_32t c oznacza, że dostajesz tylko 24 bity znaczące i to niekoniecznie te najmłodsze

Warto rozumieć jaki jest format binarny poszczególnych typów i co z tego wynika a także to, jakich argumentów używają funkcje biblioteczne i co oddają. Niektóre konwersje robione są "w locie" po prostu przez inne traktowanie kolejnych bajtów ale akurat przejście między fload a int nie jest trywialne. To samo z arytmetyką. Paradoksalnie mnożenie zmiennoprzecinkowe jest szybsze (a przynajmniej deterministyczne w czasie bo nie zależy od wartości arguementów) niż dodawanie, które wymaga wstępnego wyrównania argumentów co może być bardzo szybkie (gdy nie trzeba go robić albo gdy nie ma sensu bo argumenty sa bardzo od siebie różne), albo baaardzo wolne gdy trzeba przesuwać o ileś bitów (w zależności od różnicy wykładników) 24-bitowe mantysy.

Dziękuję za wyczerpujący opis mechanizmu.

Ostatecznie okazuje się, że tak jak już napisałem najlepsze efekty daje próbkowanie z częstotliwością 801 Hz. OCR = 38 i preskaler = 256. Daje to 16,02 próbki na okres. Przy 256 bitach mam praktycznie równe 16 próbek i dziele przez 256 co mieści się w czasie. Aczkolwiek docelowo jest to troche mała szybkość próbkowania.

Zakładam teraz, że wystarczy mi wartość skuteczna w postaci kwadratu lu bteż pierwiastka bez dzelenia pod nim. Przy takiej długości tablicy. Jeśli usunąbym którąś z tych czasochłonnych operacji to sądzę iż możnaby obrać większą prędkość a przede wszytkim dowolną długość tablicy, dla okrągłej dla okrągłej liczby próbek.

Jeśli tylko zmienię funkcję pomiarową na typ uint32, oraz rms_u na uint32 to nie jest przekazywana żadna wartość z funkcji.

Eee tam. Nie może być przekazywana "żadna wartość". Może być zawsze zero, jakaś inna stała, coś wyglądającego losowo lub tylko trochę dziwna albo nieskorelowana z pomiarami ale "żadna"? Przecież coś program wołający dostaje w tych rejestrach? Każda kombinacja bitów jest jakimś wynikiem. Umnie AVRGCC bez problemu radzi sobie z podwójną konwersją podczas (uint32_t)(sqrt(float)(suma)), gdy suma jest dowolnym uint-em.

Spróbuj trzymać w tablicy kwadraty próbek zamiast ich wartości. Wtedy nie będziesz musiał podnosić do kwadratu przy odejmowaniu usuwanego elementu. No i zainteresuj się szybkim liczeniem pierwiastka całkowitoliczbowego, ale skoro możesz się obejść i bez tego, to jaki masz teraz czas wykonywania jednego przebiegu? Bo bez sqrt to już naprawdę niewiele zostało i powinno się liczyć migiem. Być może mógłbyś jeszcze coś ugrać na zadeklarowaniu sumy jako "register", ale bez każdorazowego zaglądania w listing asemblerowy to raczej gdybanie. Zaoszczędziłbyś na 8 ładowaniach i 8 składowaniach tej zmiennej z/do pamięci plus na kilku PUSH/POP w prologu/epilogu funkcji. Patrzyłeś w to co generuje kompilator? Nie wiem czy łyknie pozbawienia go na dzień dobry aż 8 cennych rejestrów. Mam nadzieję, że zauważa wielokrotne użycie wskaźnika do tab_u[indeks_u]. Jeżeli nie, to może warto obliczać go gdzieś na początku funkcji tylko raz:

ptr = &tab_u[indeks_u];

a potem operować na *ptr. Co więcej, można go zrobić statycznym i wtedy z każdym wykonaniem tej funkcji tylko popychać go do przodu ++ a raz na jakiś czas wpisywać mu

ptr = tab_u;

Nie trzeba będzie za każdym razem liczyć wskaźnika do tablicy, czyli odpadnie operacja mnożenia indeksu przez 2 (bo 2-bajtowe uint16_t) i dodawania adresu bazowego tablicy.

No tak, to znowu obcinanie paznokci, ale w końcu gdzie to robić jak nie tu? 🙂

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ę »
×
×
  • Utwórz nowe...