Mesures simultanées de deux signaux

Bonjour,

Dans le cadre d’un projet, je dois faire des mesures sur des signaux radars : un signal A continu, carré et symétrique de fréquence 4 kHz/de période 250 µs, représentant les 16 384 impulsions émises par le radar, et un signal B étant une unique pulsation de 125 µs toutes les 4,096 secondes, représentant le début d’un nouveau tour de l’antenne.

Je souhaite faire deux mesures sur chacun ces signaux : compter le nombre d’impulsions (donc en théorie 16 384 impulsions du signal A entre deux impulsions de signal B), et compter la durée de chacune de ces impulsions (en théorie 250 / 2 = 125 µs).

En utilisant les interruptions, j’arrive à compter avec précision le nombre d’impulsions en comptant les fronts montants de chaque signal, donc pas de souci de ce côté.

Là où ça se corse, c’est pour mesurer simultanément les durées des impulsions des deux signaux : j’ai d’abord utilisé la fonction micros() avec mes interruptions sur les fronts montants et descendants, puis fait la différence des deux pour obtenir la durée des impulsions. Malheureusement, la résolution de cette fonction n’est “que” de 4 µs, là où je souhaite obtenir une précision à 1 µs.

Je suis ensuite passé à la fonction pulseIn(), qui elle a l’air d’offrir 1 µs de précision, mais qui en revanche est bloquante : mes impulsions de signal B étant espacées d’environ 4 secondes, la mesure de durée des impulsions du signal A ne se fait pas.

J’ai bricolé un peu aujourd’hui avec des protothreads, ainsi qu’avec une bibliothèque offrant une précision à 0,5 µs, mais sans réel succès.

Plutôt que continuer à m’enliser dans du code, je suis ici pour demander votre avis : avez-vous des conseils ou des recommandations ? Est-ce que ce que je tente de faire est réalisable ? Que pourriez-vous me suggérer pour avancer ?

Merci d’avance ! :slight_smile:

Edit : j’ai oublié de préciser : je travaille avec une Arduino Uno.

Bonjour,

Je suppose que tu travaille avec une MEGA2560 ou une UNO ou équivalent travaillant à 16MHz.
Sur la DUE à 84MHz, il y a un compteur indépendant “SysTick” utilisé par la fonction micros().
Celle-ci est interruptible, c’est écrit dans le code source:

// Interrupt-compatible version of micros
// Theory: repeatedly take readings of SysTick counter, millis counter and SysTick interrupt pending flag.
// When it appears that millis counter and pending is stable and SysTick hasn't rolled over, use these 
// values to calculate micros. If there is a pending SysTick, add one to the millis counter in the calculation.
uint32_t micros( void )
{
    uint32_t ticks, ticks2;
    uint32_t pend, pend2;
    uint32_t count, count2;

    ticks2  = SysTick->VAL;
    pend2   = !!((SCB->ICSR & SCB_ICSR_PENDSTSET_Msk)||((SCB->SHCSR & SCB_SHCSR_SYSTICKACT_Msk)))  ;
    count2  = GetTickCount();

    do {
        ticks=ticks2;
        pend=pend2;
        count=count2;
        ticks2  = SysTick->VAL;
        pend2   = !!((SCB->ICSR & SCB_ICSR_PENDSTSET_Msk)||((SCB->SHCSR & SCB_SHCSR_SYSTICKACT_Msk)))  ;
        count2  = GetTickCount();
    } while ((pend != pend2) || (count != count2) || (ticks < ticks2));

    return ((count+pend) * 1000) + (((SysTick->LOAD  - ticks)*(1048576/(F_CPU/1000000)))>>20) ; 
    // this is an optimization to turn a runtime division into two compile-time divisions and 
    // a runtime multiplication and shift, saving a few cycles
}

Je n’ai jamais testé la précision, mais en principe de 4µS on devrait passer sous la µS.

compter le nombre d'impulsions du signal A entre deux impulsions de signal B

Bonjour,

c'est l'indication pour l'utilisation du module capture (voir le datasheet du 328p) : le signal A est l'horloge du timer1 prise sur l'entrée T1 le signal B est l'entrée capture ICP1 de ce même timer

l'irruption du signal B fait mémoriser instantanément le continu du timer1 dans une paire de registres dédiés

