Efektywna inwersja (1 / x) dla AVR

12

Próbuję znaleźć skuteczny sposób obliczenia odwrotności na AVR (lub przybliżenia go).

Próbuję obliczyć okres impulsu dla silnika krokowego, aby móc liniowo zmieniać prędkość. Okres jest proporcjonalny do odwrotności prędkości ( p = K/v), ale nie mogę wymyślić dobrego sposobu na obliczenie tego w locie.

Moja formuła to

p = 202/v + 298; // p in us; v varies from 1->100

Testując na Arduino, podział wydaje się całkowicie ignorowany, pozostawiając pustalony na 298(chociaż być może byłoby inaczej w avr-gcc). Próbowałem także sumować vw pętli, aż przekroczy 202, i licząc pętle, ale jest to dość powolne.

Mogłem wygenerować tabelę odnośników i zapisać ją w pamięci flash, ale zastanawiałem się, czy istnieje inny sposób.

Edycja : Może tytuł powinien być „efektywnym podziałem” ...

Aktualizacja : Jak wskazuje pingwin, moja formuła mapowania okresu na prędkość jest nieprawidłowa. Ale głównym problemem jest operacja dzielenia.

Edycja 2 : Podczas dalszych badań podział działa na arduino, problem był spowodowany zarówno nieprawidłową formułą powyżej, jak i przepełnieniem int w innym miejscu.

Peter Gibson
źródło
2
Czy v jest liczbą całkowitą czy zmiennoprzecinkową?
mjh2007
Liczba całkowita, ale ponieważ daje nam kropkę, podział liczb całkowitych jest tutaj wystarczająco dokładny.
Peter Gibson,
Możesz naprawdę obliczyć wartości 100 liczb całkowitych i utworzyć tablicę przeglądową wstępnych skalerów do mnożenia, jeśli naprawdę zależy Ci na prędkości. Oczywiście istnieje kompromis pamięci.
RYS

Odpowiedzi:

7

Jedną fajną rzeczą w podziale jest to, że mniej więcej wszyscy to robią. Jest to dość podstawowa funkcja języka C, a kompilatory takie jak AVR-GCC (wywoływane przez Arduino IDE) wybiorą najlepszy dostępny algorytm podziału, nawet jeśli mikrokontroler nie posiada instrukcji podziału sprzętu.

Innymi słowy, nie musisz martwić się o sposób podziału, chyba że masz bardzo dziwny przypadek szczególny.


Jeśli się martwisz, możesz przeczytać oficjalne algorytmy podziału Atmela (jeden zoptymalizowany pod kątem rozmiaru kodu, a drugi zoptymalizowany pod kątem szybkości wykonywania; żadne z nich nie zajmuje pamięci). Są w:

http://www.atmel.com/dyn/resources/prod_documents/doc0936.pdf

czyli nota aplikacyjna „AVR200: Mnożenie i dzielenie procedur” wymieniona na stronie Atmel ze względu na (dość duże) procesory Atmega, takie jak Atmega 168 i Atmega 328, stosowane w standardowych Arduinos. Lista kart danych i notatek aplikacyjnych znajduje się w:

http://www.atmel.com/dyn/products/product_card.asp?part_id=4720

Jack Schmidt
źródło
4

wygląda na to, że wszystko, czego potrzebujesz, to 100-wejściowa tabela odnośników. Nie robi się dużo szybciej.

#define VALUE_FOR_V_EQUALS_ZERO 0
uint16_t formula_lookup[100] = {VALUE_FOR_V_EQUALS_ZERO, 500, 399, 365, 348, ..., 300};

...

//"calculate" formula
p = formula_lookup[v > 67 ? 67 : v];

EDYTUJ w rzeczywistości tylko tabelę wyszukiwania wartości 68, ponieważ wartości v większe niż 67 zawsze dają wynik 300.

vicatcu
źródło
Jak powiedziałem w pytaniu, zastanawiałem się, czy istnieje inny sposób
Peter Gibson
3

Jest kilka bardzo dobrych technik wymienionych w książce „Hackers Delight” Henry'ego Warrena i na jego stronie internetowej hackersdelight.org . Aby zapoznać się z techniką, która działa dobrze z mniejszymi mikrokontrolerami podczas dzielenia przez stałe, zapoznaj się z tym plikiem .

timrorr
źródło
Wyglądają dobrze na dzielenie przez stałe, jak mówisz, ale tak naprawdę nie dotyczą mojego problemu. Używa technik takich jak wstępne obliczenie odwrotności - pomnożenie przez nią, a następnie przesunięcie.
Peter Gibson,
To doskonała książka!
Windell Oskay,
3

Nie wydaje się, aby twoja funkcja dawała pożądany efekt. Na przykład wartość 50 zwraca około 302, a 100 zwraca około 300. Te dwa wyniki nie spowodują prawie żadnej zmiany prędkości silnika.

Jeśli dobrze cię rozumiem, naprawdę szukasz szybkiego sposobu na zmapowanie liczb od 1 do 100 w zakresie od 300 do 500 (w przybliżeniu), na przykład od 1 mapy do 500 i 100 map do 300.

Być może spróbuj: p = 500 - (2 * v)

Ale mogę się nieporozumieć - czy próbujesz obliczyć czas fali prostokątnej o stałej częstotliwości? Co to jest 298?

