PrettyPrint

giovedì 28 ottobre 2010

Usare Arduino come devboard per software scritto con avr-libc




La tentazione è forte: abbiamo comprato un'ottima scheda italiana Arduino e ora vogliamo cimentarci oltre il linguaggio Wiring, con programmi in assembler o in C (es. avr-libc).

Non c'è bisogno di comprare una scheda di sviluppo, perché è possibile usare il nostro Arduino come una scheda di sviluppo per programmi scritti in avr-libc, assembly, piuttosto che per gli sketch in linguaggio wiring, senza la necessità di un programmatore hardware esterno: il nostro codice oggetto verrà eseguito dal boot loader del micro di Arduino, in modo semplice e soprattutto totalmente "indolore"...

Il nostro file config va modificato aggiungendo delle opzioni per avrdude (il software di programmazione per i micro AVR) che ci consentiranno di "uploadare" il codice oggetto sulla nostra scheda Arduino.

Di seguito il file config per Arduino 2009 (ATmega168 e clock da 16MHz). Per successive versioni di Arduino fare riferimento al tipo di micro e alla frequenza del quarzo.


# MCU name

MCU = atmega168

# Processor frequency (in Hz)

F_CPU = 16000000

# Target file name (without extension)

TARGET = main

# Object files directory
#     To put object files in current directory, use a dot (.), do NOT make
#     this an empty or blank macro!

OBJDIR = .

# List C source files here. (C dependencies are automatically generated.)
# The first one must be TARGET above defined, the other ones
# must be separated by a space. Examples:
# SRC = $(TARGET).c lcd.c i2c.

SRC = $(TARGET).c


# List C++ source files here. (C dependencies are automatically generated.) spaced

CPPSRC = 

# List Assembler source files here.
#     Make them always end in a capital .S.  Files ending in a lowercase .s
#     will not be considered source files but generated files (assembler
#     output from the compiler), and will be deleted upon "make clean"!
#     Even though the DOS/Win* filesystem matches both .s and .S the same,
#     it will preserve the spelling of the filenames, and gcc itself does
#     care about how the name is spelled on its command-line.

ASRC =



# List any extra directories to look for include files here.
#     Each directory must be seperated by a space.
#     Use forward slashes for directory separators.
#     For a directory that has spaces, enclose it in quotes.

EXTRAINCDIRS = 

# List any extra directories to look for libraries here.
#     Each directory must be seperated by a space.
#     Use forward slashes for directory separators.
#     For a directory that has spaces, enclose it in quotes.

EXTRALIBDIRS = 


# Porta programmatore
# COM1...COMx (porta seriale), LPT1 (porta parallela)
# Linux: /dev/ttyUSB0 oppure /dev/ttyUSB1

AVRDUDE_PORT = /dev/ttyUSB0  

# Tipo programmatore (stk500v1 per Arduino)

AVRDUDE_PROGRAMMER = stk500v1

# Velocità seriale (19200bps)

AVRDUDE_PORTSPEED = 19200




AVRDUDE_PORT: specificare il device dove è collegato Arduino. Sotto Linux sarà generalmente /dev/ttyUSB0. Per conoscerlo lanciare da shell:


$ dmesg | grep FTDI

e trovare la riga dove viene specificata la porta:

[20560.252484] usb 4-3: FTDI USB Serial Device converter now attached to ttyUSB0

L'opzione AVRDUDE_PROGRAMMER non va modificata, a meno che non si voglia usare il nostro makefile con altri programmatori hardware.

Come software di prova, usiamo un semplice programmino che accende cinque LED in successione. Attraverso un pulsante è possibile selezionare l'effetto di scorrimento desiderato (MODO) e attraverso un secondo pulsante è possibile modificare il tempo di lampeggio da 50ms a 500ms in step da 50ms.  Lo schema è raffigurato di seguito:


(schema creato con gEDA)


Per quanto riguarda la corrispondenza pin ATmega168 / pin Arduino:



(immagine creata con Fritzing)



Metodo di risoluzione:  ad intervalli di tempo regolari (scanditi da un timer), cambierà la combinazione di LED accesi (combinazione che chiamiamo frame), secondo una modalità definita nel vettore del relativo modo.
Ogni elemento di questo vettore contiene un byte che codifica l'accensione dei sei LED, frame per frame. La convenzione è 1 -> LED acceso, 0 -> LED spento.  Il vettore verrà così scandito (a un intervallo di tempo definito da un timer) in ricircolo.

