Programació en mpasm del PIC 16F690

Referència Trucs Perifèrics   Recursos CITCEA
Tutorial Exemples Projectes   Inici

Exemple LI - Encendre un LED de manera intermitent

Ara volem que el mateix LED estigui encès de manera intermitent. A primer cop d'ull podríem pensar en aquest programa (les línies que estan en color són les noves o modificades respecte a l'anterior):

#include <p16F690.inc>
	__config (_INTRC_OSC_NOCLKOUT&_WDT_OFF&_PWRTE_OFF&_MCLRE_OFF&_CP_OFF&_BOR_OFF&_IESO_OFF&_FCMEN_OFF)
	org 0
Inici
	bsf	STATUS,RP0	; Tria el banc 1
	bcf	TRISC,0	; Posa el bit 0 del port C com a sortida
	bcf	STATUS,RP0	; Tria el banc 0
Bucle
	bsf	PORTC,0	; Activa el bit 0 del port C
	bcf	PORTC,0	; Desactiva el bit 0 del port C
	goto	Bucle	; Repetim-ho...
	end

Aquest programa té dues diferències respecte a l'anterior. La primera és que després d'activar el LED (bit C0) es torna a desactivar. La segona és que ara es torna a repetir indefinidament el procés d'activació i desactivació i, per fer-ho, el GOTO va a un lloc diferent que abans.

Si proveu aquest programa veureu que no fa el que esperàvem. El LED està encès tota l'estona encara que, si ens hi fixem bé, veurem que fa menys llum. Per què passa això?

El bucle té tres instruccions. En principi, cada instrucció triga un temps en executar-se, el que anomenem un cicle d'instrucció. Amb la configuració definida del microcontrolador cada instrucció normal triga 1 μs. Les instruccions de salt, com goto, necessiten dos cicles d'instrucció. Així doncs el cicle del nostre programa dura el següent:

Instrucció Durada Estat del LED
durant la instrucció
Temps total
bcf PORTC,0 1 μs Encès 1 μs
goto Bucle 2 μs Apagat 3 μs
bsf PORTC,0 1 μs Apagat
Total 4 μs

Dels 4 μss que dura el cicle, el LED està encès la quarta part i apagat la resta. Com el temps és tan breu (la freqüència és tan alta) nosaltres no som capaços de veure que el LED s'encén i s'apaga sinó que el veiem encès però fent menys llum.

Per aconseguir veure el LED encenent-se i apagant-se, cal donar més temps a cada estat. Podem fer un comptador decrementant una variable. Crearem la variable Retard1 i l'anirem decrementant dins un bucle fins que arribi a zero i quan això passi canviarem l'estat del LED. Les nostres variables són d'un byte (és a dir, 8 bits), per tant poden tenir valors entre 0 i 255 (00h a FFh). Quan es decrementa un 00h s'obté un FFh. Farem servir dos bucles, un per encendre el LED i un per apagar el LED.

#include <p16F690.inc>
	__config (_INTRC_OSC_NOCLKOUT&_WDT_OFF&_PWRTE_OFF&_MCLRE_OFF&_CP_OFF&_BOR_OFF&_IESO_OFF&_FCMEN_OFF)
	cblock 0x20
Retard1				; Definim una variable de comptatge
	endc
	org 0
Inici
	bsf	STATUS,RP0	; Tria el banc 1
	bcf	TRISC,0		; Posa el bit 0 del port C com a sortida
	bcf	STATUS,RP0	; Tria el banc 0
Bucle
	bsf	PORTC,0		; Activa el bit 0 del port C
Ret1
	decfsz	Retard1,f	; Decrementa la variable
				; si dona zero, no es fa la instrucció següent
	goto	Ret1		; Salta, excepte si el resultat ha estat zero
	bcf	PORTC,0		; Desactiva el bit 0 del port C
Ret2
	decfsz	Retard1,f	; Decrementa la variable
				; si dona zero, no es fa la instrucció següent
	goto	Ret2		; Salta, excepte si el resultat ha estat zero
	goto	Bucle		; Repetim-ho...
	end

Anem a comentar les línies que inclouen novetats significatives respecte al programa anterior. La comanda cblock serveix per indicar que comença una definició de variables i l'adreça de la memòria de dades on cal posar-les. En aquest cas hem indicat 0x20 que correspon a la posició 20h que és la primera adreça lliure de la memòria de dades (les adreces de 00h a 1Fh són registres de funcions especials). Només definim la variable Retard1 i acabem la definició amb la instrucció endc. Podeu trobar l'organització de la memòria de dades aquí.

