Plusieurs animations en même temps

Bonjour,

Tout est dans le titre... Je voudrais savoir s'il est possible de faire plusieurs animations de leds en même temps (plusieurs changements de valeur de plusieurs leds, indépendamment).
Dans mon programme je gère du DMX, la réception de données d'un capteur, et j'aimerais piloter quelques shiftregisters en plus de tout ça, sous forme d'animations cycliques...

J'utilise actuellement TimeScheduler... le DMX est dans ma Loop et la réception du capteur est dans le premier TimeScheduler.
J'ai codé des fonctions pour le DMX (notamment une fonction fade qui fait un fading des valeurs DMX en allant les chercher dans un tableau et en comparant avec la valeur demandé selon les canaux R V B L). L'ennui est que pendant que cette boucle fonctionne, je n'ai plus aucun contrôle sur le reste.
En gros dans mon code j'ai

Timer.start(2); // Capteur
anim_stop=0; // Dépend du capteur

while (anim_stop==0) {
fade(1, 255, 0, 0, 255, 2400); // N° Projecteur, R, V, B, L, temps total du fading
fade(1, 0, 0, 255, 255, 2300);
fade(1, 0, 255, 0, 255, 2500);
}

Dans ma boucle fade() j'ai mis une condition supplémentaire pour que si anim_stop=1 alors on met directement la valeur finale, ce qui permet de sauter toutes les fonctions fade en même temps, mais ça me parait comme étant du bricolage ^^
Et au passage, c'est en même temps que cette boucle (au sein de la boucle générale, et non celle du fade) que j'aimerais ajouter des animations.

Quelqu'un a une idée, une astuce ? =)

Merci !

C'est tout à fait faisable, mais cela nécessite probablement de coder différemment tes fonctions de base.

Là, si je comprends bien, tu as une fonction fade() qui, lorsqu'elle s'exécute, prend la main pendant une durée pénalisante pour les autres fonctionnalités.

Tu dois repenser ton code pour avoir des unités d'exécution plus petites.

Par exemple, je parierais que ta fonction fade() contient un delay() entre chaque palier.
Il vaut mieux fractionner son exécution, en utilisant des variables globales.
Et surtout, elle ne devrait contenir aucun delay().

On peut voir le code de ta fonction fade() ?

Bonjour,

Oui bien sûr, la voici :

void fade(int n, int r, int v, int b, int l, int t) {

float actuel_r=valeur_r(n);
float actuel_v=valeur_v(n);
float actuel_b=valeur_b(n);
float actuel_l=valeur_l(n);

int r_fini=0;
int v_fini=0;
int b_fini=0;
int l_fini=0;

float pas_r = (r-actuel_r)/t;
float pas_v = (v-actuel_v)/t;
float pas_b = (b-actuel_b)/t;
float pas_l = (l-actuel_l)/t;

while(int i=1) {
actuel_r=actuel_r+pas_r;
actuel_v=actuel_v+pas_v;
actuel_b=actuel_b+pas_b;
actuel_l=actuel_l+pas_l;

if ((actuel_r>=r && pas_r>=0) || anim_stop==1){
actuel_r=r;
r_fini=1;
}
if ((actuel_r<=r && pas_r<=0) || anim_stop==1){
actuel_r=r;
r_fini=1;
}

if ((actuel_v>=v && pas_v>=0) || anim_stop==1){
actuel_v=v;
v_fini=1;
}
if ((actuel_v<=v && pas_v<=0) || anim_stop==1){
actuel_v=v;
v_fini=1;
}

if ((actuel_b>=b && pas_b>=0) || anim_stop==1){
actuel_b=b;
b_fini=1;
}
if ((actuel_b<=b && pas_b<=0) || anim_stop==1){
actuel_b=b;
b_fini=1;
}

if ((actuel_l>=l && pas_l>=0) || anim_stop==1){
actuel_l=l;
l_fini=1;
}
if ((actuel_l<=l && pas_l<=0) || anim_stop==1){
actuel_l=l;
l_fini=1;
}

set_rvbl(n, int(actuel_r), int(actuel_v), int(actuel_b), int(actuel_l));

if(r_fini==1 && v_fini==1 && b_fini==1 && l_fini==1) {
//Serial.println("Fini :");
set_rvbl(n, r, v, b, l);
break;
}

delay(1);
}

}

