El módulo
QEI de los dsPIC está compuesto por 3 pines de entrada, las dos fases + pulso Index, lo que nos permitirá gestionar sólo un
quadrature encoder. Así como de otro tipo de módulos viene bien despachado, en este tema se han quedado un poco cortitos los señores de Microchip.
Pero nuestro solitario módulo es capaz de:
- contar pulsos ascendentes o descendentes hasta una palabra de 16 bits
- contar en resolución simple (x2) o doble (x4)
- filtrar la señal de entrada con filtros digitales programables
- generar una interrupción según determinados eventos

Para colmo, también podemos utilizar la entrada Phase-A como entrada de Timer si necesitamos alguno más, además de los 9 que trae.
Este es el diagrama del módulo QEI:
Modos de funcionamientoA medida que nuestro encoder va girando se van sucediendo pulsos en las entradas Phase-A, Phase-B e Index como hemos descrito anteriormente. El módulo QEI las utilizará según esté configurado:
-
Phase-A: cada flanco de subida o bajada incrementa/decrementa el contador
-
Phase-B: permite determinar la dirección del movimiento. Además, en modo de alta resolución (4x) incrementa/decrementa el contador.
-
Index: se recibe un pulso por cada revolución (si el encoder tiene esta salida). El módulo puede resetear el contador de posición al recibir dicho pulso.
El contador de posición se almacena en el registro
POSCNT que es registro de lectura y escritura.
El registro
MAXCNT nos permitirá determinar el límite máximo al que queremos que llegue. Una vez superado la cuenta vuelve a empezar por 0.
El único pin de salida de nuestro módulo es el
UPDN que se pondrá a 0 en un sentido de giro y a 1 en el otro. Este pin puede activarse o desactivarse según se establezca en la configuración.
También se puede programar un modo de control de errores. Nuestro QEI puede estar esperando un pulso
Index en el momento programado como recuento máximo y si no lo recibe generará un error.
El módulo QEI también puede utilizarse como un Timer estándar con las mismas características que el resto de los timer, a las que hay que añadir la posibilidad de cuenta descendente, que se elige en la configuración.
A todas estas prestaciones, el módulo QEI añade una serie de
filtros digitales programables que asegurarán la calidad de la señal recibida incluso en ambientes adversos.
InterrupcionesEl módulo QEI puede generar interrupciones por cualquiera de estos eventos:
- cuando el contador de 16 bits pasa de 0x0000 o de 0xFFFF
- detección de un pulso de Reset o un nuevo error
- fin de periodo del Timer, cuando funciona como timer
- evento de acumulación Gate
ConfiguraciónEl módulo QEI se configura mediante los siguientes bits de configuración:
QEICON.QEIM<2:0>: admite los siguientes valores
- 111: resolución 4x, puesta a 0 del contador POSCNT cuando se alcanza MAXCNT
- 110: resolución 4x, puesta a 0 del contador POSCNT al recibir un pulso en Index.
- 101: resolución 2x, puesta a 0 del contador POSCNT cuando se alcanza MAXCNT
- 100: resolución 2x, puesta a 0 del contador POSCNT al recibir un pulso en Index.
- 001: uso como Timer
- en el resto de combinaciones, el módulo QEI estará apagado.
QEICON.QEISIDL: si lo ponemos a 0, el módulo seguirá funcionando cuando el micro entre en modo Idle.
QEICON.SWPAB: podemos seleccionar si queremos las entradas Phase-A / Phase-B en sus pines originales (0) o intercambiadas entre sí (1).
QEICON.PCDOUT: si colocamos un 1, activará el uso del pin UPDN para informar del sentido de giro del encoder. Si ponemos un 0, el pin se dejará como un I/O más.
QEICON.TQGATE: activa o desactiva el modo de acumulación de Gate.
QEICON.TQCKPS<1:0>: configuración del prescaler cuando actúa como Timer
-
00 prescaler 1:1
-
01 prescaler 1:8
-
10 prescaler 1:64
-
11 prescaler 1:256
QEICON.POSRES: hay que poner a 1 este bit si queremos el un pulso en Index ponga a 0 el contador POSCNT.
QEICON.TQCS: seleccionamos la fuente para el timer. Con un 1 serán los pulsos recibidos en Phase-A y con un 0 será la frecuencia de instrucciones.
QEICON.UPDN_SRC: sólo funciona en modo Timer. Admite las siguientes dos opciones:
-
1: la dirección del contador del Timer vendrá dada por el estado del pin Phase-B
-
0: la dirección del contador del Timer vendrá dada por el bit UPDN.
DFLTCON.IMV<2:0>: este par de bits tienen funciones distintas, según tengamos configurado el módulo a 2x o 4x
a 4x: indicaremos cómo tienen que estar las señales Phase-A (IMV1) y Phase-B (IMV0) cuando se reciba un pulso de Reset.
a 2x: con el primer bit seleccionamos cuál de las dos señales será crítica al recibir un pulso Index (0 para Phase-A y 1 para Phase-B). Con el segundo bit indicaremos el estado al que tendrá que estar la señal seleccionada al recibir el Index.
DFLTCON.CEID: lo ponemos a 0 si queremos activar la interrupción por recuento de errores.
DFLTCON.QEOUT: se activa el filtro digital poniendo este bit a 1.
DFLTCON.QECK<2:0>: seleccionaremos el divisor de clock para el filtro digital, según la siguiente relación:
- 111 = 1:256
- 110 = 1:128
- 101 = 1:64
- 100 = 1:32
- 011 = 1:16
- 010 = 1:4
- 001 = 1:2
- 000 = 1:1
Además del registro POSCNT, el módulo nos informará de su funcionamiento en los siguientes bits:
QEICON.INDEX: indicará el estado de la entrada Index
QEICON.UPDN: indicará el sentido del movimiento
EjemploA continuación pego un programa de ejemplo y un vídeo con su ejecución.
#include <p33FJ64MC706.h>
#define __33FJ64MC706_H
_FOSCSEL(3);
_FOSC (194);
_FICD (223);
#include "LCD_HD44780.h"
#include <stdio.h>
char yo[] = {'N','o','c','t','u','r','n','o',' ','-',' ','2','0','0','6','\0'};
const elrebujito[] = {'Q','U','A','D','R','A','T','U','R','E',' ','E','N','C','.','\0'};
const c1[] = {24,24,24,24,24,24,24,24};
const c2[] = {12,12,12,12,12,12,12,12};
const c3[] = {6,6,6,6,6,6,6,6};
const c4[] = {3,3,3,3,3,3,3,3};
const c5[] = {1,1,1,1,1,1,1,1};
void barrita (int posicion) {
char x,col;
x = ((posicion % 80) / 5);
col = (posicion % 5);
if (x==0)
LCD_Goto (16,2);
else
LCD_Goto (x,2);
printf (" ");
LCD_Goto (x+1,2);
printf ("%c",col+1);
if (x==15)
LCD_Goto (1,2);
else
LCD_Goto (x+2,2);
printf (" ");
}
int main(void){
int i=1;
CLKDIVbits.PLLPRE=10;
CLKDIVbits.PLLPOST=1;
PLLFBD=238;
AD1PCFGL=0xFFFF;
AD2PCFGL=0xFFFF;
TRISB=0xFFFF;
LCD_Inicializacion();
LCD_Goto (1,1);
LCD_EscribeCadena(yo);
LCD_Goto (2,2);
LCD_EscribeConstante(elrebujito);
delay_ms(300);
LCD_DefineCaracter (8,c1);
LCD_DefineCaracter (16,c2);
LCD_DefineCaracter (24,c3);
LCD_DefineCaracter (32,c4);
LCD_DefineCaracter (40,c5);
POSCNT = 120;
QEICONbits.QEIM = 0b100; // resolución 4x, modo de Reset por MAXCNT
QEICONbits.QEISIDL = 0; // continuar en modo idle (0)
QEICONbits.SWPAB = 1; // Phase-A y Phase-B intercambiados
QEICONbits.PCDOUT = 1; // activado el pin de Position Counter Status
QEICONbits.TQGATE = 0; // Timer gate apagado
QEICONbits.TQCKPS = 0; // Prescaler 1:1
QEICONbits.POSRES = 0; // Un pulso en INDEX no hace un reset
QEICONbits.TQCS =0; // Usamos clock interno para el timer
QEICONbits.UPDN_SRC=1; // Phase-B indica dirección
MAXCNT=160;
DFLTCON = 0x00F0;
LCD_BorraPantalla();
while(1) {
LCD_Goto (1,1);
printf ("Pos:%3u Dir:",POSCNT);
if (QEICON & 0x0800)
printf ("DER.");
else
printf ("IZQ.");
barrita (POSCNT);
};
}El vídeo con el funcionamiento del programa anterior puede ser visto aquí:
Este artículo es un resumen del publicado en
El Rebujito.