La instrucció decfsz decrementa la variable i si val zero salta una línia. El primer argument (Retard1 en el nostre cas) indica la variable que es decrementa i el segon (f en el nostre cas) indica si el resultat es guarda en la mateixa variable (és el nostre cas, ja que hi hem posat f) o en un registre de treball anomenat acumulador. Si el resultat de l'operació és zero no s'executa la línia següent. En el nostre cas la línia següent és el goto que repeteix el bucle. Com a conseqüència, el bucle es farà 256 cops i quan el registre valgui zero no es farà el salt sinó que es passarà a la instrucció següent (desactivar la sortida).

Si envieu aquest programa, tampoc veureu que el LED s'encengui i apagui. El motiu és que la durada de cada estat és

Instrucció Durada Estat del LED
durant la instrucció
Temps total
decfsz Retard1,f 255 x 1 μs Encès 768 μs
goto Ret1 255 x 2 μs
decfsz Retard1,f 2 μs
bcf PORTC,0 1 μs
decfsz Retard1,f 255 x 1 μs Apagat 770 μs
goto Ret2 255 x 2 μs
decfsz Retard1,f 2 μs
goto Bucle 2 μs
bsf PORTC,0 1 μs
Total 1538 μs

Ara el temps d'encès i apagat és gairebé el mateix però seguim sense veure res perquè cal que el període superi els 40 ms (freqüència de 25 Hz) per veure alguna cosa.

La versió definitiva del nostre programa tindrà dues variables i dos bucles de decrement, un dins de l'altre. Decrementarem una variable i cada cop que aquesta arribi a zero decrementarem l'altra. Cada cop que la segona arribi a zero canviarem l'estat del LED. El programa serà:

#include <p16F690.inc>
	__config (_INTRC_OSC_NOCLKOUT&_WDT_OFF&_PWRTE_OFF&_MCLRE_OFF&_CP_OFF&_BOR_OFF&_IESO_OFF&_FCMEN_OFF)
	cblock 0x20
Retard1				; Definim dues variables de comptatge
Retard2
	endc
	org 0
Inici
	bsf	STATUS,RP0	; Tria el banc 1
	bcf	TRISC,0		; Posa el bit 0 del port C com a sortida
	bcf	STATUS,RP0	; Tria el banc 0
Bucle
	bsf	PORTC,0		; Activa el bit 0 del port C
Ret1
	decfsz	Retard1,f	; Decrementa la variable
				; si dona zero, no es fa la instrucció següent
	goto	Ret1		; Salta, excepte si el resultat ha estat zero
	decfsz	Retard2,f	; Decrementa la variable
	goto	Ret1		; Salta, excepte si el resultat ha estat zero
	bcf	PORTC,0		; Desactiva el bit 0 del port C
Ret2
	decfsz	Retard1,f	; Decrementa la variable
				; si dona zero, no es fa la instrucció següent
	goto	Ret2		; Salta, excepte si el resultat ha estat zero
	decfsz	Retard2,f	; Decrementa la variable
	goto	Ret2		; Salta, excepte si el resultat ha estat zero
	goto	Bucle		; Repetim-ho...
	end

Si envieu aquest nou programa, sí veureu que el LED encendre's i apagar-se. Ara cada iteració del bucle de Retard1 dura 3 μs, per tant les 256 voltes duren 768 μs. A cada volta s'hi afegeixen 3 μs del decrement de Retard2 i el salt del segon bucle. Per tant el segon bucle repeteix 771 μs un total de 256 vegades i, per això, dura 0,19 s. Arrodonint, podem dir que el LED està encès 0,2 s i apagat uns altres 0,2 s, o sigui que fluctua a 2,5 Hz.

Podríem allargar el temps posant una instrucció nop dins el bucle petit (just abans de decfsz Retard1,f). Aquesta instrucció no fa res però consumeix un cicle (1 μs en el nostre cas).

Ara imaginem-nos que volem que en lloc d'un LED se n'encenguin dos; per exemple el 0 i el 2. El programa podria ser el següent.

#include <p16F690.inc>
	__config (_INTRC_OSC_NOCLKOUT&_WDT_OFF&_PWRTE_OFF&_MCLRE_OFF&_CP_OFF&_BOR_OFF&_IESO_OFF&_FCMEN_OFF)
	cblock 0x20
