Artigo visualizado 2069 vezes

Codificador Rotativo e Arduino: Considerações para uso

Introdução

Um codificador rotativo (Rotary Encoder em inglês) é um dispositivo eletromecânico capaz de fornecer pulsos elétricos a partir do movimento rotacional de seu eixo. Pode ser qualificado como um transdutor rotativo, ou angular.

São utilizados para converter deslocamentos rotativos ou lineares em impulsos elétricos de onda quadrada, que geram uma quantidade exata de impulsos por volta em uma distribuição uniforme destes ao longo dos 360 graus do giro de seu eixo.

Podem ser aplicados em conjunto com contadores, tacômetros, controladores lógicos programáveis (CLP), ou conversores de frequência para sinais analógicos. Constituem-se como parte de fresadoras de sistemas CAD/CAM (Computer-Aided Design / Computer-Aided Manufacturing), Itens robóticos, posicionadores angulares, velocímetros, rádios e aparelhos sonoros, além de mais uma diversidade de outras possibilidades.

Inicialmente abordo as variantes básicas de sinais obtidos do codificador rotativo, para depois tratar especificamente do uso de codificadores rotativos incrementais populares nos universos de prototipação, robótica educacional e maker.

Tecnologias de sinal dos codificares rotativos

Existem dois tipos de tecnologia de sinal do codificador: incremental; e absoluto. A primeira implica em custos menores, e maior probabilidade de perda de informação, a segunda inerentemente mais cara tem por característica a informação se apresentar com garantia de integridade.

Codificador Incremental

O codificador incremental é uma solução em automação industrial que funciona por meio de um sistema eletrônico externo. A peça interpreta a posição com base na contagem dos eventos ocorridos no dispositivo, tudo em forma de pulsos.

Os codificadores incrementais contam com um sistema eletrônico externo para interpretar a posição com base na contagem dos eventos que ocorreram nesse dispositivo. As saídas para codificadores incrementais podem vir na forma de um única onda quadrada (A), em sinais de ondas quadradas (A e B) para determinar a direção de rotação ou em ondas quadradas faseadas mais um índice ou pulso zero (marker) por volta (A, B e Z).

Diagramas de tempo de sinais de codificadores rotativos sem e com sinal de index
Figura 1: Sinais de codificadores incrementais

O conceito de ondas quadradas faseadas para determinar o sentido rotacional é muitas vezes referido como “quadratura”.

Os meios para atingir um sinal incremental são geralmente referidos como tecnologia do codificador. As duas principais categorias de tecnologia são óptica e magnética. Em ambas as tecnologias, o alinhamento de um sensor é executado para proporcionar a compatibilidade de saída.

A ilustração a seguir esquematiza a distribuição construtiva de codificador rotativo de única onda quadrada.

Ilustração da arquitetura básica para detecção de movimento em um codificador rotativo com sinal singular.
Figura 2: Aspecto esquemático de construção de um codificador rotativo de sinal singular

Já um codificador rotativo de dois sinais faseados e índice de volta, assume o aspecto da próxima ilustração:

Ilustração da arquitetura básica para detecção de movimento em um codificador rotativo com sinal faseado mais índice.
Figura 3: Aspecto esquemático de construção de um codificador rotativo de sinal faseado mais índice

Os codificadores rotativos incrementais podem ser encontrados no mercado com diversificadas quantidades de pulsos por revolução, desde 16 pulsos, onde cada pulso implica em um arco de 22,5°, além outras quantidades.

Como ilustrado a seguir, se apresentam três discos um para 16 pulso, outro para 32 e o último para 64 pulsos por rotação, respectivamente 22,5°; 11,25°; e finalmente 5,625° de arco por pulso.

Imagem de três discos de codificador rotativo incremental que geram diferentes número de pulsos por revolução.
Figura 4: Diferentes quantidades de pulsos por totação

Codificador Absoluto

Diferentemente do incremental, o codificador absoluto tem uma maneira mais precisa de detectar e explicitar a movimentação do dispositivo.

Estas peças fornecem uma maneira de se ter conhecimento do ângulo exato da rotação em relação ao dispositivo. O codificador absoluto trabalha com padrões binários de n-bits, que como não se repetem dentro da volta, oferecem à peça uma leitura de caráter instantâneo e absoluto, o que permite uma leitura direta da posição dispensando a contagem incremental dos pulsos por sistemas externos ao codificador.

Ilustração do grafismo de uma codificação digital em uma fita de codificador absoluto linear e o correspondente stream de bits representado.
Figura 5: Sinais de Codificador Absoluto de Código Binário de 7 bits

Caso a posição de rotação seja alterada, o valor da leitura também muda. No caso de haver engrenagens direcionadas ao controle do número de rotações de um codificador absoluto, este é chamado de multi-turn (multivoltas em português).

Existem ainda os codificadores absolutos ópticos, nos quais há uma fonte emissora de luz e um sensor. O disco, então, gira entre os dois e os recortes presentes nele geram um fluxo de luz para o sensor que é intermitente. Essa movimentação gera o “on-off” detectado pelo sistema do codificador.

Codificadores absolutos podem atingir resoluções elevadas como aproximadamente 40 segundos de arco ou 0,011°

A leitura de dados pode ser paralela ou serial. No modo paralelo, cada dígito tem um conjunto de fios, já no modo serial a leitura é obtida bit a bit em sincronia com um sinal de relógio.

O protocolo mais comum usado em codificadores absolutos é o SSI – Synchronous Serial Interface (Interface Serial Síncrona), já o BiSS permite uma comunicação bidirecional. Atualmente há no mercado codificadores com comunicação por barramento, permitindo a leitura de diversos codificadores em uma via de transmissão única, onde os protocolos DeviceNet, Profibus e Ethercat são os mais populares.

O código no disco do codificador absoluto, pode utilizar código binário ou código de Gray, como mostrado a seguir:

Ilustração de dois discos de codificadores rotativos absolutos, o primeiro à esquerda de código binário, de pois à direita de código gray.
Figura 6: Discos de Codificadores Rotativos com Códigos Binário e Gray
Obs.:  O código de Gray, inventado por Frank Gray, é um sistema de código binário não ponderado onde, de um número para o seguinte ou anterior apenas um bit varia. Sua criação remonta do tempo em que os circuitos lógicos digitais se realizavam com válvulas termoiônicas e dispositivos eletromecânicos, pois os contadores necessitavam de potências muito elevadas e geravam muito ruído quando vários bits modificavam-se simultaneamente.
Esta é a mesma codificação utilizada no Mapa de Karnaugh.

Exemplos de codificadores

Iniciando por codificadores profissionais e seguindo até os populares de baixo custo serão mostrados 16 exemplares de codificadores. Os primeiros quatro serão profissionais do tipo incremental, um deles, o primeiro, é linear magnético, em seguida um codificador para ser acoplado a rolamentos, depois dois codificadores rotativos um de 100 PPR (pulsos por rotação) e outro de 400 PPR.

Fotografia de um codificador incremental linear
Figura 7: Codificador incremental linear
Fotografia de um codificador incremental rotativo integrado a um rolamento
Figura 8: Codificador incremental rotativo para rolamento
Fotografia de um codificador rotativo incremental de cem pulsos por rotação
Figura 9: Codificador incremental rotativo 100 PPR
Fotografia de um codificador rotativo incremental de quatrocentos pulsos por rotação da marca Orange modelo 3806-OPTI-400-AB-OC
Figura 10: Codificador incremental rotativo 400 PPR

Seguindo estão três codificadores profissionais absolutos, sendo o último deles multivoltas, primeiro com interfaceamento paralelo, depois um de interfaceamento serial usando ethercat, finalmente um codificador rotacional absoluto multivoltas, com interface serial IO-Link.

Fotografia de um codificador rotativo absoluto com transimssão paralela de sinais
Figura 11: Codificador rotativo absoluto (sinal paralelo)
Fotografia de um codificador rotativo absoluto com transimssão seriada do sinal
Figura 12: Codificador rotativo absoluto (sinal em série)
Fotografia de um Codificador Rotativo Absoluto Multivoltas com transmissão de sinal em série da marca IO-Link.
Figura 13: Codificador Rotativo Absoluto Multivoltas com comunicação serial

Deste ponto em diante codificadores incrementais, os dois primeiros de sinal simples utilizados em robótica educacional, normalmente para medir velocidade e/ou deslocamento de veículos robotizados.

Fotografia de um disco de codificador rotativo de vinte pulsos por rotação e um módulo eletrônico de codificador rotativo de aplicação em construções maker ou robótica educacional e/ou recreativa
Figura 14: Codificador 20 PPR
Fotografia de um disco de codificador rotativo de vinte pulsos por revolução e um módulo eletrônico de codificador rotativo de outro modelo de aplicação em construções maker ou robótica educacional e/ou recreativa
Figura 15: Codificador rotativo 20 PPR

Estes codificadores incrementais de sinal faseado e são soluções comercialmente utilizáveis em produtos (não só hobby ou aprendizado).

Fotografia de um codificador rotativo profissional de baixo custo
Figura 16: Codificador rotativo incremental
Fotografia de um codificador rotativo incremental acoplado a um motor de corrente contínua
Figura 17: Codificador rotativo incremental acoplado a motor DC

Este codificador é utilizado largamente em mouses para detectar o movimento da “roda de scroll”.

Fotografia de dois codificadores rotativos incrementais de mesmo modelo, vista superior e viats inferior, usado na construção de mouses de computadores, para detectar movimentação da roda de rolagem (scroll)
Figura 18: Codificador rotativo incremental para uso em mouse

Daqui por diante, são codificadores incrementais para controles de equipamentos, primeiro os de implementação de potenciômetro ou dial eletrônico. Podendo ter o eixo translúcido com um LED, até mesmo RGB, fornecendo iluminação.

Fotografia de três codificadores rotativos incrementais sem chave de pressão acoplada para uso em controles de instrumentos e aparelhos ao invés de potenciômetros
Figura 19: Codificador rotativo incremental sem chave
Fotografia de um Codificador Rotativo Incremental com eixo translúcido permitindo iluminação por LED.
Figura 20: Codificador Rotativo Incremental de eixo iluminado por LED

Os três próximos são módulos com codificadores rotativos incrementais, o primeiro deles com iluminação do eixo por LED RGB e interface serial do tipo I²C, o segundo e terceiro, mais comumente encontrados, são codificadores rotativos incrementais com chave push-button acoplada e sinais discretos, tanto de quadratura, quanto do contato da chave, conhecidos, respectivamente, como HW-040 e KY-040.

Fotografia de um módulo codificador rotativo incremental de eixo com iluminação por LED RGB e sinalização por comunicação I²C
Figura 21: Codificador rotativo incremental iluminado com LED RGB e comunicação I²C

Fotografia de um popular módulo HW-040, codificador rotativo incremental com botão de pressão acoplado ao eixo
Figura 22: Codificador rotativo incremental HW-040
Fotografia de um popular módulo KY-040, codificador rotativo incremental com botão de pressão acoplado ao eixo
Figura 23: Codificador rotativo incremental KY-040

Utilização de codificadores rotativos no universo da robótica educacional e maker

Os codificadores rotativos incrementais são os mais populares no universo da robótica educacional devido a seu reduzido custo de aquisição, podemos encontrar codificadores sendo utilizados para medir velocidade rotacional de motores, ou mesmo de veículos e em medições lineares de distância.