après, selon le détail de ton fonctionnement que je n'ai pas totalement saisi, il faut remettre le timer1 à 0 ou soustraire l'ancienne capture de la nouvelle pour avoir le nombre d'impulsions écoulé

cependant je ne sais pas si la bibliothèque idoine existe, il faut peut-être coder manuellement dans les registres

bilbo83: Je suppose que tu travaille avec une MEGA2560 ou une UNO ou équivalent travaillant à 16MHz. Sur la DUE à 84MHz, il y a un compteur indépendant "SysTick" utilisé par la fonction micros(). Celle-ci est interruptible, c'est écrit dans le code source:

Je travaille avec une Uno, j'ai oublié de préciser. Ça signifie quoi que la fonction micros() soit interruptible ? C'est la première fois que j'utilise des interruptions, d'ordinaire je fais plutôt clignoter des DEL ou bouger des servomoteurs..!

pepe: Je pense important de connaître les traitements (calculs, affichage, transmission, commande de sorties) qui devront accompagner ces mesures, afin d'avoir une idée des contraintes de réalisation.

Côté logiciel, les mesures effectuées doivent être lues et la polarité de la capture reconfigurée avant la survenue du front suivant.

La principale difficulté résiderait donc dans une mesure logicielle suffisamment précise d'un second signal, parallèlement à l'acquisition du premier signal et au traitement de l'ensemble des résultats... (une mesure à 0,25 µs près peut être espérée.)

On pourrait aussi envisager d'utiliser deux cartes pour faire les mesures, par exemple en adjoignant un petit Arduino Pro Mini à l'Arduino principal, et en les faisant communiquer par l'I2C.

Afficher les résultats dans le moniteur série de l'IDE serait déjà une bonne chose, sinon j'envisageais d'envoyer ces informations en temps réel à un Raspberry Pi en RX/TX pour un affichage dans une interface graphique en Python.

"La polarité de la capture reconfigurée avant la survenue du front suivant" je ne comprends pas trop cette histoire de polarité...

Toutes les 4,096 secondes, il y a en effet deux impulsions qui se chevauchent, donc matériellement j'imagine que ça ne peut pas être traité en simultané réel. Mais s'il y a un décalage de 0,25 µs, ça me va très bien. :)

J'ai également une Arduino Nano sous le coude, donc j'avais pensé à faire une carte par signal, mais comme l'idée était également de créer un shield perso sur une platine pour la Uno, ça risque de compliquer un peu les choses niveau place... :/

trimarco232: c'est l'indication pour l'utilisation du module capture (voir le datasheet du 328p) : le signal A est l'horloge du timer1 prise sur l'entrée T1 le signal B est l'entrée capture ICP1 de ce même timer

après, selon le détail de ton fonctionnement que je n'ai pas totalement saisi, il faut remettre le timer1 à 0 ou soustraire l'ancienne capture de la nouvelle pour avoir le nombre d'impulsions écoulé

J'avais essayé de modifier la valeur du registre CLKPR pour tenter d'affiner la précision de l'horloge, mais ce n'est visiblement pas aussi simple, et les 600 pages de la datasheet m'ont un peu refroidi ! Je vais aller en lire un peu plus sur ce que tu mentionnes. ;)

J'ai mis du temps avant d'obtenir un post que je pensais être clair, mais je me doute qu'il y a des choses qui ont dû m'échapper, donc n'hésitez pas si vous avez besoin de précisions ! :)


J'ai déjà grâce à vous quelques pistes pour me remettre au travail, donc merci à tous pour vos réponses ! :)

n'hésitez pas si vous avez besoin de précisions !

un petit dessin, même brouillon, les impulsions A, les impulsions B, quelques annotations ...

Bonjour,

Désolé pour la réponse tardive, je n’ai pas eu accès à Internet depuis un ordinateur depuis mon dernier message.

trimarco232:
un petit dessin, même brouillon, les impulsions A, les impulsions B, quelques annotations …

J’ai fait un petit schéma en pièce-jointe.

pepe:
L’affichage dans le moniteur de l’IDE des valeurs décimales des mesures représente un volume de données conséquent, qui impose un débit de transmission de 2 Mbit/s (16MHz/8), le débit immédiatement inférieur de 1Mbit/s étant insuffisant. Or, un débit aussi élevé est difficilement envisageable, compte tenu notamment du taux élevé d’erreurs de transmission attendu (désynchronisation entre l’ATmega328P et l’ATmega16U2, entre autres).