pingswept
źródło
Tak, dziękuję, formuła jest zła. Chodzi o to, aby uzyskać liniowe przyspieszenie z wyjścia silnika krokowego, zmieniając docelową prędkość o stałą za każdym razem (prędkość ++ powiedzmy). Musi to być odwzorowane na okres (częstotliwość), w którym zbocze + ve jest wysyłane do sterownika silnika krokowego - stąd odwrotna zależność (p = 1 / v).
Peter Gibson,
Masz na myśli stałe przyspieszenie, tj. Liniowo rosnącą prędkość?
pingswept
Ach tak, ciągłe przyspieszenie, spieprzyłem to, kiedy pisałem pytanie i pamiętam, żeby je tam też naprawić
Peter Gibson,
3

Skutecznym sposobem przybliżenia podziałów jest zmiana. np. jeśli x = y / 103; dzielenie przez 103 jest równoznaczne z pomnożeniem przez 0,0097087, dlatego w celu przybliżenia tego pierwszego wybierz „dobrą” liczbę przesunięcia (tj. liczbę podstawową 2, 2, 4, 8, 16, 32 itd.)

W tym przykładzie 1024 jest dobrym dopasowaniem, ponieważ możemy powiedzieć, że 10/1024 = 0,009765 Można wtedy kodować:

x = (y * 10) >> 10;

Pamiętając oczywiście, aby upewnić się, że zmienna y nie przepełnia swojego typu po pomnożeniu. To nie jest dokładne, ale szybkie.


źródło
Jest to podobne do technik w linkach dostarczanych przez timrorr i działa dobrze do dzielenia przez stałe, ale nie przy dzieleniu przez wartość nieznaną w czasie kompilacji.
Peter Gibson,
3

Z drugiej strony, jeśli próbujesz dokonać podziału na procesorze, który nie obsługuje dzielenia, w tym artykule na Wiki jest naprawdę fajny sposób.

http://en.wikipedia.org/wiki/Multiplicative_inverse

Aby przybliżyć przybliżenie x, używając tylko mnożenia i odejmowania, można odgadnąć liczbę y, a następnie wielokrotnie zamieniać y na 2y - xy2. Gdy zmiana w y staje się (i pozostaje) wystarczająco mała, y jest przybliżeniem odwrotności x.

mjh2007
źródło
Ciekawe, zastanawiam się, jak to porównać do innych wspomnianych metod
Peter Gibson,
1

Ten proces tutaj wygląda na przyjazny dla mcu, choć może wymagać trochę przeniesienia.

Choć wydaje się, że LUT byłoby łatwiejsze. Potrzebowałbyś tylko 100 bajtów, mniej, gdybyś użył interpolacji, a ponieważ LUT jest wypełniona stałymi, kompilator może nawet zlokalizować ją w obszarze kodu zamiast w obszarze danych.

ajs410
źródło
Próbowałem coś podobnego w sumowaniu dzielnika, dopóki nie wyrówna lub przekroczy dywidendy, ale okazało się, że jest dość powolny. Wygląda na to, że LUT będzie właściwą drogą - używając avr-gcc potrzebujesz specjalnych makr w <avr / progmem.h>, aby przechowywać je we flashu.
Peter Gibson,
1

Sprawdź, aby upewnić się, że podział jest wykonywany jako zmiennoprzecinkowy. Używam Microchipa nie AVR, ale używając C18 musisz wymusić, by twoje literały były traktowane jako zmiennoprzecinkowe. Na przykład. Spróbuj zmienić formułę na:

p = 202.0/v + 298.0;

mjh2007
źródło
1

Chcesz szybko, więc oto idzie ..... Ponieważ AVR nie może efektywnie normalizować (przesuwając w lewo, aż nie można już przesunąć), zignoruj ​​wszelkie pseudo algorytmy zmiennoprzecinkowe. Najprostszym sposobem na bardzo dokładny i najszybszy podział liczb całkowitych w AVR jest zastosowanie wzajemnej tabeli przeglądowej. Tabela będzie przechowywać odwrotności skalowane dużą liczbą (powiedzmy 2 ^ 32). Następnie implementujesz mnożenie unsigned32 x unsigned32 = niepodpisane 64 w asemblerze, więc odpowiedz = (licznik * inverseQ32 [mianownik]) >> 32.
Zaimplementowałem funkcję mnożenia za pomocą wbudowanego asemblera, (owiniętego funkcją ac). GCC obsługuje 64-bitowe „długie długie”, jednak aby uzyskać wynik, należy pomnożyć 64 bity przez 64 bity, a nie 32 x 32 = 64 z powodu ograniczeń języka C w architekturze 8-bitowej ......

Minusem tej metody jest użycie 4K x 4 = 16 K pamięci flash, jeśli chcesz podzielić przez liczby całkowite od 1 do 4096 ......

Bardzo dokładny podział bez znaku osiąga się teraz w około 300 cyklach w C.

Można rozważyć użycie liczb całkowitych skalowanych w 24 lub 16 bitach dla większej prędkości, mniejszej dokładności.

Nacięcie
źródło
1
p = 202/v + 298; // p in us; v varies from 1->100

p=298Zwrócona wartość twojego równania już jest, ponieważ kompilator najpierw dzieli, a następnie dodaje, użyj liczb całkowitych muldiv, czyli:

p = ((202*100)/v + (298*100))/100 

Używanie tego jest takie samo mnożenie a*f, z ułamkiem = liczba całkowita f =.

Ta wydajność, r=a*fale f=b/cpotem, r=a*b/cale jeszcze nie działa, ponieważ pozycja operatorów, funkcja końcowa r=(a*b)/club funkcja muldiv, sposób obliczania liczb ułamkowych przy użyciu tylko liczby całkowitej.

nepermath
źródło