Es sencillo el planteamiento, ya que lo único que cambia entre dos pasos consecutivos es un sólo bit, por lo tanto es lo único que tienes que cambiar.
Por ejemplo tienes
0b1001 Estado Anterior
0b1000 Estado actual
0b1100 Estado Siguiente
Observa que entre estados contiguos sólo cambia un bit, ya sea prendiéndolo o apagándolo, con lo cual reduces el número de operaciones.
Esto sigue lo que se conoce como código grey y es empleado mucho en telecomunicaciones para que si ocurre un error, el cambio es por un sólo bit, y por lo tanto sea más fácil identificar y corregir el error.
No es lo mismo detectar un error al pasar de un
0b0011 -> 0b0100
que pasar de un
0b0011 -> 0b0010
Como ejemplo te muestro lo que en Telecomunicación se conoce como una constelación 16-QAM
. . | . .
. . | . .
---|---
. . | . .
. . | . .
Tienes un total de 16 puntos en la constelación, los cuales en un inicio vamos a codificar de la siguiente manera.
0000 0100 | 1000 1100
0001 0101 | 1001 1101
----------|----------
0010 0110 | 1010 1110
0011 0111 | 1011 1111
Supongamos que el valor que se envió fue un 0001, que se encuentra en el cuadrante 1, en caso de que exista ruido en el medio de comunicación, y este ruido es lo suficientemente grande como para mandarlo para que se transforme en cualquiera de los puntos que tiene más cerca, que serían 0000,0010, 0101.
En el mejor de los casos se va a mover a 0000 o 0101, lo cual implica cambiar un sólo bit, por lo tanto tienes error en un sólo bit, pero en el peor de los casos, se va a mover a 0010, lo cual lo va a dejar con dos bits de error.
Cuando sigues una codificación grey, como el cambio entre adjuntos es en un sólo bit, el error se va a mantener en un sólo bit, a menos que el error ya sea exagerando que no permita una comunicación confiable.
Por ejemplo, una constelación 16-QAM siguiendo el código grey
0000 0100 | 1100 1000
0001 0101 | 1101 1001
----------|----------
0011 0111 | 1111 1011
0010 0110 | 1110 1010
Por ejemplo mandamos un 1111, con suficiente ruido este se puede transformar en 1101, 1110, 0111 o 1011, pero el error se mantiene en un sólo bit.
Por otro lado, hace tiempo hice lo siguiente para controlar dos motores, pero usé máquinas de estados y una interrupción de timer para manejar varias tareas, como la adquisición de datos externos que le permitieran decidir la dirección del robot, y por lo tanto la dirección de los motores. Para ello usé un pic16f628a con SDCC.
Primero definí los estados del Robot
//Robot global state
#define HLD 0
#define FW 1
#define BW 2
#define LFT 3
#define RGT 4
#define IDLE 5
//Individual Motor States
#define M_HLD 0
#define M_CW 1
#define M_CCW 2
#define M_IDLE 3
El robot puede estar en uno de 6 estados, Hold, Forward, Backward, Left, Right, Idle, y por lo tanto cada motor puede estar en Hold, Clock Wise, Counter Clock Wise e Idle. Esto es porque los motores lo único que saben es ir en sentido de las manecillas del reloj o encontra de las manecillas del reloj.
No usé medio paso así que definí una variable llamada M_STEPS_F con pasos completos
unsigned char M_STEPS_F[] = {0x0C, 0x06,0x03,0x09};
Además de definir variables extras como:
unsigned char adc_lectures[3] = {0,0,0}; //[MIC, RLDR, LLDR]
unsigned char current_channel = 0;
unsigned char c_task = 0;
unsigned char GLOBAL_STATE = IDLE;
unsigned char M1_STATE = M_IDLE;
unsigned char M2_STATE = M_IDLE;
Entonces cada vez que entraba a la interrupción del timer, leía un canal de ADC y llamaba una función para aplicar el estímulo al estado del robot, y si estaba relacionado con los canales de RLDR y LLDR y el valor estaba de bajo de cierto límite, entonces cambiaba el estado global del robot y llamaba la función set_direction().
void set_direction()
{
switch(GLOBAL_STATE)
{
case HLD:
M1_STATE = M_HLD;
M2_STATE = M_HLD;
break;
case FW:
M1_STATE = M_CW;
M2_STATE = M_CCW;
break;
case BW:
M1_STATE = M_CCW;
M2_STATE = M_CW;
break;
case LFT:
M1_STATE = M_CCW;
M2_STATE = M_CCW;
break;
case RGT:
M1_STATE = M_CW;
M2_STATE = M_CW;
break;
case IDLE:
M1_STATE = M_IDLE;
M2_STATE = M_IDLE;
break;
default:
M1_STATE = M_IDLE;
M2_STATE = M_IDLE;
break;
}
}
Lo mas seguro es la función set_direction va a ser diferente a lo que tu estas haciendo, ya que en lugar de ruedas, los motores estaban viendo hacia abajo.
Para mover el robot, lo único que hacía falta era que en la misma interrupción del timer llamar una función llamada move() solo en el caso que el contador de velocidad fuera mayor a cierto valor predefinido.
void move()
{
unsigned char m1_move, m2_move;
if(GLOBAL_STATE == HLD){}
else if(GLOBAL_STATE == IDLE)
{
PORTC = 0x00;
} else {
if(M1_STATE==M_CW)
{
m1_step_count++;
} else {
m1_step_count--;
}
if(M2_STATE==M_CW)
{
m2_step_count++;
} else {
m2_step_count--;
}
}
m1_move = M_STEPS_F[m1_step_count&0x03];
m2_move = M_STEPS_F[m2_step_count&0x03]<<4;
PORTC = m2_move + m1_move;
}
Si observas, sólo se requirió un definir una sóla vez el arreglo que contiene la secuencia de pasos, que es lo que KILLERJC está explicando.
¿Porqué m1_step_count&0x03 y m2_step_count&0x03 ?
Sólo me interesan los últimos dos bits del contador porque sólo tengo 4 elementos en el arreglo, así que los puedo dejar correr libremente.
Por ejemplo:
m = 0
m++
m = 1
indice = m&0x03
indice = 1
o
m--
m=255
indice = m&0x03
indice = 3
En tu caso, tienes un arreglo que tiene 8 entradas, de 0b00000000 a 0b00000111, así que también podrías dejar correr los contadores de los pasos libremente sólo que en lugar de m&0x03 tendrías un m&0x07.
Aquí la función de interrupción (SDCC).
void isr(void) __interrupt 0 {
if(T0IF)
{
m_velocity_task_counter++;
adc_lectures[current_channel] = read_adc();
apply_stimulus(current_channel,adc_lectures[current_channel]);
current_channel = (current_channel+1)%3;
set_adc_channel(current_channel);
if(m_velocity_task_counter > m_velocity)
{
move();
m_velocity_task_counter = 0;
}
switch(c_task&0x01)
{
case LEDS:
c_task++;
leds();
break;
case SOUND:
c_task++;
sound();
break;
}
TMR0 = STMR0;
T0IF = 0;
}
GIE = 1;
}
No muy elegante pero en ese momento hacía su trabajo ya que lo que quería mostrarle a mi sobrino como reciclar ciertos objetos que por lo general tenemos en las casas para hacer un robot. Después de tiempo que le mostré esto, ahora quiere un robot que caminé, tenga brazos y que lo pueda mover con el cuerpo.