Comment caractériser un traitement numérique ?

NIVEAU 2

Objectifs

  • Caractériser un système numérique
  • Mesurer le temps de calcul d’un traitement numérique

Pré-requis

Caractérisation d’un système numérique

Un système numérique est un système qui traite des données numériques d’entrée pour ensuite calculer des commandes à appliquer sur des sorties numériques pour réaliser la tâche qui lui est associée. La plupart du temps, il est au coeur d’un système embarqué, incluant également des capteurs pour récupérer des informations de son environnement et des actionneurs pour pouvoir agir sur ce dernier en fonction de la tâche qu’il doit réaliser (voir figure ci-dessous).

Des étages de conversions (analogique-numérique en entrée et numérique-analogique en sortie) permettent de faire le lien entre le monde analogique qui nous entoure et le calculateur numérique du système.

Modélisation / Caractérisation

Dans tous les cas, un système numérique (filtre numérique, pilotage d’un système embarqué…) peut être modélisé comme le montre la figure suivante :

Quelque soit le système, pour le caractériser, il faut connaître :

  • son algorithme de traitement, souvent noté \(H(n)\) pour un système numérique (qui peut être non-linéaire), c’est à dire la relation entre les entrées et les sorties (dans le monde analogique, cette fonction de transfert est souvent associée à la réponse en fréquence du système)
  • son temps de réponse, noté \(t_r\), c’est à dire le temps que met le système à réagir à une variation de ses entrées

RemarqueLorsque le traitement numérique est linéaire, le système est appelé filtre numérique et l’algorithme peut être décrit par une fonction de transfert (transformée en z).

Traitements numériques

Ce type de système doit souvent répondre à des sollicitations extérieures (prise d’information sur des capteurs…) le plus rapidement possible (systèmes dits temps réel). Pour cela, le programme embarqué dans un tel système utilise des instructions qui prennent chacune un certain temps pour être exécuté.

Il est intéressant de pouvoir mesurer le temps de calcul d’une chaîne de traitement numérique afin de pouvoir caractériser son système, en particulier pour connaître le temps de réponse à une sollicitation extérieure.

Mesure de temps de calcul

La mesure du temps de calcul des traitements numériques associés à l’application n’est pas simple. Le système étant prévu pour travailler en autonomie, il n’y a pas d’accès direct aux informations qu’il stocke.

Méthode

Pour pouvoir connaître le temps que prend un certain bloc d’instructions pour être exécuté sur le microcontroleur, on peut utiliser une sortie numérique supplémentaire que l’on pourra mettre à jour aux instants qui nous intéressent.

Dans la figure précédente, où l’on souhaite mesurer le temps d’exécution T2 du bloc d’instructions noté 2, on ajoute une instruction qui vient mettre à ‘1’ la sortie out avant ce bloc et une autre instruction qui vient mettre à ‘0’ la sortie out. En mesurant alors le temps de passage à l’état haut de cette sortie à l’aide d’un oscilloscope, on peut connaitre la durée d’exécution du bloc 2.

Attention Il faut cependant faire attention avec cette méthode car le temps d’exécution de la mise à jour de la sortie out peut ne pas être négligeable par rapport au temps de calcul du bloc à mesurer. Il peut donc être intéressant, avant de réaliser la mesure précédente, de mesurer le temps d’exécution des instructions permettant de mettre à ‘1’ puis à ‘0’ la sortie out.

Par la suite, il faudra alors retrancher cette valeur (notée \(\Delta{}T\) dans l’exemple précédent) à la mesure réalisée de l’exécution du bloc 2.

Exemple

Dans l’exemple complet donné ici, on s’intéresse au temps que met le microcontroleur à réaliser la lecture d’une donnée analogique (voir tutoriel Récupérer un signal analogique).

#include "mbed.h"

AnalogIn analog_value(A0);            // Entrée Analogique
DigitalOut out(PA_4,0);               // Sortie Numérique utilisée comme indicateur déclaré à 0.

int main()
{
    int meas;

    while(1) {
        out=1;                          // Changement d'état de l'indicateur
        meas = analog_value.read_u16(); // Convertit et lit la tension d'entée analogique (valeur entre 0 et 65535)
        out=0;                          // Changement d'état de l'indicateur
    }
}

La broche PA_4 du composant est configurée en sortie, nommée out dans la suite du programme. Dans le boucle principale, l’instruction dont on cherche à connaître le temps d’exécution est entourée des instructions out = 1 et out = 0, permettant respectivement de mettre à ‘1’ cette sortie et à ‘0’. En visualisant à l’oscilloscope le signal sur la sortie out (ou la broche PA_4), on obtient un signal périodique puisque les instructions permettant de modifier la sortie out sont inclus dans la boucle infinie.

