Come programmare un microcontrollore ARM in Linguaggio Assembly – Parte 2

arm logo series

Nella prima parte dell'articolo sono stati introdotti brevemente i microcontrollori ARM Cortex-M, si è parlato dei vari set di istruzioni, in genere Thumb-1 e Thumb-2 e sono stati presentati alcuni esempi. Questa seconda parte è invece molto più pratica e mostrerà le diverse modalità con cui è possibile interfacciarsi a linguaggi di più alto livello con il Linguaggio C, partendo però da una trattazione delle istruzioni di Branch, utili sia per creare i cicli sia per spostarsi da una procedura ad un'altra.

Branch

Le istruzioni di Branch sono utili per il controllo di flusso, a questo proposito citerei il teorema di Böhm-Jacopini, enunciato nel 1966, che afferma:

Qualunque algoritmo può essere implementato utilizzando tre sole strutture, la sequenza, la selezione e l'iterazione, che possono essere tra loro innestate fino a giungere ad un qualsivoglia livello di profondità finito (come le scatole cinesi).

Essenzialmente, le principali istruzioni di questo gruppo sono B, BX,BL e BLX. L'istruzione B permette di caricare sul Program Counter (PC) o R15 un indirizzo di memoria, in pratica permette di spostare il flusso di esecuzione all'indirizzo di memoria della procedura a cui si vuole accedere. Per convenzione e comodità, l'indirizzo di memoria delle procedure viene identificato univocamente con il nome della label o etichetta. Le istruzioni BL e BLX sono simili all'istruzione B, con la differenza che vanno a spostare il valore della prossima istruzione sul registro R14, Link Register (LR), in modo da poter tornare alla procedura chiamante, al termine della procedura richiamata. BLX essenzialmente permette di spostare il set di istruzioni. In ultimo, parliamo dell'istruzione BX che serve per il caricamento indiretto della locazione di memoria a cui fare il Branch. Questa istruzione è del tipo:

BX <Rd>

dove Rd è il registro di destinazione, contenente la locazione di memoria dove eseguire il salto.

Tutte queste istruzioni possono essere condizionate. Vediamo un esempio di loop con istruzione di Branch:

; Esempio, somma dei primi 10 numeri interi. Il risultato verrà caricato in R2
mov r2, #1  ;carico 0 nel registro R2
Loop:add r2, r2, #1 add r0, r0, #1 @ Incremento R0
cmp  r0, #10 @ Verifico il limite
ble Loop  @ Torno alla label loop se non ho finito

In quest'ultimo esempio si può vedere un caso di salto condizionato tramite l'istruzione BLE. Da notare che i salti condizionati, molto spesso, ma comunque non sempre, vengono eseguiti dopo l'operazione di CMP (Compare).

Istruzioni condizionate

Prima di passare alle istruzioni di Load e Store, ci soffermiamo un attimo sulla possibilità di eseguire le istruzioni solo in caso di alcune ben definite condizioni del registro CPRS. Ricordo comunque che le architetture che implementano unicamente le istruzioni Thumb_1, per intenderci ARM Cortex-M0 ed M0+, non permettono le istruzioni condizionate, eccetto che per le istruzioni di Branch. Tra gli esempi del paragrafo dedicato abbiamo già visto un esempio di istruzione condizionata, con MOVLE, dove viene eseguita l'istruzione di MOV solo al verificarsi della condizione LE. Anche nell'esempio di salto condizionato abbiamo visto l'istruzione condizionata BNE. Vediamo ora di riportare una tabella con tutti i valori possibili delle istruzioni.

