Autor Tema: Debo asignar mi uint32_t a un float para que no lo trunque en la operación?  (Leído 1817 veces)

0 Usuarios y 1 Visitante están viendo este tema.

Desconectado vra

  • PIC10
  • *
  • Mensajes: 27
Hola a todos, cómo están?.

Estoy haciendo el control de velocidad para un motor BLDC mediante un control PID, lo que necesito ahora es poder calcular la velocidad de giro del motor en forma legible. De momento puedo obtener mediante el uso de un temporizador el tiempo que tarda en cambiar de posición el motor leyendo los sensores de efecto HALL.

Para el cálculo de la velocidad tengo los siguientes datos

vel es un uint32_t donde se almacena el tiempo que tarda en cambiar el motor de una posición a otra
tRevCount es una constante la cual almacena la base de tiempo para el cálculo de la velocidad del motor en r.p.m., es decir, 60s.
prescalador es un uint16_t el cual almacena el valor del prescalador del temporizador.
HALL_nPos es una constante la cual almacena la cantidad de posiciones posibles que puede tomar el motor.

La frecuencia del reloj del micro la obtengo con la función CLOCK_SystemFrequencyGet() de las librerías de la microchip y retorna un UL.

La función con la cual pretendo calcular la velocidad en formato legible es la siguiente:

Código: [Seleccionar]
float BLDC_Motor_Check_Vel(uint32_t vel)
{
    uint16_t prescalador = 0;
    float div1 = 0, div2 = div1, div3 = div1, div4 = div1, div5 = div1;
    switch(T1CONbits.TCKPS)
    {
        case 0:
        {
            prescalador = 0;
        }
        break;
        case 1:
        {
            prescalador = 8;
        }
        break;
        case 2:
        {
            prescalador = 64;
        }
        break;
        case 3:
        {
            prescalador = 256;
        }
        break;
        default:
        {
            //No debe existir, ERROR.
        }
        break;
    }
    div5 = tRevCount/(vel*prescalador*HALL_nPos*2/CLOCK_SystemFrequencyGet());
    return div5;
}

 sin embargo, de esa manera el resultado a almacenar en div5 siempre es cero, para que me funcione tengo que realizar el cambio en el cálculo de div5 por el siguiente cálculo:

Código: [Seleccionar]
    div1 = vel;
    div2 = div1*2/CLOCK_SystemFrequencyGet();
    div3 = prescalador*HALL_nPos;
    div4 = div2 * div3;
    div5 = tRevCount/div4;

es decir, primero tengo que asignar la variable que tenga otro formato al tipo de variable de salida siendo esta última una de tipo flotante y no entiendo por qué, hacer un casting a float no me sirve ya solo toma el número como esté y interpreta como si fuese un flotante lo y obtengo de resultado un número incomprensible.

Cómo puedo resolver este problema?

Gracias de antemano por la ayuda brindada.

Desconectado vra

  • PIC10
  • *
  • Mensajes: 27
Re:Debo asignar mi uint32_t a un float para que no lo trunque en la operación?
« Respuesta #1 en: 28 de Junio de 2021, 17:11:55 »
Descuiden, leyendo la siguiente publicación:

https://www.todopic.com.ar/foros/index.php?topic=5557.0

comprendí que mi problema es de una pérdida de resolución y creo haberlo resuelto.

Desconectado remi04

  • PIC24F
  • *****
  • Mensajes: 657
Re:Debo asignar mi uint32_t a un float para que no lo trunque en la operación?
« Respuesta #2 en: 28 de Junio de 2021, 19:41:07 »
Tendrías que hacer cast a float a todas las variables de tipo uint involucradas sobre todo donde hay divisiones. 
« Última modificación: 28 de Junio de 2021, 20:09:21 por remi04 »

Desconectado Eduardo2

  • PIC24F
  • *****
  • Mensajes: 946
Re:Debo asignar mi uint32_t a un float para que no lo trunque en la operación?
« Respuesta #3 en: 29 de Junio de 2021, 01:23:25 »
Importa el orden en que se hacen las multiplicaciones y divisiones.

Si se empieza ejecutando una división entre enteros el resultado será entero, jamás 0.25 por ejemplo.

La variable prescalador toma valores 0 -->  estás en el horno porque no podés dividir por 0.

En la rutina solo pueden variar prescalador y vel (que están dividiendo) , hacé directamente:

Código: [Seleccionar]
#define tRevCount 60.
#define HALL_nPos  4. 
//  con punto para que sean float y no confiar en los defaults
float Ks = CLOCK_SystemFrequencyGet()*tRevCount/(2.*HALL_nPos) ;