En comparaison, une transmission purement binaire vers un logiciel spécifique pourrait se contenter d’un débit de 166,6 kbit/s (16MHz/8/12).

La capture est effectuée sur un front de polarité déterminée (front montant ou front descendant). Après chaque front capturé, il faut préparer le système de capture pour réagir au front suivant, car il est nécessairement de polarité opposée.

Justement, j’ai avancé dans mon projet, et je me suis rendu compte que même si mon programme arrive à tout compter, l’affichage dans la console n’est pas régulier, et mes lignes s’affichent de 10 en 10.

16 MHz pour l’horloge de l’Arduino, 8 pour un octet j’imagine, mais à quoi correspond le 12 ? Edit : les 12 MHz du contrôleur USB ?

Ok, je comprends mieux cette histoire de polarité, et c’est ce que j’avais fait dans mon programme sans mettre de nom dessus alors :wink:


J’ai modifié mon code et obtenu un résultat fonctionnel, mais comme l’indique pepe dans son message, c’est l’affichage dans la console de l’IDE avec Serial.print() qui est à la ramasse… J’envisage désormais d’utiliser une liaison SPI pour envoyer toutes ces données à un Raspberry Pi qui s’occuperait de leur affichage, toujours en console avec du C ou en graphique avec du Python. Qu’en pensez-vous ?

Sachant qu’à la base je comptais faire le traitement sur Raspberry Pi avant de me rendre compte que l’OS n’étant pas temps réel, certaines données passaient régulièrement à la trappe. Du coup, est-ce que le Raspberry Pi “n’oublierait” pas également certaines de ces valeurs même en utilisant une liaison SPI ?


Mon code Arduino si vous souhaitez y jeter un œil. J’utilise la bibliothèque dont je parle dans mon premier post, qui offre une alternative à la fonction micros() précise à 1 µs. N’hésitez pas à me faire part de vos remarques et conseils ! :slight_smile:
(Je dois déjà virer quelques unsigned long pour des types plus adaptés…)

#include <eRCaGuy_Timer2_Counter.h>

#define pinA 2
#define pinB 3

volatile unsigned long ACount     = 0UL;
volatile unsigned long BCount     = 0UL;
unsigned long          ACountCopy = 0UL;
unsigned long          BCountCopy = 0UL;
unsigned long          AStartUs   = 0UL;
unsigned long          BStartUs   = 0UL;
unsigned long          AStopUs    = 0UL;
unsigned long          BStopUs    = 0UL;

void setup()
{

  timer2.setup();
  
  pinMode(pinA, INPUT_PULLUP);
  pinMode(pinB, INPUT_PULLUP);
  
  attachInterrupt(digitalPinToInterrupt(pinA), ARising, RISING);
  attachInterrupt(digitalPinToInterrupt(pinB), BRising, RISING);
  
  Serial.begin(230400);

}

void loop()
{
  
  noInterrupts();
  ACountCopy = ACount;
  BCountCopy = BCount;
  interrupts();
        
  Serial.print(F("B n°"));
  Serial.print(BCountCopy);
  Serial.print(F(" : "));
  Serial.print(BStopUs - BStartUs);
  Serial.print(F(" µs | A n°"));
  Serial.print(ACountCopy);
  Serial.print(F(" : "));
  Serial.print(AStopUs - AStartUs);
  Serial.println(F(" µs."));
     
}

void ARising()
{

  AStartUs = timer2.get_count() / 2UL;
  
  attachInterrupt(digitalPinToInterrupt(pinA), AFalling, FALLING);
  
}

void AFalling()
{

  AStopUs = timer2.get_count() / 2UL;

  ACount++;
  
  timer2.reset();

  attachInterrupt(digitalPinToInterrupt(pinA), ARising, RISING);

}

void BRising()
{

  BCount++;

  BStartUs = timer2.get_count() / 2UL;
  
  attachInterrupt(digitalPinToInterrupt(pinB), BFalling, FALLING);
  
}

void BFalling()
{

  BStopUs = timer2.get_count() / 2UL;
  
  timer2.reset();

  ACount = 0UL;

  attachInterrupt(digitalPinToInterrupt(pinB), BRising, RISING);

}

