Programmazione AVR: Porte I/O Digitali

Condividi:

In questo articolo vengono presentate le istruzioni per l’utilizzo delle porte I/O digitali di una mcu avr. Vengono presentati vari esempi mostrando pregi e difetti di ogni approccio. Il codice viene compilato ed eseguito su una mcu Atmega328p con clock a 16MHz

Ogni mcu dispone di una o più porte di I/O con cui comunicare con l’hardware esterno composte da pin che possono svolgere molteplici funzioni. Ogni pin è collegato a vari moduli interni ( PWM, I2C, ADC, Interrupt On Change, ecc ) del microcontrollore e tramite la programmazione di vari registri decidiamo quale funzione deve assumere il pin ed i parametri di funzionamento del modulo che vogliamo utilizzare

Registri delle porte digitali di I/O

Il numero di porte presenti nella mcu varia da modello a modello e sono identificate con una lettera dell’alfabeto latino ( A, B, C, … ), mentre i pin di ciascuna porta sono numerati da 0 a 7.
I principali registri che governano una generica porta n I/O digitale sono 3

  • DDRx: Data Direction Register della porta x
    Questo registro imposta la direzione dei Pin. Settare ad 1 il bit DDxn significa impostare il pin n della porta x come uscita. Settare a 0 il bit significa impostare il pin relativo come ingresso
  • PORTx: Port Data Register della porta x
    Questo registro governa le resistenze di pull-up dei pin.

    • Pin n impostato come Ingresso ( DDxn = 0 )
      Se PORTxn è settato ad 1 la resistenza di pull-up è attiva. Per disattivarla PORTxn deve essere impostato a 0 oppure il pin n deve essere impostato come uscita
    • Pin n impostato come Uscita ( DDxn = 1 )
      Se PORTxn è settato ad 1 Pin n assume il valore logico 1
      Se PORTxn è settato ad 0 Pin n assume il valore logico 0
  • PINx: Port Input Pins Address della porta x
    Indipendentemente dal valore del registro DDRx, lo stato logico dei pin può sempre essere letto tramite i bit PINxn. Se DDRx è impostato come Ingresso viene letto lo stato logico presente sul Pin, se è impostato come uscita viene letto il valore impostato sul latch interno
    Impostare ad 1 il PINxn ha come effetto quello di impostare il valore di PORTxn indipendentemente dal valore di DDxn

Le resistenze di pull-up possono essere disabilitate globalmente impostando ad 1 il valore del bit PUD ( bit 4 ) del registro MCUCR ( MCU Control Register ). Con questo bit settato ad 1 tutte le resistenze di pull up saranno disabilitate indipendentemente dai valori dei bit nei registri DDR e PORTx

Macro per operare sui registri

Le operazioni sui singoli bit di questi registri possono essere effettuare tramite i comandi assembler sbi ed cbi la cui esecuzione impiega 2 cicli macchina. L’include file <avr/sfr_defs.h> che viene richiamato da <avr/io.h> rende disponibili le macro necessarie per impostare, testare ed attendere lo stato di ogni bit

Il cuore di queste macro è _BV(bit) che implementa l’operazione di shift a sinistra dei bit (1 << (bit))

Queste istruzioni verranno tradotte in assembler con i comandi

Ovviamente impostare più bit all’interno della stessa istruzione con queste macro come si vede in molti tutorial non può essere tradotto in una sola istruzione assembler

Viene tradotto così, dove 0x08 è il registro PORTC

Trattandosi di soli due bit sarebbe stato più conveniente settarli con un’istruzione ciascuno

Le istruzioni assembler sbi, cbi, sbis e sbic possono accedere solo ai registri situati nell’intervallo di indirizzi che vanno da 0x00 a 0x31. Tutti gli altri registri vengono gestiti con normali operazioni di lettura – modifica – scrittura

Pin inutilizzati

Per evitare che segnali spuri inneschino interrupt o causino consumi anomali di corrente su ogni pin deve essere presente uno stato logico stabile. Questo si può ottenere configurando i pin come ingresso ed attivando le resistenze di pull-up o utilizzando pull-up o pull-down esterne. Collegare direttamente i pin a GND o a VCC è sconsigliato perchè nel caso la direzione del pin cambiasse accidentalmente in output potrebbe comportare un consumo eccessivo di corrente

Interrupt

I pin contrassegnati con le label INTn ( External Interrupt source n ) e PCINTn ( Pin Change Interrupt source n ) possono essere impiegati come sorgenti per Interrupt on change ed External Interrupt. Le interrupt vengono scatenate anche quando questi pin sono impostati come uscite, in questo modo è possibile generare segnali di interrupt via software

Sommario delle possibili configurazioni
DDxn PORTxn MCUCR.PUD I/O Pull-Up Risultato
0 0 X Ingresso Disattiva Alta Impedenza
0 1 0 Ingresso Attiva Pxn eroga corrente al carico se è collegato a massa
0 1 1 Ingresso Disattiva Alta Impedenza
1 0 X Uscita Disattiva Stato logico 0
( Assorbe corrente )
1 1 X Uscita Disattiva Stato logico 1
( Fornisce corrente )
Operazioni per la configurazione dei pin

Analizzando la tabella  precedente, si capisce come vanno configurati i registri per le operazioni di I/O più comuni con MCUCR.PUD settato ad 0 di default. Con le pull-ups disabilitate l’unica differenza è che diventa inutile settare PORTxn a 1 perchè vanno utilizzate restistenze esterne

