Codice per indizio musicale

Salve a tutti,
Il mio primo progetto con arduino è attualmente una specie di "trappola sonora", cioè un prototipo composto da:

-Arduino UNO
-Buzzer passivo
-Sensore ad ultrasuoni HC-SR04
-Una piccola breadboard
-Prototype shield

Lo scopo è quello di far partire una sequenza musicale, riprodotta dal buzzer, quando viene rimosso un oggetto posto sotto il sensore ad ultrasuoni. La melodia viene riprodotta una volta soltanto dopo di che arduino si spegne (entra in sleep mode).

Questo è il codice che ho utilizzato per realizzare questo piccolo prototipo

#include <avr/sleep.h>
#define REST     1
#define NOTE_C5  523
#define NOTE_G5  784
#define NOTE_A5  880
#define NOTE_B5  988
#define NOTE_C6  1047
#define NOTE_F5  698
#define NOTE_E5  659
#define NOTE_D5  587
#define NOTE_A4  440
#define NOTE_B4  494 
#define NOTE_G4  392
#define NOTE_D6  1175
#define NOTE_DS6 1245 
#define NOTE_AS5 932
#define NOTE_DS5 622


// notes in the melody:
int melody[] = {
  NOTE_B5, NOTE_C6, NOTE_B5, NOTE_A5, NOTE_G5, NOTE_C5, 
  NOTE_G5, NOTE_F5, NOTE_E5, NOTE_D5, NOTE_E5, NOTE_A4, 
  NOTE_B4, NOTE_C5, NOTE_G5, NOTE_D5, NOTE_G4, NOTE_A4, 
  NOTE_B4, NOTE_C5, NOTE_G5, NOTE_D5, NOTE_E5, NOTE_B5, 
  NOTE_C6, NOTE_B5, NOTE_A5, NOTE_G5, NOTE_C5, NOTE_G5, 
  NOTE_F5, NOTE_E5, NOTE_D5, NOTE_E5, NOTE_A4, NOTE_B4,
  NOTE_C5, NOTE_G5, NOTE_D5, NOTE_G5, NOTE_A5, REST, 
  NOTE_C6, NOTE_B5, NOTE_A5, NOTE_G5, NOTE_C5, NOTE_C6, 
  REST, NOTE_B5, NOTE_C6, NOTE_D6, NOTE_G5, NOTE_DS6, 
  REST, NOTE_D6, NOTE_C6, NOTE_AS5, NOTE_G5, NOTE_AS5, 
  NOTE_C6, NOTE_D6, NOTE_DS6, NOTE_C6, NOTE_DS5, NOTE_F5,
  NOTE_G5
  
};

// note durations: 4 = quarter note, 8 = eighth note, etc.:
int noteDurations[] = {
  8, 8, 8, 8, 4, 4, 8, 8, 8 ,8, 
  2, 8, 8, 8, 8, 4, 4, 8, 8, 8, 
  8, 4, 4, 8, 8, 8, 8, 4, 4, 8, 
  8, 8, 8, 2, 8, 8, 8, 8, 4, 4, 
  1, 16, 2, 8, 8, 2, 2, 2, 8, 8,
  8, 2, 2, 2, 8, 8, 8, 2, 4, 4,
  2, 4, 4, 2, 8, 8, 2,
  };
  int thisNote = 0;
  const int triggerPort = 9;
  const int echoPort = 10;

void setup() {
  
   pinMode(triggerPort, OUTPUT);
   pinMode(echoPort, INPUT);
   Serial.begin(9600);
   Serial.print( "Sensore Ultrasuoni: ");
 
   
}

void sleepNow(){
     
           set_sleep_mode(SLEEP_MODE_PWR_DOWN);
           sleep_enable();
           sleep_mode();
         }
           
      

