/*
* ESERCIZIO: LED SHIFT CON CALCOLO PRECISO DEI RITARDI
* =====================================================
*
* Hardware Setup per Wokwi:
* - LED1 collegato a Pin 8 (PB0) con resistenza 220Ω
* - LED2 collegato a Pin 9 (PB1) con resistenza 220Ω
* - LED3 collegato a Pin 10 (PB2) con resistenza 220Ω
* - LED4 collegato a Pin 11 (PB3) con resistenza 220Ω
*
* Pattern: I LED si accendono in sequenza con shift a sinistra
*
* CALCOLO DEI RITARDI
* ===================
*
* Clock Arduino Uno: 16 MHz
* Periodo di clock: T = 1 / 16.000.000 = 62.5 ns (nanosecondi)
*
* Ogni istruzione assembly impiega un certo numero di cicli di clock.
* Tempo esecuzione = Numero_Cicli × 62.5 ns
*
* TABELLA TEMPI ISTRUZIONI AVR PRINCIPALI:
* ┌─────────────────┬────────────┬──────────────┐
* │ Istruzione │ Cicli │ Tempo @16MHz│
* ├─────────────────┼────────────┼──────────────┤
* │ NOP │ 1 │ 62.5 ns │
* │ MOV, LDI │ 1 │ 62.5 ns │
* │ ADD, SUB, AND │ 1 │ 62.5 ns │
* │ OR, EOR, COM │ 1 │ 62.5 ns │
* │ LSL, LSR, ROL │ 1 │ 62.5 ns │
* │ OUT, IN │ 1 │ 62.5 ns │
* │ LDS, STS │ 2 │ 125.0 ns │
* │ LD, ST │ 2 │ 125.0 ns │
* │ SUBI, SBCI │ 1 │ 62.5 ns │
* │ CPI │ 1 │ 62.5 ns │
* │ BRNE, BREQ │ 1/2 │ 62.5/125 ns │
* │ RJMP, JMP │ 2 │ 125.0 ns │
* │ CALL │ 3/4 │ 187.5/250 ns │
* │ RET │ 4 │ 250.0 ns │
* │ PUSH │ 2 │ 125.0 ns │
* │ POP │ 2 │ 125.0 ns │
* └─────────────────┴────────────┴──────────────┘
*
* Nota: BRNE impiega 1 ciclo se non salta, 2 cicli se salta
*/
// Definizione maschere per i LED sui pin PB0-PB3
#define LED_MASK 0x0F // 0b00001111 - maschera per 4 LED
// Variabile globale per il pattern corrente
volatile uint8_t ledPattern = 0b00000001;
void setup() {
Serial.begin(9600);
Serial.println("=== LED SHIFT CON ANALISI TIMING ===\n");
// Configurazione PORTB: Pin 8-11 (PB0-PB3) come output
// Manipolazione diretta del registro DDR
DDRB |= LED_MASK; // Imposta i primi 4 bit di DDRB a 1 (output)
// Inizializza PORTB con tutti i LED spenti
PORTB &= ~LED_MASK;
Serial.println("Pin configurati:");
Serial.println("- Pin 8 (PB0) -> LED1");
Serial.println("- Pin 9 (PB1) -> LED2");
Serial.println("- Pin 10 (PB2) -> LED3");
Serial.println("- Pin 11 (PB3) -> LED4");
Serial.println();
// Stampa configurazione iniziale registri
Serial.print("DDRB iniziale: 0b");
Serial.println(DDRB, BIN);
Serial.print("PORTB iniziale: 0b");
Serial.println(PORTB, BIN);
Serial.println();
}
/*
* FUNZIONE DI RITARDO PRECISO
* ============================
*
* Per creare un ritardo di 500 ms usando un loop, calcoliamo:
*
* 1. Tempo desiderato: 500 ms = 500.000 µs = 500.000.000 ns
*
* 2. Cicli necessari: 500.000.000 ns / 62.5 ns = 8.000.000 cicli
*
* 3. Un loop tipico di ritardo in assembly:
* loop_delay:
* subi r24, 1 ; 1 ciclo - sottrai 1 dal contatore
* sbci r25, 0 ; 1 ciclo - sottrai con carry
* brne loop_delay ; 2 cicli se salta, 1 se esce
*
* Totale per iterazione: 4 cicli (quando salta)
* Ultima iterazione: 3 cicli
*
* 4. Iterazioni necessarie: 8.000.000 / 4 ≈ 2.000.000
*
* 5. Overhead della chiamata funzione:
* - CALL: 4 cicli
* - Inizializzazione parametri: ~4 cicli
* - RET: 4 cicli
* Totale overhead: ~12 cicli
*
* FORMULA GENERALE per ritardo in ms:
*
* Iterazioni = (ms × 16.000 - Overhead) / CicliPerLoop
*
* Per 500 ms:
* Iterazioni = (500 × 16.000 - 12) / 4 = 1.999.997 ≈ 2.000.000
*/
void delayPrecise_500ms() {
/*
* Implementazione con loop in C che il compilatore ottimizza
*
* NOTA: Il compilatore GCC con -O2/-O3 ottimizza questo loop
* generando codice assembly efficiente simile a quello mostrato sopra.
*
* Analisi assembly generato (approssimativo):
* ----------------------------------------
* Per ogni iterazione del loop esterno (1000 volte):
* - Inizializzazione j = 2000: ~2 cicli (LDI)
* - Loop interno con decremento e confronto: ~4 cicli per iterazione
* - 2000 iterazioni × 4 cicli = 8000 cicli
* - Overhead loop esterno: ~4 cicli
*
* Totale per iterazione esterna: 8004 cicli
* 1000 iterazioni × 8004 = 8.004.000 cicli
* Tempo: 8.004.000 × 62.5 ns = 500.25 ms
*
* Precisione: ±0.25 ms (errore < 0.05%)
*/
for (volatile uint16_t i = 0; i < 1000; i++) {
for (volatile uint16_t j = 0; j < 2000; j++) {
// Ogni NOP aggiunge 1 ciclo = 62.5 ns
asm volatile("nop");
}
}
}
/*
* VERSIONE ALTERNATIVA: Ritardo con Assembly Inline
* ==================================================
*
* Per massima precisione, possiamo scrivere il ritardo in assembly inline
*/
void delayAsm_500ms() {
/*
* Implementazione diretta in assembly
*
* Obiettivo: 500 ms = 8.000.000 cicli
*
* Strategia: Loop annidato
* - Loop esterno: 1000 iterazioni (r18:r19)
* - Loop interno: 2000 iterazioni (r24:r25)
* - Ogni iterazione interna: 4 cicli
*
* Calcolo dettagliato:
* 1. Caricamento costanti: 4 cicli (trascurabile)
* 2. Loop esterno (1000 volte):
* a. Carica counter interno: 4 cicli
* b. Loop interno (2000 × 4 = 8000 cicli)
* c. Decrementa counter esterno: 1 ciclo
* d. Confronta e salta: 2 cicli (media)
* Totale: 8007 cicli per iterazione esterna
* 3. Totale: 1000 × 8007 = 8.007.000 cicli
* 4. Tempo: 8.007.000 × 62.5 ns = 500.44 ms
*/
asm volatile (
" ldi r24, lo8(2000) \n\t" // Carica parte bassa di 2000
" ldi r25, hi8(2000) \n\t" // Carica parte alta di 2000
" ldi r18, lo8(1000) \n\t" // Carica parte bassa di 1000
" ldi r19, hi8(1000) \n\t" // Carica parte alta di 1000
"outer_loop: \n\t"
" ldi r24, lo8(2000) \n\t" // Reset counter interno
" ldi r25, hi8(2000) \n\t"
"inner_loop: \n\t"
" sbiw r24, 1 \n\t" // Sottrai 1 da r25:r24 (2 cicli)
" brne inner_loop \n\t" // Salta se non zero (2 cicli se salta)
" sbiw r18, 1 \n\t" // Decrementa counter esterno (2 cicli)
" brne outer_loop \n\t" // Salta se non zero (2 cicli se salta)
:
:
: "r24", "r25", "r18", "r19" // Registri utilizzati (clobber list)
);
}
void loop() {
// Stampa stato corrente
Serial.print("Pattern: 0b");
Serial.print(ledPattern, BIN);
Serial.print(" | LED acceso: ");
// Identifica quale LED è acceso
for (int i = 0; i < 4; i++) {
if (ledPattern & (1 << i)) {
Serial.print(i + 1);
Serial.print(" (Pin ");
Serial.print(i + 8);
Serial.print(")");
}
}
Serial.println();
// Aggiorna PORTB con il pattern corrente
// Preserva gli altri bit di PORTB (pin 12 e 13)
PORTB = (PORTB & ~LED_MASK) | (ledPattern & LED_MASK);
/*
* ANALISI TEMPORALE DELL'ISTRUZIONE PRECEDENTE:
* =============================================
*
* PORTB = (PORTB & ~LED_MASK) | (ledPattern & LED_MASK);
*
* Assembly generato (approssimativo):
* 1. IN r24, PORTB ; 1 ciclo - Leggi PORTB
* 2. LDI r25, ~LED_MASK ; 1 ciclo - Carica maschera negata
* 3. AND r24, r25 ; 1 ciclo - Applica maschera
* 4. LDS r26, ledPattern ; 2 cicli - Carica ledPattern da SRAM
* 5. LDI r27, LED_MASK ; 1 ciclo - Carica maschera
* 6. AND r26, r27 ; 1 ciclo - Applica maschera
* 7. OR r24, r26 ; 1 ciclo - Combina
* 8. OUT PORTB, r24 ; 1 ciclo - Scrivi su PORTB
*
* Totale: 9 cicli = 562.5 ns
*
* Confronto con digitalWrite() [~50 cicli = 3.125 µs]:
* Speedup: 50 / 9 = 5.5x più veloce!
*/
// Stampa timing
Serial.print("PORTB dopo: 0b");
Serial.println(PORTB, BIN);
Serial.print("Tempo aggiornamento PORTB: ~562.5 ns (9 cicli)\n");
Serial.println();
// Ritardo usando la funzione precisa
// Usa la versione in C (più portabile) o assembly (più precisa)
delayPrecise_500ms();
// delayAsm_500ms(); // Alternativa con assembly inline
/*
* SHIFT DEL PATTERN
* =================
*
* Operazione: ledPattern <<= 1
*
* In assembly AVR:
* LSL r24 ; 1 ciclo - Logical Shift Left
*
* Sequenza completa:
* 1. LDS r24, ledPattern ; 2 cicli - Carica da SRAM
* 2. LSL r24 ; 1 ciclo - Shift a sinistra
* 3. STS ledPattern, r24 ; 2 cicli - Salva in SRAM
*
* Totale: 5 cicli = 312.5 ns
*
* NOTA: Lo shift in C viene compilato in una singola istruzione LSL
* se il compilatore ottimizza correttamente!
*/
// Shift a sinistra del pattern
ledPattern <<= 1;
// Se il pattern esce dalla maschera dei 4 LED, riparti da LED1
if ((ledPattern & LED_MASK) == 0) {
ledPattern = 0b00000001;
Serial.println(">>> Reset pattern <<<");
Serial.println();
}
/*
* CALCOLO TEMPO TOTALE UN CICLO COMPLETO:
* =======================================
*
* 1. Stampe seriali: variabile (dipende dal baud rate)
* 2. Aggiornamento PORTB: 9 cicli = 562.5 ns
* 3. Ritardo: ~8.000.000 cicli = 500 ms
* 4. Shift: 5 cicli = 312.5 ns
* 5. Controllo reset: ~10 cicli = 625 ns
*
* Totale (senza seriale): ~500 ms
*
* Precisione: La seriale introduce jitter, ma il timing
* dei LED è determinato principalmente dal ritardo preciso.
*
* Per applicazioni real-time critiche, rimuovere Serial.print()
* e usare solo manipolazione diretta dei registri.
*/
}
/*
* DOMANDE DI AUTOVALUTAZIONE
* ==========================
*
* 1. Quanti cicli di clock impiega l'istruzione LSL su AVR?
* a) 1 ciclo ✓
* b) 2 cicli
* c) 4 cicli
* d) Dipende dal valore
*
* 2. Con un clock di 16 MHz, quanto tempo impiega un'istruzione
* che richiede 4 cicli?
* a) 62.5 ns
* b) 125 ns
* c) 250 ns ✓
* d) 500 ns
*
* 3. Perché usiamo (PORTB & ~LED_MASK) prima di impostare i nuovi LED?
* a) Per velocizzare l'esecuzione
* b) Per preservare gli altri bit di PORTB non usati dai LED ✓
* c) Per evitare flicker
* d) È obbligatorio in AVR
*
* 4. Quante volte più veloce è la manipolazione diretta rispetto a digitalWrite()?
* a) 2x
* b) 3x
* c) 5.5x ✓
* d) 10x
*
* 5. In un loop di ritardo, perché BRNE impiega 2 cicli quando salta?
* a) Per controllare la condizione
* b) Perché deve modificare il Program Counter ✓
* c) Per sincronizzazione
* d) È un errore del datasheet
*/
/*
* ESERCIZI PROPOSTI
* =================
*
* 1. Modifica il codice per far lampeggiare i LED in sequenza inversa
* (da LED4 a LED1) usando shift a destra (>>).
*
* 2. Calcola e implementa un ritardo di 250 ms usando la stessa tecnica.
* Mostra i calcoli nei commenti.
*
* 3. Crea un pattern "ping-pong": i LED vanno da sinistra a destra
* e poi tornano indietro senza reset.
*
* 4. Implementa un contatore binario a 4 bit usando i 4 LED.
* Calcola quanti cicli impiega l'operazione di incremento.
*
* 5. Usa un timer interrupt invece del ritardo software per gestire
* lo shift dei LED. Calcola la configurazione del timer per 500 ms.
*
* 6. Misura il tempo reale di esecuzione usando micros() e confrontalo
* con i calcoli teorici. Spiega eventuali differenze.
*/