Facciamo un gioco

Vedendo dei codici scritti da me e da altri mi son sempre chiesto se vi era la possibità di ottimizzare meglio il codice e renderlo sempre più compatto e meno esigente di risorse. Mi son detto perchè non proponiamo un gioco sul forum in modo da far capire a chi è all'inizio della programmazione come si può ottiimizzare un codice per renderlo più veloce e occupare meno memoria? L'idea è questa: propongo un tema molto semplice, una piccola funzione che deve ricreare l' SOS in codice morse. E' una cosa molto semplice che può fare chiunque ha un arduino buttato nel cassetto a prender polvere, deve solo collegare la usb e utilizzare il led a bordo. Per chi non lo sapesse l' SOS in codice morse è strutturato in questo modo: (S)1 0 1 0 1(spazio tra lettere) 000 (O)111 0 111 0 111 (Spazio tra lettere) 000 (S)1 0 1 0 1 (spazio tra parole)0000000... ripete. In pratica ogni 1 corrisponde al tempo di un punto, uno 0 corrisponde allo spazio tra un punto e l' altro e ha la stessa durata dell' 1. Tra una lettera e l' altra ci sono 000 quindi ipotizzando un tempo di 1= 250ms lo spazio tra due lettere durerà 750 ms led OFF. Per scrivere una linea ci vogliono 111 = 750 ms led ON. Lo spazio tra una parola e la successiva è dato da 0000000 = 1750 ms led OFF. Ci sono solo alcune regole del gioco da rispettare:

  1. La funzione creata non deve essere bloccante quindi niente uso di delay o cicli che possano bloccare l'esecuzione del programma.
  2. Per poter essere comprensibile a tutti, non bisogna far uso di istruzioni assembler o accesso diretto a porte e registri.
  3. A chi mastica già da tempo con la programmazione, dare prima la possibilita a chi è poco pratico di dare la propria soluzione in modo da fargli capire eventuali errori commessi.

Qui sotto un esempio del risultato finale scritto nel peggiore dei modi. In bocca al lupo.

/* clock 250 ms */
/* costruzione stringa SOS " 1 0 1 0 1 000 111 0 111 0 111 000 1 0 1 0 1 0000000 ....ripete " */

#define t_punto 250
#define t_linea 750
#define t_simbolo_simbolo 250
#define t_lettera_lettera 750
#define t_parola_parola 1750
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
digitalWrite(LED_BUILTIN, true);
delay(t_punto);
digitalWrite(LED_BUILTIN, false);
delay(t_simbolo_simbolo);
digitalWrite(LED_BUILTIN, true);
delay(t_punto);
digitalWrite(LED_BUILTIN, false);
delay(t_simbolo_simbolo);
digitalWrite(LED_BUILTIN, true);
delay(t_punto);
digitalWrite(LED_BUILTIN, false);

delay(t_lettera_lettera);

digitalWrite(LED_BUILTIN, true);
delay(t_linea);
digitalWrite(LED_BUILTIN, false);
delay(t_simbolo_simbolo);
digitalWrite(LED_BUILTIN, true);
delay(t_linea);
digitalWrite(LED_BUILTIN, false);
delay(t_simbolo_simbolo);
digitalWrite(LED_BUILTIN, true);
delay(t_linea);
digitalWrite(LED_BUILTIN, false);

delay(t_lettera_lettera);

digitalWrite(LED_BUILTIN, true);
delay(t_punto);
digitalWrite(LED_BUILTIN, false);
delay(250);
digitalWrite(LED_BUILTIN, true);
delay(t_punto);
digitalWrite(LED_BUILTIN, false);
delay(250);
digitalWrite(LED_BUILTIN, true);
delay(t_punto);
digitalWrite(LED_BUILTIN, false);

delay(t_parola_parola);
}

Io ci sto

Però ho 2 domande?

Coma si fa a dire quale programma è migliore?

Meno Ram?
Eseguibile più corto?
Più veloce? E come si misura la velocità di un programma non bloccante?

E poi
A parità di ottimizzazioni del compilatore?

Aggiungo un ulteriore metro!
Flessibilità e riutilizzo del codice... magari un bel parser da testo libero a codice morse.

Allora attenzione, bisogna seguire esattamente la traccia altrimenti non vi è modo di confrontare i codici. La traccia chiede di eseguire l'SOS in maniera ripetitiva e basta. Vediamo prima se la cosa suscita curiosità e diamo la possibilità anche ai novizi di partecipare. Per quanto riguarda l'utilizzo di memoria cerchiamo di compilarlo su un solo arduino x confrontarlo. Al momento io posso provare su di un arduino nano. Per quanto riguarda la velocità la misureremo utilizzando un pin di uscita attivato all'inizio della funzione e disattivato alla fine e misurato con oscilloscopio.
Altre idee?
Ovviamente si richiede lealtà nel partecipare. Non si vince nulla quindi quello che fate fatelo voi senza andare a sbirciare in giro.

