Debian logo [embedded-µC.LINUX]

12. Contraintes de temps sur les sorties numériques

Même si les microcontrôleurs de la famille MSC12xx ne peuvent rivaliser avec les composants logiques programmables spécialisés (FPGAs, etc.), il est utile d'évaluer précisément les performances en fréquence que l'on peut obtenir sur les sorties numériques.

Cette section traite de l'utilisation des ports «classiques» du microcontrôleur. Les périphériques spécialisés tels que les timers ou le port SPI font l'objet de sections particulières.

On présente donc un exemple de programme qui permet d'évaluer les différents types de temporisations logicielles ainsi que le temps d'exécution des différentes boucles du Langage C. Le code source du programme complet est disponible sous le nom : sdcc_port_timings.c.

Pour chaque exemple de temporisation logicielle, on présente le code en Langage C, le code assembleur correspondant ainsi que les mesures de temps pour un appel unique et pour une exécution supplémentaire.

Remarque importante : les mesures temps ont été réalisées à l'aide d'un microcontrôleur MSC1210Y5 cadencé à 22.1184MHz.

12.1. Temps d'exécution d'une instruction nop

L'instruction nop (acronyme de no operation) est une instruction assembleur dont le seul but est de gaspiller un cycle d'horloge du microcontrôleur. En appelant cette instruction directement à partir du langage C on doit obtenir le plus petit temps d'exécution possible.

  • Codage en Langage C :

    #define nop()   {_asm nop _endasm;}
    
    TEST_PIN = 0;
    nop();
    TEST_PIN = 1;
    
  • Code assembleur produit par le compilateur sdcc :

    ;       sdcc_port_timings.c:79: TEST_PIN = 0;
    clr     _TEST_PIN
    ;       sdcc_port_timings.c:80: _asm nop _endasm;
    nop
    ;       sdcc_port_timings.c:81: TEST_PIN = 1;
    setb    _TEST_PIN
    
  • Temps mesuré à l'analyseur logique :

12.2. Temps d'exécution d'une temporisation codée en assembleur en ligne

