Nivel: Intermedio
¿Qué tengo que saber para este post?
- Haber leído el post anterior.
- Tener conocimientos sobre programación en C.
- Conocer el funcionamiento de un encoder de cuadratura (acá voy a mencionarlo igual).
- Haber programado un timer en algún µC.
—————————————
Los timers generales con los que contamos en el STM32F1 tienen un Encoder Interface como fuente de clock para el contador. Esto es muy interesante ya que hacerlo por software suele ser muy ineficiente, el hecho de contar con este mecanismo realmente nos simplifica la vida al trabajar con motores o con un encoder de cuadratura mecánico u óptico.
¿Cómo funciona un encoder de cuadratura?
El funcionamiento de un encoder lo pueden encontrar en muchas muchas partes, por ejemplo en este video:
En el video anterior usaban un método por software bastante rústico para leerlo. Lo ideal sería utilizar 2 entradas de interrupción consultando el estado del otro pin y así saber si hay que sumar o restar… cosas que quedarán en el pasado sabiendo que tenemos el hardware que ya nos soluciona la vida.
¿Cómo lo hace?
Para intentar entender cómo funciona el mecanismo, veamos un recorte de la figura 100 del Reference Manual de ST que ya he mostrado en otro post:
En la figura anterior vemos que el control de la interfaz es a través de las señales TI1FP1 y TI2FP2. Podemos optar por contar por una, por la otra o ambas (próxima figura). Para eso debemos utilizar las entradas o canales 1 y 2 del TIM que son las que se conectan a TI1/IC1 e TI2/IC2.
Este modo es parte de los modos slave del contador. Así que, para acceder a él utilizaremos la siguiente función de libOpenCM3 y con alguno de los defines a continuación:
void timer_slave_set_mode(uint32_t timer_peripheral, uint8_t mode);
/* Modos:
* + TIM_SMCR_SMS_OFF : Modo esclavo desactivado
* + TIM_SMCR_SMS_EM1 : Modo Encoder 1 - flanco en TI1FP1
* + TIM_SMCR_SMS_EM2 : Modo Encoder 2 - flanco en TI2FP2
* + TIM_SMCR_SMS_EM3 : Modo Encoder 3 - ambos flancos
*/
Una vez configurado debemos mapear los IC a los TI con la siguiente función:
void timer_ic_set_input(uint32_t timer_peripheral, enum tim_ic_id ic, enum tim_ic_input in);
Luego queda habilitar el contador con timer_enable_counter()
como ya lo hacíamos. A partir de este momento, en el CNT llevará la contabilización de los pasos de nuestro encoder. Si hubo una vuelta o un paso tendremos 4 pulsos almacenados en el contador. Cabe destacar que el ARR sigue funcionando es decir que al llegar al valor del ARR el CNT vuelve a 0 y en caso de haber un downflow se carga este valor. El valor que cargamos allí dependerá de la aplicación en sí.
El ejemplo con un rotary encoder mecánico
A continuación les expondré el circuito que use para el siguiente ejemplo y el funcionamiento del mismo. En sí es muy sencillo, básicamente se coloca el rotary en las entradas CH1 y CH2 del TIM4 (PB6 y PB7), y se lo configura.
El rotary que utilicé es uno de los más vendidos y utilizados (el mismo del video). Éste tiene 20 posiciones, es decir que al dar la vuelta completa obtendremos 80 cuentas (4 pulsos por paso). Los LEDs los usaré como contador binario, por lo tanto, haciendo una cuenta rápida, dividiré la posición del encoder por 5 para ver como se van prendiendo en secuencia hasta dar la vuelta completa y volver a 0 (cero).
Aquí el código completo:
#include <libopencm3/stm32/rcc.h>
#include <libopencm3/stm32/gpio.h>
#include <libopencm3/stm32/timer.h>
int main()
{
// SYSCLK a 72Mhz
rcc_clock_setup_in_hse_8mhz_out_72mhz();
/*
Se configuran los pinea del PA0 al PA3 como salidas para visualizar
el cambio en la cuenta.
*/
rcc_periph_clock_enable(RCC_GPIOA);
gpio_set_mode(
GPIOA,
GPIO_MODE_OUTPUT_2_MHZ,
GPIO_CNF_OUTPUT_PUSHPULL,
GPIO0 | GPIO1 | GPIO2 | GPIO3);
// Se habilita el clock del TIM4.
rcc_periph_clock_enable(RCC_TIM4);
/*
El encoder de cuadratura genera 4 pulsos cuando pasa de una posición
a la otra y tiene 20 posiciones, por lo tanto 20*4 = 80 pulsos por vuelta.
*/
timer_set_period(TIM4, 79);
/*
Modo encoder 3: Sube y baja el contador por cualquiera de los pulsos en
TI1FP1 o TI2FP2.
*/
timer_slave_set_mode(TIM4, TIM_SMCR_SMS_EM3);
// Configuración de los Canales de Entrada TI1 y TI2:
timer_ic_set_input(TIM4, TIM_IC1, TIM_IC_IN_TI1);
timer_ic_set_input(TIM4, TIM_IC2, TIM_IC_IN_TI2);
// Se habilita el contador:
timer_enable_counter(TIM4);
uint16_t old_pos = 0, new_pos;
while (true)
{
new_pos = timer_get_counter(TIM4);
if (old_pos != new_pos) // Ante un cambio en la posición:
{
gpio_clear(GPIOA, GPIO0 | GPIO1 | GPIO2 | GPIO3);
gpio_set(GPIOA, new_pos / 5); // 80 -> 16 (4 bits)
old_pos = new_pos;
}
}
}
Sinceramente ahorra un montón de dolores de cabeza (porque por software podemos llegar a perdernos algún pulso… ¡aunque usemos interrupciones!). Además siempre que se pueda aprovechar una ventaja de hardware es conveniente hacerlo. Aquí ni siquiera necesitamos interrupciones para trabajar. Les dejo el link al repo de gitlab para clonarlo directo en PlatformIO:
Dejar una respuesta