Programmare l’Arduino in puro C: primi passi

Sicuramente uno dei maggiori punti di forza dell’Arduino è la facilità nel creare le proprie applicazioni (sketch) per mezzo di ambiente di sviluppo e linguaggio di programmazione estremamente semplici.

D’altra parte è possibile bypassare l’utilizzo delle librerie per lo sviluppo, come wiring, per creare applicazioni scritte direttamente in C, come se si stesse scrivendo un programma per il proprio pc. In questo modo quindi l’Arduino non viene programmato con sketch aventi la struttura composta da un void setup e un loop, ma con un programma che viene scritto per il microcontrollore stesso, quindi caratterizzato dalla dichiarazione di variabili, definizione di funzioni, set dei bit di registro etc.
I vantaggi? Di certo un’ottimizzazione dello spazio occupato dallo sketch stesso; infatti lo stesso programma scritto con l’IDE (e quindi con la libreria) e scritto in puro C comporta una differenza nell’occupazione di memoria, ovviamente con il secondo metodo si ha minore ingombro.

Questa prima parte tratterà le risorse software , i comandi e le prime spiegazioni su una delle componenti fondamentali dei microcontrollori: i registri. Per semplicità si svilupperà un semplice programma simile al “blink” disponibile tra gli esempi dell’Arduino.

Iniziamo!

Risorse software

Supponendo di aver già installato l’IDE sulla nostra macchina si farà uso dell’ avr-gcc( il compilatore), avr-objcopy e avrdude( il programmatore del nostro Arduino), gli stessi componenti utilizzati nel software distribuito da Arduino.

Per i sistemi operativi Linux questi programmi sono accessibili direttamente dal terminale mentre per windows si trovano nel percorso “/hardware/toold/avr/bin” relativi alla nostra cartella d’installazione dell’IDE.

Ci serviranno inoltre delle librerie associate a queste applicazioni pensate appositamente per gli AVR ossia le librerie avr-libc( si può scaricare qui l’ultima versione, la 1.8.0) che ci permettono di avere delle funzionalità indispenabili per poter programmare per il nostro Arduino senza far riferimento a comandi dell’Assembly.

PinOut dell’Arduino e porte corripondenti

Nel nostro progetto non possiamo far riferimento ai pin esterni che abbiamo sull’Arduino, ma dovremo utilizzare i valori delle porte e dei pin dell’ATMega328.

Di seguito è riportata un immagine che mostra il pinout dell’ATMega168(che è lo stesso del 328) e i corrispondenti pin sull’Arduino ( cliccare sull’ immagine per ingrandirla).

Le porte e i registri

Come si può vedere dal pinout la maggior parte dei pin è composto da 3 caratteri: il primo ‘P’ sta per “Port”, il secondo identifica il tipo di porta e il terzo il bit corrispondente nel registro.

Nell’ ATMega ci sono 3 tipi di porte:

  • Porta “B” : comprende i pin digitali dal numero 8 al 13.
  • Porta “C” : comprende i bit analogici di input (A0-A5).
  • Porta “D” : comprende i pin digitali dal numero 0 al 7.

Per manipolare i pin delle varie porte abbiamo opportuni registri. I registri non sono altro che delle locazioni di memoria note al microcontrollore nel quale vengono scritte le giuste informazioni per far funzionare il nostro programma. Per semplificare il lavoro e rendere più comprensibile il codice, i registri hanno un nome leggibile, che poi il compilatore interpreterà come la vera e propria locazione di memoria.

Per esempio le porte hanno dei semplici registri per settare, scrivere e leggere sui singoli pin. Prendendo per riferimento la porta B essi sono:

  • DDRB : registro che tiene in memoria se i pin sono di input(0) o outpit(1). E’ possibile leggerci e scriverci.
  • PORTB : registro che serve ad impostare il valore di output di un pin. E’ possibile leggerci o scriverci.
  • PINB : registro che serve a leggere il valore di input di un pin in formato digitale( 0 o 1). E’ possibile solo leggerci.

Anche le porte C e D hanno dei registri analoghi nei quali cambia solo l’ultima lettera che lo costituisce(es. PORTD e PORTC).

Esistono registri per impostare qualunque feature del microcontrollore che ci può servire (pwm, lettura analogica, timing e comunicazione ad esempio).

Manipolare i registri

Dovendo lavorare con i bit dovremo utilizzare operazioni bitwise ossia bit-a-bit e inoltre, come per i registri, le librerie ci consentono di utilizzare direttamente i nomi dei pin invece che il loro valore binario per semplificarci il lavoro.

