Problème de lecture d'un comparateur mécanique

Hello,
avec un Nano j'ai réalisé un montage assez classique (vu plusieurs fois) de lecture d'un pied à coulisse numérique, aucun problème les valeurs lues sont exactes. Les sorties du pied à coulisse semblent (Clock et Data) assez universelles.
Ensuite j'ai voulu faire la même chose avec un comparateur numérique (sans marque, un peu ancien) qui comporte aussi les Clock et Data, mais en fait le protocole des données est différent et je n'arrive pas à lire des valeurs cohérentes.
Avec un oscilloscope j'ai visualisé les Clock et Data, il semble qu'à partir du 4 ème bit clock du premier "groupe" le data est stable et correspond à la mesure, avant c'est instable ça change continuellement,
le deuxième "groupe" c'est plutôt au 6 ème bit clock que c'est stable.
Ces parties stables changent quand le comparateur est enfoncé et indique une nouvelle valeur.

J'ai demandé de l'aide à l'IA sans succès, Perplexity a essayé de définir des fenêtres stables avec des temps, mais je ne suis pas sûr que ce soit le plus adapté et fiable, après trop d'erreurs l'IA baisse les bras et moi aussi !

Si vous avez une idée de ce protocole utilisé par ce comparateur et comment faire une lecture fiable, je vous remercie par avance !

/*
  Lit les valeurs du comparateur en ne considérant que les parties stables du signal Data.
  Les parties stables sont définies par une fenêtre de temps après chaque front descendant du signal Clock.
*/

// Définition des broches
const int clockPin = 2;    // Broche connectée au signal Clock du comparateur
const int dataPin = 3;     // Broche connectée au signal Data du comparateur

// Paramètres de la fenêtre stable (à ajuster selon votre oscilloscope)
const unsigned long stableWindowStart = 34;   // Début de la fenêtre stable en microsecondes
const unsigned long stableWindowEnd = 386;     // Fin de la fenêtre stable en microsecondes

// Variables globales
volatile unsigned long lastClockTime = 0;   // Temps du dernier front descendant de Clock
volatile bool newDataAvailable = false;    // Indique si une nouvelle donnée est disponible
volatile bool currentDataValue = false;   // Stocke la valeur actuelle du signal Data

// Fonction d'interruption pour détecter les fronts descendants du signal Clock
void IRAM_ATTR clockInterruptHandler() {
  unsigned long currentTime = micros();

  // Vérifie si le temps écoulé depuis le dernier front est suffisant (anti-rebond)
  if (currentTime - lastClockTime > 50) {  // Ajuster cette valeur selon les besoins
    lastClockTime = currentTime;

    // Vérifie si on est dans la fenêtre stable
    if (currentTime - lastClockTime >= stableWindowStart && currentTime - lastClockTime <= stableWindowEnd) {
      // Lit la valeur du signal Data
      currentDataValue = digitalRead(dataPin);
      newDataAvailable = true;  // Marque une nouvelle donnée comme disponible
    }
  }
}

void setup() {
  Serial.begin(115200);  // Initialise la communication série

  // Configure les broches en entrée
  pinMode(clockPin, INPUT_PULLUP);  // Utilise la résistance de pull-up interne
  pinMode(dataPin, INPUT);

  // Attache l'interruption au signal Clock (front descendant)
  attachInterrupt(digitalPinToInterrupt(clockPin), clockInterruptHandler, FALLING);

  Serial.println("Lecture des données du comparateur...");
}

void loop() {
  // Vérifie si une nouvelle donnée est disponible
  if (newDataAvailable) {
    newDataAvailable = false;  // Réinitialise le flag

    // Affiche la valeur stable du signal Data
    Serial.print("Data Stable: ");
    Serial.println(currentDataValue);
  }
}

Pour identifier le protocole, il faudrait plus d'acquisitions à l'oscilloscopes accompagné des valeurs affichées sur le comparateur. Et répéter ça sur différentes mesures, dont une mesure faite à 0 et puis quelques mesures sur différentes cotes connues faites avec des cales, par exemple.