nello79:
... diamo la possibilità anche ai novizi di partecipare.
...
Per quanto riguarda la velocità la misureremo utilizzando un pin di uscita attivato all'inizio della funzione e disattivato alla fine e misurato con oscilloscopio. ...

... ti rendi conto che qui, nel 90% dei casi, le due cose sono in antitesi ? ? ?

Se hanno un multimetro è già grasso che cola ... un oscilloscopio scordatelo proprio ... :grin:

Ti toccerà farlo a te per ogni programma che viene proposto ... :smiley:

Guglielmo

... se il ripetitore di SOS lo faccio in legno e bronzo, conta lo stesso ? ... mi riesce piu facile ... :stuck_out_tongue:

(scusa, non ho resistito :D)

Pero' in effetti il concetto di "migliore" e' un po aleatorio ... se ad esempio il programma A impiega il doppio del tempo ma occupa la meta' della memoria del programma B, quale definisci migliore ? ... :wink:

Quale funzione?

Cosa fa pensare che esista UNA funzione che fa il lavoro?

Nella mia implementazione non c'è...

Si potrebbe pensare invece ad un contatore unsigned long long
Incrementato free running dalla loop

Il programma che in capo a un ciclo trasmissivo ha contato di più è quello che ha avanzato più tempo
E quindi che ha consumato meno tempo macchina per lavorare
Ma non mi piace molto questa soluzione
Servirebbe una stampa su seriale, che include un fracco di codice inutile al lavoro principale

E aggiungo

Ci sono deroghe?

Ovvero i tempi sono cogenti o no?

Modifiche di qualche millisecondo sono accettate?

Pero' in effetti il concetto di "migliore" e' un po aleatorio ... se ad esempio il programma A impiega il doppio del tempo ma occupa la meta' della memoria del programma B, quale definisci migliore ?

Ragazzi ma alla fine non si vince nulla. Però penso che conta di più imparare a scrivere un codice per le nostre esigenze. Se uno trova una soluzione più veloce ma che consuma più memoria ci sarà qualcun'altro cha avrà imparato a scrivere un codice più veloce, viceversa se qualcuno scrive un codice meno veloce ma che occupa meno memoria ci sarà qualcun altro che avrà imparato a risparmiare memoria. Poi le esigenze non sono mica tutte uguali. Se poi esce chi scrive un programma più veloce e che impiega meno memoria ci saranno tante persone che impareranno a farlo.
Almeno cambiamo un po gli argomenti dei post, ultimamente leggo solo di arduino bruciati, arduino non si programma, arduino non comunica...

E aggiungo
Ci sono deroghe?
Ovvero i tempi sono cogenti o no?
Modifiche di qualche millisecondo sono accettate?

Da quello che ho capito io il clock viene dato dalla durata del punto, non so se esiste una normativa, l'importante che tutto fa riferimento alla durata del clock. I 250 ms io li ho decisi in maniera arbitraria ma in questo caso non credo che cambi qualcosa se sono 249 o 251. L'importante che siano sempre "più o meno uguali" :slight_smile:

Allora, da "nonprogrammatore" ho appena buttato giu questa schifezza (col notepad, perche' sto ribaltando il portatile e devo ancora reinstallare quasi tutto, per cui non so neppure se potrebbe compilare, figuriamoci se va :stuck_out_tongue: :D) ... che dici, fa abbastanza schifo ? ...

const pinled = 10 //un pin a caso per il led o il buzzer o quello che vuoi
byte posiz[33] = {1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0};
byte posnum = 0;

void setup() {
   pinMode(pinled, OUTPUT); /led collegato al VCC, acceso con low
}

void loop() {
   if ((posiz[posnum] == 0) && (tempo == 1)) {
      digitalWrite (pinled, HIGH);
      milnow = millis();
      tempo = 0;
   }
   else if ((posiz[posnum] == 1) && (tempo == 1)) {
      digitalWrite (pinled, LOW);
      milnow = millis();   
      tempo = 0;
   }

   if (((millis() - milnow) > 250) && (posnum <= 32)) {
      posnum++;
      tempo = 1;
   }
   else if (((millis() - milnow) > 250) && (posnum => 33)) {
      posnum = 0;
      tempo = 1;
   }
}

Caio,

questa la mia proposta:

#define TEMPO 250

uint64_t SOS = 11336911488;
uint32_t myMillis = 0;
int8_t pos = 33;

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, LOW);
  myMillis = millis();
}

void loop() {
  if (millis() - myMillis < TEMPO) {
    digitalWrite(LED_BUILTIN, bitRead(SOS, pos));
  } else {
    myMillis = millis();
    pos--;
    if (pos < 0) {
      pos = 33;
    }
  }
}