Condizione Descrizione CPSR Flag
EQ Equal (the result is zero) Z == 1
NEQ not Equal (the result is not 0) Z == 0
CS or HS the Carry bit is set C == 1
CC or LO the Carry bit is cleared C == 0
MI MInus (the result is negative) N == 1
PL PLus (the result is positive) N == 0
VS the V flag is set (signed overflow) V == 1
VC the V flag is Cleared (no signed overflow) V == 0
HI unsigned HIgher (after comparision) C == 0 && Z == 0
LS unsigned Lower (after comparision) C == 0 && Z == 1
GE signed Greater than or Equal N == V
LT signed Less Than N != V
GT signed Greater Than N == V && Z == 0
LE signed Less than or Equal N != V && Z == 1
NV NeVer (ARMv1 and ARMv2 only) do not use FALSE

Istruzioni Load e Store

Abbiamo già accennato in precedenza, nella prima parte, che l'architettura ARM è una architettura di tipo Load e Store. Abbiamo già analizzato velocemente le istruzioni di Load e Store quando abbiamo trattato i tipi di dati; vediamo adesso più in dettaglio, le funzionalità di queste istruzioni. L’istruzione LDR (Load Register) carica in un registro destinatario un byte, una half word o una full word contenuta in una data locazione della memoria principale. Mentre, l’istruzione STR (Store Register) carica da un registro sorgente un byte, una half word o una full word in una data locazione della memoria principale.

Essenzialmente, le istruzioni di Load e Store sono strutturate nel modo seguente:

LDR Ra, [Rb]   @ [Rb] - l'indirizzo di origine è il valore presente in Rb
STR Rb, [Ra]   @ [Ra] - l'indirizzo di destinazione e il valore presente in Ra

STR    Ra, [Rb, imm]  - Forma con immediate in pratica imm viene sommato al valore di Rb
LDR    Ra, [Rb, imm]  - Forma con immediate in pratica imm viene sommato al valore di Rb

Vediamo un esempio:

.data var1: .word 3 var2: .word 4 .text .global func1 func1:    ldr  r0, adr_var1  @ carico il valore dell'indirizzo di memoria adr_var1 in r0
    ldr r1, adr_var2  @ carico il valore dell'indirizzo di memoria adr_var1 in r1 
    ldr r2, [r0]      @ carico il valore in r2 all'indirizzo presente in r0 
    str r2, [r1]      @ carico il valore di r2 in memoria centrale all ndirizzo presente in r0 
    bl             

adr_var1: .word var1  /* indirizzo di var1 */
adr_var2: .word var2  /* indirizzo di var2 */

L'esempio tratta unicamente dati di tipo word, ovviamente come detto in precedenza possiamo usare anche byte ed half-word andando opportunamente a inserire gli opportuni suffissi (h e b) alle istruzioni, come descritto in precedenza. Per completezza, è utile definire il significato della direttiva .word che indica il tipo di valore da trasferire in memoria. Analogamente, si possono inserire altri tipi di valore come per esempio .byte, queste parole infatti non appartengono al linguaggio Assembly, ma sono invece direttive del compilatore. Quindi, per conoscerne pienamente il significato, è utile avere a portata di mano il manuale del compilatore.

Istruzioni Push e Pop

Queste due istruzioni, molto utili quando ci si sposta da una funzione/procedura ad un'altra, permettono di salvare il contenuto di una serie di registri dalla o verso la memoria centrale. L'istruzione push permette di inserire il contenuto di una serie di registri, nell'area di stack della RAM. Viceversa, l'istruzione pop permette di caricare i registri della core ARM con valori presi dallo stack. Ovviamente, il registro a cui si fa riferimento per conoscere l'indirizzo di memoria su cui leggere o scrivere è lo Stack-Pointer (SP). [...]

ATTENZIONE: quello che hai appena letto è solo un estratto, l'Articolo Tecnico completo è composto da ben 2526 parole ed è riservato agli ABBONATI. Con l'Abbonamento avrai anche accesso a tutti gli altri Articoli Tecnici che potrai leggere in formato PDF per un anno. ABBONATI ORA, è semplice e sicuro.

Scarica subito una copia gratis

Scrivi un commento

Send this to a friend