Go Down

Topic: lancement de fonctions en différé (Read 4861 times) previous topic - next topic

Super_Cinci

Oct 17, 2015, 11:16 am Last Edit: Oct 24, 2015, 08:38 pm by Super_Cinci
Je partage mon code, car il me rend de très grands services. Si quelqu'un se sent d'en faire une lib, qu'il ne se gêne pas!

Qu'est-ce que ça fait?

Ca lance la fonction de son choix au bout d'un certain temps, sans bloquer le reste du code. Une bonne alternative à la fonction delay().

Ca monopolise quoi?

Le timer 2, donc toutes les libs temporelles utilisant le timer2 seront en conflit (lib servo par exemple).

Utilisation : on copie ce code dans le sketch (de préférence au début)

Code: [Select]

/************************
 *  Lancements retardés *
 ************************/ 
 
/********************************************************
 * Permet de lancer une void au bout d'un certain temps *
 *  exprimé en millisecondes (max 65 535 ms, soit 1min et 5 sec).*
 ********************************************************/
// il est possible d'augmenter le temps max en déclarant ms et T2_cmpt en long
// (on atteint alors plus de 4 milliards de ms, soit plus de 1 110 heures...)


#include <arduino.h>

#ifdef __cplusplus
extern "C"{
#endif
  typedef void (*voidFuncPtr)(void);
#ifdef __cplusplus
} // extern "C"
#endif

static volatile voidFuncPtr launch_void;  // fonction à lancer
volatile word T2_cmpt;

void delay_void(word ms, void (*userFunc)(void)) {
    launch_void = userFunc;  // prise en compte de la fonction à utiliser
    TCCR2A = 0x02; // mode CTC sur OCR2A
    TIMSK2 = 0x02; // autoriser int OC2A
    OCR2A = 125;   // 125 x 8µs = 1ms
    TCNT2 = 0;               // RAZ timer
    T2_cmpt = ms;            // initialiser le décompte
    TCCR2B = 0x05;           // lancer le timer 2
}

ISR(TIMER2_COMPA_vect) {  // interruption appelée toutes les 1ms
  T2_cmpt--;
  if(T2_cmpt == 0) {  // on est arrivé au bout du décompte?
    TCCR2B = 0x00;  // arrêter le timer
    if(launch_void) launch_void();  // lancer la void
    launch_void = 0;
  }
}



dans le sketch, il suffit d'appeler la void delay_void(), par exemple :

Code: [Select]

void maFonction() {
  digitalWrite(13, HIGH); // allumer la led pin 13
}

void loop() {
  (...) faire des trucs
  if(bouton == appuyé) {
    digitalWrite(13, LOW);  // éteindre la led
    delay_void(10000, maFonction);  // allumera la led 13 dans exactement 10 secondes
  }
  (...) faire d'autres trucs
}

Lors de l'appui sur le bouton, ça éteindra la led si elle est déjà allumée, ça déclenchera la minuterie et le reste du code continuera à tourner (faire d'autres trucs, puis faire des trucs). Au bout de 10 secondes, maFonction() sera exécutée, donc la led s'allumera toute seule.

Je l'utilise pour faire du débounce sur un clavier : dans la fonction qui lit les touches du clavier, ça interdit toute prise en compte des évènements du clavier, ça lance la minuterie et ça retourne direct à la suite du programme. au bout de cette minuterie, la fonction se contente de réactiver le clavier, alors que le reste du programme a déjà repris son cours. Perso, je suis très fier de moi!

EDIT du 24/10/2015 : erreur au niveau de delay_void(), corrigé.

Artouste


Super_Cinci

bonjour S5
Pakon ! :smiley-mr-green:

Merci.