Módulos KY-040 e HW-040

A oferta de mercado para este item é bastante generosa, no entanto há a distribuição de dois modelos diferentes, indiscriminadamente, identificados no comércio como KY-040, o próprio KY-040 e o HW-040. Seu uso é muito prático por vir montado em placa de circuito impresso (PCI), com terminais para conexão, seja em placas de experimentação (protoboard), seja em receptáculos fêmea de cabos jumper e serão estes os componentes abordados aqui.

Fotografia perspectiva e vista inferior (mostrando a montagem dos resistores de PULL-UP) do módulo HW-040 ou KY-040 (suas construções são quase idênticas)
Figura 24: Aspectos do módulo HW-040 e KY-040

Características físicas

Em ambos a placa de circuito impresso do módulo mede 26,2mm x 18,6mm, contando com dois furos de fixação de 2,2mm de diâmetro, distantes 13,8mm um do outro e a 4,6mm da borda da PCI (de 1,1mm de espessura) contrária ao conector de 5 pinos com passo de 2,54mm no módulo HW-040, e distantes 16,8mm um do outro e a 4,0mm da borda da PCI (de 1,6 mm de espessura)contrária ao conector de 5 pinos com passo de 2,54mm no módulo KY-040, finalmente a altura do eixo é de aproximadamente 26,7mm da face dos componentes até seu topo. Estas dimensões são ilustradas a seguir, ressaltando que o desenho não está em escala. Vale ressaltar que ao ser pressionado o eixo que acopla o botão push-button, recua 0,5mm.

Desenho da planta e elevação dos módulo HW-040 (porção superior da imagem) e KY-040 (porção inferior da imagem)
Figura 25: Dimensões mecânica dos módulos KW-040 e KY-040

A seguir está o diagrama esquemático simplificado dos módulos XX-040 e ao lado o circuito sugerido para testar o codificador rotativo. Apesar de existir na placa de circuito a previsão de um resistor de pull-up para a chave de contato momentâneo acoplada ao eixo do codificador, este não é montado em nenhum dos módulos.

Em seguida apresenta-se a pinagem dos módulos.

Ilustração da pinagem, ou seja designação dos pinos, de qualquer dos módulos HW-040 ou KY-040, já que são idênticas
Figura 26: Designação dos pinos do módulo XX-040

Perfeitamente adaptados ao uso com Arduino ou Raspberry Pi, tem larga aplicação na criação de interfaces de usuário mais até do que controle de grandezas físicas como intensidade luminosa ou sonora.

Deve-se observar que apesar de serem mecanicamente muito semelhantes, o HW-040 tem por característica gerar 30 pulso para uma revolução completa do eixo e o KY-040 20 pulsos, além de a cadência dos sinais guardar diferenças significativas, uma vez que o HW-040 sendo utilizado no lugar do KY-040 necessita de dois “cliques” para gerar a mesma cadência que o KY-040 gera com um único clique.

Particularidades do módulo

Sinalização e realimentação táctil

O modelo HW-040 para um giro completo gera 30 pulsos tanto no terminal CLK quanto no DT, o que coincide com os “cliques” sentidos durante a rotação do eixo. Tomando os terminais CLK e DT, observa-se a sequência tabelada a seguir:

Posição do eixoSentido HorárioSentido Anti-horárioRealimentação táctil
CLKDTValorCLKDTValor
1113113click
102011
2000000click
011102
3113113click
102011
........................
..................
30000000click
102011

Tabela 1: Sinalização do codificador rotativo HW-040

Caracterizando para giro na direção horária a sequência:    [3] 2 [0] 1 [3] …

E para o giro na direção anti-horária a sequência:                  [3] 1 [0] 2 [3] …

O modelo KY-040 para um giro completo gera 40 pulsos tanto no terminal CLK quanto no DT, o que é o dobro de “cliques” sentidos durante a rotação do eixo. Tomando os terminais CLK e DT, observa-se a sequência tabelada a seguir:

Posição do eixoSentido HorárioSentido Anti-horárioRealimentação táctil
CLKDTValorCLKDTValor
1113113click
102011
000000
011102
2113113click
102011
000000
011102
3113113click
..................
..................
..................
..................
20113113click
102011
000000
011102

Tabela 2: Sinalização do codificador rotativo KY-040

Caracterizando para giro na direção horária a sequência:    [3] 2 0 1 [3] …

E para o giro na direção anti-horária a sequência:                  [3] 1 0 2 [3] …

Obs.: os números entre colchetes são a indicação de uma realimentação táctil, ou clique (click).

Qualidade da sinalização

Devido às limitações de custo estes codificadores empregam chaves de contato mecânico para gerar os pulso e não circuito ópticos, sendo assim está sujeito ao efeito rebote (bounce) em seus contatos, tanto do próprio codificador, quanto da chave pulsante acoplada axialmente a seu eixo.

Pode ser desejável adotar alguma técnica de minimização do efeito rebote. Adiante algumas delas serão abordadas.

Diagrama elétrico do módulo

O diagrama esquemático completo equivalente dos HW-040 e  KY-040 é mostrado a seguir:

Desenho do esquema elétrico dos módulos HW040 ou KY-040, observar que R1 não é montado em qualquer deles
Figura 27: Diagrama esquemático completo dos módulos HW-040 e KY-040

Para se familiarizar com o codificador rotativo qualquer dos dois circuitos de teste abaixo podem ser usados, o circuito 1 acende os LEDs em lógica negativa (LED aceso sinal correspondente em nível 0), já o circuito 2 acende os LEDs (com baixa intensidade) em lógica positiva (sinal correspondente em nível 1).

Diagrama de ligações para duas modalidades de teste do módulo. Mais à esqueda os LEDs acendem quando o sinal correspondente é baixo (0 lógico), e à direita os LEDs acendem (fraco) quando o sinal correspondente é alto (1 lógico).
Figura 28: Diagrama de circuitos de teste

Falando sobre Bounce

O efeito rebote, ou efeito repique e em inglês bounce efect, é característico dos contatos mecânicos aplicados a circuitos eletroeletrônicos, e é uma série de ruídos aleatórios, um fenômeno chamado transitórios, com duração relativamente pequena, da ordem de milissegundos. No entanto no âmbito da eletrônica esses milissegundos podem ser comparáveis a, digamos, algumas horas em termos humanos.

Sua origem reside no fato de que no contato não se estabelece uma circulação de corrente elétrica imediata e homogenia. Os contatos mecânicos além de sua imperfeição superficial, oscilam quando se chocam e o resultado é refletivo no sinal elétrico.

A seguir é mostrado o sinal elétrico e sua interpretação lógica efetuada por circuitos digitais.

Na porção superior imagem da forma de onda obtida em osciloscópio do comportamento do sinal elétrico de uma chave de contatos mecânicos. Porção inferior, mesmo sinal passado na saída de uma porta lógica do tipo shmitt-trigger.
Figura 29: Aspecto do ruído de contato de chave analógico real e digitalizado

Como visto acima um sinal “aleatório” pode levar à ocorrência de um trem de pulsos na interpretação de circuitos digitais, a ilustração a seguir mostra o possível e provável  comportamento resultante da leitura de uma chave de contato ideal comparado ao comportamento da leitura de uma chave real. Devido ao rebote (ou repique), ao invés de uma única transição ao pressionar a chave e outra ao soltá-la, obtemos um trem de pulsos e totalmente irregulares, tanto no fechamento quanto na abertura do contato.

Ilustração comparativa entre o sinal digital obtido de uma chave de contatos ideal (acima) e real (abaixo).
Figura 30: Sinal digital de uma comutação de chave mecânica – fechamento e abertura

Este efeito indesejável pode receber uma série de tratamentos para minimizá-lo. Newton C. Braga tem um excelente artigo publicado no web site do Instituto NCB que aborda diversas dessas possibilidades, chamado Problemas de Repique – Como Resolver, sua leitura é altamente recomendável para uma melhor compreensão do problema (URL do artigo nas referências).

Tratamento de Debounce

Minimizando ou eliminando o rebote por hardware

Para minimizar por hardware o efeito rebote, se só se dispõe de contato simples na chave a alternativa é aplicar um filtro passa-baixa adaptado para operar de forma assimétrica em relação à transição negativa e positiva do sinal.

Sabe-se que quando o contato é estabelecido os terminais da chave representam uma baixa impedância, e quando em aberto representam alta impedância. O filtro conforme mostrado na ilustração a seguir pode ser adaptado de forma a carregar lentamente o capacitor quando a chave apresentar alta impedância e descarregar rapidamente o capacitor pela chave quando esta apresentar baixa impedância.

Os tempos de carga e descarga podem ter uma razão da ordem de 10 a 100 vezes ou mais. A carga se dá via R1 e R2 que estão associados em série, já a descarga ocorre apenas através de R2, assim a razão entre os valores dos resistores determina a razão dos tempos de carga e descarga. Valores práticos normalmente utilizados são 10 kohm para R1 e 100 ohm para R2, fazendo então uma ração de 101:1 entre o tempo de carga e de descarga.

O debounce será efetivo para a chave em alta impedância, portanto o valor para C1 é calculado para sua carga pela equação C = tc / R (decorrente de tc = R * C), onde o tempo é dado em segundos, a resistência em Ohms e a capacitância em Farads. Arbitrando um tempo de debounce de 10ms temos um capacitor de 0,01 / 10000 portanto 0,000001F ou seja 1 micro Farad.

Ilustração com três elementos. Em cima à esquerda diagrama elétrico de uma chave com um contato à terra e o outro contecado a dois resistores, R1 e R2. R1 ligado à alimentação e R2 sendo a saída do circuito que conta com um capacitor entre sí e o terra. À direita equações para cálculo de tempo de carga e descarga do capacitor, em função de R1 e R2. Na porção inferior forma de onda da tenão na saída em função do tempo
Figura 31: Bases da técnica de debounce por hardware
Obs.: O cálculo de filtro passa baixa passivos padrão (aqui é 
empregado um adaptado para atuar mais eficientemente para 
filtragem antirrepique) seu é efetuado segundo a fórmula para 
cálculo da frequência de corte que é Fc = 1/ (2 * pi * R * C), 
onde a frequência é expressa em Hz, a resistência em Ohms e a 
capacitância em Farads.
Ilustração do sinal digital resultante da aplicação do circuito incluí na mesma ilustração, mostrando que se não elimina minimiza o efeito repique da chave
Figura 32: Sinal digital resultante do debounce no domínio do tempo

Com esta melhoria pode não ser possível garantir a eliminação do efeito rebote, mas ele será ser efetivamente diminuído, restando lançar mão de estratégias de software para completar sua eliminação caso seja necessário.

Por outro lado, caso a chave seja de contato reversível uma solução mais robusta pode ser adotada empregando duas portas NAND ligadas em configuração de flip-flop R-S, como ilustrado a seguir, as saídas NH e NL são respectivamente Normalmente Alto (Normally High) e Normalmente Baixo (Normally Low), similarmente a contatos NA (ou NO) e NC (ou NF).