INPUT OUTPUT Stato logico 0 OUTPUT Stato logico 1
DDxn: 0
PORTxn: 1Il valore dello stato logico viene letto su PINxn
DDxn: 1
PORTxn: 0
DDxn: 1
PORTxn: 1
Esempio Output pin: Generatore di onda quadra

In questi tre esempi metteremo in luce la differenza di velocità di esecuzione del codice tra l’accesso ai singoli bit e la scrittura dell’intero registro.

Nel primo esempio utilizzeremo le macro per alternare da 0 ad 1 l’uscita digitale di PORTC0

Listato assembly

Notiamo che la semi onda in cui il pin è a 0 dura di più a causa dei cicli macchina necessari all’esecuzione di rjmp. Il periodo dell’onda quadra è di soli 376 nano secondi

Nel secondo esempio eseguiremo un test sul valore del bit

Listato assembly

Sono state introdotte altre istruzioni assembler che fanno salire il periodo dell’onda ad 812 nano secondi

Il terzo esempio è simile al primo, ma scriviamo l’intero registro. Per forzare il compilatore a non utilizzare le istruzioni assembler che operano sui singoli bit utilizziamo contemporaneamente i bit 0 e 1

Listato assembly

Notiamo i due blocchi di istruzioni di lettura ( in ), modifica ( ori o andi ) e scrittura ( out ) sul registro. Il periodo dell’ onda è di 502 nano secondi contro i 376 del primo esempio

Esempio Output pin: Contatore digitale

In questi esempi sfrutteremo la presenza delle resistenze di pull-up sui pin ed imposteremo il valore voluto in uscita tramite il registro PORTC. Viene implementato un contatore a 2 bit sulle uscite PINC0 e PINC1. Il contatore parte da 0b11 decrementa di 1 fino a 0b00 e poi riparte da 0b11. Il micro utilizzato è un Atmega328P che utilizza il un quarzo ad 16MHz

Set e clear dei singoli bit

In questo esempio definiremo le macro sbi e cbi per operare sui singoli bit dei registri utilizzando _BV(bit)

Listato assembly

Il listato è molto contenuto, ma questo approccio non è il massimo se vogliamo che i due bit siano sincronizzati.

Si tratta comunque di differenze di 120-140 nanosecondi dovuti ai cicli macchina necessari per eseguire ogni istruzione.
E’ possibile controllare la durata dei periodi Ton e TOff utilizzando delle delay con periodi molto maggiori di questi intervalli anche perchè l’hardware che collegheremo alla mcu sarà progettata per assorbire queste tolleranze

Impostiamo i valori di Ton e Toff di bit0 a 10 microsecondi a questo ordine di scala i 100 nanosecondi di differenza tra la commutazione dei due bit non si vede più

Qui abbiamo il doppio vantaggio di avere le due onde sincronizzate entro valori che riteniamo non critici e di poter impostare singolarmente i singoli bit dei registri senza dover effettuare una operazione di lettura sul valore del registro

Scrittura dell’ intero registro

Per sincronizzare la commutazione di tutti i bit è necessario scrivere sui registri con una sola istruzione. I registri delle porte di I/O sono registri volatili quindi ogni volta che effettueremo una operazione che ne altera il valore basandosi su quello precedente verrà tradotta dal compilatore in una lettura del registro, modifica e scrittura. Se non ci interessa utilizzare gli altri 6 pin, possiamo scrivere direttamente sui registri di controllo ignorando il valore assunto in quel momento

Listato assembly

Il registro 0x08 corrisponde al registro PORTC

Lo svantaggio principale è che non possiamo utilizzare pin da PORTC2 a PORTC7 per altre funzioni perchè verrebbero alterati

Operazioni bitwise sul registro

Si può ovviare al problema effettuando delle operazioni bitwise sui due singoli bit del registro lasciando gli altri inalterati, quindi con operazioni di tipo Lettura-Modifica-Scrittura del registro

Listato assembly

Ogni istruzione del listato C inizia copiando il valore del registro PORTC in un registro di appoggio ( in r24,0x8, in r25,0x8 ), viene effettuata l’operazione ed infine il risultato viene ritrasferito sul registro PORTC ( out 0x8,r24 ).

Ci sono ovviamente degli effetti indesiderati con questo approccio:

  • Rispetto all’utilizzo delle macro che inseriscono le istruzioni assembler sbi e cbi, vengono effettuate da 3 a 4 operazioni al posto di una o due
  • Dopo caricato il valore di PORTC sul registro potrebbe intervenire una interrupt che altera il valore di PORTC. In questo caso al termine della interrupt service routine, quello che verrebbe modificato dall’operazione bitwise sarebbe il valore vecchio salvato sul registro di appoggio e non quello attuale di PORTC

Esempio di lettura di un pin digitale

In questo esempio valuteremo lo stato logico di PINC1 mantenuto alto tramite pull-up interna.
Alla pressione del pulsante lo stato logico passa a 0 ed il programma prosegue impostato ad 1 PINC0 che era stato impostato ad 1 durante il setup

Listato assembly

Questo è il diagramma temporale degli stati dei due pin

Dalla lettura dello 0 su PINC1 alla scrittura di 1 su PORTC0 intercorrono circa 320 nanosecondi

In questo esempio invece disabiliteremo globalmente le pull-ups ed utilizzeremo una resistenza esterna

 

 

Listato assembly

Notiamo che il set del bit POD sul registro MCUCR non è stato tradotto con l’istruzione sbi perchè questo registro risiede all’indirizzo 0x35 a cui le istruzioni su singolo bit non possono accedere

Questo è il diagramma temporale degli stati dei due pin

Dalla lettura dello 0 su PINC0 alla scrittura di 1 su PORTC1 intercorrono circa 380 nanosecondi