Je m'essaye au code, qui veut m'aider à identifier mes erreurs de noob?

j'ai commencé à potasser le code. La pratique et l'expérimentation étants un bon moyen de progresser, je rassemble un nano et quelques composants de base, et j'essaie ce tuto...
https://dronebotworkshop.com/rotary-encoders-arduino/
Tout fonctionne super bien, aucun problème.

Du coup je rajoute une led (et sa résistance), un peu de code et voilà le bouton poussoir de l'encodeur qui à aussi sa petite interaction.

Alors je rajoute un LCD i2C 20x4, l'idée est d'aussi y afficher les infos qui sont déjà envoyées dans le Moniteur Série, et notamment un X en haut à droite de l'écran quand le switch est actionné.

...quelques oublis, maladresses et typos plus loin, ça compile et ça fonctionne, mais pas totalement aussi bien que la version d'origine..
Voici le code:

/*
  Rotary Encoder Demo deeply revisited as a test guinea pig
  UNSTABLE, It's not advised to use this code
  Original code details:
  --------------------------------------------------------
  rot-encode-demo.ino
  Demonstrates operation of Rotary Encoder
  Displays results on Serial Monitor
  DroneBot Workshop 2019
  https://dronebotworkshop.com
*/

#include <Wire.h>
#include <LiquidCrystal_I2C.h> //FDEBRABANDER

// Rotary Encoder Inputs
#define inputCLK 4
#define inputDT 5
#define inputSW 6

// LED Outputs
#define ledCW 8
#define ledCCW 9
#define ledSW 10

LiquidCrystal_I2C lcd(0x27, 20, 4);

int counter = 0;
int currentStateSW = 0;
int currentStateCLK;
int previousStateCLK;

String encdir = "";

void setup() {

  lcd.begin(); // initialisation de l'afficheur
  lcd.clear(); // removes all characters from display

  // Set encoder pins as inputs
  pinMode (inputCLK, INPUT);
  pinMode (inputDT, INPUT);

  // Set switch pin as input
  pinMode (inputSW, INPUT);


  // Set LED pins as outputs
  pinMode (ledCW, OUTPUT);
  pinMode (ledCCW, OUTPUT);
  pinMode (ledSW, OUTPUT);

  // Setup Serial Monitor
  Serial.begin (9600);

  // Read the initial state of inputCLK
  // Assign to previousStateCLK variable
  previousStateCLK = digitalRead(inputCLK);

}

void loop() {

  lcd.backlight();

  // read the state of the switch inputSW value: DIGITAL
  currentStateSW = digitalRead(inputSW);

  // check if the pushbutton is NOT-pressed. If it is, the currentStateSW is HIGH due to pull-up restistors:
  if (currentStateSW == HIGH) {
    digitalWrite(ledSW, LOW); // turn LED off
    lcd.setCursor(19, 0); //set cursor at top right corner
    lcd.print(" "); //write blank char.
  } else {
    digitalWrite(ledSW, HIGH); // turn LED on
    lcd.setCursor(19, 0); //set cursor at top right corner
    lcd.print("X"); //write an X
  }

  // Read the current state of inputCLK
  currentStateCLK = digitalRead(inputCLK);

  // If the previous and the current state of the inputCLK are different then a pulse has occured
  if (currentStateCLK != previousStateCLK) {

    // If the inputDT state is different than the inputCLK state then
    // the encoder is rotating counterclockwise
    if (digitalRead(inputDT) != currentStateCLK) {
      counter --;
      encdir = "CCW";
      digitalWrite(ledCW, LOW);
      digitalWrite(ledCCW, HIGH);

    } else {
      // Encoder is rotating clockwise
      counter ++;
      encdir = "CW";
      digitalWrite(ledCW, HIGH);
      digitalWrite(ledCCW, LOW);

    }
    Serial.print("Direction: ");
    Serial.print(encdir);
    Serial.print(" -- Value: ");
    Serial.println(counter);

    // LCD print all the values anoted
    lcd.clear(); // removes all characters from display
    lcd.setCursor(0, 0);
    lcd.print("Direction: ");
    lcd.print(encdir);
    lcd.setCursor(0, 1);
    lcd.print("Value: ");
    lcd.print(counter);  // and also start a new line

  }
  // Update previousStateCLK with the current state
  previousStateCLK = currentStateCLK;
}

En principe, quand l'encodeur tourne dans le sens horaire (clockwise) la led verte s'allume et l'information CW et un nombre incrémenté s'affichent,
Et quand on tourne dans le sens anti-horaire (counter CW) la led rouge s'allume et l'information CCW et un nombre décrémenté s'affichent.

Niveau led + moniteur série + LCD tout s'affiche, et idem pour l'activation du switch,

