Leitura do Encoder E2-Q2
This tutorial is not available in English

Introdução
Existem diversas aplicações em que precisamos saber em que posição o eixo de um motor está, ter um retorno de qual é sua velocidade atual ou até mesmo saber em que sentido o eixo do motor está girando. Nestes casos podemos adicionar um encoder ao eixo do motor (seja ele de entrada ou de saída) e monitorar esses parâmetros que são essenciais nestes tipos de aplicações.
Neste tutorial vamos aprender como usar o módulo encoder E2-Q2 para identificar o sentido e a velocidade de rotação do motor DC com caixa de redução e eixo duplo.
Lista de Materiais
Conceitos Teóricos
Encoder
Um encoder é um dispositivo eletromecânico que é responsável por transformar um movimento ou uma posição em um sinal elétrico. Este tipo de dispositivo costuma ser desenvolvido com um de dois tipos de sensores: os ópticos e os magnéticos (também conhecidos como de efeito Hall). Os encoders baseados em sensores de efeito Hall utilizam um ímã fixado no eixo do motor para identificar a mudança de pólo magnético, como vimos em nosso tutorial sobre o sensor de fluxo de água. Já os encoders com sensores ópticos necessitam de um disco/engrenagem com alguns furos/dentes para identificar a passagem ou a reflexão de luz emitida por um LED, como na imagem a seguir.

Fonte: Analog IC Tips
Normalmente os encoders geram um sinal analógico nas suas saídas, o que pode dificultar a leitura em microcontroladores, portanto é comum converter esse sinal para uma onda quadrada (digital), por exemplo com um Schmitt Trigger.
Encoder de Quadratura
Embora encoders com um sensor único funcionem muito bem e ajudem a obter a rotação de um motor, eles não são capazes de nos ajudar a identificar em que sentido o motor está girando. Para isso precisamos adicionar mais um sensor ao sistema do encoder, criando um modelo chamado de encoder de quadratura. Este tipo de encoder possui duas saídas de sinais de onda quadrada, e essas ondas possuem uma defasagem ("offset") de 90° entre cada borda de descida ou de subida, como na imagem a seguir.

Fonte: Electronics Tutorials
Por conta dessa defasagem de 90° entre as ondas dos sinais de saída, há uma divergência na sequência lógica de cada canal que permite identificar o sentido de rotação do eixo do motor. A tabela a seguir ilustra a sequência lógica de cada canal, em ambos os sentidos e iniciando em níveis lógicos baixos.
Sequência Lógica | |||
---|---|---|---|
Horário | Anti-Horário | ||
Canal A | Canal B | Canal A | Canal B |
0 | 0 | 0 | 0 |
0 | 1 | 1 | 0 |
1 | 1 | 1 | 1 |
1 | 0 | 0 | 1 |
O módulo encoder fabricado pela RoboCore realiza a leitura da reflexão de luz infravermelha emitida pelo sensor QRE, que é retornada para o sensor quando um dente da engrenagem do kit está sobre o componente. E, como ele é composto de dois sensores QRE, ele também é um encoder de quadratura, portanto utilizaremos essa sequência lógica para determinar o sentido de rotação do motor deste tutorial.
Montagem Mecânica
Antes de mais nada, siga o manual do botão abaixo para fixar o módulo encoder na lateral do motor e prender o disco de seis dentes ao eixo do motor.
Manual de MontagemCircuito
Com o conjunto completo fixado, monte o circuito a seguir.