Plutôt que d'utiliser plusieurs cartes (UNO, PI, etc...) tu peux tout faire sur une seule avec une DUE. Tu programmes 2 timer capture (c'est hardware) et tu fais tes calculs dans la loop() ainsi que l'affichage via la liaison série ou sur un LCD. Et voila !

125 us sont l'équivalent de 10504 coups d'horloge à 84 MHz, ce qui laisse énormément de marge.

ard_newbie: Plutôt que d'utiliser plusieurs cartes (UNO, PI, etc...) tu peux tout faire sur une seule avec une DUE. Tu programmes 2 timer capture (c'est hardware) et tu fais tes calculs dans la loop() ainsi que l'affichage via la liaison série ou sur un LCD. Et voila !

125 us sont l'équivalent de 10504 coups d'horloge à 84 MHz, ce qui laisse énormément de marge.

Effectivement, c'est une bonne idée d'alternative ! Je garde ça dans un coin si je n'arrive pas à faire ce que je veux avec une Uno ! ;) Est-ce que c'est garanti qu'avec une Due je pourrais afficher en Serial.print() dans la console toutes mes informations sans perte ?

pepe: • Le 8 correspond au rapport entre la fréquence de l'horloge système du microcontrôleur et de la plus haute fréquence de l'horloge de son interface série.

Donc sur l'Arduino Uno ou Nano à 16 MHz, la plus haute fréquence de transmission des bits (diviseur f/1) est f = 16MHz/8 = 2 Mbit/s.

Mais pour envoyer un octet sur l'interface asynchrone, il faut au minimum 10 bits, car il est au minimum accompagné d'un bit de start et d'un bit de stop. Le débit maximum de données est donc de 200 koctet/s.

• Le 12 correspond au plus grand diviseur de l'horloge série (et donc au débit le plus faible) qui permettrait la transmission des données dont je parlais (f/12 = 2M/12 = 166,6 kbit/s).

Merci pour les explications détaillées, j'y vois un peu plus clair ! :)

pepe: Concernant la Raspberry Pi, son système matériel est de toute façon orienté "application", et implique d'utiliser des périphériques dédiés pour toutes les tâches exigeant à la fois vitesse et précision. Bien que le logiciel proposé par défaut soit capable de recevoir et de traiter des débits d'information beaucoup élevés que ceux dont il est question ici (USB, Ethernet, HDMI, etc.), il est possible qu'il soit inadapté pour prendre en charge spécifiquement la liaison SPI haute vitesse envisagée. Mais ne m'étant pas penché sur la question, je ne saurais dire si le problème est dû à une limite matérielle ou à une insuffisance du logiciel (OS ou applicatif).

Sur un Uno ou un Nano, pour réaliser la mesure d'un seul signal, l'appel à des fonctions logicielles est parfaitement pertinent. Mais la mesure de deux signaux simultanément ne reste envisageable que si les fronts ne surviennent que dans des configurations attendues (fronts quasi-simultanés sur les deux voies ou déphasage constant connu, par exemple), ce qui permettraient d'adapter le logiciel en conséquence, ou bien si l'on code un programme suffisamment performant en vitesse (interruptions et appels de fonctions proscrits, accès directs aux registres obligatoire, programmation en assembleur conseillée).

A la base, j'utilisais sur RPi un soft en C avec la bibliothèque WiringPi pour lire le signal et l'afficher dans la console, avant de me rendre compte que certaines valeurs étaient zappées : le compte d'impulsions était bon, mais pas leur affichage. Ai-je raison en affirmant que si le RPi n'était pas capable d'afficher avec précisions ces valeurs, il ne le sera pas plus même si j'utilise une liaison SPI ou I2C pour lui transmettre les données ?

Il n'y a qu'un seul moment où les lectures posent problèmes, c'est lors d'une impulsion du signal B, qui intervient en même temps qu'une impulsion du signal A. Pour régler ça, j'incrémente mon compteur pour le signal A sur les fronts descendants, et celui du signal B sur les fronts montants, de manière à ne pas tenter de faire deux mesures en même temps. Ça c'est la théorie, mais en vrai, est-ce que ça fait l'affaire ?


Merci pour vos réponses ! :)