Lectura mejorada del codificador rotatorio Arduino

Los codificadores rotativos son excelentes dispositivos de entrada para proyectos de electrónica. Esperamos que este Instructable lo inspire y lo ayude a usar uno en su próximo proyecto.

¿Por qué escribir código de codificador rotatorio?

Quería usar un codificador rotatorio de bajo costo como mecanismo de entrada para uno de mis próximos proyectos y al principio me desconcertaron las opciones de código disponibles para tomar lecturas del codificador rotatorio y determinar cuántos "retenes" o ciclos había pasado el codificador y en que direccion Creo que mi boceto principal necesitará usar la mayor parte de la memoria de mi Arduino, por lo que estoy evitando las diversas bibliotecas de codificadores disponibles, que parecían difíciles de hacer cuando probé un par de ellas. También parecen usar mucho más del presupuesto de código que los enfoques de código basados ​​en bocetos discutidos de aquí en adelante.

Si solo quieres evitar el pensamiento detrás de mi enfoque y entrar directamente en el Instructable, ¡no dudes en avanzar al Paso 1!

Otros enfoques

Varios de los principales enfoques basados ​​en bocetos (es decir, no usan una biblioteca) se discuten en la publicación de blog de rt donde escriben código de codificador rotatorio que hace que los codificadores más baratos puedan usarse como entradas de Arduino. También tienen un buen ejemplo de la señal lógica que produce el codificador. rt descubrió que un sistema de interrupción del temporizador funcionó mejor para ellos, pero me preocupa que la frecuencia de sondeo disminuya la velocidad de actualización de la pantalla en el bucle principal del bosquejo de mi proyecto. Dado que el codificador rotatorio se moverá durante una pequeña proporción del tiempo que quiero que la pantalla se actualice, esto parece una mala coincidencia para mi aplicación.

Elegí comenzar usando el código de Steve Spence aquí, que estaba bien por sí mismo, pero parecía disminuir realmente cuando incorporé el resto de mi código de boceto (que implica escribir actualizaciones de pantalla en una pequeña pantalla TFT). Inicialmente, me preguntaba si podría ser porque el bucle principal contiene una declaración de rebote.

Luego leí el artículo del codificador rotatorio de Oleg en una versión de rutina de servicio de interrupción de su publicación anterior, también pensé que podría ser una buena idea usar la manipulación de puerto directo para leer ambos pines simultáneamente y tan pronto como se active la interrupción. Su código se puede usar en cualquier pin de entrada si se reescribe el código de manipulación del puerto. Por el contrario, decidí usar solo las interrupciones de hardware en los pines digitales 2 y 3, por lo que podemos configurar las interrupciones para que solo se activen en un borde ascendente del voltaje del pin, en lugar de en el cambio de voltaje del pin, que incluye los bordes caídos. Esto reduce la cantidad de veces que se llama al ISR, lo que distrae del bucle principal.

El código de Oleg usa una tabla de búsqueda para reducir el tamaño del código compilado a un tamaño realmente pequeño, pero no pude obtener resultados confiables que atraparían una rotación muy lenta y una rotación razonablemente rápida. Tenga en cuenta que la eliminación del rebote del hardware (consulte el Paso 2) puede ayudar mucho con la confiabilidad de las lecturas, pero estaba buscando una solución de software para simplificar la construcción del hardware y ser lo más portátil posible para otras aplicaciones de hardware.

Esto concluye la introducción de mi desafío y consideraciones. En el Paso 2, veremos el hardware del codificador, la terminología y algunas consideraciones prácticas cuando desee integrar un codificador rotatorio en su proyecto.

Paso 1: Un poco sobre los codificadores rotativos

¿Por qué los codificadores rotativos son tan geniales?

  1. A diferencia de un resistor / potenciómetro variable, tienen un recorrido infinito en cualquier dirección y, debido a que producen un "código gris" digital, puede escalar sus lecturas al rango que desee.
  2. La doble dirección los hace útiles para aumentar o disminuir un valor dentro de una variable o navegar por los menús.
  3. Finalmente, muchos de estos codificadores rotativos vienen con un botón central, que se puede usar para seleccionar elementos del menú, restablecer un contador o hacer cualquier cosa que se le ocurra que pueda adaptarse a un botón momentáneo.

Condiciones

  1. PPR: pulsos por rotación, generalmente 12, 20 o 24. También puede ver las especificaciones para la rotación máxima en rpm, etc. Esto probablemente esté determinado por la propensión del codificador a "rebotar" los contactos. Consulte a continuación.
  2. Detención: el pequeño clic de la acción a medida que salta a un punto de descanso natural entre pulsos. Puede haber un retén por pulso / ciclo (no igual a una rotación del eje) o dos.
  3. Rebote: los contactos mecánicos dentro del codificador literalmente rebotan lo suficiente como para saltar y retroceder en un contacto al girar, lo que puede conducir a demasiadas lecturas atribuidas a esa fase del viaje entre los retenes.
  4. Debounce: Esto se puede hacer en hardware, quizás con un condensador cerámico de bajo valor entre cada pin y tierra, o en software, quizás con un retraso. En cualquier caso, el objetivo es crear un sistema que ignore los contactos que rebotan.