Bonjour michelm

Peut être qu'avec une bonne photo on pourrais retrouver le modèle avec Google Lens ou l'IA?

Cordialement
jpbbricole

Bonjour et merci de vous intéresser à mon problème !

Oui j'ai une photo de mesures différentes que j'avais d'ailleurs donnée à l'IA mais ça n'a pas aidé.
Le comparateur j'ai cherché, les sans marques se ressemblent un peu mais ça n'a m'a pas aidé pour le moment.

La photo du comparateur

Il n'y a pas d'indications au dos?

Non rien, ni à l'intérieur, il est assez bien fait au niveau mécanique

Essaies de poser la question sur https://www.usinages.com/

hello
avec un analyseur logique et pulse View ?

Bonsoir michelm
Le signal de ton comparateur ressemble à celui d'un calibre chinois de type A

Pause la question à ChatGPT:
Avec un Arduino, comment décoder un pied à coulisse chinois de type A?

A+
jpbbricole

Merci !

Sur l'oscilloscope j'ai essayé le décodage mais j'avoue que je n'ai pas compris si ça décodait quelque chose.

jpbbricole bien vu, ça serait un type A, il faut que je regarde ça....

Après plusieurs essais avec protocole de type A l'IA : En conclusion, il est peu probable que vous puissiez décoder ce signal directement avec un simple code Arduino. Vous devrez utiliser un décodeur CAN spécialisé ou un analyseur de protocole pour comprendre et décoder les données transmises par le comparateur.

le dernier essai qui ne donnait rien c'était :slight_smile:

const int clockPin = 2;
const int dataPin = 3;
volatile bool dataValid = false;

struct {
  float measurement;
  bool isMetric;
  bool isNegative;
} caliperData;

volatile byte bitCounter = 0;
volatile uint8_t receivedBytes[6];
volatile unsigned long lastEdge = 0;

void setup() {
  Serial.begin(115200);
  pinMode(dataPin, INPUT);
  pinMode(clockPin, INPUT);  // Pas de pull-up
  attachInterrupt(digitalPinToInterrupt(clockPin), readData, RISING); // Front montant
  Serial.println("Systeme pret - Surveillance active");
}

void readData() {
  unsigned long currentTime = micros();

  // Anti-rebond adaptatif
  if (currentTime - lastEdge < 20) return;
  lastEdge = currentTime;

  // Lecture bits inversés (MSB first)
  if (bitCounter < 48) {
    int byteIndex = bitCounter / 8;
    int bitIndex = 7 - (bitCounter % 8); // MSB first
    receivedBytes[byteIndex] |= ((!digitalRead(dataPin)) << bitIndex); // Signal DATA inversé
    bitCounter++;
  }

  // Traitement 6 octets
  if (bitCounter == 48) {
    processData();
    bitCounter = 0;
    memset(receivedBytes, 0, sizeof(receivedBytes));
    dataValid = true;
  }
}

void processData() {
  // Vérification du préambule EF B5
  if (receivedBytes[0] == 0xEF && receivedBytes[1] == 0xB5) {

    // Extraction valeur (octets 2 et 3, combinés)
    int32_t rawValue = (receivedBytes[2] << 8) | receivedBytes[3];

    // Inversion signe si nécessaire
    caliperData.isNegative = (rawValue < 0); // Vérifie si le MSB est à 1
    if (caliperData.isNegative) {
      rawValue = rawValue & 0x7FFF; // Enlève le bit de signe pour les calculs
      rawValue = -rawValue;
    }

    // Conversion en mm (resolution 0.01)
    caliperData.measurement = rawValue / 100.0f;

    // Toujours en métrique
    caliperData.isMetric = true;

  } else {
    // Valeur invalide
    caliperData.measurement = -999;
  }
}