On pourrait aller plus loin, en utilisant un tableau de fonctions comme dans la fonction attachInterrupt() (c'est d'ailleurs là que j'ai piqué une partie du code), ce qui permettrait alors de lancer plusieurs minuteurs avec la même fonction, car tel-quel, il est difficile de gérer plusieurs départs différés.

Je viens de programmer un petit truc où je l'utilise pour le débounce clavier, mais aussi en générateur de time_out pour un MCC qui déplace un charriot. Le souci, c'est que pour utiliser le time out, il faut nécessairement désactiver le clavier pendant ce temps-là, sous peine d'avoir un beau cafouillage si on appuie sur une touche pendant le décompte.

Je vais peut-être réfléchir à la question...

Artouste

Merci.

On pourrait aller plus loin, en utilisant un tableau de fonctions comme dans la fonction attachInterrupt() (c'est d'ailleurs là que j'ai piqué une partie du code), ce qui permettrait alors de lancer plusieurs minuteurs avec la même fonction, car tel-quel, il est difficile de gérer plusieurs départs différés.

Je viens de programmer un petit truc où je l'utilise pour le débounce clavier, mais aussi en générateur de time_out pour un MCC qui déplace un charriot. Le souci, c'est que pour utiliser le time out, il faut nécessairement désactiver le clavier pendant ce temps-là, sous peine d'avoir un beau cafouillage si on appuie sur une touche pendant le décompte.

Je vais peut-être réfléchir à la question...
comme ça à chaud
ajouter un parametre de repetition ?
avec 1 fois par defaut

Super_Cinci

Oui, pourquoi pas, 1 fois par défaut, et si on le met à 0, répétition infinie...

J'ai finalement commencé à mettre un tableau pour lancer plusieurs comptes à rebours, et j'arrive à une prog pas belle du tout. Je suis donc en train d'essayer de comprendre comment déclarer une structure et pouvoir utiliser l'instruction "with", si tant est qu'elle existe en C, j'en ai un vague souvenir dans le basic...

Je voudrais donc transformer

Code: [Select]
#define T2_max_cmpts 3  // 4 compteurs

volatile voidFuncPtr launch_void[T2_max_cmpts+1];  // fonction à lancer
volatile word T2_cmpt[T2_max_cmpts+1];  // compteurs
volatile boolean T2_flag[T2_max_cmpts+1];  // flag d'utilisation des compteurs

ISR(TIMER2_COMPA_vect) {  // int appelée toutes les 4ms
  sei();
  boolean T2_on = false;
  for (byte i=T2_max_cmpts; i>0; i--){
    T2_on = T2_on || T2_flag[i];
    if(T2_flag[i]){
      T2_cmpt[i]--;
      if(T2_cmpt[i] == 0) {
        T2_flag[i] = false;
        if(launch_void[i]) launch_void[i]();  // lancer la void
      }
    }
  }
  if(!T2_on) TCCR2B = 0x00;  // arrêter le timer
}


en

Code: [Select]

#define T2_max_cmpts 3  // 4 compteurs

typedef struct {
  voidFuncPtr launch_void;  // fonction à lancer
  word cmpt;  // compteurs
  boolean flag;  // flag d'utilisation des compteurs
} T2_void_struct;

volatile T2_void_struct T2_tab[T2_max_cmpts+1];

ISR(TIMER2_COMPA_vect) {  // int appelée toutes les 4ms
  sei();
  boolean T2_on = false;
  for (byte i=T2_max_cmpts; i>0; i--){
    with T2_tab[i] {                    // utiliser la table d'index i
      T2_on = T2_on || flag;
      if(flag){
        cmpt--;
        if(cmpt == 0) {
          flag = false;
          if(launch_void) launch_void();  // lancer la void
        }
      }
    }
  }
  if(!T2_on) TCCR2B = 0x00;  // arrêter le timer
}
Mais là, comme ça, je doute que ça marche, je dois avoir deux ou trois trucs à apprendre sur ce coup là!

B@tto

Blog électronique : battomicro.wordpress.com
Photographie : www.interactive-celebration.fr
Fablab de Montpellier : www.labsud.org

XavierMiller

#6
Oct 19, 2015, 01:27 pm Last Edit: Oct 19, 2015, 01:28 pm by XavierMiller
Et via des listes chaînées contenant des pointeurs de fonctions ?

Super_Cinci

Salut,

Ca ressemble pas mal à ça en version librairie : https://www.pjrc.com/teensy/td_libs_TimerOne.html
Oui, c'est un peu le même genre.

J'ai modifié ma première version pour passer en tableaux, mais pas encore essayé. Pour ne pas pourrir le temps d'exécution, j'ai augmenté la résolution du timer à 4ms. on est donc moins précis, mais ça laisse le temps de lancer quelques petites fonctions...

Et via des listes chaînées contenant des pointeurs de fonctions ?
encore des gros mots! Sérieusement, je suis totalement autodidacte en C / CPP. si niveau pointeurs, je sais de quoi ça parle, les listes chaînées, va falloir que je me renseigne... OK, c'est fait. Pas con dans le principe, mais très gourmand en mémoire et assez lent à l'exécution, donc mauvaise idée pour un traitement dans une interruption.

Tout en restant dans les structures, j'ai cherché partout le "with", mais il ne semble pas exister, bien que je crois l'avoir vu traîner un jour sur la toile... En même temps, c'est un mot courant, donc pour cibler la recherche, pas facile.

Je me dis qu'utiliser des tableaux doit être assez lent, car il faut à chaque fois charger l'adresse du tableau, puis se décaler de i, pas top. Ecrit en ASM, par contre, on aurait moyen de faire ça beaucoup plus vite...

n'oublions pas que 1ms, c'est 16 000 instructions, et que si une autre interruption s'invite pendant ce temps-là (car j'autorise les int, il peut y en avoir de très importantes qu'il ne faut absolument pas louper), ça pourrait devenir critique...

XavierMiller


Super_Cinci

le with n'existe pas en C/C++
Ah, ben voilà une réponse claire et utile! merci!

Je ne propose donc pas (pour l'instant) la version "multi-void", puisqu'elle est trop lente (j'entends mal codée) à mon goût. Il faudrait que je regarde ce que ça donne, une fois compilé et décodé en assembleur, ça pourrait être une base de travail intéressante pour réécrire en assembleur et simplifier le traitement!

Super_Cinci

Bon, petit test fait, l'interruption dans mon premier post prend 70 instructions (pas loin d'une dizaine de µs), contre 102 dans le mode "multi". Mais je n'ai pas calculé le temps que ça peut prendre en multi, il y a des boucles... Il va falloir uploader et mesurer à l'oscilloscope...

XavierMiller

#11
Oct 22, 2015, 03:09 pm Last Edit: Oct 22, 2015, 03:09 pm by XavierMiller
Tu pourrais t'inspirer de l'implémentation de atexit(), qui "amasse" des fonctions à exécuter en cas d' exit() en les chaînant

Mais comme tu dis, boucler (ou passer d'un pointeur à un autre) prend quelques cycles.

Super_Cinci

J'ai commencé à traduire un peu l'assembleur obtenu. Pas facile quand on n'a aucun nom de variables, juste des adresses mémoire...

Il y a une instruction intéressante : "LDD Rd,Z+k" qui charge dans le registre Rd le contenu à l'adresse mémoire (Z+k) en 2 cycles d'horloge. Z pourrait alors être l'adresse du début de mon tableau, et je pourrais accéder à n'importe quelle donnée en direct, dans la limite de k=255 bien sûr (une centaine de word, on a de quoi voir venir). Avec ça, je ferais des merveilles sans boucle et avec seulement 5 ou 6 registres, mais intégrer de l'assembleur dans du C, faut savoir où on va, et là, je ne m'y connais pas assez... Ceci dit, j'ai presque déjà mon interruption en ASM dans ma tête, et ça a l'air drôlement bien optimisé...

Mais si je m'écoutais, je ne programmerais plus qu'en assembleur...

Dans l'ASM pondu par GCC, il y a 20 octets en push + pop, soit déjà 80 cycles CPU (5µs) juste pour la sauvegarde des registres!

Super_Cinci

Bon, là, je tombe sur le c*l! je lis dans quelques docs techniques AVR :

Quote
This is in contrast to the C compiler that uses the C type int by default in order to calculate constant integer expressions.
à traduire par
"Ceci est différent du compilateur C qui utilise le type int par défaut afin d'évaluer les constantes entières".

Ca veut dire que dans "for (byte i=0, i<3, i++){}", i (un entier 8 bits à la base) sera converti en int (16 bits signé), car 3 est une constante et que par défaut, une constante est un int 16 bits signé. Ca veut dire que pour la comparaison "i<3" et l'incrémentation "i++", ça se fera en 16 bits signé. Je comprends maintenant pourquoi je trouve partout dans mon code asm des calculs en 16 bits avec des conversions signées qui prennent 6 cycles d'horloge, alors que finalement, en 1 cycle, on fait la même opération en 8 bits... Je sens qu'il y a de l'optimisation à faire! ou alors travailler avec un AVR 16 bits...

peut-être que faire un "for (byte i=(byte)0, i<(byte)3, i++){}" résoudrait la chose, mais bonjour l'illisibilité du programme...

fifi82

sacré boulot en tout cas, mais c'est sûre que le compillo n'optimise pas tout en fait j'utilise pas mal les byte au lieu de int quand cela est nécessaire mais ça sert à rien vu que les comparaisons sont sur des int ! je ne pige pas tout encore en C moi lol

Go Up