al principio del programa y después terminás  BLDC_Motor_Check_Vel  con:

Código: [Seleccionar]
      return Ks/(prescalador*vel) ;

Desconectado vra

  • PIC10
  • *
  • Mensajes: 27
Re:Debo asignar mi uint32_t a un float para que no lo trunque en la operación?
« Respuesta #4 en: 30 de Junio de 2021, 16:19:01 »
Importa el orden en que se hacen las multiplicaciones y divisiones.

Si se empieza ejecutando una división entre enteros el resultado será entero, jamás 0.25 por ejemplo.

La variable prescalador toma valores 0 -->  estás en el horno porque no podés dividir por 0.

En la rutina solo pueden variar prescalador y vel (que están dividiendo) , hacé directamente:

Código: [Seleccionar]
#define tRevCount 60.
#define HALL_nPos  4. 
//  con punto para que sean float y no confiar en los defaults
float Ks = CLOCK_SystemFrequencyGet()*tRevCount/(2.*HALL_nPos) ;


al principio del programa y después terminás  BLDC_Motor_Check_Vel  con:

Código: [Seleccionar]
      return Ks/(prescalador*vel) ;

Las constantes desde el inicio las tengo definidas como tu y apliqué este código pero no me funcionó, obviamente estoy aplicando las operaciones con flotantes mal así que me toca preguntar cuál es el criterio que debo utilizar para hacer bien las operaciones con flotantes

Desconectado vra

  • PIC10
  • *
  • Mensajes: 27
Re:Debo asignar mi uint32_t a un float para que no lo trunque en la operación?
« Respuesta #5 en: 30 de Junio de 2021, 21:02:22 »
Oigan, alguna locura debo haber estado haciendo porque me di cuenta después de haber perdido mucho tiempo buscando el error donde no estaba, que el problema no es la función que me calcula la velocidad sino el dato de entrada "vel".

Pasa lo siguiente: "vel" es un uint32_t el cual almacena la la cantidad de incrementos totales del timer 1 entre posiciones del motor, mi criterio de diseño es hacerlo genérico ya que en un futuro espero que cercano voy a estar controlando la velocidad pero necesito primero poder presentarla.

La cosa va así, como el timer 1 va a ser el mismo timer que va a manejar el RTC, no puedo estarlo reiniciando a cada rato, por lo cual ideé lo siguiente:

                    ...................|...............|...............|..............
                   t0     ^          1               2     ...       n             ^
                           |                                                            |
                          ini                                                          fin

"ini" es el instante de tiempo en el que se activa el motor (valor de TMR1 en ese instante), almacenado en tPrev.
Los valores "1 a n" indican la cantidad de veces que el timer 1 realizó una  cuenta completa de 2^16 bits
"fin" es el instante en que el motor llega al punto donde se detecta mediante los sensores de efecto HALL un cambio de posición (desplazamiento de 60 grados físicos en el caso de mi motor).
El intervalo de tiempo entre "ini" y el instante "1" lo calculo restándole "ini" a 2^16.
La cantidad de cuentas totales que hace el timer 1 con desborde completo (2^16 bits contados) la calculo multiplicando 2^16 por "n-1" debido a que el desborde número 1 no se cuenta por estar incompleto, para esto las librerías de microchip tienen una función llamada TMR1_SoftwareCounterGet() la cual retorna la cantidad de desbordes.
El intervalo de tiempo "fin" el es valor actual del TMR1 en el instante en el que se detecta el cambio de posición y lo almaceno en tActual.

Entonces, la cantidad de cuentas totales la calculo de la siguiente manera:

Código: [Seleccionar]
vel = ((uint32_t) (0xffff - tPrev))
                + 0xffff *((uint32_t) (TMR1_SoftwareCounterGet() - 1))
                + ((uint32_t) tActual);

esto lo ingreso como parámetro en la función Check_Vel y se realizan los cálculos, estuve realizando los cálculos internos de Check_Vel por fuera con una calculadora y en todos los casos el resultado que da Check_Vel es correcto pero el valor de entrada "vel" tiene como contenido un numero de cuentas totales aproximado de 31 millones, Check_Vel arroja como resultado una velocidad de aproximadamente 0,16 rpm considerando que esas 31 millones de cuentas al ser convertidas con la función Check_Vel dan aproximadamente 735 segundos una revolución completa, cuando el cambio de posición del motor es medido con una herramienta externa la el intervalo de tiempo es de aproximadamente 0,47 segundis inicialmente lo cual da un tiempo de aproximadamente 2,85 segundos para una revolución completa lo cual significa que mi calculo de cuentas es una locura.

