Curiosità sulla programmazione condizionale

Buongiorno,

una mera curiostà, tra gli esempi che riporto qui sotto voi quale preferite e perchè ?

VERSIONE 1

#define ledPin 15

unsigned int blinkingFrequency = 1000;
unsigned long oldBlinkingTime = 0;
bool ledStateOn;

void setup() {
  
  Serial.begin(115200);
  pinMode(ledPin, OUTPUT);

}

void loop() {

  if(millis() - oldBlinkingTime >= blinkingFrequency) {

    if(ledStateOn == true) {
      digitalWrite(ledPin, LOW);
      ledStateOn = !ledStateOn;
    } else {
      digitalWrite(ledPin,HIGH);
      ledStateOn = !ledStateOn;
    }

    oldBlinkingTime = millis();
  }
}

VERSIONE 2

#define ledPin 15

unsigned int blinkingFrequency = 1000;
unsigned long oldBlinkingTime = 0;
bool ledStateOn;

void setup() {
  
  Serial.begin(115200);
  pinMode(ledPin, OUTPUT);

}

void loop() {

  if(millis() - oldBlinkingTime >= blinkingFrequency) {

    digitalWrite(ledPin, ledStateOn? HIGH : LOW);
    ledStateOn = !ledStateOn;
    
    oldBlinkingTime = millis();
  }
}

VERSIONE 3

#define ledPin 15

unsigned int blinkingFrequency = 1000;
unsigned long oldBlinkingTime = 0;

void setup() {
  
  Serial.begin(115200);
  pinMode(ledPin, OUTPUT);


}

void loop() {

  if(millis() - oldBlinkingTime >= blinkingFrequency && digitalRead(ledPin) == HIGH) {
    
    digitalWrite(ledPin, LOW);
    oldBlinkingTime = millis();
    
  }else if(millis() - oldBlinkingTime >= blinkingFrequency && digitalRead(ledPin) == LOW) {
    
    digitalWrite(ledPin, HIGH);
    oldBlinkingTime = millis();
  }
}

so che molto dipende da come uno è abituato a programmare, ma questa mia curiosità nasce dal fatto che molte volte mi fermo a pensare come sia meglio scrivere il mio codice, sicuramente qualcuno di più esperto potrà dire la sua per sanare questa mia curiosità :smiley:

preferisco la quarta versione :slight_smile:

#define ledPin 15

unsigned int blinkingFrequency = 1000;
unsigned long oldBlinkingTime = 0;
bool ledStateOn;

void setup() {
 
  Serial.begin(115200);
  pinMode(ledPin, OUTPUT);

}

void loop() {

  if(millis() - oldBlinkingTime >= blinkingFrequency) {

    digitalWrite(ledPin, !ledStateOn);
    ledStateOn = !ledStateOn;
    oldBlinkingTime = millis();
  }
}

In quanto booleana ledStateOn per pilotare direttamente lo stato del pin.
A parte questo la terza è la peggiore secondo me perché coinvolge in un solo if dua parti logiche che è mi piace più tenere separate (Es. in futuro di aumentazione le condizioni a parità di tempo di millis devi modificare tuti gli if e eventualmente aggiungerne altri)
La prima l'if è "unnecessary" e la seconda è uguale alla mia quarta ma senza il ternario che di nuovo non serve.

fabpolli:
preferisco la quarta versione :slight_smile:

sono d'accordo, e aggiungo che se ledStateOn non serve ad altro, io la eliminerei.

digitalWrite(ledPin, !digitalRead(ledPin));

Federico

Si è fatto tardi e ho letto sommariamente, ma il nome blinkingFrequency non è corretto: non è una frequenza, ma un semiperiodo, quindi si potrebbe chiamare Ton.

fabpolli:
In quanto booleana ledStateOn per pilotare direttamente lo stato del pin.

Azz... vero :cold_sweat:

comunque la terza versione pure a me non piace l'ho messa così per vedere cosa ne pensavate, diciamo io preferisco la prima versione... (più per immediatezza di comprensione in fase di lettura che per altro).

Federico66:
sono d'accordo, e aggiungo che se ledStateOn non serve ad altro, io la eliminerei.

digitalWrite(ledPin, !digitalRead(ledPin));

Verissimo e si ottiene lo stesso risultato con una variabile in meno, ma mi sorge un dubbio non sono due cose diverse ? Cioè utilizzare digitalRead per vedere lo stato di un pin non è diverso rispetto al controllare lo stato di una variabile boleana ? Parlo a livello del micro.

Moce993:
Cioè utilizzare digitalRead per vedere lo stato di un pin non è diverso rispetto al controllare lo stato di una variabile boleana ?

non capisco bene la domanda, in che senso diverso?
se ti riferisci al tempo di esecuzione, non saprei risponderti, ma azzarderai che cambia ben poco.

