Gestione in assembler di due display a led a 7 segmenti con pic16F628
Abbiamo già visto nella lezione 8 del corso di programmazione picmicro in c, uno dei tanti modi in cui è possibile realizzare il multiplex con un picmicro per poter gestire 2 o più display a led a 7 segmenti.
In questo articolo viene presentato un listato che effettua le stesse operazioni, ma scritto in assembler e per un pic16F628.
Come molti di voi ben sanno, l’assembler è il linguaggio di programmazione predefinito per i picmicro (e in genere di tutti i microcontrollori), in quanto è quello che più si avvicina al linguaggio macchina e al meccanismo di funzionamento di un microcontrollore.
Programmare in assembler fornisce un codice il più ottimizzato possibile ed inoltre il compilatore assembler è disponibile senza limitazioni e anche a scopi commerciali. Ovviamente, però, c’è anche l’altra faccia della medaglia: l’assembler è piuttosto “ostico” da imparare dal momento che ha una sintassi piuttosto difficile e non vi sono tutte le strutture di programmazione (cicli, if…then ecc) a cui si è abituati con qualsiasi altro linguaggio di programmazione: tutte le operazioni vengono difatti svolte operando unicamente sui singoli bit dei registri.
Non ci metteremo in questa sede, a spiegare come si scrive un programma in assembler anche perchè in rete è già presente uno dei documenti più autorevoli in questo campo: il famoso corso “Pic By Example” scritto da Sergio Tanzilli, il quale corso ha costituito il punto di partenza per tutti quelli che hanno incominiciato a muoversi nel mondo dei PICMicro; tale corso è riferito al “vecchio” (ma ancora attualmente in produzione) PIC16F84 ma ovviamente è valido per tutti i picmicro: le istruzioni e il meccanismo di funzionamento sono gli stessi. Comunque per ogni picmicro, nel datasheet, è sempre elencato il set di istruzioni assembler disponibili.
Mi limiterò pertanto a spiegare unicamente come viene compilato un programma scritto in assembler per poter generare l’HEX da caricare nel picmicro. Possiamo compilare il programma assembler sia con il tool MPASMWIN che direttamente da MPLAB, descriverò qui entrambi i metodi.
Indice dei contenuti
Compilazione di un programma assembler con MPASMWIN
Se avete già installato MPLAB, avete già a disposizione il compilatore assembler, denominato MPASMWIN. Per accedere al compilatore assembler non è necessario avviare mplab: lo si può richiamare separatamente dal menù Start/Programmi: è posizionato nella stessa cartella in cui c’è mplab:
Il suo utilizzo è molto semplice, basta soltanto selezionare il file .asm cliccando su “Browse” e quindi compilarlo premendo “Assemble”.
Nota:
Se utilizzate MPASM, il file da compilare deve necessariamente trovarsi all’interno di una cartella che non abbia spazi nel nome, anche il file da compilare non deve presentare spazi, altrimenti si verificano degli errori e il programma non viene compilato.
Per compilare il programma, possiamo lasciare le impostazioni di default, è necessario specificare il processore utilizzato (menù Processor) solo se nel programma non avete incluso la direttiva “processor”.
Alla fine della compilazione, se non vi sono errori, appare la seguente finestra:
Se vi sono errori nel sorgente, la compilazione non va a buon fine e viene mostrato il numero di errori trovati. Ricordo che, purtroppo, quando vi sono errori, MPASM si chiude e bisogna riavviarlo, quindi a mio avviso è meglio utilizzare MPLAB come descritto al paragrafo seguente.
Nella stessa cartella dove c’è il sorgente, oltre al file .hex da caricare sul picmicro, vi è anche un file .ERR, leggibile con un qualsiasi editor di testo (es.: blocco note di windows), all’interno del quale vi sono indicazioni sugli errori e sui warnings. Ricordo ancora una volta che i warnings sono soltanto avvertimenti, e non compromettono il funzionamento del programma.
Compilazione di un programma assembler con MPLAB
La procedura è identica a quella già vista per compilare un programma in C, con la sola differenza che questa volta andremo a selezionare, come toolsuite, “Microchip MPASM Toolsuite”:
Il circuito
Il circuito è molto semplice: i due display a 7 segmenti hanno come sempre tutti i segmenti uguali collegati insieme e quindi pilotati dalla porta A del pic tramite le resistenze per limitare la corrente.
La porta RA4, essendo a collettore aperto, ha bisogno di una resistenza di pullup altrimenti non è in grado di fornire il livello logico alto.
I catodi delle decine e delle unità vengono pilotati rispettivamente dalle porte RB1 e RB3 tramite due comuni transistor NPN (al posto dei BC547 si può utilizzare un BC337).
Il PIC, inoltre, viene fatto funzionare con l’oscillatore interno, per cui non avremo bisogno della classica configurazione con quarzo e condensatori e quindi abbiamo liberi e a disposizione anche i pin 15 e 16 (RA6 e RA7).
I pulsanti per l’incremento e il decremento del contatore sono collegati rispettivamente sulle porte RB4 e RB5 tramite le solite resistenze di pull-up.
Il Programma in assembler
Il programma ha lo stesso meccanismo di funzionamento di quello già visto per la lezione 8 del corso di programmazione per picmicro, con la differenza che è scritto in assembler:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 | ;******************************************************************* ;* display015.asm * ;* PIC16F628A * ;* Premendo i pulsanti RB4/5, decrementa/incrememta display (00-99)* ;* I display sono connessi a PORTA. * ;* Catodi comuni: DisplayDecine PORTB,1 - DisplayUnita PORTB,3 * ;* Mettere resistenza di pull-up (connessa a 5V) su RA4 (open * ;* drain port) valore 1K. * ;* by Roberto Carraro (www.robertocarraro.com) ;* www.settorezero.com ;******************************************************************* PROCESSOR 16f628a INCLUDE "P16f628a.inc" ;usa il clock interno __CONFIG _CP_OFF & _WDT_OFF & _INTRC_OSC_NOCLKOUT & _LVP_OFF ;dichiarazione variabili per il timer e per i display d1 equ 0x20 d2 equ 0x21 Unita equ 0x22 Decine equ 0x23 actdisplay equ 0x24 ;variabile per stabilire il display attivo #define ButtonUp PORTB,5 #define ButtonDown PORTB,4 #define DisplayDecine PORTB,1 #define DisplayUnita PORTB,3 org 0 goto Init ;Interrupt vector ;Punto di inizio per tutte le subroutine di gestione degli interrupt org 04H ;********************************************************************** ; Interrupt handler ;********************************************************************** ;verifica che l'interrupt derivi veramente da TMR0 btfsc INTCON,T0IF goto IntT0IF EndIh bcf INTCON,T0IF ;cancella flag interrupt TMR0 retfie ;Ritorna da interrupt ;********************************************************************** ; TMR0 Interrupt handler ;********************************************************************** IntT0IF ;struttura IF-THEN-ELSE comf actdisplay,F ;actdisplay passa da 00 a FF o viceversa ad ogni overflow del timer btfsc STATUS,Z ;se passa a 00 salta seguente goto _Else bsf DisplayDecine ;attiva display decine bcf DisplayUnita ;disattiva display unita movfw Decine goto _EndIf _Else bcf DisplayDecine ;disattiva display decine bsf DisplayUnita ;attiva display unita movfw Unita _EndIf call _7SegDisp movwf PORTA goto EndIh Init ;setta a OFF i comparatori movlw 0x07 movwf CMCON bsf STATUS,RP0 ;vai a bank 1 ;setta OPTION register: ; Prescaler a 32 e assegnato a TMR0 movlw b'00000100' movwf OPTION_REG ;Abilita l'interrupt globale (GIE) e sull'overflow del TMR0 (T0IE) movlw b'10100000' movwf INTCON ;RB5 e RB4 come input, con resistenza di pull-up movlw b'00110000' ;setta porta RB5 e RB4 come Input, altre Output movwf TRISB clrf TRISA ;setta porte RA in Output bcf STATUS,RP0 ;vai a bank 0 clrf PORTB ;azzera PORTB clrf Unita ;setta a 0 clrf Decine ;setta a 0 clrf actdisplay ;switch Unita=0 o Decine=1 Main btfss ButtonUp ;testa pulsante: se non premuto (HIGH - 5V) salta prossima riga call PulsIncr btfss ButtonDown ;testa pulsante: se non premuto (HIGH - 5V) salta prossima riga call PulsDecr goto Main ;se non premuto (quindi rimane HIGH - 5V) torna a Main PulsIncr call Delay100ms ;chiama routine di ritardo di 100ms, per consentire di stabilizzare lo stato premuto (0V) del pulsante btfss ButtonUp ;testa pulsante: se rilasciato (Set - 5V), dopo essere stato premuto, salta prossima riga return ;se ancora premuto (Clear - 0V) ritorna a Main call Delay100ms ;chiama routine di ritardo di 100ms, per consentire di stabilizzare lo stato rilasciato (5V) del pulsante incf Unita movlw d'10' ;carica 10 in W subwf Unita,W ;sottrai 10 da Unita e lascia il risultato in W btfsc STATUS,Z ;se il risultato dell'operazione di sopra non è 0 ; quindi se Unita non è a 10, salta la riga seguente goto _Else1 ;Unita è a 10: vai a _Else1 goto _EndIf1 _Else1 clrf Unita ;Unita=10 quindi cancella Unita incf Decine,F ;incrementa Decine movlw d'10' ;carica 10 in W subwf Decine,W ;sottrai 10 da Decine e lascia il risultato in W btfsc STATUS,Z ;se il risultato dell'operazione di sopra non è 0 ; quindi se Decine non è a 10, salta la riga seguente clrf Decine ;Decine=10 quindi cancella Decine _EndIf1 return PulsDecr call Delay100ms ;chiama routine di ritardo di 100ms, per consentire di stabilizzare lo stato premuto (0V) del pulsante btfss ButtonDown ;testa pulsante: se rilasciato (Set - 5V), dopo essere stato premuto, salta prossima riga return ;se ancora premuto (Clear - 0V) ritorna a Main call Delay100ms ;chiama routine di ritardo di 100ms, per consentire di stabilizzare lo stato rilasciato (5V) del pulsante movlw d'0' subwf Unita,W ;W=0-Unita btfsc STATUS,Z ;se il risultato dell'operazione di sopra non è 0, salta la riga seguente goto _SetUnita9 ;Unita è a 0: vai a _SetUnita9 goto _DecUnita ;Unita non è a 0: vai a _DecUnita _SetUnita9 movlw d'9' movwf Unita ;Unita è a 0 quindi Unita=9 movlw d'0' subwf Decine,W ;W=0-Decine btfss STATUS,Z ;se il risultato dell'operazione di sopra è 0 salta la seguente riga goto _DecDecine movlw d'9' movwf Decine ;Decine=0 quindi Decine=9 return _DecUnita decf Unita return _DecDecine decf Decine return _EndIf2 return Delay100ms ;routine di ritardo 100ms ;99998 cycles - 100ms movlw 0x1F movwf d1 movlw 0x4F movwf d2 delay_0 decfsz d1, f goto $+2 decfsz d2, f goto delay_0 ;2 cycles goto $+1 return _7SegDisp ;routine di visualizzazione su display 7 segmenti addwf PCL ;il program counter salta in base al valore di W ;valore per 0 retlw b'01111110' ;valore per 1 retlw b'00110000' ;valore per 2 retlw b'01101101' ;valore per 3 retlw b'01111001' ;valore per 4 retlw b'00110011' ;valore per 5 retlw b'01011011' ;valore per 6 retlw b'01011111' ;valore per 7 retlw b'01110000' ;valore per 8 retlw b'01111111' ;valore per 9 retlw b'01111011' ;valore extra senza senso, non dovrebbe arrivare fino a qua retlw b'10101010' end |
Viene incluso il file per la definizione dei registri del pic16F628 e quindi vengono definite le locazioni di memoria utilizzate per la memorizzazione delle variabili.
Vediamo che la funzione CONFIG predispone il PIC per il funzionamento con l’oscillatore interno (_INTRC_OSC_NOCLKOUT).
Tra i settaggi da eseguire all’inizio vi è lo spegnimento dei comparatori che si trovano posizionati sulle porte RA0-RA3: operazione necessaria in quanto di default sono attivi e fanno quindi funzionare le porte in questione come analogiche (questa è una dimenticanza che molti commettono, così come il non montare la resistenza di pullup su RA4).
Il valore da impostare sulla porta A per l’accensione dei segmenti giusti viene gestito in maniera classica: il numero da visualizzare sul display viene memorizzato nell’accumulatore e viene quindi richiamata la routine “_7SegDisp”, il valore che si trova nell’accumulatore viene quindi sommato al program counter in maniera tale da spostare l’esecuzione del programma sulla riga voluta per ottenere il valore da dare alla porta A, il quale viene impostato tramite l’istruzione RETLW che lo memorizza nell’accumulatore ed esegue il ritorno dalla subroutine.
[download#89]
[download#90]