Consejos

  1. Busque una sección roscada cerca de la base del eje y una tuerca correspondiente si desea montar su codificador en un panel o gabinete.
  2. Muchos mandos están disponibles para codificadores rotativos, y los más fácilmente disponibles vienen en ejes de 6 mm de diámetro.
  3. Preste atención a si el eje del codificador usa una cara plana o estrías para lograr un ajuste adecuado con la perilla.
  4. El cuerpo del codificador giratorio también puede venir con un pasador / trozo elevado, destinado a acoplarse con una pequeña muesca / orificio en su panel (probablemente oculto por su perilla) y evitar que su codificador gire cuando gire la perilla. Es posible que desee eliminar esto si puede crear suficiente fricción para evitar la rotación del cuerpo del codificador utilizando el perno de montaje para atornillar el codificador en el panel o la carcasa.
  5. Asegúrese de averiguar dónde está el estado de retención para su codificador y adapte su código en consecuencia. Mi ejemplo utiliza un codificador cuyos pines están desconectados de tierra y son elevados por sus respectivas resistencias pullup de entrada. Esto impulsa mi selección de una interrupción RISING. Si ambos pines estuvieran conectados a tierra cuando estaban detenidos, necesitarían un código que buscara el voltaje del pin CAÍDO.

Paso 2: el circuito

El circuito es muy simple. Necesitará:



• Un Arduino basado en ATMEGA328P, como el Uno, Pro Mini o Nano.
• Un codificador rotatorio en cuadratura mecánico (en lugar de óptico): este es el tipo más común, así que no se preocupe demasiado si no se especifica. Los listados de eBay y Aliexpress a menudo mencionarán a Arduino en la descripción y este es un buen indicador de que uno es adecuado.
• Cables de conexión / puente de conexión.
• Opcional: una placa de prototipos.


En primer lugar, busque una colección de tres pines en un lado del codificador. Estos son los tres para medir la rotación con nuestro código. Si hay dos pines juntos en otro lado, es probable que sean para el botón central. Los ignoraremos por ahora.

De los tres pines juntos, el pin de tierra del codificador está conectado al pin de tierra Arduino. Cualquiera de los otros dos pines está conectado al pin digital 2 y el resto está conectado al pin digital 3. Si su dirección de rotación no es la que desea, simplemente cambie los dos pines sin conexión a tierra.

Los pines 2 y 3 son importantes porque en los Arduinos basados ​​en ATMEGA328P son los únicos pines que tienen la capacidad de detectar interrupciones de cambio de pin RISING y FALLING. Las placas MEGA 2560, etc. tienen otros pines de interrupción de hardware que pueden hacer esto.

Nota: En el diagrama, el pin de tierra es uno de los pines finales. En realidad, el pin de tierra es a menudo el pin central, pero este no es siempre el caso, así que lea la hoja de datos o pruebe su codificador para averiguar qué pin está conectado a tierra.

Otra nota: ArneTR hizo un buen comentario acerca de no tener una conexión cableada por separado al voltaje positivo lógico (por ejemplo, 5 V o 3, 3 V) para el circuito del codificador rotatorio que se muestra. El Arduino no puede leer el codificador rotatorio sin una señal de tierra (a la que hemos conectado un cable) y el voltaje lógico (a veces anotado como Vcc o Vdd), entonces, ¿cómo puede el Arduino leer la lógica de este codificador sin un positivo? cable de voltaje? La respuesta es que el chip ATMEGA328P en el Arduino tiene un modo especial que puede configurar en los pines digitales (que estamos usando) donde un pin es automáticamente "elevado" al voltaje lógico por una resistencia interna. Busque en el código "pinMode (pinX, INPUT_PULLUP)" para vernos diciéndole al Arduino que queremos aprovechar este modo. Una vez configurados, solo necesitamos proporcionar al codificador un cable de tierra ya que los cables de detección de los pines digitales ya están proporcionando el voltaje lógico.

UNA COSA MÁS ... Githyuk descubrió que un codificador de marca en particular no funcionaba con esta forma de hacer las cosas (es decir, el código a continuación). Consulte la sección de comentarios para obtener detalles, pero en general, probar un codificador diferente sería un buen paso de depuración cuando haya agotado los pasos más fáciles / más rápidos / más baratos.

Paso 3: el código

Si no está familiarizado con la programación de Arduinos, póngase al día con este recurso de Arduino.

Este código es gratuito para su uso (ya que no tiene costo y se modificará a su gusto), por favor atribuya donde debería.


/ ******* Boceto de codificador rotativo basado en interrupciones *******
por Simon Merrett, basado en el conocimiento de Oleg Mazurov, Nick Gammon, rt, Steve Spence
* /