void loop() {
  
      //porta bassa l'uscita del trigger
    digitalWrite( triggerPort, LOW );
    //invia un impulso di 10microsec su trigger
    digitalWrite( triggerPort, HIGH );
    delayMicroseconds( 10 );
    digitalWrite( triggerPort, LOW );
     
    long durata = pulseIn( echoPort, HIGH );
     
    long distanza = 0.034 * durata / 2;
     
    Serial.print("distanza: ");
     
    //dopo 38ms è fuori dalla portata del sensore
    if( durata > 38000 ){
    Serial.println("Fuori portata   ");
    }
    else{ 
    Serial.print(distanza); 
    Serial.println(" cm     ");
       
  }
    
   if(distanza > 18){
     
     delay(2100);
     
     for (thisNote = 0; thisNote < 67; thisNote++) {
      // to calculate the note duration, take one second
      // divided by the note type.
      //e.g. quarter note = 1000 / 4, eighth note = 1000/8, etc.
      int noteDuration = 2420/ noteDurations[thisNote];
      tone(8, melody[thisNote], noteDuration);
  
      // to distinguish the notes, set a minimum time between them.
      // the note's duration + 30% seems to work well:
      int pauseBetweenNotes = noteDuration * 1.30;
      delay(pauseBetweenNotes);
      // stop the tone playing:
      noTone(8);
         }
         
     
       if(thisNote == 67){
         
           sleepNow();
         }
           
       } 
     }

Sono partito dagli esempi offerti per "Tone Melody" e per il funzionamento del sensore ad ultrasuoni, lasciando gli appunti vicino al codice per capirne il funzionamento e poi riadattarlo.

Il codice funziona benissimo e così il piccolo prototipo, almeno per i miei scopi, ma la domanda sorge spontanea:

C'è un modo più semplice per realizzare il tutto?
In che modo avrei potuto migliorare la scrittura del codice?

così in caso in futuro dovesse essere necessario realizzare un prop simile potrei farlo in una maniera più snella ed elegante imparando qualcosa di nuovo :slight_smile:

grazie a tutti ;D

Ciao, grandi margini non credo ve ne siano, l'unica cosa che puoi "snellire" se così lo possiamo definire è questo if

if(thisNote == 67){
         
           sleepNow();
         }

Che essendo dentro il for viene valutato ogni volta e fa andare in sleep il micro quando hai finito di suonare tutte le note, basta eliminarlo e spostare la chiamata alla funzione sleepNow fuoi dal ciclo for.
L'altra cosa "migliorabile" potrebbe essere quella di dare la possibilità di interrompere l'esecuzione della melodia, per fare questo dovresti si stravolgere il codice per evitare l'uso dei delay di pausa tra una nota e l'altra e gestire il play della melodia non con un ciclo for ma demandando al loop la necessità di eseguire le varie note alle giuste tempistiche richiamando quindi la tone senza il terzo parametro se non vado errato. Ma se non è una cosa applicabile al progetto (e non credo lo sia in quando eseguita la melodia Arduino di fatto smette di fare qualsiasi cosa visto che nessuno lo risveglia dallo sleep) sarebbe un innutile complicazione del codice.
Se lo sfrutti per imparare l'uso di millis e di una macchina a stati finiti allora il discorso cambia perché diventa didattico e quello non è mai tempo perso :slight_smile:

Altra cosa migliorabile ad ogni ciclo di loop tu fai

  //porta bassa l'uscita del trigger
    digitalWrite( triggerPort, LOW );

Imemdiatamente prima di portare l'usita ad HIG e poi a LOW dopo 10 microsecondi, puoi evitarlo visto che tanto metterai sicuramente a LOW l'uscita dopo i 10 microsecondi.
Per essere certo dello stato iniziale della porta poi spostarlo nel setup subito dopo aver settato il pin come output

