Utiliser un codeur rotatif en code Arduino avec un ESP32
(Mis à jour le 09/01/2023)
Un codeur rotatif est un type de capteur de position qui convertit la position angulaire (rotation) d’un axe en un signal de sortie utilisé pour déterminer la position et le sens de rotation. Il est utilisé essentiellement dans les moteurs pour faire de l’asservissement, mais également dans les interfaces utilisateurs pour remplacer les potentiomètres. En plus, la plupart des codeurs que l’on trouve dans les kits DIY sont munis d’un bouton-poussoir intégré !
Prendre en main un codeur rotatif : le KY-040
Dans ce tutoriel, nous allons voir comment utiliser un module avec un codeur rotatif KY-040, très présent dans les kits DIY. On l’utilise souvent pour faire une interface utilisateur avec notre programme : sélection d’un menu, augmenter/diminuer une variable…
Différence entre codeur rotatif et potentiomètre
À première vue, le codeur rotatif KY-040
peut être confondu avec un potentiomètre. Mais en réalité, le potentiomètre est un capteur analogique alors que le codeur rotatif est numérique. Un potentiomètre modifie la valeur d’une résistance, mais possède un nombre de tours limité.
Le codeur rotatif, quant à lui, détecte un nombre de « pas » par tour et envoie un signal à chaque pas. Il tourne aussi à l’infini. On peut sentir la différence à travers le toucher : le potentiomètre tourne de manière fluide, alors que le codeur rotatif tourne de manière saccadée.
Le codeur rotatif peut être directement utilisé par un microcontrôleur puisqu’il envoie des niveaux logiques. Sa résolution est déterminée par le nombre de pas par tour, alors que la résolution du potentiomètre dépend de la résolution de l’ADC nécessaire pour estimer sa position.
Note
On utilise un potentiomètre pour régler des paramètres analogiques, comme le volume d’un ampli, tandis que le codeur rotatif est employé pour déterminer précisément une position angulaire et une direction.
Fonctionnement théorique d’un encodeur rotatif
Ce tutoriel ne rentre pas trop dans les détails techniques, mais il y a beaucoup de choses à dire 🤓. Si vous voulez connaître réellement le fonctionnement physique, les différentes technologies et les topologies classiques (par exemple, le comprendre le fonctionnement d’un encodeur rotatif optique à quadrature de phase 😨), je vous redirige vers la présentation théorique d’un encodeur rotatif .
Branchements de l’encodeur rotatif KY-040 sur l’ESP32
Ce codeur rotatif possède 2 signaux pour connaître la position : CLK
et DT
. La broche SW
est reliée au bouton-poussoir intégré (SWitch).
Avertissement
Si vous utilisez un codeur rotatif seul (pas intégré dans un module), vous devrez rajouter des résistances pull-up sur les 3 broches logiques. En effet, comme sur le circuit basique du bouton-poussoir, elles sont nécessaires pour différencier correctement les niveaux logiques.
Voici une proposition de branchement sur une carte ESP32 d’uPesy:
Codeur Rotatif |
ESP32 |
---|---|
|
|
|
|
|
|
|
|
|
|
Le schéma à faire est le suivant :
Et voici un exemple de montage sur breadboard :
Connaître la position angulaire du codeur rotatif KY-040 en code Arduino
Avec ce type d’encodeur incrémental, il ne renvoie pas précisément la position angulaire en degré mais plutôt le nombre d’incrément fait par l’utilisateur. C’est dans le programme que l’on peut en déduire l’angle. Ici, on veut juste connaître la valeur de l’incrément.
Il existe différentes façons de procéder pour déterminer l’incrément du codeur. Idéalement on voudrait une méthode fiable, qui compte bien tous les pas sans être bloquante dans le programme. En effet, si on utilise une approche simple, qui regarde dans une boucle en permanence si des signaux logiques sont reçus depuis le codeur, il risque d’y avoir des pas qui ne seront pas pris en compte si on tourne très rapidement le codeur.
Note
Parfois le programme compte dans le mauvais sens s’il n’arrive plus à suivre la cadence. (Un bug classique)
Ils existent beaucoup de code sur le Net pour utiliser des encodeurs rotatifs, mais tous ne se valent pas ! L’approche optimale c’est d’utiliser des interruptions hardware qui vont se déclencher dès qu’un changement de niveau logique est détecté. Elles seront complètement détachées du CPU: le code sera en prime non bloquant.
Note
On peut avoir des interruptions sur n’importe quelle broche de sorties de l’ESP32 (Contrairement à l’Arduino ou il y en a seulement quelques-unes…)
Les librairies Arduino compatibles avec l’ESP32
Même si l’on peut le faire tout le code nous-même, il est préférable d’utiliser des librairies. Je vous en conseille 2 qui sont optimisées pour le fonctionnement sur un ESP32 :
AiEsp32RotaryEncoder : Librairie qui utilise l’approche les interruptions hardware avec un calcul de l’incrément dans la routine d’interruptions. Elle est spécifiquement adaptée pour être utilisé avec codeur rotatif muni d’un bouton-poussoir intégré. Elle est donc parfaite avec le codeur KY-040. Je vous conseille d’utiliser celle-ci si c’est pour faire une interface utilisateur (Sélection menu, modifier valeur d’un paramètre…)
-
ESP32Encoder : Cette librairie utilise un périphérique hardware de l’ESP32 PCNT pour faire le comptage. Il n’y a aucun traitement CPU nécessaire contrairement à l’autre : tout est fait en hardware, les performances sont donc au rendez-vous. Par contre, ses fonctionnalités sont plus limitées : il n’y a pas d’interruptions à chaque incrément, pas de gestion d’un bouton-poussoir… Je recommande d’utiliser cette librairie pour lire l’encodeur rotatif intégré à un moteur , où il y aura des centaines d’incréments par seconde. Votre code pourra lire l’incrément actuel à tout moment via une fonction.
Note
La librairie permet de gérer jusqu’à 8 encodeurs rotatifs en même temps uniquement avec le PCNT hardware de l’ESP32.
Les 2 librairies s’installent facilement depuis l’Arduino IDE :
Utiliser un codeur rotatif pour une interface utilisateur avec la librairie AiEsp32RotaryEncoder
La librairie s’installe depuis le gestionnaire de bibliothèque dans l’Arduino IDE.
Voici un code squelette pour montrer les possiblités de cette librairie :
#include "AiEsp32RotaryEncoder.h"
#include "Arduino.h"
#define ROTARY_ENCODER_A_PIN 23
#define ROTARY_ENCODER_B_PIN 22
#define ROTARY_ENCODER_BUTTON_PIN 21
#define ROTARY_ENCODER_VCC_PIN -1
#define ROTARY_ENCODER_STEPS 4
//instead of changing here, rather change numbers above
AiEsp32RotaryEncoder rotaryEncoder = AiEsp32RotaryEncoder(ROTARY_ENCODER_A_PIN, ROTARY_ENCODER_B_PIN, ROTARY_ENCODER_BUTTON_PIN, ROTARY_ENCODER_VCC_PIN, ROTARY_ENCODER_STEPS);
void rotary_onButtonClick()
{
static unsigned long lastTimePressed = 0; // Soft debouncing
if (millis() - lastTimePressed < 500)
{
return;
}
lastTimePressed = millis();
Serial.print("button pressed ");
Serial.print(millis());
Serial.println(" milliseconds after restart");
}
void rotary_loop()
{
//dont print anything unless value changed
if (rotaryEncoder.encoderChanged())
{
Serial.print("Value: ");
Serial.println(rotaryEncoder.readEncoder());
}
if (rotaryEncoder.isEncoderButtonClicked())
{
rotary_onButtonClick();
}
}
void IRAM_ATTR readEncoderISR()
{
rotaryEncoder.readEncoder_ISR();
}
void setup()
{
Serial.begin(115200);
//we must initialize rotary encoder
rotaryEncoder.begin();
rotaryEncoder.setup(readEncoderISR);
//set boundaries and if values should cycle or not
//in this example we will set possible values between 0 and 1000;
bool circleValues = false;
rotaryEncoder.setBoundaries(0, 1000, circleValues); //minValue, maxValue, circleValues true|false (when max go to min and vice versa)
/*Rotary acceleration introduced 25.2.2021.
* in case range to select is huge, for example - select a value between 0 and 1000 and we want 785
* without accelerateion you need long time to get to that number
* Using acceleration, faster you turn, faster will the value raise.
* For fine tuning slow down.
*/
//rotaryEncoder.disableAcceleration(); //acceleration is now enabled by default - disable if you dont need it
rotaryEncoder.setAcceleration(250); //or set the value - larger number = more accelearation; 0 or 1 means disabled acceleration
}
void loop()
{
//in loop call your custom function which will process rotary encoder values
rotary_loop();
delay(50); //or do whatever you need to do...
}
Voici ce que l’on obtient dans le moniteur série quand on tourne le codeur :
Vous remarquerez que les valeurs n’augmentent pas toujours de manière linéaire. En fait, c’est parce que le mode “Acceleration” est activé ! Il permet d’augmenter plus rapidement la valeur quand on tourne rapidement le codeur. C’est très pratique pour changer la valeur d’un paramètre. Vous pouvez la désactiver via la fonction rotaryEncoder.disableAcceleration()
. Sinon vous pouvez modifier son comportement avec la fonction rotaryEncoder.setAcceleration(0-1000)
.
Avec cette librairie, le comptage se fait en arrière-plan, même si on retire la fonction rotary_loop()
de la loop()
. Le code présenté ci-dessus peut vous servir de code “squelette” que vous pourrez modifier pour votre projet. Pour connaître toutes les possibilités de la librairie, vous pouvez consulter la doc sur le dépôt GitHub .
Connaître la position angulaire d’un encodeur rotatif en utilisant le compteur matériel de l’ESP32 (PCNT) avec la lib ESP32Encoder
La librairie ESP32Encoder
s’installe directement depuis l’Arduino IDE :
Le code est beaucoup plus simple que la librairie précédente, mais en même temps, on peut récupérer uniquement la valeur de l’incrément du codeur.
#include <ESP32Encoder.h>
#define CLK 22 // CLK ENCODER
#define DT 23 // DT ENCODER
ESP32Encoder encoder;
void setup () {
encoder.attachHalfQuad(DT, CLK);
encoder.setCount(0);
Serial.begin ( 115200 );
}
void loop () {
long newPosition = encoder.getCount();
Serial.println(newPosition);
delay(25);
}
On obtient également dans le terminal série la valeur qui s’incrémente progressivement. Même si l’on mettait un délai de 500ms dans la loop()
le compte sera quand même correct, car le comptage est entièrement fait par le périphérique hardware PCNT. La fonction getCount()
ne fait que récupérer la valeur du registre du compteur matériel, qui lui compte dans son coin.
Note
Si vous avez malgré tout des pas qui sont manqués, vous pouvez activer la fonction setFilter()
pour filtrer au maximum l’entrée du signal au niveau du compteur matériel (PCNT).
Il existe une documentation brute pour connaître les différentes fonctions de cette librairie.