Por esta razón me toca preguntarles cuál falla ven en la determinación de "vel"

Desconectado Eduardo2

  • PIC24F
  • *****
  • Mensajes: 946
Re:Debo asignar mi uint32_t a un float para que no lo trunque en la operación?
« Respuesta #6 en: 30 de Junio de 2021, 23:28:31 »
Cuando operás con enteros tenés que tener cuidado con el truncamiento en la división y con el overflow en producto/suma/resta.

Ademas, en C se realiza la operación de izquierda a derecha respetando la prioridad de los signos y ajustando el tipo de variable temporal.

O sea, si tenés:

Código: [Seleccionar]
float w , x1=2.33  ;
uint16_t n1=3 , n2=4 , n3=350 , n4=290 ;


//  Error por truncamiento

w = n1/n2 ;     // MAL
                // resulta w=0   
                // porque primero hace n1/n2 como división entera
                // y después convierte el resultado a float.

w = x1*n1/n2 ;  // OK
                // resulta w=1.7475
                // porque primero hace x1*n1 y el resultado es float
                // y despues divide ese float por n2
               
w = x1*(n1/n2); // MAL
                // resulta w=0   
                // porque primero hace lo que está entr () que es 0

w = n1/5  ;     // MAL
                // resulta w=0
                // pues la se hace en enteros.
               
w = n1/5. ;     // OK
                // resulta w=0.6
                // pues la constante 5. es float

//  Error por overflow

w = x1/(n3*n4); // MAL
                // en XC8 y CCS resulta w=0 pero depende como trate el compilador el error
                // n3 y n4 son uint16_t --> el producto 350*290 da overflow y te devuelve 0
               
w = x1/n3/n4 ;  // OK
                // resulta w=2.2955 10^(-5)
                // es la misma operación que la anterior, pero se evalúa primero x1/n3 que sale float
                // y luego el float dividido n4

En el mensaje anterior me comi el overflow de prescalador*vel , ya que si
prescalador=256 y vel=31000000  el producto da overflow
Debió ser 
Código: [Seleccionar]
       return Ks/prescalador/vel


Respecto a la manera de calcular vel , te complicás la vida y el resultado es incorrecto (parecido pero incorrecto)
HAé directamente:  vel =  tActual + TMR1_SoftwareCounterGet*0x10000 - tPrev ;
« Última modificación: 01 de Julio de 2021, 00:03:54 por Eduardo2 »

Desconectado vra

  • PIC10
  • *
  • Mensajes: 27
Re:Debo asignar mi uint32_t a un float para que no lo trunque en la operación?
« Respuesta #7 en: 15 de Julio de 2021, 18:52:35 »
@Eduardo2 Muchas gracias por el aporte, disculpen la tardanza de nuevo pero no me llegan las notificaciones y no encuentro la opción para activarlas...o están activadas pero el foro no me las envía, no se por qué.

Otra cosa, un amigo me comentó que para evitar problemas de precisión quizás era mejor utilizar enteros de 64 bits y realizar las operaciones con esos enteros, que después las escale pero no entiendo la forma de hacerlo, podrían explicarme o al menos enviarme un enlace/tutorial para trabajar decimales con enteros de 64 bits?.

Desconectado Eduardo2

  • PIC24F
  • *****
  • Mensajes: 946
Re:Debo asignar mi uint32_t a un float para que no lo trunque en la operación?
« Respuesta #8 en: 15 de Julio de 2021, 23:46:48 »
...
Otra cosa, un amigo me comentó que para evitar problemas de precisión quizás era mejor utilizar enteros de 64 bits y realizar las operaciones con esos enteros, que después las escale pero no entiendo la forma de hacerlo,
Tu amigo recomienda que uses aritmética de punto fijo (Googlear).   
Es una alternativa para mejorar el error de redondeos o cuando los resultados o las entradas tienen una cantidad fija de decimales.

Citar
podrían explicarme o al menos enviarme un enlace/tutorial para trabajar decimales con enteros de 64 bits?.
En punto fijo los números se guardan como un entero (N)  pero está implícito que el número representado es N/d .
Ahí la suma/resta es:  N3=N1±N2
El producto necesita un ajuste:  N3=N1*N2/d
La division también:  N3=d*N1/N2

Si trabajás a 2 decimales: d=100 ; si es con 3 decimales: d=1000
Pero también puede ser d=4, 16, 256, 1024,...    que tiene la ventaja que los ajustes en multiplicación/diivisión son desplazamientos.

