21 ago 2022

MICROCONTROLADORES PROGRAMABLES PIC (III)

 

Tercera entrega de esta publicación sobre Microcontroladores Programables PIC, en la veremos como mejorar nuestro programa aplicando subrutinas, lectura de puertos, operadores lógicos y su aplicación práctica.






SUBRUTINAS.

En programación, una subrutina es una sección de código o programa, que puede ser llamada como y cuando la necesites, con el objetivo de realizar una serie de funciones repetidas. Por eso se usan si vas a ejecutar la misma función más de una vez, por ejemplo, para crear un retardo. 
La ventaja de utilizar una subrutina, es que más sencillo modificar el valor determinado, solo una vez dentro de la subrutina, en vez de hacerlo cada vez que aparezca en el programa. Y por tanto, también ayudará a reducir el total de memoria que ocupa tu programa dentro del PIC.
Este podría ser un ejemplo de subrutina:


1
2
3
4
5
6
RUTINA
        CONTADOR    equ 255
ETIQUETA
        decfsz      CONTADOR,1
        goto        ETIQUETA
        return


Para acceder a la subrutina desde cualquier punto de nuestro programa, tan solo hemos de  escribir la instrucción CALL seguida por el nombre de la subrutina. 
Cuando alcanzamos la parte de nuestro programa que dice CALL [Nombre Subrutina], el programa salta a donde quiera que resida la subrutina nombrada. Las instrucciones dentro de la subrutina se ejecutan. Cuando se alcanza la instrucción RETURN, el programa salta de vuelta a nuestro programa principal, justo a la instrucción que va inmediatamente después de nuestra instrucción CALL [Nombre Subrutina]. 
Las subrutinas, pueden ser invocadas o llamadas tantas veces como necesitemos, y esa es la razón por la que al utilizar subrutinas, reduce el tamaño total de nuestro programa. Sin embargo, hay un par de cosas que se deben tener en cuenta. 
  • La primera, igual que en el programa principal, cualquier constante, debe ser declarada antes de utilizarla. Pueden ser declaradas dentro de la subrutina misma, o justo al comienzo del programa principal. Lo recomendable es declarar todo al comienzo del programa principal, para que así sepas que todo se encuentra en el mismo sitio. 

  • Lo segundo, es que debemos asegurarnos de que el programa principal pasa por alto la subrutina. Es decir, que si pones la subrutina justo al final del programa principal, a menos que usemos una instrucción 'goto' para saltar la subrutina, el programa seguirá y ejecutará la subrutina, tanto si quieres como si no. El PIC no diferencia entre una subrutina y el programa principal. 

Para que quede más claro, vamos a verlo en nuestro programa de parpadeo de LED, pero esta vez usaremos una subrutina para el bucle de retardo. Con esto, comprobaremos que el programa queda más sencillo:



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
;**********************************************************;   
; PROGRAMA LED2.asm                    FECHA: XX XXXXX 2022  ; 
; Programa para hacer parpadear un LED                       ;
; Usa el puerto RA 1 (Pin 18)                                 ;
; Revisión: 2.1                   Programa para PIC16F48A      ;
; Velocidad de Reloj: XTAL externo 4Mhz (1Mhz=1us)          ;
; WatchDog = OFF                     Tipo Reloj:  Cristal      ;
; Autor: XMG                      Por: XANUR2001/ ACME 2022    ;
; Compilado en : MPLAB X IDE v2.05 (MICROCHIP)              ;
; *********************************************************;
;
;*****Establecimiento de constantes *******
STATUS      equ       03h           ; Dirección del registro STATUS
TRISA       equ       85h           ; Dirección del registro Puerto A.
PORTA       equ       05h           ; Dirección del Puerto A.
CONTADOR1   equ       08h           ; Primer contador para bucles de retardo.
CONTADOR2   equ       09h           ; Segundo contador para bucles de retardo
;****Configuración del Puerto****
            bsf       STATUS,5       ; Cambiamos al banco 1
            movlw     00h            ; Ponemos pines del puerto A ...
            movwf     TRISA          ; ...como salidas.
            bcf       STATUS,5       ; Y volvemos al Banco 0.
