🇮🇹 IT 🇬🇧 EN

📘 Fai girare un servo G90 360
Tutorial passo-passo in assembly AVR

Partiamo da questo codice che fa ruotare continuamente un servo G90 360 e impariamo ogni singola istruzione

🎯 Il programma che hai caricato

.org 0 rjmp init .org 0x68 init: sbi 4,5 ; DDRB bit5 = 1 (pin D13 come output) sbi 5,5 ; PORTB bit5 = 1 (LED D13 ON) rcall wait1 ; chiama la pausa cbi 5,5 ; PORTB bit5 = 0 (LED D13 OFF) rcall wait1 ; chiama la pausa rjmp init ; ricomincia da capo wait1: ldi r17,32 ; carica 32 nel registro r17 wait: dec r16 ; decrementa r16 (parte da 0 → 255, poi 254...) brne wait ; se r16 non è zero, continua a decrementare dec r17 ; decrementa r17 brne wait ; se r17 non è zero, ricomincia il loop con r16 ret

📌 Questo è il punto di partenza del tutorial. Analizziamolo riga per riga.

passo 1

🗺️ Dove mettere il codice: .org e rjmp

.org 0
rjmp init

🧠 Cosa succede qui?

.org 0 dice all'assemblatore: "il prossimo codice deve essere messo all'indirizzo 0 della memoria del microcontrollore".

All'indirizzo 0 c'è il vettore di reset: quando Arduino si accende o resetta, parte da qui.

rjmp init è un salto (relative jump) all'etichetta init. Serve per saltare la tabella dei vettori di interrupt che occupa i primi indirizzi.

🚪 Metafora dell'armadio

Immagina che la memoria sia un lungo corridoio con tante porte. La porta 0 è l'ingresso principale. .org 0 dice: "mettiamo un cartello alla prima porta". rjmp init è come dire: "non entrare qui, vai direttamente alla porta numero 0x68, dove c'è la festa".

; In sintesi: .org 0 ; all'indirizzo 0... rjmp init ; ...metti solo un'istruzione di salto a init .org 0x68 ; il vero programma inizia da 0x68
passo 2

🔧 DDRB: dire ai pin come comportarsi

sbi 4,5

⚡ SBI = Set Bit in I/O register

sbi 4,5 significa: vai al registro all'indirizzo 4 (che noi chiamiamo DDRB) e metti a 1 il bit numero 5.

Il bit 5 di DDRB corrisponde al pin fisico PB5, che su Arduino è il pin D13 (il LED integrato).

Mettere a 1 un bit in DDRB configura quel pin come OUTPUT. Può quindi mandare corrente (accendere LED) o essere spento.

📊 DDRB (indirizzo 0x04) dopo sbi 4,5

7
6
5 ⬅️
4
3
2
1
0
PB7
PB6
PB5 (D13)
PB4
PB3
PB2
PB1
PB0

✅ Solo il bit 5 è 1 → solo il pin D13 è in modalità OUTPUT. Gli altri pin sono in INPUT (alta impedenza).

🚪 Metafora dell'armadio

Il cassetto 0x04 (DDRB) ha 8 interruttori. Ogni interruttore decide se il corrispondente pin può "parlare" (OUTPUT) o solo "ascoltare" (INPUT). Noi abbiamo attivato l'interruttore #5: ora il pin D13 è pronto per accendere il LED.

passo 3

💡 PORTB: accendere e spegnere

sbi 5,5
cbi 5,5

🔛 SBI e CBI su PORTB

sbi 5,5 = registro 0x05 (PORTB), bit 5 = 1 → il pin D13 diventa HIGH (5V), il LED si accende.

cbi 5,5 = stesso registro, bit 5 = 0 → il pin D13 diventa LOW (0V), il LED si spegne.

Nota: se non avessimo prima impostato DDRB, queste istruzioni non avrebbero effetto (il pin sarebbe in INPUT).

📊 PORTB (indirizzo 0x05) dopo sbi 5,5

7
6
5 ⬅️
4
3
2
1
0
PB7
PB6
PB5 (D13) = 5V
PB4
PB3
PB2
PB1
PB0

⚡ Sul pin D13 ora c'è tensione! Il LED onboard si accende.

; Schema logico: sbi 4,5 ; Abilita l'interruttore (DDRB) sbi 5,5 ; Manda corrente (PORTB) → LED ON cbi 5,5 ; Toglie corrente → LED OFF
passo 4

⏱️ wait1: come contare il tempo

rcall wait1

rcall è una chiamata a subroutine. Salva l'indirizzo di ritorno nello stack e salta all'etichetta wait1. Quando incontra ret, torna indietro.