Au delà de mon code, qui peut certainement être optimisé, je me demande comment peut-on gérer plusieurs animation simultanément, au niveau du code ?

Merci :slight_smile:

Oui on peut faire plusieurs choses en même temps avec un arduino, sans forcément utiliser des librairies complexes, mais il faut coder ses fonctions de manière adaptée.

Par exemple : j'ai deux fonctions A et B que je souhaite exécuter "en même temps"

Je découpe ma fonction A en a1+a2+a3+a4+a5
Je découpe ma fonction B en b1+b2+b3+b4+b5
J'exécute a1 puis b1 puis a2 puis b2 etc...

Quand je dis "je découpe", il ne s'agit pas simplement de découper le code source de manière linéaire.

La logique d'exécution serait plutôt :

  • j'initialise le contexte d'exécution de ma fonction A = un ensemble de variables qui définissent l'état de la fonction A à un instant donné
  • j'initialise le contexte d'exécution de ma fonction B
  • j'exécute un petit bout de ma fonction A en mettant à jour mes variables globales pour savoir où reprendre
  • j'exécute un petit bout de ma fonction B
  • je poursuis l'exécution de ma fonction A
  • je poursuis l'exécution de ma fonction B
  • etc.

Même un petit arduino tourne largement assez vite pour donner une impression de parallélisme sur un ensemble de fonctions.

Dans ton programme, ton fade() est géré de manière monolithique : il est non interruptible au bémol près de ton petit bricolage.

A la place, il te faudrait

  • une fonction d'initialisation, qui mémorise le délai et la valeur à atteindre
  • une fonction d'évaluation, qui regarde l'avancement du temps avec la fonction millis(), pour éventuellement faire un unique appel à ta fonction set_rvbl(). Cette fonction doit être appelée le plus souvent possible, mais intercalée avec d'autres fonctions du programme.

Tu pourrais ainsi gérer plusieurs fade simultanément, y compris sur des durées différentes.

Oui je vois le principe, globalement, mais il faudrait que je pose ça sur papier pour y voir plus clair.