void loop() {
  if (dataValid) {
    if (caliperData.measurement != -999 && caliperData.measurement >= -10 && caliperData.measurement <= 1000) {  // Plage de valeurs plausibles

      // Affichage XX.XX
      char buffer[10];
      dtostrf(caliperData.measurement, 5, 2, buffer);
      Serial.print("Mesure: ");
      Serial.print(buffer);
      Serial.println(" mm");
    }
    dataValid = false;
  }
}

readData() applique un filtre sur l'horloge en testant s'il y a moins de 20μs entre 2 fronts cela ne peut pas fonctionner la période de ton horloge est de 96kHz comme te l'indique ton oscillo et on peut aussi le voir à l'écran, la base de temps est réglée à 50μs par division et on compte environ 5 périodes d'horloge par division.
Le signal semble bien propre, le filtre est peut-être inutile.
Ensuite, à la réception du 48ème bits tu lances un décodeur. Mais comme tu n'es pas certain que c'est le bon protocole il y a de fortes chances pour que le code soit rejeté. A ce niveau et pendant les investigations, il serait plus productif de faire afficher les 6 octets en binaire pour essayer d'identifier un pattern.

A noter, il semble y avoir un marqueur de début sous la forme du signal d'horloge à 1 pendant environ 50μs (tu pourrais filtrer avec un test sur le signal d'horloge à 1 pendant plus de 40μs par exemple).

Bonjour michelm

J'ai retrouvé mon programme qui traite ce genre de codage, je te le mets en ligne en fin de matinée.

A+
jpbbricole

Merci fdufnews.
Mes compétences sont (très !) limitées c'est pour ça que j'ai essayé avec l'IA, mais je m’aperçois que l'IA part facilement vers de mauvais choix et que je devrais mieux analyser ce qui se passe et mieux diriger les essais.
Je dois m'absenter mais je regarde ça dès que possible.
Ce qui m'a étonné c'est qu'il y a une première partie instable dans data, alors que l'afficheur du comparateur est parfaitement stable, et la partie stable qui change effectivement si le comparateur mesure quelque chose, pareil pour la seconde partie.
En néophyte je me suis dit qu'il fallait s'occuper de ces parties stables pour lire la valeur...

jpbbricole merci !

Super, dès que possible je teste ça.

Bonjour michelm

Je te mets le programme, il est fait pour 2 appareils de mesures.
Le signal CLOCK (envoi de l'horloge au comparateur) est en sortie et DATA en entrée:

		pinMode(dlsmicSsyPin[d], OUTPUT);
		pinMode(dlsmicDataPin[d], INPUT_PULLUP);

Il faut, éventuellement, enlever l'inversion de lecture, le ! :slight_smile:

		dlsMeasureValue |= (!digitalRead(pinData)<<dlcBitOffset);                   // Inverser le bit parce que à travers un transistor

Le schéma:


Ce montage comme le programme sont faits pour gérer 2 règles SHAHE versuin USB micro.

Le programme:

/*
Name:       AppMes_DLSmicro.ino
Created:	21.05.2023 16:35:23
Author:     jpbbricole

Digital Linear Scale
Gestion de règles SHAHE (version avec micro USB)
*/

#include <TimerOne.h>     // https://github.com/PaulStoffregen/TimerOne

//------------------------------------- DLS micro USB (dlsmic)
enum dlsmicIndex {dlsmicA, dlsmicB, dlsmicNombre};
String dlsmicLabel[] = {"DLSA", "DLSB"};

struct dlsmicDef
{
	float posMm;     // Position en millimètres
	float offsetMm0;     // Offset
};
dlsmicDef dlsmic[dlsmicNombre];

const int dlsmicSsyPin[] = {6, 7};     // DSLx clock OUTPUT
const int dlsmicDataPin[] = {5, 11};     // DSLx Data Input
boolean dlsmicToBeUpdate = false;     // Les DLS doivent êtte mis à jour

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

	dlsmicInitialisation();

	Timer1.initialize(250000);     // Toutes les 250 millisecondes
	Timer1.attachInterrupt(dlsmicUpdateTime);     // attach the service routine here
}