Circuito elétrico para eliminação de bounce quando a chave tem dois contatos, um normalmente aberto e outro normalmente fechado, aplicado-se um flip-flop do tipo RS, resuktando uma saída normalmente alta (1 lógico) e outra normalmente baixa (0 lógico)
Figura 33: Circuito de debounce por flip-flop R-S

Aqui a vantagem é a eliminação total do efeito rebote. Mas há a desvantagem da elevação do custo de componentes do circuito.

Tratamento do rebote por software

A primeira e mais simples técnica de tratamento é a de permanecer num loop temporizado. Para entrar no loop de espera do temporizador deve-se aguardar um mudança de estado na entrada de leitura do contato e após o término da temporização efetuar nova leitura que será o efetivo estado do contato a ser assumido. Nestas condições a temporização pode ser arbitrariamente assumida com base na “qualidade” da chave utilizada, em geral tempos entre 5ms a 20ms são suficientes.

No entanto esta técnica tem o custo de “segurar” a execução do programa como um todo.

A segunda técnica de tratamento consiste em, com base em uma interrupção de intervalo cíclico, contar a quantidade de vezes do contato em dado estado, até seja atingido o valor parametrizado como tempo de debounce para o contato, sendo que sempre que a leitura indicar retorno ao estado anterior reiniciar o contador.

A terceira técnica de tratamento pressupõe a utilização da mudança de estado do sinal do contato como gerador de interrupção, que mede o tempo de duração do estado anterior e somente quando expirar o tempo parametrizado como tempo de debounce (supostamente outra interrupção) admitir como estável o estado do contato.

Esta relação de técnicas não pretende ser exaustiva, mas de toda forma, como visto, todas tem seu preço, seja em desempenho seja em consumo de recursos e/ou complexidade de implementação.

Uso do codificador rotativo com Arduino

Circuito utilizado para este estudo

O diagrama esquemático mostrado será utilizado como circuito padrão para o desenvolvimento das rotinas de manipulação de codificadores rotativos digitais.

Representação esquemática das conexões entre um Arduino UNO R3 e o módulo de codificador rotativo (seja HW-040 ou KY-040), incluíndo resistores e capacitores para eminizar, ou mesmo eliminar, o efeito de repique
Figura 34: Diagrama Esquemático do circuito para estudo

As conexões foram selecionadas com base nos pinos que podem gerar interrupção externa, que são os pinos D2 e D3 do Arduino UNO. Assim DT do módulo é ligado ao pino D2 e CLK do módulo ao pino D3, finalmente o SW do módulo pode ser ligado a qualquer outra entrada que suporte pull-up em sua configuração. Com este arranjo pode ser atingido o máximo em possibilidades de software como será visto.

Visando a minimização do efeito rebote adota-se a aplicação de um filtro passa baixa adaptado, que garanta a minimização efetiva do efeito, no entanto o efeito é tão maior quanto menor a qualidade da chave, podem ir de uns poucos milissegundos até mais de uma centena de milissegundos.

Muito embora um filtro passa-baixa de primeira ordem seja calculado com base na fórmula Fc = 1 / (2 * pi * R * C), aqui a intenção não é filtrar sinais de forma simétrica, senão atuar sobre a flutuação do contato em aberto, o que muda quando se faz uso de sinal normalmente alto ou normalmente baixo. Como será usado o resistor pull-up do módulo, o capacitor será conectado à referência comum, portanto, ao 0V. Já se fosse usado um resistor de pull-down o capacitor seria conectado ao positivo da alimentação do circuito, portanto, ao +Vcc.

Os cálculos envolvidos na determinação do valor do capacitor e do resistor adicional do filtro passa-baixa adaptado foi abordado na primeira parte do segmento “Minimizando ou eliminando o rebote por hardware” anteriormente, empregando-se no circuito os valores ali determinados para os resistores adicionais e os capacitores (100 ohm e 1 micro Farad).

Software para leitura do codificador rotativo digital

Para manter algum didatismo na abordagem os sketchs serão escritos sem otimização, se valendo assim dos recursos “puros” da linguagem wiring para Arduino, assim como será ignorada a chave push-button acoplada ao eixo do codificador, que para ser lida pode receber o tratamento normal de chaves de contato.

Inicialmente é utilizada uma técnica básica chamada polling (varredura), na qual por software, conforme a disponibilidade, é feita a leitura do estado da chaves. Observando que neste ponto não é incluída qualquer tentativa de debouncing por software.

No loop principal do sketch é chamada a função (rotina) de leitura do codificador, esta abordagem implica em colocar tantas chamadas à função quantas forem necessárias para garantir que não se percam transições de qualquer dos sinais (DT e CLK).

Como o tempo de execução do loop principal pode variar consideravelmente em função das situações em que se encontre o “equipamento”, esta certamente é a abordagem menos aconselhável, tento de ser utilizada como último recurso.

Rotina 1 – Detecção de movimento horário ou anti-horário no codificador

Primeiramente estabelece-se um algoritmo de detecção de movimento do eixo do codificador, seja no sentido que for, basta conseguir perceber que ocorreu algum movimento.

A única chamada efetuada no loop é para a função de leitura do codificador por polling.

A função de leitura do codificador, cria dinamicamente as variáveis sglCK, sglDT e flagDSP, respectivamente: sinal CLK, sinal DT e semáforo de solicitação de exibição (display) de dados. São criadas na forma estática duas variáveis oldCK e oldDT, para guardarem o estado lido na varredura anterior à corrente.

Estas variáveis estáticas têm por função permitir a comparação do estado lido de cada chave com o anterior para sinalizar a mudança de estado das respectivas chaves. Sempre que uma mudança é detectada o novo estado é gravado na variável correspondente e o semáforo de exibição é ajustado para o valor lógico TRUE, indicando a necessidade de nova exibição dos dados.

Ao final da função o valor de ambos os sinais é enviado pelo monitor serial e o semáforo é resetado, aguardando nova detecção de alteração de estado. Com isto só há transmissão pelo monitor serial quando da ocorrência de um efetivo evento.

Segue o código da rotina 1:

/* Rotina 1 - Detecção de movimento horário ou anti-horário
 *  Data: 20210127
 *  Autor: Chico Lopes
 *  Obs.: No monitor serial a cada linha é mostrado o estado dos dois sinais
 *  apenas quando ocorre mudança de qualquer deles
 */

#define pinCK 2         // Entrada digital para leitura do sinal CLK
#define pinDT 3         // Entrada digital para leitura do sinal DT
#define tipoEnc 2       //  HW-040 = 1; KY-040 = 2
#define DEBUG 1        // Indica se imprime mais (1) ou menos (0) no monitor serial

void setup() {
  Serial.begin(9600);  
}

void loop() {

  polling();      // Efetua uma varredura dos estados de DT e CLK

}

void polling(void) {
  static byte oldCK, oldDT;   // Anotação do estado das chaves na varredura
                              // anterior
  byte sglCK, sglDT;
  bool flagDSP = 0;

  sglCK = digitalRead(pinCK);   // Lê o estado de CLK
  if (sglCK != oldCK) {         // Verifica se houve mudança de estado?
    oldCK = sglCK;              // Mudou, anota novo estado
    flagDSP = true;             // Sinaliza que deve mostrar novo estado
  }
  sglDT = digitalRead(pinDT);   // Lê o estado de DT
  if (sglDT != oldDT) {         // Verifica se houve mudança de estado?
    oldDT = sglDT;              // Mudou, anota novo estado
    flagDSP = true;             // Sinaliza que deve mostrar novo estado
  }
  
  // Exibe estado de CLK e DT atuais
  if ((DEBUG == 1) && (flagDSP)) {
    Serial.print(" CK: "); Serial.print(sglCK);
    Serial.print(" DT: "); Serial.print(sglDT);
    Serial.println();
    flagDSP = false;            // Sinaliza que já mostrou o estado
  }
}
//FIM

Leituras obtidas no monitor serial para a utilização do codificador rotativo HW-040. As leituras marcadas com “click” indicam os pontos de realimentação táctil do eixo do codificador:

  CK: 1 DT: 1      <= Energização do Arduino
  CK: 1 DT: 1      <= após pressionar o botão de reset
 =>  Início da rotação no sentido horário
  CK: 1 DT: 0
  CK: 0 DT: 0
  CK: 0 DT: 1
  CK: 1 DT: 1      <= click
  CK: 1 DT: 0
  CK: 0 DT: 0
  CK: 0 DT: 1
  CK: 1 DT: 1      <= click
 =>  Início da rotação no sentido anti-horário
  CK: 0 DT: 1
  CK: 0 DT: 0
  CK: 1 DT: 0
  CK: 1 DT: 1      <= click
  CK: 0 DT: 1
  CK: 0 DT: 0
  CK: 1 DT: 0
  CK: 1 DT: 1      <= click 

Leituras obtidas no monitor serial para a utilização do codificador rotativo KY-040. As leituras marcadas com “click” indicam os pontos de realimentação táctil do eixo do codificador:

 CK: 1 DT: 1    <= Energização do Arduino
 CK: 1 DT: 1    <= após pressionar o botão de reset
=>    Início da rotação no sentido horário
  CK: 1 DT: 0
  CK: 0 DT: 0    <= click
  CK: 0 DT: 1
  CK: 1 DT: 1    <= click
  CK: 1 DT: 0
  CK: 0 DT: 0    <= click
 =>    Início da rotação no sentido anti-horário
  CK: 0 DT: 1
  CK: 0 DT: 0    <= click
  CK: 1 DT: 0
  CK: 1 DT: 1    <= click
  CK: 0 DT: 1
  CK: 0 DT: 0    <= click
  CK: 1 DT: 0
  CK: 1 DT: 1    <= click

Desta forma fica claro que a construção dos dois modelos de codificadores é diferente e portanto utilizando-se o HW-040 ou o KY-040 haverá o dobro de transições dos sinais para um só passo de giro do eixo.

Nota: Este sketch ocupa 2008 bytes da memória de programa e 202 bytes da memória dinâmica para um Arduino UNO R3.

Observação: A variável Debug controla se o sketch deve transmitir ou não eventos pelo monitor serial e quais deles. Esta técnica será utilizada em todos os exemplos de programas neste estudo.

Rotina 2 – Detecção de sentido de rotação horário ou anti-horário

Nesta abordagem utiliza-se a leitura dos dois sinais (CLK e DT) para montar um índice de acesso a um vetor bidimensional, onde o outro índice é a leitura da varredura anterior dos mesmos sinais. A montagem do índice é feita pela multiplicação do valor lido de CLK por 2 somando ao valor lido de DT, o que resulta em valores no intervalo de 0 a 3 (4 valores portanto).

A única chamada efetuada no loop é para a função de leitura do codificador por polling com detecção de sentido de rotação, seja horária ou anti-horária. As variáveis thisPolling e oldPolling são utilizadas para determinar quando há alteração do valor resultante da varredura para efeito de transmissão pelo monitor serial.

A função de leitura do codificador, cria dinamicamente as variáveis thisEncoder e thisVector. Como variáveis estáticas são criadas as “matrizes” (arrays) de detecção de movimento, uma para cada modelo de codificador rotativo (vetorHW e vetorKY) e thisEncoder para armazenar a leitura da varredura para uso na próxima varredura.

A varredura anterior, endereça as colunas da matriz e a leitura atual suas linhas, desta intersecção obtém-se a situação do eixo de rotação em três diferentes possibilidades: estável ou parado (0), girando em sentido horário (1), e girando em sentido anti-horário (-1).

