Introduzione
Questo articolo rappresenta un commento passo-passo del codice di un progetto apparso sulle pagine di PcTuner qualche tempo fa: si tratta del firmware di controllo della mia waterstation.
L'obiettivo è quello di fornire ai principianti una descrizione particolareggiata di come si stila un programma in assembly partendo da zero.
Il cuore della waterstation è un glorioso 16F84 che si occupa di fare un po' di cose interessanti e, se anche buona parte del programma è finalizzato al progetto specifico, con un po' di pratica se ne possono estrapolare parti utili per altri progetti e soprattutto toccare con mano la programmazione del microcontrollore.
Spero che questo serva a "rompere il ghiaccio" per chi ogni volta, armato di buona volontà, arriva alla pagina bianca del MPLab e si ferma esclamando "e mo da dove comincio?!?".
Inizio quindi un commento "riga per riga" del programma con tutte le spiegazioni del perchè e per come le cose siano state fatte in un certo modo anche da un punto di vista delle scelte circuitali effettuate.
Di seguito fornisco il codice completo per visionarlo nell'insieme e scaricarlo
;________________________________________________________
;
;
; FluidStation
;
; ver 1.0 (senza standby)
;
; copyright by Stefano Paladini 01/08/04
;
;________________________________________________________
PROCESSOR 16F84a
RADIX DEC
INCLUDE "P16F84a.INC"
ERRORLEVEL -302, -305
;PIC configuration flags
__CONFIG 0x3FF1 ;XT oscillator
;Disable watch dog timer
;Enable power up timer
;Disable code protect
cod_1 equ 11H ;\
cod_2 equ 6BH ; |
cod_3 equ 5BH ; |
cod_4 equ 1DH ; |
cod_5 equ 5EH ; |
cod_6 equ 7EH ; >Codici per le 10 cifre + Error +Low + High
cod_7 equ 13H ; |
cod_8 equ 7FH ; |
cod_9 equ 5FH ; |
cod_0 equ 77H ; |
cod_E equ 6EH ; |
cod_L equ 64H ; |
cod_H equ 3DH ;/
ORG 00H ;Reset Vector - Punto di inizio del programma al reset della CPU
goto start
;_______Interrupt Vector - Punto d'inizio delle routines di interrupt handling_______________
; Routine che serve a generare un'onda quadra sul pin RA0 alla frequenza di circa 400Hz
ORG 04H
goto interrupt_handler
ORG 0CH
start
;____________________Configurazione Generale____________________________________
Display_u res 1 ;8 bit per unità
Display_d res 1 ;8 bit per decine
Display_c res 1 ;8 bit per le centinaia anche se non servono
Delay res 1 ;Delay per il mezzo clock
LoopDelay res 1 ;Delay per il mezzo clock ausiliaria
BitCount res 1 ;Contatore bit trasmessi
uni res 1 ;unità
dec res 1 ;decine
cen res 1 ;centinaia
loops res 1
loops2 res 1
KEY res 1
count res 1
ds1820 res 1
temp1 res 1 ;byte alto della temperatura letto dalla sonda
temp2 res 1 ;byte basso della temperatura letto dalla sonda
d1 res 1
d2 res 1
d3 res 1
errore res 1 ;variabile da usare come flag
temp3 res 1 ;variabile temporanea per conversione temperatura
Pintera res 1
Pdecimale res 1
;___________ Sezione di configurazione delle porte e del prescaler ____________________
bsf STATUS,RP0 ;Commuta sul secondo banco dei registri
;Configurazione del Timer
;Assegna il PRESCALER a TMR0 e lo configura a 1:4
movlw 01000001B ;T0CS=0, PSA=0, PS2-PS1-PS0=100 ->1/4 --- INTEDG = 1
movwf OPTION_REG ;250000 / 258 = 976.6 Hz ->onda quadra a 488.3 Hz
;Definizione delle linee di I/O (0=Uscita, 1=Ingresso)
movlw 00001010B
movwf TRISA
;RA0->USCITA PWM RB0->INGRESSO LINEA DAL PC
;RA1->INGRSSO SENS LIVELLO RB1->USCITA LINEA VERSO IL PC
;RA2->ACCENSIONE DISPLAY RB2->RELE' VENTOLA
;RA3->USCITA SENSORE TEMP RB3->RELE' POMPE
;RA4->INGRESSO SENSORE TEMP RB4->SEGNALE ALLARME
; RB5->SWITCH VENTOLA
; RB6->DISPLAY CLOCK
; RB7->DISPLAY DATO
movlw 00100001B
movwf TRISB
clrf INTCON
bcf INTCON,T0IE ;Disabilitazione del Timer Overflow
bcf INTCON,RBIE ;Disabilitazione interrupt su RB4-RB7
bcf STATUS,RP0 ;Commuta sul primo banco dei registri
bcf INTCON,GIE ;Global Interrupt Enable
;______Inizializzazione delle variabili___________________
clrf Display_u
clrf Display_d
clrf Display_c
clrf errore
bcf PORTA,0
bsf PORTA,4
bcf PORTA,2
bcf PORTB,1
bcf PORTB,2
bcf PORTB,3 ;spegne le pompe
bcf PORTB,6
bcf PORTB,7
bcf PORTB,4
;________________________________________________________
call test_allarme
Main
btfss PORTB,0 ;controlla se è alta la linea del PC
goto controllo_dialogo ;se la linea è bassa controllare se il pc vuole comunicare
bsf PORTB,3 ;accende le pompe
bsf PORTA,2 ;accende il display
btfsc errore,1 ;flag per controllare se si è appena accesa la WS
call test_allarme ;3 beep di allarme
bsf INTCON,T0IE ;riabilitazione del timer interrupt
bsf PORTB,4 ;ripristina il livello verso il PC
call delay_15ms ;ritardo per dare il tempo al condensatore del sensore di livello di caricarsi
bsf INTCON,RBIE ;Riabilitazione interrupt su RB4-RB7
btfss PORTA,1 ;controlla il livello dell'acqua
goto Allarme ;loop di allarme
bcf INTCON,T0IE ;Disabilitazione del Timer Overflow
call Acquisition ;leggi temperatura
bsf INTCON,T0IE ;Abilitazione del Timer Overflow
btfsc errore,0 ; controlla che sia presente la sonda
goto Vis_Error ; assegna i codici per visualizzare EE
btfsc temp1,7 ; controlla il bit di segno
goto Vis_LL ; assegna i codici per visualizzare LL
call Conversion ; converti temperatura da 16 bit parte intera, parte decimale e segno
call Conversion_BCD ; converte la parte intera in BCD
bcf STATUS,Z
movfw cen ; controlla che le centinaia siano 0
sublw 0
btfss STATUS,Z
goto Vis_HH ; assegna i codici per visualizzare HH
call Display_Coding ; assegna i codici per unità e decine
trasm
call Transmission ; trasmette i dati al display
goto Main
Vis_Error
movlw cod_E
movwf Display_u
movwf Display_d
goto trasm
Vis_LL
movlw cod_L
movwf Display_u
movwf Display_d
goto trasm
Vis_HH
movlw cod_H
movwf Display_u
movwf Display_d
goto trasm
Allarme
bcf PORTB,4
movlw cod_H
movwf Display_d
movlw cod_2
movwf Display_u
goto trasm
;__________________Data acquisition______________________
Acquisition
call inizializza_ds1820 ; inizializzazione e riconoscimento del DS1820
call delay_600us
movlw 0xCC
movwf ds1820
call scrivi_ds1820 ; salta la ROM
call delay_600us
movlw 0x44
movwf ds1820
call scrivi_ds1820 ; comando 0x44: converti temperatura
call delay_1s ;tempo necessario per la conversione 750ms -> attesa 1s
call inizializza_ds1820
call delay_600us
movlw 0xCC
movwf ds1820
call scrivi_ds1820 ; salta la ROM
movlw 0xBE
movwf ds1820
call scrivi_ds1820 ; comando 0xBE: leggi la Scratchpad
call leggi_ds1820
movf ds1820, w
movwf temp2 ; Temperatura LSB
call leggi_ds1820
movf ds1820, w
movwf temp1 ; Temperatura MSB
return
inizializza_ds1820
bcf PORTA, 4
call delay_600us ; 600 us se e' zero -> resetta
bsf PORTA, 4
call delay_80us ; Aspetta 80 us
bcf errore,0
btfsc PORTA, 3
bsf errore,0 ;La sonda non ha risposto -> errore
return
scrivi_ds1820
movlw 0x08
movwf KEY
scrivi_ds1820_loop
rrf ds1820, f ; sposta verso destra
nop
btfss STATUS, C
goto $+3
call scrivi_uno
goto $+2
call scrivi_zero
decfsz KEY, f
goto scrivi_ds1820_loop ; continua fino a che sono stati inviati 8 bit
return
scrivi_uno
bcf PORTA, 4
call delay_7us ; Aspetta 7 us
bsf PORTA,4
call delay_80us ; Aspetta 80 us
return
scrivi_zero
bcf PORTA,4
call delay_80us ; Aspetta 80 us
bsf PORTA,4
return
leggi_ds1820
clrf ds1820
bcf STATUS, C
movlw 0x08
movwf KEY
leggi_ds1820_loop
bcf PORTA, 4 ; basso -> 0
nop
nop
bsf PORTA, 4 ; se e' alto non sara' trasmesso
nop
nop
nop
nop
nop
nop
nop
nop
bsf STATUS, C
btfss PORTA,3 ; campionamento del bus
bcf STATUS,C
rrf ds1820,f ; sposta verso destra
call delay_80us ; Aspetta 80 us
decfsz KEY, f
goto leggi_ds1820_loop ; Aspetta fino a che sono stati inviati 8 bit
return
;_______________________________________________________________________________________________
Conversion
bcf STATUS,C
movfw temp1
movwf Pintera
movfw temp2
movwf Pdecimale
btfss temp1,7
goto cnv
return
cnv
swapf Pintera
movlw 00001111B
iorwf Pintera,F
xorwf Pintera,F
movfw Pdecimale
movwf temp3
swapf temp3
movlw 11110000B
iorwf temp3,F
xorwf temp3,F
movfw temp3
iorwf Pintera,F
return
;--------routines di ritardo per il dialogo con DS18B20------------------
delay_600us movlw 0xC6 ; Aspetta 600 us
movwf d1
delay_600us_loop decfsz d1, f
goto delay_600us_loop
nop
return
delay_50us movlw 0x0F ; Aspetta 50 us
movwf d1
delay_50us_loop decfsz d1, f
goto delay_50us_loop
return
delay_30us movlw 30 ; Aspetta 30 us
movwf d1
delay_30us_loop decfsz d1, f
goto delay_30us_loop
return
delay_15ms movlw 182 ; Aspetta 15 ms
movwf d1
movlw 12
movwf d2
delay_15ms_loop decfsz d1, f
goto $+2
decfsz d2, f
goto delay_15ms_loop
goto $+1
nop
return
delay_7us goto $+1 ; Aspetta 7 us
nop
nop
return
delay_80us movlw 0x19 ; Aspetta 80 us
movwf d1
delay_80us_loop decfsz d1, f
goto delay_80us_loop
return
delay_1s movlw 0x07 ; Aspetta 1 s
movwf d1
movlw 0x2F
movwf d2
movlw 0x03
movwf d3
delay_1s_loop decfsz d1, f
goto $+2
decfsz d2, f
goto $+2
decfsz d3, f
goto delay_1s_loop
goto $+1
goto $+1
goto $+1
return
;__________________Digits conversion______________________
Conversion_BCD
clrf uni
clrf dec
clrf cen
rip1
movlw D'100' ;pone 100 -> W
subwf Pintera,0 ;f-W -> W
btfsc STATUS,C ;salta se C=0 sottrazione non possibile
goto centinaia
rip2
movlw D'10' ;pone 10 -> W
subwf Pintera,0 ;f-W -> W
btfsc STATUS,C ;salta se C=0
goto decine
movf Pintera,W
movwf uni ;salva le unità
return ;fine programma
centinaia
movwf Pintera ;salva nuovo valore
incf cen ;incrementa centinaia
goto rip1
decine
movwf Pintera ;salva nuovo valore
incf dec ;incrementa unità
goto rip2
;_________________________________________________________
;__________Coding_for_7_Segment_Display__________________
Display_Coding
movlw 01
movwf PCLATH
;Codifica Unità
movfw uni
sublw 9
call Assegna_codice
movwf Display_u
;Codifica Decine
movfw dec
sublw 9
call Assegna_codice
movwf Display_d
return
Assegna_codice
addwf PCL,F
retlw cod_9
retlw cod_8
retlw cod_7
retlw cod_6
retlw cod_5
retlw cod_4
retlw cod_3
retlw cod_2
retlw cod_1
retlw cod_0
;_________________________________________________________
;__________________Data Transmission______________________
Transmission
call Aspetta ;Attesa
bsf PORTB,7 ;Invio bit di start al display
bsf PORTB,6 ;Alza il clk
call Aspetta ;Attesa
bcf PORTB,6 ;Abbassa il clk
call dont_care ;primo don't care
movlw 7
movwf BitCount ;Preparo il contatore per i 7 bit dei codici
InizioCifre
rlf Display_u
btfss Display_u,7
goto AbbassaLinea
call Aspetta ;Attesa
bsf PORTB,7 ;Alza la linea
bsf PORTB,6 ;Alza il clk
call Aspetta
bcf PORTB,6
decfsz BitCount
goto InizioCifre
goto dc0
AbbassaLinea
call Aspetta ;Attesa
bcf PORTB,7
bsf PORTB,6
call Aspetta
bcf PORTB,6
decfsz BitCount
goto InizioCifre
dc0
call dont_care ;primo gruppo di 3 don't care
call dont_care
call dont_care
Ini_cifra2
movlw 4
movwf BitCount ;Preparo il contatore per i primi 4 bit della 2a cifra
Cifra2
rlf Display_d
btfss Display_d,7
goto AbbassaLinea2
call Aspetta ;Attesa
bsf PORTB,7
bsf PORTB,6
call Aspetta
bcf PORTB,6
decfsz BitCount
goto Cifra2
goto dc1
AbbassaLinea2
call Aspetta ;Attesa
bcf PORTB,7
bsf PORTB,6
call Aspetta
bcf PORTB,6
decfsz BitCount
goto Cifra2
dc1
call dont_care ;4 bit di don't care
call dont_care
call dont_care
call dont_care
movlw 3
movwf BitCount ;Preparo il contatore per i restanti 3 bit dei codici
Cifra2_1
rlf Display_d
btfss Display_d,7
goto AbbassaLinea2_1
call Aspetta ;Attesa
bsf PORTB,7
bsf PORTB,6
call Aspetta
bcf PORTB,6
decfsz BitCount
goto Cifra2_1
goto Ini_cifra3
AbbassaLinea2_1
call Aspetta ;Attesa
bcf PORTB,7
bsf PORTB,6
call Aspetta
bcf PORTB,6
decfsz BitCount
goto Cifra2_1
Ini_cifra3
movlw 3
movwf BitCount ;Preparo il contatore per i primi 3 della 3a cifra
Cifra3
rlf Display_c
btfss Display_c,7
goto AbbassaLinea3
call Aspetta ;Attesa
bsf PORTB,7
bsf PORTB,6
call Aspetta
bcf PORTB,6
decfsz BitCount
goto Cifra3
goto dc2
AbbassaLinea3
call Aspetta ;Attesa
bcf PORTB,7
bsf PORTB,6
call Aspetta
bcf PORTB,6
decfsz BitCount
goto Cifra3
dc2
call dont_care ;don't care bit isolato
rlf Display_c
btfss Display_c,7
goto AbbassaLinea3_isolato
call Aspetta ;Attesa
bsf PORTB,7
bsf PORTB,6
call Aspetta
bcf PORTB,6
goto dc3
AbbassaLinea3_isolato
call Aspetta ;Attesa
bcf PORTB,7
bsf PORTB,6
call Aspetta
bcf PORTB,6
dc3
call dont_care ;altri 2 don't care isolati
call dont_care
ultimi3
movlw 3
movwf BitCount ;Preparo il contatore per i primi 3 bit dei codici
Cifra3_1
rlf Display_c
btfss Display_c,7
goto AbbassaLinea3_1
call Aspetta ;Attesa
bsf PORTB,7
bsf PORTB,6
call Aspetta
bcf PORTB,6
decfsz BitCount
goto Cifra3_1
goto dc4
AbbassaLinea3_1
call Aspetta ;Attesa
bcf PORTB,7
bsf PORTB,6
call Aspetta
bcf PORTB,6
decfsz BitCount
goto Cifra3_1
dc4 ;ultimo don't care + 2 bit mezza cifra
call dont_care
call dont_care
call dont_care
return
;_________________________________________________________
;________________Routine di attesa ________________________
Aspetta
movlw 10
movwf Delay
Aspe2
movlw 20
movwf LoopDelay
decfsz Delay
goto D_Loop
return
D_Loop
decfsz LoopDelay
goto D_Loop
goto Aspe2
return
;_________________________________________________________
;__________Routine per generare un bit don't care_________
dont_care
call Aspetta ;Attesa
bcf PORTB,7
bsf PORTB,6
call Aspetta
bcf PORTB,6
return
;________________Routine di attesa x rilascio_________________________________________
Aspetta_rilascio
btfsc PORTB,5
goto Aspetta_rilascio
call delay_15ms
return
;_________________________________________________________________________________
;________________Routines controllate dall'interrupt_________________________________________
test_allarme
bcf INTCON,GIE ;disabilito interrupt
bcf PORTB,4
call delay_1s
bsf PORTB,4
call delay_15ms
call delay_15ms
bcf PORTB,4
call delay_1s
bsf PORTB,4
call delay_15ms
call delay_15ms
bcf PORTB,4
call delay_1s
bsf PORTB,4
bcf errore,1
bsf INTCON,GIE ;riabilito interrupt
return
;**************Interrupt handler**************************
interrupt_handler
bcf INTCON,GIE ;disabilito gli interrupt per evitare accavallamenti
;controllo per capire quale interrupt si sia verificato
btfsc INTCON,T0IF ;timer
goto timer
btfsc INTCON,RBIF ;cambiamento di stato delle porte da RB4 a RB7
goto controllo_pulsanti
bcf INTCON,T0IF
retfie
timer
btfsc PORTA,0 ;controlla lo stato attuale della porta
goto azzera_bit
bsf PORTA,0 ;PWM On
bcf INTCON,T0IF ;Azzera nuovamente il flag T0IF per consentire all'interrupt di ripetersi
bsf INTCON,GIE ;riabilito gli interrupt
retfie
azzera_bit
bcf PORTA,0 ;PMW Off
bcf INTCON,T0IF ;Azzera nuovamente il flag T0IF per consentire all'interrupt di ripetersi
bsf INTCON,GIE ;riabilito gli interrupt
retfie
controllo_pulsanti
movlw 00000100B
xorwf PORTB,F
bcf STATUS,Z
call Aspetta_rilascio
call delay_15ms
call delay_15ms
call delay_15ms
call delay_15ms ;cicli di attesa per rimbalzi
bcf INTCON,RBIF ;azzera il flag per permettere all'interrupt di ripetersi
bsf INTCON,GIE ;riabilito gli interrupt
retfie
controllo_dialogo
call delay_1s
call delay_15ms
btfsc PORTB,0 ;controlla lo stato attuale della linea RB0
goto Main ;se è alto il pc vuole comunicare ma in questo caso si ritorna alla main
; goto spegnimento ;altrimenti il PC si è spento e bisogna spegnere le pompe
spegnimento
bcf PORTB,3 ;spegne le pompe e la ventola
bcf PORTB,2 ;mette la ventola a bassa potenza
bcf PORTA,0 ;mette a 0 il PWM
bcf PORTB,4 ;mette a 0 il segnale di allarme
bcf PORTA,2 ;spegne il display
bcf INTCON,T0IE ;Disabilitazione del Timer Overflow
bcf INTCON,RBIE ;Disabilitazione interrupt su RB4-RB7
bsf errore,1
goto Main
end
Apri schermo intero
Scarica