;
;****Encendido del LED ****
Inicio      movlw     02h             ; Encendemos el LED poniendo primero el valor...
            movwf     PORTA           ;... en el registro W y después al puerto
;  
;****Añadimos un retardo ****
            call      Retardo         ; Llamada a la Subrutina.
;****Retardo terminado, ahora apagamos el LED ****
            movlw     00h             ; Apaga el LED poniendo primero el alor ...
            movwf     PORTA           ; en el registro w y después  al puerto
;
;****Añadimos otro retardo****
            call Retardo              ; Llamada a la Subrutina
;****Ahora volvemos al inicio del programa
            goto      Inicio          ; Vuelve al principio y enciende el LED.
;****Aquí está nuestra Subrutina ***
Retardo
Bucle1      decfsz    CONTADOR1,1     ;Este bucle hace el retardo...
            goto      Bucle1          ;para el encendido y apagado
            decfsz    CONTADOR2,1     ;de nuestro LED.
            goto      Bucle1
            return
;****Termina el Programa****
            end           
; Algunos compiladores necesitan esta instrucción. O por si acaso olvidamos poner la instrucción 'goto'. 


Al utilizar una subrutina para nuestro bucle de retardo, hemos reducido el tamaño del programa considerablemente. El programa original tenía 120 bytes de tamaño. Ahora con el uso de la subrutina, hemos reducido el programa a 103 bytes. 
Cada vez que queramos hacer un retardo, ya sea cuando el LED esté apagado o cuando esté encendido, simplemente llamamos a la subrutina de retardo. Al final de la subrutina, el programa retorna a la línea siguiente a la instrucción CALL
En el ejemplo, encendemos el LED. Después llamamos a la subrutina. Entonces el programa retorna para que podamos apagar el LED. Llamamos a la subrutina de nuevo, y cuando la subrutina termina, el programa retorna a la siguiente instrucción que ve, que es 'GOTO Inicio'. 



LEER PUERTOS DE ENTRADA/SALIDA.

Hemos estado viendo cómo escribir en el Puerto A, para poder encender y apagar el LED. Ahora vamos a ver cómo podemos leer los pines de E/S de los puertos. Esto es para que podamos interactuar mediante un circuito externo. 
En los capítulos anteriores vimos que para configurar los puertos de E/S, tenemos que cambiarnos del Banco 0 al Banco 1:
 

1
2
3
4
STATUR     equ    03h        ; Dirección del Registro STATUS
TRISA      equ    85h        ; Dirección del Registro Puerto A
PORTA      equ    05h        ; Dirección del puerto A.
           bsf    STATUS,5   ; Cambia al Banco 1


Para configurar el pin de un puerto para que sea una salida, enviamos un 0 al registro TRISA. Y para poner el pin como entrada, ponemos un 1 en el registro TRISA:

1
2
3
movlw      01h                ; Para configurar el pin 0 del puerto A...
movwf      TRISA              ; ... como entrada.
bcf        STATUS,5           ; vuelve al Banco 0.


Ahora hemos puesto el bit 0 del puerto A como entrada. Lo que necesitamos hacer ahora es comprobar si el pin está a nivel alto o a nivel bajo. 
Para ello, podemos usar una de estas dos instrucciones: BTFSC y BTFSS

La instrucción BTFSC significa "Haz una comprobación de bit en el registro y bit que especificamos. Si es un 0, entonces sáltate la siguiente instrucción". 

La instrucción BTFSS significa "Haz una comprobación de bit en el registro y bit que especificamos. Si es un 1, entonces sáltate la siguiente instrucción". 

Usaremos una u otra, dependiendo de cómo queramos que reaccione nuestro programa, cuando lea la entrada. Por ejemplo, si simplemente estamos esperando que la entrada sea 1, entonces podríamos utilizar la instrucción BTFSS de este modo:


1
2
3
4
5
6
; Aquí estaría nuestro código...
:
BTFSS    PortA,0
Goto     Inicio
;Continua por aquí...
:


En este caso el programa solo se moverá hacia 'Continua por aquí' si el bit 0 del puerto A se pone a 1.

Vamos ahora a escribir un programa con el que el LED parpadeará a una velocidad, pero si accionamos un conmutador, parpadeará a la mitad de velocidad. Estamos usando el mismo circuito que antes, con un conmutador añadido al pin RA0 del PIC y a la línea de alimentación positiva. Este es el esquema y seguidamente el código:




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
;**********************************************************;   
; PROGRAMA LED3.asm                    FECHA: XX XXXXX 2022  ; 
; Programa para hacer parpadear un LED y leer entrada       ;
; Usa el puerto RA 1 (Pin 18)                                 ;
; Revisión: 2.1                   Programa para PIC16F48A      ;
; Velocidad de Reloj: XTAL externo 4Mhz (1Mhz=1us)          ;
; WatchDog = OFF                     Tipo Reloj:  Cristal      ;
; Autor: XMG                      Por: XANUR2001/ ACME 2022    ;
; Compilado en : MPLAB X IDE v2.05 (MICROCHIP)              ;
; *********************************************************;
;
;*****Establecimiento de constantes *******
STATUS      equ       03h           ; Dirección del registro STATUS
TRISA       equ       85h           ; Dirección del registro Puerto A.
PORTA       equ       05h           ; Dirección del Puerto A.
CONTADOR1   equ       08h           ; Primer contador para bucles de retardo.
CONTADOR2   equ       09h           ; Segundo contador para bucles de retardo
;****Configuración del Puerto****
            bsf       STATUS,5       ; Cambiamos al banco 1
            movlw     00h            ; Ponemos pines del puerto A ...
            movwf     TRISA          ; ...como salidas.
            bcf       STATUS,5       ; Y volvemos al Banco 0.
;
;****Encendido del LED ****
Inicio      movlw     02h             ; Encendemos el LED poniendo primero el valor...
            movwf     PORTA           ;... en el registro W y después al puerto
;  
;****Comprueba si el conmutador está cerrado ****
            btfsc     PORTA,0         ; Comprueba el valor del bit 0 del puerto A. Si =0, salta a la siguiente instrucción y continua normalmente.
            call      Retardo         ; Si =1, añade un retardo extra.
;****Añadimos un retardo ****
            call      Retardo         ; Llamada a subrutina.
;****Retardo terminado. Ahora apagamos el Led ****
            movlw     00h             ; Apaga el Led poniendo primero el valor en el registro W...
            movwf     PORTA           ; y después lo pasa al Puerto A.
;****Comprueba si el conmutador sigue cerrado ****
            btfsc     PORTA,0         ; Comprueba el valor del bit 0 del puerto A. Si =0, salta a la siguiente instrucción y continua normalmente.
            call      Retardo         ; Si =1, añade un retardo extra.
;****Añadimos otro retardo ****
            call      Retardo
;****Ahora volvemos al inicio del programa ****
            goto      Inicio          ; Vuelve al Inicio.
;****Aquí está nuestra subrutina ****
Retardo
Bucle1      decfsz    CONTADOR1,1      ; Este bucle hace el retardo para el encendido y apagado del LED.
            goto      Bucle1           
            decfsz    CONTADOR2,1
            goto      Bucle1
            return
;**** Termina el Programa ****
            end
;Algunos compiladores necesitan esta instrucción. O por si acaso olvidamos poner la instrucción 'goto'. 


Lo que hace el programa es encender el LED. Luego comprobar si el conmutador está cerrado. Si está cerrado, entonces hacemos una llamada extra a nuestra subrutina de retardo. De lo contrario solo hace una llamada, dando el mismo retardo que anteriormente. 
Lo mismo pasa cuando el LED está apagado. Si el conmutador no está cerrado, entonces tenemos nuestros tiempos de encendido y apagado como antes. 
Bueno, de momento solo hemos hecho que el PIC haga parpadear un LED. Después fuimos capaces de interactuar con nuestro PIC añadiendo un conmutador, para modificar el ritmo de parpadeo. Y con solo 10 de las 35 instrucciones del PIC 16F84. 