Como as respostas dos codificadores são diferentes, cada um tem sua própria tabela. A decisão de qual usar é tomada com base em uma constante definida no início do código (tipoEnc).

Uma vez consultada a matriz devida o valor de oldEncoder já pode ser atualizado em preparo à próxima varredura com o valor de thisEncoder. A consulta a matriz é capturada em thisVector que será usada como parâmetro de retorno da função.

Segue o código utilizado na rotina 2:

/* Rotina 2 - Detecção de sentido de rotação horário ou anti-horário
  *  Data: 20210127
  *  Autor: Chico Lopes
  *  Obs.: No monitor serial é mostrado o estado do eixo
  *        quando ocorre mudança em sua situação
  */
  
 #define pinCK 2    // Entrada digital para leitura do sinal CLK
 #define pinDT 3    // Entrada digital para leitura do sinal DT
 #define tipoEnc 2  // HW-040 = 1; KY-040 = 2
 #define DEBUG 1    // Imprime (1) ou não (<>1) no monitor serial
  
byte Debug = DEBUG; // indica nível de debug a ser usado
                    // na execução
  
 void setup() {
   Serial.begin(9600);
 }
  
 void loop() {
   int thisPolling, oldPolling;
   
   thisPolling = polling();  // Efetua uma varredura
                             // de movimentação
   
   // Verifica se o valor varrido teve alteração?
   if ( (Debug == 1) && (oldPolling != thisPolling) ) {
     // Ocorreu alteração de valor
     oldPolling = thisPolling;
     Serial.print(thisPolling);
     Serial.println();
   }
 }
  
 int polling(void) {
   /*
    * Retorna um valor correspondente ao movimento do eixo
    * do codificador, assim:
    *   0  Parado
    *   1  Movimento no sentido horário
    *  -1  Movimento no sentido anti-horário
    * 
    *  Para detectar o sentido de rotação este algoritmo usa
    *  a leitura atual para endereçar linhas de um ARRAY e a
    *  leitura anterior para endereçar colunas.
    *  O valor lido é 1 (um) para rotação no sentido horário e
    *  -1 (menos um) no sentido anti-horário.
    *  Para determinar o endereço das linhas e colunas,
    *  basta multiplicar o valor  lido de CLK por 2 e somá-lo
    *  ao lido de DT.
    */
   // Array para o codificador rotativo HW-040
   // um pulso a cada clique, sendo um ciclo de quadratura
   static int vetorHW[][4] = {
     {  0,  1, -1,  0},
     {  0,  0,  0,  0},
     {  0,  0,  0,  0},
     {  0, -1,  1,  0},
   };
   // Array para o codificador rotativo KY-040
   // um pulso a cada clique, sendo dois ciclos de quadratura
   static int vetorKY[][4] = {
     {  0,  0,  0,  0},
     {  0,  0,  0,  0},
     {  0,  0,  0,  0},
     {  0, -1,  1,  0},
   };
   static int oldEncoder;   // Anotação do estado da
                            // varredura anterior
   int thisEncoder, thisVector;
  
   thisEncoder = (digitalRead(pinCK) << 1) + digitalRead(pinDT);
  
   // Identifica o codificador em uso 1 = HW-040 / 2 = KY-040
   if ( tipoEnc == 1 ) {
     thisVector = vetorHW[thisEncoder][oldEncoder];
   } else {
     thisVector = vetorKY[thisEncoder][oldEncoder];
   }
  
   // Leitura atual passa a ser varredura anterior
   oldEncoder = thisEncoder;
   return thisVector;     // Retorna com o indicador de movimento
 }
 //FIM 

Nota: Este sketch ocupa 2030 bytes da memória de programa e 222 bytes da memória dinâmica para um Arduino UNO R3.

Leituras obtidas no monitor serial independem do modelo de codificador rotativo utilizado. Todas as leituras são coincidentes com os pontos de realimentação táctil do eixo do codificador:

  1    <= 1° click no sentido horário
  1    <= 2° click no sentido horário
  1    <= 3° click no sentido horário
 -1    <= 1° click no sentido anti-horário
 -1    <= 2° click no sentido anti-horário
 -1    <= 3° click no sentido anti-horário

Rotina 3 – Detecção de sentido de rotação com máquina de estados

Nesta abordagem utiliza-se a leitura dos dois sinais (CLK e DT) para determinar o estado de uma máquina de estados. As diversas possíveis transições dos estados da máquina determinam quando houve uma movimentação e em qual sentido.

Cada codificador tem sua sequência de estados definida da seguinte forma. Para o HW-040, a sequência é: [3] 2 [0] 1, e para o KY-040 a sequência é [3] 2 0 1. Observa-se que a sequência de estados é a mesma, no entanto os estados marcados entre colchetes determinam a diferença entre os codificadores.

Nos estado marcados com colchetes ocorre a “emissão” da informação de movimento e em qual sentido, sendo 1 para o sentido horário e -1 para o sentido anti-horário.

A única chamada efetuada no loop é para a função de leitura do codificador por polling com detecção de sentido de rotação, seja horária ou anti-horária. As variáveis thisPolling e oldPolling são utilizadas para determinar quando há alteração do valor resultante da varredura para efeito de transmissão pelo monitor serial.

A função de leitura do codificador, cria dinamicamente as variáveis thisEstado e rdEncoder. Com variáveis estáticas é criada a variável lastEstado.

A varredura atual é o estado corrente da máquina em cujos estado pertinentes é avaliado o seu estado anterior. Como as respostas dos codificadores são diferentes, é confrontado o tipo de codificador em uso permitindo ou não a geração de saída nos estados devidos. A decisão é tomada com base em uma constante definida no início do código (tipoEnc).

Segue o código utilizado na rotina 3:

/* Rotina 3 - Detecção de sentido de rotação com máquina de estados
 * Data: 20210127
 * Autor: Chico
 * Obs.: No monitor será mostrado o sentido do movimento do eixo
 *        1 - sentido horário
 *       -1 - sentido anti-horário
 * Aqui o algoritmo é o da implementação de uma máquina de estados
 * para o HW-040 teremos a sequência de estados [DT:CK] [3] 2 [0] 1
 * a transição do estado 3 para o 2 caracteriza sentido horário
 * a transição do estado 3 para o 1 caracteriza sentido anti-horário
 * a transição do estado 0 para o 2 caracteriza sentido anti-horário
 * a transição do estado 0 para o 1 caracteriza sentido horário
 * para o KY-040 teremos a sequência de estados [DT:CK] [3] 2 0 1
 * a transição do estado 3 para o 2 caracteriza sentido horário
 * a transição do estado 3 para o 1 caracteriza sentido anti-horário
 */

#define pinCLK  2     // Entrada digital para leitura do sinal CLK
#define pinDT   3     // Entrada digital para leitura do sinal DT
#define tipoEnc 2     // HW-040 = 1; KY-040 = 2
#define DEBUG 1       // Imprime (1) ou não (<>1) no monitor serial

void setup () {
  Serial.begin (9600);                // Inicializa o monitor serial
}

void loop() {
  int thisPolling, oldPolling;
  
  thisPolling = polling();
  
  if ( (DEBUG == 1) && (oldPolling != thisPolling) ) {
    oldPolling = thisPolling;
    Serial.println(thisPolling);
  }
}

int polling( void ) {
  static int lastEstado;
         int thisEstado, rdEncoder;

  rdEncoder = 0;
  thisEstado = (digitalRead(pinCLK) << 1) + digitalRead(pinDT);
  switch (thisEstado) {
    case 3:                   // Estado estável para HW-040 e KY-040
      switch (lastEstado) {
        case 1:
          rdEncoder = -1;
        break;
        case 2:
          rdEncoder = 1;
        break;
      }
    break;
    case 0:                   // Estado estável para HW-040
      if (tipoEnc == 1) {
        switch (lastEstado) {
          case 1:
            rdEncoder = 1;
          break;
          case 2:
            rdEncoder = -1;
          break;
        }
      }
    break;
  }
  lastEstado = thisEstado;
  return rdEncoder;
}
//FIM

Nota: Este sketch ocupa 2008 bytes da memória de programa e 202 bytes da memória dinâmica para um Arduino UNO R3.

Leituras obtidas no monitor serial independem do modelo de codificador rotativo utilizado. Todas as leituras são coincidentes com os pontos de realimentação táctil do eixo do codificador:

  1    <= 1° click no sentido horário
  1    <= 2° click no sentido horário
  1    <= 3° click no sentido horário
 -1    <= 1° click no sentido anti-horário
 -1    <= 2° click no sentido anti-horário
 -1    <= 3° click no sentido anti-horário

Rotina 4 – Contagem de movimentações do eixo do codificador, conforme o sentido de rotação, por varredura

Aqui pode ser empregada qualquer das estratégias anteriores (matriz ou máquina de estados), pois ambas retornam o valor entre -1 e 1 indicando o módulo de movimentação do eixo (-1 sentido anti-horário, 0 sem movimentação, ou 1 movimento no sentido horário). Assim no loop principal é efetuada a chamada para a função de varredura e o valor dela retornado é somado ao acumulador de movimento, resultando desta forma na quantificação do deslocamento do eixo.

Segue o código utilizado na rotina 4:

/* Rotina 4 - Contagem de movimentações do eixo do codificador
 * por polling, respeitando o sentido de rotação para incrementar
 * (sentido horário) ou decrementar (sentido anti-horário).
 * Data: 20210127
 * Autor: Chico
 * Obs.: No monitor será mostrado uma contagem de movimentações
 *       crescente para o sentido horário e decrescente para o
 *       sentido anti-horário
 * Esta rotina utiliza a técnica da máquina de estados na detecção da
 * movimentação e seu sentido.
 */

#define pinCLK  2     // Entrada digital para leitura do sinal CLK
#define pinDT   3     // Entrada digital para leitura do sinal DT
#define tipoEnc 2     // HW-040 = 1; KY-040 = 2
#define DEBUG 1       // Imprime (1) ou não (<>1) no monitor serial

void setup () {
  Serial.begin (9600);                // Inicializa o monitor serial
}

void loop() {
  static int lastCounter = 0;
  int thisCounter;
  
  thisCounter = lastCounter + polling();
  
  if ( (DEBUG == 1) && (lastCounter != thisCounter) ) {
    lastCounter = thisCounter;
    Serial.println(thisCounter);
  }
}

int polling( void ) {
  static int lastEstado;
         int thisEstado, rdEncoder;

  rdEncoder = 0;
  thisEstado = (digitalRead(pinCLK) << 1) + digitalRead(pinDT);
  switch (thisEstado) {
    case 3:                   // Estado estável para HW-040 e KY-040
      switch (lastEstado) {
        case 1:
          rdEncoder = -1;
        break;
        case 2:
          rdEncoder = 1;
        break;
      }
    break;
    case 0:                   // Estado estável para HW-040
      if (tipoEnc == 1) {
        switch (lastEstado) {
          case 1:
            rdEncoder = 1;
          break;
          case 2:
            rdEncoder = -1;
          break;
        }
      }
    break;
  }
  lastEstado = thisEstado;
  return rdEncoder;
}
//FIM