void loop()
{
	if (dlsmicToBeUpdate)     // Si temps de mise à jour
	{
		dlsmicUpdate();
		
		Serial.println("");
		Serial.println(dlsmicLabel[dlsmicA] + " " + String(dlsmic[dlsmicA].posMm));
		//Serial.println(dlsmicLabel[dlsmicB] + " " + String(dlsmic[dlsmicB].posMm));

		dlsmicToBeUpdate = false;
	}
}

//------------------------------------- DLS
/*-----------------------------------------------------------------------------
Read a Digital Linear Scale (dls) like shahe 5403
http://www.yuriystoys.com/2012/01/reading-gtizzly-igaging-scales-with.html
'*-----------------------------------------------------------------------------
*/
void dlsmicUpdateTime()     // Interruption du timer
{
	dlsmicToBeUpdate = true;
}

void dlsmicUpdate()
{
	for (int d = 0; d < dlsmicNombre; d ++)     // Initialisation des DLS
	{
		dlsmic[d].posMm = dlsmicGetPosMm(d);
	}
}

float dlsmicGetPosMm(int dlsmicNum)     // Position par rapport à position de départ
{
	float mesMm = dlsmicGetPositionMmReal(dlsmicSsyPin[dlsmicNum], dlsmicDataPin[dlsmicNum]);
	mesMm = mesMm - dlsmic[dlsmicNum].offsetMm0;

	return mesMm;
}


float dlsmicGetPositionMmReal(int pinClock, int pinData)
{
	int dlcBitOffset;
	long dlsMeasureValue = 0;

	for(dlcBitOffset = 0; dlcBitOffset<20; dlcBitOffset++)                          // Lecture des 20 premiers bits
	{
		digitalWrite(pinClock, HIGH);
		delayMicroseconds(10);                                               // Impulsion d'horloge vers dls
		digitalWrite(pinClock, LOW);

		dlsMeasureValue |= (!digitalRead(pinData)<<dlcBitOffset);                   // Inverser le bit parceque à travers un transistor
	}

	//------------------------------------------- lecture du dernier bit (21)
	digitalWrite(pinClock, HIGH);
	delayMicroseconds(10);
	digitalWrite(pinClock, LOW);

	if(digitalRead(pinData) != HIGH)                                                // Inverser le bit parceque à travers un transistor
	{
		dlsMeasureValue |= (0x7ff << 21);
	}

	//Serial.println("> " + String((float)dlsMeasureValue / 100));
	return (float)dlsMeasureValue / 100;
}

void dlsmicInitialisation()
{
	for (int d = 0; d < dlsmicNombre; d ++)     // Initialisation des DLS
	{
		pinMode(dlsmicSsyPin[d], OUTPUT);
		pinMode(dlsmicDataPin[d], INPUT_PULLUP);

		dlsmic[d].posMm = 0.0;
		dlsmic[d].offsetMm0 = (dlsmicGetPositionMmReal(dlsmicSsyPin[d], dlsmicDataPin[d])) * 2;
	}
}

A ta disposition pour toutes questions :wink:

PS: Je me suis déjà bien amusé avec ces appareils de mesures :wink::

A+
jpbbricole

Bonsoir jpbbricole et merci.

Il va me falloir un peu de temps pour essayer de comprendre !

Alors ça tombe bien ton programme parce que j'avais commandé un autre comparateur numérique, le mien semblant ne pas fonctionner du tout au début : pas d'affichage etc. et je viens de recevoir le neuf qui est un SHAHE avec prise USB.
Donc avec ton programme j'ai peut-être la possibilité de lire le nouveau comparateur.
C'est peut-être du même style que les règles DHAHE version USB,

Ceci dit J'aimerais bien quand même aussi lire l'ancien comparateur.