OPERADORES LÓGICOS.

En el código que hemos hecho para el parpadeo del LED realmente, lo que hacemos primero es cargar nuestro registro W con 02h, después lo pusimos en nuestro registro del puerto A para encender el LED. Para apagarlo, cargamos W con 00h y después lo pusimos en nuestro registro del puerto A. Y entre ambas rutinas, teníamos que llamar a una subrutina de retardo para que pudiéramos ver el LED parpadear. 
Así que hemos tenido que mover dos conjuntos de datos dos veces (una vez al registro W y después al PORTA) y llamar a la subrutina dos veces (una vez para el encendido y otra para el apagado). 
Esto se puede hacer de una manera más eficiente, utilizando operadores lógicos, concretamente, una instrucción llamada XORWF. Esta instrucción, ejecuta una función OR Exclusiva entre el registro W y el registro que le pasamos como dato. Para más información sobre OR Exclusiva, ver la entrada https://blog-xanur.blogspot.com, pero esto es un resumen de su función:
La OR Exclusiva, la señal de salida se activa cuando tan SOLO una de las señales de entrada se activa. Equivale a la suma de dos productos lógicos, de las entradas contrapuestas:



Mantenemos el valor de A igual a 1, y hacemos OR exclusiva entre él y el valor de la salida anterior, la salida resultante conmuta: 

Valor de salida actual => 0

OR-Ex con la salida y con 1 => 1; 1 es el nuevo valor de salida.
OR-EX con la salida y con 1 => 0; 0 es el nuevo valor de salida.
OR-Ex con la salida y con 1 => 1; 1 es el nuevo valor de salida.

... así sucesivamente. 

Así que, teniendo en cuenta esto, para encender y apagar el LED, tan solo necesitamos dos líneas:

1
2
MOVLW      02h               
XORWF      PORTA,1


Lo que estamos haciendo aquí es cargar nuestro registro W con 02h. Después le hacemos una OR exclusiva a este número que hay en W con lo que quiera que esté en nuestro registro del puerto A. Si el bit 1 es 1, cambiará a 0. Si el bit 1 es 0, cambiará a 1.
Así que, vamos a ver el código con esta modificación:


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
;**********************************************************;   
; PROGRAMA LED4.asm                    FECHA: XX XXXXX 2022  ; 
; Programa para hacer parpadear un LED                       ;
; Usa el puerto RA 1 (Pin 18)                                 ;
; Revisión: 2.0                   Programa para PIC16F48A      ;
; Velocidad de Reloj: XTAL externo 4Mhz (1Mhz=1us)          ;
; WatchDog = OFF                     Tipo Reloj:  Cristal      ;
; Autor: XMG                      Por: XANUR2001/ ACME 2022    ;
; Compilado en : MPLAB X IDE v2.05 (MICROCHIP)              ;
; *********************************************************;
;
;*****Establecimiento de constantes *******
STATUS      equ       03h           ; Dirección del registro STATUS
TRISA       equ       85h           ; Dirección del registro Puerto A.
PORTA       equ       05h           ; Dirección del Puerto A.
CONTADOR1   equ       08h           ; Primer contador para bucles de retardo.
CONTADOR2   equ       09h           ; Segundo contador para bucles de retardo
;****Configuración del Puerto****
            bsf       STATUS,5       ; Cambiamos al banco 1
            movlw     00h            ; Ponemos pines del puerto A ...
            movwf     TRISA          ; ...como salidas.
            bcf       STATUS,5       ; Y volvemos al Banco 0.
             movlw     02h            ; Configura W con 2h