Nota: Este sketch ocupa 2008 bytes da memória de programa e 202 bytes da memória dinâmica para um Arduino UNO R3.

Leituras obtidas no monitor serial independem do modelo de codificador rotativo utilizado. Todas as leituras são coincidentes com os pontos de realimentação táctil do eixo do codificador:

 1    <= 1° click no sentido horário
 2    <= 2° click no sentido horário
 3    <= 3° click no sentido horário
 …
 n-1    <= n°-1 click no sentido horário
 n    <= n° click no sentido horário
 n-1    <= 1° click no sentido anti-horário
 n-2    <= 2° click no sentido anti-horário
 n-3    <= 3° click no sentido anti-horário
 …
 2    <= n°-1 click no sentido anti-horário
 1    <= n° click no sentido anti-horário

Esta experimentação foi obtida acoplando-se o eixo de um motor com rotação de 120RPM ao eixo do codificado rotativo KY-040. Assim estabeleceu-se uma regularidade dos sinais CLK e DT gerados, com temporização conforme ilustração a seguir.

Carta de tempos para o codificador rotativo KY-040 submetido a 120 RPM de velocidade de giro constante. Os números 3, 2, 0, e 1 indicam a sequência de código obtida com os sinais DT e CLK. Os algarismos destacados, são os que ocorre a realimentação táctil do eixo (cliques)
Figura 35: Temporização do sinal de quadratura do KY-040

Até este ponto não ocorreram perdas na detecção dos sinais, no entanto para rotações maiores do eixo, não há garantias de que o método de varredura por software não perca a detecção de algum dos eventos, assim a varredura baseada em interrupção será mais segura se o codificador ficar exposto a “usos mais extremos”.

Rotina 5 – Detecção de movimentações do eixo do codificador por interrupção

Aqui a estratégia com hardware mínimo limita à utilização dos pinos digitais 2 e 3 para a conexão dos sinais DT e CLK do codificador rotativo, devido serem, no Arduino UNO os únicos com capacidade de geração de interrupção externa.

Pela utilização de uma porta ou exclusivo (Exclusive-OR) com os
dois sinais em suas entradas e a saída no pino D2 ou D3 pode 
levar à economia do recurso de interrupção externa ao custo de 
necessitar de outros dois pinos de entrada para a leitura de DT e 
CLK. Esse arranjo é mostrado no diagrama a seguir, juntamente com 
sua carta de tempos.
Porta XOR para gerar interrupção a partir dos sinais do codificador rotativo com diagrama de tempo.
Ilustra alternativa para uso de uma única interrupção para codificador rotativo.

A rotina de serviço de interrupção pode utilizar qualquer das duas técnica de varredura abordadas.

Segue código utilizado na rotina5:

/* Rotina 5 - Contagem de movimentações do eixo do codificador por
   interrupção externa
   Data: 20210201
   Autor: Chico
   Obs.: No monitor será mostrado uma contagem de movimentações
         crescente para o sentido horário e decrescente para o
         sentido anti-horário
   Esta rotina utiliza a técnica da máquina de estados
*/

#define pinCLK  2     // Entrada digital para leitura do sinal CLK
#define pinDT   3     // Entrada digital para leitura do sinal DT
#define tipoEnc 2     // HW-040 = 1; KY-040 = 2
#define DEBUG 1       // Imprime (1) ou não (<>1) no monitor serial

volatile int rdEncoder = 0;   // Indicador de movimento do eixo
                              // do encoder
         int lastCounter = 0;

void setup () {
  Serial.begin (9600);                // Inicializa o monitor serial

  // Configura a rotina de atendimento de interrupção
  attachInterrupt(digitalPinToInterrupt(pinDT),  polling_isr, CHANGE);    // Atende interrupção do sinal DT
  attachInterrupt(digitalPinToInterrupt(pinCLK), polling_isr, CHANGE);    // Atende interrupção do sinal CLK

  Serial.print("Pronto! Gire o codificador rotativo.");
  Serial.println();
  Serial.println();
}

void loop() {
  int thisCounter;

  if (rdEncoder != 0) {
    thisCounter = (lastCounter + rdEncoder);
    (DEBUG == 1) ? Serial.println(thisCounter) : rdEncoder=0;
    lastCounter = thisCounter;
    rdEncoder = 0;
  }
}

void polling_isr( ) {
  static int lastEstado;
  int thisEstado;

  thisEstado = (digitalRead(pinCLK) << 1) + digitalRead(pinDT);
  switch (thisEstado) {
    case 3:                   // Estado estável para HW-040 e KY-040
      switch (lastEstado) {
        case 1:
          rdEncoder = -1;
          break;
        case 2:
          rdEncoder = 1;
          break;
      }
      break;
    case 0:                   // Estado estável para HW-040
      if (tipoEnc == 1) {
        switch (lastEstado) {
          case 1:
            rdEncoder = 1;
            break;
          case 2:
            rdEncoder = -1;
            break;
        }
      }
      break;
  }
  lastEstado = thisEstado;
}
//FIM

Nota: Este sketch ocupa 2340 bytes da memória de programa e 234 bytes da memória dinâmica para um Arduino UNO R3.

Optou-se aqui pela utilização da detecção de movimento por máquina de estados.

É importante observar que uma interrupção não tem retorno de qualquer valor, logo o resultado de seu trabalho deve ficar em memória armazenado por exemplo em uma variável global (alternativa mais simples, porém menos técnica), e no caso do Arduino tal variável deve ser qualificada como volatile para que seu valor nunca seja tomado de um registrador, mas sim um espaço na memória RAM, pois seu valor poderia não ser o mais atual e sim uma imagem desatualizada. Assim no sketch a variável rdEncoder (leitura do codificador) assume a função de reter o resultado da operação da rotina de serviço de interrupção.

Os pinos digitais 2 e 3 no Arduino UNO são capazes de captar uma interrupção externa cada um deles (outros modelos de Arduino, podem, eventualmente, capturar interrupções externas em outros pinos). A eles está associada a mesma rotina de serviço de interrupção que dá tratamento ao par de sinais DT e CLK do codificador digital.

O loop principal neste sketch tem por função observar o conteúdo de rdEncoder e quando este for diferente de zero, ou seja, algum movimento foi detectado no eixo do codificador, seu valor é somado com o conteúdo de lastCounter (última contagem de movimento) colocando-se o resultado em thisCounter (esta contagem de movimento) para que seja enviado pelo monitor serial para observar a posição corrente do eixo do codificador. Feito este tratamento rdEncoder é zerado (condição de repouso, ficando à espera da próxima movimentação) e lastCounter então recebe o conteúdo de thisCounter para uso no próximo ciclo do loop.

Assim com esta arquitetura do software mais nenhum pulso será perdido (desde que a rotação não seja tão alta que exceda o desempenho de execução de instruções do microcontrolador).

Leituras obtidas no monitor serial independem do modelo de codificador rotativo utilizado. Todas as leituras são coincidentes com os pontos de realimentação táctil do eixo do codificador:

 1    <= 1° click no sentido horário
 2    <= 2° click no sentido horário
 3    <= 3° click no sentido horário
 …
 n-1    <= n°-1 click no sentido horário
 n    <= n° click no sentido horário
 n-1    <= 1° click no sentido anti-horário
 n-2    <= 2° click no sentido anti-horário
 n-3    <= 3° click no sentido anti-horário
 …
 2    <= n°-1 click no sentido anti-horário
 1    <= n° click no sentido anti-horário

Rotina 6 – Detecção de movimentações do eixo do codificador por interrupção de temporizador

Esta abordagem “sacrifica” algum dos temporizadores do Arduino, lembrando que no UNO o timer0 é utilizado nas funções delay(); millis(); e micros(), timer1 para comandar o PWM (no controle de servos via biblioteca), finalmente o timer2 para a função tone().

Para utilizar o timer2 é necessário estabelecer a rotina de serviço de interrupção, nela estará o sensoriamento dos sinais DT e CLK, incluindo a capacidade de debounce.

Inicialmente deve-se configurar o timer2 para operar de tal forma que não gere sinais externos, pois não há tal requisito no hardware empregado, e opere no modo normal, pois basta que ele ciclicamente conte de certo valor ao seu máximo (0xFF), gerando uma interrupção neste ponto. Para tanto os registradores TCCR2A; TCCR2B; e TIMSK2 devem ser carregados respectivamente com 0x00; 0x07; e 0x01. Já TCNT2 deve ser carregado com o valor de corresponda ao intervalo de interrupção desejado, e é calculado segundo a fórmula:

t = (256 – ValorTCNT2) x Prescaler/ fosc è ValorTCNT2 = 256 – (t * fosc / Prescaler)

onde:
     ValorTCNT2 é o valor a ser carregado no temporizador
     t é o tempo entre interrupções em segundos
     fosc é a frequência do oscilador
     Prescaler é o valor programado para o divisor de entrada

De tal forma que para interrupções com intervalo de aproximadamente 2,1 milissegundos (~475Hz), utilizando um cristal de 16MHz (Arduino UNO) e o Prescaler em 1024 o valor a ser carregado no temporizador2 é 223 (resultando em 2,112ms).

A rotina de serviço de interrupção chama uma função denominada debounce(), que recebe como parâmetros de chamada os números dos pinos onde estão conectados CLK e DT do codificador rotativo respectivamente.

Para efetuar o debounce dos sinais das chaves do codificador (se fosse óptico o debounce poderia ser eliminado) é guardado nos nibbles (dígitos) de um byte os contadores de estado e o estado atual e anterior de cada um dos sinais, para tanto aloca-se os bits do byte da seguinte forma:

          +-----+-----+-----+-----+-----+-----+------+------+
     Bit  |  7  |  6  |  5  |  4  |  3  |  2  |   1  |   0  |
  Função  |count DT   |count CLK  |lstDT|vl DT|lstCLK|vl CLK|
 Vlr Inic |        0  |        0  |  0  |  0  |   0  |   0  |
          +-----+-----+-----+-----+-----+-----+------+------+
      countDT[7:6] – Contador de estado de DT
     countCLK[5:4] – Contador de estado de CLK
          lstDT[3] – Última leitura de DT
         lstCLK[2] – Última leitura de CLK
          vl DT[1] – Leitura de DT
         vl CLK[0] – Leitura de CLK

Em outro byte nos bits 1 e 0 são guardados os estados estáveis (pós debounce) dos sinais DT e CLK, respectivamente.

O Mecanismo de Debounce

A ideia é contar nos nibbles da variável tipo byte denominada cntDebounce quantas vezes se lê o mesmo estado de cada um dos sinais, quando a contagem atingir 3 o sinal ganha a qualificação de estável e seu valor é copiado para a variável do tipo byte denominada ultEstado. Caso haja mudança de valor no sinal lido o contador correspondente é zerado, iniciando então um novo ciclo de debounce.

O valor representado pelos bits 1 e 0 de ultEstado é o último estado da máquina de estados e o valor representado pelos bits 1 e 0 de cntDebounce é o estado atual da máquina.

Com os dois valores da máquina de estados mais o tipo do codificador digital empregado uma cadeia de comparações implementada por três switch cases anota na variável global rdEncoder o valor de deslocamento do eixo do codificador rotativo, podendo assumir valores negativos; 0; ou valores positivos, correspondendo proporcionalmente a quantidade de rotação no sentido anti-horário, parado e quantidade de rotação no sentido horário.