In ogni caso, la mia è una semplificazione per questo specifico esempio, nella stragrande maggioranza dei casi, probabilmente farai il contrario, assegnerai il valore della digitalRead a qualche variabile che utilizzerai per svariati controlli.

Sia ben chiaro che si sta discutendo di un esempio specifico, e non è possibile generalizzare.

Federico

La digitalread è certamente più lenta

E qualcuno ha detto che va solo sugli AVR
Non so se è vero
Ma fa risparmiare una variabile, servisse

Io preferisco la 5' versione

{
    digitalWrite(ledPin,  ledStateOn =!ledStateOn);
    oldBlinkingTime = millis();
}

Federico66:
se ti riferisci al tempo di esecuzione, non saprei risponderti, ma azzarderai che cambia ben poco.

Visto che in questo thread mi sembrate tutti piuttosto "scafatelli" :grin: ...
... ottimizziamo ancora di più e rendiamo massima la velocità di quel "PIN toggle".

Prima di tutto dovete avere ben a mente i vari pin di Arduino a cosa corrispondono nella MCU ...

//                  ARDUINO ATMEGA328P
//
//                       +-\/-+
//                 PC6  1|    |28  PC5 (AI 5) (D 19)
//           (D 0) PD0  2|    |27  PC4 (AI 4) (D 18)
//           (D 1) PD1  3|    |26  PC3 (AI 3) (D 17)
//           (D 2) PD2  4|    |25  PC2 (AI 2) (D 16)
//       PWM (D 3) PD3  5|    |24  PC1 (AI 1) (D 15)
//           (D 4) PD4  6|    |23  PC0 (AI 0) (D 14)
//                 VCC  7|    |22  GND
//                 GND  8|    |21  AREF
//                 PB6  9|    |20  AVCC
//                 PB7 10|    |19  PB5 (D 13)
//       PWM (D 5) PD5 11|    |18  PB4 (D 12)
//       PWM (D 6) PD6 12|    |17  PB3 (D 11) PWM
//           (D 7) PD7 13|    |16  PB2 (D 10) PWM
//           (D 8) PB0 14|    |15  PB1 (D 9)  PWM
//                       +----+
//
//
//                    BIT NUMBERS
//
//  +-----+-----+-----+-----+-----+-----+-----+-----+
//  | Px7 | Px6 | Px5 | Px4 | Px3 | Px2 | Px1 | Px0 |
//  +-----+-----+-----+-----+-----+-----+-----+-----+
//
//  x = B (PORTB), C (PORTC), D (PORTD)

... così sapete come individuare un dato pin digitale (usiamo per il nostro esempio il pin D10) e trovare in che porta e posizione è ... se guardate nello schemino, (D 10) corrisponde al PIN PB2 della porta B.

A questo punto sfruttiamo una particolarità del ATmega328P ... normalmente l'identificativo PINx indica una lettura dalla porta e si usa in input, ma ... se ci scriviamo, detta MCU effettua automaticamente il "toggle" del pin.

Per cui, dato che ad ogni giro volete invertire il pin rispetto a come era (ovvero fare appunto un toggle), fissato nel nostro esempio che dobbiamo agire sulla porta B e sul pin B2, scriveremo:

PINB = _BV(PB2);

... ed il gioco è fatto! Più veloci di così a fare il toggle è impossibile :smiley:

Guglielmo

P.S.: la macro _BV() è definita in <avr/io.h> come: #define _BV(bit) (1 << (bit))

Interessante. Ma quanto veloce? Riesco a vederlo su un oscilloscopio da 25MHz?

speedyant:
Interessante. Ma quanto veloce? Riesco a vederlo su un oscilloscopio da 25MHz?

Beh ... certo, NON più veloce del clock della MCU non ti pare? :smiling_imp:

Anzi, quel toggle viene così tradotto dal compilatore (listato assembler):

ldi r24, (1<<2)
out PINB, r24

... quindi, almeno due cicli macchina per un toggle :wink:

Guglielmo

Federico66:
Sia ben chiaro che si sta discutendo di un esempio specifico, e non è possibile generalizzare.

Si hai ragione, forse ho sbagliato ad usare come esempio il digitalWrite, la mia voleva essere una considerazione più generica tipo la seguente:

bool condition = true;
unsigned int runTimeFrequency = 1000;
unsigned long oldRunTime = 0;

void setup() {
  
  Serial.begin(115200);

}

void loop() {

  if(millis() - oldRunTime >= runTimeFrequency) {

    if(condition == true) {
      Serial.println("A");
    } else {
      Serial.println("B");
    }

    condition = !condition;
    oldRunTime = millis();
    
  }
}

OPPURE

bool condition = true;
unsigned int runTimeFrequency = 1000;
unsigned long oldRunTime = 0;