Ad esempio il registro DDRB avrà i seguenti bit che possiamo impostare:

Possiamo utilizzare il comando _BV() (Bit Value) che serve a creare un valore in bit nel quale l’unico valore settato ad 1 è quello del pin corrispondente al registro e utilizzare i nomi dei pin

Ad esempio _BV(DDRB5) avrà come risultato il valore binario 0010000.

Quindi se vogliamo impostare come abbiamo fatto precedentemente il pin13 come OUTPUT possiamo semplicemente scrivere:

DDRB |= _BV(DDRB5);

N.B. l’operatore “|=” è un OR bit a bit tra DDRB e _BV(DDRB5) e il risultato viene assegnato a DDRB: praticamente viene settato ad 1 (o lasciato alterato se lo era già) solo il bit corrispondente a PB5.

N.B. il comando _BV() non è un comando standard e per mantenere la massima compatibilità è consigliato utilizzare una sintassi del tipo :  DDRB |=  (1 << DDRB5);

Senza questi “aiuti” avremmo dovuto scrivere:

DDRB |= B0010000;

Il che risulta molto meno leggibile.

Invece se dovessimo far passare lo stato del pin13 (che abbiamo impostato come OUTPUT manipolando precedentemente il DDRB) da 1 a 0 (praticamente spegniamo il led) dovremmo scrivere:

PORTB &= ~_BV(PORTB5);

N.B. l’operatore “&=” è un AND bit a bit tra DDRB e _BV(PORTB5) con il complemento ad uno e il risultato viene assegnato a DDRB: praticamente ~_BV(PORTB5) è un byte con tutti i bit a 1 tranne quello corrispondente a PB5 e, facendo un’ operazione di AND con PORTB farà in modo che tutti i bit rimangano inalterati tranne PB5 che verrà settato a 0.

La nostra applicazione

L’applicazione che andremo a scrivere sarà un semplice “blink” utilizzando il pin 13 dell’Arduino( e quindi non dobbiamo nemmeno usare un led esterno).

Apriamo un semplice blocco note e scriviamo il seguente codice: 

#include
#include

void main (void)
{
/* settiamo il pin13 (PB5) come output*/
DDRB |= _BV(DDB5);

while(1) {
/* accendi il led */
PORTB |= _BV(PORTB5);
_delay_ms(500);

/* spegni il led */
PORTB &= ~_BV(PORTB5);
_delay_ms(500);
}

}

Nella prima parte viene fatto l’include del file io.h e delay.in.h presenti all’interno della cartella di avr-libc/include

Come sempre ci serve un punto di ingresso per la nostra applicazione : la funzione main() che, a differenza di come siamo abituati normalmente, non riporta niente e quindi il tipo di dato di ritorno sarà void.

Nella prima parte impostiamo il bit del nostro pin13 come OUTPUT ed entramo nel loop.

Viene utilizzata la funzione _delay_ms(500) per impostare un delay di 500 ms, successivamente viene impostato ad uno l’uscita del pin 13, richiamata un’altra volta la funzione di delay e riazzerato il bit del nostro LED. 

Salviamo questo file con il nome “led.c”.

Compilazione

(i comandi successivi sono stati eseguiti su una distribuzione Linux)

Apriamo il terminale o la console, portiamoci nella cartella del nostro progetto e digitiamo:

avr-gcc -Os -DF_CPU=16000000UL -mmcu=atmega328p -c -o led.o led.c

avr-gcc -mmcu=atmega328p led.o -o led

avr-objcopy -O ihex -R .eeprom led led.hex

avrdude -F -V -c arduino -p ATMEGA328P -P /dev/ttyUSB0 -b 57600 -U flash:w:CCGen.hex

N.B nell’ultimo comando se si utilizza un componente esterno(come l’adattatore USB-Seriale) bisogna impostare un baud-rate di 115200 invece di 57600 altrimenti avrdude riporterà un’errore dicendo che il programmatore non è in sync.

Riferimenti

Fondamentale il datasheet dell’ ATMega328 : disponibile qui.

Il manuale di avr-libc : disponibile qui.

Questo articolo è tratto da(in inglese) : Programming Arduino in pure C.

Luca Panebianco
Ciao! Mi chiamo Luca e frequento la magistrale in Ingegneria Informatica e dell'Automazione all'Università Politecnica delle Marche. Qui ho apprezzato pian piano la programmazione su microcontrollori oltre che la canonica sul PC. Sono interessato ai vari campi della programmazione su diversi dispositivi.
By Luca Panebianco | aprile 19th, 2012 | SHOW COMMENTS (9)