Nota: A rotina conta com um recurso de geração de sinal para medição de tempo de execução da ISR e aferição de intervalo de tempo do timer2, controlado pela constante DEBUG. Caso DEBUG valha mais que 2 ou mais o pulso é gerado dentro de ISR.

Segue código utilizado na rotina6:

/* Rotina 6 - Detecção de movimentações do eixo do codificador
   por interrupção de temporizador
   Data: 20210204
   Autor: Chico
   Obs.: No monitor será mostrado uma contagem de movimentações
         crescente para o sentido horário e decrescente para o
         sentido anti-horário
   Esta rotina utiliza a técnica da máquina de estados
*/
// Definições para o temporizador
#define modeTim2  0    // Operação em modo normal sem geração de sinais externos
#define vPrescaler 7  // Prescaler dividindo Fosc por 1024
#define cteTempo  254  // Intervalor de ~6,2ms [ 256 - (tempo x fosc / Prescaler) ]

/*
 * #define cteTempo 0   // (256-  0)*1024*62,5e-9=16,388e-3 ou ~60Hz
 * #define cteTempo 99  // (256- 99)*1024*62,5e-9=10,048e-3 ou ~100Hz
 * #define cteTempo 126 // (256-126)*1024*62,5e-9= 8,333e-3 ou ~120Hz
 * #define cteTempo 159 // (256-159)*1024*62,5e-9= 6,208e-3 ou ~160Hz
 * #define cteTempo 191 // (256-191)*1024*62,5e-9= 4,166e-3 ou ~240Hz
 * #define cteTempo 207 // (256-207)*1024*62,5e-9= 3,136e-3 ou ~320Hz
 * #define cteTempo 223 // (256-223)*1024*62,5e-9= 2,112e-3 ou ~475Hz
 * #define cteTempo 239 // (256-239)*1024*62,5e-9= 1,088e-3 ou ~900Hz
 */
 
// Definições de hardware
#define pinCLK  2     // Entrada digital para leitura do sinal CLK
#define pinDT   3     // Entrada digital para leitura do sinal DT
#define tipoEnc 2     // HW-040 = 1; KY-040 = 2
#define DEBUG 2       // Imprime (1) ou não (<>1) no monitor serial

#define cteTempo 223   // (256 - 223) * 1024 * 62,5e-9 =  2,112e-3 ou ~475Hz

volatile int rdEncoder = 0;   // Indicador de movimento do eixo
                              // do encoder
         int lastCounter = 0;

ISR(TIMER2_OVF_vect) {
  TCNT2 = cteTempo;
  if (DEBUG > 1) {
    digitalWrite(10, !digitalRead(10));   // Para medir o intervalo
                                          // de interrupção
                                          //delayMicroseconds(250);
    //digitalWrite(10, !digitalRead(10)); // Para medir o intervalo de interrupção
  }
  Debounce(pinCLK, pinDT);
  if (DEBUG > 1) {
    delayMicroseconds(150);
    digitalWrite(10, !digitalRead(10));  // Para medir duração da
                                         // rotina de interrupção
  }
}

void Debounce(byte pin1, byte pin2) {
         byte _CK_;
         byte _DT_;
  static byte ultEstado=0x00;
  static byte cntDebounce=0x00;

  _CK_=digitalRead(pin1);
  _DT_=digitalRead(pin2);

  // Trata CK bits 2 e 0
  if ((cntDebounce & 0x01) != (_CK_ << 0)) {
    // Se diferentes zera o contador CK
    cntDebounce &= 0xCF;
    // Copia pino em "ck"
    (_CK_==0) ? cntDebounce &= 0xFE : cntDebounce |= 0x01;
  } else {
    if ((cntDebounce & 0x30) < 0x30) {
      cntDebounce += 0x10;       // Se ainda não deu debounce conta
                                 // mais um no estado
      if ((cntDebounce & 0x30) == 0x30) {
        // Se atingiu o debounce trata o novo estado
        // Copia estado do pino e seta o flag de estavel 
        (_CK_==0) ? cntDebounce &= 0xFA : cntDebounce |= 0x05;
        stateMachine((cntDebounce & 0x03) , (ultEstado));
        // Copia o novo estado estavel de dt e de ck
        ultEstado = cntDebounce & 0x03;
      }
    }
  }
  // Trata DT bits 3 e 1
  if ((cntDebounce & 0x02) != (_DT_ << 1)) {
    // Se diferente zera contador de DT
    cntDebounce &= 0x3F;
    // Copia pino em "dt"
    (_DT_==0) ? cntDebounce &= 0xFD : cntDebounce |= 0x2;
  } else {
    if ((cntDebounce & 0xC0) < 0xC0) {
      // Se ainda não deu debounce conta mais um no estado
      cntDebounce += 0x40;
      if ((cntDebounce & 0xC0) == 0xC0) {
        // Se atingiu o debounce trata o novo estado
        // Copia estado do pino e seta o flag de estável
        (_DT_==0) ? cntDebounce &= 0xF5 : cntDebounce |= 0x0A;
        stateMachine((cntDebounce & 0x03) , (ultEstado));
        // Copia o novo estado estável de dt e de ck
        ultEstado = cntDebounce & 0x03;
      }
    }
  }
}

void stateMachine(byte velho, byte novo) {
  switch (novo) {
    case 3:                   // Estado estável para HW-040 e KY-040
      switch (velho) {
        case 1:
          rdEncoder -= 1;
          break;
        case 2:
          rdEncoder += 1;
          break;
      }
      break;
    case 0:                   // Estado estável para HW-040
      if (tipoEnc == 1) {
        switch (velho) {
          case 1:
            rdEncoder += 1;
            break;
          case 2:
            rdEncoder -= 1;
            break;
        }
      }
      break;
  }
}

void setup() {
  Serial.begin(9600);
  pinMode(10, OUTPUT);
  
  // ConfiguraTimer 2
  TCCR2A = modeTim2;
  TCCR2B = vPrescaler;
  TCNT2=cteTempo;
  TIMSK2=1;

  Serial.print("Gire o Cursor do codificador!");
  Serial.println();
}

void loop() {
  int thisCounter;

  if (rdEncoder != 0) {
    thisCounter = (lastCounter + rdEncoder);
    (DEBUG > 0) ? Serial.println(thisCounter) : rdEncoder=0;
    lastCounter = thisCounter;
    rdEncoder = 0;
  }
}
//FIM

Nota: Este sketch ocupa 2484 bytes da memória de programa e 224 bytes da memória dinâmica para um Arduino UNO R3. No caso da geração de pulsos de medição internamente à ISR o espaço ocupado na memória de programa passa a ser de 2644 bytes.

Optou-se aqui pela utilização da detecção de movimento por máquina de estados.

Como já citado é importante observar que uma interrupção não tem retorno de qualquer valor, logo o resultado de seu trabalho deve ficar em memória armazenado por exemplo em uma variável global (alternativa mais simples, porém menos técnica), e no caso do Arduino tal variável deve ser qualificada como volatile para que seu valor nunca seja tomado de um registrador, mas sim um espaço na memória RAM, pois seu valor poderia não ser o mais atual e sim uma imagem desatualizada. Assim no sketch a variável rdEncoder (leitura do codificador) assume a função de reter o resultado da operação da rotina de serviço de interrupção.

O loop principal neste sketch tem por função observar o conteúdo de rdEncoder e quando este for diferente de zero, ou seja, algum movimento foi detectado no eixo do codificador, seu valor é somado com o conteúdo de lastCounter (última contagem de movimento) colocando-se o resultado em thisCounter (esta contagem de movimento) para que seja enviado pelo monitor serial para observar a posição corrente do eixo do codificador. Feito este tratamento rdEncoder é zerado (condição de repouso, ficando à espera da próxima movimentação) e lastCounter então recebe o conteúdo de thisCounter para uso no próximo ciclo do loop.

Com esta arquitetura do software podem ocorrer perda de pulsos do codificador rotativo, no entanto não haverá pulsos falsos.

Leituras obtidas no monitor serial independem do modelo de codificador rotativo utilizado. Todas as leituras são coincidentes com os pontos de realimentação táctil do eixo do codificador:

 1    <= 1° click no sentido horário
 2    <= 2° click no sentido horário
 3    <= 3° click no sentido horário
 …
 n-1   <= n°-1 click no sentido horário
 n     <= n° click no sentido horário
 n-1   <= 1° click no sentido anti-horário
 n-2   <= 2° click no sentido anti-horário
 n-3   <= 3° click no sentido anti-horário
 …
 2     <= n°-1 click no sentido anti-horário
 1     <= n° click no sentido anti-horário

Com isto foram abordadas as principais estratégias de implementação de software para uso dos codificadores rotativos modelos HW-040 e/ou KY-040.

Algumas Bibliotecas Disponíveis para uso de Codificador Rotativo no Arduino

Segue uma relação de quatro bibliotecas para uso de codificadores rotativos com Arduino, escolhidas em meio a mais de 15 bibliotecas extraídas da página do web site Arduino no URL: <https://www.arduino.cc/en/Reference/Libraries>.

Esta breve relação não tem a pretensão de ser exaustiva, mas atender às necessidades mais comuns de aplicações gerais com codificadores rotativos. Na tabela abaixo constam os dados para localizar as biblioteca no github e em seguida um quadro resumo para cada biblioteca onde constam as seguintes informações: Denominação; Versão (à época da criação deste texto); Tipo de licença; Classe(s) da biblioteca; Método construtor e parâmetros; Métodos disponíveis, parâmetros e retornos; Sketch exemplo; e Observações.

Os sketchs exemplo são variações sobre os exemplos das próprias bibliotecas e incluem comentários do autor.

As observações do quadro resumo têm caráter de notas pessoais do autor. Todas as quatro bibliotecas foram percebidas como fracas de documentação, devido não haver qualquer texto orientativo cerca o uso dos métodos disponíveis, não obstante, seja prática da comunidade documentar com base em exemplos.

#ResponsávelTítuloURLObservações
1David AntlerGroveEncoderhttps://github.com/dantler/GroveEncoderPara Arduino 101
2Lennart HennigsESPRotaryhttps://github.com/LennartHennigs/ESPRotaryPode requerer a biblioteca Button2
3Mattias HertelRotaryEncoderhttps://github.com/mathertel/RotaryEncoderMais popular
4Manuel ReimerEncoderStepCounterhttps://github.com/M-Reimer/EncoderStepCounter
#ResponsávelTítuloGithub / SiteObservações
1David AntlerGroveEncoder<https://github.com/dantler/GroveEncoder>Para Arduino 101
2Lennart HennigsESP Rotary<https://github.com/LennartHennigs/ESPRotary>Pode requerer a biblioteca: Button2
3Matthias HertelRotary Encoder<https://github.com/mathertel/RotaryEncoder>Mais popular
4Manuel ReimerEncoderStepCounter<https://github.com/M-Reimer/EncoderStepCounter> 
Tabela 3: Relação das bibliotecas citadas
    Biblioteca: GroveEncoder (David Antler)
        Versão: 1.2.0
       Licença: Própria ou indeterminada
     Classe(s): GroveEncoder