void setup() {
  
  Serial.begin(115200);

}

void loop() {

  if(millis() - oldRunTime >= runTimeFrequency) {
    
    Serial.println(condition? "A" : "B");
    condition = !condition;
    oldRunTime = millis();
    
  }
}

Forse così sono riuscito a rendere più generico l'esempio ::slight_smile:

Federico66:
non capisco bene la domanda, in che senso diverso?

Intendevo a basso livello i "passaggi", passatemi il termine, che il micro deve fare per verificare una boleana sono diversi rispetto a quelli che deve fare per controllare lo stato di un pin ? (sono ignorante in materia...) e di conseguenza anche i tempi di reazione.

Ma visto che con il mio esempio ho scoperchiato un vaso di pandora e visto che abbiamo alzato il tiro ne approfitto :smiley:

gpb01:
Visto che in questo thread mi sembrate tutti piuttosto "scafatelli" :grin: ...
... ottimizziamo ancora di più e rendiamo massima la velocità di quel "PIN toggle".

Dipende cosa intendi per "scafatelli" :sweat_smile: (parlo per me ovviamente)

gpb01:
... ed il gioco è fatto! Più veloci di così a fare il toggle è impossibile :smiley:

:o :o :o Ottima spiegazione. Ovvio che bisogna conoscere molto bene il datasheet.

Ma è una caratteristica solo del ATMega328P oppure si può collocare questa caratteristica all'interno di una più vasta macro famiglia ?

Moce993:
Ma è una caratteristica solo del ATMega328P oppure si può collocare questa caratteristica all'interno di una più vasta macro famiglia ?

Famiglia AVR :wink:

Guglielmo

Moce993 se usi if else o un operatore ternario non cambia molto!

Se tu dovessi tradurre in pseudocodice entrami i programmi, lo pseudocodice sarebbe identico.

L' operatore ternario lo tradurresti se vero allora A altrimenti B , e if else altrettanto :slight_smile:

In ogni caso l'operatore ternario (per quanto ad alcuni di voi piaccia particolarmente) è DA EVITARE ed in alcuni ambienti è proprio proibitio.

IBM, nel suo documento "IBM Rational Test RealTime Code Review", che integra alcune delle regole del MISRA C, ha espressamente messo la regola E12.51 (trovate il tutto QUI):

E12.51- Ternary expression ?: should not be used.

Ricordatevene e adattatevi, che vi piaccia o meno ... tanto alla fine, un IF scritto per esteso o un IF fatto con l'operatore ternario, generano lo stesso codice macchina, quindi ... peggiorla la leggibilità, ma NON migliora il codice generato.

Guglielmo

Vediamo se riesco a spiegare le mie idee

Versione 1
Il test uguale uguale true è pleonastico
Anche ripetere ledstate uguale a not ledstate è una inutilità

Versione 2
L'operatore ternario permetterebbe di semplificare eventuali espressioni 'senza' dover esplicitamente usare un branch
Non è uno statement, ma un operatore, si può usare in espressioni complesse con tanti operandi
Li serve, ma qui è un overkill

Versione 3
Perché ripetere un test sui tempi?
Un semplice if nidificato semplifica il test, e lo rende più leggibile

La versione 4 è quella 'più meglio'
Nel senso che è quella canonica, in linea con tutte le regole, anche di leggibilità

La versione (senza numero) col digitalread
Non è portabile, non è leggibile da un non arduinista (la prima volta che la ho vista mi sono domandato se aveste fumato roba buona)
E non va se l'uscita è collegata con ingressi pulluppati (provare per credere, io ci ho perso un pomeriggio)

la versione 5 è la 4, più elegante ma meno leggibile
Inoltre una simile ottimizzazione in scrittura è inutile con compilatori moderni
(Che, spesso purtroppo, ottimizzano di loro sponte)

C1P8:
Inoltre una simile ottimizzazione in scrittura è inutile con compilatori moderni
(Che, spesso purtroppo, ottimizzano di loro sponte)

D'accordo con tutto tranne le parentesi, io penso che per fortuna i compilatori ottimizzano anzi... ormai è impensabile fare a meno dei compilatori con ottimizzazione.
Considerando una CPU hai voglia a scrivere in assempbler più corto e performante possibile a manazza, se scrivi in C e dai in pasto al compilatore ottimizzato Intel ad esempio ti genera magari cinquanta linee assembler in più del codice fatto a mano ma che vengono eseguite in modo più rapido e performante.
Anche su AVR esistono le versioni a pagamento del compilatore che ottimizzando migliorano performance, risorse, ecc.
Chiaro che per un hobbista tutta 'sta ottimizzazione è anche superflua nel 99% dei casi.