E così orso mi ha bruciato

I miei complimenti, li meriti

Etemanki ho dovuto correggere alcune cose ma il risultato non è corretto non fa quello che dovrebbe riprova.

Orso2001 il tuo codice funziona, sul mio nano occupa 998 byte di memori programma ( a cui bisogna togliere il bootloader ma teniamo questo per buono) e 15 byte di memoria ram. Appena possibile misuriamo anche la velocità.

in alternativa...un byte in più ma esegue meno "scritture" sul PIN:

#define TEMPO 250

uint64_t SOS = 11336911488;
uint32_t myMillis = 0;
int8_t pos = 33;
int8_t oldPos = 0;

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, LOW);
  myMillis = millis();
}

void loop() {
  if (millis() - myMillis < TEMPO) {
    if (oldPos != pos) {
      digitalWrite(LED_BUILTIN, bitRead(SOS, pos));
      oldPos = pos;
    }
  } else {
    myMillis = millis();
    pos--;
    if (pos < 0) {
      pos = 33;
    }
  }
}

Standardoil:
E così orso mi ha bruciato

I miei complimenti, li meriti

...tanta roba... :smiley:

@ORSO2001 bel lavoro :slight_smile:
Mi permetto di suggerire una miglioria...
Non so se in termini di performance migliora o peggiora (direi migliora) ma in termini di spazio occupato sicuramente è meglio, al posto di :

pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, LOW);

mettendo

DDRB = 0b00100000;
PORTB = 0b00000000;

e al posto di

digitalWrite(LED_BUILTIN, bitRead(SOS, pos));

mettendo

PORTB = 0b00100000 & bitRead(SOS, pos)<<5;

compilando su ide 1.8.12 si passa da
Lo sketch usa 1018 byte (3%) dello spazio disponibile per i programmi. Il massimo è 32256 byte.
Le variabili globali usano 16 byte (0%) di memoria dinamica, lasciando altri 2032 byte liberi per le variabili locali. Il massimo è 2048 byte.
a
Lo sketch usa 736 byte (2%) dello spazio disponibile per i programmi. Il massimo è 32256 byte.
Le variabili globali usano 16 byte (0%) di memoria dinamica, lasciando altri 2032 byte liberi per le variabili locali. Il massimo è 2048 byte.
;D

fabpolli leggi il primo post

Già mi ero perso il punto due, hai ragione :slight_smile:

mi sono permesso di copiare il programma di Orso e ricompilarlo

per avere conferma: 998 e 15 byte

adesso provo a mettere in pratica alcune delle idee aggiuntive che avevo

avevo anch'io l'idea di usare un unsigned long long settato a 010101.... e via così
il solo giorno che mi è toccato lavorare... sono stato bruciato
ma fa piacere essere in compagnia di persone con idee ottime

vedamo se riesco a rimettere insieme le altre mie idee:

primo, usare una costante letterale
secondo, elencare i bit a rovescio, per avere i 7 zeri superflui in fondo, non all'inizio
terzo, castare millis() a byte, per troncare ed eseguire ogni 256 millisecondi (poca differenza, ma importante)

e ho raggiunto il valore di
824 byte di programma e 10 di variabili

eccolo, ma state accorti: NON va

// di Nelson-StandardOil
// IDE 1.9 Beta, solo per prova
// Progetto: un SOS Morse automatico
// Scopo:
// Data inizio lavori:


#define SOS 88569621

// assolutamente equivalente a 0101010001110111011100010101 binario
// la soppressione degli zeri superflui fa il resto

byte pos ; // grazie Orso, copiato da te

void setup()
{
   pinMode(LED_BUILTIN, OUTPUT);
}

void loop()
{
   if (!(byte)millis())
      // castare a byte esclude tutti i bit oltre l'ottavo
      // il not invece fa valere la condizione solo se tutti i bit rimasti sono a 0
      // ovvero ogni 256 millisecondi
   {
      digitalWrite(LED_BUILTIN, bitRead(SOS, pos++));

      if (pos > 33)
      {
         pos = 0;
      }

      // incredibilmente usare un modulo (%) aumenta la dimensione del programma
   }
}

incredibilmente è talmente veloce che se la loop() è vuota fa in tempo a ripeterla PRIMA che sia scattato il millisecondo, e quindi spegnersi il suo led appena acceso

serve quindi rallentare artificialmente la loop() se non ci mettete dentro nulla

metteteci un delay(2) e otterrete: 968 byte di programma e 10 di variabili
io meno di così non sono capace, almeno oggi....

Io avrei una scheda uno e ben due oscilloscopi. Mi organizzo e potrei fare da "verificatore grafico", sappiate solo che sono leggermente incasinato in questo fine settimana, non garantisco tempi rapidi.