Programmazione microcontrollori AVR con linux

Condividi:

In questo articolo configureremo un ambiente linux per la programmazione di microcontrollori AVR. Utilizzeremo come programmatore seriale una scheda Arduino 1 rev 3 senza l’IDE di Arduino in combinazione con avrdude. Il microcontrollore di destinazione sarà l’ Atmega328P presente sulla scheda.

Preparazione dell’ ambiente

Per prima cosa installiamo ( se non è già presente ) la toolchain base per la programmazione in C

Installiamo la toolchain avr

Installiamo avrdude e dato che la scheda Arduino viene collegata tramite usb, anche libusb-dev ( se non è già presente )

Test di collegamento

Colleghiamo la scheda Arduino tramite una presa usb e controlliamo che venga rilevata dal sistema tramite lsusb

Vediamo che la scheda è stata rilevata, quindi cerchiamo quale interfaccia tty è stata associata. In /dev/ dovremmo trovare un file chiamato ttyUSB* o ttyACM*

Ricordiamoci di questo file perchè sarà quello che dovremo fornire ad avrdude per comunicare con il programmatore seriale.

Ora che sembra tutto funzionante, proviamo a leggere la device signature del micro presente sulla scheda Arduino.

Dovremo fornire ad avrdude tre parametri:

    • Il programmer-id, ovvero il tipo di programmatore che è elencato nel file /etc/avrdude.conf con l’opzione -c
    • Il partno, ovvero il micro di destinazione sempre elencato nel file di configurazione, con l’opzione -p
    • La porta da utilizzare, con l’opzione -P

Tutti questi parametri resteranno gli stessi ogni volta che utilizzeremo avrdude per comunicare con il programmatore.

Nel nostro caso quindi eseguiremo

avrdude ha letto la device signature del micro montato sulla scheda Arduino rilevando ovviamente che si tratta di un Atmega328p

 Programmiamo il microcontrollore

Scriviamo un semplice programma C composto da un banale ciclo while

Ora compiliamolo con avr-gcc dichiarando qual’è il device per cui generare il codice binario con l’opzione -mmcu

Prima di poter essere inserito nel micro, il file binario va convertito in hex code

Ora possiamo programmare la memoria flash del micro con file hex appena generato. L’opzione -U dice quale operazione eseguire. Il parametro è formato da 4 campi separati da due punti memtype:op:filename:filefmt

    • Il primo è il tipo di memoria su cui operare ( eeprom, efuse, flash, fuse, hfuse, lfuse, lock, signature, fuseN, application, apptable, boot, prodsig, usersig )
    • Il secondo è il tipo di operazione da eseguire ( r=lettura della memoria, w=scrittura leggendo da file, v=verifica che il contenuto della memoria sia identico al contenuto del file )
    • Il terzo è il nome del file
    • Il quarto opzionale è il formato del file ( i=Intel Hex, r=raw binary, e=ELF, a=auto detect, ecc… )

avrdude legge il file, lo scrive nella flash ed esegue la verifica che il codice che è presente nella memoria flash sia quello che è stato scritto.

Lettura della flash del microcontrollore

Con avrdude possiamo anche leggere il contenuto di un microcontrollore, nel nostro caso quello appena programmato. Possiamo leggere il contenuto di varie parti della memoria di un micro: i fuse bits, la flash, la eprom ecc… In questo caso eseguiremo un dump della memoria flash e decompileremo il codice macchina.

Per prima cosa la lettura della flash salvando il contenuto in un file

Nel file flash.hex ora è presente in formato Intel Hex il contenuto della flash del microcontrollore

Disassembliamo il contenuto della flash

Analizziamo il file flash.hex mostrando il contenuto della sezione headers

Notiamo che è presente solo una sezione denominata .sec1

Ora possiamo leggere questa sezione disassemblandone il contenuto e salvando tutto su un file

Il contenuto del file flash.dump è il seguente

  • 0x00: Qui inizia il vettore di interrupt che prevede un elemento per ogni interrupt della cpu. Il primo elemento del vettore è collegato all’ interrupt RESET. E’ la prima istruzione che esegue la mcu alla partenza o dopo un RESET e troviamo un salto all’indirizzo  0x68 da cui inizia il programma vero e proprio.
  • 0x04: Da qui fino a 0x64 si trovano tutti gli altri elementi del vettore di interrupt, quando sono programmati eseguono una jump all’indirizzo dove inizia la ISR relativa. Non avendo programmato nessuna ISR, da qui troviamo solo delle jump all’indirizzo 0x7c, dove troviamo una jump all’indirizzo 0x00 ovvero l’interrupt di RESET.
  • 0x68: Questo indirizzo ed il successivo inizializzano a 0 il registro di stato SREG che si trova all’indirizzo 0x3f
  • 0x6c: Questo indirizzo ed il successivo inizializzano i registri r28 ed r29 che rappresentano i byte basso ed alto dell’ Y register. Questo registro viene inizializzato con il valore 0x08ff ovvero l’ultimo indirizzo della SRAM interna
  • 0x70: Questo indirizzo ed il successivo inizializzano i registri SP_H ed SP_L dello stack pointer, che si trovano rispettivamente alle posizioni 0x3d e 0x3e, con i valori appena salvati sull’ Y register. Il motivo per cui viene inizializzato il puntatore di stack con l’ultimo indirizzo della SRAM è semplice: la stack cresce decrementando questo indirizzo e decresce incrementandolo. In sostanza ha preparato i puntatori dei registri Y e STACK anche se non sono state dichiarate variabili.
  • 0x74: Viene eseguita la call che lancia la funzione main del listato c
  • 0x78: Questa istruzione viene eseguita casomai terminasse la funzione main con un’istruzione ret, viene fatta una jump all’indirizzo 0x8a
  • 0x7c: Qui non ci si dovrebbe mai arrivare, nel caso ci si arrivasse, viene fatta una jump all’indirizzo 0x00 facendo ripartire tutto dall’inizio
  • 0x80: Qui inizia la funzione main. Da questo indirizzo fino a 0x86 vengono salvati sull’ Y register i valori di SP_L ed SP_H. Se ci fossero variabili dichiarate verrebbero puntate da questo valore.
  • 0x88: Esegue il jump relativo all’indirizzo 0x88, ovvero se stessa. Rappresenta il ciclo infinito while(1) del listato c
  • 0x8a: Qui il programma non arriverà mai. Il ciclo while infinito fa si che nel compilato manchino tutte le istruzioni per l’uscita dalla funzione main come la ret e tutte quelle per recuperare i valori salvati sulla stack. Viene eseguita una cli che disabilita il global interrupt flag
  • 0x8c: Anche qui il programma non arriva mai a meno che non sia stata eseguita la riga precedente, viene eseguito un altro loop infinito tramite jump relativo allo stesso indirizzo
  • 0x8e: Da qui in poi troviamo tutti op code 0xff perchè non è stato caricato nulla nella flash
Makefile

Possiamo includere i comandi di compilazione e caricamento in un makefile