Spesso qui ci si è confrontati con utenti che a discapito di leggibilità, sicurezza, ecc. preferiscono scrivere del codice "ottimizzato", altri che si ostinano per far lampeggiare un led ad usare operatori binari sulle porte con bitshift ecc rendendo il codice non portabile e poco leggibile. Quello che dice Guglielmo da sempre e anche qui è che alcune regole in alcuni ambiti vengono imposte proprio per evitare che il programmatore fantasioso posso scrivere del codice potenzialmente non sicuro perché più bello, corto e "performante".
Ottimizzare le risorse e il codice è sempre cosa buona se non si esagera e si va verso problemi o difficoltà a leggerlo dopo cinque giorni, come sempre mia opinione, chi ci lavora davvero può smentirmi, correggermi o apportare la sua più autorevole opinione che a imparare certe cose è sempre una cosa buona :slight_smile:

fabpolli:
(...) ma che vengono eseguite in modo più rapido e performante

"Più rapido" d'accordo, ma che intendi per "più performante"?...

Datman:
Che intendi per "più performante"?...

L'esempio di riferiva ad un ipotetica architettura complessa Intel dove il compilatore ottimizza le istruzioni a discapito della compattezza del codice introducendo l'uso di registri e funzioni che permettono al processore di eseguire, ad esempio, l'esecuzione del codice in parallelo, la pre esecuzione di blocchi di codice identificati come possibili candidati all'esecuzione successiva al blocco attuale, ecc.
Questo rende il tutto più performante in quanto il processore impiegherà vari core e strutture della CPU in modo più efficiente portanto l'esecuzione del codice ad essere più rapidamente eseguita ma non solo, magari lascia libere le strutture della CPU per permettere ad altri processi di essere eseguiti in parallelo quindi aumenta la performance totale del sistema. Non è solo una mera tempistica ma su architetture così complesse ci sono una marea di fattori e periferiche che se accuratamente impegnate fanno si che il sistema riesca ad elaborare e gestire più cose in contemporanea, cosa che difficilmente si ottiene scrivendo a mano il codice, occorrerebbe che il programmatore conosca alla perfezione ogni CPU dove dovrà essere esguito il suo codice e per perfezione non intendo le mere istruzioni assembly del processore ma le interazioni che sono possibili tra le varie strutture di esecuzione della CPU e peculiarità di ogni parte della CPU stessa che è un delirio puro, a parte casistiche molto molto specifiche nessuno fa a meno dei compilatori e delle loro ottimizzazioni, soprattutto su sistemi complessi dovo solo chi lo crea e chi si specializza riesce a stare dietro a tutta la complessità (Es. ditte che forniscono compilatori a pagamento)

fabpolli:
... soprattutto su sistemi complessi dovo solo chi lo crea e chi si specializza riesce a stare dietro a tutta la complessità (Es. ditte che forniscono compilatori a pagamento)

... anche NON così complessi :wink:

Considera che Microchip, se vuoi avere la massima ottimizzazione dai suoi compilatori (XC8, XC16 e XC32) ti fai acquistare la licenza PRO che ... viene 995 US$ per ciascuno dei tre compilatori e vale un anno. Gli anni successivi, per il rinnovo della licenza, il costo è 200 US$ a compilatore.

Non esattamente una sciocchezza come si vede :slight_smile:

Guglielmo

gpb01:
In ogni caso l'operatore ternario (per quanto ad alcuni di voi piaccia particolarmente) è DA EVITARE ed in alcuni ambienti è proprio proibitio.

IBM, nel suo documento "IBM Rational Test RealTime Code Review", che integra alcune delle regole del MISRA C, ha espressamente messo la regola E12.51 (trovate il tutto QUI):

E12.51- Ternary expression ?: should not be used.

Ricordatevene e adattatevi, che vi piaccia o meno ... tanto alla fine, un IF scritto per esteso o un IF fatto con l'operatore ternario, generano lo stesso codice macchina, quindi ... peggiorla la leggibilità, ma NON migliora il codice generato.

Buono a sapersi ! Quindi si potrebbe riassumere dicendo che in questo caso viene generato lo stesso codice macchina ma lo standard C impone l'uso dell'IF al posto del ternario.

Peggiora la leggibilità, sono d'accordo, ma tirando in ballo un altro linguaggio "Swift" affermano il contrario sostenendo sia più leggibile e più corto quindi un vantaggio a detta loro.

ESEMPIO

if buttonPressed {
  view.backgroundColor = .white
} else {
  view.backgroundColor = .black
}

Viene suggerito di utilizzare la forma ternaria per rendere il codice "more cleaner and more readable" aggettivi che usano spesso nelle loro guide e reference, diventando così:

view.backgroundColor = buttonPressed? .white : .black

P.S. Io lo trovo sicuramente più pulito ma di più lenta interpretazione in fase di lettura del codice, questo è il mio pensiero ovviamente