Construtor(es): GroveEncoder nomeObjeto(int parm1, void parm2) 
                Param1: Pino de conexão do sinal DT
                Param2: Ponteiro para a rotina de serviço de intr
                        (chamada aqui de call-back function)
       Métodos: int getValue () – Obtém a contagem do codificador
                Retorna um inteiro com sinal
                void setValue (int param1) – Ajusta valor inicial
                Param1: valor inteiro a ser assumido como a
                        contagem de posição
                void resetValue () – Zera a contagem corrente
                Sem parâmetros e chamada ou valor de retorno
 Sketch exemplo:
 #include 
 #define signalDT  2   // Pino leitor do sinal DT (ou A) do
                       // codificador rotativo
                       // Logo o sinal CLk (ou B) será lido pelo
                       // pino 3
 #define btnCR     4   // Pino de leitura do botão do codificador
                       // rotativo

 // Rotina de exibir contagem da posição do eixo do codificador
 void mostraPosic(int newValue) {
   Serial.println(newValue);
 }

 // Instancia o objeto do codificador
 GroveEncoder myEncoder(signalDT, &mostraPosic);

 // Configurações do Arduino
 void setup() {
   Serial.begin(9600);
   pinMode(btnCR, INPUT_PULLUP);
   pinMode(LED_BUILTIN, OUTPUT);
 }

 // Laço principal do sketch void loop() {
   int Minhavar, Oldvar;
   while(1) {
     Minhavar = myEncoder.getValue();  // Le contagem atual
     if (Minhavar != Oldvar) {
       digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
       Oldvar = Minhavar;
     }
     leBtn();    // Le o botão do codificador
   }
 }

 // Função de leitura e atuação do botão do codificador rotativo
 void leBtn(){
   static int lastBTN = 0;

   if (digitalRead(btnCR) == LOW && lastBTN == 0) {
       lastBTN = 1;
     }
   if (digitalRead(btnCR) == HIGH & lastBTN == 1) {
       lastBTN = 0;
       myEncoder.resetValue();  // Reseta o marcador de posição
   }
 }
 //FIM
 Observações: • Software preparado para o modelo KY-040, com o
                modelo HW-040;
              • Documentação pobre;
              • Exemplo único e pouco ilustrativo.

    Biblioteca: ESPRotary (Lennart Hennigs)
        Versão: 1.4.1
       Licença: MIT
     Classe(s): ESPRotary