Per esempio, il modo out_in, avrà la seguente sequenza di frame. Arrivato all'ultimo elemento si azzera il contatore dei frame, ripartendo così dal primo elemento del vettore:




Per i tempi viene impiegata una temporizzazione hardware (basata su Timer1 in modo CTC con interrupt) così da mettere ancora meglio in pratica quanto visto nei post precedenti e per la gestione dei pulsanti vengono usati i due interrupt INT0 e INT1. Per la modifica dei tempi, viene modificato in fase di esecuzione il registro OCR1A a "step" di 50ms. Il porgramma parte con un tempo di 50ms e quindi dopo 9 pressioni del pulsante "Tempo" si raggiungerà il valore massimo di 0,5s; alla decima pressione il tempo riparte da 50ms

50ms, 1-> 100m, 2-> 150m, 3-> 200m, 4-> 200m, 5->250m, 6->300m, 7->350m, 8->400m, 9->500ms

Di seguito il sorgente (main.c):


/*
 * LIGHTS 
 * fp, 2010
 */

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <util/delay.h>


#define N            31249  /* Numero del timer1 per avere un tempo max (31249=0.5s@fck=16MHz:256) */
#define NA           3124   /* Numero del timer1 per avere uno step minimo (3124=50ms@fck=16MHz:256) */
#define NMODI        4      /* Numero di modi di funzionamento */
#define PORTDATALED  PORTB  /* Porta dove sono collegati i sei LED */
#define PORTDDRLED   DDRB   /* Registro direzione di PORTDATALED */


/* Prototipi delle routine dei vari modi di funzionamento */
void Shift_DX();
void Shift_SX();
void Shift_SX_DX();
void Out_In();


/* Variabili globali (classificate static perché devono "vivere" anche all'uscita delle funzioni */
/* e qualificate volatile per quelle condivise con le routine di servizio interrupt) */
static int frame=0;                   /* Frame iniziale da visualizzare */
volatile static uint8_t modo=0;       /* Modo iniziale di funzionamento (da 0 a NMODI -1) */
static uint8_t stato=0;               /* Variabile di stato (usata per il modo ShiftSXDX e OnOff) */


/* Sequenze di frame per ciascun modo */
/* memorizzate nella memoria flash (per risparmiare RAM) */
const uint8_t  sx_dx[6] PROGMEM = {0b00100000, 0b00010000, 0b00001000, 0b00000100, 0b00000010, 0b00000001};
const uint8_t out_in[4] PROGMEM = {0b00100001, 0b00010010, 0b00001100, 0b00010010};



/* Inizializza/configura l'hwardware (direzione pin I/O, timer, interrupt) */
void init()
{
   PORTDDRLED |= 0b00111111; /* pin Px5..Px0 di uscita */
   EIMSK |= (1 << INT0)| (1 << INT1) ; // abilita interrupt INT0 e INT1
   TCCR1B |= (1 << WGM12); // imposta il Timer1 in modo CTC (su OCR1A)
   OCR1A = NA;      // carica NA nel registro OCR1A (tempo minimo)
   TIMSK1 = (1 << OCIE1A);  // abilita interrupt CTC su OCR1A
   TCCR1B |= (1 << CS12); // abilita prescaler (fck/256)
}



int main (void)
{

   init();

   sei();  // abilita gli interrupt

   while(1)
   {
      /* non fa niente...aspetta un'interruzione */
   }
}



/* Routine servizio interrupt TIMER1 CTC: viene eseguita ogni (OCR1A+1)*256*Tck sec. */
ISR( TIMER1_COMPA_vect )
{
   switch(modo){
      case 0: Shift_DX();
        break;
      case 1: Shift_SX();
        break;
      case 2: Shift_SX_DX();
        break;
      case 3: Out_In();
        break;  
   }
}



/* Routine servizio interrupt INT0 (pulsante variazione del tempo in step di 50ms ) */
ISR ( INT0_vect )
{
  OCR1A += NA;    // incrementa di 50ms
  if (OCR1A >= N) // raggiunto il tempo max...
     OCR1A=NA;    //...torna al tempo min.
  _delay_ms(500); // debounce
}



