Inverso eficiente (1 / x) para AVR

12

Estoy tratando de encontrar una manera eficiente de calcular un inverso en un AVR (o aproximarlo).

Estoy tratando de calcular el período de pulso para un motor paso a paso para que pueda variar la velocidad linealmente. El período es proporcional a la inversa de la velocidad ( p = K/v ), pero no puedo pensar en una buena forma de calcular esto sobre la marcha.

Mi fórmula es

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

Probando en el Arduino, la división parece ignorarse por completo, dejando p fijo en 298 (aunque quizás esto sería diferente en avr-gcc). También he intentado sumar v en un bucle hasta que supere 202 , y contar los bucles, pero esto es bastante lento.

Podría generar una tabla de búsqueda y almacenarla en flash, pero me preguntaba si había otra forma.

Editar : tal vez el título debería ser "división eficiente" ...

Actualización : como señala pingswept, mi fórmula para asignar el período a la velocidad es incorrecta. Pero el problema principal es la operación de división.

Edición 2 : en una investigación más a fondo, divide está trabajando en el arduino, el problema se debió tanto a la fórmula incorrecta anterior como a un desbordamiento de int en otra parte.

    
pregunta Peter Gibson

10 respuestas

7

Una cosa buena de la división es que más o menos todos lo están haciendo. Es una característica bastante básica del lenguaje C, y compiladores como AVR-GCC (llamado por el IDE de Arduino) elegirán el mejor algoritmo de división disponible, incluso cuando el microcontrolador no tenga una instrucción de división de hardware.

En otras palabras, no necesita preocuparse por cómo se implementa la división a menos que tenga un caso especial muy extraño.

Si se preocupa, entonces puede disfrutar leyendo los algoritmos de división sugeridos oficialmente de Atmel (uno optimizado para el tamaño del código y otro optimizado para la velocidad de ejecución; ninguno de los dos acepta memoria de datos). Están en:

enlace

que es la nota de aplicación "AVR200: Multiplica y divide rutinas" listada en la página de Atmel para sus procesadores Atmega (razonablemente grandes) como el Atmega 168 y el Atmega 328 utilizados en los Arduinos estándar. La lista de hojas de datos y notas de aplicación se encuentra en:

enlace

    
respondido por el Jack Schmidt
4

me parece que todo lo que necesitas es una tabla de búsqueda de 100 entradas. No hay mucho más rápido que eso.

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

EDITAR en realidad solo tiene una tabla de búsqueda de 68 valores, ya que los valores de v mayor que 67 siempre se evalúan a 300.

    
respondido por el vicatcu
3

Hay algunas técnicas muy buenas mencionadas en el libro "Hackers Delight por Henry Warren y en su sitio web hackersdelight.org . Para una técnica que funciona bien con microcontroladores más pequeños cuando se divide por constantes un vistazo a este archivo .

    
respondido por el timrorr
3

Su función no parece dar el resultado que desea. Por ejemplo, el valor 50 devuelve aproximadamente 302, mientras que 100 retorna aproximadamente 300. Esos dos resultados no causarán casi ningún cambio en la velocidad del motor.

Si te entiendo bien, realmente estás buscando una manera rápida de asignar los números 1-100 al rango 300-500 (aproximadamente), de modo que 1 se asigne a 500 y 100 a 300.

Quizás intente: p = 500 - (2 * v)

Pero podría ser un malentendido: ¿estás tratando de calcular el tiempo de activación de una onda cuadrada de frecuencia constante? ¿Qué es el 298?

    
respondido por el pingswept
3

Una forma eficiente de aproximar las divisiones es por turnos. p.ej. si x = y / 103; dividir por 103 es lo mismo que multiplicar por 0.0097087, así que para aproximar esto primero seleccione un número de cambio "bueno" (es decir, un número base-2, 2,4,8,16,32 y así sucesivamente)

Para este ejemplo, 1024 es un buen ajuste, ya que podemos decir que 10/1024 = 0.009765 Entonces es posible codificar:

x = (y * 10) > > 10;

Por supuesto, recordar que la variable y no desborda su tipo cuando se multiplica. No es exacto, pero es rápido.

    
respondido por el BullBoyShoes
3

En otra nota, si estás tratando de hacer una división en una CPU que no admite la división, hay una manera realmente genial de hacerlo en este artículo de Wiki.

enlace

  

Para aproximar el recíproco de x,   usando solo la multiplicación y   resta, uno puede adivinar un número y,   y luego reemplazar repetidamente y con 2y   - xy2. Una vez que el cambio en y se convierte en   (y permanece) suficientemente pequeño, y es   una aproximación de lo recíproco de   x.

    
respondido por el mjh2007
1

Este proceso aquí parece fácil de usar, aunque puede necesitar un poco de transferencia.

Aunque parece que la LUT sería más fácil. Solo necesitaría 100 bytes, menos si usara alguna interpolación, y como la LUT está llena de constantes, el compilador podría incluso ubicarla en el área de código en lugar del área de datos.

    
respondido por el ajs410
1

Compruebe para asegurarse de que la división se realiza como punto flotante. Yo uso Microchip no AVR, pero cuando usas C18 debes forzar a tus literales a ser tratados como punto flotante. P.ej. Intenta cambiar tu fórmula a:

p = 202.0/v + 298.0;

    
respondido por el mjh2007
1

Quieres rápido, así que aquí va ..... Dado que el AVR no puede realizar la normalización de manera eficiente (desplazándose hacia la izquierda hasta que ya no pueda cambiar más), ignore los algoritmos de pseudo punto flotante. La forma más sencilla para una división de enteros muy precisa y rápida en un AVR es a través de una tabla de consulta recíproca. La tabla almacenará recíprocos escalados por un número grande (digamos 2 ^ 32). Luego implementas un unsigned32 x unsigned32 = unsigned 64 multiplicación en el ensamblador, así que responde = (numerador * inverseQ32 [denominador]) > > 32.
Implementé la función de multiplicación usando un ensamblador en línea, (envuelto en una función c). GCC admite "largos largos" de 64 bits, sin embargo, para obtener el resultado, debe multiplicar 64 bits por 64 bits, no 32x32 = 64 debido a las limitaciones del lenguaje C en la arquitectura de 8 bits ...

La desventaja de este método es que usarás 4K x 4 = 16K de flash si quieres dividir por enteros de 1 a 4096 ......

La división sin firma muy precisa ahora se logra en aproximadamente 300 ciclos en C.

Podría considerar el uso de enteros con escala de 24 o 16 bits para obtener más velocidad, menos precisión.

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

El valor de retorno de tu ecuación ya es p=298 ya que el compilador se divide primero y luego agrega, usa la resolución muldiv de entero que es:

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

Usando esto es lo mismo, multiplica a*f , con a = entero f = fracción.

Ese rendimiento r=a*f pero f=b/c luego r=a*b/c , pero aún no funciona debido a que la posición de los operadores produce la función final r=(a*b)/c o muldiv, una manera de calcular números de fracciones usando solo números enteros.

    
respondido por el nepermath

Lea otras preguntas en las etiquetas