estático int pinA = 2; // Nuestro primer pin de interrupción de hardware es el pin digital 2
estático int pinB = 3; // Nuestro segundo pin de interrupción de hardware es el pin digital 3
byte volátil aFlag = 0; // avísenos cuando esperamos un borde ascendente en el pinA para indicar que el codificador ha llegado a un tope
byte volátil bFlag = 0; // avísenos cuando esperamos un borde ascendente en el pinB para indicar que el codificador ha llegado a un tope (dirección opuesta a cuando se establece aFlag)
codificador de bytes volátiles Pos = 0; // esta variable almacena nuestro valor actual de la posición del codificador. Cambie a int o uin16_t en lugar de byte si desea grabar un rango mayor que 0-255
byte volátil oldEncPos = 0; // almacena el último valor de posición del codificador para que podamos compararlo con la lectura actual y ver si ha cambiado (para saber cuándo imprimir en el monitor en serie)
lectura de bytes volátiles = 0; // en algún lugar para almacenar los valores directos que leemos de nuestros pines de interrupción antes de verificar si hemos movido un retén completo

configuración nula () {
pinMode (pinA, INPUT_PULLUP); // establece el pinA como entrada, elevado HIGH al voltaje lógico (5V o 3.3V para la mayoría de los casos)
pinMode (pinB, INPUT_PULLUP); // establece el pinB como entrada, elevado a la tensión lógica (5V o 3.3V para la mayoría de los casos)
attachInterrupt (0, PinA, RISING); // establece una interrupción en PinA, busca una señal de flanco ascendente y ejecuta la rutina de servicio de interrupción "PinA" (abajo)
attachInterrupt (1, PinB, RISING); // establece una interrupción en PinB, busca una señal de flanco ascendente y ejecuta la rutina de servicio de interrupción "PinB" (abajo)
Serial.begin (115200); // inicia el enlace del monitor serie
}

anular PinA () {
cli (); // detener las interrupciones antes de que leamos los valores del pin
lectura = PIND & 0xC; // lee los ocho valores de pin y luego quita todos los valores excepto pinA y pinB
if (lectura == B00001100 && aFlag) {// verifique que tengamos ambas clavijas en retención (ALTA) y que esperamos una retención en el borde ascendente de esta clavija
encoderPos -; // disminuye el recuento de posición del codificador
bFlag = 0; // restablecer banderas para el siguiente turno
aFlag = 0; // restablecer banderas para el siguiente turno
}
si no (lectura == B00000100) bFlag = 1; // señala que esperamos que el pinB señale la transición al retén desde la rotación libre
sei (); // reiniciar interrupciones
}

anular PinB () {
cli (); // detener las interrupciones antes de que leamos los valores del pin
lectura = PIND & 0xC; // lee los ocho valores de pin y luego quita todos los valores excepto pinA y pinB
if (lectura == B00001100 && bFlag) {// verifique que tengamos ambas clavijas en retención (ALTA) y que esperamos retención en el borde ascendente de esta clavija
encoderPos ++; // incrementa el recuento de posición del codificador
bFlag = 0; // restablecer banderas para el siguiente turno
aFlag = 0; // restablecer banderas para el siguiente turno
}
más si (lectura == B00001000) aFlag = 1; // señala que esperamos que el pinA señale la transición al retén desde la rotación libre
sei (); // reiniciar interrupciones
}

bucle vacío () {
if (oldEncPos! = encoderPos) {
Serial.println (encoderPos);
oldEncPos = encoderPos;
}
}

¡Eso es!

Archivos adjuntos

  • rotaryEncoder.ino Descargar

Paso 4: Conclusión

Espero que encuentre este código útil para su próximo proyecto que use un codificador rotatorio o que lo haya inspirado a considerar un codificador rotatorio como entrada para su próximo proyecto.

Resumen de los objetivos

He intentado escribir un código que logre un buen equilibrio de:

  • Portabilidad (el código de manipulación del puerto es el compromiso al pasar a otros chips)
  • Velocidad (la manipulación del puerto realmente ayuda)
  • Tamaño de código compilado bajo (ayuda en la manipulación de puertos y bitmath)
  • Graba confiablemente la rotación manual lenta y rápida
  • Llamadas de rutina de servicio de interrupción nugatoria reducidas (usando interrupción RISING y desactivando temporalmente las interrupciones)

Advertencias e ideas para mejorar

Este código no es perfecto de ninguna manera y es posible que desee cambiarlo para usar otros pines. Probé este código con el boceto que causaba la mayor demora y las lecturas menos confiables con los otros enfoques discutidos. Ciertamente no lo he comparado con los temporizadores para ver qué código produce menos rutinas de servicio de interrupción nugatorio, toma menos tiempo para ejecutarse o filtra el mayor porcentaje de rebotes de contacto. Quizás a alguien le gustaría hacer una prueba de referencia con los otros enfoques disponibles.

Artículos Relacionados