/* Routine servizio interrupt INT1 (pulsante variazione del modo fra o e NMODI) */
ISR ( INT1_vect )
{
  modo++;          // passa al modo successivo
  if (modo==NMODI) // raggiunto il massimo modo...
     modo=0;       // ...riparti dal modo 0
 _delay_ms(500);   // debounce
}



/* Modo 0: Scorre i LED verso destra */
void Shift_DX()
{
   if (frame==6) // raggiunto l'estremo dx...
      frame=0;   // ...vai al LED estremo sinistro
   PORTDATALED = pgm_read_byte(&sx_dx[frame]);  // leggi il frame e invialo al port
   frame++;      // passa al frame successivo
}


/* Modo 1: Scorre i LED verso sinistra */
void Shift_SX()
{
   if (frame==-1)  // raggiunto l'estremo sx...
      frame=5;     // ...vai al LED estremo destro

   PORTDATALED = pgm_read_byte(&sx_dx[frame]); // leggi il frame e invialo al port
   frame--;        // passa al frame precedente
}



/* Modo 2: Scorre i LED da sinistra e poi da destra */
void Shift_SX_DX()
{
  if (frame==6) // raggiunto il LED estremo destro
  {
    frame=4;    // vai indietro di un LED
    stato=1;    //  stato 1: ora scorri i LED da dx a sx
  }
  if (frame==-1) // raggiunto il LED estremo sinistro
  {
    frame=1;   // vai avanti di un LED
    stato=0;   // stato 0: ora scorri i LED da sx a dx
  }

  PORTDATALED = pgm_read_byte(&sx_dx[frame]); // leggi il frame e invialo al port

  switch (stato) {
   case 0: frame++; break; // se scorri da sx a dx, incrementa
   case 1: frame--; break; // se scorri da dx a sx, decrementa
  }

}


/* Modo 3: Scorre i LED dall'esterno verso l'interno e poi di nuovo verso l'esterno */
void Out_In()
{
  if (frame>=4)  // se siamo a un frame superiore a quello massimo
     frame=0;    // riparti dal primo frame
  PORTDATALED = pgm_read_byte(&out_in[frame]); // leggi il frame e invialo al port
  frame++;
}



Creiamo una cartella (es. lights) nella quale ricopiamo i file:

main.c
config
makefile


Portiamoci in questa directory e lanciamo

make all 

e dunque "uploadiamo" il file oggetto su Arduino con:

make program

Se tutto va a buon fine, nella nostra shell apparirà il seguente output di avrdude:

make program
avrdude -p atmega168 -P /dev/ttyUSB0   -c stk500v1 -b 19200     -F -U flash:w:main.hex 

avrdude: AVR device initialized and ready to accept instructions

Reading | ################################################## | 100% 0.02s

avrdude: Device signature = 0x000000
avrdude: Yikes!  Invalid device signature.
avrdude: Expected signature for ATMEGA168 is 1E 94 06
avrdude: NOTE: FLASH memory has been specified, an erase cycle will be performed
         To disable this feature, specify the -D option.
avrdude: erasing chip
avrdude: reading input file "main.hex"
avrdude: input file main.hex auto detected as Intel Hex
avrdude: writing flash (790 bytes):

Writing | ################################################## | 100% 0.59s

avrdude: 790 bytes of flash written
avrdude: verifying flash memory against main.hex:
avrdude: load data flash data from input file main.hex:
avrdude: input file main.hex auto detected as Intel Hex
avrdude: input file main.hex contains 790 bytes
avrdude: reading on-chip flash data:

Reading | ################################################## | 100% 0.53s

avrdude: verifying ...
avrdude: 790 bytes of flash verified

avrdude: safemode: Fuses OK

avrdude done.  Thank you.

lights.zip



I contenuti di questo documento, sono stati resi possibili grazie ai seguenti strumenti gratuiti:
- FidoCADJ (disegno grafici)
- Google Code Prettify (sorgenti con sintassi evidenziata)
- gEDA (disegno schemi elettrici)
- Fritzing (disegno board Arduino)
- Inkscape (aggiunta nomi pin al disegno della board Arduino)