Inverso efficiente (1 / x) per AVR


12

Sto cercando di trovare un modo efficiente per calcolare un inverso su un AVR (o approssimarlo).

Sto cercando di calcolare il periodo di impulso per un motore passo-passo in modo da poter variare la velocità in modo lineare. Il periodo è proporzionale all'inverso della velocità ( p = K/v), ma non riesco a pensare a un buon modo per calcolarlo al volo.

La mia formula è

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

Testando su Arduino, la divisione sembra essere completamente ignorata lasciando pfisso su 298(anche se forse questo sarebbe diverso in avr-gcc). Ho anche provato a sommare vin un ciclo fino a quando non supera 202, e contare i loop, ma questo è abbastanza lento.

Potrei generare una tabella di ricerca e archiviarla in flash, ma mi chiedevo se ci fosse un altro modo.

Modifica : forse il titolo dovrebbe essere "divisione efficiente" ...

Aggiornamento : come sottolineato da pingswept, la mia formula per mappare il periodo alla velocità non è corretta. Ma il problema principale è l'operazione di divisione.

Modifica 2 : su ulteriori indagini, il divario sta funzionando sull'arduino, il problema era dovuto sia alla formula errata sopra che a un overflow int altrove.


2
V è un numero intero o un virgola mobile?
mjh2007,

Un numero intero, ma dato che ci dà un punto, la divisione dei numeri interi è abbastanza accurata qui.
Peter Gibson,

Potresti pre-calcolare i valori dei 100 numeri interi e creare una tabella di ricerca dei pre-scaler per la moltiplicazione se sei veramente interessato alla velocità. Naturalmente c'è un compromesso della memoria.
RYS,

Risposte:


7

