PROGRAMACIÓN EN
ENSAMBLADOR DEL PIC18F4550
CON BOOTLOADER
Por Francisco Javier García Olmos
Instituto Politécnico Nacional
México 2012
NOTA: Para utilizar este manual teórico-práctico es necesario tener conocimientos básicos de electrónica y circuitos, lógica
digital y nociones básicas de programación de computadoras en cualquier lenguaje. Es necesario también tener en cuenta la lectura del manual (data sheet) para manejar los registros necesarios para la configuración de este microcontrolador.
Dedicado a mis amigos programadores:
Francisco y Eduardo.
Hay más personas pero el mencionar a todas ellas requeriría un documento extra para
mencionarlas, ya que son muchas. Gracias Por Su Apoyo SNAR.
INTRODUCCIÓN A MPLAB X
Para iniciar con la programación en ensamblador primero necesitamos saber acerca del IDE gratuito del cual Microchip nos provee; Microchip es la empresa que fabrica los microcontroladores PIC. Este IDE es el que usaremos para realizar nuestros proyectos, probarlos y compilarlos, ya que es multiplataforma y podemos utilizarlo tanto en Windows como en Linux.
Ambiente de desarrollo MPLAB X
El ambiente de desarrollo tiene la siguiente imagen; tome en cuenta que está mos-trado dentro del Sistema operativo Linux.
Del lado izquierdo aparece una lista con los proyectos realizados, archivos y/o clases que se utilizan en MPLAB. Siempre se abrirá con la página de inicio. Tiene su barra de menú, barra de herramientas y en la parte de abajo, oculta ventanas que son necesarias a la hora de compilar, probar y analizar la memoria. Para mayor información, verificar un curso com-pleto de MPLAB, ya que aquí solo hablaremos de las partes más importantes.
Crear un proyecto para Ensamblador
Para crear un proyecto, es necesario dar clic en el segundo icono de la barra de he-rramientas o seleccionar New Proyect del menú File y se abrirá una ventana donde pregunta el tipo de proyecto que deseamos crear; seleccionamos Standaalone Proyect y presionamos
Siguiente; nos aparecerá una lista donde tenemos que seleccionar la familia y el dispositivo a
usar, en nuestro caso colocamos Advanced 8-bit MCUs (PIC18) en familia y en dispositivo PIC18F4550, posteriormente seleccionamos siguiente. Nos aparecerá una lista de programa-dores a usar; nosotros colocaremos Simulator por que usaremos Bootloader y no es nece-sario un programador mas que solo una vez para ingresarle el Bootloader al PIC. Una vez presionado el botón siguiente (Next) aparecerá una lista con lenguajes de programación, de los cuales vamos a seleccionar MPASM, ya que usaremos el lenguaje ensamblador.
Por último, le damos un nombre al proyecto y lo guardamos en el directorio que nos agrade.
Al realizar los pasos anteriores, se mostrará nuestro proyecto en la parte izquierda del IDE. Ahora es necesario crear un archivo nuevo vació para comenzar a programar.
Para agregar el nuevo archivo hacemos clic derecho en Source Files del proyecto que estamos creando, y le hacemos clic en New y posteriormente en Empty File y se abrirá una ventana donde le colocaremos un nombre. En nuestro caso le llamaremos plantilla.asm por que será nuestra plantilla para cualquier proyecto que realicemos con bootloader.
Compilar un programa
Para compilar un programa hecho previamente, solo le damos clic al botón que tiene un martillo, se compilará el proyecto y se creará el archivo ejecutable .HEX que es el que se grabará en el PIC con el HID BOOTLOADER.
En Windows este programa es gráfico y se habilitan los botones una vez que se ac -tiva el bootloader de la placa de prueba del PIC. Seleccionamos el archivo .HEX que deseamos grabar y pulsamos sobre el botón Program / Verify y se programará el PIC.
En Linux, hay un programa en línea de comandos que graba de forma satisfactoria el PIC con bootloader y se ejecuta anexando la dirección del archivo .HEX desde la Terminal. Tiene el nombre de hid_bootloader y solo se tiene que ingresar el nombre del archivo .HEX que se desea grabar.
El bootloader del PIC se activa pulsando el SWITCH 3 de la placa de prueba del PIC18F4550 junto con el botón RESET, posteriormente soltar RESET quedando solo el swithc 3 y parpadearán los LEDS RC0 y RC1 de la placa de prueba, en ese momento soltar el SWITCH 3. Una vez realizado esto, la computadora reconocerá al dispositivo como un Human Interface Device USB. En este momento se puede grabar el programa que hemos realizado con el bootloader.
El PIC18F4550
En este capítulo vamos a ver algunas características del PIC18F4550, que son esen-ciales para entender la programación en ensamblador.
1. Es un dispositivo CMOS de bajo consumo de energía, uso de voltaje +5V y uso máximo de corriente de 25mA, no consumir más de esta corriente ya que si no, podríamos dañar el pin del microcontrolador. También tener en cuenta que los disposi-tivos CMOS son delicados a la corriente estática.
2. Es un microcontrolador que trae en su interior un microprocesador (MPU o ALU), memoria RAM y memoria de programa flash (regrabable) y memoria EEPROM de datos y sus propios bus de datos, control y direcciones internamente.
3. Integrado de 40 pines en su presentación DIP o 44 pines en su presentación para montaje superficial.
4. Es un microcontrolador de 8 bits en su bus de datos, osea que solo puede manejar 8 bits por cálculo.
5. Tiene 5 puertos de entrada y salida: A, B, C, D y E. Tres de ellos son de 8 bits, uno de 7 bits y uno de ellos es de 4 bits, mas sin embargo, estas características no son posibles por el uso de pines por oscilador externo (RA5 y RA6), uso de USB (RC4 y RC5) y por último uso de MCLR (Master Clear Reset) en el pin RE4. Esto nos deja con 1 puertos de 6 bits, dos puertos de 8 bits, uno de cinco y uno de 3 bits.
6. Oscilador interno de 8MHz. Nosotros usaremos el externo de 20MHz por el uso del módulo USB para el BOOTLOADER.
7. Tiene 13 canales analógicos (ADC) de 10 bits. 8. Módulo USB y posibilidad de usar BOOTLOADER.
9. Modulo USART para comunicación serial. Si se requiere comunicación PC-PIC se necesitará un integrado MAX232 para la conversión de voltajes.
10.Tres interrupciones externas.
11. Cuatro temporizadores (Timers) o contadores. 12.Dos modulos capturador/comparador/PWM (CCP).
13.Un módulo embebido capturador/comparador/PWM (ECCP). 14.Un módulo Master Synchronous Serial Port (MSSP).
Las conexiones básicas del PIC18F4550, les de estado y 2 botones de cambio de flanco (SW2 y SW3) para el inicio del bootloader son las siguientes:
Conexiones básicas:
1. Conexión a VDD y VSS no mostradas en el diagrama. VDD es de +5V y VSS es tierra GND.
2. 1 push button para MCLR con sus respectivas resistencias de PULL-UP externas. 3. Oscilador de 20MHz con dos capacitores de 22pF conectados a tierra.
Conexiones extras de la placa de pruebas del PIC18F4550: 1. 2 Leds en RA4 y RA5.
2. 2 Leds en RC0 y RC1 para el estado del Bootloader.
3. 2 push button para las interrupciones de cambio de flanco con sus respectivas resiste-ncias de PULL-UP externas (SW2 y SW3).
LO BÁSICO EN ENSAMBLADOR
Comencemos la programación en ensamblador definiendo algunos términos y acla-rando algunas situaciones con respecto del bootloader.
El bootloader es un programa dentro del microcontrolador PIC que graba otro programa a partir de la dirección 0x1000 de la memoria de programa. Es como un sistema operativo (mas sin embargo no lo es) que se ejecuta cada que presionamos el botón SWITCH 3 después del RESET.
Las direcciones de inicio del PIC son: 0x0000 MCLR (Master Clear Reset) 0x0008 High Vector Sector
0x0018 Low Vector Sector
Pero como nosotros usamos Bootloader, las direcciones cambian. 0x1000 Remaped Reset Sector
0x1008 Remaped High Vector Sector 0x1018 Remaped Low Vector Sector
Posteriormente explicaré a que se refiere cada una de ellas en el capítulo de interrup - ciones, que abarcaremos más adelante.
La numeración en ensamblador puede ser hexadecimal colocando 0xNN y el núme-ro, colocando H'NN' y el número o colocando NLh (0Ah), para este tipo de numeración, se debe de comenzar por un número, en caso de ser de dos letras, se utiliza cero (0FFh). La numeración decimal se realiza colocando un punto y el número en decimal. Para la numeración binaria se procede a colocar B'XXXXXXX' y el número en binario,
Como vemos, lo que se refiere al bootloader, son las direcciones de inicio y posteriormente veremos que la frecuencia también cambia, así que esta programación tam-bién se puede utiliza sin bootloader con el inconveniente que necesitamos un programador como el PICKIT2 para grabar nuestros programas.
La configuración inicial (La plantilla)
Para iniciar con la plantilla necesitamos incluir el archivo P18F4550.INC que es un archivo que contiene definiciones de registros y sus direcciones de memoria.
#include <P18F4550.INC> esta parte del código es para que ensamblador reconozca los registros del pic y las direcciones de memoria.
LIST P=18F4550 es para que reconozca el tipo de microcontrolador a programar el ensamblador.
Bits de configuración
Son los que controlan el comportamiento del microcontrolador. Los tres básicos son: CONFIG FOSC = HS ;Configura el oscilador a usar, los posibles osciladores son RC (oscilador RC), XT (cristal de baja frecuencia), HS (cristal de alta frecuencia). Hay más, para mayores referencias consultar el manual.
CONFIG WDT = OFF ;configura el perro guardián (este es un timer que resetea al pic en caso de que tenga un ciclo infinito) el valor que nosotros manejamos es OFF (apagado).
CONFIG LVP = OFF ;configura el bajo voltaje de programación, nosotros lo apaga-mos para que programe en alto voltaje.
Para el Bootloader (modo que nosotros usaremos) hay más bits, están ya colocados en la plantilla y no deberán de ser modificados. Para más información ver la ayuda de MPLAB sobre los bits de configuración.
Sectores de código
Los sectores de código son aquellos que le dicen al programador (PICKIT2, PICKIT3, etc) en que parte de la memoria de programa va a programar el código. Hay dos formas de hacerlo: ORG $dirección o CODE $dirección
Ejemplo 1: Colocar el vector de reset en la localidad cero de la memoria. ORG 0x0000 ;localidad cero
goto main
RESET_VECTOR_SECTOR CODE 0x0000 ;localidad cero (notar que podemos nombrar dicho sector con CODE)
goto main
Ejemplo 2: Colocar el vector reset en la localidad mil hexadecimal. ORG 0x1000
goto main
REMAPED_VECTOR_SECTOR CODE 0x1000 goto main
Para usar bootloader el vector de reset se encuentra en 0x1000 ya que el demás espacio es usado por el propio bootloader previamente cargado.
La palabra reservada END termina con todos los CODE. Se utiliza para finalizar el código. Solo se pone una vez y hasta el final del programa.
Sectores de memoria
Los sectores de memoria especifican la memoria que va a ser reservada para los registros del microcontrolador o un espacio de memoria que se necesite para guardar algún dato o infromación.
El sector de memoria se ubica debajo de la palabra UDATA, para reservar memoria para registros del microcontrolador, básicamente para usar información que restaure el algunos registros del microcontrolador en la interrupción del baja prioridad.
Para reservar memoria de datos, vamos a hacerlo debajo de la palabra UDATA_ACS y se reserva memoria colocando un nombre al registro de memoria, posteriormente se coloca la palabra RES y finalizar con el número de bytes que se desean reservar.
Ejemplo: Reservar memoria para un dato, donde el registro de memoria tendrá el nombre de DATO1.
UDATA_ACS ;UDATA y UDATA_ACS llevan sangría DATO1 RES 1 ;la reserva de memoria no lleva sangría.
En el ejemplo anterior reservamos un byte de memoria para un registro llamado DATO1. Podemos hacer la reserva de memoria mientras no terminemos la memoria RAM del microcontrolador. UDATA_ACS DATO1 RES 1 DATO2 RES 1 DATO3 RES 1
Constantes
Para que el lenguaje ensamblador reconozca una constante, es necesario darle un nombre y un número al cual sera igualada; para ello se utiliza la palabra reservada EQU.
UNO EQU 1 DOS EQU 2 TRES EQU 3
Y con esto tenemos a UNO con un valor de 1, DOS con un valor de 2 y a TRES con un valor de 3.
Nuestra plantilla quedará de la siguiente forma:
;************************************************************************ ; Autor: Francisco Javier García Olmos *
; Fecha: 2 de Enero de 2012 *
; Plantilla para trabajar con BOOTLOADER en Ensamblador * ;************************************************************************ LIST P=18F4550, F=INHX32 #include <P18F4550.INC> ;*************************************************************************** ;Bits de configuración CONFIG PLLDIV = 5
CONFIG CPUDIV = OSC1_PLL2 CONFIG USBDIV = 2
CONFIG FOSC = HSPLL_HS CONFIG FCMEN = OFF CONFIG IESO = OFF CONFIG PWRT = OFF CONFIG BOR = ON CONFIG BORV = 3 CONFIG VREGEN = ON config WDT = OFF config WDTPS = 32768 config MCLRE = ON config LPT1OSC = OFF config PBADEN = OFF config CCP2MX = ON config STVREN = ON config LVP = OFF config ICPRT = OFF config XINST = OFF config CP0 = OFF config CP1 = OFF config CP2 = OFF config CP3 = OFF config CPB = OFF config CPD = OFF config WRT0 = OFF config WRT1 = OFF config WRT2 = OFF config WRT3 = OFF config WRTB = OFF config WRTC = OFF config WRTD = OFF config EBTR0 = OFF config EBTR1 = OFF config EBTR2 = OFF
config EBTR3 = OFF config EBTRB = OFF
;*************************************************************************** ;*************************************************************************** ; Definición de variables
; Variables definidas si se utiliza la interrupción de baja prioridad UDATA
WREG_TEMP RES 1 ;variable en RAM para guardar contexto STATUS_TEMP RES 1 ;variable en RAM para guardar contexto BSR_TEMP RES 1 ;variable en RAM para guardar contexto RESET_VECTOR CODE 0x0000 goto main REMAPED_RESET_VECTOR CODE 0x1000 goto main REMAPED_HIGH_VECTOR CODE 0x1008 bra ISRH REMAPED_LOW_VECTOR CODE 0x1018 bra ISRL CODE 0x102A ISRH:
; *** Interrupción de alta prioridad retfie FAST
ISRL:
movff STATUS,STATUS_TEMP ;guarda el registro de estado movff WREG,WREG_TEMP ;guarda el registro de trabajo movff BSR,BSR_TEMP ;guarda el registro BSR
; *** Interrupción de baja prioridad ***
movff BSR_TEMP,BSR ;restaura el registro BSR
movff WREG_TEMP,WREG ;restaura el registro de trabajo movff STATUS_TEMP,STATUS ;restaura el registro de estado retfie
main:
clrf WREG
; *** La función principal va aquí *** ciclo
goto ciclo END
Instrucciones básicas del lenguaje ensamblador
Antes de ver las instrucciones veremos cuales son los comentarios en el lenguaje ensamblador. El lenguaje ensamblador toma como comentario cualquier texto que esté después de punto y coma.
;esto es un comentario
Instrucción NOP
Esta instrucción se utiliza para que el microcontrolador no realice operación alguna. Se utiliza para hacer retardos de tiempo equivalentes a cuatro ciclos del oscilador principal.
Su sintaxis es: NOP
No realiza operación alguna.
Instrucción MOV
Mueve datos de un registro a otro. Hay que tomar en cuenta que el los microcontro -ladores de Microchip usan el registro de trabajo para realizar este proceso. Este registro es llamado WREG o solamente W.
La instrucción MOVLW 0x01 mueve el número 0x01 al registro de trabajo.
La instrucción MOVWF REGISTRO mueve el contenido guardado en el registro de trabajo a cualquier otro registro.
Ejemplo: Configurar el registro ADCON1 con 0x0F MOVLW 0x0F
MOVWF ADCON1
Ejemplo mover e número 0xF0 a la variable DATO1 MOVLW 0xF0
MOVWF DATO1
La instrucción MOVF se usa para mover contenidos de registros a el mismo registro o al registro W. Su sintaxis es:
Donde DESTINO puede ser 0 o 1. Si es cero, se guarda en el registro W, si es uno se guarda en el mismo registro.
Ejemplo: mover el contenido de la dirección de INTCON al registro de trabajo: MOVF INTCON, W
Si colocamos MOVF INTCON, 1 movemos el contenido de ese registro al mismo re-gistro. No es muy útil hacer esto a menos que sea necesario refrescar la memoria RAM.
La instrucción MOVFF se usa mara mover el contenido de un registro a otro registro. Ejemplo: mover el contenido del registro PORTB a un registro de memoria llamado DATO1.
MOVFF PORTB, DATO1
Instrucción CLRF
Coloca a cero un registro.
Ejemplo: Limpiar el registro de trabajo. CLRF WREG
Instrucción SET
La instrucción SET se utiliza para colocar un todo el un registro con uno, como si co-locáramos 0xFF al registro.
Ejemplo: Colocar en uno todo el registro de trabajo. SET WREG
Es idéntico que hacer: MOVLW 0xFF
GOTO y Etiquetas
de-jar las etiquetas sin sangría y las instrucciones con sangría. Las etiquetas se colocan con nombre y puede o no llevar dos puntos al final, pero los dos puntos se recomiendan para fun-ciones o subrutinas. Las etiquetas no se pueden repetir dentro de un programa, hay que cambiar el nombre en caso de que tengamos dos con el mismo, si no, provocará un error en el compilador.
El GOTO se usa para dirigirse a una etiqueta colocando enfrente el nombre. Ejemplo:
main: ;rutina principal main lleva dos puntos y no lleva sangría. CLRF WREG ;código con sangría
infinito ;etiqueta sin sangría
GOTO infinito ;ir a la etiqueta infinito (se hace un bucle infinito)
Instrucción BCF
La instrucción BCF coloca un bit en cero de un registro específico. El conteo de bits es desde del cero hasta el siete. Su sintaxis es la que a continuación se muestra:
BCF REGISTRO, BIT
Ejemplo: Colocar el BIT 0 del registro PORTC en cero. BCF PORTC, 0
También podemos usar el nombre del bit, para eso, consultar el manual del micro-controlador y algunos de ellos serán explicados en este documento.
Ejemplo: Colocar el BIT RC0 del registro PORTC en cero. BCF PORTC, RC0
o
BCF PORTC, 0
Instrucción BSF
Es exactamente igual que BCF solo que este coloca en uno el bit indicado de el re-gistro deseado.
Ejemplo: Colocar en uno el bit RA4 del registro PORTA. BSF PORTA, RA4
Instrucción RLCF
Esta instrucción realiza un giro a la izquierda de bits del contenido un registro cualquiera pasando por la bandera Carry del registro STATUS. La instrucción que realiza la mis -ma operación sin que pase por la bandera Carry del registro STATUS es RLNCF. Con esta instrucción el contenido será el mismo pero desplazado un bit a la izquierda. Su sintaxis es:
RLCF REGISTRO, DESTINO RLNCF REGISTRO, DESTINO
Donde DESTINO puede ser 0 o 1. Si es cero, se guarda en el registro W, si es uno, se guarda en el mismo registro (REGISTRO).
Instrucción RRCF
Esta instrucción realiza un giro a la derecha de bits del contenido de cualquier regis-tro pasando por la bandera Carry del regisregis-tro STATUS. La instrucción que realiza la misma o-peración sin que pase por la bandera Carry del registro STATUS es RRNCF. Con esta ins-trucción el contenido será el mismo, pero desplazado a la derecha. Su sintaxis es:
RRCF REGISTRO, DESTINO RRNCF REGISTRO, DESTINO
Donde DESTINO puede ser 0 o 1. Si es cero, se guarda en el registro W, si es uno, se guarda en el mismo registro (REGISTRO).
Instrucción COMF
Realiza el complemento de un registro y lo puede guardar en el mismo registro o en el registro W. Su sintaxis es:
COMF REGISTRO, DESTINO
Donde DESTINO puede ser 0 o 1. Si DESTINO es 0, el resultado se guarda en el re-gistro W, si es 1, se guarda en el mismo rere-gistro (REGISTRO).
Instrucción SWAPF
Cambia el nibble alto por el nibble bajo y viceversa. Su sintaxis es: SWAPF REGISTRO, DESTINO
Donde DESTINO puede ser 0 o 1. Si es 0, se guarda en el registro W, si es 1, se guarda en el mismo registro.
Ejemplo:
Si registro contiene 35h. SWAPF REGISTRO, 1
Ahora registro contendrá 53h.
Instrucción CALL
Esta instrucción manda a llamar a una subrutina. Las subrutinas son aquellas etique-tas especiales y terminan en la palabra reservada RETURN.
Ejemplo: Llamar a la subrutina OPERACION desde la rutina main. OPERACION:
RETURN ;regresa al main main:
CALL OPERACION ;llama a la subrutina OPERACION ;sigue el programa
Instrucción RETURN
Esta instrucción se utiliza para regresar de una subrutina a la instrucción siguiente de la rutina principal. Solo se coloca RETURN al final de la subrutina. Vease el ejemplo anterior.
Si queremos devolver algún valor en el registro de trabajo podemos finalizar la subru-tina con la palabra reservada RETLW. Su sintaxis es:
RETLW LITERAL
Donde LITERAL puede ser cualquier valor dentro del rango 0 a 255. Este valor será guardado en el registro W.
Instrucción RETFIE
Esta instrucción se utiliza para finalizar una subrutina de interrupción. Si se finaliza la subrutina de interrupción de alta prioridad, se coloca FAST frente a la palabra reservada RETFIE.
Instrucción RESET
Realiza un RESET desde el software. Se realiza exactamente lo mismo que si se presionara el botón MCLR en la tarjeta de pruebas del PIC.
Comparadores
Estas instrucciones sirven para comparar resultados o bits de estados de distintos re-gistros. Sirven para tomar decisiones acerca del comportamiento del programa y cambiar su flujo.
Instrucción TSTFSZ
Esta instrucción comprueba que un registro sea cero; si es verdadero, salta una ins -trucción. Su sintaxis es:
TSTFSZ REGISTRO
Ejemplo: Comparar si el registro PORTB es cero. TSTFSZ PORTB
;no es cero ;es cero
Instrucción CPFSEQ
Esta instrucción compara el registro W con cualquier otro registro. Salta si el conteni-do de los registros es idéntico. Su sintaxis es:
CPFSEQ REGITRO
Ejemplo: Comparar PORTA con W. CPFSEQ PORTA
;no son identicos ;son idénticos
Instrucción CPFSGT
Compara si un registro con el registro W. Salta si el contenido del registro es más grande que el contenido de W. Su sintaxis es:
Ejemplo: Comparar si el contenido de un registro es mayor al contenido de W. CPFSGT REGISTRO
;no es mayor ;es mayor
Instrucción CPFSLT
Compara un registro con el registro W. Salta si el contenido del registro es más pe-queño que el contenido de W. Su sintaxis es:
CPFSLT REGISTRO
Ejemplo: Comparar si el contenido de un registro es menor al contenido de W. CPFSLT REGISTRO
;no es menor ;es menor
Instrucción BTFSC
Verifica si un bit específico de un registro es cero, en caso de que sea verdadero, sal-ta una instrucción.
Ejemplo: Verificar que el bit RB4 del registro PORTB sea cero. BTFSC PORTB, RB4
GOTO falso GOTO verdadero
En el ejemplo anterior, si la comparación es falsa, lee la instrucción siguiente la cual es GOTO falso, donde falso es una etiqueta. Si es verdadero, salta la instrucción y lee la ins-trucción GOTO verdadero, donde verdadero es una etiqueta.
Esta instrucción se utiliza para realizar comparaciones y tomar decisiones acerca de un bit específico de un registro.
Instrucción BTFSS
Es idéntico que la instrucción anterior. Verifica que un bit específico de un registro sea uno, en caso de que sea verdadero, salta una instrucción.
Ejemplo: Verificar que el bit RB5 del registro PORTB sea uno. BTFSS PORTB, RB5
GOTO falso GOTO verdadero
En el ejemplo anterior, si la comparación es falsa, lee la instrucción siguiente la cual es GOTO falso, donde falso es una etiqueta. Si es verdadero, salta la instrucción y lee la ins-trucción GOTO verdadero, donde verdadero es una etiqueta.
Al igual que la instrucción anterior, también esta se utiliza para realizar comparacio-nes y tomar decisiocomparacio-nes en cuanto un bit específico de un registro.
Operadores Lógicos
Los operadores lógicos realizan las operaciones del álgebra booleana. Estas ciones en ensamblador son AND, OR, XOR, que corresponden respectivamente a una opera-ción AND (Y), operaopera-ción INCLUSIVE OR (O inclusiva) y EXCLUSIVE OR (O exclusiva).
Operación AND
Se puede realizar de dos maneras: ANDWF y ANDLW. La primera realiza la operación AND entre un registro y el registro W; la segunda, realiza la operaoperación AND entre el re -gistro W y una literal (un número). Sus sintaxis respectivas son:
ANDWF REGISTRO, DESTINO
Donde DESTINO puede ser 0 o 1. Si es cero, se guarda en W, si es uno, se guarda en el mismo registro.
ANDLW LITERAL
Donde LITERAL es un número del 0 al 255.
Operación OR
Igual que la instrucción anterior, se puede realizar de dos maneas: IORWF o con la instrucción IORLW. La primera realiza al operación OR entre un registro y el registro W, la segunda realiza la operación OR entre el registro W y una literal. Sus sintaxis respectivas son:
IORWF REGISTRO, DESTINO
Donde DESTINO puede ser 0 o 1. Si es cero, se guarda en W, si es uno, se guarda en el mismo registro.
IORLW LITERAL
Donde LITERAL puede ser cualquier número del 0 al 255.
Operación XOR
La operación XOR al igual que las anteriores tiene dos formas de hacerse entre re-gistros y una a bit. Primero veamos la operación XOR a rere-gistros.
Se puede realizar de dos maneras: XORWF o XORLW. La primera realiza la opera-ción XOR entre el registro W y cualquier otro registro, la segunda realiza la operaopera-ción XOR entre un registro y una literal. Sus sintaxis respectivas son:
XORWF REGISTRO, DESTINO
Donde destino puede ser 0 o 1. Si es cero, se guarda en W, si es uno, se guarda en el mismo registro.
XORLW LITERAL
Donde LITERAL puede ser cualquier número del 0 al 255.
Hay un caso particular en donde se puede realizar la operación XOR a un bit. Lo que realiza esta instrucción es cambiar el estado de un bit, si es cero, lo cambia a 1, si es uno lo cambia a 0. Puede ser también visto como una negación o una operación toggle (refiriéndose a flip-flops). Esta instrucción es BTG. Su sintaxis es:
BGT REGISTRO, BIT
Donde REGISTRO es cualquier registro y BIT, es el número de bit entre el 0 y el 7. Básicamente se puede usar para realizar parpadeos o cambios de estado en un re-gistro.
Operadores aritméticos
Los operadores aritméticos se utilizan para sumar, restar y multiplicar números y re-gistros. Tomar en cuenta que en ensamblador para PIC no existe la operación DIVISIÓN, mas sin embargo, se puede realizar una operación que la realice. Como la operación módulo depende de la operación división, tampoco existe dentro de las operaciones básicas del en-samblador.
Una cosa muy importante que hay que tomar en cuenta es que al realizar operacio-nes aritméticas se generan cambios en el registro STATUS. Este registro de estado controla el resultado de una operación aritmética o lógica. Su descripción la veremos más adelante cuando veamos registros y lo explicaremos ampliamente por importancia.
Operador NEGF
Este operador sirve para convertir un número contenido en un registro a negativo o viceversa. Su sintaxis es:
NEGF REGISTRO
Donde REGISTRO es cualquier registro que contiene un número entre el -128 a 127.
Suma o adición
Para realizar la suma o adición se suelen usar dos instrucciones: ADDWF para hacer la adición entre el registro W y otro registro o ADDLW para hacer la suma del registro W con una literal. Sus sintaxis respectivas son:
ADDWF REGISTRO, DESTINO
Donde REGISTRO es cualquier registro y DESTINO puede ser un valor 0 o 1. Si es cero, se guarda el resultado en W, si es uno se guarda el resultado en el mismo registro.
ADDLW LITERAL
Donde LITERAL es cualquier número entre el 0 y el 255.
En ambos casos si la suma supera el 255 se encenderá las banderas de Carrier y Overflow del registro STATUS.
Resta o sustracción
Para realizar la suma o sustracción, se suelen usar dos instrucciones: SUBWF para hacer la sustracción entre el registro W y cualquier otro registro o SUBLW para hacer la resta del registro W con una literal. Sus sintaxis respectivas son:
SUBWF REGISTRO, DESTINO
Donde REGISTRO es cualquier registro y DESTINO toma el valor de 0 o 1. Si es ce-ro, se guardará el resultado en el registro W, si es uno, se guardara el resultado en el mismo registro.
SUBLW LITERAL
Donde literal es cualquier número entre el 0 y el 255.
Multiplicación
La multiplicación se realiza con dos instrucciones: MULWF para realizar la multiplica-ción entre el registro W y cualquier otro registro o MULLW para realizar la multiplicamultiplica-ción entre el registro W y una literal. Sus sintaxis respectivas son:
MULWF REGISTRO MULLW LITERAL
Donde LITERAL puede ser cualquier número del 0 al 255. Registro puede ser cual-quier registro de estado o memoria.
NOTA: El resultado de esta operación se guarda en los registros PRODH y PRODL que son el resultado de 16 bits en su nible alto y bajo respectivamente.
Incrementos y decrementos
Los incrementos y decrementos se realizan de dos formas, realizando solo su opera-ción convencional o realizando su operaopera-ción con una comparaopera-ción. Explicaré esto en cada caso.
Incremento
Los incrementos lo que realizan es sumar uno a un determinado registro. Una vez que llegue a 255 el siguiente incremento causará que el contenido del registro sea cero, y se encenderá la bandera de overflow del registro STATUS. La palabra reservada para realizar un incremento se utiliza la palabra reservada INCF. Su sintaxis es la siguiente:
INCF REGISTRO
Donde registro es cualquier registro de memoria.
Para evitar que estemos comprobando el error de OVERFLOW en la bandera STATUS, tenemos una instrucción que lo hace por nosotros y cuando esto sucede, salta una instrucción. Esta instrucción tiene la palabra reservada INCFSZ que compara cuando el re-gistro vuelve a ser cero y como mencioné anteriormente, salta la siguiente instrucción. INCFSNZ realiza lo mismo pero salta la siguiente instrucción si no es cero. Sus sintaxis respectivas son las siguientes:
INCFSZ REGISTRO ;No hay overflow ;Hay overflow
INCFSNZ REGISTRO ;Hay overflow
;No hay overflow
Decrementos
Los decrementos lo que hacen es restar uno a un determinado registro hasta llegar a cero. La palabra reservada para realizar un decremento es DECF y su sintaxis es la siguien-te:
DECF REGISTRO
Hay una instrucción que comprueba automáticamente cuando llega a cero, la cual es DECFSZ y salta la siguiente instrucción cuando esto sucede. DECFSNZ realiza la misma tarea pero salta la siguiente instrucción mientras no sea cero. Sus sintaxis son:
DECFSZ REGISTRO ;no es cero ;es cero DECFSNZ REGISTRO ;es cero ;no es cero
PROGRAMACIÓN BÁSICA DEL PIC18F4550
Ahora en este apartado vamos a ver como se programa el PIC18F4550, pero antes vamos a ver una descripción de los registros que vamos a utilizar en los primeros pasos.
Registros básicos del PIC18F4550
Vamos a ver tres registros básicos del PIC18F4550, los cuales son WREG, TRIS y PORT.
Un registro es un mapeo en memoria RAM de algún dispositivo físico del PIC, como son los puertos de entrada o salida, temporizadores, módulo usb, modulo serial, modulo ADC, etc.
Registro WREG
El registro WREG se utiliza para guardar datos temporales en RAM para operaciones y el intercambio de datos entre las mismas operaciones. Se puede utilizar para regresar da -tos de subrutinas y de tablas.
Registros TRIS
Los registros TRIS sirven para configurar puertos, lo explicaré más adelante. Hay cinco, que son el número de puertos que tiene el PIC18F4550, los cuales son TRISA, TRISB, TRISC, TRISD y TRISE. Todos son de 8 bits, excepto TRISE que es de 4 bits (nibble bajo) los demás no tienen efecto.
Registros PORT
Los registros PORT sirven para leer y escribir puertos, y también lo explicaré más a-delante. Hay cinco, que son el número de puertos que tiene el PIC18F4550, los cuales son PORTA, PORTB, PORTC, PORD y PORTE. Todos son de 8 bits excepto PORTE que es de 4 bits y se lee como cero el nibble alto del registro.
Configurar puertos
Antes de configurar los puertos es necesario saber que si usamos el oscilador exter-no (que es nuestro caso) el puerto A tendrá dos pines meexter-nos los cuales son RA6 y RA7. También están desactivados de forma predeterminada los pines RC4 y RC5 ya que utilizan el módulo USB y no está disponible RC3; esto nos deja el puerto C de bits, a menos que desactivemos el módulo USB. El puerto E tiene solo 3 bits ya que RE3 es usado por MCLR.
dos se usan con el oscilador) se realiza lo siguiente: MOVLW 0x00
MOVWF TRISA
Configurar el puerto B como salida: MOVLW 0xFF
MOVWF TRISB
Configurar la primera mitad del puerto como entrada y la segunda mitad del puerto como salida.
MOVLW 0xF0 ;nibble bajo salida (0) nibble alto entrada (F) MOVWF TRISD
Para los demás puertos es exactamente el mismo procedimiento, considerando las restricciones que tenemos en los pines de los puertos A, C y E.
Leer y escribir puertos
Debemos tener las mismas consideraciones que en el registro TRIS sobre los pines desactivados por el uso de oscilador externo, USB y MCLR. Ahora veamos como se escribe y se leen los puertos.
ESCRIBIR:
MOVLW 0xFF
MOVWF PORTD ;se encienden todos los pines del puerto D
LEER:
O también podemos realizar la lectura hacia un registro de memoria.
MOVFF PORTB, VARIABLE ;se mueve el contenido en el registro PORTB a una variable en RAM.
Consideraciones para el flujo del programa
El PIC cuando es borrado, solo tiene instrucciones NOP en su interior las cuales son el código 0xFFF. Para evitar que al terminar un programa vuelva al inicio de la memoria (VECTOR RESET) debemos de realizar un ciclo infinito que evite que se lea la zona de NOP que está al final del programa. Para esto usamos la etiqueta ciclo y el goto ciclo al final de la plantilla.
Otra consideración importante es que cuando encendemos el microcontrolador PIC18F4550 los puertos son configurados como analógicos, para configurar los puertos como digitales tenemos que colocar 0x0F en el registro ADCON1. Ahora no lo explico porque lo vamos a ver más adelante en Convertidor Analógico Digital.
Antes que cualquier instrucción, se debe de limpiar el registro de trabajo W. Debe de ser la primera instrucción después de la etiqueta main.
Ahora veamos un pequeño programa. Colocaré solo el código que va dentro de la rutina principal llamada main para evitar colocar toda la plantilla.
Ejemplo 1: Encender todo el puerto A.
Solución: Colocar 0xFF o 0x3F en el PORTA y en TRISA 0x00 o 0xC0, considerando
los pines que no están disponibles por el oscilador. Solución 1.1
main:
CLRF WREG ;Limpiamos el registro de trabajo.
MOVLW H'0F' ;Configuramos todos los puertos como digitales MOVWF ADCON1
CRLF TRISA ;Configuramos todo el puerto como salida SETF PORTA
ciclo
GOTO ciclo END
Solución 1.2
main:
CLRF WREG ;Limpiamos el registro de trabajo.
MOVLW H'0F' ;Configuramos todos los puertos como digitales MOVWF ADCON1 MOVLW H'C0' MOVWF TRISA MOVLW H'3F' MOVWF PORTA ciclo GOTO ciclo END
Las dos soluciones nos dan el mismo resultado. Al escribir este código en el pic con el HID Bootloader, veremos que se enciende todo el puerto. En nuestra placa de prueba, encenderán RA4 y RA5.
Ejemplo 2: Encender solo el pin RA4.
Solución: Colocamos 0x00 en TRISA y 0x10 en PORTA o usar BSF para encender
el PIN RA4.
Solución 2.1
main:
CLRF WREG MOVLW H'0F'
MOVWF ADCON1 ;Configuramos los puertos como digitales CLRF TRISA MOVLW H'10' MOVWF PORTA ciclo GOTO ciclo END
Solución 2.2
main:
CLRF WREG MOVLW H'0F'
MOVWF ADCON1 ;Configuramos los puertos como digitales CLRF TRISA ;Todo el puerto como salida
CLRF PORTA ;Ponemos el puerto A en ceros BSF PORTA, RA4 ;Encendemos RA4
ciclo
GOTO ciclo END
Los dos ejemplos realizan exactamente lo mismo. Como podemos ver, se puede co-locar todo el puerto como salida aunque no utilicemos el resto de los pines.
Ejemplo 3: Verificar que el puerto B contenga el número 55h o AAh, en el primer
ca-so, encender RC0, en el segundo caso encender RC1, cualquier otro caso apagar ambos pi-nes; los dos pines no pueden estar encendidos al mismo tiempo. Hacer la comprobación de forma indefinida.
Solución: Colocar la comprobación dentro del ciclo infinito. Utilizar la comparación
de igualdad CPFSEQ.
main:
CLRF WREG MOVLW H'0F'
MOVWF ADCON1 ;Configuramos los puertos como digitales CLRF TRISC ;Todo el puerto como salida
SETF PORTB ;Todo el puerto como entrada CLRF PORTC ;Ponemos el puerto C en ceros ciclo MOVLW H'55' CPFSEQ PORTB GOTO comprueba2 MOVLW H'01' MOVWF PORTC GOTO ciclo
comprueba2 MOVLW H'AA' CPFSEQ PORTB GOTO apagapuerto MOVLW H'02' MOVWF PORTC GOTO ciclo apagapuerto CLRF PORTC GOTO ciclo END
Notemos que cada vez que realizamos un programa más avanzado, va aumentando su tamaño. Todo es dependiendo del objetivo a alcanzar. Hay que tener mucha lógica para realizar un programa para microcontrolador.
Retardos
Para realizar un retardo dentro de un programa podemos usar instrucciones NOP, que equivalen a 4 ciclos del oscilador principal, mas sin embargo, esta condición no se cum-ple cuando usamos Bootloader, ya que realizamos un aumento en frecuencia para el uso de USB. La frecuencia necesaria para la comunicación USB es de 48MHz.
La frecuencia es una cuarta parte, por que cada instrucción se ejecuta en 4 ciclos del oscilador, por lo tanto, la frecuencia de una instrucción es de 12MHz.
Sabemos también que el periodo es inverso de la frecuencia, así que, el tiempo que tarda en realizarse cada instrucción es de 83.33ns.
T =1 f T = 1 48MHz=20.83ns Ti=4T Ti=4x(20.83ns)=83.33ns
Tomando en cuenta que cada instrucción toma 83.33ns, realicemos un retardo de 10μs. Para ello tenemos que dividir 10μs entre 83.33ns; esto nos dará un número, al cual le llamaremos número mágico de retardo, y lo simbolizaremos con #.
#= 10μ s
83.33 ns=120
Entonces tenemos que realizar 120 instrucciones consecutivas para realizar el retar-do de 10μs.
¿Cómo vamos a realizar el retardo o demora? Usaremos decrementos.
Primero vamos a generar una variable en RAM y le vamos a cargar el número má -gico, posteriormente vamos a realizar el decremento hasta llegar a cero. Esto nos garantiza que se realicen # instrucciones.
Ejemplo: Realizar un programa que haga que el led RA5 parpadee cada 10μs. Solución: Realizar un retardo de 10μs y usar BTG para hacer parpadear el led RA5.
UDATA_ACS
RETARDO RES 1 ;Reservamos memoria para el registro RETARDO ;nos saltaremos esta parte de la plantilla
retardo: ;Subrutina de retardo
MOVLW .120 ;Colocamos 120 decimal en WREG
MOVWF RETARDO ;Colocamos el valor de WREG en RETARDO decremento
DECFSZ RETARDO ;Realizamos el retardo GOTO decremento RETURN main: CLRF WREG MOVLW H'0F' MOVLW ADCON1 CLRF TRISA CLRF PORTA ciclo BTG PORTA, RA4 CALL retardo GOTO ciclo END
Como podemos observar en el ejemplo anterior, se realizó una subrutina que realiza el retardo de 10μs.
La división y el módulo
Como ya antes habíamos mencionado, la división en ensamblador para el PIC18F4550 no existe. Nosotros tenemos que realizarla con un código bastante pequeño.
Vamos a las matemáticas básicas: La multiplicación es una suma sucesiva de núme-ros y como la división es la operación contraria, entonces por definición es una resta sucesi-va. Esta operación la realizaremos restando el divisor al dividendo, hasta que sea más grande el divisor que el dividendo; en este momento tendremos el resultado que es el núme-ro de restas que hemos hecho. Si tomamos en cuenta que el módulo es el residuo de la divi-sión, entonces el dividendo que queda al final será el módulo.
Hagamos un algoritmo para la división y posteriormente lo pasaremos al lenguaje ensamblador.
1. Tenemos una variable divisor y una dividendo.
2. Verificamos que el dividendo sea mayor al divisor. Si es cierto seguir, si no ir al paso 6. 3. Restar el divisor al dividendo.
4. Incrementamos uno un contador. 5. Ir al paso 2.
6. El resultado de la división es el contador, el resultado del módulo es el residuo del dividendo.
7. Fin del algoritmo.
Ahora hagamos la subrutina en ensamblador.
UDATA_ACS
DIVISOR RES 1 ;Reservamos memoria para el divisor. DIVIDENDO RES 1 ;Reservamos memoria para el dividendo.
DIVISION RES 1 ;Reservamos memoria para el contador de la división. ;Nos saltaremos esta parte de la plantilla.
division:
CLRF DIVISION ;colocamos en cero el contador.
MOVF DIVIDENDO, W ;Colocamos el valor del dividendo en W. comparadiv
CPFSLT DIVISOR ;Comparamos W con DIVISOR. GOTO hacerdivision
hacerdivision
SUBWF DIVISOR, W ;Realizamos la resta. El resultado se guarda en W. INCF DIVISION ;Incrementamos el contador.
GOTO comparadiv findivision
MOVF DIVISION, W ;Colocamos el resultado de la división en W. ;El resultado se regresará en WREG.
RETURN ;Fin del subproceso.
Para el módulo es exactamente lo mismo, solo cambiaremos el nombre de la sub-rutina, las etiquetas y el resultado.
modulo:
CLRF DIVISION ;colocamos en cero el contador.
MOVF DIVIDENDO, W ;Colocamos el valor del dividendo en W. comparamodulo
CPFSLT DIVISOR ;Comparamos W con DIVISOR. GOTO hacermodulo
GOTO finmodulo hacermodulo
SUBWF DIVISOR, W ;Realizamos la resta. El resultado se guarda en W. INCF DIVISION ;Incrementamos el contador.
GOTO comparamodulo finmodulo
;el resultado del módulo está en W que es el sobrante de la resta. ;El resultado se regresará en WREG.
RETURN ;Fin del subproceso.
Como podemos observar en los algoritmos anteriores, podemos realizar operaciones con las instrucciones básicas del PIC.
El display LCD alfanumérico
El display LCD alfanumérico que mostraré en esta sección, será de 16 caracteres por dos líneas de matriz de 5x7. Realizaré 4 subrutinas para hacer el proceso lo más fácil posible; no voy a explicar estas subrutinas a fondo, solo mencionaré como funcionan y qué
es lo que hay que agregar en caso de requerir una funcionalidad extra. Explicaré un archivo INC que contiene definiciones de las instrucciones del LCD. Para mayor información con-sultar el manual técnico (data sheet) del LCD alfanumérico.
Comencemos con el archivo LCD.INC. Este archivo contiene algunas constantes con instrucciones del LCD que controlan su funcionamiento.
;***************************************** ; Autor: García Olmos Francisco Javier * ; Fecha: 24 de Noviembre de 2011 *
; Archivo: LCD.INC *
; Biblioteca para el display LCD * ;***************************************** ;Acciones del display
CLEAR EQU H'01'
CURSOR_HOME EQU H'02' ;Control del display
DISPLAY_CONTROL EQU H'08' DON EQU H'04' CURSOR_ON EQU H'02' BLINK_ON EQU H'01' ;Modo de entrada DISPLAY_MODE EQU H'04' INCREMENT EQU H'02' SHIFT EQU H'01' ;Shift_Cursor Display SHIFT_CUR_LEFT EQU H'13' SHIFT_CUR_RIGHT EQU H'17' SHIFT_DISP_LEFT EQU H'1B' SHIFT_DISP_RIGHT EQU H'1F' ;Function Set FUNCTION_SET EQU H'20' EIGHT_BIT EQU H'10' LINE_5x7 EQU H'03' LINE_5x10 EQU H'07' LINES_5x7 EQU H'0B' DDRAM_L1 EQU H'80' DDRAM_L2 EQU H'C0' DDRAM_L3 EQU H'90' DDRAM_L4 EQU H'D0'
Ahora mostraré las subrutinas, las cuales se usarán en el proceso de escritura y configuración del LCD alfanumérico.
;Variables necesarias para el funcionamiento del LCD alfanumérico. UDATA_ACS DATO RES 1 DATOTEMP RES 1 RTMUL RES 1 RETARDO1 RES 1 RETARDO2 RES 1
;Nos saltaremos esta parte de la plantilla
;Funciones para escritura y configuración del LCD alfanumérico.
Delay1K: retardo0 DECFSZ RTMUL goto retardo1 goto finretardo retardo1 MOVLW .100 MOVWF RETARDO1 ciclo1 DECFSZ RETARDO1 GOTO retardo2 GOTO retardo0 retardo2 MOVLW .10 MOVWF RETARDO2 ciclo2 DECFSZ RETARDO2 GOTO ciclo2 GOTO ciclo1 finretardo RETURN Comando_LCD:
MOVFF DATO, DATOTEMP RRCF DATO
RRCF DATO RRCF DATO RRCF DATO
MOVFF DATO, PORTD BCF PORTD, RD4 BCF PORTD, RD5 BSF PORTD, RD6 MOVLW .60 MOVWF RTMUL CALL Delay1K BCF PORTD, RD6
MOVFF DATOTEMP, PORTD BCF PORTD, RD4 BCF PORTD, RD5 BSF PORTD, RD6 MOVLW .60 MOVWF RTMUL CALL Delay1K BCF PORTD, RD6 RETURN
Dato_LCD:
MOVFF DATO, DATOTEMP RRCF DATO
RRCF DATO RRCF DATO RRCF DATO
MOVFF DATO, PORTD BSF PORTD, RD4 BCF PORTD, RD5 BSF PORTD, RD6 MOVLW .60 MOVWF RTMUL CALL Delay1K BCF PORTD, RD6
MOVFF DATOTEMP, PORTD BSF PORTD, RD4 BCF PORTD, RD5 BSF PORTD, RD6 MOVLW .60 MOVWF RTMUL CALL Delay1K BCF PORTD, RD6 RETURN Inicializa_LCD: MOVLW .137 MOVWF RTMUL CALL Delay1K MOVLW 0x02 MOVWF PORTD BSF PORTD, RD6 MOVLW .60 CALL Delay1K BCF PORTD, RD6 MOVLW .187 MOVWF RTMUL CALL Delay1K MOVLW FUNCTION_SET IORLW LINES_5x7 MOVWF DATO CALL Comando_LCD MOVLW DISPLAY_CONTROL IORLW DON MOVWF DATO CALL Comando_LCD MOVLW CLEAR MOVWF DATO CALL Comando_LCD MOVLW DISPLAY_MODE IORLW INCREMENT MOVWF DATO CALL Comando_LCD RETURN
Las cuatro subrutinas son muy fáciles de entender, mas sin embargo, será tarea ex -tra, ya que por motivos de tiempo y espacio, no podré hacer en este documento, solo
explica-ré su funcionamiento.
La función Delay1K sirve para darnos un retardo de 1000 ciclos de máquina (4 de os-cilador principal). Esta función recibe un parámetro dentro del registro RTMUL que será el multiplicador, entonces será un retardo de RTMUL por mil ciclos de máquina.
La subrutina Comando_LCD recibe un parámetro en el registro DATO y lo escribe en el LCD en forma de comando. La subrutina Dato_LCD también recibe un parámetro en el re-gistro DATO pero este lo escribirá en el LCD como un DATO y se mostrará.
La subrutina Inicializa_LCD no recibe parámetros. Sirve para configurar el LCD como un Display LCD de 16x2, de matriz de 5x7 y de incremento automático. No se mostrará el cursor y no parpadeará el caracter. Si se necesitan estas funciones, cambiarlas en esta subrutina.
Procedimiento para escribir en el LCD: 1. Inicializar el LCD.
2. Limpiar pantalla.
3. Moverse a la posición deseada. 4. Escribir.
Bueno, entonces hagamos un pequeño programa que muestre HOLA en la pantalla pero antes mencionaré que se conectará al puerto D.
Ejemplo 1: Mostrar HOLA en el LCD de 16x2.
Solución: Utilizar las funciones explicadas anteriormente, así como el procedimiento.
; *** Aquí van los registros de memoria *** ; *** Saltaremos esta parte de la plantilla ***
; *** Aquí van las subrutinas o funciones del LCD *** main: CLRF WREG MOVLW H'0F' MOVWF ADCON1 CLRF TRISD CLRF PORTD
CALL Inicializa_LCD ;Inicializamos el LCD.
MOVLW CLEAR ;Instrucción para limpiar en el LCD (ver LCD.INC). MOVWF DATO ;Movemos al parámetro DATO.
CALL Comando_LCD
MOVLW DDRAM_L1 ;Instrucción para moverse a la línea 1
ADDLW .6 ;Sumamos 6 para desplazarnos 6 caracteres y centrar el texto. MOVWF DATO ;Movemos al parámetro DATO.
CALL Comando_LCD MOVLW 'H'
MOVWF DATO CALL Dato_LCD MOVLW 'O'
MOVWF DATO CALL Dato_LCD MOVLW 'L' MOVWF DATO CALL Dato_LCD MOVLW 'A' MOVWF DATO CALL Dato_LCD
CLRF PORTD ;Limpiamos el puerto D. ciclo
GOTO ciclo END
Se puede observar que lo más complicado de entender son los comandos, los datos se mandan consecutivamente sin mayor problema. Un punto importante es que se realizó una suma de 6 para desplazar la memoria y centrar el texto. Si no hubiéramos hecho esta suma, el texto hubiera aparecido desde el primer carácter en el LCD.
Ahora veamos las conexiones del LCD. El LCD tiene dos pines de alimentación, uno de contraste, tres de control, ocho de datos y dos extras en caso de que tenga luz de fondo (back light).
VSS y VDD se conectan a +5V y tierra, y el contraste VEE se conecta a un potenciómetro de 10KΩ, el pin 4 de control RS (decide si es dato o comando) a RD4, el pin 5 de control RW (lectura / escritura) va conectado a RD5 o a tierra, el pin 6 de control E (habilitado) a RD6. De los siguientes ocho pines de datos solo se utilizarán los últimos 4 (D4, D5, D6, D7) que van conectados a RD0, RD1, RD2, y RD3; los pines D0, D1, D2 y D3 no se conectan. En caso de luz de fondo se conectaran a +5V y tierra los últimos pines.
El siguiente diagrama muestra la forma de conexión del LCD alfanumérico de 16x2.
PROGRAMACIÓN CON INTERRUPCIONES
En este capítulo veremos la programación con interrupciones, veremos más profun-damente las direcciones de los vectores de interrupción y las rutinas ISRL e ISRH. Veremos los registros INTCON y RCON que son los relacionados con las interrupciones.
Primero explicaré que es una interrupción. Básicamente es una petición que se le ha-ce al microproha-cesador o microcontrolador (no son lo mismo) para que deje la tarea principal y haga una tarea específica. Un ejemplo de una interrupción es el RESET, que deja la tarea principal para reiniciar la memoria del microcontrolador.
Clasificación de las interrupciones
Se pueden clasificar las interrupciones de tres formas distintas: Por enmascaramien-to, por prioridad y por su locación.
Clasificación por enmascaramiento
Hay dos tipos de interrupciones: enmascarables y no enmascarables.
1. Las interrupciones enmascarables son aquellas donde el microcontrolador puede decidir si la puede atender o no (enmascara la interrupción).
2. Las interrupciones no enmascarables son aquellas que forzosamente se tienen que atender, por ejemplo, el RESET.
Clasificación por prioridad
Hay dos tipos de interrupciones: de alta prioridad y de baja prioridad.
1. Las interrupciones de alta prioridad son aquellas que no pueden interrumpirse mien-tras son atendidas.
2. Las interrupciones de baja prioridad si pueden interrumpirse por otras mientras son atendidas, mas sin embargo su tiempo de respuesta es más amplio que el de una de alta prioridad.
Clasificación por locación
Hay tres tipos de interrupciones: Internas, cambio de flanco y externas.
1. Las interrupciones internas son las que se provocan por los dispositivos embebidos en el microcontrolador, como son temporizadores, ADC, USB, USART, Comparadores, etc.
nibble alto del puerto B.
3. Las interrupciones externas son las que se provocan al activar los tres primeros pines del puerto B (RB0, RB1 y RB2); se identifican como INT0, INT1 e INT2. INT0 siempre es de alta prioridad.
Vectores de interrupción
Antes de comenzar, explicaré que es un vector. En algunos lenguajes de programa-ción como C ANSI, C++, BASIC, etc, se pueden realizar vectores de datos y se manejan los datos a través de un apuntador, el cual contiene una dirección de memoria. Nosotros nos referiremos a un vector de interrupción a la posición de memoria a la cual el microcontrolador se va a referir cuando se realice una interrupción. En los microcontroladores PIC18 hay 3 vectores de interrupción: RESET, alta prioridad y baja prioridad. Abajo explicare sus direccio-nes:
0x0000 MCLR Vector de interrupción de RESET. 0x0008 ISRH Vector de interrupción de alta prioridad. 0x0018 ISRL Vector de interrupción de baja prioridad.
Estas direcciones se cargan automáticamente en el stack de direcciones al realizarse una interrupción. Mas sin embargo, cuando trabajamos con el BOOTLOADER, estas direcciones cambian.
0x1000 REMAPED MCLR Vector remapeado de RESET. 0x1008 REMAPED ISRH Vector remapeado de alta prioridad. 0x1018 REMAPED ISRL Vector remapeado de baja prioridad.
Como observamos, son las que están en nuestra plantilla. Podríamos hacer la rutina de interrupción en esta dirección pero el espacio es muy pequeño, lo que realizamos es un salto a las rutinas llamadas main, ISRH e ISRL. En estas rutinas, ya podemos extendernos lo que sea necesario, siempre y cuando nos alcance la memoria de programa del microcontro -lador PIC.
Declaramos dichos vectores con CODE o con ORG como ya lo habíamos mencionado anteriormente.
REMAPED_RESET_VECTOR CODE 0x1000 goto main ;saltamos a la rutina main.
Configurar las interrupciones
Las interrupciones se configuran cambiando sus bits específicos en los registros de interrupción INTCON, INTCON2, INTCON3, PIR1, PIR2, IPR1, PIR2, PIE1 y PIE2. Para las interrupciones externas y de cambio de flanco se usan solo los tres primeros registros men -cionados anteriormente. La única interrupción que no se configura es RESET.
El método que se utiliza es el siguiente: 1. Borramos la bandera (FLAG) de interrupción.
2. Le damos una prioridad ya sea alta o baja, excepto INT0 que es de alta prioridad. 3. Habilitamos la interrupción.
4. Habilitamos las interrupciones globales con RCON.
5. Habilitar las prioridades globales de las interrupciones con INTCON.
Las interrupciones globales son todas aquellas interrupciones enmascarables que hay dentro del microcontrolador. Se activan colocando el bit IPEN del registro RCON en uno. Las prioridades globales se activan colocando en uno el bit GIEH para las interrupciones de alta prioridad y GIEL para las interrupciones de baja prioridad o ambas del registro INTCON.
Las interrupciones externas tienen que configurar también el flanco en el cual estas trabajan; este flanco puede ser de subida o de bajada, esto significa que al presionar un botón, el voltaje cambia de bajo a alto para subida o de alto a bajo para bajada.
Una configuración adicional es el uso de resistencias PULL-UP. Estas resistencias realizan que el puerto B siempre esté estado alto y evitan que se usen resistencias fuera del microcontrolador. Se activan colocando el bit RBPU en cero del registro INTCON2. Al utilizar estas resistencias, el flanco de las interrupciones externas debe de ser de bajada y el botón debe de estar conectado a tierra para hacer un cambio a estado bajo.
Para mayor referencia de los registros INTCON, RCON y los demás mencionados anteriormente, revisar el manual.
Revisar las interrupciones
Cuando una interrupción se ha realizado, se enciende la bandera (FLAG) respectiva de cada interrupción. Dependiendo de la configuración verificamos que se ha realizado la interrupción en las rutinas ISRH o ISRL.
La interrupción externa INT1 tiene su bandera (FLAG) en INTCON. El bit se llama INT0IF y está en cero mientras no se ha producido la interrupción y cambia a uno cuando se ha realizado. Una acción muy importante es borrar la bandera de forma manual para evitar que se realice el proceso de interrupción cuando no se ha interrumpido el microcontrolador por esta interrupción.
Ejemplo: Revisar si se realizó la interrupción INT1 como interrupción de baja
prioridad.
ISRL:
movff STATUS,STATUS_TEMP ;guarda el registro de estado movff WREG,WREG_TEMP ;guarda el registro de trabajo movff BSR,BSR_TEMP ;guarda el registro BSR
btfss INTCON3, INT1IF ;Revisar si se realizó por INT1 goto finintl ;No se realizó por INT1
;si se realizó por INT1
bcf INTCON3, INT1IF ;Borrar la bandera ;Seguir con el proceso de interrupción. finintl
movff BSR_TEMP,BSR ;restaura el registro BSR
movff WREG_TEMP,WREG ;restaura el registro de trabajo movff STATUS_TEMP,STATUS ;restaura el registro de estado retfie
Como podemos observar, hay que conservar el código que recupera los estados de BSR, WREG y STATUS, ya que son necesarios para otras operaciones. Observamos también que se utilizó el comparador de bits BTFSS y el cambio de estado de bit a cero con la instrucción BCF.
Si hubiera más interrupciones habría que hacer el mismo procedimiento para cada una de ellas.
Interrupciones externas
En este apartado me enfocaré solamente en las interrupciones externas. Realizaré el código de configuración de las tres interrupciones y posteriormente realizaré su verificación en las subrutinas ISRH e ISRL.
Ejemplo 1: Realizar la configuración de las interrupciones externas con resistencias
de pull-up y flanco de bajada. INT1 es de alta prioridad e INT2 es de baja prioridad. Colocar el Puerto C como salida.
Solución 1: Realizar el procedimiento de configuración de las interrupciones
toman-do en cuenta que INT0 siempre es de alta prioridad. Colocar el puerto B como entrada y el puerto C como salida colocando TRISB en uno y TRISC en cero.
main:
clrf WREG movlw H'0F'
movwf ADCON1 ;configuramos los puertos como digitales. setf TRISB ;Puerto B como entrada.
clrf TRISC ;Puerto C como salida. clrf PORTC ;Puerto C apagado.
; *** Configuramos las interrupciones *** ;INT0
BCF INTCON, INT0IF ;Limpiamos la bandera IF. BCF INTCON2, INTEDG0 ;Flanco de bajada.
BSF INTCON, INT0IE ;Habilitamos la interrupción. ;INT1
BCF INTCON3, INT1IF ;Limpiamos la bandera IF. BCF INTCON2, INTEDG1 ;Flanco de bajada.
BSF INTCON3, INT1IP ;INT1 de alta prioridad (IP=1).
BSF INTCON3, INT1IE ;Habilitamos la interrupción. ;INT2
BCF INTCON3, INT2IF ;Limpiamos la bandera IF. BCF INTCON2, INTEDG2 ;Flanco de bajada.
BCF INTCON3, INT2IP ;INT2 de baja prioridad (IP=0).
BSF INTCON3, INT2IE ;Habilitamos la interrupción. ;Resistencias de PULL-UP
BCF INTCON2, RBPU
;Interrupciones Globales
BSF RCON, IPEN ;Habilitamos las interrupciones globales.
BSF INTCON, GIEH ;Habilitamos las interrupciones globales de alta prioridad. BSF INTCON, GIEL ;Habilitamos las interrupciones globales de baja prioridad. ciclo
goto ciclo ;ciclo infinito END
Del ejemplo anterior marque las configuraciones alta y baja prioridad. Ver como cambia BSF por BCF.
Ejemplo 2: Usar INT0 para encender RC0, INT1 para encender RC1 e INT2 para
apagar ambos bits.
Solución 2: Verificar las interrupciones en las rutinas de alta y baja prioridad con
ISRH:
btfss INTCON, INT0IF ;Revisamos si se produjo INT0. goto revisaint1
bcf INTCON, INT0IF ;Limpiamos la bandera. bsf PORTC, RC0 ;Encendemos RC0.
revisaint1
btfss INTCON3, INT1IF ;Revisamos si se produjo INT1. goto finisrh
bcf INTCON3, INT1IF ;Limpiamos la bandera. bsf PORTC, RC1 ;Encendemos RC1.
finisrh
retfie FAST
ISRL:
movff STATUS,STATUS_TEMP ;guarda el registro de estado movff WREG,WREG_TEMP ;guarda el registro de trabajo movff BSR,BSR_TEMP ;guarda el registro BSR
btfss INTCON3, INT2IF ;Revisar si se realizó por INT1 goto finisrl ;No se realizó por INT1
bcf INTCON3, INT2IF ;Borrar la bandera clrf PORTC ;Apagamos el puerto
finisrl
movff BSR_TEMP,BSR ;restaura el registro BSR
movff WREG_TEMP,WREG ;restaura el registro de trabajo movff STATUS_TEMP,STATUS ;restaura el registro de estado retfie
En este ejemplo vemos el cambio de verificación entre rutinas de baja y alta prioridad y la forma en la que se realizan los procedimientos requeridos para sus tareas respectivas.
Interrupción del puerto B por cambio de flanco
Esta interrupción se activa cuando cambiamos el estado del nibble alto del puerto B (RB4, RB5, RB6 y RB7). Se activa cuando pasamos de estado bajo a alto y cuando pasamos de estado alto a bajo, por eso su nombre de cambio de flanco.
La interrupción al igual que las interrupciones externas, se configura y se le asigna prioridad sus respectivos bits y según su prioridad, se verifica con su bandera (RBIF).
Ejemplo 1: Configurar la interrupción de cambio de flanco como interrupción de alta
prioridad. Utilizar resistencias de PULL-UP. Configurar el puerto C como salida.
Solución 1: Realizar el procedimiento para configurar interrupciones. Se tiene que
main:
clrf WREG movlw H'0F'
movwf ADCON1 ;Configuramos todos los puertos digitales. setf TRISB ;Puerto B como entrada.
clrf TRISC ;Puerto C como salida. clrf PORTC ;Apagamos el puerto. ;Configuración de las interrupciones
;Interrupción de cambio de flanco
bcf INTCON, RBIF ;Limpiamos la bandera.
bsf INTCON2, RBIP ;Le asignamos prioridad alta. bsf INTCON, RBIE ;Habilitamos la interrupción. ;Ressitencias de PULL-UP
bcf INTCON2, RBPU ;Habilitamos las resistencias de pull up. ;Interrupciones globales
bsf RCON, IPEN ;Habilitamos prioridades en interrupciones.
bsf INTCON, GIEH ;Habilitamos la interrupciones de alta prioridad.
;No es necesario habilitar las interrupciones globales de baja prioridad ;por que no las usaremos.
ciclo
goto ciclo ;ciclo infinito
Como observamos en los comentarios, no activamos las interrupciones de baja prioridad ya que no las vamos a usar.
Ejemplo 2: Revisar si se provocó una interrupción por cambio de flanco. Si sucedió,
verificar que botón fue apretado. RB4 encenderá o apagará según el estado de RC0, RB5 encenderá o apagará según el estado de RC1, RB6 cambiará el estado de cada Led y RB7 apagará los Leds.
Solución 2: Verificar con BTFSS que haya sucedido la interrupción y con BTFSC
verificar que botón está en cero (estado apretado). Tomar en cuenta que se utiliza lógica negativa por el uso de las resistencias de PULL-UP.
IRSH:
btfss INTCON, RBIF ;Verificar si se produjo la interrupción RB. goto finisrh ;Ir al final de la rutina de interrupción.
bcf INTCON, RBIF ;Borrar la bandera.
btfsc PORTB, RB4 ;Verificar si se apretó el botón RB4. goto revisarb5 ;Revisar el siguiente botón.
btg PORTC, RC0 ;Cambiar el estado de RC0
goto finisrh ;Ir al final de la rutina de interrupción. revisarb5
btfsc PORTB, RB5 ;Verificar si se apretó el botón RB5. goto revisarb6
btg PORTC, RC1 goto finisrh revisarb6
btfsc PORTB, RB6 ;Verificar si se apretó el botón RB6. goto revisarb7
btg PORTC, RC0 ;Cambia el estado de RC0. btg PORTC, RC1 ;Cambia el estado de RC1. goto finirsh
revisarb7
btfsc PORTB, RB7 ;Verificar si se apretó el botón RB7. goto finisrh ;Ir al final de la rutina de interrupción. clrf PORTC
finisrh
retfie FAST
Como podemos observar, además de preguntar por la interrupción, hay que pregun -tar por el botón que se ha apretado. Como usamos lógica negativa, cuando se presiona un botón, el bit queda en estado bajo y el microcontrolador lo reconoce como cero lógico.
¿Qué pasa si se aprietan dos botones al mismo tiempo? Pues viendo el algoritmo, el más bajo será el que reconozca como apretado, el otro, será desechado. También podríamos usar operaciones AND para ver si se apretaron dos al mismo tiempo. Sin embargo por falta de espacio y de tiempo, será tarea adicional del lector hacerlo.
A partir de ahora, todo lo que veamos será programado con interrupciones. Todos los módulos embebidos en el microcontrolador tienen interrupciones y se verificarán y configura-rán exactamente igual que como lo hicimos con estas dos interrupciones.
PROGRAMACIÓN DE HARDWARE INTERNO Y EXTERNO
En este capítulo enseñaré a programar hardware externo e interno. Como hardware externo, enseñaré el uso de Display de 7 segmentos, teclado matricial de 4x4 y como conec -tar transistores, puente H y compuertas lógicas con el PIC18F4550. Como hardware interno enseñaré a programar los temporizadores y contadores, así como el módulo ADC.
Tablas
Antes de comenzar, me gustaría ver un tema un poco complicado pero que nos ahorrará mucho código a la hora de realizar codificaciones: Las tablas.
¿Qué son las tablas? Son estructuras de datos guardados en la memoria de progra-ma ROM (flash en el caso de PIC18F). Es una alternativa a los apuntadores, ya que el uso de apuntadores es más complicado aún que las tablas. Esto también nos ayuda a ahorrar memoria RAM, pues es bastante reducida en caso de microcontroladores PIC18.
¿Cómo funciona una tabla? Realizamos retorno de literales ordenadas una tras otra, y saltamos las instrucciones necesarias para obtener el resultado requerido. Para realizar esta operación utilizamos el registro PC (Program Counter) que es registro que maneja la memoria de programa.
Le sumaremos al PCL (Program Counter Low Byte) el número de instrucciones que queremos que salte, pero tomando en cuenta que cada instrucción toma 2 direcciones de memoria, entonces deberán de ser el número de saltos por dos. Tomar en cuenta que si multiplicamos por dos, el número máximo de saltos deberá ser de 127.
Para realizar una tabla utilizaremos el registro W para colocar el número de instruc-ciones que deseamos saltar y realizaremos una subrutina que realice este procedimiento.
Ejemplo 1: Realizar una tabla que retorne el número en BCD (Código para display
de 7 Segmentos) del 0 al 9.
Solución 1: Realizar una subrutina que realice la suma al registro PCL y retorne el
número en BCD.
tabla_bcd:
mullw H'02'
movf PRODL, W ;Movemos el resultado de la multiplicación a W
addwf PCL, F ;Hacer la suma de W con PCL y guardarla en el mismo registro PCL retlw H'3F' ;Cero en BCD
retlw H'06' ;Uno en BCD retlw H'5B' ;Dos en BCD
retlw H'4F' ;Tres en BCD retlw H'66' ;Cuatro en BCD retlw H'6B' ;Cinco en BCD retlw H'7B' ;Seis en BCD retlw H'87' ;Siete en BCD retlw H'EF' ;Ocho en BCD retlw H'6F' ;Nueve en BCD
Como vemos, es fácil realizar la tabla, pero lo difícil es colocarla en el lugar correcto del código para que funcione, ya que el PCL es 1 byte (8 bits) y solo podrá contar hasta el 255. No habrá problema si estamos en un lugar que esté entre el 0 y el 246 de PCL, pero si lo hará si rebasamos este límite. Es recomendable hacer tablas pequeñas y colocadas exac-tamente al principio del código (Antes de las rutinas de Interrupción).
Ahora veamos como recuperar el código. Solo colocaré el algoritmo.
Ejemplo 2: Realizar el algoritmo de interrupción de INT0. Para que cada vez se
pre-sione, aumente un contador hasta el 9, llegando a este punto, reiniciarlo. Mostar el resultado en BCD en el puerto D.
Solución 2: Realizar el algoritmo en ISRH comprobando la interrupción INT0,
reali-zando comparaciones, incrementos y llamando a la tabla tabla_bcd realizada anteriormente.
ISRH:
btfss INTCON, INT0IF ;Comprobar si se realizó la interrupción. goto finisrh
bcf INTCON, INT0IF ;Limpiar la bandera movlw H'09'
cpfseq CONTADOR goto aumenta
clrf CONTADOR ;Coloca cero en contador. goto muestra
aumenta
incf CONTADOR muestra
movf CONTADOR, W ;Copiamos el contenido de CONTADOR a W. call tabla_bcd ;Llamar a la tabla.
movwf PORTD ;Colocar el valor devuelto de la tabla en PORTD. finisrh
retfie FAST
Con este pequeño algoritmo, hemos realizado un contador con interrupciones y un display de 7 segmentos.
Nota 1: Como estamos usando lógica positiva, solo podemos usar un display de 7
segmentos de cátodo común. Utilizar lógica negativa en la tabla para usar un display de áno-do común o utilizar un inversor externo 74LS04 o 74HS04.