Après avoir mesuré le plus petit temps d'exécution, on utilise une boucle de type do-while codée en assembleur et insérée dans le code source du programme en langage C. Cette technique, baptisée assembleur en ligne, est sensée optimiser la compilation relativement au code généré par le compilateur. Si l'optimisation manuelle est efficace, le temps d'exécution sera plus réduit qu'avec un programme écrit en langage C.

  • Codage en Langage C :

    void inline_delay (unsigned char n) __naked {
            n;
            _asm
            mov     r0, dpl
    00001$: nop
            djnz    r0, 00001$
            ret
            _endasm;
    }
    
    // dans le programme principal
    
    ser_puts("Temporisation assembleur en ligne: ");
    ser_gets_echo(chaine, MAXCHAR);
    v = atoi(chaine);
    TEST_PIN = 0;
    inline_delay(v);
    TEST_PIN = 1;
    
  • Code assembleur produit par le compilateur sdcc :

    ;------------------------------------------------------------
    ;Allocation info for local variables in function 'inline_delay'
    ;------------------------------------------------------------
    ;n                         Allocated to registers
    ;------------------------------------------------------------
    ;       sdcc_port_timings.c:37: void inline_delay (unsigned char n) __naked {
    ;       -----------------------------------------
    ;        function inline_delay
    ;       -----------------------------------------
    _inline_delay:
    ;       sdcc_port_timings.c:44: _endasm;
    
                    mov r0, dpl
            00001$:
                    nop
                    djnz r0, 00001$
                    ret
    ;       naked function: no epilogue.
    
    // dans le programme principal
    
    ;       sdcc_port_timings.c:98: TEST_PIN = 0;
            clr     _TEST_PIN
    ;       sdcc_port_timings.c:99: inline_delay(v);
            lcall   _inline_delay
    ;       sdcc_port_timings.c:100: TEST_PIN = 1;
            setb    _TEST_PIN
    
  • Temps mesuré à l'analyseur logique :

    L'appel du sous-programme inline_delay(n) dure 2.890µs et l'incrémentation d'une unité de la variable n ajoute 720ns au temps d'exécution du sous-programme.

12.3. Temps d'exécution d'une temporisation basée sur une boucle tant-que

À partir de cette étape, on n'utilise plus d'artifice d'optimisation et on s'appuie entièrement sur le travail du compilateur. Une boucle while doit avoir un temps d'exécution plus court que les autres types de boucles sachant qu'elle utilise moins d'opérations.

  • Codage en Langage C :

    void while_delay (unsigned char n) {
            while (n--)
                    ;
    }
    
    // dans le programme principal
    
    ser_puts("Temporisation boucle tantque: ");
    ser_gets_echo(chaine, MAXCHAR);
    v = atoi(chaine);
    TEST_PIN = 0;
    while_delay(v);
    TEST_PIN = 1;
    
  • Code assembleur produit par le compilateur sdcc :

    ;------------------------------------------------------------
    ;Allocation info for local variables in function 'while_delay'
    ;------------------------------------------------------------
    ;n                         Allocated to registers r2
    ;------------------------------------------------------------
    ;       sdcc_port_timings.c:47: void while_delay (unsigned char n) {
    ;       -----------------------------------------
    ;        function while_delay
    ;       -----------------------------------------
    _while_delay:
            mov     r2,dpl
    ;       sdcc_port_timings.c:48: while (n--)
    00101$:
            mov     ar3,r2
            dec     r2
            mov     a,r3
            jnz     00101$
            ret
    
    // dans le programme principal
    
    ;       sdcc_port_timings.c:114: TEST_PIN = 0;
            clr     _TEST_PIN
    ;       sdcc_port_timings.c:115: while_delay(v);
            mov     dpl,r2
            lcall   _while_delay
    ;       sdcc_port_timings.c:116: TEST_PIN = 1;
            setb    _TEST_PIN
    
  • Temps mesuré à l'analyseur logique :

    L'appel du sous-programme while_delay(n) dure 5.040µs et l'incrémentation d'une unité de la variable n ajoute 1.280µs au temps d'exécution du sous-programme.

12.4. Temps d'exécution d'une temporisation basée sur une boucle pour

Comme dans le cas précédent, on s'appuie entièrement sur le compilateur. Sachant qu'une boucle for effectue davantage d'opérations relativement à la boucle while, le temps d'exécution doit être plus important que dans tous les cas précédents.

  • Codage en Langage C :

    void for_delay (unsigned char n) {
    
            __idata unsigned char i;
    
            for (i = 0; i < n; i++)
                    ;
    }
    
    // dans le programme principal
    
    ser_puts("Temporisation boucle tantque: ");
    ser_gets_echo(chaine, MAXCHAR);
    v = atoi(chaine);
    TEST_PIN = 0;
    for_delay(v);
    TEST_PIN = 1;
    
  • Code assembleur produit par le compilateur sdcc :

    ;------------------------------------------------------------
    ;Allocation info for local variables in function 'for_delay'
    ;------------------------------------------------------------
    ;i                         Allocated to registers r3
    ;n                         Allocated to registers r2
    ;------------------------------------------------------------
    ;       sdcc_port_timings.c:55: void for_delay (unsigned char n) {
    ;       -----------------------------------------
    ;        function for_delay
    ;       -----------------------------------------
    _for_delay:
            mov     r2,dpl
    ;       sdcc_port_timings.c:59: for (i = 0; i < n; i++)
            mov     r3,#0x00
    00101$:
            clr     c
            mov     a,r3
            subb    a,r2
            jnc     00105$
            inc     r3
            sjmp    00101$
    00105$:
            ret
    
    // dans le programme principal
    
    ;       sdcc_port_timings.c:136: TEST_PIN = 0;
            clr     _TEST_PIN
    ;       sdcc_port_timings.c:137: for_delay(v);
            mov     dpl,r2
            lcall   _for_delay
    ;       sdcc_port_timings.c:138: TEST_PIN = 1;
            setb    _TEST_PIN
    
  • Temps mesuré à l'analyseur logique :

    L'appel du sous-programme for_delay(n) dure 5.790µs et l'incrémentation d'une unité de la variable n ajoute 1.790µs au temps d'exécution du sous-programme.

12.5. Récapitulatif des temps d'exécution

Tableau 11. Tableau récapitulatif des temps d'exécution

Instruction 1er appel Appel suivant
nop 540ns idem
Boucle do-while assembleur en ligne 2.890µs 720ns
Boucle while en langage C 5.040µs 1.280µs
Boucle for en langage C 5.790µs 1.790µs