Una cosa bella della divisione è che più o meno tutti lo fanno. È una caratteristica piuttosto essenziale del linguaggio C e compilatori come AVR-GCC (chiamato dall'IDE Arduino) sceglieranno il miglior algoritmo di divisione disponibile, anche quando il microcontrollore non ha un'istruzione di divisione hardware.

In altre parole, non devi preoccuparti di come viene implementata la divisione a meno che tu non abbia un caso speciale molto strano.


Se ti preoccupi, allora potresti divertirti a leggere gli algoritmi di divisione suggeriti ufficiali di Atmel (uno ottimizzato per la dimensione del codice e uno ottimizzato per la velocità di esecuzione; né prendere alcuna memoria di dati). Loro sono dentro:

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

che è la nota applicativa "AVR200: moltiplicare e dividere le routine" elencata nella pagina Atmel per i suoi (ragionevolmente grandi) processori Atmega come Atmega 168 e Atmega 328 utilizzati negli Arduinos standard. L'elenco delle schede tecniche e delle note applicative è disponibile all'indirizzo:

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


4

mi sembra che tutto ciò di cui hai bisogno sia una tabella di ricerca da 100 voci. Non è molto più veloce di così.

#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];

MODIFICA in realtà solo una tabella di ricerca di 68 valori poiché i valori di v maggiore di 67 valutano sempre a 300.


Come ho detto nella domanda, mi chiedevo se ci fosse un altro modo
Peter Gibson,

3

Ci sono alcune tecniche molto buone menzionate nel libro "Hackers Delight di Henry Warren e sul suo sito Web hackersdelight.org . Per una tecnica che funziona bene con microcontrollori più piccoli quando si divide per costanti dare un'occhiata a questo file .


Questi sembrano buoni per la divisione per costanti come dici tu, ma non si applicano davvero al mio problema. Usa tecniche come il precalcolo dell'inverso - moltiplica per esso, quindi sposta.
Peter Gibson,

Questo è un libro eccellente!
Windell Oskay,

3

La tua funzione non sembra dare il risultato desiderato. Ad esempio, il valore 50 restituisce circa 302, mentre 100 restituisce circa 300. Questi due risultati non causeranno quasi alcun cambiamento nella velocità del motore.

Se ti capisco correttamente, stai davvero cercando un modo veloce per mappare i numeri 1-100 nell'intervallo 300-500 (circa), in modo che 1 mappe a 500 e 100 mappe a 300.

Forse prova: p = 500 - (2 * v)

Ma potrei essere frainteso: stai cercando di calcolare il tempo di puntamento di un'onda quadra a frequenza costante? Qual è il 298?


Sì grazie, la formula è sbagliata. Il punto è ottenere un'accelerazione lineare dall'uscita dello stepper, variando la velocità target di una costante ogni intervallo di tempo (velocità ++ dire). Questo deve essere mappato sul periodo (frequenza) in cui un bordo + ve viene inviato al controller del motore passo-passo, quindi la relazione inversa (p = 1 / v).
Peter Gibson,

Intendi un'accelerazione costante, cioè una velocità che aumenta linearmente?
spazzato il

Ah sì, costante accelerazione, l'ho risolto quando in origine ho scritto la domanda e ricordo di averla risolta anche lì
Peter Gibson

3

Un modo efficiente per approssimare le divisioni è tramite i turni. ad es. se x = y / 103; la divisione per 103 equivale alla moltiplicazione per 0,0097087, quindi per approssimare prima selezionare un numero di spostamento "buono" (ovvero un numero base-2, 2,4,8,16,32 e così via)

Per questo esempio, 1024 è una buona soluzione in quanto possiamo dire che 10/1024 = 0,009765 È quindi possibile codificare:

x = (y * 10) >> 10;

Ricordando ovviamente di assicurarsi che la variabile y non trabocchi il suo tipo quando moltiplicata. Non è esatto, ma è veloce.


Questo è simile alle tecniche nei collegamenti che Timrorr ha fornito e funziona bene per la divisione per costanti, ma non quando si divide per un valore sconosciuto al momento della compilazione.
Peter Gibson,

3

In un'altra nota, se stai cercando di fare una divisione su una CPU che non supporta la divisione, in questo articolo Wiki c'è un modo davvero fantastico di farlo.

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

Per approssimare il reciproco di x, usando solo la moltiplicazione e la sottrazione, si può indovinare un numero y, quindi sostituire ripetutamente y con 2y - xy2. Quando il cambiamento in y diventa (e rimane) sufficientemente piccolo, y è un'approssimazione del reciproco di x.


Interessante, mi chiedo come questo possa essere paragonato agli altri metodi citati
Peter Gibson,

1

Questo processo qui sembra mcu-friendly, anche se potrebbe richiedere un po 'di porting.

Anche se sembra che il LUT sarebbe più facile. Avresti bisogno di soli 100 byte, meno se avessi usato una certa interpolazione e poiché il LUT è pieno di costanti, il compilatore potrebbe persino localizzarlo nell'area del codice invece che nell'area dei dati.


Ho provato qualcosa di simile nel sommare il divisore fino a quando non è uguale o superiore al dividendo, ma l'ho trovato abbastanza lento. Sembra che il LUT sarà la strada da percorrere - usando avr-gcc sono necessarie macro speciali in <avr / progmem.h> per memorizzarlo in flash.
Peter Gibson,

1

Verificare che la divisione venga eseguita come virgola mobile. Uso Microchip non AVR, ma quando uso C18 devi forzare i tuoi letterali a essere trattati come virgola mobile. Per esempio. Prova a cambiare la tua formula in:

p = 202.0/v + 298.0;


1

Vuoi veloce così qui va ..... Dal momento che l'AVR non può eseguire la normalizzazione in modo efficiente (spostando a sinistra fino a quando non puoi più spostarti), ignora eventuali pseudo algoritmi a virgola mobile. Il modo più semplice per una divisione intera molto accurata e veloce in un AVR è tramite una tabella di ricerca reciproca. La tabella memorizzerà i reciproci ridimensionati di un numero elevato (diciamo 2 ^ 32). Quindi implementate una moltiplicazione unsigned32 x unsigned32 = unsigned 64 nell'assemblatore, quindi answer = (numeratore * inverseQ32 [denominatore]) >> 32.
Ho implementato la funzione di moltiplicazione usando l'assemblatore inline, (racchiuso nella funzione ac). GCC supporta "long longs" a 64 bit, tuttavia, per ottenere il risultato devi moltiplicare 64 bit per 64 bit, non 32x32 = 64 a causa delle limitazioni del linguaggio C sull'architettura a 8 bit ......

Unico inconveniente di questo metodo è che userai 4K x 4 = 16K di flash se vuoi dividere per numeri interi da 1 a 4096 ......

Una divisione non firmata molto accurata viene ora raggiunta in circa 300 cicli in C.

Si potrebbe prendere in considerazione l'utilizzo di numeri interi scalati a 24 o 16 bit per una maggiore velocità e una minore precisione.


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

Il valore di ritorno della tua equazione è già p=298dal momento che il compilatore si divide prima, quindi aggiungi, usa la risoluzione muldiv intera che è:

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

Usarlo è lo stesso moltiplicare a*f, con a = intero f = frazione.

Che produce r=a*fma f=b/cpoi r=a*b/cma non funziona ancora perché la posizione degli operatori, produce la funzione finale r=(a*b)/co muldiv, un modo per calcolare i numeri di frazione usando solo numeri interi.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.