Dans cet exemple, on en déduit que le microcontroleur met environ 13µs (temps de passage à l’état ‘1’) pour effectuer la lecture de la donnée analogique et la convertir en données numériques (échelle des temps en microsecondes). A cela, il faudra déduire le temps d’exécution des instructions out = 1 et out = 0, obtenu en réalisant une seconde mesure de l’état haut de cette même sortie out à l’aide du programme suivant :

#include "mbed.h"

AnalogIn analog_value(A0);            // Entrée Analogique
DigitalOut out(PA_4,0);               // Sortie Numérique utilisée comme indicateur déclaré à 0.

int main()
{
    int meas;

    while(1) {
        out=1;                          // Changement d'état de l'indicateur
        out=0;                          // Changement d'état de l'indicateur
        meas = analog_value.read_u16(); // Convertit et lit la tension d'entée analogique (valeur entre 0 et 65535)
    }
}

On trouve par exemple ici que le temps d’exécution de la partie mesure prend quelques centaines de nanosecondes (échelle des temps en microsecondes). On peut donc déduire à présent que la lecture et la conversion d’une donnée analogique par l’instruction read_u16() prend 13µs.

Mesure avec un Timer

Qu’est-ce qu’un timer ?

Les microcontroleurs ont besoin de gérer au mieux l’aspect temporel, afin de garantir de pouvoir réaliser des actions dans des temps donnés. Pour cela, ils intègrent des modules indépendants de l’unité de calcul qu’on appelle Timer. Ces modules sont en quelque sorte comme une fonction “chronomètre”. Ils permettent de connaître le temps qui s’est écoulé entre deux instants.

Ces modules Timer sont basés sur un compteur interne au microcontroleur, sur lequel on peut modifier la fréquence d’entrée à laquelle ce compteur s’incrémente. Ainsi, il est possible de déterminer le temps qu’il s’est écoulé entre un instant \(t_0\) où ce compteur aura une valeur \(N_0\) et un instant \(t_1\) où ce compteur sera arrivé à une valeur \(N_1\).

Dans l’exemple précédent, on peut déduire \(\Delta{}t = t_1 – t_0\). En effet, chaque pas d’incrémentation a pour durée la période du signal d’horloge associée au module Timer. Ainsi, \(\Delta{}t = (N_1 – N_0) \cdot T_H\) (où \(T_H\) est la période du signal d’horloge).

Méthode de mesure

C’est à partir de l’un de ces modules de Timer (au nombre de 3 différents sur les cartes Nucléo L476RG) que se base la méthode proposée ici.

Pour utiliser cette méthode, il est nécessaire d’utiliser un affichage distant via la liaison série (voir tutoriel Déboguer son programme et utiliser l’affichage série).

Déclaration du Timer

Une fois que l’affichage distant a été configuré, il est nécessaire de déclarer le module Timer grâce à la ligne de code ci-dessous :

Timer t;

Initialisation du Timer

Par la suite, il faut l’initialiser avec la ligne de code suivante.

t.reset();

Utilisation du Timer

Enfin, il s’utilise de la façon suivante :

t.start();
... // bloc à caractériser
t.stop();

Lecture du Timer

Une fois le module Timer démarré (start) puis arrêté (stop), il est intéressant de pouvoir lire la valeur obtenue entre ces deux instants. Pour cela, il existe une fonction nommée read() qui retourne un nombre réel correspondant au nombre de secondes écoulées.

A travers la liaison série, il est possible de l’afficher de cette façon :

float t_ecoule = t.read();
pc.printf("The time taken was %f seconds\n", t_ecoule);

N.B. Il existe deux autres fonctions de lecture read_ms() et read_us() qui renvoie un nombre entier correspondant respectivement au nombre de millisecondes écoulées et au nombre de microsecondes écoulées.

Exemple

Voici ci-dessous un exemple complet de mesure de temps par Timer.

#include "mbed.h"
Timer t;
Serial pc(SERIAL_TX, SERIAL_RX);
//------------------------------------
// Hyperterminal configuration
// 9600 bauds, 8-bit data, no parity
//------------------------------------


int main()
{
    while(1) {
        t.reset();
        t.start();
        pc.printf("Hello World!\n");
        t.stop();
        pc.printf("The time taken was %f seconds\n", t.read());
        pc.printf("The time taken was %d milliseconds\n", t.read_ms());
        pc.printf("The time taken was %d µseconds\n", t.read_us());
    }
}

Dans la console, on obtient des messages de ce type :

Tutoriel lié

MInE Prototyper Prototyper avec Nucleo et MBED

Nucléo – Caractériser un traitement numérique