Codificador rotatorio STM32 con interrupciones de hardware

Estoy tratando de hacer que un codificador rotatorio funcione en mi STM32.

  1. Tengo el canal A y B subiendo a 3V y rebotando con capacitores de 1uF.
  2. La placa tiene los canales A y B conectados a PA11 y PA10 respectivamente y tiene interrupciones de hardware configuradas para ambos
  3. Probé varios algoritmos diferentes para decodificar la dirección de rotación, pero no importa lo que haga, no puedo obtener interrupciones alternas consistentes (es decir, ABABAB)
  4. Intenté disparar en ambos bordes, solo cayendo, solo subiendo y no importa lo que obtenga, las interrupciones se activan aparentemente al azar.

¿Hay algo que estoy haciendo mal? ¿Las interrupciones no son suficientes para mantener la velocidad del codificador? ¿El rebote no es suficiente (se ve bien en un osciloscopio, pero tal vez las interrupciones sean más sensibles)? ¿Hay una mejor manera de hacer esto en general? He estado atascado en esto por un tiempo, así que cualquier ayuda sería genial. Avíseme si es necesaria más información.

*Código editado para reflejar cambios (algoritmo de trabajo)

MANEJO DE INTERRUPCIONES

static int8_t states[] = {0,-1,1,0,1,0,0,-1,-1,0,0,1,0,1,-1,0};
uint8_t RotaryCurrentState = 0x00;
uint8_t RotaryTransition = 0;
int8_t RotaryPosition = 0;