Código
Agora que o circuito está montado, carregue o código a seguir para sua placa.
Observação: se você estiver utilizando um encoder com disco de leitura de 10 dentes, altere o valor da variável
NUMERO_DENTES
de 6 para 10, como mencionado no comentário do código.
/*******************************************************************************
* Primeiros Passos com o Modulo Encoder E2-Q2 (v1.0)
*
* Codigo que determina o sentido e a rotacao do eixo do motor DC com
* caixa de reducao e eixo duplo e exibe os dados no monitor serial. As
* informacoes obtidas do motor sao calculadas e exibidas no monitor serial
* a cada minuto. Para controlar o motor, pressione os botoes ("Btn0" e "Btn1")
* da propria Julieta.
*
* Copyright 2020 RoboCore.
* Escrito por Giovanni de Castro (29/10/2020).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version (<https://www.gnu.org/licenses/>).
*******************************************************************************/
//Declaracao dos pinos de controle do motor DC da Julieta
const int PINO_INA = 7;
const int PINO_ENA = 5;
//Declaracao dos pinos conectados aos botoes da Julieta
const int PINO_BOTAO1 = A0;
const int PINO_BOTAO2 = A1;
//Declaracao dos pinos conectados aos canais de saida do encoder
const int PINO_CH2 = 2;
const int PINO_CH1 = 3;
//Declaracao das variaveis auxiliares para a verificacao do sentido
int estado;
int ultimo_estado;
boolean sentido;
//Declaracao das variaveis auxiliares para o calculo da velocidade
unsigned long contador1;
unsigned long contador2;
const int NUMERO_CONTADORES = 2;
const int NUMERO_LEITURAS = 2;
//Variavel de numero de dentes do disco de leitura
const int NUMERO_DENTES = 6; //Altere se necessario
//Declaracao das variaveis auxiliares para a temporizacao de um minuto
unsigned long tempo_antes = 0;
const long MINUTO = 60000;
//---------------------------------------------------------------------------------------
void setup() {
//Inicializacao do monitor serial
Serial.begin(9600);
Serial.println("Monitoramento de Sentido e de Rotacao de Motor DC");
//Configura os pinos de controle do motor como uma saida
pinMode(PINO_INA, OUTPUT);
pinMode(PINO_ENA, OUTPUT);
//Inicia os pinos de controle do motor em nivel logico baixo
digitalWrite(PINO_INA, LOW);
analogWrite(PINO_ENA, 0);
//Configura os pinos conectados aos botoes como uma entrada
pinMode(PINO_BOTAO1, INPUT_PULLUP);
pinMode(PINO_BOTAO2, INPUT_PULLUP);
//Configuracao dos pinos conectados aos canais do encoder como entrada
pinMode(PINO_CH2, INPUT);
pinMode(PINO_CH1, INPUT);
//Inicializa as interrupcoes com os pinos configurados para chamar as funcoes
//"contador_pulso2" e "contador_pulso1" respectivamente a cada mudanca de estado das portas
attachInterrupt(digitalPinToInterrupt(PINO_CH2), contador_pulso2, CHANGE);
attachInterrupt(digitalPinToInterrupt(PINO_CH1), contador_pulso1, CHANGE);
}
//---------------------------------------------------------------------------------------
void loop() {
//Controla o sentido dos motores e reinicia as leituras
if (digitalRead(PINO_BOTAO1) == LOW || digitalRead(PINO_BOTAO2) == LOW) { //Se um dos botoes for pressionado
delay(30);
if (digitalRead(PINO_BOTAO1) == LOW || digitalRead(PINO_BOTAO2) == LOW) {
if (digitalRead(PINO_BOTAO1) == LOW) { //Se o botao "0" for pressionado
//Aciona o motor em um dos sentidos
digitalWrite(PINO_INA, HIGH);
} else { //Se o botao "1" for pressionado
//Aciona o motor no sentido inverso
digitalWrite(PINO_INA, LOW);
}
//Zera os contadores e reinicia a contagem de tempo
contador1 = 0;
contador2 = 0;
tempo_antes = millis();
//Para o motor e aguarda um segundo para o eixo parar completamente
analogWrite(PINO_ENA, 0);
delay(1000);
//Rampa de aceleracao do motor
for (int i = 0; i < 255; i++) {
analogWrite(PINO_ENA, i);
delay(10);
}
}
}
//Verifica a contagem de tempo e exibe as informacoes coletadas do motor
if ((millis() - tempo_antes) > MINUTO) { //A cada minuto
//Verifica a variavel "sentido"
if (sentido) { //Se ela for verdadeira ("true")
Serial.print("Sentido: Horario");
Serial.print(" | ");
} else { //Se ela for falsa ("false")
Serial.print("Sentido: Anti-Horario");
Serial.print(" | ");
}
//Calcula a velocidade e exibe no monitor
int media = (contador1 + contador2) / (NUMERO_CONTADORES); //Calcula a media dos contadores
int velocidade = media / (NUMERO_DENTES * NUMERO_LEITURAS); //Calcula a velocidade de acordo com o numero de dentes do disco
Serial.print("Velocidade: ");
Serial.print(velocidade);
Serial.println(" RPM");
//Zera os contadores e reinicia a contagem de tempo.
contador1 = 0;
contador2 = 0;
tempo_antes = millis();
}
}
//---------------------------------------------------------------------------------------
//Funcao de interrupcao
void contador_pulso2() {
//Incrementa o contador
contador2++;
//Verifica o sentido de rotacao do motor
estado = digitalRead(PINO_CH2);
if (ultimo_estado == LOW && estado == HIGH) {
if (digitalRead(PINO_CH1) == LOW) {
sentido = true;
} else {
sentido = false;
}
}
ultimo_estado = estado;
}
//Funcao de interrupcao
void contador_pulso1() {
//Incrementa o contador
contador1++;
}
Entendendo o Código
O código inicia com a declaração das variáveis que armazenam os pinos de controle do motor DC da Julieta (PINO_INA
e PINO_ENA
), os pinos conectados aos botões embutidos da placa (PINO_BOTAO1
e PINO_BOTAO2
) e os pinos conectados aos canais do encoder (PINO_CH1
e PINO_CH2
).
xxxxxxxxxx
//Declaracao dos pinos de controle do motor DC da Julieta
const int PINO_INA = 7;
const int PINO_ENA = 5;
//Declaracao dos pinos conectados aos botoes da Julieta
const int PINO_BOTAO1 = A0;
const int PINO_BOTAO2 = A1;
//Declaracao dos pinos conectados aos canais de saida do encoder
const int PINO_CH2 = 2;
const int PINO_CH1 = 3;
Em seguida declaramos as variáveis estado
, ultimo_estado
e sentido
, que armazenam o estado atual de leitura de um dos canais, o estado anterior da leitura do mesmo canal e o sentido em que o eixo do motor está girando. Além disso, declaramos dois contadores, um para cada canal do encoder, juntamente com três variáveis constantes que nos ajudarão a converter os pulsos contados nos canais do encoder para um valor de rotação. Por fim, temos a declaração das variáveis tempo_antes
e MINUTO
, que serão responsáveis por realizar um período de espera de um minuto.
xxxxxxxxxx
//Declaracao das variaveis auxiliares para a verificacao do sentido
int estado;
int ultimo_estado;
boolean sentido;
//Declaracao das variaveis auxiliares para o calculo da velocidade
unsigned long contador1;
unsigned long contador2;
const int NUMERO_CONTADORES = 2;
const int NUMERO_LEITURAS = 2;
const int NUMERO_DENTES = 6;
//Declaracao das variaveis auxiliares para a temporizacao de um minto
unsigned long tempo_antes = 0;
const long MINUTO = 60000;
Já na rotina de configurações do código, configuramos os pinos de controle do motor DC da Julieta como saídas do sistema com um nível lógico baixo inicial, os pinos conectados aos botões embutidos da Julieta como entradas com "pull-up" ativado (INPUT_PULLUP
), e os pinos conectados aos canais do encoder como entradas do sistema. Por fim, inicializamos as duas interrupções do código, uma para cada porta de interrupção usada (os mesmos pinos conectados aos canais do encoder), que são responsáveis por chamar as funções contador_pulso1()
e contador_pulso2()
a cada mudança de nível lógico da porta da placa (CHANGE
), seja ele de alto ("HIGH") para baixo ("LOW"), ou vice versa.
xxxxxxxxxx
//Inicializacao do monitor serial
Serial.begin(9600);
Serial.println("Monitoramento de Sentido e de Rotacao de Motor DC");
//Configura os pinos de controle do motor como uma saida
pinMode(PINO_INA, OUTPUT);
pinMode(PINO_ENA, OUTPUT);
//Inicia os pinos de controle do motor em nivel logico baixo
digitalWrite(PINO_INA, LOW);
analogWrite(PINO_ENA, 0);
//Configura os pinos conectados aos botoes como uma entrada
pinMode(PINO_BOTAO1, INPUT_PULLUP);
pinMode(PINO_BOTAO2, INPUT_PULLUP);
//Configuracao dos pinos conectados aos canais do encoder como entrada
pinMode(PINO_CH2, INPUT);
pinMode(PINO_CH1, INPUT);
//Inicializa as interrupcoes com os pinos configurados para chamar as funcoes
//"contador_pulso2" e "contador_pulso1" respectivamente a cada mudanca de estado das portas
attachInterrupt(digitalPinToInterrupt(PINO_CH2), contador_pulso2, CHANGE);
attachInterrupt(digitalPinToInterrupt(PINO_CH1), contador_pulso1, CHANGE);
A repetição do código, por sua vez, inicia com a verificação se um dos botões internos da Julieta foi pressionado, com a condição if (digitalRead(PINO_BOTAO1) == LOW || digitalRead(PINO_BOTAO2) == LOW)
, e então, depois de um debounce (delay(30)
), verificamos qual dos botões foi pressionado usando a condição if (digitalRead(PINO_BOTAO1) == LOW)
, que é seguida da condição else
para o segundo botão do sistema. Caso um dos botões seja pressionado, o motor DC é controlado para o sentido correspondente. Juntamente com isso e independentemente do botão pressionado, nós zeramos os contadores do código, atualizamos a contagem de tempo do sistema, paramos o motor por meio do comando analogWrite(PINO_ENA, 0)
, aguardamos um segundo (delay(1000)
) para o motor parar completamente, e, por fim, realizamos a rampa de aceleração do motor.
xxxxxxxxxx
//Controla o sentido dos motores e reinicia as leituras
if (digitalRead(PINO_BOTAO1) == LOW || digitalRead(PINO_BOTAO2) == LOW) { //Se um dos botoes for pressionado
delay(30);
if (digitalRead(PINO_BOTAO1) == LOW || digitalRead(PINO_BOTAO2) == LOW) {
if (digitalRead(PINO_BOTAO1) == LOW) { //Se o botao "0" for pressionado
//Aciona o motor em um dos sentidos
digitalWrite(PINO_INA, HIGH);
} else { //Se o botao "1" for pressionado
//Aciona o motor no sentido inverso
digitalWrite(PINO_INA, LOW);
}
//Zera os contadores e reinicia a contagem de tempo
contador1 = 0;
contador2 = 0;
tempo_antes = millis();
//Para o motor e aguarda um segundo para o eixo parar completamente
analogWrite(PINO_ENA, 0);
delay(1000);
//Rampa de aceleracao do motor
for (int i = 0; i <; i++) {
analogWrite(PINO_ENA, i);
delay(10);
}
}
}
Em seguida, verificamos se já se passou um minuto da contagem de tempo do sistema, pela condição if ((millis() - tempo_antes) > MINUTO)
. Se sim, o código verifica por meio da condição if (sentido)
se a variável sentido
é verdadeira ou não. Para o sentido horário, a variável sentido
é verdadeira ("true"), e para o sentido anti-horário ela é falsa ("false"). Além disso, declaramos a variável media
sendo igual à média de contagem dos dois sensores, que é equivalente à soma dos dois contadores (contador1
e contador2
) dividida pela variável NUMERO_CONTADORES
, que é igual a "2". Com a variável media
calculada, nós a utilizamos na declaração e atribuição de valor da variável velocidade
, que recebe o valor da média calculada dividida pelo produto da variável NUMERO_DENTES
, que é igual a "6", com a variável NUMERO_LEITURAS
, que é igual a "2" porque lemos as bordas de subida e de descida. Para finalizar esse laço, os contadores são zerados e atualizamos a contagem de tempo do sistema.
xxxxxxxxxx
//Verifica a contagem de tempo e exibe as informacoes coletadas do motor
if ((millis() - tempo_antes) > MINUTO) { //A cada um minuto
//Verifica a variavel "sentido"
if (sentido) { //Se ela for verdadeira ("true")
Serial.print("Sentido: Horario");
Serial.print(" | ");
} else { //Se ela for falsa ("false")
Serial.print("Sentido: Anti-Horario");
Serial.print(" | ");
}
//Calcula a velocidade e exibe no monitor
int media = (contador1 + contador2) / (NUMERO_CONTADORES); //Calcula a media dos contadores
int velocidade = media / (NUMERO_DENTES * NUMERO_LEITURAS); //Calcula a velocidade de acordo com o numero de dentes do disco
Serial.print("Velocidade: ");
Serial.print(velocidade);
Serial.println(" RPM");
//Zera os contadores e reinicia a contagem de tempo.
contador1 = 0;
contador2 = 0;
tempo_antes = millis();
}
Ao final do código, temos as duas funções chamadas pelas interrupções. A função contador_pulso1()
é responsável apenas por incrementar em uma unidade o contador contador1
. Já a função contador_pulso2()
, além de incrementar o contador contador2
, também verifica o sentido de rotação do motor.
Essa lógica de verificação de sentido do motor começa atribuindo a leitura digital do pino conectado ao canal 2 do encoder à variável estado
. Feito isso, verificamos através da condição if (ultimo_estado == LOW && estado == HIGH)
se o sinal do canal 2 passou de baixo ("LOW") para alto ("HIGH") entre as interrupções. Se essa condição for verdadeira, verificamos se a leitura digital do pino conectado ao canal 1 do encoder está em nível lógico baixo ("LOW"). Se essa condição for verdadeira, sabemos que o eixo do motor está girando no sentido horário, então atribuímos o valor "true" à variável sentido
. Entretanto, se a condição for falsa, sabemos que o eixo do motor está girando no sentido anti-horário, portanto atribuímos o valor "false" à variável sentido
. Por fim, atualizamos a variável ultimo_estado
com o valor que foi atribuído anteriormente à variável estado
.
xxxxxxxxxx
//Funcao de interrupcao
void contador_pulso2() {
//Incrementa o contador
contador2++;
//Verifica o sentido de rotacao do motor
estado = digitalRead(PINO_CH2);
if (ultimo_estado == LOW && estado == HIGH) {
if (digitalRead(PINO_CH1) == LOW) {
sentido = true;
} else {
sentido = false;
}
}
ultimo_estado = estado;
}
//Funcao de interrupcao
void contador_pulso1() {
//Incrementa o contador
contador1++;
}
O Que Deve Acontecer
Após carregar o código para a placa, alimente-a com a fonte de 9 V e pressione o botão "Btn0" ou "Btn1" para que o motor gire em um dos sentidos. Após um minuto, o monitor serial apresentará o sentido de rotação atual do motor e sua velocidade em Rotações por Minuto (RPM), como na imagem abaixo. Se quiser alterar o sentido de rotação do motor, basta pressionar o outro botão da placa, que o motor irá parar, e então partir no sentido contrário.

Indo Além
Com os conceitos que aprendemos neste tutorial, é possível controlar a velocidade do eixo do motor, basicamente como um servomotor, como mostramos em nosso tutorial Controle de Velocidade com Encoder E2-Q2. Outra possibilidade com o módulo encoder é o cálculo da distância percorrida por uma roda, por exemplo.
Conclusão
Neste tutorial aprendemos sobre o funcionamento de um encoder de quadratura e como aproveitar os seus sinais de saída para identificar o sentido de rotação do eixo do motor, assim como calcular a sua rotação.