;
;****Encendido y Apagado del LED ****
Inicio      xorwf     PORTA,1        ; Conmutación del estado del LED
;****Comprobación si el conmutador está cerrado ****
            btfsc     PORTA,0        ; Comprueba el valor del bit 0 del PUERTO A. Si =0 salta la siguiente instrucción y continua normalemnte.
            call      Retardo        ; Si =1, añade un retardo extra.
;
;****Añadimos un retardo ****
            call      Retrado        ; Llamada a la Subrutina.
;****Ahora volvemos al inicio del programa.
            goto      Inicio         ; Vuelve al inicio
; 
;****Subrutina para el retardo ****
Retardo
Bucle1      decfsz    CONTADOR1,1      ; Este bucle hace el retardo para el encendido y apagado del LED.
            goto      Bucle1
            decfsz    CONTADOR2,1
            goto      Bucle1
            return
;
;****Fin del Programa ****
            end
; Algunos compiladores necesitan esta instrucción. O por si acaso olvidamos poner la instrucción 'goto'. 



Con el uso simple instrucción para optimizar el código, hemos reducido el tamaño de nuestro programa a 99 bytes, de los 120 iniciales. 
Una vez visto cómo implementar un operador lógico, vamos a ver y explicar los operadores lógicos disponibles en el PIC, esta vez sin ejemplo práctico.


AND 

La función AND simplemente compara dos bits y produce un 1 si son iguales, y 0 si son diferentes. Equivale al producto lógico S = A · B y se corresponde con la siguiente tabla de la verdad:




Con respecto al PIC, tenemos dos modalidades para función AND. Estas son ANDLW y ANDWF

  • ANDLW nos permite hace una función AND con los contenidos del registro W, y un número que nosotros especifiquemos. Las sintaxis es: 
        ANDLW <número> 

        Donde <número> es con el que haremos AND a los contenidos de WEl resultado de la         función AND serán almacenamos de vuelta en el registro W

  • ANDWF nos permite hacer una función AND con los contenidos del registro W y otro registro, como por ejemplo un puerto. Las sintaxis es: 
        ANDWF <registro>,

        Donde <registro> es en el que estamos interesados, por ejemplo PORTA, y d dice al            PIC donde almacenar el resultado. Si d=0, el resultado se almacena en el registro W, y            si d=1 el resultado se almacena en ese registro especificado. 

Este es un ejemplo en el que comprueba el estado del PORTA, donde necesitamos ver si las entradas son 1100. Pondremos el resultado de vuelta en el registro W:

1
2
movlw      1100             
ANDWF      05h,0



Este segundo ejemplo, tan solo  comprobará los contenidos del registro W:

1
ANDWF      1100  



OR

La función OR llamada IOR, la cual es OR inclusiva. La función produce un 1 si cualquiera de los dos bits es 1, pero también si ambos bits son 1. Equivale a la suma lógica S = A + B y se corresponde con la siguiente tabla de la verdad:



Con respecto al PIC, tenemos la función IOR, la cual es OR inclusiva, que es la función OR. Esta hace la función OR con el contenido del registro W, y el resultado se coloca de nuevo en W.


OPERADORES ARITMÉTICOS. 

En esta sección, vamos a ver los operadores disponibles en el PIC.

ADD

"Add" significa sumar, y esta función hace exactamente lo que dice, que es sumar dos números. Si el resultado de sumar dos números excede de 8 bits (255 en decimal), entonces se activará un flag o bandera, llamado CARRY, que es 1 bit localizado en el bit 0 de la dirección de memoria 03h de la memoria del PIC y se usa, para hacer la misma función en una suma de “me llevo una".

En este caso, el PIC dispone de dos modalidades de ADD, que son ADDLW y ADDWF. 

  • ADDLW suma el contenido del registro W, con un número que le especifiquemos. La sintaxis es: 
        ADDLW <número> 
  • ADDWF suma el contenido del registro W a cualquier otro registro que le especifiquemos. La sintaxis es: 
        ADDWF <registro>,d  

        Donde <registro> es el que queremos especificar, y d le dice al PIC donde almacenar el         resultado. Si d=0, el resultado se almacena en el registro W, y si d=1 el resultado se                almacena en ese registro especificado.