Retard1				; Definim dues variables de comptatge
Retard2
	endc
	org 0
Inici
	bsf	STATUS,RP0	; Tria el banc 1
	bcf	TRISC,0		; Posa el bit 0 del port C com a sortida
	bcf	TRISC,2		; Posa el bit 2 del port C com a sortida
	bcf	STATUS,RP0	; Tria el banc 0
Bucle
	bsf	PORTC,0		; Activa el bit 0 del port C
	bsf	PORTC,2		; Activa el bit 2 del port C
Ret1
	decfsz	Retard1,f	; Decrementa la variable 1
					; si dona zero, no es fa la instrucció següent
	goto	Ret1		; Salta, excepte si el resultat ha estat zero
	decfsz	Retard2,f	; Decrementa la variable 2
	goto	Ret1		; Salta, excepte si el resultat ha estat zero
	bcf	PORTC,0		; Desactiva el bit 0 del port C
	bcf	PORTC,2		; Desactiva el bit 2 del port C
Ret2
	decfsz	Retard1,f	; Decrementa la variable 1
					; si dona zero, no es fa la instrucció següent
	goto	Ret2		; Salta, excepte si el resultat ha estat zero
	decfsz	Retard2,f	; Decrementa la variable 2
	goto	Ret2		; Salta, excepte si el resultat ha estat zero
	goto	Bucle		; Repetim-ho...
	end

Si ho proveu, veureu que no fa el que ens esperàvem. No s'encenen els LED 0 i 2 sinó només el 2. Si proveu de permutar les dues línies de bcf sobre el PORTC resultarà que només s'encén el LED 0. Per què passa això?

L'activació o desactivació de bits sobre els ports no funciona igual com amb la resta de memòria. Quan hi ha una instrucció bcf o bsf el microcontrolador llegeix l'adreça completa (vuit bits), modifica el bit i torna a escriure l'edreça completa. En el cas dels ports, el que es llegeix no és l'estat de les sortides sinó el de les entrades. Així quan fem el segon bsf, es llegeix l'estat de les entrades i com l'entrada RC0 està desactivada tenim un zero en aquesta posició, es canvia el bit 2 i s'escriu.

Per solucionar el problema, podem fer les modificacions sobre una variable i després copiar tota la variable sencera sobre el port. Així si en altres punts del programa es modifica també la variable el que s'enviarà serà el contingut complet. A continuació tenim el programa.

#include <p16F690.inc>
	__config (_INTRC_OSC_NOCLKOUT&_WDT_OFF&_PWRTE_OFF&_MCLRE_OFF&_CP_OFF&_BOR_OFF&_IESO_OFF&_FCMEN_OFF)
	cblock 0x20
Retard1				; Definim dues variables de comptatge
Retard2
Port
	endc
	org 0
Inici
	bsf	STATUS,RP0	; Tria el banc 1
	bcf	TRISC,0		; Posa el bit 0 del port C com a sortida
	bcf	TRISC,2		; Posa el bit 2 del port C com a sortida
	bcf	STATUS,RP0	; Tria el banc 0
Bucle
	bsf	Port,0		; Activa el bit 0 de la variable Port
	bsf	Port,2		; Activa el bit 2 de la variable Port
	movf	Port,w		; Copia Port a l'acumulador
	movwf	PORTC		; I ho guarda al PORTC
Ret1
	decfsz	Retard1,f	; Decrementa la variable 1
				; si dona zero, no es fa la instrucció següent
	goto	Ret1		; Salta, excepte si el resultat ha estat zero
	decfsz	Retard2,f	; Decrementa la variable 2
	goto	Ret1		; Salta, excepte si el resultat ha estat zero
	bcf	Port,0		; Desactiva el bit 0 de la variable Port
	bcf	Port,2		; Desactiva el bit 2 de la variable Port
	movf	Port,w		; Copia Port a l'acumulador
	movwf	PORTC		; I ho guarda al PORTC
Ret2
	decfsz	Retard1,f	; Decrementa la variable 1
				; si dona zero, no es fa la instrucció següent
	goto	Ret2		; Salta, excepte si el resultat ha estat zero
	decfsz	Retard2,f	; Decrementa la variable 2
	goto	Ret2		; Salta, excepte si el resultat ha estat zero
	goto	Bucle		; Repetim-ho...
	end

 

 

Llicència de Creative Commons
Aquesta obra d'Oriol Boix està llicenciada sota una llicència no importada Reconeixement-NoComercial-SenseObraDerivada 3.0.