fabpolli:
Che essendo dentro il for viene valutato ogni volta e fa andare in sleep il micro quando hai finito di suonare tutte le note, basta eliminarlo e spostare la chiamata alla funzione sleepNow fuoi dal ciclo for.
L'altra cosa "migliorabile" potrebbe essere quella di dare la possibilità di interrompere l'esecuzione della melodia, per fare questo dovresti si stravolgere il codice per evitare l'uso dei delay di pausa tra una nota e l'altra e gestire il play della melodia non con un ciclo for ma demandando al loop la necessità di eseguire le varie note alle giuste tempistiche richiamando quindi la tone senza il terzo parametro se non vado errato. Ma se non è una cosa applicabile al progetto (e non credo lo sia in quando eseguita la melodia Arduino di fatto smette di fare qualsiasi cosa visto che nessuno lo risveglia dallo sleep) sarebbe un innutile complicazione del codice.
Se lo sfrutti per imparare l'uso di millis e di una macchina a stati finiti allora il discorso cambia perché diventa didattico e quello non è mai tempo perso :slight_smile:

Intanto ti rigrazio per la risposta!

Il progetto iniziale prevedeva che una volta attivata la melodia si sarebbe dovuta riprodurre una volta al giorno allo stesso orario. Avevo quindi considerato di utilizzare millis per questa funzione e, in caso, di temporizzare la musica stessa, purtroppo mi sono reso conto che era una cosa più grande di me ed essendo alle prime armi e senza solide basi ho optato per questa soluzione.

In futuro spero di riuscire ad utilizzarlo per bene dato che ho in mente di fare un piccolo spettacolo di luci e musica a tempo e credo che mi possa tornare molto utile come comando se non risultare essenziale ;D

Nel caso avessi utilizzato millis, per avere un'idea generale, come mi sarei dovuto comportare?

Allora millis per temporizzare compiti sulla media/lunga durata non va bene perchè non è preciso, se ti serve fare una determinta cosa una volta al giorno in orario determinato occorre usare un RTC.
L'argomento millis ti conviene approcciarlo per bene e comprenderlo fin da subito, perché tanto prima o poi ti servirà, quindi ti conviene partire (secondo me) da un progetto a se stante partendo dalla base minima, guarda l'esempio dell'IDE blick without delay per partire, da quello fai prove e quando hai compreso la logica dell'uso di millis allora potresti, ad esempio, aggiungere un led all'esempio dell'IDE e farli lampeggiare con frequenza differente unorispetto all'altro. Comunque per capire le basi con cui si usa millis() consiglio prima la lettura di QUESTO post esplicativo, dopo di che lo studio di come si usa la funzione millis(), prima QUI, poi QUI e QUI e QUI e tutti gli articoli che sono in QUESTA pagina ... alla fine il tutto dovrebbe essere più chiaro :slight_smile:

Aggiungo solo qualche spunto "stilistico" (che serve non solo a te per poter gestire meglio il codice, soprattutto nel debugging, ma anche a chi lo mostri, cioè noi :wink: ), stando al listato che hai postato.

Tu metti sempre la graffa sulla riga iniziale, esempio:

      if(thisNote == 67){
        
          sleepNow();
        }

Va anche bene, è uno dei due possibili "stili" (graffa a capo o in linea), e solitamente si fa più che altro per compattare il listato (magari meglio distanziarla con uno spazio dopo la parentesi chiusa), ma dopo metti anche praticamente sempre (almeno) una linea vuota quindi direi che non è per compattare il codice... :wink:
Inoltre la graffa chiusa la indenti spesso in modo "casuale", ossia spesso più a destra dell'apertura o del codice, esempio:

     for (thisNote = 0; thisNote < 67; thisNote++) {
      // to calculate the note duration, take one second
      // divided by the note type.
      //e.g. quarter note = 1000 / 4, eighth note = 1000/8, etc.
      int noteDuration = 2420/ noteDurations[thisNote];
...
      // stop the tone playing:
      noTone(8);
         }     <-- QUESTA!