Par contre avec ma version, la rotation anti-horaire est TRES mal détectée... elle l'est de temps à autre, mais on est loin du systématique infaillible de la version d'origine
Quand elle n'est pas bien détectée, l'incrémentation ne change pas, et c'est le sens horaire (CW) et la led verte qui s'affichent, au lieu que ce soit l'info CCW et la led rouge.

J'ai expérimenté différents 'emplacements' dans les conditions et la boucle, mais sans grand succès, cette version étant la moins pire.. :sweat_smile:

Et merci d'avance! :hugs:

Détail d'importance, j'ai bien des résistances pull-up sur la sortie DT et CLK de l'encodeur (vers 5v). Pas de res inline , ni de caps.

Et d'ailleurs ça fonctionnait sans faille sur la version d'origine... donc j'accuserais plus facilement mon codage de grand spécialiste! :stuck_out_tongue_winking_eye: :woozy_face:

Tu peux déplacer lcd.backlight() dans le setup.

Que se passe-t-il si tu commentes toutes les lignes du LCD et que tu le déconnectes ? Est-ce mieux ?

Ok je vais le faire, ça fera effectivement un truc de moins dans loop. Je l'avais mis là pour plus tard experimenter le clignotements de l'écran.

Je fais ça ce soir et je te dis quoi

J'ai vu certains tutos qui pilotent directement l'afficheur avec 4 ou 8 data-lines, CLK, etc..
Quand on a suffisamment de broches dispo, c'est mieux/préférable/plus efficace que par l'i2c? Peut-être qu'il y a moins de lag à l'affichage?

Avec une gestion de l'encodeur sous interruptions, cela irait certainement mieux. Je l'ai fait avec un afficheur OLED I2C :
encoder.cpp

La détection de la rotation dans les deux sens de l'encodeur fonctionne correctement.
Effectivement, comme ça plus aucun doute sur l'origine du coupable... Je n'ai même pas eu besoin de débrancher le LCD,

Bon ben je vais tâcher de m'imprégner de la méthode de fonctionnement sous interruptions! :woozy_face: :sweat_smile:

C'est comme delay() et timers... delay() c'était trop beau et simple pour être vrais... :stuck_out_tongue_winking_eye:

En fait, ' gestion sous interruptions' ça veut dire en français, ça en anglais?

Donc,

Par contre, du peu que je comprends pour le moment dans le code, pourquoi ne pas attachInterrupt en CHANGE plutôt qu'en RISE?
RISE pourrait faire rater un changement d'état descendant, donc une détection?

Bonjour frenchydude08

Ca dépend quelle "vitesse" tu veux de ton encodeur. Ainsi, pour un de ces encodeur


Qui a 20 "crans" au tour, tu peux obtenir 80 "crans" au tour (en quadrature)
Ainsi si tu as un interrupt seulement sur A et RISE détecté, tu n'utilises B que pour voire le sens de rotation, tu auras qu'une information par cran. Avec CHANGE détecté, tu auras 2 informations.
Si tu mets l'interrupt sur A et B, tu double les possibilités.
Le mode quadrature (interrupt A et B et CHANGE) est surtout utilisé sur les encodeurs de positionnement comme ceux-ci.

qui ont entre 360 et 600 pas selon les modèles, ce qui donne une précision entre 1440 et 2400 pas/tour.
Une bonne explication, ici

Cordialement
jpbbricole

1 Like

Rebonjour frenchydude08

Je te montre un exemple de traitement d'un codeur 600 tics/tour avec interrupt A et B et événement CHANGE, ce qui donne 2400 tics/tour.

/*
    Name:       Test_RotencQuadra.ino
    Created:	01.10.2021
    Author:     jpbbricole
*/

//------------------------------------- Encodeur rotatif
#define rotencApin 2     // 2 pin pour A et B du CR
#define rotencBpin 3     // Ces 2 pin sont des entrees avec interrupt sur le UNO ou Nano

//-------------------------------------Définition de la structure (objet) Rotary Encoder CR
struct rotencDef 
{
	volatile int stepIncrement;     // Valeur d'incrément
	volatile long posSteps;     // Position en pas
};
rotencDef rotenc;

boolean rotencPosNew = false;     // Quan nouvelle position de l'encodeur

void setup()
{
	Serial.begin(115200);

	//--------------------------------- Initialisation des interruptions du CR
	//                                  Activation des pins d'entrée du CR avec r?sistances de polarisation (PULLUP)
	pinMode(rotencApin, INPUT_PULLUP);
	pinMode(rotencBpin, INPUT_PULLUP);
	//------------------------------------ Activation des interrupts du CR avec renvoi ? la routine de traitement (Hasndling)
	attachInterrupt(digitalPinToInterrupt(rotencApin), rotencAchangeHandling, CHANGE);
	attachInterrupt(digitalPinToInterrupt(rotencBpin), rotencBchangeHandling, CHANGE);

	rotenc.stepIncrement = 1;
}

