Partage
  • Partager sur Facebook
  • Partager sur Twitter

[Arduino] équivalant "explode()" de PHP

Sujet résolu
    31 mai 2014 à 17:03:42

    Salut le monde qui sent (bon) la soudure !:)

    je cherche l'équivalant d'explode(PHP) pour découper un string dans Arduino.

    la chaîne ressemble à ceci :

    PARAM1|PARAM2|PARAM3(.....)


    l'idée serais donc de découper aux '|' pour extraire les PARAM(X), j'ai fait quelques tentative du coté de strtok(), sauf que j'ai encore du mal à piger comment cette function s'argumente, et du mal avec l'initialisation des variables (Merci PHP de ne pas m'avoir appris le typage des variables :colere2:), enfin bref, du coup, mon code brailles et comme je débute, je m’emmêle les pinceaux grave !:euh:

    exemple qui marche avec strtok :

    char str[] = "now # is the time for all # good men to come to the # aid of their country";
    char delims[] = "#";
    char *result = NULL;
    result = strtok( str, delims );
    while( result != NULL )
       {
       printf( "result is \"%s\"\n", result );
       result = strtok( NULL, delims );
       }


    et dés que je veut remplacer le string d'exemple par :

    char str[] = myFile.read();

    L'IDE vire à l'orange :

    error: initializer fails to determine size of 'str' !

    Pourtant, myFile.read() contient bien ma chaîne, si je l'envoi sur le Serial, mon string s'affiche...

    Si quelqu'un à une idée, je suis preneur !

    -
    Edité par Air_maX 31 mai 2014 à 17:04:41

    • Partager sur Facebook
    • Partager sur Twitter
      31 mai 2014 à 18:43:26

      Concernant les chaines de caractère en C, il faut savoir qu'elles se gèrent avec le caractère de fin de chaine \0.

      Petit exemple pour bien comprendre :

      char str[] = "Bonjour";
      Serial.print(str); // Affiche Bonjour
      str[2] = '\0';
      Serial.print(str); // Affiche Bo

      Ce que fait strtok, c'est insérer des \0 aux bons endroit (pour découper la chaine) et retourner des pointeurs aux différents endroits découpés.

      Une autre chose à savoir, c'est qu'il n'existe pas (ou qu'il n'est pas vraiment conseillé) de faire de l'allocation dynamique sur de petits processeurs.

      Impossible donc de faire un tableau variable mais on peut en faire un de taille fixe ;)

      Toujours un petit exemple (avec plus ou moins la solution) :

      char str[] ="AB|CD|EF|GH";
      char dlm[] = "|";
      
      int cnt = 0;
      char* tab[10] = { 0 };
      
      char *pch = strtok(str, dlm);
      
      while ( pch != NULL ) {
          if (cnt < 10) {
              tab[cnt++] = pch;
          } else {
              break;
          }
          pch = strtok (NULL, dlm);
      }

      A la suite de ce code, cnt vaut 4 pour indiqué qu'il a trouvé 4 parties.

      Les pointeurs de ces parties sont accessibles grace à tab (Serial.print(tab[0]) va donc afficher AB)

      Notons qu'il n'y aura que 10 parties au maximum (mais rien ne t’empêche de remplacer 10 par 16 ou plus si tu le souhaites).

      ==========

      Quand à Serial.read, ça renvoie un seul caractère, pas plusieurs !

      ET même si ça en renvoie plusieurs, il ne peut pas faire l'affection car impossible de savoir la place à réserver à l'avance.

      Là encore, tu dois utiliser un nombre fixe.

      Petit exemple :

      char str[64];
      int cnt = 0;
      while (cnt < 63) {
          if (Serial.available()) {
              str[cnt] = Serial.read();
          } else {
              break;
          }
      }
      str[cnt] = '\0'; // Surtout, ne pas oublier le \0 !

      Notons que la chaine ne fera que 63 caractères au maximum mais rien ne t'empèche de changer 64/63 ;)


      -
      Edité par lorrio 31 mai 2014 à 18:45:51

      • Partager sur Facebook
      • Partager sur Twitter
        31 mai 2014 à 19:11:51

        donc en fait, cela signifie qu'il faut déterminer à l'avance (et de manière définitive), la longueur des chaines qui seront exploiter par le script, en revanche, dans cette longueur de chaîne, je peut jouer avec le nombre et la position des séparateurs...c'est bien ça ?

        pour read(), j'avais pas capter de c'étais caractères par caractères, vu que l'exemple sur lequel je me base lit plusieurs lignes, j'étais partie sur l'idée que le while() utilisé servais à passer sur la ligne du dessous, pas au caractère suivant....I'me quiche parfois :D 

        donc, il faut (si j'ai bien suivi l'idée) :

        • récupérer caractère par caractère la ligne (en identifiant la fin de la ligne) issu du fichier de la carte SD
        • recoller tout les caractères pour reconstruire la chaîne d'origine
        • séparer la chaine grâce strtok

        sinon, question bête, une fois la chaine reconstitué, y aurais pas un moyen de compter les caractères, pour avoir des longueurs dynamiques ?

        • Partager sur Facebook
        • Partager sur Twitter
          31 mai 2014 à 21:07:46

          Ne me fais pas dire ce que je n'ai pas dis ;)

          J'ai dis qu'il était déconseillé de faire de l'allocation dynamique, je n'ai pas dit que ça n'existait pas.

          Sur les petits processeurs, la quantité de RAM est extrêmement limitée donc il faut faire très attention à ce que tu utilises.

          En faisant de l’allocation dynamique, impossible de savoir combien tu utilises ni si tu te retrouveras en manque de RAM en plein milieu du programme.

          Par contre, en utilisant des variables globales pour tous tes buffers de grosses tailles en spécifiant les tailles, le compilateur est capable de vérifier que l'ensemble va tenir dans la RAM du proc.

          Par exemple, si tu écris char buffer[65536];, il est certain que le compilateur refusera de compiler et te sortira une erreur du genre data RAM space too short.

          A l'inverse, si tu fais char* buffer = malloc(65536);, il est certain que le programme va compiler mais que le malloc retournera un pointeur NULL car l'allocation a échouée. Si par derrière tu fais des accès à ce pointeur sans le tester, ça plante ... ou ça fait n'importe quoi ...

          Si tu veux te simplifier la vie, tu peux utiliser la classe String qui fait de l'allocation dynamique de façon à gérer la taille dynamiquement.

          Tu pourrais donc avoir :

          /*
              Fonction bloquante qui ne rend la main qu'après avoir lue une ligne entière sur le Serial
          */
          String SerialReadLine() {
              String str;
              for (;;) {
                  if (Serial.available()) {
                      char c = Serial.read();
                      if (c == '\n') {
                          break;
                      }
                      str += c;
                  }
              }
              return str;
          }

          Par contre, si jamais tu t'amuses à envoyer des données sur le Serial non stop, sans saut de ligne, l'arduino va forcément atteindre sa limite de RAM et je ne sais pas du tout ce qu'il pourrait se passer.

          Dans le milieux professionnel ou je travail, l'allocation dynamique est donc tout simplement interdite.

          Tout ce fait avec des tailles constantes et vérifications des tailles maxi à l'exécution.

          Par exemple, si l'on doit lire les lignes d'un fichier une par une sachant qu'en théorie, elles ne dépassent pas 100 caractère, on prend un buffer de 128 histoire d'avoir un peu de marge.

          Et lors de la lecture, si jamais la taille maxi du buffer est atteinte (ce qui en théorie, n'arrive jamais), on continue de lire sans écriture dans le buffer (en clair, on ignore la fin de la ligne mais on continue de lire pour atteindre le \n).

          • Partager sur Facebook
          • Partager sur Twitter
            31 mai 2014 à 22:27:00

            c'est vrai que de mon coté, j'ai une grosse tendance à ne pas prendre attention à ce genre de chose (j'arrive juste dans l'électro, jusqu'à présent, j’étais plus orienté web, donc des échelles de mémoire BEAUCOUP plus larges....)

            donc, effectivement, il vaut mieux que je m'impose une bride sur la taille de mes lignes !;)

            en revanche, je suis étonné qu'on ne puisse pas charger une ligne d'un seul coup, qu'on soit obliger de faire du caractère à caractère...

            bon, je vais recommencer depuis le départ, en espérant avoir bien saisie tout les principes !

            • Partager sur Facebook
            • Partager sur Twitter
              31 mai 2014 à 23:25:39

              Bienvenue dans le monde de l'électronique embarquée :)

              La puissance de calculs des processeurs embarqué est très réduite donc l'ensemble des fonctions de base se résume à des choses simples.

              Et encore, je dirais que tu as de la chance d'être sur arduino et toute la surcouche qu'il y a avec.

              Si tu descend sur des processeur PIC low cost, tu verras qu'il y a encore moins de choses.

              Par exemple, il n'y a pas de Serial.print. Si tu veux écrire une chaine, c'est caractère par caractère en prenant soin de ne pas surcharger le module UART qui se charge de la com série (soit par boucle while bloquante, soit par interruptions).

              Pareil pour Serial.read, c'est à toi d'utiliser une interruption pour lire rapidement le caractère dès qu'il est disponible (car sinon, il sera écrasé par le prochain caractère reçu), le stocker dans un buffer temporaire et le traiter plus tard.

              -
              Edité par lorrio 31 mai 2014 à 23:29:16

              • Partager sur Facebook
              • Partager sur Twitter
                31 mai 2014 à 23:41:33

                en gros, je passe de la mémoire d'élePHPant, à celle du moineau....je me console en me disant qu'en utilisant une carte SD, j'économise un peu, c'est déjà ça ! ;)

                • Partager sur Facebook
                • Partager sur Twitter
                  1 juin 2014 à 12:39:50

                  ATTENTION SPOIL!!! si tu tiens à tout savoir, non seulement tu passe de l'elePHPant au poisson rouge maintenant, mais tu vas constater d'ici quelques mois au fait que tu passe aussi du lièvre à la tortue (qui part à l'heure seulement si tu code bien en plus - j'aime pas dire "à point" en parlant d'un animal, ça fait cuisine comme truc). tout ça sans vouloir sauter du coq à l'âne bien sur. FIN DU SPOIL!!!

                  blague à part, la carte SD c'est beau, y'a de la place, mais ça a un temps d'accès beaucoup plus long que la RAM, et surtout, ça augmente pas la taille maxi du string. et comme lorrio a pu te le faire remarquer, le string parachute (pour la surface de voilure, si tu suis l'image :lol: ) ça existe pas en électronique

                  • Partager sur Facebook
                  • Partager sur Twitter

                  oui. non. enfin je regarde et je te dis.

                    1 juin 2014 à 13:00:36

                    :D
                    ça fait quand même un paquet de concept à assimilé en même temps, je crois que j'ai eu les yeux plus gros que les ventres de toute ma famille réunis !
                    bon, ça me démoralise pas, ni ne me fait songer à réduire mes objectifs, en revanche, il est clair que ça va me prendre beaucoup plus de temps que prévu initialement (d'un autre coté, partir d'ou je part et concevoir ce projet avec cette quantité de contraintes lié au matériel, ça fait un p.tain de challenge :))!
                    aujourd'hui, pas de code (enfin, pas avec les doigts, tout reste dans le buffer que j'ai entre les oreilles !), je fini les plans de la future cage de mon calopsitte...
                    • Partager sur Facebook
                    • Partager sur Twitter
                      2 juin 2014 à 8:21:11

                      Moi j'ai une question/remarque... Puisque maintenant on a clarifie le fait qu'on faisait une lecture car. par car. , ca devient presque trivial de stocker les caracteres, detecter le pipe ( | ), faire une action puis continuer a lire, non ?

                      • Partager sur Facebook
                      • Partager sur Twitter

                      Retrouvez moi sur mon blog et ma chaine Youtube !

                        2 juin 2014 à 10:25:58

                        C'est vrai que vu comme ça, il devient facile de combiner lecture de la ligne et détection des pipes en simultanée.
                        • Partager sur Facebook
                        • Partager sur Twitter
                          2 juin 2014 à 10:38:24

                          Salut Eskimon !

                          en fait, j'essaie de mettre en place le stockage de mes menu sur carte SD.

                          voila l'idée de base :

                          un fichier s'appelle ACCUEIL.txt, il contient la liste des boutons (et les params de ces boutons) que contient le menu Accueil, genre :

                          //title-logo-size-background-target

                          Circulaire|A|MIN|RED|SUBA

                          Defonceuse|B|MIN|BLUE|SUBB

                          à l'aide d'une boucle, j'envoi chaque ligne dans une function qui découpe à chaque pipe et retourne les éléments dans la function qui construit le menu.

                          enfin, ça, c'est la théorie !:D

                          dans la pratique, j'avoue,  là tout de suite, je nage la pierre comme un poisson hors de l'eau....

                          • Partager sur Facebook
                          • Partager sur Twitter
                            2 juin 2014 à 10:47:12

                            Si c'etait moi, j'aurais fait une sorte de specifications pour mes chaines... Du genre "Le titre fait 20 caractere max", "le logo est represente par une lettre capitale", "la taille est represente par trois lettres"... etc. Comme ca, tu peux tres simplement faire tes buffers (tu connais deja la taille) et faire assez facilement le découpage aussi. Bref, tu fais plus d'effort avant (preparation) pour en faire moins apres (codage)

                            • Partager sur Facebook
                            • Partager sur Twitter

                            Retrouvez moi sur mon blog et ma chaine Youtube !

                              2 juin 2014 à 10:52:01

                              c'est un peu ce que je m'éfforce de faire, sauf que pour le moment, le buffer, je maitrise pas du tout ni la notion, ni son application, j'ai du mal à clarifier tout ça dans ma tête, et imaginer la structure de mon code, donc, pour le moment, je suis + en phase d'expérimentation sur l'IDE.

                              j'écris un truc, l'IDE braille, j'essaie de comprendre, je modifie un truc, il braille encore, j'insiste, il braille toujours, mais pas le même charabia...bref, c'est un peu comme avec une femme quoi !:D

                              • Partager sur Facebook
                              • Partager sur Twitter
                                2 juin 2014 à 16:03:10

                                pas tout à fait comme une femme: ton programme finira par entendre raison et finira par faire ce que tu veux, sans aller jusqu'au point godwin ni déclencer une guerre nucléaire :p pis l'IDE  quand il braille, il te dit plus ou moins ce qui va pas. c'est pas toujours le cas côté féminin.:lol:

                                la parenthese "blagues  misogynes" étant passée, ce que tu peux faire c' est un peu une espece de fichier xml (ptetre un peu lourd à lire) ou  csv (enfin un tableur dans un fichier texte), avec par bouton: le logo, le nom/titre, la position sur l'écran, des couleurs d'affichage (texte, logo,  arriere plan)... ça se lit bien, et ça fait pas un trop gros tableau à édcortiquer (vive strtok)

                                -
                                Edité par remace 2 juin 2014 à 16:19:57

                                • Partager sur Facebook
                                • Partager sur Twitter

                                oui. non. enfin je regarde et je te dis.

                                  2 juin 2014 à 16:48:00

                                  Je répond peut être à côté de la question, mais personnellement je ne m’embêterais même pas à lire un fichier, le parser, gérer les erreurs, etc.. En C, les possibilité de méta-programmation sont extrêmement limité, mais on est dans un des rares cas ou le préprocesseur peut générer du code pour nous alors autant ne pas s’en priver.

                                  N.B. ce que je dis est valable pour du C, je ne sais pas à quel point le langage arduino lui est différent, à prendre avec des pincettes donc.

                                  • Partager sur Facebook
                                  • Partager sur Twitter
                                  Zeste de Savoirbépocode minimal  — Ge0 <3
                                    3 juin 2014 à 11:55:16

                                    simbilou=>

                                    J'avoue, tu m'a perdu ! (je démarre juste, alors la méta-prog...c'est pas pour tout de suite, si tenté que cette solution soit compatible Arduino :))

                                    remace=> 

                                    Partir sur une base CSV, j'trouve qu'on se rapproche pas mal de mon idée, ça me plait (mais pourquoi diable n'y ais-je pas pensé ? :colere2:)

                                    si tu peut m'en dire un peu plus sur la manière dont tu aurais procédé ?(pas du code tout cuit hein, je veut juste savoir comment tu aurais organiser le déroulement des choses ;))

                                    • Partager sur Facebook
                                    • Partager sur Twitter
                                      3 juin 2014 à 19:41:04

                                      simbilou: le probleme du code généré par le préprocesseur c'est qu'il vient quand même prendre de la place en RAM a final. le but d'aller stocker des choses dans des fichiers, c'est de pouvoir les effacer de la RAM et de les ouvrir que quand il y en a besoin)

                                      babas: bah moi j'aurais fait un truc du style d'écrire un objet par ligne avec un peu tout ce qui te semble utile dans ses caractéristiques pour créer ton interface (même sur un fichier texte bateau quoi). en utilisant bien des séparateurs pratiques(\t, \n, qui sont les plus lisibles), et après, bah tu lis caractere par caractere au moment de décoder. tu fais un fichier par menu.

                                      plus en détail je sais pas faire, parce que comme je te l'ai surement déjà dit, je n'ai pas d'arduino, je n'ai pas de shield SD. même si je vois comment faire, je l'ai jamais fait proprement.

                                      • Partager sur Facebook
                                      • Partager sur Twitter

                                      oui. non. enfin je regarde et je te dis.

                                        3 juin 2014 à 19:46:09

                                        remace a écrit:

                                        simbilou: le probleme du code généré par le préprocesseur c'est qu'il vient quand même prendre de la place en RAM a final. le but d'aller stocker des choses dans des fichiers, c'est de pouvoir les effacer de la RAM et de les ouvrir que quand il y en a besoin)

                                        Ah oui c’est sur, c’est moins flexible. Enfin on parle d’un microcontrôleur là, la flexibilité de l’application est rarement ce qu’on en attend. Cela dit je comprend tout à fait l’aspect pédagogique et amusant du truc.

                                        • Partager sur Facebook
                                        • Partager sur Twitter
                                        Zeste de Savoirbépocode minimal  — Ge0 <3
                                          3 juin 2014 à 22:21:55

                                          simbilou=>

                                          je pense qu'il s'agit + d'un soucie de capacité mémoire que de flexibilité ;)

                                          remace=>

                                          ben en fait, t'est complètement dans rentré dans ma tête (c'est mon coté skyzo qui va être content :D)

                                          donc, pour résumé : je fait un .csv dans lequel je rentre mes params de boutons (1 bouton par ligne)

                                          ensuite, coté Arduino mtn :

                                          • je commence par déclaré un tableau vide
                                          ensuite, je lance une boucle qui tourne tant qu'il y a de quoi lire dans le .csv :
                                          • je déclare une variable "data" vide
                                          • tant que je n'ai pas croiser un séparateur, j'ajoute chaque nouveau char lu à la valeur de "data"
                                          • dés que je croise un séparateur, j'envoi le contenu de "data" dans le tableau (sur le pointeur courant)
                                          • dés que je croise une fin de ligne, j'envoi mon tableau à la fonction de création de bouton

                                          sur le principe, j'ai bon, ou y a des erreurs de logique ?:euh:

                                          • Partager sur Facebook
                                          • Partager sur Twitter
                                            3 juin 2014 à 22:26:08

                                            Nan c'est ca :) Après l'idéal serait de connaitre a l'avance la taille maximal que devra prendre data pour pouvoir déclarer ton tableau au plus juste +1 et ce sera nickel :)

                                            • Partager sur Facebook
                                            • Partager sur Twitter

                                            Retrouvez moi sur mon blog et ma chaine Youtube !

                                              3 juin 2014 à 22:37:55

                                              ok, cool ! :)

                                              juste 2 points que je voudrais éclaircir :

                                              pour le moment, je n'ai pas encore inclus la gestion du buffer (à vrai dire, je l'appréhende pas trop du tout cette notion du buffer :euh:)

                                              faut-il l'implémenté ? si oui où ? comment ?

                                              comment optimiser la logique de mon code ? ou et comment économiser des ressources ?

                                              • Partager sur Facebook
                                              • Partager sur Twitter
                                                3 juin 2014 à 22:48:30

                                                babasdu24 a écrit:

                                                je pense qu'il s'agit + d'un soucie de capacité mémoire que de flexibilité ;)

                                                Ah oui tiens, j’avais mal compris l’argument. Alors là pour le coup je n’y crois pas. La mémoire devra un moment ou un autre tenir les données pour afficher le menu, et je doute que l’executable soit plus petit avec le code pour aller chercher les données qu’avec les données en dur.

                                                Mais dans tous les cas continue sur ta méthode, c’est un excellent exercice et tu au moins tu sais où tu met les pieds.

                                                -
                                                Edité par simbilou 3 juin 2014 à 22:49:47

                                                • Partager sur Facebook
                                                • Partager sur Twitter
                                                Zeste de Savoirbépocode minimal  — Ge0 <3
                                                  3 juin 2014 à 23:04:43

                                                  ben en fait, j'essaie d'économiser au max en construisant puis affichant au fur et à mesure que je lit le .csv, donc en théorie, je tire le moins possible sur les ressources (enfin, je pense, mais les plus expérimentés verrons sûrement un moyen d'économiser encore plus ^^), par contre, je suis d'accord avec toi, avec les données en dur, j'aurais peut être consommer moins, mais avec une possibilité de mise à jour de mes menus plus contraignante (j'ai compris ce que tu voulais dire par flexibilité ;) )
                                                  • Partager sur Facebook
                                                  • Partager sur Twitter
                                                    4 juin 2014 à 8:52:25

                                                    Un buffer (aussi appele zone tampon) c'est une zone memoire qu'on se garde pour faire une transition entre deux trucs. Par exemple, tu dois surement connaitre et utiliser de maniere transparente les buffers d'emission et de reception de la voie serie. Tu vas envoyer a vitesse grand V des donnees a emettre, mais la voie serie ne peut qu'envoyer qu'une quantite limites d'octets par secondes. Il faut donc un endroit ou stocker ces octets a envoyer. Ensuite, ton programme peut tourner dans son coin pendant que le module de la voie serie tournera dans le sien pour prendre les donnees dans le buffer d'emission et les envoyer... Il se passe la meme chose pour la reception. Tu vas recevoir de maniere asynchrone des donnees. Tu ne peux pas savoir quand elles vont arriver. Ton programme est peut etre parti faire des calculs super complique et s'en cogne de la voie serie pour le moment. Du coup les donnees arriveront dans cette zone temporaire et tu iras les chercher quand tu seras pret (c'est ce que retourne Serial.available ! si oui ou non des donnees sont dans le buffer) Bien sur il faut faire gaffe a vider de temps en temps sinon les nouvelles donnees risquent d'ecraser les anciennes ou ne seront pas ajoutee et donc perdu.

                                                    • Partager sur Facebook
                                                    • Partager sur Twitter

                                                    Retrouvez moi sur mon blog et ma chaine Youtube !

                                                      4 juin 2014 à 10:39:59

                                                      ok !

                                                      dans la pratique appliqué à mon idée de code, vu que je construit au fur et à mesure de la réception, en théorie, du coté du buffer, je ne risque pas grand chose, non ?

                                                      ou faut-il que je prévois un RAZ du buffer après exploitation de chaque ligne ?

                                                      • Partager sur Facebook
                                                      • Partager sur Twitter
                                                        4 juin 2014 à 11:00:36

                                                        La en effet j'ai un peu fait de l'abus de langage car tu vas etre sur un truc synchrone, donc ce sera pas vraiment un buffer, plutot un "tableau de stockage temporaire" ou une "zone de swap", sorte de zone d'echange entre ta SD et ton ecran.

                                                        • Partager sur Facebook
                                                        • Partager sur Twitter

                                                        Retrouvez moi sur mon blog et ma chaine Youtube !

                                                          4 juin 2014 à 11:07:46

                                                          donc, le fait de re-déclarer ma variable vide à chaque séparateur, et mon tableau vide à chaque retour de ligne "nettoie" ma mémoire, non ?
                                                          • Partager sur Facebook
                                                          • Partager sur Twitter
                                                            4 juin 2014 à 11:45:38

                                                            redeclare le pas, contente toi de pointer sur sa premiere case et ca suffira (suffira juste de bien definir le delimiteur de fin de chaine pour savoir ou s'arreter quand on lit le tableau)

                                                            Apres je suis peut etre completement a cote de la plaque, peut etre que je suis parti dans un truc alors que c'est pas ca, il faudrait que je relise le debut du sujet XD

                                                            • Partager sur Facebook
                                                            • Partager sur Twitter

                                                            Retrouvez moi sur mon blog et ma chaine Youtube !

                                                            [Arduino] équivalant "explode()" de PHP

                                                            × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                                                            × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                                                            • Editeur
                                                            • Markdown