/*Rotary Encoder Interrupt Handler
Channel A (IRQ11) & B (IRQ10)

              CW --->
 A (IRQ11)  ¯|___|¯¯¯¯|___|¯¯¯¯
 Interrupts  ^   ^    ^   ^
 B (IRQ 10) ¯¯¯|___|¯¯¯¯¯|___|¯
 Interrupts    ^   ^     ^   ^
                  CCW <---

void EXTI4_15_IRQHandler(void)
{ 
  RotaryCurrentState = (Read_IO(ROTARY_A) << 1) | Read_IO(ROTARY_B);
  RotaryTransition = (RotaryTransition <<2 ) | RotaryCurrentState;
  RotaryPosition = RotaryPosition + states[RotaryTransition & 0x0F];

  EXTI_ClearITPendingBit(EXTI_Line10);  //Clear Channel B
  EXTI_ClearITPendingBit(EXTI_Line11);  //Clear Channel A
}

C PRINCIPAL

//Initialize 
RotaryTransition = Read_IO(ROTARY_A) << 3 | Read_IO(ROTARY_B) << 2 | \
                   Read_IO(ROTARY_A) << 1 | Read_IO(ROTARY_B);

while(1)
{
  //CW Transition
  if (RotaryPosition == 4)
  {
    STM_EVAL_LEDToggle(LED4);
    RotaryPosition = 0;
  }
  //CCW Transition
  else if (RotaryPosition == -4)
  {
    STM_EVAL_LEDToggle(LED3);
    RotaryPosition = 0;
  }
}
Los codificadores en el STM32F4 se pueden manejar directamente con temporizadores.
Desafortunadamente, estoy atascado usando estos IO debido al diseño de la placa y las limitaciones de IO. No creo que puedan hacer la decodificación usando el temporizador.
Estos codificadores tienen dos canales. La forma más fácil de usarlos es usar uno como 'reloj' y adjuntarle una interrupción activada por un solo borde, luego el otro canal actúa como un indicador de 'dirección'. En cada interrupción, inspecciona el valor del canal de 'dirección' y suma o resta un paso del valor actual.
Detalles importantes: ¿Cómo se elevan exactamente las entradas de interrupción? ¿Las entradas de interrupción tienen histéresis?
¿No te estás moviendo demasiado? ¿No debería el 8 ser un 2?
Es posible que en realidad no necesite eliminar el rebote de un codificador de cuadratura: puede oscilar entre dos estados, pero no debe acumular recuentos falsos, ya que solo un canal debe estar en la "zona de rebote" a la vez. Es concebible que un circuito de rebote mal pensado pueda conducir a una interpretación errónea. Para que ambos canales reboten a la vez, debe tener una tasa de rotación (conmutación) cercana a la duración del rebote, momento en el que no parece que sea posible definir una característica de rebote que lo excluya sin que a veces enmascarar la señal real.
Probé el método de cronometraje y, aunque era un poco más confiable, todavía no se podía usar.
Las líneas están siendo elevadas por resistencias de 10k en el tablero. No veo por qué tendrían histéresis, pero tal vez me estoy perdiendo algo.
Estoy codificando cada estado como un número de 8 bits y los estados combinados como 16 bits, así que creo que estoy cambiando correctamente, aunque quizás no de la manera más eficiente.
La histéresis de @spizzak puede ser crítica si intenta detectar un borde en una entrada con un borde que sube lentamente. Tiene una constante de tiempo RC de 10 ms en sus entradas que es muy, muy lenta y las deja susceptibles a disparos falsos debido al ruido. Los procesadores NXP que uso tienen histéresis en las entradas para eliminar este problema, no sé sobre los procesadores ST.
¡¡Buen trabajo!! Lo único que agregaría a esto es establecer un indicador en la interrupción y solo ejecutar la verificación en el bucle principal si hay un cambio en el estado del codificador: while(~encoderflag){} ; ..... Es solo para que sepa que está ingresando al siguiente bloque en el lugar apropiado
Detalle menor, pero puede salirse con la suya con solo una llamada a EXTI_ClearITPendingBit:EXTI_ClearITPendingBit(EXTI_Line10 | EXTI_Line11)

Respuestas (1)

Ese no es un gran algoritmo en su controlador. Deberías tener CERO si. Sin decisiones.

Guarde su estado AB, es decir, 00 o 01, luego agregue su siguiente estado, es decir, 0001 significa que AB pasó de 00 a 01, por lo que B cambió de 0 a 1. Haga de esto un +1. Si comienza desde 00 y cambia a 10, llámelo -1. Cree una matriz de 16 elementos de todas las transiciones posibles que contengan el número que debe agregarse a su conteo si ocurre, y tenga en cuenta que algunas son ilegales y deben manejarse.

0000       0    0  no transition


0001       1   +1

0010       2   -1

0011       3    0   Illegal, two transitions,  and so on.

Indexe esta matriz en cada transición, esté atento a los eventos ilegales y trátelos como mejor le parezca. Agregue el resultado al conteo. Cambie los nuevos valores al punto de valor anterior en el número de índice y repita para siempre

en pseudocódigo

 signed int8 add_subt[16] = { 0, 1 ,-1 ,  ....};
 unsigned int8 idx;
 signed int32 pos_count;

main() {
% initialize idx
idx = readA <<3 + readB<<2 + readA<<1 + readB;
while(1){}
}

interrupt_on_any_change(){
idx=idx<<2 & 0x0F + readA<<1 + readB;
pos_count=pos_count+add_subt[idx];
}

Podría mantener err_idx para ayudarlo a marcar malas transiciones

¿No se ve un cabello más simple?

Esto parece tener sentido y vi un ejemplo similar en línea mientras buscaba que implementaba la matriz como una máquina de estado 4x6 que pensé que era un enfoque interesante. Entonces, básicamente, dispararía en el borde ascendente y descendente de ambos canales y luego leo el valor de ambas líneas en cada interrupción.
Sí. dispara en cada transición y lee A y B en los dos bits inferiores del índice.
Tampoco es necesaria una máquina de estado. Una matriz, por índice, le dice si necesita incrementar o disminuir su conteo.
¡Implementado y funcionando muy bien! Gracias por la sugerencia. He actualizado mi código en la publicación original a la versión de trabajo.
¡¡Buen trabajo!! Originalmente, pensé que estaba tratando de usar esto para el codificador cuádruple de un motor, en cuyo caso las interrupciones en los DIO probablemente estarían en el límite, ¡pero luego me vinculé a la parte con la que estaba trabajando! Creo que en realidad puedes hacer esto un poco más pequeño usando dos enteros, uno para incrementar y otro para decrementar, y enmascarando el bit apropiado, algo así como count + incr&(1<<idx) - decr&(1<<idx) , pero si puede pagar la matriz, ¿por qué no?
¡Este es un estilo de codificación incrustado! Muy a menudo vemos interrupciones con muchas cosas adentro. ¡No! Las interrupciones deben ser como la propuesta: ¡Maldita sea rápido!
Vaya, ese último bit sería contar = contar + (incr&(1<<idx))>>idx - (decr&(1<<idx))>>idx. Creo que es un poco menos de montaje que (incr&(1<<idx))>0. Tengo la sensación de que el código podría ser un poco más lento para este enfoque que el enfoque de matriz. El enfoque correcto, por supuesto, depende de la cantidad de memoria que intente extraer del sistema.
Al completar la tabla de transiciones, ¿no son todas las demás transiciones un estado no válido o una transición cero?
@Michael No. Hay entradas legítimas para cada estado inicial. 0 1 puede ir a 0 0 por ejemplo. El fragmento de tabla que mostré es solo para comenzar en 0 0
@Scott gracias. Creo que me falta una comprensión básica de cómo funciona este codificador o este algoritmo. Pensé que el objetivo es contar efectivamente las transiciones de borde positivas con la relación entre A y B determinando la dirección. Como tales, las otras transiciones no son necesarias para contar y, por lo tanto, ¿no brindan información útil para la suma?
La interrupción se ejecuta en cada transición, por lo que las transiciones se hornean en el pastel.
Veo. Encontré algo de ayuda en otra página usando este tipo de algoritmo. El punto que me perdí es que la relación entre pos_count y el DPR del codificador: 360/(degrees_per_revolution*4) =degrees_per_pos_count