Per rendere più facilmente leggibile il codice (cosa che non è "estetica", ripeto, ma serve anche a te per "vedere" meglio il listato ed identificare prima i problemi) consiglio sempre la graffa a capo, e la graffa chiusa allineata con quella aperta, oltre a spaziare le parentesi della condizione delle if(). Inoltre indentare sempre con DUE spazi (tu spesso indenti con uno solo o con tre o più!) per if() con una sola istruzioni potresti anche evitare le graffe (cosa che alcuni sconsigliano, ma con una buona indentazione non ci si sbaglia). Per iniziare ti può anche bastare usare il Ctrl-T dell'IDE...

Ad esempio:

...
  if(distanza > 18)
  {
    for (thisNote = 0; thisNote < 67; thisNote++)
    {
      // to calculate the note duration, take one second
      // divided by the note type.
      //e.g. quarter note = 1000 / 4, eighth note = 1000/8, etc.
      int noteDuration = 2420/ noteDurations[thisNote];
      tone(8, melody[thisNote], noteDuration);

      // to distinguish the notes, set a minimum time between them.
      // the note's duration + 30% seems to work well:
      int pauseBetweenNotes = noteDuration * 1.30;
      delay(pauseBetweenNotes);
      // stop the tone playing:
      noTone(8);
    }
    if (thisNote == 67)
      sleepNow();
   }
...

Grazie fabpolli!
Cercherò di prendere la mano con millis() e credo che il modo migliore per farlo sia quello di utilizzarlo in un qualche progetto ;D

L'opzione Real Time Clock l'avevo intravista in alcuni progetti trovati online ma non avevo ben inteso di cosa si trattasse. Deduco sia un pezzo esterno da aggiungere alla scheda giusto? e se non sbaglio non dovrebbe costare neanche tanto.
Ho diversi progetti che conto di realizzare e molti di questi prevedono lunghi intervalli di tempo quindi penso che mi farà bene imparare ad usarlo.

docdoc,
grazie davvero.
Non avevo fatto caso a tutta questa spaziatura qua e là. Effettivamente io apro la graffa in linea per avere subito sott'occhio il "comando" a cui si riferisce, altrimenti nel conteggio me le perdo un po'.
Errori e fretta.
Quella che si trova nel mezzo del cammin di nostra vita, credo sia lì perché era tipo l'ennesima volta che riscrivevo quella parte di codice e quindi dove era era volevo solo testarlo. Però effettivamente rende più difficile la lettura e il debugging.

Per le indentature invece, credo che le triple siano state volontarie^^'
Pensavo che indentare sempre di più durante la scrittura del codice mi avrebbe permesso di non confondermi tra le sue varie parti. Una specie di scaletta che prosegue man mano che si allunga quantità di operazioni poste all'interno di un void() sia esso setup o loop.

Forse sono un po troppo estetico, hai ragione.
Cercherò di migliorare dove posso, se non altro per rendere il tutto leggibile nella maniera giusta.

Grazie ancora a entrambi ;D

Merambolin:
Grazie fabpolli!
Cercherò di prendere la mano con millis() e credo che il modo migliore per farlo sia quello di utilizzarlo in un qualche progetto ;D

L'opzione Real Time Clock l'avevo intravista in alcuni progetti trovati online ma non avevo ben inteso di cosa si trattasse. Deduco sia un pezzo esterno da aggiungere alla scheda giusto? e se non sbaglio non dovrebbe costare neanche tanto.
Ho diversi progetti che conto di realizzare e molti di questi prevedono lunghi intervalli di tempo quindi penso che mi farà bene imparare ad usarlo.

Si è un componente hardware esterno, non costa tantissimo, ci sono ache a bassissico costo ma sono poco precisi, il DS3231 è un buon compromesso tra costo e affidabilità, è dotato di allarmi (due) e permette di essere precisi nei compiti a "un certo orario" nel tempo e anche, grazie agli allarmi impostabili, mettere a dormire tutto il sistema e aspettare che l'RTC risvegli il tutto all'ora desierata, ma già questo è un po' più avanzato dell'uso di base dell'RTC, sicuramente ti conviene imparare ad usarlo