Alors tout ça c'est en fait pour un banc de relevé de diagramme de distribution d'arbres à cames, 2 moteurs pas à pas sont commandés par le Nano et tourne l'arbre de 1° en 1° , la levée est donc mesurée par le pied à coulisse numérique (pour le moment) et transmise par le Nano vers un tableau Libre Office avec macro : Capture avec OpenDaqCalc sous LibreOffice Calc,
ça fonctionne, mais j'aimerais remplacer le pied à coulisse par le comparateur.

Le montage et des courbes (levée, vitesse, accélération) pour exemple :



Le programme pour le banc de test arbre à cames avec relevé etc.


#include <Stepper.h>

/* ========== CONSTANTES D'ORIGINE ========== */
const float angleMax = 210.0;
const int cycleTime = 100;
const int stepsPerRevolution = 800;
const int stepsPerDegree = 16;
const int motorSpeed = 2;

/* ===== NOUVEAU : DÉLAI ENTRE MOUVEMENT ET MESURE ===== */
const int delaiApresMouvement = 1000; // 1 seconde (ajustable)

/* ========== CONFIGURATION D'ORIGINE ========== */
const byte clockPin = 2;
const byte dataPin = 3;
const int boutonPin = 4;

volatile long rawValue = 0;
volatile int interruptFlag = 0;
volatile int currentBit = 0;
volatile int sign = 1;

float mesureMm = 0.0;
unsigned long angleDeg = 0;
bool systemActive = false;
bool enAttente = false; // Nouveau flag pour gérer l'attente

Stepper myStepper(stepsPerRevolution, 8, 9, 10, 11);

/* ========== FONCTIONS ORIGINALES (inchangées) ========== */
void processCaliperData() {
  static unsigned long lastPulse = 0;
  byte dataIn = digitalRead(dataPin);
  unsigned long now = millis();

  if((now - lastPulse) > cycleTime) {
    mesureMm = (rawValue * sign) / 100.00;
    currentBit = 0;
    rawValue = 0;
    sign = 1;
  } 
  else if(currentBit < 16) {
    if(!dataIn) rawValue |= 1 << currentBit;
    else if(currentBit == 20) sign = -1;
    currentBit++;
  }
  lastPulse = now;
}

void handleButton() {
  static bool lastState = HIGH;
  bool currentState = digitalRead(boutonPin);
  
  if(currentState != lastState) {
    delay(50);
    if(currentState == LOW) {
      systemActive = !systemActive;
      if(systemActive) {
        angleDeg = 0;
        enAttente = false;
      }
    }
    lastState = currentState;
  }
}

/* ========== SETUP ORIGINAL ========== */
void setup() {
  Serial.begin(115200);
  pinMode(clockPin, INPUT);
  pinMode(dataPin, INPUT);
  pinMode(boutonPin, INPUT_PULLUP);
  
  myStepper.setSpeed(motorSpeed);
  attachInterrupt(digitalPinToInterrupt(clockPin), []{ interruptFlag = 1; }, RISING);
  
  Serial.println("LABEL,Angle(°),Mesure(mm)");
}

/* ========== LOOP MODIFIÉ POUR LE DÉLAI ========== */
void loop() {
  static unsigned long tempsDebutAttente = 0;
  
  handleButton();

  if(interruptFlag) {
    interruptFlag = 0;
    processCaliperData();
  }

  if(systemActive && angleDeg < angleMax) {
    if(!enAttente) {
      // Phase 1 : Mouvement
      myStepper.step(stepsPerDegree);
      angleDeg++;
      tempsDebutAttente = millis();
      enAttente = true;
    }
    else {
      // Phase 2 : Attente avant mesure
      if(millis() - tempsDebutAttente >= delaiApresMouvement) {
        Serial.print("DATA,");
        Serial.print(angleDeg);
        Serial.print(",");
        Serial.println(mesureMm, 2);
        enAttente = false;
      }
    }
  }
}