De todas maneras, las operaciones con enteros o punto fijo no tienen recetas for dummies.  Si la operación es larga y no tenés  claro lo que estás haciendo vas a tener errores por truncamiento/overflow a cada rato.  Y si es corta, es mas sencillo tunear la escritura de la operación que cualquier otro método.   Sencillamente porque se depende mucho del tipo, orden y cantidad de operaciones.

La única solución es saber como se evalúa la operación y se va manejando los tipos de variable ,  y tener un mínimo de conocimientos en álgebra para reacomodar la operación. 
Ojo que un mínimo conocimiento en álgebra no es el de la ESO, pues ése es nulo y empeorando.


Cuando la operación es sencilla bastan los ojos para darse cuenta de la influencia de los errores de truncamiento, pero cuando es de mediana a complicada ya puede ser muy difícil seguir la propagación (en procesos iterativos por ejemplo).   
En estos últimos casos, el truco usado desde los comienzos de la informática es hacer dos programas: Uno con tipos de variable exagerados (cálculo preciso y lento) y otro con tipos de variable mas eficientes  --> Se comparan las diferencias y se corrige donde lo merezca.



Desconectado remi04

  • PIC24F
  • *****
  • Mensajes: 657
Re:Debo asignar mi uint32_t a un float para que no lo trunque en la operación?
« Respuesta #9 en: 16 de Julio de 2021, 03:05:33 »
...
Otra cosa, un amigo me comentó que para evitar problemas de precisión quizás era mejor utilizar enteros de 64 bits y realizar las operaciones con esos enteros, que después las escale pero no entiendo la forma de hacerlo,
Tu amigo recomienda que uses aritmética de punto fijo (Googlear).   
Es una alternativa para mejorar el error de redondeos o cuando los resultados o las entradas tienen una cantidad fija de decimales.

Citar
podrían explicarme o al menos enviarme un enlace/tutorial para trabajar decimales con enteros de 64 bits?.
En punto fijo los números se guardan como un entero (N)  pero está implícito que el número representado es N/d .
Ahí la suma/resta es:  N3=N1±N2
El producto necesita un ajuste:  N3=N1*N2/d
La division también:  N3=d*N1/N2

Si trabajás a 2 decimales: d=100 ; si es con 3 decimales: d=1000
Pero también puede ser d=4, 16, 256, 1024,...    que tiene la ventaja que los ajustes en multiplicación/diivisión son desplazamientos.

De todas maneras, las operaciones con enteros o punto fijo no tienen recetas for dummies.  Si la operación es larga y no tenés  claro lo que estás haciendo vas a tener errores por truncamiento/overflow a cada rato.  Y si es corta, es mas sencillo tunear la escritura de la operación que cualquier otro método.   Sencillamente porque se depende mucho del tipo, orden y cantidad de operaciones.

La única solución es saber como se evalúa la operación y se va manejando los tipos de variable ,  y tener un mínimo de conocimientos en álgebra para reacomodar la operación. 
Ojo que un mínimo conocimiento en álgebra no es el de la ESO, pues ése es nulo y empeorando.


Cuando la operación es sencilla bastan los ojos para darse cuenta de la influencia de los errores de truncamiento, pero cuando es de mediana a complicada ya puede ser muy difícil seguir la propagación (en procesos iterativos por ejemplo).   
En estos últimos casos, el truco usado desde los comienzos de la informática es hacer dos programas: Uno con tipos de variable exagerados (cálculo preciso y lento) y otro con tipos de variable mas eficientes  --> Se comparan las diferencias y se corrige donde lo merezca.

  De esto del punto fijo saben muchos los que solo tocan asm y se ven en la necesidad de hacer operaciones complejas. Si al menos se cuenta con un uC con instrucciones específicas para esto, como los dspic por ejemplo todo se facilita mucho. Incluso en un compilador C que evidentemente optimizaría el programa haciendo uso de esas instrucciones directas del procesador.

  Yo por ejemplo sufría mucho el trabajo en punto fijo cuando programaba en Basic con un compilador que no implementa flotantes y que encima el tipo de variable más grande que admitía era un long de 16 bits. 

 Aún así en C cuidado con todo lo que comentas, overflow, redondeos, casting no realizados, prioridad, etc, por que a la hora de hacer operaciones complejas o involucradas en iteraciones y demás, uno se hecha las manos a la cabeza o se pega un tiro entre pierna y pierna.  Lo que primero pase  :D 

  Saludos.