void loop()
{
	if (rotencPosNew)
	{
		Serial.print(F("Position:\t")); Serial.println(rotenc.posSteps);
		rotencPosNew = false;
	}
}

//------------------------------------- Rotary encoder Traitement des interruptions
void rotencAchangeHandling()     // Sortie A
{
	if (!digitalRead(rotencApin) == !digitalRead(rotencBpin))
	{rotenc.posSteps -= rotenc.stepIncrement;} else {rotenc.posSteps += rotenc.stepIncrement;}
	rotencPosNew = true;
}

void rotencBchangeHandling()     // Sortie B
{
	if (!digitalRead(rotencApin) == !digitalRead(rotencBpin))
	{rotenc.posSteps += rotenc.stepIncrement;} else	{rotenc.posSteps -= rotenc.stepIncrement;}

	rotencPosNew = true;
}

Tout cela est de l'intérêt d'étudier les interruptions et les codeurs rotatifs mais si c'est pour "lire" un simple codeur rotatif manuel tu as tout intérêt à utiliser une bibliothèque comme l'Encoder.

Cordialement
jpbbricole

1 Like

Non, ce n'est pas aussi simple.
C'est avant tout une problématique liée à la mécanique.
Il y a des codeurs incrémentaux avec des crans mécaniques. Le codeur a donc un certain nombres de positions matérialisées mécaniquement par une bille qui bloque dans une cuvette.
Si pour chaque cran le signal CLK génère les états bas -> haut -> bas il n'y a aucun intérêt à utiliser CHANGE pour l'interruption car tu auras effectivement 2 interruptions mais comme tu ne peux pas t'arrêter mécaniquement entre 2 crans ton comptage avancera toujours de 2.

L'intérêt de CHANGE c'est quand tu as un encodeur à rotation continue sans crans. Là effectivement avec CHANGE tu peux maximiser la résolution.
Le même raisonnement s'applique si on utilise les interruptions sur CLK et sur DT (des fois appelés A et B dans certaines docs). On a alors une résolution multipliée par 4.
Donc avant tout il faut bien regarder comment est conçu ton encodeur.

1 Like

Bonjour JPB, et merci pour tes précisions et liens très intéressants. :pray:

Mon encodeur est un ky40 de base, pas besoin de plus pour l'usage que j'en fait ici.

Oui j'ai vu l'usage de encodeur.h dans les liens de Riton je vais la potasser car idéale et très utile.

Donc niveau interruptions, tout n'est également qu'une question de nécessité.
Ex. Je pourrais avoir un interrupt CHANGE sur A et B pour un maximum de sensibilité de détection (puis éventuellement diviser le nombre de détections pour éventuellement ramener à la valeur 'réelle')..
Mais dans cet usage de base et puisqu'il y a utilisation d'interruptions, et aussi compte tenu de la nature en quadrature du signal,..une surveillance RISE sur une seule des deux pin suffit avec l'interrupt à déclencher le reste du code qui va capturer l'état de l'autre pin de l'encodeur...!

Yes @fdufnews, c'est vrais qu'il y a aussi cet aspect là ! Merci pour cette précision! :+1:

Bonjour frenchydude08

Tu as tout compris :+1:, bonne continuation!

Cordialement
jpbbricole

:+1:
Il ne me reste plus qu'à assimiler encoder.h debounce.h interrupt et la façon de coder tout ça de façon simple, légère et efficace, par ce que là, pour les besoins de l'exercice, j'ai même eu droit à la méthode hardcode pour démonstration....
CQFD!:stuck_out_tongue_winking_eye:

Bonjour frenchydude08

Pour assimiler encoder, les exemples de la bibliothèque encoder sont suffisamment explicites, pour ce qui est de debounce.h ce n'est pas vraiment nécessaire. Pour le bouton de ton codeur, utilises plutôt la librairie button.h. Ces 2 librairies "s'occupent" des rebonds.

Cordialement
jpbbricole

Ah ok, c'est built-in! Logique mais top.

Sur un de mes projets, les librairies Encoder ou Rotary fonctionnaient mal (un click générait plusieurs incrémentations/décrémentations), avec un encodeur acheté sur AliExpress.
Avec un vrai Bourns le fonctionnement était OK. Donc tout dépend de l'encodeur.

Je me suis inspiré de ce code :

Je l'ai simplement transformé en classe C++ (post #5)

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.