9 Responses

  1. Giovanni says

    interessante,
    ho programmato in C via ICSP i PIC microchip il quel caso non era presente alcun bootloader.
    Nel caso da voi esposto si tratta di una programmazione analoga (diretta sul micro attraverso i 6 PIN seriali ICSP?) o si lavora ancora con la USB seriale e il bootloader tipico delle schede Arduino?
    Dalla lettura mi sembra sia rimasto implicito. Se mi è sfuggito qualcosa mi scuso.
    Grazie

  2. Stefano says

    Interessante questo articolo!
    Prima di mettermi ad approfondire il discorso, volevo farti alcune domande:

    quali sono i vantaggi di usare questo approccio piuttosto che quello più semplificato dell’IDE arduino?
    Immagino che:
    – sicuramente ci sarà la possibilità di usare più a fondo le funzionalità del micro.
    Cosa interessante se ce ne dovesse essere la necessita

    – probabilmente il file .hex risultante sarà più contenuto e performante rispetto a quello generato dall’ide arduino

    – si risparmia lo spazio del bootloader (cosa che già si può fare scrivendo il file .hex con un programmatore tipo USBASP)

    – non so se si potranno usare in maniera indolore le librerie già pronte per arduino.
    Sarebbe interessante se fosse possibile

    La cosa mi intrippa parecchio e sono abbastanza convinto che ne valga la pena, però vorrei capire se effettivamente è così.
    Grazie.
    Ciao.

    • Luca Panebianco
      Luca Panebianco says

      Ciao,
      Credo che il vantaggio fondamentale sia nella maggiore comprensione di come funzioni il micro e “vedere” cosa ti nasconde l’ide(come l’impostazione del timer0 e l’interruzione per contare i millisecondi della funzione millis() ), argomenti sicuramente da approfondire dopo un po’ che uno ha giocato con l’Arduino, sopratutto per il fatto che queste conoscenze si possono utilizzare in un qualunque altro micro(a me è servito come introduzione al mondo dei PIC della Microchip).
      Questo però lo ritengo un approccio utile solo a scopo “educativo” in quanto porta solitamente a più errori e tempi di sviluppo più elevati rispetto all’utilizzo di un’IDE .
      Non l’ho mai testato dal punto di vista di occupazione di memoria, ma le funzioni che utilizza l’ide sono abbastanza ben fatte, e soprattutto ti sgravano parecchio lavoro da fare (ad esempio una gestione avanzata e profonda delle pwm dei pin 9 e 10 è già fatta da opportune librerie esterne e oramai qualunque sensore esistente ha una libreria già pronta) che nessuno però ti vieta di aprire, approfondire e migliorare.
      Per quanto riguarda l’ultimo punto credo che ogni libreria sia da modificare in quanto possono usare tutto un set di funzioni già definite in altre librerie(di sicuro quelle definite includendo Arduino. h).

      Spero di averti risposto(almeno in parte). Per qualunque curiosità siamo qui ☺

      • Stefano says

        L’unica cosa che mi spaventa di questo approccio è proprio quello di non riuscire ad utilizzare le librerie già pronte per arduino.
        Su alcune cose so già che non riuscirei a scrivere del codice mio per rimpiazzarle.
        Per quanto riguarda il fatto che l’IDE ti sgrava da un po’ di lavoro è vero, però nulla che non si possa risolvere scrivendo delle proprie librerie ad-hoc.
        Comunque sia, il fatto di avere più controllo sugli oggetti che uso è una cosa che mi ha sempre attratto.
        Ti ringrazio per la risposta e disponibilità 🙂

  3. valentino says

    Ciao, a 2 anni dalla sua pubblicazione questa guida è comunque interessante!
    Per programmare l’integrato si usa il programmatore della stessa scheda? quindi senza rimuovere l’integrato..?
    Così facendo però si sovrascrive il bootloader giusto? quindi sarà imposibile riprogrammarlo con l’ide (se non si ricarica il bootloader).
    Grazie in anticipo per la risposta!
    E grazie della condivisione!

  4. gigio says

    Grazie. Ottima guida. Gli underscore si usano per tutte le funzioni?

    • Automazione Open Source
      automazioneos says

      asda

  5. Qualcuno sperimenta con Arduino? - Pagina 14 - BaroneRosso.it - Forum Modellismo says

    […] per conoscere l'ATMEL 328 e arduino: Arduino UNO Tutorial 4 – Simple Cylon Sempre per iniziare : Programmare l'Arduino in puro C: primi passi | Automazione Open Source […]

  6. Balau says

    Ciao,
    grazie per aver tradotto e arricchito il mio post, e complimenti per il sito!
    Francesco