Grosso modo mon désir aurait été de dire (c'est un exemple) :

  • à t0s je lance le fade du spot 1 pour 2 secondes;
  • à t=1s je lance le fade du spot 2 pour 1 seconde;
  • à t=2s je lance le fade du spot 3 pour 3 secondes

Mais du coup ça me pose un problème parce que ça devient ultra complexe, à savoir que je ne peux plus stocker mes valeurs finales dans des variables locales... ce qui est le cas ici.

J'ai imaginé ce principe pour le variateur :

  • une variable sert à dire combien de spots sont branchés
  • un premier dictionnaire référence le premier canal DMX du spot
  • un deuxième dictionnaire référence le type de spot associé au numéro
  • un troisième qui référence toutes les valeurs DMX (R, V, B, L) dans le même ordre
  • plusieurs autres références les concordances de canaux entre les différents types

Comme exemple avec 2 spots dont le premier est de type 1 ayant pour ses canaux locaux R, V, B, L et un deuxième de type deux ayant pour canal L, R, V B. Le premier spot est sur le canal 1 et le deuxième, par addition, sur le canal 5.
Il vient donc :
nombre de spot : 2
canaux : { 1, 5 }
types : { 1, 2 }
valeurs : {R1, V1, B1, L1, R2, V2, B2, L2} tous initialisés à 0 (ce ne sont pas des variables c'est pour rappeler)
type1 : {0, 1, 2, 3} // R V B L
type2 : {1, 2, 3, 0} // L R V B

(les types sont relatifs à l'ordre donné dans valeurs, comme ça il n'y a qu'une addition à faire à la fin; il me suffit de faire type1[0] ou type2[0 pour savoir où le canal rouge se trouve, après avoir additionné ce nombre au canal du spot).

La fonction valeur_r(n) va chercher le canal R du spot n°n et ainsi de suite pour les autres informations. Pour ça c'est simple il suffit de faire une addition entre le n° du canal du spot et +0 pour R, +1 pour V, +2 pour B, +3 pour L.

La fonction set_rvbl(n, r, v, b, l); va chercher le numéro du canal (global cette fois) en allant chercher le n° du canal du spot en fonction de n, et calcule ça en fonction du type (système d'addition aussi).

En clair si je me complique la vie à ce point c'est que j'aimerais que ce soit simple d'utilisation (cad ne pas créer une fonction par spot... Qu'il y ait juste le nombre de spots à indiquer, le canal et leur type). La méthode des dictionnaires me paraissait la plus appropriée parce qu'elle a l'avantage de ne pas nécessiter de créer 1000000 variables...

En fait plus je cherche et plus je me demande si je ne ferais pas mieux de faire un fichier texte sur une carte SD qui indiquerait la valeur de chaque canal à chaque instant et demander à l'arduino de lire ce fichier à vitesse constante. Il me suffirait de faire un programme sous Xcode avec des courbes d'automations pour générer le fichier automatiquement (sans quoi je peux m'amuser un moment!).

Mais la question des animations simultanées m'intéresse, indépendamment de la remarque que je viens de faire.

Voilà voilà merci :slight_smile:

Ce n'est pas si dur que ça, regarde :

Tu devrais pouvoir partir du code ci-dessous

int calculerValeur(int valeur_depart, int valeur_arrivee, unsigned long chrono_depart, unsigned long duree_totale)
//fonction standalone de calcul de progression temporelle
{
  unsigned long duree, x;
  int nouvelle_valeur;


  duree = millis() - chrono_depart;

  if (duree >= duree_totale)
  {
    nouvelle_valeur = valeur_arrivee;
  }
  else if (valeur_depart < valeur_arrivee)
  {
    x = ((((unsigned long)valeur_arrivee - valeur_depart) * duree) / duree_totale);
    nouvelle_valeur =  valeur_depart + x;
  }
  else
  {
    x = ((((unsigned long)valeur_depart - valeur_arrivee) * duree) / duree_totale);
    nouvelle_valeur = valeur_depart - x;
  }

  return nouvelle_valeur;
}

#define MAXN 5

unsigned long chrono_depart[MAXN];
unsigned long duree_totale[MAXN];
int valeur_depart[MAXN][4];
int valeur_arrivee[MAXN][4];
bool fade_en_cours[MAXN]; // init à false à la compil (enfin, espérons)

void fade(int n, int r, int v, int b, int l, int t)
{
//Enregistrement d'une demande de fade
  chrono_depart[n] = millis();
  duree_totale[n] = t;

  valeur_depart[n][0] = valeur_r(n);
  valeur_depart[n][1] = valeur_v(n);
  valeur_depart[n][2] = valeur_b(n);
  valeur_depart[n][3] = valeur_l(n);

  valeur_arrivee[n][0] = r;
  valeur_arrivee[n][1] = v;
  valeur_arrivee[n][2] = b;
  valeur_arrivee[n][3] = l;

  fade_en_cours[n] = true;
}

void exec_fade()
{
//evaluation des fade en cours
  int i, r,v,b,l;

  for (int i = 0; i<MAXN; i++)
  {
    if (fade_en_cours[i])
    {
      r = calculerValeur(valeur_depart[i][0], valeur_arrivee[i][0], chrono_depart[i], duree_totale[i]);
      v = calculerValeur(valeur_depart[i][1], valeur_arrivee[i][1], chrono_depart[i], duree_totale[i]);
      b = calculerValeur(valeur_depart[i][2], valeur_arrivee[i][2], chrono_depart[i], duree_totale[i]);
      l = calculerValeur(valeur_depart[i][3], valeur_arrivee[i][3], chrono_depart[i], duree_totale[i]);
      if (r != valeur_r(i) || v != valeur_v(i) || b != valeur_b(i) || l != valeur_l(i))
      {
        set_rvbl(i, r, v, b, l);
        if (r==valeur_arrivee[i][0] && v==valeur_arrivee[i][1] && b==valeur_arrivee[i][2] && l==valeur_arrivee[i][3])
        {
          fade_en_cours[i] = false;
        }
      }
    }
  }
}

Tu insères tout ce code dans ton programme, en remplacement de ton actuelle fonction fade().

Tu disposes à présent :

  • d'une fonction fade() qui a les mêmes paramètres d'appel que la précédente, mais qui ne fait qu'enregistrer la demande de fade et rend la main tout de suite.
  • d'une fonction exec_fade(), à appeler le plus souvent possible, et qui se chargera de faire avancer les fade sur tous les canaux en fonction du temps qui passe.

C'est une base de travail, y aura peut-être quelques ajustements à faire.

Pour appeler exec_fade() souvent, le plus simple (mais le plus moche) est de remplacer tous les delay(xxxx) de ton programme par des delay_fade(xxxx), avec

void delay_fade(unsigned long delai)
{
  unsigned long chrono = millis();
  while (millis() - chrono < delai) exec_fade();
}

NB : je suppose que ta fonction set_rvbl() ne contient aucun delay(). Si c'est le cas, il ne faut surtout pas les remplacer par un delay_fade(). Idem pour tes fonctions valeur_x().

De manière plus générale, si tu pars d'un programme déjà écrit, et que tu veux ajouter une ou plusieurs tâches de fond sans avoir à tout réécrire, la méthode est :

  1. Tu codes ta tâche de fond avec son jeu de variables globales, à l'image de exec_fade() ci-dessus
  2. Tu remplaces tous les delay() du programme par :
void delay_avec_taches_de_fond(unsigned long delai)
{
  static bool protection_appels_recursifs = false;

  if (protection_appels_recursifs)
  {
    delay(delai);
  }
  else
  {
    protection_appels_recursifs = true;

    unsigned long chrono = millis();
    while (millis() - chrono < delai)
    {
    //exécution des tâches de fond
      exec_fade();
    //...
    }

    protection_appels_recursifs = false;
  }  
}
  1. tu t'assures que toutes les boucles du programme qui durent longtemps, contiennent bien un petit delay de temps à autre.

Par exemple, si ton programme contient

while (digitalRead(pin) == HIGH)
{
...
}

Cette boucle peut durer très longtemps. Il convient de lui ajouter un petit delay_avec_taches_de_fond(1).

Ce n'est pas une solution miracle, l'exécution des tâches de fond peut être un peu saccadée selon le code général du programme.
Mais cela peut permettre de répondre aux besoins.

Il est préférable de penser la simultanéité des tâches dès le début du coding, en utilisant par exemple ceci

Salut !

Désolé de n'avoir pas pu répondre plus tôt, j'étais en prestation c'était un peu tendu ! Alors merci pour cet éclairage je comprends mieux ce que tu veux dire, et c'est aussi un peu comme ça que je le voyais.

Ceci dit, pourrais-tu juste m'expliquer le rôle de valeur_depart[n][0] ? Enfin je ne comprends pas la double [] (je n'ai jamais vu ça ^^)

Ça répond bien à ma première question concernant l'optimisation du temps d'exécution de la boucle principale, parce qu'ici la représentation graphique de l'automation est linéaire... Mais si je voulais faire des automations avec quelque chose de non linéaire ? Je veux dire par là, quelque chose qui ne pourrait pas passer par une opération mathématique applicable à chaque itération...

En Pure Data je fais ça très simplement en dessinant ma courbe de réponse et en demandant à l'arduino d'aller chercher la valeur n à instant t... Mais si je veux me passer de Pure Data et de la liaison Serial, est-ce qu'il y a un moyen de faire ce genre de chose ? L'une des solutions que je vois serait de passer par un tableau pour y stocker les valeurs ou les opérateurs et, par le même procédé que la fonction fade du dmx, opérer ce changement... Mais est-ce qu'une multiplication des tableaux ne risque pas de déstabiliser le comportement de l'arduino ? Je cherche vraiment une solution la plus fiable possible, c'est ça mon problème ! Si tu avais un conseil à me donner sur ce point... :slight_smile:

En tout cas merci !

Toto[x][y] est juste un tableau à deux dimensions.

La fonction proposée : int calculerValeur(int valeur_depart, int valeur_arrivee, unsigned long chrono_depart, unsigned long duree_totale);
calcule, par règle de trois, une simple progression linéaire à partir de la valeur de l'instant présent fournie par millis().

Rien ne t'empêche de coder une progression non linéaire.
Le prototype de fonction restera le même.

Tu peux même en coder plusieurs, et paramétrer laquelle utiliser en fonction du canal de fade.

"Déstabiliser l'arduino" ne veut pas dire grand chose.

#define MAXN 5

unsigned long chrono_depart[MAXN];
unsigned long duree_totale[MAXN];
int valeur_depart[MAXN][4];
int valeur_arrivee[MAXN][4];
bool fade_en_cours[MAXN];

occupe 4x5 + 4x5 + 2x5x4 + 2x5x4 + 1x5 = 125 octets de RAM

Cela reste peu par rapport à la RAM disponible, même sur une petite UNO.

bricoleau:
Rien ne t'empêche de coder une progression non linéaire.

Justement c'est là que ma question se pose. Ici tu utilises une règle de trois. Donc la fonction ne fait qu'effectuer la même fonction à chaque fois. Simplement, si je veux réaliser (c'est un simple exemple) une automation de ce type pour la led 1 :

Cette animation pour la led 2 :

Et pendant ce temps, gérer le DMX et rester disponible pour recevoir des données de capteurs (ce qui se passe en ce moment avec mon arduino mais avec l'ancienne fonction fade et une utilisation du Scheduler), il faudrait donc bien passer par un tableau, non ?

Ok

Lorsque je parlais d'un fade non linéaire, je pensais à une courbe qui pouvait quand même être exprimée avec une formule mathématique f(temps), genre une sinusoide, facile à coder.

Là, tes courbes ressemblent plutôt à une série discrète de valeurs quelconques, avec une progression linéaire entre chaque point.

Théoriquement il y a deux solutions :

  1. soit tu considères que chaque courbe est en réalité une succession de n fade linéaires que tu codes quel tels

Par exemple pour la courbe de ta led 1, enchaîner :
un fade de 0 à 1 sur une seconde
un fade de 1 à 1,5 sur deux secondes
un fade de 1,5 à 3 sur deux secondes
etc.

  1. soit effectivement tu stockes quelque part un tableau de constantes (x,y) qui représente les points de ta courbe.

Puis tu programmes la fonction calculerValeur(...) qui répond toujours au même prototype, et qui utilise ce tableau de constantes pour calculer la bonne valeur en fonction du temps.

Le tableau de constantes peut être laissé en RAM si pas trop gros, sinon en flash via l'instruction PROGMEM.

Et oui, quelle que soit la solution retenue, il est possible d'arriver à faire tout ça en parallèle sur plusieurs canaux, pendant le programme gère aussi autre chose.

bricoleau:
Lorsque je parlais d'un fade non linéaire, je pensais à une courbe qui pouvait quand même être exprimée avec une formule mathématique f(temps), genre une sinusoide, facile à coder.

Oui c'est ce que j'essayais d'expliquer en disant :

pierreko:
Mais si je voulais faire des automations avec quelque chose de non linéaire ? Je veux dire par là, quelque chose qui ne pourrait pas passer par une opération mathématique applicable à chaque itération...

C'est bien ce à quoi j'avais pensé, finalement !

Merci pour ton aide et tes conseils !

A la prochaine !

Merci pour tous ces conseils intéressants !
J'ai été confronté il y à peu de temps à des problématiques très similaires (avec une fonction fade() et du DMX).
Dés que j'ai un moment, je revoie mon code à la lumière de ces conseils.

En tout vas, j'ai adapté tout ça à mon sketch, et ça a l'air de fonctionner. Je pense que je vais quand même faire un programme sur MaxMSP ou xCode pour gérer l'automation, parce que c'est très abstrait de fonctionner en ligne de code. Et surtout, pour bosser en direct, c'est pas vraiment marrant de devoir revenir au début du programme pour en voir un passage. Sachant que tout ça n'est qu'un test, je passerai peut être par un boitier professionnel avec contacts secs pour le projet final étant donné que la partie DMX est vraiment ce qu'il y a de plus simple. Derrière ça j'ai plusieurs capteurs à gérer ainsi qu'un communication avec rétrocontrôle avec un ordinateur... De quoi m'amuser :slight_smile:

Si j'arrive à faire ce que je veux en terme d'automation je passerai ici poster ma solution !

Et sa va faire maintenant 1 ans et demi que pierreko tente du mieux qu'il peut de terminer sont projet.
La légende raconte qu'il est toujours dans sa cave à travailler d'arrache pied.
En tendant l'oreille, on peux de temps en temps entendre un rire emplie de folie et de frustration...
( désoler de déterrer ce topic juste pour ça mais j'étais trop tenter xp )
(ps: j'assume totalement! ^^')