SUB

SUB es la abreviación del inglés “Substract”, que es sustraer.  Así pues, esta función sustrae o resta un bit a otro. Igual que para la suma, esta también tiene dos modalidades: SUBLW y SUBWF. Y la sintaxis es exactamente la misma que para la función ADD.
 

INCREMENTO

En el caso de querer añadir 1 a un número en el PIC, podríamos utilizar la función ADD, y usar el número 1. El problema con esto es que primero tenemos que poner el número 1 en el registro W, y después utilizar el comando "ADDLW 1" para incrementarlo. Y si queremos añadir 1 a cualquier otro registro, la cosa se pone peor. Primero tenemos que poner el número 1 en el registro W, y después utilizar "ADDWF <registro>,1". Así que por ejemplo, para añadir 1 a la posición 0Ch, tendríamos que tener la siguiente sección de código: 


1
2
movlw      01             
addwf      0Ch,1


Por suerte, el PIC nos da la opción de una manera mejor de hacer esto. Utilizando la instrucción INCF. La sintaxis es: 

INCF <registro>,d  

Donde <registro> es un registro, o posición de memoria en la que estemos interesados, y d le dice al PIC donde almacenar el resultado. Si d=0, el resultado se almacena en el registro W, y si d=1 el resultado se almacena en ese registro especificado. Mediante el uso de esta instrucción, podemos hacer la mitad de código. 
Existe otro comando de incremento. Es el INCFSZ. Este comando incrementará el registro que nosotros le especifiquemos, pero si el registro es igual a 0 después del incremento (esto ocurrirá cuando añadamos 1 a 255) entonces el PIC se saltará la siguiente instrucción:


1
2
3
4
5
Bulce    incfsz    0Ch
         goto      Bucle
:
:
;Resto del programa...


En el ejemplo de código anterior, el registro de la posición de memoria OCh será incrementado en 1. Después la instrucción “goto” nos lleva de vuelta a nuestra etiqueta "Bucle", e incrementa OCh en 1 de nuevo. Y así sucesivamente, hasta que 0Ch sea igual a 255. En este momento, cuando incrementemos OCh en 1, 0Ch será igual a 0. Nuestra instrucción le dirá al PIC que se salte la siguiente instrucción, y por continúe con el resto del programa.


DNCREMENTO

La instrucción DECFSZ ya fue explicada en el capítulo de Bucles de Retardo, por lo que no incidiremos en ella.


COMPLEMENTO

La última instrucción de este grupo, lo que hace es invertir todos los bits de un registro que le especifiquemos. La sintaxis es: 

COMF <registro>,d  

Donde <registro> es el registro que deseamos invertir, y d le dice al PIC donde almacenar el resultado. Si d=0, el resultado se almacena en el registro W, y si d=1 el resultado se almacena en ese registro especificado. 

A modo de ejemplo, si hacemos  el complemento de 11001100, tendríamos algo así:

0Ch = 11001100 
COMF 0Ch,1 
0Ch = 00110011 



En la siguiente publicación, continuaremos con las operaciones con Bits: 
Microcontroladores Programables PIC (IV)

También, podéis consultar las publicaciones anteriores:


ENLACES.

Y a continuación, una serie de enlaces útiles sobre lo expuesto:

Hoja de características (Datasheet) del PIC 16F84A®:

Web del Fabricante Microchip®:

MPLAB de Microchip®:

Software PICKit2 Programmer (y otros recursos en Microchip):

NotePad++:

WinPic800:

IC-PROG:

Programador JDM, en Blog Xanur:

Programador PicKit, en Blog Xanur:

Electrónica Digital, en Blog Xanur:




Esperamos que os haya gustado esta publicación. Si es así, no dudes en compartirla.

© Se permite reproducción total o parcial de este contenido, siempre y cuando se reconozca la fuente de información utilizada y se incluya el enlace a este artículo.

Equipo Xanur©2022.