Gestione in assembler di due display a led a 7 segmenti con pic16F628

codice_assemblerAbbiamo 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.

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:

microchip-mpasmwin

Il suo utilizzo è molto semplice, basta soltanto selezionare il file .asm cliccando su “Browse” e quindi compilarlo premendo “Assemble”.

mpasm

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:

mpasm_fine

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”:

mplab_assembler_01

mplab_assembler_02

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.

schema_counter_pic16f628

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]

Se questo articolo ti è piaciuto, condividilo su un social:
Se l'articolo ti è piaciuto o ti è stato utile, potresti dedicare un minuto a leggere questa pagina, dove ho elencato alcune cose che potrebbero farmi contento? Grazie :)