Analisi della subroutine wait1

wait1: ldi r17,32 ; r17 = 32 (contatore esterno) wait: dec r16 ; r16 = r16 - 1 (parte da 0 → diventa 255, poi 254...) brne wait ; se r16 ≠ 0, torna a 'wait' dec r17 ; r17 = r17 - 1 brne wait ; se r17 ≠ 0, torna a 'wait' (r16 riparte da 0) ret

🧮 Come funziona il contatore?

ldi r17,32 carica il valore 32 nel registro r17. Questo sarà il loop esterno.

Poi si entra in wait:. Nota che r16 non viene inizializzato! La prima volta che il programma esegue dec r16, r16 vale 0 (i registri all'accensione sono 0).

dec r16 decrementa: 0 - 1 = 255 (perché gli 8 bit vanno da 0 a 255, sotto 0 si avvolge a 255). Poi continua 254, 253... fino a 0.

Quando r16 arriva a 0, brne wait non salta (perché zero = condizione falsa) e si passa a dec r17.

r17 viene decrementato: se non è zero, si torna a wait e r16 riparte da 0 (perché non l'abbiamo mai cambiato se non con dec). Quindi il ciclo interno si ripete 32 volte.

⏳ Metafora della clessidra

r16 è come una clessidra piccola che si rovescia 256 volte. Ogni volta che finisce, r17 è un contatore che tiene traccia di quante volte la clessidra piccola si è rovesciata. Quando r17 arriva a 0, è passato abbastanza tempo.

passo 5

🔄 init: il ciclo infinito

init: sbi 4,5 ; configura (lo fa solo la prima volta) sbi 5,5 ; LED ON rcall wait1 ; aspetta cbi 5,5 ; LED OFF rcall wait1 ; aspetta rjmp init ; torna a init (senza riconfigurare DDRB? In realtà sì, ma va bene)

🔄 Analisi del ciclo

Dopo init: abbiamo la configurazione di DDRB (sbi 4,5). Poi accensione, pausa, spegnimento, pausa, e rjmp init.

Nota: rjmp init salta all'inizio, quindi riconfigura DDRB a ogni ciclo. Non serve, ma non fa male: impostare un bit già a 1 non cambia nulla. È solo un po' ridondante.

Il risultato: il LED lampeggia continuamente, con una pausa tra ON e OFF e una pausa tra OFF e ON.

passo 6

⚠️ Il piccolo difetto: r16 non inizializzato

Nel codice originale, r16 non viene mai inizializzato. Funziona perché la prima volta parte da 0, ma è una cattiva pratica: in programmi più complessi potrebbe contenere qualsiasi valore.

✅ Versione corretta

.org 0 rjmp init .org 0x68 init: sbi 4,5 ; DDRB bit5 = 1 loop: sbi 5,5 ; LED ON rcall wait1 cbi 5,5 ; LED OFF rcall wait1 rjmp loop wait1: ldi r17, 32 ; ⬇️ ORA INIZIALIZZIAMO R16 ldi r16, 0 wait: dec r16 brne wait dec r17 brne wait ret

Ora il comportamento è deterministico e il codice è più robusto.

prossimo

🎯 Dove andare ora?

Ora che hai capito le basi, puoi esplorare altri progetti:

⚙️ Motore passo-passo

Sequenze di bit per far girare un motore 28BYJ-48 con ULN2003.

Vai al tutorial →

☠️ Crash ASM

Lezioni etiche dagli errori di stack che hanno causato disastri reali.

Esplora →

📝 Verifica quello che hai imparato

Domanda: Cosa succede se ometto sbi 4,5 e faccio solo sbi 5,5?

Mostra risposta

Il LED non si accende. Perché sbi 4,5 configura il pin come OUTPUT. Se il pin è in INPUT, sbi 5,5 abilita la resistenza di pull-up interna, ma non manda corrente sufficiente ad accendere il LED (e comunque il pin non è in modalità output).

📚 Riassunto istruzioni

Istruzione Significato
.org N Metti il codice successivo all'indirizzo N
rjmp etichetta Salto relativo all'etichetta
sbi indirizzo, bit Set Bit in I/O: metti a 1 il bit del registro
cbi indirizzo, bit Clear Bit in I/O: metti a 0 il bit del registro
rcall etichetta Chiama subroutine (salva indirizzo di ritorno)
ret Ritorna dalla subroutine
ldi reg, valore Carica valore immediato in un registro (r16-r31)
dec reg Decrementa registro
brne etichetta Branch if Not Equal (salta se l'ultima operazione non ha dato zero)