Construtor(es): ESPRotary (byte param1, byte param2, [bytes param3], [int param4], [int param5], [int param6])
                Param1: Pino de conexão do sinal DT
                Param2: Pino de conexão do sinal CLK
                Param3: Número de pulsos por click (opcional [4])
                Param4: Limite máximo (opcional [N/D])
                Param5: Limite mínimo (opcional [N/D])
                Param6: Posição inicial (opcional [0])
 obs.: Valores entre colchetes são assumidos como default
       N/D indica que não há valor definido, portanto sem limites
       Métodos: void setChangeHandler (CallBackFunction) - Função
                  a ser executada quando houver mudança de
                  posição (seja horário ou anti-horário)
                Param1: Nome da função a ser executada
                void setRightRotationHandler (CallBackFunction) -
                  Função a ser executada quando houver giro no
                  sentido horário
                Param1: Nome da função a ser executada
                void setLeftRotationHandler (CallBackFunction) -
                  Função a ser executada quando houver giro no
                  sentido anti-horário
                Param1: Nome da função a ser exeuctada
                void setUpperOverflowHandler (CallBackFunction) -
                  Função a ser executada quando atinge o limite
                  superior da escala
                Param1: Nome da função a ser executada
                void setLowerOverflowHandler (CallBackFunction) -
                  Função a ser executada quando atinge o limite
                  inferior da escaal
                Param1: Nome da função a ser executada
                int getPosition () – Lê posição do eixo
                Retorna um inteiro sinalizado com a posição do
                  codificador.
                void resetPosition(int param1 bool fireCallBack) 
                Param1: Valor inteiro com sinal a ser assumido,
                  se omitido Zero é assumido
                Param2: Sinalizador (flag) de permissão de
                  execução da rotina de CallBack, se omitido é
                  assumido verdadeiro.
                byte getDirection() - Obtém o sentido de rotação
                  do codificador em valor numérico
                Retorna um byte sinalizado com a direção de giro
                  do eixo (1 para direita e -1 para esquerda)
                string directionToString(byte direction) - Traduz
                  valor em texto de direção
                Param1: Valor a ser traduzido (no contexto de
                  direção valendo [-1..0]
                Retorna direção em forma de texto, "LEFT" para
                  param1 valendo -1 de outra forma retorna
                  "RIGHT"
                void setStepPerClick (int steps) - Ajusta a qtde
                  de pulsos por click
                Param1: Número inteiro sinalizado de pulsos por
                  clique no eixo do codificador
                int getStepPerClick () – Obtém número de pulsos
                  por click
                Retorna um inteiro sinalizado com o número de
                  pulsos (passos) por click no codificador
                void loop() – Rotina a ser chamada do loop
                  principal para acionar serviços internos da
                  biblioteca
                Sem parâmetros e chamada ou valor de retorno
 Sketch exemplo:
/* Este exemplo utiliza a biblioteca Button2-master do mesmo
 * mantenedor, ampliando as possibilidades de utilização dos
 * HW-040 e KY-040 pelo uso do push-button incorporado
 */ 
 //  Em : https://github.com/LennartHennigs/Button2
 include "Button2.h";  include "ESPRotary.h";
 /* ========================================================= */
 define ROTARY_PIN1  2    // Conexão do sinal DT  (ou A)
 define ROTARY_PIN2 3     // Conexão do sinal CLK (ou B)
 define BUTTON_PIN  4     // Conexão do contato do botão
 ****************************************************************
 * O HW-040 gera 2 pulsos a cada clique no eixo, um para DT
 * e um para CLK
 * O KY-040 gera 4 pulsos a cada clique no eixo, dois para DT e
 * dois para CLK
 */ 
 define HW_040  2
 define KY_040  4
 define CLICKS_PER_STEP   KY_040  // Utiliza o KY-040
 define MIN_POS   5               // Valor mínimo da escala
 define MAX_POS   20              // Valor máximo da escala
 /**************************************************************
  * Parametrização de tempos (em milissegundos) para operação do
  * botão:
  *     DEBOUNCE_MS - Tempo de eliminação de repique
  *    LONGCLICK_MS - Limite mínimo para definir click longo
  *  DOUBLECLICK_MS - Intervalo para detecção de múltiplos clicks
  */
 define DEBOUNCE_MS 50
 define LONGCLICK_MS 200
 define DOUBLECLICK_MS 300
 // Instancia o codificador digital
 ESPRotary r1 = ESPRotary(ROTARY_PIN1, ROTARY_PIN2, CLICKS_PER_STEP, MIN_POS, MAX_POS);
 // Instancia o botão
 Button2 b1 = Button2(BUTTON_PIN);

 // Inicializações gerais
 void setup() {
   Serial.begin(9600);
 // Define rotinas de ação para eventos do codificador rotativo 
 // Mudança de posição do eixo
 r1.setChangedHandler(rotate);
 // Giro à esquerda
 r1.setLeftRotationHandler(showDirection);    // <= *1
 // Giro à direita
 r1.setRightRotationHandler(showDirection);   // <= *1
 // Alcance do valor de mínimo
 r1.setLowerOverflowHandler(minimo);
 // Alcance do valor de máxima
 r1.setUpperOverflowHandler(maximo);
 // Define rotinas de ação para os eventos do botão "b1"
 // Click simples
 b1.setTapHandler(click);
 // Click duplo
 b1.setDoubleClickHandler(dclick);
 // Click triplo
 b1.setTripleClickHandler(tclick);
 // Click longo
 b1.setLongClickHandler(resetPosition);
 // Banner de teste pronto para operaer
 Serial.println("\n\Ranged Counter");
 Serial.println("You can only set values between " + String(MIN_POS) + " and " + String(MAX_POS) +".");
 }

 void loop() {
   r1.loop(); // Executa o loop interno da biblioteca ESPRotary
   b1.loop(); // Executa o loop interno da biblioteca Button2
 }

 // Rotinas de tramanento de eventos
 // Eventos do eixo do codificador
 // Mudança de posição do eixo (on change)
 void rotate(ESPRotary& r) {
    Serial.println(r1.getPosition());
 }
 // Rotação do eixo à direita ou esquerda (left or right)
 void showDirection(ESPRotary& r) {
   Serial.println(r1.directionToString(r1.getDirection()));
 }
 // Alcance de valor de posição mínimo (Lower Overflow)
 void minimo(ESPRotary& r) {
    Serial.println("Mínimo atingido!");
 }
 // Alcance de valor de posição máximo (Upper Overflow)
 void maximo(ESPRotary& r) {
    Serial.println("Máximo atingido!");
 }
 // Tratamento de eventos do botão do codificador
 // Detecção de pressionamento (single click)
 //  Obs.: Ocorre para qualquer dos próximos eventos
 void click(Button2& btn) {
   Serial.println("Click!");
 }
 // Detecção de duplo pressionamento (Double click)
 void dclick(Button2& btn) {
   Serial.println("Double Click!");
 }
 // Detecção de Triplo pressionamento (Triple click)
 void tclick(Button2& btn) {
   Serial.println("Triple Click!");
 }
 // Detecção de pressionamento longo (Long click)
 void resetPosition(Button2& btn) {
   r1.resetPosition();
   Serial.println("Reset!");
 }
 // *1 – comente para não escrever a direção de rotação
 // FIM
 Observações: • Software preparado para o modelo HW-040, KY-040 e
 eventualmente outros, sendo necessário informar a quantidade de
 alterações de estados nos sinais DT e CLK gerados a cada "ponto
 de parada" do eixo do codificador rotativo;
              • Documentação pobre;
              • Exemplos ilustrativos.

    Biblioteca: RotaeyEncoder (Mattias Hertel)
        Versão: 1.5.0
       Licença: BSD
     Classe(s): RotaryEncoder
Construtor(es): RotaryEncoder (int param1, int param2, [enum param3])
                Param1: Pino de conexão do sinal DT
                Param2: Pino de conexão do sinal CLK
                Param3: Tipo de codificador (opcional)
Os tipos são os seguintes:
TWO03 (vale 3) codificadores de 2 passos, click na posição 0
FOUR0 (vale 2) codificadores de 4 passos, click na posição 0
FOUR3 (vale 1) codificadores de 4 passos, click na posição 3
Obs.: Use TWO3 para HW-040 e FOUR03 para KY-040. Default : FOUR3
       Métodos: long getPosition () - Lê a posição do eixo
                Retorna um inteiro longo sinalizado como posição
                enum getDirection () - Lê a direção de rotação
                Retorna: 0 parado; 1 à direita; -1 à esquerda
                         À direita igual horário
                         À esquerda igual anti-horário
                void setPosition (long param1) - Inicializa uma
                         posição como inicial
                Param1: Valor inteiro longo com sinal com a
                         posição inicial
                void tick () - Função a ser chamada no loop 
                         principal e em interrupções
                Sem valor de retorno ou parâmetro de chamada, 
                         é o processamento interno da biblioteca
                ulong getMillisBetweenRotations () - Lê o tempo
                         de observação
                Retorna um inteiro longo sem sinal com o tempo 
                         de espera por uma movimentação
                ulong getRPM () – Lê a rotação do eixo por minuto
                Retorna um inteiro sem sinal com a rotação em RPM
 Sketch exemplo:
/*
 * Exemplo de uso da biblioteca RotaryEncoder
 * Este exemplo usa interrupção para a detecção de movimentação
 * do codificador rotativo.
 * Também fará uso da caraterística de operação em faixa de
 * valores e terá  uma função de reset de posição ao pressionar o
 * botão do codificador.
 * Operacionalmente sempre que houver a alteração de posição seu
 * valor será mostrado no monitor serial.  
 ****************************************************************
 * Configuração de hardware usa conectar o sinal DT (ou A) ao
 * pino 2 e CLK (ou B) ao pino 3 a chave do botão estará no
 * pino 4
 */ 
 #include <RotaryEncoder.h>

 #define KY040 true
 #define HW040 false

 #define pin_DS 2
 #define pin_CK 3
 #define pin_SW 4

 #define ROTARYSTEPS 1
 #define ROTARYMIN   0
 #define ROTARYMAX   18

 #if defined(KY040)
RotaryEncoder encoder(pin_DS, pin_CK, RotaryEncoder::LatchMode::FOUR3);
 #elif defined(HW040)
RotaryEncoder encoder(pin_DT, pin_CK, RotaryEncoder::LatchMode::TWO03);
 #endif

 // Rotina de atendimento de interrupção (chamada em qualquer
 // mudança dos sinais DT ou CLK)
 void checkPosition() {
   encoder.tick();         // Rotina do serviço de detecção de 
                           // movimento da biblioteca
 }

 void setup() {
   Serial.begin(9600);
   while (! Serial);     // Aguarda monitor serial estar pronto
   // Inicializa a posição do codificador rotativo
   encoder.setPosition(ROTARYMIN / ROTARYSTEPS);
   // Define a rotina de servico de interrupção
   attachInterrupt(pin_DS, checkPosition, CHANGE);
   attachInterrupt(pin_CK, checkPosition, CHANGE);
   // Sinaliza sistema de teste pronto
   Serial.println("Exemplo com a biblioteca RotaryEncoder");
 }

 void loop() {
   static int pos = ROTARYMIN; // Marcador de posição do eixo
   static int lastBTN = 0;     // Último estado do botão

   encoder.tick();               // Rotina do serviço de detecção
                                 // de movimento da biblioteca
   // Mostra posição do eixo se houver mudança
   int newPos = encoder.getPosition();
   if (newPos < ROTARYMIN){
     encoder.setPosition(ROTARYMIN / ROTARYSTEPS);
     newPos = ROTARYMIN;
     if (pos == newPos) Serial.println("Mínimo já atingido!");
   }
   if (newPos > ROTARYMAX){
     encoder.setPosition(ROTARYMAX / ROTARYSTEPS);
     newPos = ROTARYMAX;
     if (pos == newPos) Serial.println("Máximo já atingido!");
   }
  
   if (pos != newPos) {
     Serial.println(newPos);
     pos = newPos;       // Posição envelheceu
   }
  
   // Tratamento do botão do codificador rotativo
   if (digitalRead(pin_SW) == LOW && lastBTN == 0) {
     lastBTN = 1;
   }
   if (digitalRead(pin_SW) == HIGH & lastBTN == 1) {
     lastBTN = 0;
     // Reseta a posição do codificador rotativo
     encoder.setPosition(ROTARYMIN / ROTARYSTEPS);
     newPos = ROTARYMIN;
     pos = ROTARYMIN;
   }
 }
 // FIM
 Observações: • Software preparado para o modelo HW-040, KY-040 e
                eventualmente outros, sendo necessário informar a 
                quantidade de alterações de estados nos sinais DT
                e CLK gerados a cada "ponto de parada" do eixo do
                codificador rotativo;
              • Documentação pobre;
              • Exemplos ilustrativos.

    Biblioteca: EncoderStepCounter (Manuel Reimer)
        Versão: 1.1.0
       Licença: LGPL-3.0
     Classe(s): EncoderStepCounter
Construtor(es): EncoderStepCounter (int param1, int param2, [enum param3])
                Param1: Pino de conexão do sinal CLK (ou B)
                Param2: Pino de conexão do sinal DT (ou A)
                Param3: Tipo de codificador (opcional)
 obs.: Valores admitidos FULL_STEP (default )ou HALF_STEP
       FULL_STEP => KY-040 e HALF_STEP => HW-040
       Métodos: char getPosition () cont - Lê a posição do eixo
                Retorna: um valor char (=byte) sinalizado com a
                         posição
                void reset () - Inicializa a posição do eixo
                Sem retorno ou passagem de parâmetros
                void tick () - Função a ser chamada no loop 
                         principal
                Sem valor de retorno ou parâmetro de chamada
                void begin (enum param1) - Sobreposição de
                         inicialização (após instanciação)
                Param1: Tipo de codificador (opcional)
                         FULL_STEP (KY-040) | HALF_STEP (HW-040)
                char setPosition (char param1) - Inicia posição
                          do eixo (* método não documentado)
                Param1: valor do tipo char (=byte) sinalizado
 Sketch exemplo:
/* Este exemplo utiliza a biblioteca EncoderStepCounter-master
 * que dá suporte a codificadores de meio passo ou passo
 * completo.
 * HW-040 - codificador de meio passo (HALF_STEP)
 * KY-040 - codificador de passo completo (FULL_STEP)
 */ 
 include <EncoderStepCounter.h>

 /* === Definições de Hardware === */
 #define ENCODER_PINA 2
 #define ENCODER_PINB 3

 #define ENCODER_INTA digitalPinToInterrupt(ENCODER_PINA)
 #define ENCODER_INTB digitalPinToInterrupt(ENCODER_PINB)

 #define KY040

 #ifdef KW040
 // Cria instancia para um codificador FULL_STEP
 EncoderStepCounter encoder(ENCODER_PINB, ENCODER_PINA);
 #else
 // Cria instancia para um codificador HALF_STEP
 EncoderStepCounter encoder(ENCODER_PINB, ENCODER_PINA, HALF_STEP);
 #endif
 
 void setup() {
   Serial.begin(9600);
   while (! Serial);

   // Inicializa o software para operar
   // Obs.: Aqui é que pode ser mudado o tipo de codificador
   // como agora
   encoder.begin(FULL_STEP);

   // "Instala" ISRs
   attachInterrupt(ENCODER_INTA, interrupt, CHANGE);
   attachInterrupt(ENCODER_INTB, interrupt, CHANGE);

   Serial.println("Pronto para sensoriar o codificador!");

   // Ajusta uma posição inicial arbitrária para o codificador
   // Espera um dado tipo signed char (esta função não está
   // documentada em .h)
   encoder.setPosition(-129);
 }

 // Rotina de Serviço de Interrupção chamada a cada mudança em
 // qualquer dos sinais
 void interrupt() {
   encoder.tick();
 }
 /* A biblioteca mantém um marcador de posição em um char com
  * sinal.
  * Aqui exemplifica-se como marcar a posição do codificador
  * em uma variável long sinalizada. Sempre que ela é alterada
  * o marcador interno da biblioteca é zerado.
  * Obs.: Isto permite acumular "clicks" para não perde-los.
  */
 signed long position = 0;  // Marcador de posição corrente do
                            // codificador

 void loop() {
 signed char pos = encoder.getPosition(); // Lê posição
                                          // corrente do eixo
   if (pos != 0) {
     position += pos;
     encoder.reset();         // Zera contagem de posição
                              // interna da biblioteca
     Serial.println(position);
   }
 }
 // FIM
 Observações: • Software preparado para o modelo HW-040, KY-040 e
                eventualmente outros, sendo necessário informar a
                quantidade de alterações de estados nos sinais DT
                e CLK gerados a cada "ponto de parada" do eixo do
                codificador rotativo;
              • Documentação pobre;
              • Exemplos ilustrativos.

Considerações finais

Todas as análises até aqui feitas se referem aos codificadores rotativos populares, no entanto se aplicam quase que totalmente aos modelos profissionais, exceto à aquelas que incluem em sua construção controladores, como os de uso industrial com interfaces para barramentos que incluem a circuitaria e firmware necessários para entregar a informação processada.

As técnica de interpretação do sinal de quadratura por máquina de estados, pode, diferentemente do mostrado, ser mais complexa e detalhada. A análise do código GroveEncoder.cpp ilustra bem  o aperfeiçoamento possível no uso de máquinas de estado, a ilustração a seguir mostra os cinco estados assumidos pela máquina naquela biblioteca.

Representação gráfica dos estados e suas transições , implementada pela biblioteca GroveEncoder. Os pares de números 0 e 1 são o valor dos sinais DT e CLK respectivamente. Os termos seguidos dos sinais "mais mais" e "menos menos", indicam a transição em que a máquina de estados conta ou desconta uma nova posição em função do sentido de rotação
Figura 36: Estados da Máquina de Estados da biblioteca GroveEncoder

Assim torna-se claro que a discriminação dos sinais pode ser abordada de formas diversas das mostradas aqui.

É recomendável a inclusão de mecanismos de software para permitir o acúmulo de coletas dos sinais de quadratura, característica não abordada em qualquer das rotinas 1 a 6, conferindo um grau de segurança mais elevado com relação à captura de eventos do codificador rotativo, permitindo além de uma operação mais homogênea, a elevação da capacidade de detecção para rotações mais elevadas do eixo do codificador.

Referências

Arduino Playground. RotaryEncoders. 2011 Disponível em <https://playground.arduino.cc/Main/RotaryEncoders/>, acesso em 02/fev./2021

BRAGA, Newton C. Problema de Repique – Como Resolver (MIC202). Instituto Newton C. Braga. 14/fev./2019 Disponível em <https://www.newtoncbraga.com.br/index.php/microcontrolador/143-tecnologia/16303-problemas-de-repique-como-resolver-mic202> acesso em 17/jan./2021

Cadernos de Laboratório. Projeto de filtros ativos: Passa baixo primeira ordem. 2020. Disponível em <https://cadernodelaboratorio.com.br/projeto-de-filtros-ativos-passa-baixo-primeira-ordem/>, acesso em 27/jan./2021

DYNAPAR. Encoders Incrementais, Absolutos e Resolvers: como escolher a melhor opção? Barueri: 2019. Disponível em <https://www.dynaparencoders.com.br/blog/encoder-incremental-absoluto-resolver/> acesso em 07-dez.-2020

Hertel, Mattias . A Library for the Arduino environment for using a rotary encoder as an input. Disponível em  <http://www.mathertel.de/Arduino/RotaryEncoderLibrary.aspx> , acesso em 07/fev./2021

HI TECNOLOGIA. O que é Encoder? Para que serve? Como escolher? Como interfacear? Campinas: 2018. Disponível em <https://www.hitecnologia.com.br/blog/o-que-%C3%A9-encoder-para-que-serve-como-escolher-como-interfacear/> acesso em 07-dez-2020

Protological. Debounce calaculator. 2014. Disponível em <https://protological.com/debounce-calaculator/>, acesso em 27/jan./2021

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Esse site utiliza o Akismet para reduzir spam. Aprenda como seus dados de comentários são processados.