Hola,
Os voy a explicar el problema que me he encontrado con CCS y su forma de operar con los números con signo.
El problema me ha venido al implementar un movimiento de "rebote" de una bolita en una matriz de leds usando un acelerómetro de 3 ejes.
Para dar realismo a la bolita, tenía que hacerla rebotar contra los bordes de la matriz ( como si estuviera en una cajita )con lo cual esto implica:
- invertir el sentido de marcha de la bolita en los rebotes
- invertir el sentido de la marcha de la bolita cuando la gravedad contrarreste la velocidad de ascension
- aplicar una absorción a la velocidad de la bolita en el rebote (perdida de enercia potencial en el impacto)
Utilizando un vector de 2 dimensiones para la velocidad v(x,y), ésta va a aumentar con el tiempo en una proporción directa a su aceleración, es decir, el valor que nos devuelva el acelerómetro sobre ese eje, va a ir aumentando o reduciendo la velocidad del mismo en el vector v(x,y)
Supongamos que la variable (pax) nos da el valor del punto de reposo del eje del acelerómetro ( cero grados )
Y que la variable (ax) es el valor analógico que nos devuelve el acelerómetro en ese eje.
entonces tenemos que la inclinación ( factor de aceleracion ) será ax-pax lo que implica que la aceleración será positiva hacia un lado y negativa hacia el contrario.
Bien , ahora la fórmula que nos va a dar la velocidad en el eje va a ser del tipo:
vel_x = vel_x + (ax - pax);
La velocidad será tambien negativa hacia un lado y positiva hacia el otro ¿porque el signo? pues porque es la manera más simple de cambiar la posición aplicando aceleración y velocidad:
pos_x = pos_x + vel_x;
y juntando todo, tenemos un bucle (que es la recta del tiempo en la ecuacion v=a*t ):
While(1);
{
set_adc_channel(1); //Leemos valor en ADC
delay_us(10);
ax = read_adc();
vel_x = vel_x + (ax - pax) //velocidad va aumentando o disminuyendo
//según la inclinación
pos_x = pos_x + vel_x; //La posición varía según la velocidad
}
Ahora resulta que como la velocidad crece muy rápido la pelota pasa del principo al final de la matriz en un instante, entonces es necesario aplicar una corrección al incremento de la velocidad, así que voy a dividir por (por ejemplo por 10) la velocidad, ya que si divido la aceleracion (ax-pax) me quedo sin rango en ella porque es una escala muy pequeña (aproximadamente 1 bit por cada 2 grados de inclinación).
Como lo importante es preservar memoria y capacidad de cómputo en un PIC, en lugar de la división por 10, lo divido por 8 desplazando 3 bits a la derecha:
While(1);
{
set_adc_channel(1); //Leemos valor en ADC
delay_us(10);
ax = read_adc();
vel_x = vel_x + (ax - pax)>>3 //velocidad va aumentando o disminuyendo
//según la inclinación. Aceleracion dividida por 8
pos_x = pos_x + vel_x; //La posición varía según la velocidad
}
Como quiero que el movimiento sea suave y solo tengo (por ejemplo) una matriz de 20 leds de eje x, y pos_x crece y disminuye muy rápido, entonces voy a trabajar en una proporcionalidad de 1/1000, es decir, el rango del eje x va a ser (0-19999) con lo que la posición real de x en la matrix será:
px = pos_x/1000
Bien, ahora ¿que pasa cuando la bola rebota en los bordes de la matriz pos_x=0 y pos_x=20000? pues sencillamente si invertimos el signo de la velocidad, pos_x tambien cambiará de sentido de desplazamiento, y si mantenemos la inclinación que hizo desplazarse la bola hacia el borde en el que ha rebotado, la velocidad va a ir reduciendose proporcionalmente según avance el tiempo ( otra vez v=a*t y e=v/t ).
Ejemplo:
Valor ADC ax=500, punto de 0 grados pax=425.
Factor de aceleracion (ax-pax)=75
Supongamos que llega al borde con una velocidad vel_x=17500, entonces cuando pos_x=19999, vel_x=0 - vel_x y ahora vel_x =-17500
Conforme avanza el tiempo y si se mantiene la inclinacion ( aceleracion ax-pax=75), vel_x va a ir -17500,-17425,-17350..... hasta llegar a cero y luego empezará a aumentar 0,75,150,225,..... que será cuando la pelota llegue al final de su rebote y vuelva a descender.
Así que añadimos el control del rebote en x=0 y x=19999:
While(1);
{
set_adc_channel(1); //Leemos valor en ADC
delay_us(10);
ax = read_adc();
vel_x = vel_x + (ax - pax)>>3 //velocidad va aumentando o disminuyendo
//según la inclinación. Aceleracion dividida por 8
pos_x = pos_x + vel_x; //La posición varía según la velocidad
if (pos_x < 0) {
pos_x = 0;
vel_x = 0 - (vel_x);
}
if (pos_x > 19999) {
pos_x = 19999;
vel_x = 0 - (vel_x);
}
}
Bien hasta aquí, la cosa parece sencilla. Ahora resulta que el rebote así no es real, ya que cuando una pelota golpea el suelo, pierde energía y rebota a menor altura que desde donde se lanzó ( bueno, menos las pelotitas saltarinas endiabladas que por efecto de la contracción y expansión del caucho generan un rebote mayor, pero siempre acaban cayendo...
), así que tenemos que aplicar un efecto de absorción al rebote.
Muy fácil, además de cambiar de signo el valor de la velocidad, lo reducimos en una proporcion de 0.75 por ejemplo, que es lo mismo que multiplicarlo por 3 y dividirlo entre 4 (vel_x=vel_x * (3/4) ).
Y no nos olvidemos de definir correctamente las variables tipo int, long y signed según el caso en como se usen:
void bolita()
{
long ax,ay,pax,pay; //Definición de variables
signed long pos_x,pos_y;
signed long vel_x,vel_y;
int px,py;
While(1);
{
set_adc_channel(1); //Leemos valor en ADC
delay_us(10);
ax = read_adc();
vel_x = vel_x + (ax - pax)>>3 //velocidad va aumentando o disminuyendo
//según la inclinación. Aceleracion dividida por 8
pos_x = pos_x + vel_x; //La posición varía según la velocidad
if (pos_x < 0) { //rebote en x=0 con absorción de 0.75
pos_x = 0;
vel_x = 0 - (3*(vel_x>>2));
}
if (pos_x > 19999) { //rebote en x=19999 con absorción de 0.75
pos_x = 19999;
vel_x = 0 - (3*(vel_x>>2));
}
px = pos_x/1000 //posición real en la matriz
}
Bueno, todo esto en un uC Freescale y compilado en Codewarrior funciona ok, pero no así en CCS ¿Porque? pues después de unos cuantos rompederos de cabeza, resulta que por culpa del uso de los signos por parte del compilador.
El CCS usa complemento a 2 para los números negativos, pero con la particularidad que aún siendo un número de 16bits (tipo long) si el valor está entre -128 y 127 usa un complemento a 2 de 8bits ( el bit signo está en el 8º bit del byte de menor peso ) cuando lo normal es que el bit de signo estuviera en el 16º bit de la variable siendo un complemento a 2 de 16 bits.
Es decir, para una variable definida como: signed long vel_x
para vel_x = -85, el valor en Hex leído es de 00AB cuando en 16 bits debería ser FFAB que en binario sería:
-85 en 8bits=
10101011 (en rojo el bit de signo)
-85 en 16bits =
11111111 10101011 (en rojo el bit de signo)
Con las sumas y las restas, no hay problema porque se opera perfectamente, pero los productos y divisiones (internamente CCS usa muchos desplazamientos para operar productos y divisiones) es un desastre, porque se desplaza el bit de signo y ocurren cosas como esta:
Imaginemos que en la ejecución se produce un rebote de la bolita y la velocidad en ese momento era de -12500 = 0xCF2C
Entonces llegamos al punto del programa del cambio de signo de la velocidad y de aplicación del rebote:
vel_x = 0 - (3*(vel_x>>2));
Lo primero es que al hacer vel_x>>2 el resultado es:
-12500 = 0b_
11001111_00101100 >> 2 = 0b_00
110011_11001011 que es 13256 en lugar de -3125
Pensé en sustituir el desplazamiento >>2 por la división /4, pero el resultado es el mismo, ya que internamente no tiene en cuenta los signos en los productos ni las divisiones.
Con el producto -12500*3 me ocurrió algo similar.
Busqué alguna libreria de aritmética con signo pero no la encontré y además posiblemente ocuparía mucha RAM, así que opté por modificar las operaciones jugando con los signos.
La opción más rápida que se me ocurrió fue hacer las operaciones con el valor absoluto en caso de números negativos y luego devolverles el signo.
En el caso de -12500>>2 sería:
0 - (-12500 ) = 12500 le quito el signo.
12500 >> 2 Desplazo
0 - 3125 = -3125 Le devuelvo el signo
Pasandolo al programa, entonces tengo:
if (vel_x<0) //Si la velocidad es negativa
{
velx_tmp = 0 - (3*(0 - vel_x)>>2); //opero sin signo y se los devuelvo poniendolo en un temporal
}
else
velx_tmp = (3*vel_x>>2); // Si es positiva opero normalmente y pongo en temporal
if (pos_x < 0) { //si hay colision
pos_x = 0;
vel_x = 0 - velx_tmp; //invierto la velocidad
}
if (pos_x > 4999) { //si hay colision
pos_x = 4999;
vel_x = 0 - velx_tmp; //invierto la velocidad
}
Y el resto, bueno, pues lo mismo con el resto de operaciones y lo demás ya es historia...
Es una pena que CCS no sea capaz de operar completamente con los números con signo, ya que si se han definido como signed, deberían serlo desde el principio hasta el final ( como el toro que es toro hasta el final del rabo )
Si alguien ve una solución mejor o yo lo haya hecho mal y sí se pueda operar sin andar con cambios de signo, que me lo haga saber...
hasta aqui la experiencia y este es el resultado:
Salu2