168
Introducción al PIC Introducción Bienvenidos al inicio del Tutorial sobre PICs. Estas páginas te llevaran desde la estructura básica del dispositivo, hasta los métodos y técnicas de programación. También habrá sugerencias de como modificar el código para que lo puedas adaptar el PIC a tus propias aplicaciones. No incluiré diagramas de arquitectura interna, ya que esto puede llevar a confusiones. Si quieres echar un vistazo a la 'datasheet', la puedes bajar del sitio de Microchip . Para empezar, echemos un vistazo al PIC. Microcontrolador Microchip PIC 16F84 Microchip fabrica una serie de microcontroladores llamados PIC. Puedes ver toda la gama de sus microcontroladores aquí . Los hay disponibles de distintas capacidades, desde algunos tipos básicos con poca memoria, hasta los que tienen convertidores Analógico a Digital (ADC ) incluidos o incluso los que llevan dentro PWMs (Pulse Width Modulators = Moduladores de Ancho de Pulso). Voy a concentrarme en el PIC 16F84. Una vez que aprendas como programar un tipo de PIC, aprender el resto será fácil. Hay diversas formas de programar el PIC, - usando BASIC, C, o Lenguaje Ensamblador. Voy a mostrarte el Lenguaje Ensamblador. No te asustes. Solo hay 35 instrucciones que aprender, y es la manera más económica de programar los PICs, ya que no necesitas ningún otro software extra que no sea de los gratuitos. Los pines del 16F84 Mas abajo verás el diagrama de patillas(pines en adelante) del PIC 16F84. Pasaré por cada pin, explicando para que se utiliza cada uno.

Introducción al PIC

Embed Size (px)

Citation preview

Page 1: Introducción al PIC

Introducción al PIC

Introducción

Bienvenidos al inicio del Tutorial sobre PICs. Estas páginas te llevaran desde la estructura

básica del dispositivo, hasta los métodos y técnicas de programación. También habrá

sugerencias de como modificar el código para que lo puedas adaptar el PIC a tus propias

aplicaciones. No incluiré diagramas de arquitectura interna, ya que esto puede llevar a

confusiones. Si quieres echar un vistazo a la 'datasheet', la puedes bajar del sitio de Microchip.

Para empezar, echemos un vistazo al PIC.

Microcontrolador Microchip PIC 16F84

Microchip fabrica una serie de microcontroladores llamados PIC. Puedes ver toda la gama de

sus microcontroladores aquí . Los hay disponibles de distintas capacidades, desde algunos

tipos básicos con poca memoria, hasta los que tienen convertidores Analógico a Digital (ADC)

incluidos o incluso los que llevan dentro PWMs (Pulse Width Modulators = Moduladores de

Ancho de Pulso). Voy a concentrarme en el PIC 16F84. Una vez que aprendas como

programar un tipo de PIC, aprender el resto será fácil.

Hay diversas formas de programar el PIC, - usando BASIC, C, o Lenguaje Ensamblador. Voy a

mostrarte el Lenguaje Ensamblador. No te asustes. Solo hay 35 instrucciones que aprender, y

es la manera más económica de programar los PICs, ya que no necesitas ningún otro software

extra que no sea de los gratuitos.

Los pines del 16F84

Mas abajo verás el diagrama de patillas(pines en adelante) del PIC 16F84. Pasaré por cada

pin, explicando para que se utiliza cada uno.

RA0 a RA4

Page 2: Introducción al PIC

RA es un puerto bidireccional. Eso quiere decir que puede ser configurado como entrada o

como salida. El número que hay después de RA indica el numero de bit (0 a 4). Por tanto,

tenemos un puerto bidireccional de 5 bits donde cada bit puede ser configurado como entrada o

como salida.

RB0 a RB7

RB es un segundo puerto bidireccional. Se comporta exactamente de la misma manera que

RA, excepto que este tiene 8 bits.

VSS y VDD

Estos son los pins de alimentación. VDD es la alimentación positiva, y VSS es el negativo de la

alimentación, o 0 Voltios. La tensión máxima de alimentación que puedes utilizar son 6 Voltios,

y el mínimo son 2 Voltios.

OSC1/CLK IN y OSC2/CLKOUT

Estos pines son donde conectaremos el reloj externo, para que el microcontrolador disponga de

algún tipo de temporización.

MCLR

Este pin se utiliza para borrar las posiciones de memoria dentro del PIC (p.ej. cuando quiero

reprogramarlo). Durante el funcionamiento normal está conectado a la alimentación positiva.

INT

Este es un pin de entrada que puede ser monitorizado. Si el pin se pone a nivel alto, podemos

hacer que el programa se reinicie, se pare o cualquier otra función de deseemos. No lo

utilizaremos mucho.

TOCK1

Esta es otra entrada de reloj, que opera con un temporizador interno. Opera aisladamente del

reloj principal. De nuevo, este tampoco lo utilizaremos mucho.

Como Programar el PIC

Bien, espero que no te hayas asustado mucho. Ahora, querrás conocer como programar el PIC,

pero además de aprender las instrucciones de código de ensamble, ¿como programas

Page 3: Introducción al PIC

realmente ese código y lo metes en el PIC? Pues hay dos maneras, la sencilla y la "Hazlo tu

mismo". La manera sencilla es comprar un programador de PIC, que se conecte a tu PC, que

trae un software con el que puedes programar el PIC. La "Hazlo tú mismo" se trata de que

construyas tu propio programador y utilices software gratuito de Internet y lo programes de ese

modo.

Si prefieres el método "hazlo tu mismo", te recomendaría este sitio. Pulsa sobre "Supported

Programmers" para ver los circuitos. El más económico es el "TAIT Classic Programmer". El

software para programar el PIC también lo puedes bajar de esa página, ves a "Download".

Si quieres ir por la vía fácil, echa un vistazo a este sitio: (falta el sitio, el del texto original no

funciona).

Otro buen sitio de software gratuito es este. Este permite utilizar cualquier programador, puesto

que el software es completamente configurable.

Cualquier método funcionará, ya que ambos darán el mismo resultado, programar el PIC.

Lo siguiente que necesitas es un ensamblador. Este convertirá el programa que escribas en un

formato que el PIC comprende. El mejor es del propio Microchip, llamado MPLAB. Es un

programa de ventanas, que incluye un editor, un simulador y el ensamblador. Este es un

software escrito por los propios fabricantes del PIC, y por encima de todo es gratuito !!!

La siguiente imagen ilustra el proceso de programación de un PIC.

También recomiendo utilizar una placa de inserción para hacer tus circuitos, mientras juegas

con el PIC. Hay varios tamaños disponibles.

A continuación veremos como conectar un circuito simple para el desarrollo con el PIC.

Conectarse al microcontrolador PIC

Una placa de entrenamiento sencilla

Page 4: Introducción al PIC

Bien, ahora ya tienes tu programador, y uno o dos PICs. Es muy simple conocer la teoría para

saber como programar el PIC, pero el verdadero aprendizaje viene cuando intentas probar tu

código en un PIC y ves los resultados en tu propio circuito. He incluido el diagrama de un

circuito que muestra una placa de entrenamiento muy básica y económica. Por supuesto, le

puedes añadir LEDs y switches, pero yo he dejado las patillas sin conectar. Puedes monitorizar

los pines de entrada/salida conectando LEDs directamente a los pines, y se encenderán

cuando los pines se pongan a nivel alto. También, puedes añadir switches a los pines, para

poder seleccionar que pines poner a nivel alto, y cuales a nivel bajo. Básicamente, lo que estoy

diciendo es que si comienzas con este circuito, puedes añadir lo que creas necesario.

La linea de alimentación está puesta a 6 Voltios, que es el máximo voltaje para el PIC. Puedes

utilizar cualquier voltaje inferior, hasta un mínimo de 2 Voltios. C3 es conocido como un

condensador de 'bypass'. Todo lo que se hace C3 es reducir el ruido de la linea de

alimentación. X1 es un cristal de 4 MHz. Puedes utilizar un circuito RC (resistencia y

condensador) (Nota de edición: crear enlace aquí), pero el precio del cristal es insignificante, y

es mas estable. C1 y C2 ayudan a reducir cualquier desviación en la oscilación cristal, y a

eliminar cualquier ruido no deseado antes de que la señal llegue al PIC.

Page 5: Introducción al PIC

Buenas técnicas para programar

Antes de meternos en harina con la programación del PIC, creo que ahora es un buen

momento para explicar algunas técnicas para programar bien.

Si escribes un ; (punto y coma) en cualquier punto de tu programa, el compilador ignorará

cualquier cosa que haya detrás de él, hasta llegar al retorno de carro. Esto significa que

podemos añadir comentarios a nuestro programa que nos recuerden que estábamos haciendo

en ese punto. Esta es una buena práctica incluso para los programas más sencillos. Ahora

mismo puede que entiendas completamente qué es lo que hace tu programa, pero dentro de

unos meses, puede que te acabes tirando de los pelos. Por tanto, utiliza comentarios donde

puedas , no hay límites.

Segundo, puedes asignar nombres a las constantes vía los registros (hablaremos de estos más

adelante). Hace lo que estás escribiendo mucho más sencillo de leer, para saber de que valor

se trata, mas que intentar entender que significan todos esos números. Así que utiliza nombres

reales como CONTADOR. Date cuenta de que hemos puesto el nombre en letras mayúsculas.

Esto lo hace destacar, y también significa (por convención) que se trata de una constante.

Tercero, añade algún tipo de cabecera en tus programas utilizando los punto y coma. Un

ejemplo sería algo así:

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Autor:;; Fecha:;; Versión:;; Titulo:;;;; Descripción:; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

Date cuenta de que hemos hecho una especie de caja utilizando puntos y comas. Esto es

simplemente para hacerlo más pulcro.

Finalmente, prueba y documenta el programa sobre papel también. Puedes usar o bien

diagramas de flujo o bien algoritmos o lo que tu quieras. Esto te ayudará a escribir tu programa

paso a paso.

Page 6: Introducción al PIC

Bien, eso es todo al respecto, vamos a entrar en materia.

Los Registros

Un registro es un lugar dentro del PIC que puede ser escrito, leído o ambas cosas. Piensa en

un registro como si fuese un trozo de papel donde tu puedes ver la información o escribirla.

La figura de más abajo muestra el mapa de registros del interior del PIC16F84. No te

preocupes si no has visto nada parecido antes, es solo para mostrar donde están los diferentes

bits y piezas dentro del PIC, y nos ayudará a explicar unos cuantos comandos.

La primera cosa que notarás es que está dividido en dos - Banco 0 y Banco 1. El Banco 1 es

utilizado para controlar las propias operaciones del PIC, por ejemplo para decirle al PIC cuales

bits del Puerto A son entradas y cuales son salidas. El Banco 0 se utiliza para manipular los

Page 7: Introducción al PIC

datos. Un ejemplo es el siguiente: Digamos que queremos poner un bit del puerto A a nivel alto.

Lo primero que necesitamos hacer es ir al Banco 1 para poner ese bit o pin en particular en el

puerto A como salida. Después volvemos al Banco 0 y enviamos un 1 lógico a ese pin.

Los registros que vamos a usar mas comunes en el Banco 1 son STATUS, TRISA y TRISB. El

primero permite volver al Banco 0, TRISA nos permite establecer los pines que serán entradas

y los que serán salidas del Puerto A, TRISB nos permite establecer los pines que serán

entradas y los que serán salidas del puerto B.

Vamos a ver con más detenimiento estos tres registros.

STATUS

Para cambiar del Banco 0 al Banco 1 utilizamos el registro STATUS. Hacemos esto poniendo el

bit 5 del registro STATUS a 1. Para cambiar de nuevo al Banco 0, ponemos el bit 5 del registro

STATUS a 0. El registro STATUS se localiza en la dirección 03h (la 'h' significa que el número

es hexadecimal).

TRISA y TRISB

Están localizados en las direcciones 85h y 86h respectivamente. Para programar que un pin

sea una salida o una entrada, simplemente enviamos un 0 o un 1 al bit en cuestión en el

registro. Ahora, podemos hacer esto ya sea en binario o en hexadecimal. Personalmente uso

ambos, ya que el binario ayuda mucho a visualizar el puerto. Si no estás familiarizado con el

paso de binario a hexadecimal y viceversa, utiliza una calculadora científica.

Entonces en el puerto A tenemos 5 pines, por tanto 5 bits. Si deseamos poner uno de los pines

como entrada, enviamos un 1 al bit en cuestión. Si deseamos poner uno de los pines como

salida, ponemos un 0 en ese bit. Los bits están definidos de manera correspondiente con los

pines, en otras palabras el bit 0 es el RA0, el bit 1 es el RA1, el bit 2 es el RA2, y así

sucesivamente. Vamos a tomar un ejemplo. Si queremos poner RA0, RA3 y RA4 como salidas, y

RA1 y RA2 como entradas, enviamos esto: 00110 (06h). Date cuenta de que el bit cero está a la

derecha, como se muestra aquí:

Pin del Puerto A RA4 RA3 RA2 RA1 RA0

Numero de bit 4 3 2 1 0

Valor Binario 0 0 1 1 0

Lo mismo se aplica para TRISB.

PORTA y PORTB

Page 8: Introducción al PIC

Para poner uno de nuestros pines de salida a nivel alto, simplemente ponemos un 1 el bit

correspondiente en nuestro registro PORTA o PORTB. El formato es el mismo que para los

registros TRISA y TRISB. Para leer si un pin está a nivel alto o nivel bajo en los pines de

nuestro puerto, podemos ejecutar un chequeo para ver si el bit en particular correspondiente

esta puesto a nivel alto (1) o está puesto a nivel bajo (0).

Antes de dar un ejemplo de código, tenemos que explicar dos registros mas - W y F.

W

El registro W es un registro de propósito general al cual le puedes asignar cualquier valor que

desees. Una vez que has asignado un valor a ese registro, puedes sumarle cualquier otro valor,

o moverlo. Si le asignas otro valor a W, su contenido es sobrescrito.

Un ejemplo de código

Vamos a darte un ejemplo de código sobre lo que acabamos de aprender. No intentes compilar

esto todavía, lo haremos cuando hagamos nuestro primer programa. Simplemente estamos

intentado mostrar como se hace la programación de lo anterior y de paso presentando un par

de instrucciones. Vamos a poner el Puerto A como en el ejemplo anterior.

Lo primero, necesitamos cambiar del banco 0 al banco 1. Hacemos esto modificando el registro

STATUS, que está en la dirección 03h, poniendo el bit 5 a 1.

BSF 03h,5

La instrucción BSF significa en ingles "Bit Set F" (Poner a 1 un bit de la Memoria). La

letra F significa que vamos a utilizar una posición de memoria, o un registro en memoria.

Vamos a utilizar dos números después de esta instrucción - 03h, el cual se refiere a la dirección

del registro STATUS, y el número 5 que corresponde al número de bit. Por tanto, lo que

estamos diciendo es "pon a 1 el bit 5 de la dirección de memoria 03h".

Ahora ya estamos en el banco 1.

MOVLW b'00110'

Estamos poniendo el valor binario 00110 (la letra 'b' significa que el número está en binario) en

nuestro registro de propósito general W. Podríamos haber hecho esto en hexadecimal, en cuyo

caso nuestra instrucción hubiese sido:

MOVLW 06h

Page 9: Introducción al PIC

Cualquiera de las dos funcionará. La instrucción MOVLW significa en ingles "Move Literal Value

into W", en castellano, mover un valor literal directamente al registro W.

Ahora necesitamos poner este valor en el registro TRISA para configurar el puerto:

MOVWF 85h

Esta instrucción significa "poner los contenidos de W en el registro cuya dirección viene a

continuación", en este caso la dirección 85h, que apunta a TRISA.

Nuestro registro TRISA ahora tiene el valor 00110 o mostrado gráficamente :

Pin del Puerto A RA4 RA3 RA2 RA1 RA0

Valor Binario 0 0 1 1 0

Entrada/Salida S S E E S

Ahora tenemos que configurar los pines del Puerto A, y para ello necesitamos volver al banco 0

para manipular cualquier dato.

BCF 03h,5

Esta instrucción hace lo contrario a BSF. Significa en ingles "Bit Clear F" (en castellano, poner

a 0 un bit de la memoria). Los dos números que le siguen son la dirección del registro, en este

caso del registro STATUS, y el número de bit, es este caso el 5. Así que lo que hemos hecho

ahora es poner a 0 el bit 5 del registro STATUS.

Ya estamos de vuelta en el Banco 0.

Aquí está el código en un solo bloque:

BSF 03h,5 ; Ve al banco 1MOVLW 06h ; Pon 00110 en WMOVWF 85h ; Mueve 00110 a TRISABCF 03h,5 ; Vuelve al Banco 0

Léelo hasta que las entiendas. De momento ya hemos visto 4 instrucciones. ¡Solo nos quedan

31 para terminar!

Cómo escribir en los puertos

En el apartado anterior, hemos mostrado como configurar los pines de un puerto del PIC como

entradas o como salidas. En este apartado, vamos a mostrar como enviar datos a los puertos.

En el siguiente apartado terminaremos haciendo que un LED parpadee incluyendo el listado

completo del programa y un diagrama de un circuito simple para que puedas ver al PIC

Page 10: Introducción al PIC

haciendo exactamente lo que esperamos que haga. No intentes compilar o programar tu PIC

con estos listados de aquí, ya que son solo ejemplos.

Primero, pongamos el bit 2 del puerto A como salida:

bsf 03h,5 ;Ir al Banco 1movlw 00h ;Poner 00000 en Wmovwf 85h ;Mover 00000 al TRISA – todos los pines como salidas.bcf 03h,5 ;volver al Banco 1

Esto te sonará del apartado anterior. La única diferencia es que hemos puesto todos los pines

del Puerto A como salidas, poniendo 0h en el registro tri-estado (TRISA).

Ahora lo que tenemos que hacer es encender el LED. Hacemos esto poniendo uno de los pines

(aquel que tenga el LED conectado) a nivel alto. En otras palabras, enviamos un 1 al pin. Así es

como se hace (Mira los comentarios de cada linea):

movlw 02h ; Escribe 02h en el registro W. En binario es 00010, ; ...lo cual pone a 1 el bit 2 (pin 18) mientras mantiene los otros pines a 0.movwf 05h ; Ahora mueve los contenidos de W (02h) al puerto A, cuya dirección es 05h.

Por tanto, ahora tu LED está encendido, y ahora queremos apagarlo:

movlw 00h ; Escribe 00h en el registro W. Esto pone a 0 todos los pines.movwf 05h ; Ahora mueve todos los contenidos de W (0h) al puerto A, cuya dirección es 05h.

Así que lo que hemos hecho ha sido encender y apagar el LED una vez.

Lo que queremos es que el LED se encienda y se apague continuamente. Para hacer esto

tenemos que volver al principio del programa. Para conseguir esto lo primero que hacemos es

poner una etiqueta al comienzo de nuestro programa, y diciéndole al programa que vaya a ese

punto constantemente.

Definimos una etiqueta muy simple. Escribimos un nombre, digamos INICIO, entonces el

código queda:

Inicio movlw 02h ; Escribe 02h en el registro W. En binario es 00010, ; ...lo cual pone a 1 el bit 2 (pin 18)

Page 11: Introducción al PIC

mientras mantiene los otros pines a 0. movwf 05h ; Ahora mueve los contenidos de W (02h) al puerto A, cuya dirección es 05h. movlw 00h ; Escribe 00h en el registro W. Esto pone a 0 todos los pines. movwf 05h ; Ahora mueve todos los contenidos de W (0h) al puerto A, cuya dirección es 05h. goto Inicio ; ve donde esté Inicio.

Como puedes ver, primer decimos la palabra 'Inicio' justo al comienzo del programa. Después,

justo al final del programa decimos simplemente 'goto Inicio', ves a Inicio. La instrucción 'goto'

significa en ingles 'ir a', y eso es lo que hace.

Este programa encenderá y apagará el LED constantemente, desde el momento que le demos

alimentación al circuito, y se detendrá cuando le quitemos la alimentación.

Creo que deberíamos echar un vistazo de nuevo a nuestro programa:

; bsf 03h,5 movlw 00h movwf 85h bcf 03h,5Inicio movlw 02h movwf 05h movlw 00h movwf 05h goto Inicio

Bien, he quitado los comentarios. Pero, ¿ te das cuenta de que todo lo que vemos son

instrucciones y números ? Esto puede ser algo confuso si mas tarde intentas depurar el

programa, y también cuando escribes código que te tengas que acordar de todas las

direcciones de memoria. Incluso con los comentarios puestos, puede ser un poco lioso. Lo que

necesitamos es dar nombres a estos números. Esto se consigue con otra instrucción: "equ"

La instrucción "equ" simplemente significa que algo equivale a algo [Nota de la traducción:

"equ" viene del termino ingles "equivalence", en castellano "equivalencia"]. No es una

instrucción del PIC, sino para el ensamblador. Con esta instrucción podemos asignar un

nombre a la dirección de localización de un registro, o en términos de programación asignar

una constante. Vamos a establecer algunas constantes para nuestro programa, y verás que

sencillo es de leer.

STATUS equ 03h ; Este asigna a la palabra STATUS el valor 03h, que es la dirección del registro STATUS.TRISA equ 85h ; Este asigna a la palabra TRISA el

Page 12: Introducción al PIC

valor 85h, que es la dirección del registro tri-estado del Puerto A.PORTA equ 05h ; Este asigna a la palabra PORTA el valor 05h, que es la dirección del Puerto A.

Así que ahora que hemos establecido nuestro valores constantes, vamos a ponerlos en nuestro

programa. Los valores constantes deben ser definidos antes de que los usemos. Para estar

seguros de ello los ponemos siempre al comienzo del programa. Reescribiremos de nuevo el

programa sin comentarios, para que puedas comparar el listado anterior con este:

STATUS equ 03hTRISA equ 85hPORTA equ 05h; bsf STATUS,5 movlw 00h movwf TRISA bcf STATUS,5Inicio movlw 02h movwf PORTA movlw 00h movwf PORTA goto Inicio

Seguro que ahora puedes ver que las constantes hacen el programa un poco más sencillo,

aunque todavía no hemos puesto los comentarios. Sin embargo, no hemos terminado todavía.

Bucles de Retardo

Existe un ligero inconveniente en nuestro programa del LED parpadeante. Cada instrucción

necesita un ciclo de reloj para ser completada. Si utilizamos un cristal de 4 Mhz, cada

instrucción tardará 1/4 Mhz o 1 microsegundo en ser completada. Como solo estamos usando

5 instrucciones, el LED se encenderá y apagará en 5 microsegundos. Esto es demasiado

rápido para que lo podamos ver, y parecerá que el LED está permanentemente encendido. Lo

que necesitamos hacer es introducir un retardo entre el momento de encendido y apagado y

viceversa.

El principio para retardo es el de contar hacia atrás desde un número previamente establecido

y cuando llegue a cero, paramos de contar. El valor cero indica el fin del retardo y continuamos

nuestro camino a través del programa.

Así que lo primero que necesitamos hacer es definir una constante que usaremos como

contado. La llamaremos CONTADOR. Lo siguiente, necesitamos decidir el tamaño del número

desde el que contar. Bien, el número mayor que podemos tener es 255 o FFh en hexadecimal.

Ahora, como hemos mencionado en el apartado anterior, la instrucción equ asigna una palabra

Page 13: Introducción al PIC

a una localización de un registro. Esto significa que cualquiera que sea el número que

asignemos a CONTADOR, será igual al contenido de un registro.

Si lo probamos y asignamos el valor FFh, el compilador entenderá que estamos asignando la

dirección de memoria FFh a la constante, y obtendremos un error cuando vayamos a compilar

el programa. Esto es debido a que la localización FFh está reservada, y por tanto no podemos

acceder a ella. Así que, ¿ como hacemos para asignar un número real ? Bien, se requiere

hacer un poco de "pensamiento lateral". Si asignamos a nuestro CONTADOR, por ejemplo, a la

dirección 08h, este apuntará a un registro de propósito general. Las posiciones de memoria

tienen un valor por defecto de FFh. De este modo, si CONTADOR apunta a 08h, tendrá un

valor de FFh la primera vez que lo pongamos en marcha.

Pero, sí, no llores, ¿ cómo ponemos un valor distinto en CONTADOR ? Bien, todo lo que

tenemos que hacer es primero 'mover' un valor a esta posición. Por ejemplo, si queremos que

CONTADOR tenga un valor de 85h, no podemos decir 'CONTADOR equ 85h' porque esta es la

localización del registro tri-estado del puerto A (TRISA). Lo que hacemos es esto:

movlw 85h ; Primero, ponemos el valor 85h en el registro W.movwf 08h ; Ahora lo movemos a nuestro registro 08h.

Ahora, podemos decir 'CONTADOR equ 08h', CONTADOR será igual al valor 85h. Sutil,

¿ verdad ?

Así que lo primero definimos nuestra constante:

CONTADOR equ 08h

A continuación necesitamos disminuir este CONTADOR en 1 hasta que alcance cero. Da la

casualidad de que hay una sola instrucción que hace esto por nosotros, con la ayuda de un

'goto' y una etiqueta. La instrucción que usaremos es:

decfsz CONTADOR,1

Esta instrucción dice "resta 1 al registro (en esta caso CONTADOR). Si llegamos a cero, salta 2

lugares hacia delante"[Nota de la traducción: El valor que le sigue a la coma, indica donde debe

almacenarse el resultado de la operación. Si es 1, como en el ejemplo anterior, el resultado se

almacena en el mismo registro indicado en la instrucción, y si es 0 el resultado se almacena en

el registro w.] . Un montón de palabras para una sola instrucción. Veamosla en acción antes,

después la pondremos en nuestro programa.

Page 14: Introducción al PIC

CONTADOR equ 08hETIQUETA decfsz CONTADOR,1 goto ETIQUETA ;Continua por aquí. : : :

Lo que hemos hecho es primero poner nuestra constante CONTADOR a 255. La siguiente linea

pone una etiqueta, llamada ETIQUETA seguida de nuestra instrucción decfsz. La instrucción

decfsz CONTADOR,1 disminuye el valor de CONTADOR en 1, y almacena el resultado de

vuelta en CONTADOR. También comprueba si CONTADOR tiene un valor de 0. Si no lo tiene,

hace que el programa salte a la siguiente linea. Aquí tenemos una instrucción de 'goto' que nos

envía de vuelta a nuestra instrucción decfsz. Si el valor de CONTADOR es igual a cero,

entonces la instrucción decfsz hace que el programa salte dos lugares hacia adelante, y se

sitúe donde hemos escrito "Continua por aquí". Así que, como puedes ver, hemos hecho que el

programa permanezca en un lugar durante un tiempo predeterminado antes de seguir adelante.

Esto se llama bucle de retardo. Si necesitamos un retardo mayor, podemos poner un bucle

detrás de otro. Cuantos mas bucles pongamos, mayor será el retardo. Nosotros vamos a

necesitar por lo menos dos, si queremos ver parpadear al LED.

Vamos a poner estos bucles de retardo en nuestro programa, y terminaremos haciendo un

programa real añadiendo los comentarios:

;*****Establecimiento constantes **** STATUS equ 03h ; Dirección del registro STATUSTRISA equ 85h ; Dirección del registro triestado para el Puerto A.PORTA equ 05h ; Dirección del Puerto A.CONTADOR1 equ 08h ; Primer contador para nuestros bucles de retardo.CONTADOR2 equ 09h ; Segundo contador para nuestros bucles de retardo.;;****Configuración del Puerto**** bsf STATUS,5 ; Cambiamos al banco 1Switch to Bank 1 movlw 00h ; Ponemos los pines del puerto A ... movwf TRISA ; ...como salidas. bcf STATUS,5 ; Volvemos al Banco 0.;;****Encendido del LED **** Inicio movlw 02h ; Encendemos el LED poniendo primero el valor...

Page 15: Introducción al PIC

movwf PORTA ; ... en el registro w y después al puerto; ;****Inicio del buble de retardo 1**** Bucle1 decfsz CONTADOR1,1 ; Restamos 1 a 255. goto Bucle1 ; Si CONTADOR es cero, continuamos. decfsz CONTADOR2,1 ; Restamos 1 a 255 goto Bucle1 ; Volvemos al inicio de nuestro bucle ; Este retardo cuenta hacia atrás ... ; ...desde 255 a 0, 255 veces.;;****Retardo terminado, ahora apagamos el LED **** movlw 00h ; Apaga el LED poniendo primero el valor ... movwf PORTA ; ... en el registro w y después al puerto;;****Añadimos otro retardo**** Bucle2 decfsz CONTADOR1,1 ; Este segundo bucle mantiene el LED... goto Bucle2 ; ...apagado el tiempo suficiente... decfsz CONTADOR2,1 ; ...para que lo veamos goto Bucle2 ; ;;****Ahora volvemos al inicio del programa goto Inicio ; Vuelve al principio y enciende el LED...

; ...de nuevo.;****Termina el Programa**** end ; Algunos compiladores necesitan esta instrucción. ; y también por si acaso olvidamos poner... ; ... la instrucción 'goto'.

Puedes compilar este programa y programar el PIC con él. Por su puesto, querrás probar el

circuito para ver si funciona realmente. Aquí está el diagrama del circuito para que lo

construyas una vez que hayas programado tu PIC:

Page 16: Introducción al PIC

Felicidades, acabas de escribir tu primer programa para PIC, y construido un circuito para

hacer parpadear un LED. Así que, si has seguido este tutorial hasta aquí, has aprendido 7

instrucciones de 35, y ya controlas los puertos de entrada/salida !

¿ Por qué no intentas modificar los bucles de retardo para hacer que el LED parpadee mas

rápido ? Cual es el valor mínimo de CONTADOR para poder ver el LED parpadear ? ¿ Por qué

no añades un tercer bucle o incluso más bucles de retardo después del primero para hacer más

lento el apagado mas lento ? Necesitarás una constante para cada bucle de retardo. Podrías

incluso ajustar tus bucles de retardo para hacer que el LED parpadease con un ritmo definido,

por ejemplo una vez por segundo.

En la siguiente sección veremos como podemos usar una cosa llamada sub-rutina para ayudar

a mantener el programa simple y pequeño.

Subrutinas

Una subrutina es una sección de código o programa, que puede ser llamada como y cuando la

necesites. Las subrutinas se usan si vas a ejecutar la misma función función más de una vez,

por ejemplo para crear un retardo. Las ventajas de utilizar una subrutina son que hará más

sencillo modificar el valor una vez dentro de la subrutina antes que, digamos, hacerlo diez

veces a través de tu programa. Y también te ayudará a reducir el total de memoria que ocupa

tu programa dentro del PIC.

Miremos una subrutina:

Page 17: Introducción al PIC

RUTINA CONTADOR equ 255ETIQUETA decfsz CONTADOR,1 goto ETIQUETA return

Primero tenemos que dar un nombre a la subrutina, y en este caso hemos elegido RUTINA.

Después escribimos el código que queramos como hacemos normalmente. En este caso,

hemos elegido el código del retardo para el programa de parpadeo de nuestro LED.

Finalmente, terminamos la subrutina tecleando la instrucción RETURN.

Para arrancar la subrutina desde cualquier punto de nuestro programa, simplemente escribimos

la instrucción CALL seguida por el nombre de la subrutina.

Vamos a ver esto con algo más de detalle. Cuando alcanzamos la parte de nuestro programa

que dice CALL xxx, donde xxx es el nombre de nuestra subrutina, el programa salta a donde

quiera que resida la subrutina xxx. 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

xxx.

Puedes llamar a la misma subrutina tantas veces como quieras, esa es la razón por la que

utilizar subrutinas reduce el tamaño total de nuestro programa. Sin embargo, hay dos cosas

que debes tener en cuenta. La primera, igual que en tu 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. Recomendaríamos que declarases todo al comienzo

del programa principal, para que así sepas que todo se encuentra en el mismo sitio. Lo

segundo, te debes asegurar de que el programa principal pasa por alto la subrutina. Lo que

queremos decir con esto es que si pones la subrutina justo al final del programa principal, a

menos que uses 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.

Vamos a verlo en nuestro programa de parpadeo de LED, pero esta vez utilizaremos una

subrutina para el bucle de retardo. Con suerte verás que sencillo se queda el programa, y

también veras como funciona la subrutina en la realidad:

;***** Establecimiento constantes **** STATUS equ 03h ; Dirección del registro STATUSTRISA equ 85h ; Dirección del registro tri-estado para el Puerto A.PORTA equ 05h ; Dirección del Puerto A.CONTADOR1 equ 08h ; Primer contador para nuestros bucles de retardo.

Page 18: Introducción al PIC

CONTADOR2 equ 09h ; Segundo contador para nuestros bucles de retardo.;;**** Configuración del Puerto **** bsf STATUS,5 ; Cambiamos al Banco 1 movlw 00h ; Ponemos los pines del puerto A ... movwf TRISA ; ...como salidas. bcf STATUS,5 ; 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;;**** Retardo terminado, ahora apagamos el LED **** movlw 00h ; Apaga el LED poniendo primero el valor ... movwf PORTA ; ... en el registro w y después al puerto;;**** Añadimos otro retardo **** call Retardo;;**** Ahora volvemos al inicio del programa goto Inicio ; Vuelve al principio y enciende el LED...

; ...de nuevo.; ;**** Aquí está nuestra SubrutinaRetardoBucle1 decfsz CONTADOR1,1 ; Este segundo bucle mantiene el LED... goto Bucle1 ; ...apagado el tiempo suficiente... decfsz CONTADOR2,1 ; ...para que lo veamos goto Bucle1 ; return;;**** Fin del Programa**** end ; Algunos compiladores necesitan esta instrucción, ; y también por si acaso olvidamos poner... ; ... la instrucción 'goto'.

Page 19: Introducción al PIC

Puedes ver que utilizando una subrutina para nuestro bucle de retardo, hemos reducido el

tamaño del programa. 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 linea siguiente a la instrucción 'call'. En el ejemplo

anterior, 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 surutina

termina, el programa retorna a la siguiente instrucción que ve, que es 'goto Inicio'.

Para aquellos de vosotros que estéis interesados, nuestro programa original tenia 120 bytes de

tamaño. Mediante el uso de la subrutina, hemos reducido el programa a 103 bytes.[Nota de la

traducción: Estos tamaños en bytes a los que se refiere el autor, son los tamaños de los

ficheros .HEX que resultan de compilar estos listados con el compilador de Microhip MPASM.]

Puede que no parezca gran cosa, pero teniendo en cuenta que solo tenemos 1024 bytes en

total dentro del PIC , cada pequeño bit ayuda.

[Nota de la traducción: Cuando el autor dice que el 16F84 tiene 1024 bytes, se esta refiriendo al

área de memoria para el almacenamiento del código o programas. Y, aunque utiliza la palabra

bytes aquí, en realidad no se trata de bytes(los cuales tienen 8 bits), sino que se trata 14-bit

words(en castellano, palabras de 14-bits, un poco más de un byte). De modo, que lo que sería

correcto decir es, que el área de memoria de programa en el 16F84 tiene un tamaño 1024 x 14

bit words, o 1K x 14 bit words, o 1024 posiciones de 14 bits cada una. Como dato adicional,

cada instrucción del conjunto de instrucciones del 16F84 ocupa una palabra de 14 bits. Es

decir, cada una ocupa una de esas 1024 posiciones de memoria disponible.Microchip fabrica

otros PICs con mayor espacio de memoria interna. Estos se pueden ver en las páginas de

Microchip. ]

En el próximo capitulo, veremos cómo leer de los puertos.

Cómo leer de los puertos E/S

Hasta este punto, hemos estado escribiendo en el Puerto A para poder encender y apagar el

LED. Ahora vamos a ver como podemos leer los pines de E/S de los puertos. Esto es para que

podamos conectar un circuito externo y actuar sobre cualquier salida que este nos dé.

Si recuerdas de los capítulos previos, para configurar los puertos de E/S, tenemos que

cambiarnos del Banco 0 al Banco 1. Hagamos eso primero:

STATUS equ 03h ; Dirección del registro STATUSTRISA equ 85h ; Dirección del registro tri-estado para el Puerto A.PORTA equ 05h ; Dirección del Puerto A. bsf STATUS,5 ; Cambia al Banco 1.

Page 20: Introducción al PIC

Ahora, para configurar el pin de un puerto para que sea una salida, enviamos un 0 al registro

TRISA. Para poner el pin como entrada, ponemos un 1 en el registro TRISA.

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

La que usemos dependerá de como queramos que nuestro programa reaccione 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:

;Aquí el código:BTFSS PortA,0Goto Inicio;Continua por aquí::

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 un

conmutador[Nota de la traducción: en ingles el término original es "switch"] se cierra,

parpadeará a la mitad de velocidad. Seguramente puedas hacer el programa por ti mismo, pero

hemos incluido el listado de todos modos. Podrías probar a escribir el programa completo, solo

para ver si has comprendido los conceptos. Estamos usando el mismo circuito que antes, con

un conmutador añadido al pin RA0 del PIC y a la linea de alimentación positiva.

;***** Establecimiento constantes **** STATUS equ 03h ; Dirección del registro STATUSTRISA equ 85h ; Dirección del registro tri-estado para el Puerto A.PORTA equ 05h ; Dirección del Puerto A.CONTADOR1 equ 08h ; Primer contador para nuestros bucles de retardo.

Page 21: Introducción al PIC

CONTADOR2 equ 09h ; Segundo contador para nuestros bucles de retardo.;;**** Configuración del Puerto **** bsf STATUS,5 ; Cambiamos al Banco 1 movlw 01h ; Ponemos los pines del puerto A ... movwf TRISA ; ...el bit 1 como salida, el bit 0 como entrada. bcf STATUS,5 ; Volvemos al Banco 0.;;**** Encendemos del LED **** Inicio movlw 02h ; Encendemos el LED poniendo primero el valor... movwf PORTA ; ... en el registro w y después al puerto; ;**** Comprobamos si el conmutador está cerrado **** btfsc PORTA,0 ; Tomamos el valor del bit 0 del puerto A y comprobamos si es 0. ; Si es 0, sáltate la siguiente instrucción y continua normalmente. call Retardo ; Si es 1, ejecuta esta instrucción añadiendo un retardo extra.;;**** Añade un retardo call Retardo;;**** Retardo terminado, ahora apagamos el LED **** movlw 00h ; Apaga el LED poniendo primero el valor ... movwf PORTA ; ... en el registro w y después al puerto;;**** Comprobamos si el conmutador está todavía cerrado **** btfsc PORTA,0 ; Tomamos el valor del bit 0 del puerto A y comprobamos si es 0. ; Si es 0, saltate la siguiente instrucción y continua normalmente. call Retardo ; Si es 1, ejecuta esta instrucción añadiendo un retardo extra.;;**** Añadimos otro retardo **** call Retardo;;**** Ahora volvemos al inicio del programa goto Inicio ; Vuelve al principio y enciende el LED...

; ...de nuevo.;

Page 22: Introducción al PIC

;**** Aquí está nuestra SubrutinaRetardoBucle1 decfsz CONTADOR1,1 ; Este segundo bucle mantiene el LED... goto Bucle1 ; ...apagado el tiempo suficiente... decfsz CONTADOR2,1 ; ...para que lo veamos goto Bucle1 ; return;;**** Fin del Programa**** end ; Algunos compiladores necesitan esta instrucción, ; y también por si acaso olvidamos poner... ; ... la instrucción 'goto'.

Lo que hemos hecho aquí es encender el LED. Después comprobar si el conmutador está

cerrado. Si está cerrado, entonces hacemos una llamada a nuestra subrutina de retardo. Esto

nos da el mismo retardo que anteriormente, pero ahora la estamos llamando dos veces. Lo

mismo pasa cuando el LED está apagado. Si el conmutador no está cerrado, entonces

tenemos nuestros tiempos de encendido y apagado como antes.

Puedes compilar y ejecutar este programa. Sin embargo, una nota de advertencia: El circuito

final y el código puede resultar irrelevante para alguien que no le interese programar

microcontroladores. Por tanto, no te enfades si cuando enseñes el circuito a tu familia y amigos

sobre como puedes cambiar la velocidad de parpadeo de un LED, ves que no muestran ni el

mas mínimo interés ¡ Hablamos desde nuestra experiencia personal !

Si has ido siguiendo estos capítulos desde el comienzo, puede que te interese saber que

¡ahora llevas aprendidas 10 de 35 instrucciones para el PIC 16F84 ! Y todas ellas las has

aprendido simplemente con el encendiendo y apagando de un LED.

Hasta ahora 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. El

único problema es que el programa es muy largo y desperdicia mucha memoria. Era aceptable

ya que introducíamos comandos por primera vez, pero debe existir una manera mejor de

hacerlo. Bueno, la hay (sabias que la había, ¿verdad? ).

Vamos a examinar como estábamos haciendo el parpadeo del LED realmente.

movlw 02hmovwf PORTAmovlw 00hmovlw PORTA

Page 23: Introducción al PIC

Primero cargamos 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. Entremedias de estas rutinas teníamos que llamar a una

subrutina 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).

Así que, ¿ cómo podemos hacer esto de una manera mas eficiente ? Sencillo. Utilizamos otra

instrucción llamada XORWF.

La instrucción XORWF ejecuta una función OR Exclusiva entre el registro w y el registro que le

pasamos como dato. Será mejor que expliquemos que narices es una OR Exclusiva antes de

continuar. [Nota de la traducción: También llamada función XOR simplemente]

Si tenemos dos entradas, y una salida, la salida solo será 1 si, y solo si, las dos entradas son

diferentes. Si son iguales, la salida será 0. Aquí está la tabla de verdad, para aquellos que

prefieran verlo en una tabla:

A B Salida

0 0 0

0 1 1

1 0 1

1 1 0

Vamos a ver que ocurre si hacemos que B tome el valor de salida anterior, y simplemente

cambiamos el valor de A:

A B Salida

0 0 0

1 0 1

1 1 0

1 0 1

Si 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. Para los que no lo vean de este modo en la tabla de

verdad, aquí está utilizando binario:

Valor de salida actual => 0

OR-Ex con él y con 1 => 1  ; 1 es el nuevo valor de salida.

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

Page 24: Introducción al PIC

OR-Ex con él y con 1 => 1  ; 1 es el nuevo valor de salida.

.... así sucesivamente.

Espero que puedas entender que haciendo OR exclusiva de una salida con un 1, lo que

estamos haciendo ahora es conmutar de 0 a 1 a 0, etc...

Así que ahora, para encender y apagar nuestro LED, necesitamos solo dos lineas:

MOVLW 02hXORWF 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. [Nota de la traducción: El

número que va después del registro especificado en la instrucción XORWF, indica donde debe

de ser almacenado el resultado de dicha operación OR exclusiva. Si, como ocurre en este

ejemplo anterior, ponemos un 1, el resultado se almacenará de vuelta en el registro de

memoria especificado. Si pusiésemos 0, el resultado de la operación OR exclusiva se

almacenaría en el registro w]

Vamos a ejecutar este código un par de veces, para mostrar como funciona en binario:

PORTA

00010

xorwf 00000

xorwf 00010

xorwf 00000

xorwf 00010

Incluso no necesitamos cargar el mismo valor en nuestro registro w cada vez, lo podemos

hacer una sola vez al principio, y simplemente saltar hacia atrás a nuestro comando de

conmutación. Además, no tenemos que establecer una valor al registro de nuestro puerto A.

¿ Por qué ? Bien, si durante el encendido es un 1, lo haremos conmutar. Si por el contrario es

un 0 durante el arranque, también lo conmutaremos.

Así que, vamos a ver nuestro nuevo código. El primero es nuestro código original de parpadeo

del LED, y el segundo es donde hemos añadido el conmutador externo:

LED Parpadeante:

;***** Establecerlas constantes **** STATUS equ 03h ; Dirección del registro STATUSTRISA equ 85h ; Dirección del registro tri-estado del puerto A.

Page 25: Introducción al PIC

PORTA equ 05h ; Direccion del puerto ACONTADOR1 equ 08h ; Primer contador para nuestros bucles de retardo.CONTADOR2 equ 09h ; Segundo contador para nuestros bucles de retardo.;**** Configurar el puerto**** bsf STATUS,5 ; Cambia al Banco 1 movlw 00h ; Configura las pines del puerto A ... movwf TRISA ; ...como salidas. bcf STATUS,5 ; Vuelve al Banco 0 movlw 02h ; Configura nuestro registro w con 02h;**** Enciende y apaga el LED **** Inicio xorwf PORTA,1 ; Conmuta el LED;**** Añade un retardo call Retardo ; El retardo mínimo necesario;**** Ahora vuelve al inicio del programa goto Inicio ; Vuelve al inicio para conmutar el LED de nuevo.; ;**** Aquí está nuestra subrutinaRetardoBucle1 decfsz CONTADOR1,1 ; Este bucle mantiene el LED apagado o encendido... goto Bucle1 ; ... el tiempo suficiente ... decfsz CONTADOR2,1 ; ... para que lo podamos ver. goto Bucle1 ; return ;**** Final del programa **** end ; Algunos compiladores necesitan esta instrucción, ; y también por si acaso olvidamos poner... ; ... la instrucción 'goto'.

LED parpadeante con el conmutador externo:

;***** Establecerlas constantes **** STATUS equ 03h ; Dirección del registro STATUSTRISA equ 85h ; Dirección del registro tri-estado del puerto A.PORTA equ 05h ; Direccion del puerto ACONTADOR1 equ 08h ; Primer contador para nuestros bucles de retardo.CONTADOR2 equ 09h ; Segundo contador para nuestros bucles de retardo.;;**** Configurar el puerto**** bsf STATUS,5 ; Cambia al Banco 1

Page 26: Introducción al PIC

movlw 00h ; Configura las pines del puerto A ... movwf TRISA ; ...como salidas. bcf STATUS,5 ; Vuelve al Banco 0 movlw 02h ; Configura nuestro registro w con 02h;;**** Enciende y apaga el LED **** Inicio xorwf PORTA,1 ; Conmuta el LED;;**** Comprobamos si el conmutador está cerrado **** btfsc PORTA,0 ; Tomamos el valor del bit 0 del puerto A y comprobamos si es 0. ; Si es 0, sáltate la siguiente instrucción y continua normalmente. call Retardo ; Si es 1, ejecuta esta instrucción añadiendo un retardo extra.;;**** Añade un retardo call Retardo ; El retardo mínimo necesario;;**** Ahora vuelve al inicio del programa goto Inicio ; Vuelve al inicio para conmutar el LED de nuevo.; ;**** Aquí está nuestra subrutinaRetardoBucle1 decfsz CONTADOR1,1 ; Este bucle mantiene el LED apagado o encendido... goto Bucle1 ; ... el tiempo suficiente ... decfsz CONTADOR2,1 ; ... para que lo podamos ver. goto Bucle1 ; return ;;**** Final del programa **** end ; Algunos compiladores necesitan esta instrucción, ; y también por si acaso olvidamos poner... ; ... la instrucción 'goto'.

Esperamos que hayas podido comprender cómo con el uso de una simple instrucción, hemos

reducido el tamaño de nuestro programa. De hecho, simplemente para mostrar en cuanto se

han reducido nuestros programas, hemos mostrado los dos programas, los cambios que

hicimos, y sus tamaños en la siguiente tabla:

Programa Cambio Tamaño (en bytes)

Page 27: Introducción al PIC

LED parpadeante Original 120

LED Parpadeante Añadiendo subrutina 103

LED Parpadeante Utilizando función XOR 91

LED con conmutador Original 132

LED con conmutador Utilizando función XOR 99

[Nota de la traducción: Estos tamaños en bytes a los que se refiere el autor, son los tamaños

de los ficheros .HEX que resultan de compilar estos listados con el compilador de Microhip

MPASM.]

Así que no solo hemos aprendido nuevas instrucciones, ¡también hemos reducido el tamaño de

nuestro código!

Operadores Lógicos y Aritméticos

Operadores Lógicos

En el último capítulo presentamos la función OR exclusiva. La función ExOR (o XOR) es

conocida como un operador lógico. En este capítulo vamos a explicar otros operadores lógicos

que soporta el PIC. No habrá ningún programa de ejemplo, pero explicaremos como usar los

operadores utilizando pequeñas secciones de código.

AND

La función AND simplemente compara dos bits y produce un 1 si son iguales, y 0 si son

diferentes. Por ejemplo, si decimos 1 AND 1, el resultado es 1, mientras que si decimos 1 AND

0 el resultado será 0. Por supuesto, podemos comparar palabras (o bytes) también, y la

función AND hace esta comparación de ambas bit a bit. El ejemplo de más abajo muestra dos

palabras de 8 bits a las que se aplica AND con el siguiente resultado:

11001011AND 10110011Igual a 10000011

Como puedes ver, el resultado solo tiene un 1 cuando dos 1s coinciden en ambas palabras.

Podemos utilizar la función AND para hacer comprobaciones de puertos, por ejemplo. Si

estamos monitorizando pines E/S que están conectados a un circuito, y tenemos que

monitorizar una condición en concreto donde solo algunos pines están a nivel alto, entonces

simplemente podemos leer el puerto, y aplicar a ese valor la función AND con la condición que

estamos buscando, simplemente como en el ejemplo anterior.

Page 28: Introducción al PIC

[Nota de la Traducción: Incluimos aquí la tabla de verdad de esta función que no estaba en el

texto original por homogeneizar:]

A B Resultado de AND

0 0 1

0 1 0

1 0 0

1 1 1

El PIC nos da dos modalidades para 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 W. El

resultado de la función AND serán almacenamos de vuelta en el registro W. [Nota de la

traducción: El valor de <número> tiene que estar comprendido entre 0 y 255]]

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>,d  ; donde <registro> es el registro 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.

Las dos secciones de código de más abajo muestran un ejemplo de cada instrucción AND. La

primera comprueba el estado del PORTA, donde necesitamos ver si las entradas son 1100.

Pondremos el resultado de vuelta en el registro W:

movlw 1100ANDWF 05h,0

El segundo ejemplo comprobará ahora los contenidos del registro W:

ANDLW 1100

OR

Page 29: Introducción al PIC

Ya hemos visto una función OR, llamada XOR. Esta produce un 1 si dos bits son diferentes, pero

no si son iguales. Hay una segunda 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. Aquí está la

tabla de verdad para demostrar esto:

A B Resultado de OR

0 0 0

0 1 1

1 0 1

1 1 1

Operadores Aritméticos

ADD

Esta función exactamente lo que dice [Nota de la traducción: "Add" en ingles significa sumar]

¡Suma dos números! Si el resultado de sumar dos números excede de 8 bits [Nota de la

traducción: 8 bits pueden contener un valor máximo de 255 en decimal], entonces se activará

un flag llamado CARRY [Nota de la traducción: "flag" en castellano se podría traducir por

banderín, y normalmente se trata de 1 bit que se ubica en un registro en concreto de la

memoria del PIC. "CARRY" en castellano puede traducirse como "llevar", podría ser como

cuando hacemos una cuenta a mano y utilizamos la expresión "me llevo una".]. El flag de

CARRY está localizado en el bit 0 de la dirección de memoria 03h. Si este bit se activa, quiere

decir que la suma de esos 2 números excedió de 8 bits. Si es 0, entonces el resultado queda

dentro de los 8 bits.

De nuevo, el PIC nos da dos modalidades de ADD, que son ADDLW y ADDWF. Como puedes

suponer, es muy similar a la función anterior.

ADDLW añade los contenidos del registro W con un número que le especifiquemos. La sintaxis

es:

ADDLW <número>

ADDWF añadirá los contenidos del registro W y cualquier otro registro que le especifiquemos.

La sintaxis es:

Page 30: Introducción al PIC

ADDWF <registro>,d  ; donde <registro> es el registro 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

Ahora, ¡apuesto a que sabes que hace esta función! Sí, lo que suponías, esta función sustrae(o

resta) un bit a otro ['Nota de la traducción: SUB, viene del ingles "substract" que significa

"sustraer" en castellano]]. Una vez más el PIC da dos modalidades: SUBLW y SUBWF. La

sintaxis es exactamente la misma que para la función ADD, excepto por supuesto ¡que tienes

que escribir SUB en lugar de ADD!

Incremento

Si queremos añadir 1 a un número en el PIC, podríamos simplemente 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. 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:

movlw 01addwf 0Ch,1

Hay una manera mejor de hacer esto. Podemos utilizar 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 simple instrucción podemos literalmente hacer la mitad de código. Si

queremos que el resultado vaya de vuelta al registro W, usando el ejemplo anterior,

hubiésemos tenido que añadir otro comando para mover el contenido de la posición 0Ch de

vuelta al registro W, y después poner en el registro 0Ch lo que quiera que tuviese al principio.

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

Page 31: Introducción al PIC

ocurrirá cuando añadamos 1 a 255) entonces el PIC se saltará la siguiente instrucción. La

sección de código de más abajo lo demuestra:

Bucle incfsz 0Ch goto Bucle : : ;Resto del programa.

En la sección de código anterior, el registro de la posición de memoria OCh será incrementado

en 1. Después tenemos una instrucción que le dice al PIC que vaya de vuelta a nuestra

etiqueta llamada "Bucle", e incremente OC en 1 de nuevo. Esto ocurrirá constantemente 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, la cual es en este

caso "goto", y por tanto que el PIC continúe con el resto del programa.

Decremento

[Nota de la traducción: La palabra "decremento" no existe en castellano. Surge de traducir un

poco a la ligera la palabra inglesa "decrement". Así que nos permitimos una pequeña licencia,

por comodidad y claridad.]

Ya hemos cubierto la función de decremento (o disminución en 1) en el capitulo Bucles de

Retardo, así que no me voy a repetir aquí.

Complemento

La última instrucción de este grupo invierte 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.

El siguiente ejemplo muestra la instrucción en acción:

0Ch = 11001100

COMF 0Ch,1

0Ch = 00110011

Page 32: Introducción al PIC

Esto se puede usar, por ejemplo, para cambiar rápidamente todos los bits de un puerto de

salida a entra y viceversa.

Operaciones con Bits

Las operaciones con bits nos permite manipular un solo bit dentro de una palabra (word). Nos

permiten, mover, activar o desactivar bits individualmente en registros o números que

especifiquemos. Al final de este capítulo te mostraremos un programa que producirá un

conjunto de luces que se desplazan en un sentido, y después en el sentido contrario. Vimos

esto cuando revisamos la función OR, donde hacíamos OR exclusiva con una palabra.

Ya hemos visto un par de operaciones con un bits, cuando configurábamos los puertos del PIC,

y voy a repetir aquí su uso.

BCF

Esta instrucción pone a cero un bit que especifiquemos en el registro que especifiquemos. La

sintaxis es la siguiente:

BCF <registro>,<bit>

La hemos utilizado previamente para cambiar del Banco 1 al Banco 0 cuando poníamos a 0 el

bit 5 del registro STATUS. También podemos utilizarla para poner a 0 cualquier bit de cualquier

otro registro/posición de memoria. Por ejemplo, si queremos poner a 0 el tercer bit de

11001101 que está almacenado en la posición de memoria 0Ch, introduciríamos:

BCF 0Ch,2

[Nota de la Traducción: el valor que puede tomar el parámetro <bit> va de 0 a 7, donde 0 es el

bit mas a la derecha de nuestro número binario. La siguiente tabla clarifica un poco más la

disposición de los bits en un byte (o palabra de 8 bits) y sus posiciones:

Posición 8º bit 7º bit 6º bit 5º bit 4º bit 3er bit 2º bit 1er bit

Número de bit 7 6 5 4 3 2 1 0

Nuestro ejemplo 1 1 0 0 1 1 0 1

Después de "BCF 0Ch,2" 1 1 1 0 0 0 0 1

]

Page 33: Introducción al PIC

BSF

Esta instrucción pondrá a 1 el bit que especifiquemos en cualquier registro que especifiquemos.

Hemos utilizado esta operación antes para cambiar del Banco 0 al Banco 1. La sintaxis es:

BSF <registro>,<bit> , y se utiliza exactamente de la misma forma que la BCF de más arriba.

BTFSC

Hasta ahora hemos puesto a 1 o a 0 un bit en un registro. Pero ¿qué pasa si queremos

simplemente comprobar si un bit es 0 ó 1 en un registro? Bien, podemos utilizar BTFCS. Esta

dice "Comprueba un bit en el registro o posición de memoria (F), y salta si es 0". Esta

instrucción comprobará el bit que le especifiquemos en el registro. Si el bit es 0, la instrucción le

dice al PIC que se salte la instrucción que viene a continuación. Podríamos utilizar esta

instrucción si queremos comprobar un flag, como por ejemplo en flag de CARRY. Esto nos

ahorra tener que leer el registro STATUS y mirar los bits individualmente para ver que flags

están a 1. Por ejemplo, si queremos comprobar si el flag de CARRY ha sido puesto a 1 cuando

hayamos sumado dos números, haremos lo siguiente:

; BTFSC 03h,0 <instrucción x> ; continua por aquí si CARRY está a 1. <instrucción y> ; o por aquí si está a 0.

Si el valor del bit es 1, entonces BTFSC continuará por la instrucción inmediatamente siguiente.

Si está a 0, entonces se salta esa instrucción. La siguiente sección de código muestra donde

podría ser utilizada:

Bucle : : : BTFSC 03,0 Goto Bucle

En el código anterior, el PIC solo saldrá de bucle si el bit 0 del registro STATUS (o el flag de

CARRY) está puesto a 0. De otro modo, el comando 'goto' será ejecutado.

Page 34: Introducción al PIC

BTFSS

Esta instrucción dice "Comprueba el bit del registro o posición de memoria(F), y salta si está a

1". Esta es similar a la instrucción BTFSC, excepto que el PIC se saltará la siguiente instrucción

si el bit que estamos comprobando está a 1, en lugar de a 0.

CLRF

Esta instrucción pondrá todos los bits del contenido de un registro a 0. La sintaxis es:

CLRF <registro>

La hemos usado anteriormente para poner todos los pines de un puerto como salidas, haciendo

"CLRF 85h". También lo usamos para poner los pines de un puerto que estaban como salidas

todos a 0, haciendo "CLRF 05h".

CLRW

Es similar a la instrucción CLRF, excepto en que solo pone a 0 el registro W. La sintaxis es

bastante sencilla:

CLRW

RLF y RRF

Estas instrucciones desplazan los bits del contenido de un registro un lugar hacia la izquierda

(RLF), o un lugar hacia la derecha (RRF). Por ejemplo, si tenemos 00000001 y utilizamos la

instrucción RLF, tendríamos 00000010. Ahora, ¿qué ocurre si tenemos 10000000 y empleamos

RLF? Bien, el bit 1 será desplazado al flag CARRY. y si empleamos RLF una vez más, el 1

Page 35: Introducción al PIC

reaparecerá justo al principio [Nota de la traducción: es decir, por el lado derecho del byte, o

dicho de otro modo en el bit0]. Lo mismo ocurre con la instrucción RRF pero de manera

inversa. El ejemplo de más abajo ilustra esto para la instrucción RLF, donde mostramos los 8

bits del contenido de un registro, y el flag de CARRY:

C 76543210

0 00000001

RLF 0 00000010

RLF 0 00000100

RLF 0 00001000

RLF 0 00010000

RLF 0 00100000

RLF 0 01000000

RLF 0 10000000

RLF 1 00000000

RLF 0 00000001

[Nota de la traducción: La sintaxis para estas dos instrucciones es:

RLF <registro>,d 

RRF <registro>,d 

Donde 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.]

Programa de ejemplo

Ahora vamos a darte un ejemplo de código que puedes compilar y ejecutar. Este hará que una

luz que se desplace, comenzando en el bit 0 del puerto B hasta el bit 8 del mismo, siguiendo al

bit 0 del puerto A hasta el bit 5, y después haciendo todo el camino inverso hasta al principio.

Conecta LEDs a todos los pines de los puertos A y B. Aquí comprenderás algunas de las

operaciones de bit mencionadas en este capítulo:

Page 36: Introducción al PIC

TIEMPO equ 9Fh ; Variable para el bucle de retardo.PORTB equ 06h ; Dirección del Port B.TRISB equ 86h ; Dirección del registro tri-estado del Port B.PORTA equ 05h ; Dirección del Port A.

TRISA equ 85h ; Dirección del registro tri-estado del Port A.STATUS equ 03h ; Registro para seleccionar el banco.CONTADOR1 equ 0Ch ; Registro para el bucle.CONTADOR2 equ 0Dh ; Registro para el bucle.  bsf STATUS,5 ; Va al Banco 1 movlw 00h ; y configura movwf TRISB ; ambos puertos A y B movlw 00h ; como salidas, movwf TRISA ; después vuelve bcf STATUS,5 ; al Banco 0. movlw 00h ; Pon a 0 el Puerto A. movwf PORTA ;;; Comienzo del programa;CorreLuz movlw 01h ; Pon a 1 el primer bit movwf PORTB ; del Puerto B. PORTB = 00000001 call Retardo ; Espera un momento. call Retardo ; ;; Desplaza el bit a la izquierda, y después pausa.; rlf PORTB,1 ; PORTB = 00000010, C = 0; call Retardo call Retardo ; rlf PORTB,1 ; PORTB = 00000100, C = 0; call Retardo call Retardo ; rlf PORTB,1 ; PORTB = 00001000, C = 0; call Retardo call Retardo; rlf PORTB,1 ; PORTB = 00010000, C = 0; call Retardo call Retardo; rlf PORTB,1 ; PORTB = 00100000, C = 0

Page 37: Introducción al PIC

; call Retardo call Retardo; rlf PORTB,1 ; PORTB = 01000000, C = 0; call Retardo call Retardo ; rlf PORTB,1 ; PORTB = 10000000, C = 0; call Retardo call Retardo ; rlf PORTB,1 ; Este desplaza el bit al ''flag'' CARRY. ; PORTB = 00000000, C = 1;; Ahora se moverá al puerto A, desplazando el bit hacia la izquierda.; rlf PORTA,1 ; Esto mueve el bit desde el ''flag'' CARRY al puerto A. ; PORTA = 00001, C = 0 ; call Retardo call Retardo ; rlf PORTA,1 ; PORTA = 00010, C = 0; call Retardo call Retardo ; rlf PORTA,1 ; PORTA = 00100, C = 0; call Retardo call Retardo; rlf PORTA,1 ; PORTA = 01000, C = 0; call Retardo call Retardo; rlf PORTA,1 ; PORTA = 10000, C = 0;; call Retardo call Retardo;; Desplaza el bit de vuelta por el puerto A ; rrf PORTA,1 ; PORTA = 01000, C = 0; call Retardo call Retardo

Page 38: Introducción al PIC

rrf PORTA,1 ; PORTA = 00100, C = 0; call Retardo call Retardo rrf PORTA,1 ; PORTA = 00010, C = 0; call Retardo call Retardo rrf PORTA,1 ; PORTA = 00001, C = 0; call Retardo call Retardo rrf PORTA,1 ; Esta desplaza el bit al ''flag'' CARRY ; PORTA = 00000, C = 1; ;; Ahora desplaza el bit de vuela al Puerto B; rrf PORTB,1 ; PORTB = 10000000, C = 0 call Retardo call Retardo rrf PORTB,1 ; PORTB = 01000000, C = 0 call Retardo call Retardo rrf PORTB,1 ; PORTB = 00100000, C = 0 call Retardo call Retardo rrf PORTB,1 ; PORTB = 00010000, C = 0 call Retardo call Retardo rrf PORTB,1 ; PORTB = 00001000, C = 0 call Retardo call Retardo rrf PORTB,1 ; PORTB = 00000100, C = 0 call Retardo call Retardo rrf PORTB,1 ; PORTB = 00000010, C = 0 call Retardo call Retardo rrf PORTB,1 ; PORTB = 00000001, C = 0 call Retardo call Retardo ; Ahora hemos vuelto a donde empezamos, ; goto CorreLuz ; Vamos a hacerlo de nuevo. ;; La subrutina para hacer el retardo entre los movimientos de bits. ;Retardo movlw TIEMPO ; Cogemos el tiempo de retardo, movwf CONTADOR1 ; y lo ponemos en una variable. Bucle1 ; decfsz CONTADOR1 ; Decrementa el tiempo de retardo hasta goto Bucle1 ; que alcance cero. movwf CONTADOR1 ; Cogemos el tiempo de retardo de nuevo,

Page 39: Introducción al PIC

Bucle2 ; y repetimos la cuenta atrás. decfsz CONTADOR1 ; goto Bucle2 ; return ; Fin de la subrutina.; end ;

Tablas de Datos

Hay una propiedad interesante en el conjunto de instrucciones del PIC, que nos va a permitir

realizar tablas de datos.

Una tabla de datos es una lista simple de valores de datos, donde cada uno de ellos puede

ser leído(solo leído) dependiendo de algún criterio.

[Nota de la traducción: El ejemplo dado en el texto original, se ha sustituido por este que

mostramos a continuación para hacer más comprensible el concepto de tabla de datos. Al final

del capítulo se incluye un programa para realizar el ejemplo:

Por ejemplo, podrías hacer un circuito con el PIC, que cuente de 0 a 9 y represente este

número en un display de 7 segmentos. El display de 7 segmentos lo vamos a conectar al

puerto B. De manera que cada pin encienda un LED del display (como tenemos 8 pines en el

puerto B, uno de ellos no sería utilizado). Para ello, podemos utilizar el siguiente esquema de

conexiones entre el PIC y el display:

Es decir, el pin RB7 no lo conectaríamos al display. De este modo tendremos la siguiente tabla

de correspondencias entre los números a representar y el valor binario que debe de tomar el

puerto B para que se enciendan los diodos LED correspondientes:

Número Valor para puerto B Imagen en el display

1 b'01000001' = 41h

Page 40: Introducción al PIC

2 b'00111011' = 3Bh

3 b'01101011' = 6Bh

4 b'01001101' = 4Dh

5 b'01101110' = 6Eh

6 b'01111110' = 7Eh

7 b'01000011' = 43h

8 b'01111111' = 7Fh

9 b'01101111' = 6Fh

Page 41: Introducción al PIC

0 b'01110111' = 77h

Bien, pues esta tabla de correspondencias la queremos tener almacenada en el PIC, y

mediante el uso de una tabla de datos, vamos a poder hacerlo. Y así podremos representar el

número adecuado en cada momento.]

Ahora, antes de continuar con la explicación de como funciona la tabla de datos, tenemos que

explicar como hace el PIC seguimiento de por donde va el programa mientras se está

ejecutando. Si alguna vez has programado en BASIC esto te ayudará a comprenderlo. Si no,

no te preocupes, aun así serás capaz de comprender el concepto.

Imagina que tenemos un programa BASIC como el que se muestra AQUÍ:

10 LET K=0 11 K=K+1 12 IF K>10 THEN GOTO 20 ELSE GOTO 11 20 PRINT K 21 END

El programa comienza en la linea 10. Una vez que K vale 0, continua con la linea 11. Después

de que le hemos añadido 1 a K nos desplazamos a la linea 12. Aquí estamos preguntando si K

es mayor que 10. Si lo es, entonces vamos a la linea 20, y si no, volvemos a la 11. La linea 20

muestra el valor de K en pantalla, y la linea 21 finaliza el programa. BASIC utiliza los números

de linea para ayudar al programador a hacer seguimiento de donde está cada cosa, ya que las

etiquetas no están permitidas.

El PIC utiliza etiquetas para saltar de unas posiciones a otras, ¿si?. Utilizamos las etiquetas

para que sepamos donde están las cosas, y también para que podamos decirle al PIC de una

manera sencilla donde tiene que ir. Lo que realmente ocurre es que el PIC utiliza un contador

de línea interno llamado Contador de Programa [Nota de la traducción: en ingles "Program

Counter", abreviado también como PC. ]. El Contador de Programa mantiene almacenada la

dirección de la posición de memoria donde se encuentra la instrucción actual. Cuando le

decimos al PIC que vaya a una etiqueta en particular, sabe la posición de memoria y por tanto

modifica el PC hasta que alcanza esa posición y la lee. Esto es exactamente el mismo modo en

el que leemos nosotros el programa BASIC anterior.

Page 42: Introducción al PIC

Aquí hay una sección de código, con las posiciones de memoria, o contenidos del PC, junto a

cada instrucción:

PC Instrucción-----------------------------0000 movlw 03h0001 movwf 0Ch0002 Bucle decfsc 0Ch0003 goto Bucle0004 end

En este ejemplo anterior hemos puesto el PC a 0000. En esta posición tenemos la instrucción

"movlw 03h". Cuando el PIC ha ejecutado esta instrucción, incrementará PC para que la

siguiente instrucción pueda ser leída. Aquí el PIC ve "movwf 0Ch". El PC se incrementa de

nuevo. Ahora el PIC lee "decfsc 0Ch". Si el contenido del registro 0Ch no es 0, entonces el PC

se incrementa en 1, y la siguiente instrucción, "goto Bucle", le dice al PC que vaya de vuelta a

la posición 0002, en la cual hemos escrito la palabra "Bucle". Si el contenido del 0Ch es 0,

entonces el PC será incrementado en 2, en otras palabras se saltará la siguiente instrucción.

Esto colocará el PC en la posición 0004, donde el programa termina.

Las posiciones son asignadas por el programa ensamblador (p.ej. MPASM), y normalmente no

nos tenemos que preocupar de lo que hace el Contador de Programa(PC). Hasta que

necesitemos controlarlo, como vamos a hacer cuando usemos tablas de datos.

La mejor manera de explicar como funciona una tabla de datos es comenzar con un pequeño

ejemplo.

PC equ 02h; movlw 03h call tabla : : :tabla addwf PC retlw 0Ah retlw 0Bh retlw 0Ch retlw 0Dh retlw 0Eh retlw 0Fh retlw 10h; return

Page 43: Introducción al PIC

La primera instrucción esta asignando a la etiqueta PC la dirección del Contador de Programa

(02h). Después colocamos el valor 03h en el registro w. A continuación hacemos una llamada a

"tabla". La primera linea en la subrutina "tabla" añade los contenidos del registro W (03h) al

Contador de Programa. Esto causa que el contador de programa se incremente en 3, o dicho

de otro modo, causa que el contador de programa se mueva 3 lineas hacia abajo. Cuando el

contador llega 3 lineas más abajo el PIC ve la instrucción retlw. Este comando pasa el valor

que viene con la instrucción, al registro W, y retorna desde la subrutina.

RETLW realmente significa "RETurn, y pon el Literal en W". Date cuenta de que hemos puesto

una coma después de la palabra "return" en la frase anterior. Como si estuviésemos en una

subrutina normal, necesitamos una instrucción de "return" para salir de ella. Eso lo hace

el RET en la instrucción. Después de la instrucción RETLW hay un número, y este es el que

colocaremos en el registro W. En este caso el número 0Ch.

[Nota de la traducción: En resumidas cuentas, el valor que ponemos en el registro W antes de

llamar a la subrutina "tabla", indica qué posición queremos mirar de la tabla. Es decir, actúa de

índice o indicador de la posición que queremos ver. Al pasarle un 3 a W antes de "call tabla", le

estamos diciendo que queremos recuperar el valor que existe en la posición 3 de la tabla. La

primera instrucción de la subrutina "tabla" dice "sumarle el contenido de W a PC", es decir, le

sumará 3 a PC. Lo cual va a trasladar el PC hasta la instrucción "retlw 0Ch". Entonces, este

valor, 0Ch, será copiado en el registro W, y se efectuará un "RETurn", que hace que el

programa continúe por la instrucción siguiente a "call tabla". Ahora, estamos de vuelta al flujo

principal del programa con W cargado con el dato que queríamos recuperar de nuestra tabla.

De este modo, hemos creado una tabla de datos, que la podremos consultar cuando queramos

para ver un valor de una posición concreta. En el ejemplo del display de 7 segmentos se ve de

modo práctico para qué se podría utilizar una tabla de datos.]

(Antes de llamar a la subrutina) podemos asignar cualquier número a W, siempre y cuando este

número al ser añadido a PC haga que PC se desplace aun elemento de la tabla de la subrutina,

allí donde hemos puesto una instrucción retlw. En el ejemplo anterior esto significa que

podemos poner un número de 1 a 7. Si nos pasamos de largo de la subrutina, podríamos

terminar ejecutando otra parte del programa. Por esta razón, siempre es buena idea poner la

tabla de datos al final del programa, así si nos pasamos de largo podemos llegar al final del

programa en cualquier caso.

[Nota de la traducción: Continuando con el ejemplo del display de 7 segmentos, ponemos aquí

la tabla de datos que tendremos que crear. La hemos sacado de la tabla del principio de este

capítulo:

Page 44: Introducción al PIC

Posción Valor Número para el display

1 b'01000001' = 41h 1

2 b'00111011' = 3Bh 2

3 b'01101011' = 6Bh 3

4 b'01001101' = 4Dh 4

5 b'01101110' = 6Eh 5

6 b'01111110' = 7Eh 6

7 b'01000011' = 43h 7

8 b'01111111' = 7Fh 8

9 b'01101111' = 6Fh 9

10 b'01110111' = 77h 0

Hemos puesto el 0 en la última posición, para que el resto de los números que hay que

representar en el display, coincidan con la posición que ocupan en la tabla.

De este modo, la tabla se podría crear utilizando el siguiente código:

PC equ 02h : :tabla addwf PC retlw 41h retlw 3Bh retlw 6Bh retlw 4Dh retlw 6Eh retlw 7Eh retlw 43h retlw 7Fh retlw 6Fh retlw 77h return

Hemos reutilizado parte del código que hemos empleado anteriormente para crear este nuevo.

Así que habrá partes que te serán familiares. Este es el código completo para hacer que el PIC

muestre en el display una cuenta ascendente de 0 a 9 y vuelta a empezar:

PC equ 02h ; Dirección del Contador de Programa(PC)TIEMPO equ 9Fh ; Variable para el bucle de retardo.PORTB equ 06h ; Dirección del Port B.TRISB equ 86h ; Dirección del registro tri-estado del Port B.STATUS equ 03h ; Registro para seleccionar el banco.CONTADOR1 equ 0Ch ; Registro para el bucle.

Page 45: Introducción al PIC

CONTADOR2 equ 0Dh ; Registro para el bucle.;; Configuramos el Puerto B; bsf STATUS,5 ; Va al Banco 1 movlw 00h ; y configura los pines del Puerto B movwf TRISB ; todos como salidas. bcf STATUS,5 ; Vuelta al Banco 0. movlw 00h ; Pon a 0 el Puerto B. Es decir, movwf PORTB ; el display totalmente apagado.;; Comienzo del programa;Inicio movlw 0Ah ; Carga en W el valor 10 antes de llamar a la tabla call tabla ; para recuperar la configuración de encendido del 0. movwf PORTB ; Ahora W = 77h, lo pone en puerto B, para representar 0 en el display.; call Retardo ; Espera un momento. call Retardo ; Para que se pueda ver el 0 ; movlw 01h ; Carga en W el valor 1 antes de llamar a la tabla call tabla ; para recuperar la configuración de encendido del 1. movwf PORTB ; De W lo pone en puerto B, para representar 1 en el display.; call Retardo ; call Retardo ; ; movlw 02h ; Carga en W el valor 2 antes de llamar a la tabla call tabla ; para recuperar la configuración de encendido del 2. movwf PORTB ; De W lo pone en puerto B, para representar 2 en el display.; call Retardo ; call Retardo ; ; movlw 03h ; Carga en W el valor 3 antes de llamar a la tabla call tabla ; para recuperar la configuración de encendido del 3. movwf PORTB ; De W lo pone en puerto B, para representar 3 en el display.;

Page 46: Introducción al PIC

call Retardo ; call Retardo ; ; movlw 04h ; Carga en W el valor 4 antes de llamar a la tabla call tabla ; para recuperar la configuración de encendido del 4. movwf PORTB ; De W lo pone en puerto B, para representar 4 en el display.; call Retardo ; call Retardo ; ; movlw 05h ; Carga en W el valor 5 antes de llamar a la tabla call tabla ; para recuperar la configuración de encendido del 5. movwf PORTB ; De W lo pone en puerto B, para representar 5 en el display.; call Retardo ; call Retardo ; ; movlw 06h ; Carga en W el valor 6 antes de llamar a la tabla call tabla ; para recuperar la configuración de encendido del 6. movwf PORTB ; De W lo pone en puerto B, para representar 6 en el display.; call Retardo ; call Retardo ; ; movlw 07h ; Carga en W el valor 7 antes de llamar a la tabla call tabla ; para recuperar la configuración de encendido del 7. movwf PORTB ; De W lo pone en puerto B, para representar 7 en el display.; call Retardo ; call Retardo ; ; movlw 08h ; Carga en W el valor 8 antes de llamar a la tabla call tabla ; para recuperar la configuración de encendido del 8. movwf PORTB ; De W lo pone en puerto B, para representar 8 en el display.; call Retardo ;

Page 47: Introducción al PIC

call Retardo ; ; movlw 09h ; Carga en W el valor 9 antes de llamar a la tabla call tabla ; para recuperar la configuración de encendido del 9. movwf PORTB ; De W lo pone en puerto B, para representar 9 en el display.; call Retardo ; call Retardo ; ; goto Inicio ; Vamos a hacerlo de nuevo. ;; La subrutina para hacer el retardo entre los números. ;Retardo movlw TIEMPO ; Cogemos el tiempo de retardo, movwf CONTADOR1 ; y lo ponemos en una variable. Bucle1 ; decfsz CONTADOR1 ; Decrementa el tiempo de retardo hasta goto Bucle1 ; que alcance cero. movwf CONTADOR2 ; Cogemos el tiempo de retardo de nuevo, Bucle2 ; y repetimos la cuenta atrás. decfsz CONTADOR2 ; goto Bucle2 ; return ; Fin de la subrutina.;; Y aquí la tabla de configuraciones para representar los números; en el display de 7 segmentos, de acuerdo a nuestro esquema de conexiones.;tabla addwf PC retlw 41h ; Carga 41h (representación de 1) en W y retorna. retlw 3Bh ; Carga 3Bh (representación de 2) en W y retorna. retlw 6Bh ; Carga 6Bh (representación de 3) en W y retorna. retlw 4Dh ; Carga 4Dh (representación de 4) en W y retorna. retlw 6Eh ; Carga 6Eh (representación de 5) en W y retorna. retlw 7Eh ; Carga 7Eh (representación de 6) en W y retorna. retlw 43h ; Carga 43h (representación de 7) en W y retorna. retlw 7Fh ; Carga 7Fh (representación de 8) en W y retorna. retlw 6Fh ; Carga 6Fh (representación de 9) en W

Page 48: Introducción al PIC

y retorna. retlw 77h ; Carga 77h (representación de 0) en W y retorna. return ; ; end ; Fin del programa.

Obviamente, este programa es un simple ejemplo que puede ser optimizado con los conceptos

que has aprendido en los anteriores capítulos. ¿ podrías reducir el número de instrucciones ? ¿

Podrías hacer que la cuenta fuese ascendente o descendente dependiendo del valor de otro

pin del puerto A ? ¡¡Animo!! , esta es una buena ocasión de experimentar con lo que ya sabes.

Interrupciones : una introducción

El tema de las interrupciones va a ser probablemente el más difícil y que más tiempo nos

llevará revisar. No hay una manera sencilla de explicar las interrupciones, pero espero que para

el final de esta sección seas capaz de implementar las interrupciones en tus propios

programas. Hemos dividido el tema en dos capítulos. Esto es para ayudar a reducir la materia,

y darte a ti, lector, una pausa.

Así que, ¿qué es una interrupción? Bien, como su nombre sugiere, una interrupción es un

proceso o una señal que hace detenerse al microprocesador/microcontrolador de lo está

haciendo, para que haga otra cosa. Permiteme darte un ejemplo de la vida cotidiana. Supón

que estás sentado en tu casa, charlando con alguien. De repente el teléfono suena. Tú

detienes la charla, levantas el teléfono y hablas con la persona que te llamó. Cuando terminas

tu conversación telefónica, vuelves para charlar con la persona que estabas antes de que

sonase el teléfono. Puedes hacerte la idea de que la rutina principal es que tú estás charlando

con alguien, que el teléfono suene causa una interrupción a tu charla, y que la rutina de

interrupción es el proceso de hablar por teléfono. Cuando la conversación telefónica ha

terminado, entonces vuelves a la rutina principal de la charla. Este ejemplo es exactamente

igual a como una interrupción hace que actúe un procesador. El programa principal está en

curso, ejecutando alguna función en un circuito, pero cuando ocurre una interrupción el

programa principal se detiene para que otra rutina se lleve a cabo. Cuando la rutina termina, el

procesador vuelve a la rutina principal de nuevo.

El PIC tiene 4 fuentes de interrupción. Se pueden dividir en dos grupos. Dos de ellas son

fuentes de interrupciones que pueden ser aplicadas externamente al PIC, mientras que las

otras dos son de procesos internos. Vamos a explicar las dos externas aquí. Las otras dos las

explicaremos en otros capítulos cuando miremos los temporizadores [Nota de la traducción:

timers] y el almacenamiento de datos.

Page 49: Introducción al PIC

Si observas los pines del PIC, verás que el pin 6 se muestra como RB0/INT. RB0 es

obviamente el bit 0 del Puerto B. El INT simboliza que también puede ser configurado como un

pin de interrupción. También, los bits del 4 al 7 del puerto B (pines 10 al 13) pueden ser

utilizados para interrupciones. Antes de que usemos INT u otros pines del puerto B,

necesitamos hacer dos cosas. La primera necesitamos decirle al PIC que vamos a usar las

interrupciones. Segunda, necesitamos especificar que pin del puerto B usaremos como

interrupción y no como un pin de Entrada/Salida.

Dentro del PIC hay un registro llamado INTCON, y su dirección es la 0Bh. Dentro de este

registro hay 8 bits que pueden ser habilitados o deshabilitados. El bit 7 de INTCON es llamado

GIE [Nota de la Traducción: en ingles de las siglas "Global Interrupt Enable", en castellano

"Habilitador de Interrupciones Global"]. Poniendo este bit a 1 le decimos al PIC que vamos a

usar una interrupción. El bit 4 de INTCON es llamado INTE [Nota de la traducción: en ingles,

"INT Enable". En castellano "Habilita INT", o dicho de otro modo que utilizaremos el pin 6 con

su función INT, no como RB0.]. Poniendo este bit a 1 decimos al PIC que RB0 será un pin de

interrupción. Poniendo a 1 el bit 3 de INTCON, llamado RBIE, decimos al PIC que usaremos los

bits del 4 al 7 del puerto B como interrupciones. Ahora el PIC sabe que cuando uno de estos

pines cambia a nivel alto o cambia nivel bajo, tiene que parar lo que esté haciendo e ir a una

rutina de interrupción. Ahora, tenemos que decirle al PIC si la interrupción se va a producir en

la transición ascendente de la señal(de 0 Voltios a +5 Voltios) o la descendente (de +5V a 0V).

En otras palabras, ¿queremos que el PIC haga la interrupción cuando la señal vaya de nivel

bajo a nivel alto, o de nivel alto a nivel bajo? . Por defecto, esto está configurado para que sea

en la transición ascendente (o flanco de subida de la señal). El flanco de "disparo" se configura

en otro registro llamado OPTION, que está en la dirección 81h.

El bit en el que estamos interesados es el bit 6, que se llama INTEDG [Nota de la traducción:

del ingles "INTerrupt EDGe", o en castellano "flanco para la interrupción" ]. Poniendo este a 1

causará que el PIC sea interrumpido en el flanco de subida o transición ascendente (el estado

por defecto) y poniéndolo a 0 causará que el PIC sea interrumpido en el flanco de bajada o

transición descendente. Si quieres que el PIC sea interrumpido en el flanco de subida, no

necesitas configurar el bit. Pero, desafortunadamente el registro OPTION está en el Banco 1, lo

que significa que tienes que cambiar del banco 0 al banco 1, cambiar el bit del registro

OPTION, y después volver al banco 0. El truco está en hacer todas las operaciones con los

registros del banco 1 de una sola vez, tales como configurar los pines de los puertos, etc... y

después volver al banco 0 cuando hayas terminado.

Page 50: Introducción al PIC

[Nota de la Traducción: A modo de esquema, esto es lo que hay que hacer por orden, para

utilizar las interrupciones externas:

1º - Poner a 1 el bit 7 (GIE) del registro INTCON (OBh).

2º - En el mismo registro INTCON:

2.1 - Poner a 1 el bit 4 (INTE), si queremos utilizar el pin 6 como entrada de

INTerrupción.

2.2 - O poner a 1 el bit 3 (RBIE), si queremos utilizar los pines del 10 al 13 (bits

4 a 7 del puerto B) como entrada de interrupción.

3º - En el registro OPTION (81h):

3.1 - Poner en el bit 6 (INTEDG) un 1 si queremos que se active la interrupción

en el flanco de subida(de 0V a +5V).

3.2 - Poner en el bit 6 (INTEDG) un 0 si queremos que se active la interrupción

en el flanco de bajada(de +5V a 0V).]

Bien, así que ahora que le hemos dicho al PIC qué pin va a ser para la interrupción, y en qué

flanco se va a disparar la misma, ¿qué ocurre en el programa y en el PIC cuando sucede la

interrupción ?

Ocurren dos cosas. Primero, se activa un flag. Este le dice al procesador interno del PIC que ha

ocurrido una interrupción. Segundo, el Contador de Programa (que hemos mencionado en el

capítulo anterior) apunta a una dirección particular dentro del PIC. Vamos a ver cada cosa

separadamente.

El Flag de Interrupción

En nuestro registro INTCON, el bit 1 es el flag de interrupción llamado INTF [Nota de la

Traducción: "INTerrupt Flag"]. Entonces, cuando ocurra una interrupción, este flag se pondrá a

1. Mientras no haya una interrupción, el flag estará a 0. Y eso es todo lo que hace. Ahora

estarás probablemente pensando, y entonces ¿para qué sirve? Bien, mientras este flag esté a

1, el PIC no puede, de ninguna manera, atender a cualquier otra interrupción. Por que digamos

que causamos una interrupción. El flag se pondrá a 1, y el PIC va a nuestra rutina de

interrupción para procesarla. Si el flag no fuese puesto a 1, al PIC se le permitiría seguir

respondiendo a las interrupciones, y por tanto una serie de pulsos continuos sobre el pin harían

que el PIC volviese a comenzar con nuestra rutina, y nunca la terminaría. Volviendo a mi

Page 51: Introducción al PIC

ejemplo del teléfono, es como descolgar el teléfono, y tan pronto como comienzas a hablar, que

volviese a sonar de nuevo porque alguien más quiere hablar contigo. Es mucho mejor terminar

una conversación, y después descolgar el teléfono de nuevo para hablar con la segunda

persona.

Hay un pequeño inconveniente respecto a este flag. Aunque el PIC automáticamente pone

el flag a 1, ¡no lo pone de nuevo a 0! Esta tarea tiene que hacerla el programador (por ejemplo

tú). Se hace muy fácilmente, y como seguro que ya supones, se tiene que hacer después de

que el PIC haya ejecutado la rutina de interrupción.

La Posición de Memoria

Cuando enciendes por primera vez el PIC, o si ocurriese un reset, el Contador de Programa

apunta a la dirección 0000h, que está justo al principio de la memoria de programa. Sin

embargo, cuando ocurre una interrupción, el Contador de Programa apuntará a

la dirección   0004h . Así que, cuando escribamos nuestro programa con interrupciones, lo

primero que tenemos que decirle al PIC es que se salte la dirección 0004h, y mantenga, la

rutina de interrupción que empieza en esa dirección 0004h, separada del resto del programa.

Esto es muy fácil de hacer.

Primero, comenzamos nuestro programa con un comando llamado ORG. Este comando

significa Origen, o inicio. Y lo que le sigue es una dirección.[Nota de la traducción: ORG no es

una instrucción del PIC sino una directiva del ensamblador, al igual que lo son EQU o END que

vimos anteriormente. ORG indica al ensamblador la dirección de memoria a partir de la cual se

deberá ubicar el código que viene escrito a continuación de ella.] Dado que el PIC comienza en

la dirección 0000h, escribimos "ORG 0000h". Lo siguiente que tenemos que hacer es saltarnos

la dirección 0004h. Hacemos esto con una instrucción "GOTO", seguida de la etiqueta que

apunte a nuestro programa principal. 

A continuación del "GOTO" ponemos otro comando ORG, pero esta vez seguido de la dirección

0004h. Es después de este comando donde introduciremos la rutina de interrupción. Ahora

después, podríamos o bien escribir directamente nuestra rutina de interrupción seguida de un

segundo comando ORG, o bien podemos poner una instrucción "GOTO" que apunte a la rutina

de interrupción (que podríamos escribirla al final del programa). Realmente es una cuestión de

elegir por tu parte. Después, en la rutina de interrupción, para decirle al PIC que ha llegado al

final de la misma tenemos que poner la instrucción "RETFIE" justo al final. Este comando

significa "retorna de la rutina de interrupción". Cuando el PIC la ve, el Contador de Programa

apuntará a la última posición en la que el PIC estaba antes de que ocurriese la interrupción.

Ponemos aquí un fragmento de código para mostrar lo anterior:

Page 52: Introducción al PIC

; ORG 0000h ; El PIC comienza aquí si se enciende o hay un reset. GOTO Inicio ; Ve al programa principal. ORG 0004h ; El PIC vendrá aquí si ocurre una interrupción. : ; Esta es nuestra rutina de interrupción : ; con lo que queremos que haga el PIC : ; cuando reciba una interrupción. RETFIE ; Fin de la rutina de interrupción.Inicio ; Este es el comienzo de nuestro programa principal

Hay dos cosas que tienes que tener en cuenta cuando utilices interrupciones. La primera que si

estás usando el mismo registro en tu programa principal y en la rutina de interrupción, recuerda

que los contenidos del registro, probablemente cambien cuando ocurra la interrupción. Por

ejemplo, estás utilizando el registro W para enviar datos al puerto A en el programa principal, y

también vas a utilizar el registro W en tu rutina de interrupción para mover datos de una

posición a otra. Si no tienes cuidado, el registro W contendrá el ultimo valor que tenía cuando

estaba en la rutina de interrupción, y cuando vuelvas de la interrupción, este dato se enviará al

puerto A en lugar del valor que tenías antes de que ocurriese la interrupción. Para evitar esto,

dentro de la rutina de interrupción, se tienen que almacenar los contenidos del registro W,

antes de que lo uses . Lo segundo es que tiene que existir un retardo entre que ocurre una

interrupción y que pueda ocurrir la siguiente. Como sabes, el PIC tiene un reloj externo que

puede ser, o bien un Cristal, o bien una red RC (resistencia-condensador). Independientemente

de la frecuencia de reloj, el PIC la divide entre 4 y la utiliza para su reloj interno. Por ejemplo, si

usas un cristal de 4MHz, el PIC llevará a cabo las instrucciones a 1MHz. A este tiempo interno

se le llama Ciclo de Instrucción. Bien, la hoja de características (datasheet), aunque en letra

pequeña, dice que debes dejar que pasen de 3 a 4 ciclos de instrucción entre interrupciones. Mi

consejo es que dejes 4 ciclos. La razón para este retardo es que el PIC necesita tiempo para

saltar a la dirección de interrupción, poner el flag, y volver de la rutina de interrupción. Así que

ten esto en mente si estas utilizando otro circuito que genere una interrupción en el PIC.

Un punto a recordar es que si utilizas los bits del 4 al 7 del puerto B como interrupción, no

puedes seleccionar un pin individual del puerto B para que sirva como interrupción. Así, si

habilitas estos pines, todos estarán habilitados a la vez. De modo que, por ejemplo, no puedes

tener solamente los bits 4 y 5, los bits 6 y 7 también los tendrás habilitados. Entonces ¿ Para

que sirve tener cuatro bits que actúan como interrupción? Bien, podrías tener un circuito

conectado al PIC, y si cualquiera de las cuatro lineas se pusiese a nivel alto, esta podría ser la

condición que necesites para que el PIC actuase rápidamente. Un ejemplo de esto puede ser la

Page 53: Introducción al PIC

alarma de una casa, donde cuatro sensores estén conectados a los bits 4 a 7 del puerto B.

Cualquier sensor podría activar al PIC para que hiciese sonar una alarma, y que la rutina de

hacer sonar la alarma fuese la rutina de interrupción. Esto ahorra el estar comprobando todo el

tiempo todos los pines, y permite al PIC continuar haciendo otras cosas.

Interrupciones : Cómo escribir el código

En el pasado capítulo cubrimos mas o menos lo básico, así que creemos que es el momento

de escribir nuestro primer programa. El programa que vamos a escribir contará el número de

veces que un switch es activado, y después mostraremos el número. El programa contará de 0

a 9, mostrando en 4 LEDs en formato binario, y la entrada o interrupción será por RB0.

La primera cosa que tenemos que hacer es decirle al PIC que salte la dirección donde el

Contador de Programa apuntará cuando ocurra una interrupción. Aquí notarás que estamos

utilizando una forma distinta de expresar los número hexadecimales. Antes usábamos "F9h",

donde "h" denotaba que era hexadecimal. Podemos escribir esto como 0xF9, y ese será el

formato que vamos a utilizar a partir de ahora.

; org 0x00 ; Aquí es donde apunta el PC si energiza el PIC o en caso de reset goto Principal ; Ir a nuestro programa principal org 0x04 ; Aquí es donde comienza nuestra rutina de interrupción. retfie ; Esto le dice al PIC que la rutina de interrupción ; ha terminado y el PC volverá a apuntar al programa principalPrincipal ; Este es el comienzo de nuestro programa principal.

Ahora tenemos que decirle al PIC que vamos a utilizar las interrupciones, y que el pin de

interrupción va a ser el pin 6 (RB0):

; bsf INTCON,7 ; GIE – Global interrupt enable (1=habilitado) bsf INTCON,4 ; INTE - RB0 interrupt enable (1=habilitado)

Vamos a poner a 0 el flag de interrupción por si acaso (¡¡no nos fiamos de nada!!):

; bcf INTCON,1 ; INTF - A 0 por si acaso.

Page 54: Introducción al PIC

Ahora tenemos que configurar nuestros dos puertos. Recuerda que como estamos utilizando

RB0 como pin de interrupción, este debe de ser configurado como entrada:

; bsf STATUS,5 ; Cambia al banco 1. movw 0x01 ; movwf TRISB ; Establece RB0 como entrada movlw 0x10 ; movwf TRISA ; Pone los 4 primeros pines del puerto A como salida bcf STATUS,5 ; Vuelve al banco 0.

Vamos a utilizar una variable llamada CONTADOR para almacenar el numero de

conmutaciones contadas.

Podríamos simplemente incrementar el valor del puerto A, pero verás porque estamos

utilizando una variable cuando escrbimos nuestra rutina de interrupción:

Bucle

movf CONTADOR,0 ; Movemos los contenidos de CONTADOR a W. movwf PORTA ; Ahora lo movemos al puerto A. goto Bucle ; Continuamos haciendo esto. end ; Fin de nuestro programa.

Así, nuestro programa principal está escrito, y ahora tenemos que decirle al PIC que hacer

cuando ocurra una interrupción. respecto a esto, nuestra interrupción va a ser una

conmutación. Lo que queremos que haga el PIC es añadir 1 a la variable CONTADOR cada

vez que el switch esté cerrado. Sin embargo, solo queremos mostrar los número que

el switch se cierra de 0 a 9 veces. Antes dijimos que simplemente podríamos incrementar el

puerto A cada vez que hubiese una interrupción. Pero, el puerto A tiene 5 bits, y si simplemente

incrementamos el valor del puerto, tendremos una cuenta de hasta 31. Hay dos razones por las

que elegimos no contar hasta 31. La primera, vamos a utilizar 4 diodos, con los cuales solo

podemos mostrar de 0 a 15 (de 0 a F en hexadecimal, o 0000 a 1111 en binario). Segundo,

también queremos mostrarte algunos comandos aritméticos que has visto en los pasados

capítulos.

Así que vamos con nuestra rutina de interrupción.

Ahora la primera cosa que tenemos que hacer es almacenar de manera temporal los

contenidos nuestro registro W, ya que lo estamos utilizando para transferir los contenidos de

CONTADOR al PORTA. Si no lo almacenamos, entonces podríamos enviar un número

Page 55: Introducción al PIC

completamente diferente como resultado de nuestras operaciones. Así que hagamos eso

primero:

; movwf TEMPORAL ; Almacenamos w en una posición temporal.

Lo siguientes que queremos es añadir 1 a nuestra variable CONTADOR:

; incf CONTADOR,1 ; Incrementamos CONTADOR en 1 y ponemos ; el resultado de vuelta en CONTADOR.

Lo siguientes es hacer un chequeo de CONTADOR para ver si hemos pasado el valor de 9. La

forma en que lo podemos hacer es restandole 10.

; movlw 0x0A ; Ponemos 10 en W. subwf CONTADOR,0 ; Restamos W a CONTADOR y ponemos ; el resultado en W.

En el capítulo Operadores Aritméticos y Lógicos vimos que si restamos un número mayor a un

número menor, el flag de Acarreo (CARRY) se pondrá a 1. Este flag también se pondrá a 1 si lo

números son iguales y los restamos.

; btfss STATUS,0 ; Comprueba el flag CARRY. Se activará si ; CONTADOR es igual o mayor que w, ; y se activará como resultado de la instrucción subwf

Ahora sabemos si el valor de CONTADOR es 9 o más. Lo que queremos hacer ahora es, si

CONTADOR es mayor que 9, ponlo de nuevo a 0, de otro modo vuelve al programa principal

para que podamos enviarlo al PORTA. La instrucción BFTSS, como sabes, se saltará la

siguiente instrucción si el flag de CARRY se pone a 1. Por ejemplo CONTADOR=10:

; goto continua ; Si CONTADOR es <10, entonces continua goto limpiar ; Si CONTADOR es >9, tenemos que ponerlo a 0continua

Page 56: Introducción al PIC

bcf INTCON,0x01 ; Tenemos que poner a 0 este ''flag'' para ; permitir más interrupciones. movfw TEMPORAL ; Restaura W al valor que tenía antes de la interrupción. retfie ; Salir de la rutina de interrupción.limpiar clrf CONTADOR ; Pon CONTADOR otra vez a 0. bcf INTCON,1 ; Tenemos que poner a 0 este ''flag'' para ; permitir más interrupciones. retfie ; Salir de la rutina de interrupción.

Todo lo que queda hacer ahora es ponerlo todo junto y también definir los valores de nuestras

constantes, lo cual se hará justo al principio de nuestro programa.

Aquí debajo está el listado completo. El circuito se muestra después del listado del programa.

Cada vez que pulses el conmutador, los LEDs contarán in binario desde 0000 a 1010, y vuelta

a 0000.

; org 0x00 ; Aquí es donde apunta el PC si energiza el PIC o en caso de reset;;*****************DEFINICIÓN DE CONSTANTES********************************INTCON EQU 0x0B ; Registro de Control de InterrupcionesPORTB EQU 0x06 ; Dirección del registro PORTBPORTA EQU 0x05 ; Dirección del registro PORTATRISA EQU 0x85 ; Dirección del registro TRISATRISB EQU 0x86 ; Dirección del registro TRISBSTATUS EQU 0X03 ; Dirección del registro STATUSCONTADOR EQU 0x0c ; Esta será nuestra variable contadorTEMPORAL EQU 0x0d ; Almacén temporal para el registro W goto Principal ; Ir a nuestro programa principal saltando se la dirección de interrupción.;;***************RUTINA DE INTERRUPCION********************************* org 0x04 ; Aquí es donde comienza nuestra rutina de interrupción. movwf TEMPORAL ; Almacenamos w en una posición

Page 57: Introducción al PIC

temporal. incf CONTADOR,1 ; Incrementamos CONTADOR en 1 y ponemos ; el resultado de vuelta en CONTADOR. movlw 0x0A ; Ponemos 10 en W. subwf CONTADOR,0 ; Restamos W a CONTADOR y ponemos ; el resultado en W. btfss STATUS,0 ; Comprueba el flag CARRY. Se activará si ; CONTADOR es igual o mayor que w, ; y se activará como resultado de la instrucción subwf goto continua ; Si CONTADOR es <10, entonces continua goto limpiar ; Si CONTADOR es >9, tenemos que ponerlo a 0continua bcf INTCON,0x01 ; Tenemos que poner a 0 este ''flag'' para ; permitir más interrupciones. movfw TEMPORAL ; Restaura W al valor que tenía antes de la interrupción. retfie ; Salir de la rutina de interrupción.limpiar clrf CONTADOR ; Pon CONTADOR otra vez a 0. bcf INTCON,1 ; Tenemos que poner a 0 este ''flag'' para ; permitir más interrupciones. retfie ; Salir de la rutina de interrupción.;;***************PROGRAMA PRINCIPAL*********************************** Principal ; Este es el comienzo de nuestro programa principal.;****************Configura los Registros de Interrupción************* bsf INTCON,7 ; GIE – Global interrupt enable (1=habilitado) bsf INTCON,4 ; INTE - RB0 interrupt enable (1=habilitado) bcf INTCON,1 ; INTF - A 0 por si acaso.;;****************Configura los puertos******************************* bsf STATUS,5 ; Cambia al banco 1. movw 0x01 ; movwf TRISB ; Establece RB0 como entrada movlw 0x10 ; movwf TRISA ; Pone los 4 primeros pines del puerto A como salida bcf STATUS,5 ; Vuelve al banco 0.

Page 58: Introducción al PIC

;;****************Ahora envía el valor de CONTADOR al PORTA***********Bucle

movf CONTADOR,0 ; Movemos los contenidos de CONTADOR a W. movwf PORTA ; Ahora lo movemos al puerto A. goto Bucle ; Continuamos haciendo esto. end ; Fin de nuestro programa.

Diagrama del circuito

Mas abajo está el diagrama del circuito funcionará con el código anterior. Hay dos cosas en el

diagrama que puede que te sorprendan. Primero, no hemos incluido el condensador de

temporización en el circuito. Este es un ingenioso truco que puedes intentar si te quedas sin

condensadores. La capacitancia viene dada por la capacitancia de separación entre el pin del

oscilador y pin de masa. Por tanto, con una resistencia y la capacitancia de separación,

tenemos un oscilador RC. Bien, no es una manera precisa de hacerlo, ya que la capacitancia

de separación variará de un circuito a otro. Pero, pensábamos que podrías encontrar

interesante esta idea. Segundo, hemos incluido un circuito anti-rebote para el switch. Este hará

que el PIC "crea" que ha existido más de una pulsación. Con el circuito anti-rebote, cuando

el switch se pone a nivel alto, el condensador se carga. No importa cuantas veces

el switch vaya a nivel alto (+5V), el condensador solo se cargará una vez. El condensador es

descargado cuando el switch se pone en la otra posición. Si quieres ver los efectos de rebote

de un switch, quita el condensador y la resistencia del switch.

El Watchdog Timer

Page 59: Introducción al PIC

Ahora vamos a echar un vistazo a un temporizador interno, llamado "Watchdog Timer" [Nota de

la traducción: En castellano sería, "temporizador perro guardián"]

Así que, ¿Qué es un watchdog timer?

Supón que has escrito un programa que está continuamente corriendo en un PIC. Ahora,

quieres asegurarte de que el programa sigue ejecutándose siempre, y no hay manera de que

se pare nunca. La primera cosa que tienes que hacer, por supuesto, es un bucle que desde el

final del programa te lleve hasta el principio. Pero ten en cuenta esto. Digamos que el PIC está

monitorizando una entrada. Cuando esta entrada se pone a nivel alto, salta a otra parte del

programa y espera por otro pin para que se ponga a nivel alto. Si el segundo pin no se pone a

nivel alto, el PIC simplemente "se sentará a esperar". Solo saldrá de ahí si el segundo pin se

pone a nivel alto.

Consideremos otro ejemplo. Supón que has escrito un programa, lo has compilado con éxito, e

incluso lo has simulado una y otra vez utilizando un simulador como MPLAB. Todo parece

funcionar bien. Programas el PIC y lo colocas en tu circuito. Sin embargo después de un largo

periodo el programa se atasca en algún punto y el PIC se queda enganchado en un bucle.

Lo que se necesita en ambos casos es alguna clase de reset(o reinicio) si el programa se

quedó atascado. Este es el propósito del watchdog timer.

Un circuito watchdog no es nada nuevo. Muchos microprocesadores y microcontroladores lo

tienen. Pero, ¿cómo funciona? Bien, dentro del PIC hay una red resistencia-condensador. Esta

proporciona un reloj único, que es independiente de cualquier reloj externo que proporciones al

circuito. Ahora cuando el watchdog timer (abreviado como WDT) está habilitado, un contador

comienza en 00 y se incrementa en 1 hasta que alcanza FF. Cuando pasa de FF a 00 (lo cual

es FF+1) el PIC será reiniciado, independientemente de lo que esté haciendo. La única manera

de evitar que el WDT reinicie el PIC es reiniciar el propio WDT poniéndolo de vuelta a 00

durante el programa. Ahora puedes ver que si tu programa se atasca por cualquier razón,

entonces el WDT no será puesto a 00 nunca. Eso hará que el WDT llegue a FF y reinicie tu

PIC, causando a nuestro programa que se reinicie desde el comienzo.

Para utilizar el WDT, tenemos que saber tres cosas:

La primera, cuanto tiempo tenemos antes de tener que hacer un reinicio al WDT.

Segundo, como lo ponemos a cero.

Finalmente, tenemos que decirle al software programador del PIC que habilite el WDT

dentro del PIC. 

Vamos a ver esto de manera separada:

Page 60: Introducción al PIC

Tiempos de WDT

La hoja de datos del PIC especifica que el WDT tiene un periodo desde su inicio hasta el final

de 18 ms. Esto depende de varios factores, como el voltaje aplicado, la temperatura del PIC,

etc... La razón de esta aproximación es debida a que el reloj del WDT es suministrado por una

red RC interna. El tiempo de carga de la red RC depende del voltaje de alimentación. También

depende de los valores de los componentes, los cuales cambian ligeramente dependiendo de

su temperatura. Así que por razones de simplicidad, tomaremos los 18 ms como el tiempo de

reinicio del WDT.

Sin embargo, podemos hacer este tiempo mayor. Dentro del PIC hay un elemento

llamado Prescaler [Nota de la Traducción: "Prescaler" se puede traducir como "etapa previa de

ajuste de escala"]. Podemos programar este prescaler para dividir el reloj interno de la red RC.

Cuanto mayor sea el factor de división, más tiempo tardará el WDT en reiniciarse.

El prescaler está localizado en el registro OPTION en la dirección 81h, los bit del 0 al 2

inclusive. Más abajo hay una tabla que muestra las asignaciones de los bits para cada ratio de

división y el tiempo de reinicio del WDT:

Bit 2 Bit 1 Bit 0 Ratio Tiempo de WDT

0 0 0 1:1 18ms

0 0 1 1:2 36ms

0 1 0 1:4 72ms

0 1 1 1:8 144ms

1 0 0 1:16 288ms

1 0 1 1:32 576ms

1 1 0 1:64 1.1 Segundos

1 1 1 1:128 2.3 Segundos

Recuerda que estos tiempos son independientes de la frecuencia de tu reloj externo. Piensa en

estos tiempos como en tiempo real, en lugar de como tiempos de reloj. Para ayudar a clarificar

esto, vamos a suponer que queremos que el WDT reinicie nuestro PIC después de cerca de

medio segundo como tiempo de seguridad ante fallo. El valor más próximo que tenemos es 576

ms o 0,576 segundos. Todo lo que hacemos es enviar b'101' a nuestro registro OPTION, tal y

como sigue:

Page 61: Introducción al PIC

movlw b’101’ ; Esto es 0x05 en hexadecimal.movwf 81h ; Este es el registro OPTION.

Realmente sencillo. Ahora, hay una trampa. Por defecto el prescaler está asignado a otro

temporizador interno [Nota de la Traducción: Se refiere al temporizador TMR0.]. Esto significa

que tenemos que cambiar el prescaler al WDT [Nota de la Traducción: Para asignar el

Prescaler al WDT hay que poner el bit 3 del registo OPTION a 1. Es decir, para poner el

prescaler a 576 ms como decíamos antes y asignarlo al WDT, Hay que enviar al registro

OPTION el valor b'1101]:

Primero, tenemos que reiniciar el otro contador (TMR0) y ponerlo a 0.

Después tenemos que cambiar al banco 1 para asignar el prescaler al WDT y

configurar el tiempo.

Y después volver al banco 0. 

El código esta aquí, donde xxx es el valor del prescaler:

bcf STATUS,0 ; Nos aseguramos de que estamos en el banco 0clrf 01h ; Dirección del otro temporizador – TMR0. Lo ponemos a 0.bsf STATUS,0 ; Cambiamos al banco 1switch to bank 1clrwdt ; reiniciamos el WDT y el ''prescaler''movlw b’1xxx’ ; Seleccionamos el valor del nuevo ''preescaler''(bits 0 al 2) ; y lo asignamos al WDT(ver bit 3 puesto a 1).movwf OPTION ; y se lo asignamos al WDTbcf STATUS,0 ; Vuelve al banco 0

La instrucción anterior CLRWDT es la que se utiliza para reiniciar el WDT antes de que este

reinicie al PIC. Así que todo lo que tenemos que hacer es calcular donde en nuestro programa

ocurrirá la finalización del tiempo del WDT, y enviar el comando CLRWDT justo antes de este

punto, para que nos aseguremos de que el PIC no se reinicia. Si tu programa es largo, ten en

cuenta que puede que necesites más de un CLRWDT. Por ejemplo, si utilizas el tiempo por

defecto 18 ms, entonces tenemos que asegurarnos de que el programa ve un CLRWDT cada

18 ms.

Así que ahora llegamos al punto donde tenemos que trabajar en cuanto tiempo tarda nuestro

código en ejecutarse, en tiempo real. El principio es muy simple, pero ¡puede que te tires de los

pelos!

Page 62: Introducción al PIC

Temporización de las instrucciones

Como probablemente ya sabrás, el PIC toma el reloj externo y lo divide por 4. Este tiempo

interno es llamado ciclo de instrucción. Como hemos dicho que conectamos un cristal de 4 Mhz

a nuestro PIC, internamente el PIC irá a 1Mhz. En términos de temporización, esto es

1/(4Mhz/4) = 1 µS. Entonces algunas instrucciones lleva ejecutarlas un solo ciclo de

instrucción, p. ej. 1 µS utilizando un cristal de 4Mhz, mientras que otras emplearán 2 µS en ser

ejecutadas.

La hoja de características nos dice cuantos ciclos lleva cada instrucción. La forma mas sencilla

para recordarlo es muy simple. Asume que todas las instrucciones tardan 1 ciclo. Pero si una

instrucción causa que el programa vaya a algún otro sitio, este se tomará 2 ciclos. Permite que

te darte un par de ejemplos.

La instrucción "movwf" tarda solo 1 ciclo, porque solo mueve un dato de un lugar a otro. La

instrucción "goto" tarda 2 ciclo, porque está causando que el contador de programa (PC) vaya a

otro sitio del programa. La instrucción RETURN tarda 2 ciclos, porque causa que el PC vuelva

al programa principal. Al menos creemos que con esto ya puedes ver por donde va el tema.

Sin embargo, hay cuatro instrucciones que pueden tardar 1 ó 2 ciclos. Estas son DECFSZ,

INCFSZ, BTFSC y BTFSS. Estas instrucciones tienen una cosa en común. Se saltan la

siguiente instrucción en caso si se cumple cierta condición. Si no se cumple la condición,

entonces se lleva a cabo la siguiente instrucción. Por ejemplo, la instrucción DECFSZ

decrementará en 1 el valor almacenado en el registro F. Si el resultado es 0, entonces la

siguiente instrucción será ejecutada. Esta instrucción por tanto tarda 1 ciclo. Si el resultado es

0, entonces la instrucción siguiente se la salta, y la siguiente a la anterior será ejecutada. En

este ejemplo la instrucción tarda 2 ciclos. La razón es que la instrucción altera el PC. Necesita

1 ciclo para ejecutar la función, y necesita otro para cambiar el PC por uno mas extra.

Para aclarar esto, vamos a mirar un código simple, y trabajaremos sobre los ciclos de

instrucción que tarda:

; movlw 02 movwf CONTADORBucle decfsz CONTADOR goto Bucle end

Nuestra primera instrucción simplemente mueve el valor 02 a W. Esto no causa ningún salto,

por tanto es solo 1 ciclo. La siguiente instrucción es similar, mueve los contenidos del registro

W a CONTADOR. De nuevo, esto tardará 1 ciclo. Ahora, la siguiente instrucción primero

Page 63: Introducción al PIC

decrementa CONTADOR en 1. Esto es 1 ciclo. Después hará una comprobación para ver que

CONTADOR es igual a 0. En este momento no lo es, por tanto vamos a la siguiente instrucción.

La siguiente instrucción es una de "goto", y por tanto tarda 2 ciclos. Volvemos a nuestra

instrucción DECFSZ, la que decrementa CONTADOR en 1 de nuevo. Esto tarda otro ciclo.

Hace una comprobación para ver si CONTADOR es igual a 0. Esta vez lo es, y por tanto se

salta la siguiente instrucción. Para saltarse la siguiente instrucción se requiere otro ciclo.

Alcanzamos el final del programa. Así que en total, con el valor de 02 para CONTADOR, este

programa tarda 7 ciclos en total. Si estamos usando un cristal de 4MHz para nuestro reloj,

entonces el programa tarda:

1/(4MHz/4) = 1 µS por ciclo , por tanto 7 ciclos son 7 x 1 µS = 7 µS.

Software Programador

Dentro del PIC hay elementos llamados "Fusibles" [Nota de Traducción: En ingles "Fuses"]. No

son los mismos que puedes encontrar en los enchufes, sino que son conmutadores

electrónicos que se pueden "fundir" por el programador. Uno de estos fusibles tiene que ser

'fundido' para que el WDT pueda operar. Hay dos formas de hacerlo. Una es escribiendo un par

de lineas al comienzo de tu programa para decirle al software programador del PIC que habilite

o deshabilite ciertos "fusibles". La otra forma de hacerlo es decirle al software programador del

PIC manualmente que fusibles habilitar. Echaremos un vistazo a nuestro programa para instruir

al software programador del capítulo pasado, cuando veamos como incluir otros ficheros y

macros. El cómo hacerlo manualmente varía dependiendo del software de programación. La

documentación que viene con el programador suele decir como hacerlo. Como estoy utilizando

el [software PICALLW], explicaremos como cambiar los "fusibles" con este programa:

Los fusibles se configuran pulsando la tecla F3, o haciendo 'click' sobre el botón de

configuración. Después seleccionas el fusible que quieres habilitado, en ese caso el WDT,

haciendo una marca en la caja que está junto a él.

Programa de ejemplo

Vamos a escribir un programa, donde modifiquemos el WDT, y permitamos al PIC ejecutar una

función. Primero borraremos el WDT periódicamente para mostrar que el programa funciona, y

después quietaremos la instrucción CLRWDT para mostrar que efectivamente el PIC se

reinicia.

El programa que hemos elegido es el utilizado en el capítulo Operadores Aritméticos y

Lógicos donde hacíamos que una fila de LEDs se encendiesen a un tiempo de izquierda a

Page 64: Introducción al PIC

derecha y de derecha a izquierda. El circuito se muestra más abajo, y con los valores RC que

mostramos le daremos una frecuencia de reloj de 8KHz. Esta velocidad de reloj nos permitirá

realmente ver los LEDs moviéndose uno a uno. Elegimos este programa porque es

suficientemente lento para que podamos jugar con el WDT, y que puedas ver fácilmente como

se reinicia el PIC. Hemos quitado los comentarios originales, y los hemos sustituido por una

descripción de las lineas del WDT, y en cada linea del tiempo total desde el inicio (asumiendo

8KHz), y el número total de ciclos de reloj de cada linea.

TIEMPO equ 9FH ; Variable para el bucle de retardo. PORTB equ 06H ; Dirección del Port B. TRISB equ 86H ; Dirección del registro tri-estado del Port B. PORTA equ 05H ; Dirección del Port A. TRISA equ 85H ; Dirección del registro tri-estado del Port A. STATUS equ 03H ; Registro para seleccionar el banco. CONTADOR1 equ 0CH ; Registro para el

Page 65: Introducción al PIC

bucle. CONTADOR2 equ 0DH ; Registro para el bucle. bsf STATUS,5 ; 1 ciclo, 0.5mS movlw 00H ; 1 ciclo, 1.0mS movwf TRISB ; 1 ciclo, 1.5mS movlw 00H ; 1 ciclo, 2.0mS movwf TRISA ; 1 ciclo, 2.5mS bcf STATUS,5 ; 1 ciclo, 3.0mS movlw 00H ; 1 ciclo, 3.5mS movwf PORTA ; 1 ciclo, 4.0mS; Comienzo del programa principalCorreLuz movlw 01H ; 1 ciclo, 4.5mS movwf PORTB ; 1 ciclo, 5.0mS call RETARDO ; 2 ciclos, 486mS call RETARDO ; 2 ciclos, 967mS; Mueve el bit por la izquierada del puerto B, después pausa. rlf PORTB,1 ; 1 ciclo, 967.5mS call RETARDO ; 2 ciclos, 1.45S call RETARDO ; 2 ciclos, 1.93S rlf PORTB,1 ; 1 ciclo, 1.93S call RETARDO ; 2 ciclos, 2.41S call RETARDO ; 2 ciclos, 2.89S rlf PORTB,1 ; 1 ciclo, 2.89S call RETARDO ; 2 ciclos, 3.37S call RETARDO ; 2 ciclos, 3.85S rlf PORTB,1 ; 1 ciclo, 3.85S call RETARDO ; 2 ciclos, 4.34S call RETARDO ; 2 ciclos, 4.82S rlf PORTB,1 ; 1 ciclo, 4.82S call RETARDO ; 2 ciclos, 5.30S call RETARDO ; 2 ciclos, 5.78S rlf PORTB,1 ; 1 ciclo, 5.78S call RETARDO ; 2 ciclos, 6.26S call RETARDO ; 2 ciclos, 6.74S rlf PORTB,1 ; 1 ciclo, 6.74S call RETARDO ; 2 ciclos, 7.22S call RETARDO ; 2 ciclos, 7.70S rlf PORTB,1 ; 1 ciclo, 7.70S; Ahora mueve lo al puerto A, al bit de la izquierda. rlf PORTA,1 ; 1 ciclo, 7.70S call RETARDO ; 2 ciclos, 8.19S call RETARDO ; 2 ciclos, 8.67S rlf PORTA,1 ; 1 ciclo, 8.67S call RETARDO ; 2 ciclos, 9.15S call RETARDO ; 2 ciclos, 9.63S rlf PORTA,1 ; 1 ciclo, 9.63S call RETARDO ; 2 ciclos,10.11S call RETARDO ; 2 ciclos,10.59S rlf PORTA,1 ; 1 ciclo, 10.59S

Page 66: Introducción al PIC

call RETARDO ; 2 ciclos,11.07S call RETARDO ; 2 ciclos,11.55S ; Mueve el bit de vuelta al puerto A. rrf PORTA,1 ; 1 ciclo, 11.55S call RETARDO ; 2 ciclos,12.04S call RETARDO ; 2 ciclos,12.52S rrf PORTA,1 ; 1 ciclo, 12.52S call RETARDO ; 2 ciclos,12.99S call RETARDO ; 2 ciclos,13.48S rrf PORTA,1 ; 1 ciclo, 13.48S call RETARDO ; 2 ciclos,13.96S call RETARDO ; 2 ciclos,14.44S rrf PORTA,1 ; 1 ciclo, 14.44S ; Ahora mueve el bit de vuelta al puerto B. rrf PORTB,1 ; 1 ciclo, 14.44S call RETARDO ; 2 ciclos,14.92S call RETARDO ; 2 ciclos,15.40S rrf PORTB,1 ; 1 ciclo, 15.40S call RETARDO ; 2 ciclos,15.89S call RETARDO ; 2 ciclos,16.37S rrf PORTB,1 ; 1 ciclo, 16.37S call RETARDO ; 2 ciclos,16.84S call RETARDO ; 2 ciclos,17.33S rrf PORTB,1 ; 1 ciclo, 17.33S call RETARDO ; 2 ciclos,17.81S call RETARDO ; 2 ciclos,18.29S rrf PORTB,1 ; 1 ciclo, 18.29S call RETARDO ; 2 ciclos,18.77S call RETARDO ; 2 ciclos,19.25S rrf PORTB,1 ; 1 ciclo, 19.25S call RETARDO ; 2 ciclos,19.73S call RETARDO ; 2 ciclos,20.22S rrf PORTB,1 ; 1 ciclo, 20.22S call RETARDO ; 2 ciclos,20.70S call RETARDO ; 2 ciclos,21.18S; goto CorreLuz ; 2 ciclos,21.18S; Subrutina para introducir un retardo entre los movimientos de los bits. ; Ciclos totales 957, 480mS RETARDO movlw TIEMPO ; 1 ciclo movwf CONTADOR1 ; 1 cicloBUCLE1 ; decfsz CONTADOR1 ; 9F x 1 ciclo + 1 ciclo = 160 ciclos goto BUCLE1 ; 9E x 2 ciclos = 316 ciclos movwf CONTADOR2 ; 1 cicloBUCLE2 ;

Page 67: Introducción al PIC

decfsz CONTADOR2 ; 9F x 1 ciclo + 1 ciclo = 256 ciclos goto BUCLE2 ; 9E x 2 ciclos = 316 ciclos; return ; 2 ciclos END ;

Con un reloj de 8KHz, tarda algo menos de 1 segundo en que el siguiente LED se ilumine, y

tarda en total 21 segundos en ir de un extremo al otro y volver (es decir, lo que tarda le rutina

en ejecutarse una vez solamente). El retardo de la subrutina es de 480ms, y la estamos

llamando dos veces antes de mover el bit por los puertos. Ahora, tenemos que hacer el reinicio

periódico del WDT. El mayor tiempo que podemos configurar para el WDT es de 2,3 segundos,

y el siguiente en la tabla es de 1,1 segundos. Tenemos dos opciones. Podríamos hacer una

llamada a la subrutina y reiniciar el WDT después de que los dos Retardos hayan terminado, o

podríamos incorporar el CLRWDT dentro del retardo mismo. Hemos decidido, sin ninguna

razón importante, poner el CLRWDT dentro de la subrutina de retardo.

TIEMPO equ 9FH ; Variable para el bucle de retardo. PORTB equ 06H ; Dirección del Port B. TRISB equ 86H ; Dirección del registro tri-estado del Port B. PORTA equ 05H ; Dirección del Port A. TRISA equ 85H ; Dirección del registro tri-estado del Port A. STATUS equ 03H ; Registro para seleccionar el banco. CONTADOR1 equ 0CH ; Registro para el bucle. CONTADOR2 equ 0DH ; Registro para el bucle. OPT equ 81h ; Registro Option para controlar el WDT ;************* Configura los puertos, el WDT y el preescaler****************** clrf 01h ; Pone a cero el TMR0 bsf STATUS,5 ; Cambia al banco 1 clrwdt ; reinicia el WDT y el prescaler movlw b’1101’ ; Selecciona un nuevo valor para el prescaler y

Page 68: Introducción al PIC

movwf OPT ; se lo asigna al WDT; movlw 00H ; Ahora configura los puertos movwf TRISB ; movlw 00H ; movwf TRISA ; bcf STATUS,5 ; Vuelve al banco 0 movlw 00H ; movwf PORTA ; ;************* Comienzo del programa principal ***************************** CorreLuz movlw 01H ; movwf PORTB ; call RETARDO ; call RETARDO ; ; Mueve el bit por la izquierada del puerto B, después pausa. rlf PORTB,1 ; call RETARDO ; call RETARDO ; rlf PORTB,1 ; call RETARDO ; call RETARDO ; rlf PORTB,1 ; call RETARDO ; call RETARDO ; rlf PORTB,1 ; call RETARDO ; call RETARDO ; rlf PORTB,1 ; call RETARDO ; call RETARDO ; rlf PORTB,1 ; call RETARDO ; call RETARDO ; rlf PORTB,1 ; call RETARDO ; call RETARDO ; rlf PORTB,1 ;; Ahora mueve lo al puerto A, al bit de la izquierda. rlf PORTA,1 ; call RETARDO ; call RETARDO ; rlf PORTA,1 ; call RETARDO ; call RETARDO ; rlf PORTA,1 ; call RETARDO ; call RETARDO ; rlf PORTA,1 ;

Page 69: Introducción al PIC

call RETARDO ; call RETARDO ; ; Mueve el bit de vuelta al puerto A. rrf PORTA,1 ; call RETARDO ; call RETARDO ; rrf PORTA,1 ; call RETARDO ; call RETARDO ; rrf PORTA,1 ; call RETARDO ; call RETARDO ; rrf PORTA,1 ; ; Ahora mueve el bit de vuelta al puerto B. rrf PORTB,1 ; call RETARDO ; call RETARDO ; rrf PORTB,1 ; call RETARDO ; call RETARDO ; rrf PORTB,1 ; call RETARDO ; call RETARDO ; rrf PORTB,1 ; call RETARDO ; call RETARDO ; rrf PORTB,1 ; call RETARDO ; call RETARDO ; rrf PORTB,1 ; call RETARDO ; call RETARDO ; rrf PORTB,1 ; call RETARDO ; call RETARDO ; ; goto CorreLuz ; ; Subrutina para introducir un retardo entre los movimientos de los bits. RETARDO

movlw TIEMPO ; 1 ciclo

movwf CONTADOR1 ; 1 ciclo

BUCLE1 ;

decfsz CONTADOR1 ; 9F x 1 ciclo + 1 ciclo = 160 ciclos goto BUCLE1 ; 9E x 2 ciclos = 316 ciclos

Page 70: Introducción al PIC

movwf CONTADOR2 ; 1 ciclo

BUCLE2 ;

decfsz CONTADOR2 ; 9F x 1 ciclo + 1 ciclo = 256 ciclos goto BUCLE2 ; 9E x 2 ciclos = 316 ciclos ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Esta parte reinicia el WDT ;;;; Quita o comenta este comando para ver que hace el WDT. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; clrwdt ; Esto simplemente reinicia el WDT.; *************** Retorna desde nuestra rutina Retado*************** return ; ; END ;

Si comentas o quitas la instrucción CLRWDT, verás que el PIC no pasa de iluminar el segundo

LED. Esto es debido a que el WDT hace que se reinicie el PIC. Con el CLRWDT en su sitio, el

programa funcionará como debe.

Page 71: Introducción al PIC

Introducción

Programando en CCS.

La programación seria prácticamente imposible sin el uso de variables. Podemos hacernos una

imagen mental de las variables consistente en una caja en la que podemos guardar algo. Esa

caja es una de las muchas que disponemos, y tiene en su frente pegada una etiqueta con su

nombre. Estas cajas tienen ciertas particularidades, que hace que solo se puedan guardar en

ellas determinados tipos de objetos.

Tabla de contenidos

 [esconder]

1   Introducción

2   Tipos

3   Declaración

4   Asignación de valores

Page 72: Introducción al PIC

5   Varibles Locales y

Globales

6   Conversiones entre

tipos

7   Temas relacionados

8   Autor

En esta analogía, cada caja es una variable, su contenido es el valor que adopta, y la etiqueta

es el nombre de la variable. Como su nombre lo indica, y como veremos mas adelante, el

contenido de una variable puede ser modificado a lo largo del programa.

[editar]Tipos

El lenguaje C proporciona cinco tipos básico de datos, con cuatro modificadores posibles.

Podemos utilizar variables de cualquiera de esos tipos. La tabla siguiente muestra los tipos

disponibles:

Tipo Ancho (Bits) Rango

short 1 0 o 1

short int 1 0 o 1

int 8 0 a 255

char 8 0 a 255

unsigned 8 0 a 255

unsigned int 8 0 a 255

signed 8 -128 a 127

signed int 8 -128 a 127

long 16 0 a 65536

Page 73: Introducción al PIC

long int 16 0 a 65536

signed long 16 -32768 a 32767

float 32 3.4E-38 a 3.4E+38

Si miras con atención la tabla anterior, puedes ver que hay tipos que parecen estar repetidos.

En realidad, ocurre que CCS permite una "forma corta" para escribir algunos de los tipos.

Concretamente, podemos utilizar unsigned, short, o long en lugar de unsigned int, short int,

o long int.

[editar]Declaración

Las variables deben ser declaradas antes de ser utilizadas en el programa. El proceso de

declaración de variables le dice a CCS de que tipo son y como se llaman. Al igual las demas

instrucciones CCS que veremos a lo largo de este tutorial, debe terminar con ;.

La forma en que se declara una variable es la siguiente:

tipo nombre_de_la_variable;

donde tipo es alguno de los enumerados en la tabla anterior. Veamos un ejemplo:

int temperatura;

Esa linea permite a nuestro programa emplear la variable temperatura, que sera capaz de

albergar cualquier valor comprendido entre 0 y 255.

[ editar]Asignación de valores

Asignar un valor a una variable es una tarea bien simple. Basta con hacer lo siguiente:

nombre_de_variable = valor;

donde nombre_de_variable es el nombre de la variable que contendra el valor. Al igual que

todas las instrucciones de CCS, debe terminar con un ; (punto y coma).

Page 74: Introducción al PIC

Por ejemplo, supongamos que queremos asignar el valor "100" a la variable "count". Lo

hacemos de la siguiente manera:

count = 100;

donde 100 es una constante.

Podemos asignar un valor a una variable en el momento en que la declaramos. lo siguientes

son algunos ejemplos de esto:

int a = 0;

Hace que la variable a sea del tipo entero, y le asigna el valor 0.

signed long a = 125, b, c = -10;

a,b y c son declaradas como long. a toma el valor de "125" y c "-10".

Si la variable es de tipo char, la constante que se le asigna debe estar entre tildes, como en el

siguiente ejemplo:

char nombre = 'juan perez';

Por ultimo, tambien podemo asignar a una variable el contenido de otra. En el siguiente

ejemplo, el valor de i sera igual a 10.

int i = 10;int j;j = 1;

[ editar]Varibles Locales y Globales

Si una variable se declara dento de una funcion, será "visible" solo dentro de ésta:

funcion1 () { char letra; . . . . }

Page 75: Introducción al PIC

En el ejemplo anterior, la variable tipo char llamada letra solo podra utilizarse dentro de

la funcion funcion1(). Si intentamos utilizarla fuera de ella, el compilador nos dará un error.

Si declaramos una variable fuera de cualquier funcion, el alcance de esta sera global, lo que

quiere decir que estará disponible en cualquier parte de nuestro programa. Vemos un ejemplo

de este último caso.

char letra;main() { . . . .}funcion1() { . . .}

La variable tipo char llamada letra podrá utilizarse dentro de main() o de funcion1().

[ editar]Conversiones entre tipos

CCS nos permite mezclar diferentes tipos de variables dentro de una misma expresión. Y

existen un conjunto de reglas que nos permiten saber que de que tipo será el resultado de la

misma.

Por ejemplo, el compilador convertirá automaticamente a int cualquier expresión que contenga

variables char, short o int. Esta conversión solo tiene efecto mientras se realizan los cálculos.

Las variables en sí mismas no cambian su tipo.

Las reglas de conversión de tipos hacen que el resultado de una operación sea siempre el

mismo que el de la variable más larga que intervenga en ella.

Sin embargo, podemos forzar a que el resultado sea de un tipo en particular, de la siguiente

forma:

(tipo) valor

donde tipo es el tipo al que queremos que pertenezca valor. El siguiente ejemplo nos aclarará

todo esto:

Page 76: Introducción al PIC

int a = 250, b = 10;long c;c = a * b;

Tal como explicamos, c no contendrá el valor 2500 como podría paracer a simple vista, por

que el tipo de c no se modica. CCScalcula a * b' y obtiene efectivamente el resultado 2500,

pero c sólo contendrá los 8 bits menos significativos de ese resultado, es decir, el decimal 196.

Si hubiesemos hecho: int a = 250, b = 10;

long c;c = (long) (a * b);

el valor almacenado en c hubiese sido efectivamente 2500.

Introducción

Programando en CCS.

Llamadas en inglés "preprocessor directives", son comandos que interpreta el primer paso de la

compilacíon que lleva a cabo CCS.

Las directivas más comunes son #define e #include, pero deberías dar un vistazo a todas.

Tabla de contenidos

 [esconder]

1   Introducción

2   #ASM /

#ENDASM

3   #BIT

4   #BYTE

5   #DEFINE

6   #DEVICE

7   #FUSE

Page 77: Introducción al PIC

8   #INCLUDE

9   #INT_xxx

10   Temas

relacionados

11   Autor

[editar]#ASM / #ENDASM

Este par de instrucciones permite que utilicemos un bloque de instrucciones

en assembler dentro de nuestro código CCS. El siguiente es un ejemplo de uso tomado de la

ayuda del CCS:

int find_parity (int data) { int count; #ASM movlw 0x8 movwf count movlw 0 loop: xorwf data,w rrf data,f decfsz count,f goto loop movlw 1 awdwf count,f movwf _return_ #ENDASM}

La variable predefinida _RETURN_ puede utilzarse para transferir un valor desde el

código ASM a CCS.

Si en lugar de #ASM utilizamos #ASM ASIS, CCS no intentará efectuar cambios de bancos de

memória automaticos para las variables que no pueden ser accedidas desde el banco actual. El

codigo assembler es utilizado "as-is" ("como es").

[editar]#BIT

Permite crear una nueva variable de un bit de tamaño, que es colocada en la memoria

del PIC en la posición del byte x y el bit y. Esto es muy útil para acceder de una manera

Page 78: Introducción al PIC

sencilla a los registros. Por supuesto, estas variables puedem ser empleadas de la misma

manera que cualquier otra variable tipo short. El formato de #BIT es el siguiente:

#BIT nombre = x.y

donde nombre es un nombre de variable CCS válido, x es una constante o una

variable CCS válida e y es una constante de 0 a 7.

Estos son alguno ejemplos de uso:

#BIT T0IF = 0xb.2 ...T0IF = 0; // Limpia el flag de interrupción del Timer 0

int resultado;#BIT resultado_primer_bit = resultado.0 ...if (resultado_primer_bit)

[editar]#BYTE

Permite crear una nueva variable de un Byte de tamaño, que es colocada en la memoria

del PIC en la posición del byte x. Esta es una herramienta muy útil para acceder de una

manera sencilla a los registros. Por supuesto, estas variables puedem ser empleadas de la

misma manera que cualquier otra variable tipo int. El formato de #BYTE es el siguiente:

#BYTE nombre = x

donde nombre es un nombre de variable CCS válido, y x es una constante o una

variable CCS válida.

Estos son alguno ejemplos de uso:

#BYTE STATUS = 3#BYTE PORTB = 6

Page 79: Introducción al PIC

[editar]#DEFINE

La instrucción #define tiene la siguiente forma:

#DEFINE <label> value

<label> es la etiqueta que usaremos en nuestro programa. Y value es el valor que estamos

asignando a esta etiqueta. Las instrucciones #DEFINE no generan codigo ASM, si no que el

preprocesador realiza los reemplazos que ellas indican en el momento de la compilación. El

uso de #DEFINE permite construir programas más ordenados y faciles de mantener.

Veamos algunos ejemplos de #DEFINE

#DEFINE TRUE 1

Cada vez que en nuestro programa aparezca la etiqueta TRUE, el precompilador la

reemplazará por 1

#DEFINE pi 3.14159265359

Cada vez que en nuestro programa aparezca la etiqueta pi, el precompilador la reemplazará

por 3.14159265359

#DEFINE MENOR_DE_EDAD (EDAD < 18).....if MENOR_DE_EDAD printf(“JOVEN”);

El ejemplo anterior permite una mayor claridad en el programa. Por supuesto, no hay que

abusar de #DEFINE, por que podemos obtener el efecto contrario, haciendo nuestros

programas bastante dificiles de comprender.

#DEFINE es una potente herramienta para la creación de macroinstrucciones, ya que soporta

el uso de variables. Veamos algunos ejemplos de esto:

#DEFINE var(x,v) unsigned int x=v;var(a,1)var(b,2)

Page 80: Introducción al PIC

var(c,3)

Cuando el preprocesador se encuentra con el código anterior, hace lo mismo que si

hubiesemos escrito lo siguiente:

unsigned int a=1;unsigned int b=2;unsigned int c=3;

Como puedes ver, #DEFINE puede hacer mucho por tus programas.

[editar]#DEVICE

Esta directiva informa al compilador que arquitectura de hardware utilizaremos, para que pueda

generar código apropiado para la cantidad de RAM, ROM y juego de instrucciones disponibles.

Para los chips con más de 256 bytes de RAM se puede seleccionar entre emplear punteros de

8 o 16 bits. Si deseamos emplear punteros de 16 bits basta con añadir *=16 a continuación del

nombre microcontroladorseleccionado.

Veamos algunos ejemplos:

#DEVICE PIC16C74 //PIC 16C74, punteros de 8 bits.#DEVICE PIC16C67 *=16 //PIC 16C67, punteros de 16 bits.

Hay más opciones que podemos agregar en las lineas #DEVICE:

ADC=x : Determina el número de [bit]]s que devuelve la función read_adc().

#DEVICE PIC16F877 *=16 ADC=10 //PIC 1616F877, punteros de 16 bits y 10 bits en el ADC.

ICD=TRUE : Genera código compatible con el ICD de [www.microchip.com

Microchips]].

#DEVICE PIC16F877 ICD=TRUE//PIC 1616F877, punteros de 8 bits y código para ICD.

WRITE_EEPROM=ASYNC :

HIGH_INTS=TRUE : Define la prioridad de las interrupciones en los PIC18.

Page 81: Introducción al PIC

[editar]#FUSE

Permite modificar el valor de los fuses del microcontrolador que estamos empleando. Los

valores posibles dependen de cadamicrocontrolador en particular, y los valores posibles se

cargan al utilizar #ICNLUDE seguido del archivo correspondiente. La forma de#FUSE es la

siguiente:

#FUSE opciones

donde opciones es una lista de las opciones posibles separadas mediante comas. Antes de

seguir, recuerda que puedes ver dentro del archivo con extensión .h correspondiente cuales

son los valores posibles para ese microcontrolador. Están al comienzo del archivo, en forma de

comentarios.

Algunos valores comunes son

Tipo de oscilador: LP, XT, HS, RC

Wach Dog Timer: WDT, NOWDT

Protección de código: PROTECT, NOPROTECT

Power Up Timer: PUT, NOPUT

Brown Out Reset: BROWNOUT, NOBROWNOUT

[editar]#INCLUDE

Permite incluir en nuestro programa uno o mas archivos (conocidos como header file) que

posean extensión .h. Estos archivos contienen información sobre funciones, sus argumentos, el

nombre de los pines de un modelo determinado de PIC o cualquier otra cosa que usemos

habitualmente en nuestros programas. Esto permite no tener que escribir un montón de cosas

cada vez que comenzamos un programa nuevo: basta con incluir el .h correspondiente.

La forma de utilizar esta instrucción es la siguiente:

#INCLUDE <archivo>

Esto hará que el contenido de <archivo> se compile junto con nuestro programa. Por ejemplo:

#INCLUDE <PIC16F877A.H>

Page 82: Introducción al PIC

hace que todas las especificaciones de nombres y registros del PIC16F877A se incluyan en

nuestro programa. Esto permitirá referirnos al pin 0 del PORTB del PIC mediante PIN_B0.

Existe la posibilidad de utilizar #INCLUDE "archivo" en lugar de #INCLUDE <archivo>. La

diferencia es que si usamos "", el archivose buscará primero en el directorio actual. Si

empleamos <>, el archivo será buscado primero en la ruta por defecto para los archivos.h.

[editar]#INT_xxx

#INT_xxxindica que la función que le sigue (en el código fuente CCS) es una función de

interrupción. Estas funciones no deben tener parámetros. Por supuesto, no todos

los PICs soportan todas las directivas disponibles:

1. INT_AD Conversión A/D finalizada.

2. I NT_ADOF Conversión A/D timeout.

3. INT_BUSCOL Colisión en bus.

4. INT_BUTTON Pushbutton.

5. INT_CCP1 Unidad CCP1.

6. INT_CCP2 Unidad CCP2.

7. INT_COMP Comparador.

8. INT_EEPROM Escritura finalizada.

9. INT_EXT Interrupción externa.

10. INT_EXT1 Interrupción externa #1.

11. INT_EXT2 Interrupción externa #2.

12. INT_I2C Interrupción por I2C.

13. INT_LCD Actividad en el LCD.

14. INT_LOWVOLT Bajo voltaje detectado.

15. INT_PSP Ingreso de datos en el Parallel Slave Port.

16. INT_RB Cambios en el port B (B4-B7).

17. INT_RC Cambios en el port C (C4-C7).

18. INT_RDA Datos disponibles en RS-232.

Page 83: Introducción al PIC

19. INT_RTCC Desbordamiento del Timer 0 (RTCC).

20. INT_SSP Actividad en SPI o I2C.

21. INT_TBE Buffer de transmisión RS-232 vacío.

22. INT_TIMER0 Desbordamiento del Timer 0 (RTCC).

23. INT_TIMER1 Desbordamiento del Timer 1.

24. INT_TIMER2 Desbordamiento del Timer 2.

25. INT_TIMER3 Desbordamiento del Timer 3.

Ejemplo:

#int_adadc_handler() { adc_active=FALSE;}#int_rtcc noclear //"noclear" evita que se borre el flag correspondiente.isr() { ...}

En CCS los operadores cumplen un rol importante. Quizas C sea uno de los lenguajes que

mas operadores tiene. Una expresión es una combinacion deoperadores y operandos. En la

mayoría de los casos, los operadores de CCSsiguen las mismas reglas que en álgebra, y se

llaman de la misma manera.

Tabla de contenidos

 [esconder]

1   Introducción

2   Operadores aritméticos

o 2.1   Atajos

3   Operadores Relacionales

4   Operadores Lógicos

5   Operadores de bits

Page 84: Introducción al PIC

o 5.1   Atajos

6   Otros operadores

7   Precedencia de los

operadores

8   Temas relacionados

9   Autor

[editar]Operadores aritméticos

CCS posee cinco operadores aritméticos:

+ (suma)

- (substracción)

* (multiplicación)

/ (división)

% (módulo)

Los primeros cuatro operadores mencionados se pueden utilizar con cualquier tipo de dato.

Estos son algunos ejemplo de como usarlos:

a = b + c;a = b - c;a = b * c;a = b / c;a = -a; //Cambia el signo de "a".a = a + 1; //suma 1 al valor de "a".

El operador % (módulo) solo puede emplearse con enteros. Devuelve el resto de una división

de enteros. Veamos un par de ejemplos:

int a = 10, b = 5, c;c = a % b; //"c" valdrá cero.int a = 20, b = 3, c;c = a % b; //"c" valdrá 2.

[editar]Atajos

Page 85: Introducción al PIC

CCS también provee atajos para utilizar los operadores aritméticos. Hay algunas operaciones

que se repiten a menudo cuando creamos nuestros programas, y estos atajos ayudan a que

podamos escribir nuestro código más rapidamente. Los atajos provistos son los siguientes.

a *= b es lo mismo que a = a * b

a /= b es lo mismo que a = a / b

a += b es lo mismo que a = a + b

a -= b es lo mismo que a = a - b

a %= b es lo mismo que a = a * b

[editar]Operadores Relacionales

Los operadores relacionales comparan dos valores, y devuelven un valor lógico basado en el

resultado de la comparación. Los operadores relacionales disponibles son los siguientes:

> mayor que

>= mayor que o igual a

< menor que

<= menor que o igual a

== igual a

!= distinto de

el resultado de la comparación, sera siempre 0 o 1. 0 significa que el resultado de la

comparación ha sido falso, y 1 que ha sidoverdadero.

[ editar]Operadores Lógicos

Los operadores lógicos disponibles permiten realizar las operaciones AND, OR y NOT:

p && q significa p AND q

p || q significa P OR q

!p significa NOT p

Page 86: Introducción al PIC

Por supuesto, puede emplearse más de un operador lógico en la misma expresión:

a = b && ( q || n )

Y se pueden comninar con los demas operadores vistos:

a = !(maximo <= 100) //a sera 1 si maximo es mayor que 100.

[ editar]Operadores de bits

Existen seis operadores pensados para trabajar directamente sobre los bits. Solamente pueden

usarse con variables tipo int y char. Son los siguientes:

&  (AND)

|  (OR)

 (XOR)

~  (complemento)

<< (desplazamiento a la izquierda)

>> (desplazamiento a la derecha)

Estas operaciones se llevan a cabo bit por bit. Veamos un ejemplo:

Supongamos que a = 120 y b = 13.

a & b = 8

a | b = 125

a ^ b = 117

~ a = 135

El porqué de estos resultados puede comprenderse mejor si se pasan los valores de a y b a

binario:

a = 11111000b = 00001101

luego

01111000 AND 00001101 = 00001000

Page 87: Introducción al PIC

01111000 OR 00001101 = 0111110101111000 XOR 00001101 = 01110101NOT 01111000 = 10000111

Los operadores de desplazamiento "corren" el contenido de la variable a la derecha o a la

izquierda, rellenando con ceros. Veamos algunos ejemplos:

a = a >> 2 //"corre" el contenido de a dos lugares a la derecha

Si a era igual a 120 ( 01111000 en binario) pasará a valer 30 (00011110 en binario).

a = a << 3 //"corre" el contenido de a cinco lugares a la izquierda

Si a era igual a 120 ( 01111000 en binario) pasará a valer 192 (11000000 en binario).

[ editar]Atajos

CCS también provee atajos para utilizar los operadores de bits. Hay algunas operaciones que

se repiten a menudo cuando creamos nuestros programas, y estos atajos ayudan a que

podamos escribir nuestro código más rapidamente. Los atajos provistos son los siguientes.

a <<= b es lo mismo que a = a << b

a >>= b es lo mismo que a = a >> b

a &= b es lo mismo que a = a & b

a |= b es lo mismo que a = a | b

a ^= b es lo mismo que a = a ^ b

[ editar]Otros operadores

Quedan por ver aun dos operadores más:

++ Operador incremento

-- Operador decremento

Estos operadores permiten sumar (o restar) uno al valor de una variable. Lo que generalmente

hariamos asi:

Page 88: Introducción al PIC

a = a + 1

0 asi:

a = a - 1

lo podemos hacer asi:

a++

o asi:

a--

el resultado sera el mismo, pero es más corto de escribir, y más fácil de utilizar en expresiones

complejas.

[ editar]Precedencia de los operadores

Al igual que ocurre en álgebra, en CCS los operadores se evalúan en un orden determinado. La

siguiente lista muestra este orden, ordenado de mayor a menor:

()

signo +, signo -, ++, --, !, (<tipo>)

*, /, %

+, -

<, <=, >, >=

==, !=

&&, ||

=, +=, -=, *=, /=, %=

Page 89: Introducción al PIC

CCS - Punteros

[editar]Introducción

Programando en CCS.

Una de las caracteristicas mas interesantes de las diferentes versiones de Cson los punteros. Por supuesto, CCS permite el manejo de punteros, con

lo que nuestros progamas pueden aprovechar toda la potencia de esta herramienta.

El presente artículo fue escrito por Pedro (PalitroqueZ), un amigo de uControl. Su dirección de correo elecrónico es [email protected]

Tabla de contenidos

 [esconder]

1   Introducción

2   ¿Qué es un puntero?

3   ¿Para que pueden servir los punteros?

4   ¿Como funcionan los punteros?

5   ¿Como podemos acceder a la dirección de una

variable?

6   Probando punteros, primera parte

7   Probando punteros, segunda parte

8   Punteros en funciones

9   Punteros en Arrays

o 9.1   Arreglando el programa con *(p+t)

10   Temas relacionados

11   Autor

[editar]¿Qué es un puntero?

Un puntero es una variable cuya finalidad es almacenar números ENTEROS POSITIVOS. Estos números no son números al azar, son direcciones de

la memoria que posee el hardware del microcontrolador (memoria de programa o RAM).

Page 90: Introducción al PIC

[editar]¿Para que pueden servir los punteros?

Esta es la pregunta que puede alborotar a mas de un programador de C. Sirve para muchísimas cosas:

Acceso a la memoria RAM del PIC.

Ahorrar memoria RAM.

Modificar más de una variable dentro de una función (y por consiguiente devolver mas de un valor)

En arreglos y cadenas strings (arrays, matrices) juega un papel importantísimo.

Permite crear tablas con montones de datos (en los PIC que soporten acceso a la memoria de programa).

En un ordenador se amplía el abanico de opciones.

Más abajo veremos detalladamente como hacer todo esto.

[editar]¿Como funcionan los punteros?

Para entender el uso de estas variables especiales hay que comprender bien un concepto:

Cuando se crea una variable en CCS (llamado registro en ensamblador), el compilador reserva un espacio de memoria cuyo tamaño varia de acuerdo

al tipo de dato.

Como todo en el mundo electrónico/digital, está basado en 2 cosas:

El registro: es la casilla donde se almacena el dato.

La dirección del registro: es la posición en la memoria donde está alojado el registro.

así pues tenemos 2 elementos diferentes pero que se relacionan.

Conociendo la dirección del registro o variable y pudiéndolo manejar nos da un poderosa herramienta para agilizar/simplificar nuestros programas.

[editar]¿Como podemos acceder a la dirección de una variable?

En CCS se hace a través del operador &. Veamos un ejemplo:

Ejemplo1:

#include <18F4550.h>#use delay(clock=4000000)void main(){ int t,k; t=5; k= &t; delay_cycles(1);}

Page 91: Introducción al PIC

al simular en el MPLAB tenemos:

Cuando detenemos en delay_cycles(1) vemos que en k se guarda la dirección de la variable t, ¿y que guarda t? guarda el número 5. todo se realiza

usando memoria RAM ó el registro de propósito general GPR.

Vamos a cambiar ligeramente el código. Usemos 3 variables tipo entero (int):

#include <18F4550.h>#use delay(clock=4000000)void main(){ int k,l,m; int t,u,v; t=0xfa; u=0xfb; v=0xfc; k= &t; l= &u; m= &v; delay_cycles(1);}

se repite lo mismo, el resultado de las direcciones en k, l y m son contiguas. Pero... ¿por que?

Para responder esta pregunta vamos a cambiar el código otra vez, declarando los 3 tipos de registros conocidos, int, long y float

Page 92: Introducción al PIC

#include <18F4550.h>#use delay(clock=4000000)void main(){ int k,l,m,n; int t; long u; float v; int z; t=0xfa; z=0xff; u=0xfffa; v=3.45000000; k= &t; l= &u; m= &v; n=&z; delay_cycles(1);}

la simulación:

Observa que las direcciones de t, u y v saltan. ¿Por que?

Dependiendo del tipo de dato se consume >= 1 byte de memoria. En el caso de t es un entero, y los enteros ocupan 1 byte (0..255).

"entero largo", ocupa dos bytes (0..65535) v es un dato "coma flotante", con parte fraccionaria en el sistema decimal y toma 4 bytes de memoria (32

bits)

en t tenemos una dirección que ocupa un byte [0xA]

en u tenemos una dirección que ocupa 2 byte [0xB - 0xC]

en v tenemos una dirección que ocupa 4 bytes [0xD - 0x10]

[ editar]Probando punteros, primera parte

Siempre que se declare una variable puntero, al momento de usarlo se debe especificar la dirección de apuntamiento de la variable normal, porque

entonces no se puede guardar un dato sino sabemos donde lo vamos a guardar. (Es obvio pero es cierto)

Page 93: Introducción al PIC

Esto quiere decir que se le debe pasar el número por valor de la dirección de la variable normal. Recordemos que:

Pasar un dato por valor: se copia el dato de una variable a otra.

Pasar un dato por referencia: se mueve/modifica el dato en la misma variable.

Variable normal: la variable que normalmente usamos.

Variable puntero: es la variable especial que estamos estudiando.

Veamos un ejemplo sencillo usando punteros:

#include <18F4550.h>#use delay(clock=4000000)//*******************************void main(){ int k; // variable normal int *p; // la variable puntero k=0xfa; // k <- 0xfa *p=0x5; delay_cycles(1);}

Dentro del código reconocemos de inmediato quien es el puntero: el que tiene el símbolo * debe ir antes de la letra p y sin separación:

*p así es como se debe declarar.

si nos vamos a MPLAB-SIM, y trazamos hasta delay_cycles(1) vemos en la ventana LOCAL:

pero... ¡no aparece nada en p! ¿Por que?

Es simple: porque no fijamos una dirección que apuntara p, y esto es muy importante saberlo, era lo que se decía al inicio de este artículo. Vamos a

darle la dirección de k:

#include <18F4550.h>#use delay(clock=4000000)

Page 94: Introducción al PIC

//*******************************void main(){ int k; // variable normal int *p; // la variable puntero p=&k; // dirección de k copiada a p k=0xfa; // k <- 0xfa *p=0x5; // k <- 0x5 delay_cycles(1);}

el resultado:

Ahora si funciona correctamente el código. Si ven la línea:

p=&k; // dirección de k copiada a p

Podran ovservar que se usa el puntero sin el *. Esto significa que se guardará allí una dirección y el compilador lo interpreta de esa manera.

Y con esta línea:

*p=0x5; // k <- 0x5

se está modificando el contenido de k, (indirectamente)

Otro detalle a tomar en cuenta es que para apuntar cierto tipos de datos, es que se debe declarar al apuntador con el mismo tipo de datos:

int k; // si queremos apuntar a kint *p; // p debe ser tipo intchar c; // si queremos apuntar a cchar *p // p debe ser tipo char

no quiere decir que el tipo de datos que contendrá el puntero sea de ese tipo de datos, el puntero siempre soportará números enteros positivos, en

realidad esto ya es a nivel interno del compilador.

Un ejemplo más:

En este código hay 2 punteros y 3 variables normales:

Page 95: Introducción al PIC

#include <18F4550.h>#use delay(clock=4000000)//*******************************void main(){ int i; // variable normal int *p; // la variable puntero int j; int *q; int k; // p=&i; // dirección de i copiada a p q=&j; // i=0xfa; // i <- 0xfa j=0x11; k=0x22; // *p=0x5; // i <- 0x5 *q=0x33; delay_cycles(1); }

Entre i, p hay 1 byte -> i ocupa 1 byte.

Entre p, j hay 2 bytes -> puntero p ocupa 2 bytes.

Entre j, q hay 1 byte -> j ocupa 1 byte

Entre q, k hay 2 bytes -> puntero q ocupa 2 bytes.

Modificando el código para que i sea del tipo float:

#include <18F4550.h>#use delay(clock=4000000)

Page 96: Introducción al PIC

//*******************************void main(){ float i; // variable normal float *p; // la variable puntero // long j; long *q; int k; // i=2.51; // i <- 0xfa j=0x11; k=0x22; // p=&i; // dirección de i copiada a p q=&j; // *p=3.99; // i <- 0x5 *q=0x33; delay_cycles(1);

}

Entre i, p hay 4 bytes -> i ocupa 4 bytes.

Entre p, j hay 2 bytes -> puntero p ocupa 2 bytes.

Entre j, q hay 2 bytes -> j ocupa 2 bytes.

Entre q, k hay 2 bytes -> puntero q ocupa 2 bytes.

En ambos casos a pesar que cambiamos el tipo de declaración de los punteros, se mantienen en 2 bytes, eso quiere decir que para el compilador el

tamaño de un puntero es de 2 bytes. No confundir con el tipo de datos a direccionar, pues eso es otra cosa.

Vamos con otro ejemplo. Supongamos que i sea del tipo float (4 bytes) pero su apuntador lo declaramos como int (1 byte):

Page 97: Introducción al PIC

#include <18F4550.h>#use delay(clock=4000000)//*******************************void main(){ float i; // variable normal int *p; // la variable puntero long j; long *q; int k; // i=2.51; // i <- 0xfa j=0x11; k=0x22; // p=&i; // dirección de i copiada a p q=&j; // *p=3.99; // i <- 0x5 *q=0x33; delay_cycles(1);}

Noten que el nuevo valor de i no corresponde con el valor que indirectamente le dimos con el apuntador.

¿Por que? Porque declaramos a ese apuntador como entero (int) y con ello le estamos diciendo al compilador que reserve para p

en vez de 4 bytes que son los que se necesitan y por eso ocurre ese truncamiento y da ese valor extraño.

Para corregir esto, se declara a p del MISMO tipo de dato de i:

#include <18F4550.h>#use delay(clock=4000000)//*******************************void main(){ float i; // variable normal float *p; // la variable puntero long j; long *q; int k; //

Page 98: Introducción al PIC

i=2.51; // i <- 0xfa j=0x11; k=0x22; // p=&i; // dirección de i copiada a p q=&j; // *p=3.99; // i <- 0x5 *q=0x33; delay_cycles(1);}

Aquí se lee que está correcto el resultado.

Nota: los punteros tiene un máximo de 2 bytes para almacenar direcciones y el CCS sigue la misma normativa. Hay una directiva llamada

xxxxxxx

Page 99: Introducción al PIC

con 4 modos de selección: CCS2,CCS3,CCS4 y ANSI. Con CCS2 y CCS3 el tamaño (size) del puntero es de 1 byte en partes de 14, 16 bits y

con CCS4 (modo por defecto) el size es de 2 bytes.

[ editar]Probando punteros, segunda parte

Analizando nuevamente lo hablado referente al size de los punteros en CCS, y en un intento de explicar que el tipo de dato y el tamaño del apuntado

son 2 cosas distintas, vamos hacer un ejemplo donde se verá claramente. Para ello vamos a usar una directiva llamada#locate, sobre la que la ayuda

del compilador reza así:

#LOCATE works like #BYTE however in addition it prevents C from using the area

bueno esto quiere decir que la variable normal la puedo alojar en cualquier dirección de la RAM (dentro de ciertos limites). Algo así como si en

ensamblador pusieramos:

variable_normal EQU 0xNNNN

Esto nos servirá porque sería como manipular el contenido de un puntero pero en tiempo de diseño

#include <18F4550.h>#use delay(clock=4000000)

Page 100: Introducción al PIC

//*********************************int dato=0xaa; // declaramos dato (GPR) y lo cargamos con 0xAA#locate dato = 0xff // le decimos al compilador que dato estará en la dirección 0xFF //del área de registro de propósito general, traducido, en la RAM del PICvoid main(){ int *p; // declaramos un puntero como entero (igual que dato) int t; // otra variable normal p=&dato; // inicializamos al puntero *p=0xbb; // dato <- 0xBB delay_cycles(1); // un nop }

Page 101: Introducción al PIC

Fíjense que el puntero p ocupa 2 bytes a pesar que está declarado como int (1 byte).

Vamos a modificar este ejemplo pero usando float (4 bytes) y colocando el GPR en la dirección 0xAF

Page 102: Introducción al PIC

Observen que el puntero p se mantuvo en 2 bytes siendo éste declarado como float.

Supongamos un ejemplo para el PIC18F4550, en el que tenemos una memoria de datos que llega hasta 0x7FF (Pág. 66 de su hoja de datos). Para que

funcione 0x7FF debe ser el 4 byte para un float, entonces

float dato=1.23456789;#locate dato = 0x7FB...

Si que funcionó. Pero, ¿que pasa si asignamos el dato a 0x800?

Page 103: Introducción al PIC

Allí vemos que el puntero se cargó bien, pero el MPLAB-SIM delata el desbordamiento, ¿Por que? Es que a partir de allí no hay memoria de datos y

las direcciones se deberían leer como puros 0x0 a pesar que compiló bien, (similarmente en programas de computadoras pueden ocurrir los lazos

infinitos popularmente llamado ‘se colgó la máquina’)

[ editar]Punteros en funciones

Todo lo que hagamos en CCS se hace a través de funciones o procedimientos, desde el punto de vista matemático una función se define así:

Una función es una relación entre dos variables numéricas, habitualmente las denominamos x e y; a una de ellas la llamamos variable dependiente

pues depende de los valores de la otra para su valor, suele ser la y; a la otra por tanto se la denomina variable independiente y suele ser la x.

además, para que una relación sea función, a cada valor de la variable independiente le corresponde uno o ningún valor de la variable dependiente,

no le pueden corresponder dos o más valores.

Aplicándolo a la programación, significa que podemos tener varios argumentos o parámetros de entrada, pero solo tendremos un dato de salida. Y

eso no es todo, en C una función pasa los argumentos por valor. ¿que quiere decir esto? Que cuando llamemos a la función y le pasemos el dato

como argumento, ésta copiará ese dato en su propia función sin alterar la variable original. veamos un ejemplo:

#include <18F4550.h>#use delay(clock=4000000)//*********************************int mi_funcion(int argumento1, argumento2){ delay_cycles(1); return (argumento1 + argumento2);}//*******************************void main(){ int k,l,resultado; k=5; L=2; resultado = mi_funcion(k,L);

Page 104: Introducción al PIC

delay_cycles(1);}

Noten que cuando llamo a mi_funcion, se copia el contenido de k -> argumento1 y L -> argumento2 , luego hace la suma y regresa un dato con el

resultado de la suma. k y L se quedan con el mismo valor anterior.

¿y si queremos cambiar esas variables como se hace?

Bueno seguro que alguien llegará y colocará a k y L como globales y entonces así se puede modificar en cualquier lado. Pero si la variable es local,

dentro de main(), no se puede modificar fuera de main()...a menos que usemos punteros. ¿y como se haría eso?

Simple: se haría pasando el argumento a la función como referencia, haciendo referencia a la dirección, es decir lo que se pasará a la función es la

dirección de k, L entonces allí si se puede modificar a gusto. Un ejemplo:

#include <18F4550.h>#use delay(clock=4000000)//*********************************int mi_funcion(int argumento1, argumento2, *la_k, *la_L){ delay_cycles(1); *la_k=0xFF; *la_L=0xAF; return (argumento1 + argumento2);}//*******************************void main(){ int k,l,resultado; k=5; l=2; resultado = mi_funcion(k,l,&k,&l); delay_cycles(1);}

[ editar]Punteros en Arrays

Como sabrán los arrays son arreglos de datos que van en direcciones consecutivas, es decir, uno detrás del otro. Un ejemplo de ello:

Page 105: Introducción al PIC

char cadena[7]={'T','o','d','o','P','i','c'};

Si lo probamos en un código:

#include <18F4550.h>#use delay(clock=4000000)//*********************************char cadena[7]={'T','o','d','o','P','i','c'};void main(){ char c; int t; for(t=0;t<7;t++){ c=cadena[t]; } delay_cycles(1);}

Page 106: Introducción al PIC

Se pueden usar punteros en el ejemplo anterior. Veamos como:

Declarando un puntero como char:

char c, *p;

lo inicializamos (le damos la dirección del primer elemento del array):

p=&cadena[0];

luego hacemos un barrido de direcciones para tomar el contenido de cada elemento y guardarlo en c

for(t=0;t<7;t++){ c= *p + t;}

Pero ese programa tiene 2 errores y no funcionará:

El primer error es que según la precedencia del operador primero está el puntero y luego viene la suma, y así estaríamos sumando direcciones que

varían, la solución es usar *(p+i)

¿Y que es eso de que varían? Pues que cuando se recorre el arrays con el puntero, este debe ir sumando direcciones, pero direcciones de números

constantes, es decir, si el tipo de datos es 1 byte, entonces el puntero debe acumular números enteros de 1 byte en 1 byte

Si el tipo de datos es long (entero largo) entonces el puntero debe ir sumando direcciones de 2 bytes en 2 bytes. ¿Porque digo esto? Es que

Page 107: Introducción al PIC

fijo (la dirección) y el truco está en desplazar al puntero tantas posiciones sea el size del tipo de dato. Este sería el segundo error y la solución es la

misma: *(p+i)

Vamos a cambiar ese ejemplo por números long para que se entienda

#include <18F4550.h>#use delay(clock=4000000)//*********************************long cadena[7]={1000,2000,3000,4000,5000,6000,7000};void main(){ long c, *p; int t; p=&cadena[0]; for(t=0;t<7;t++){ c= *p + t; } delay_cycles(1);}

Page 109: Introducción al PIC

Con esto estamos garantizando que el puntero se moverá de 2 bytes en 2 bytes, es decir

*(p+t) =

0x5 + 0x2 (desplazamiento de 2 byte)-> dame el contenido de la dirección 0x50x5 + 1x2 " -> dame el contenido de la dirección 0x70x5 + 2x2 " -> dame el contenido de la dirección 0x90x5 + 3x2 " -> dame el contenido de la dirección 0xA...

Noten que la suma se realiza no intervalos de t sino en intervalos del ancho del tipo de dato. Pero...¿esto no es lo mismo que se hizo en el código del

inicio del artículo?

O sea que ¿ c = cadena[t]; es igual a c = *(p + t) cuando p = &cadena[0]; ?

Pues si, acabamos de ver un array al desnudo, como funciona en realidad, no es mas que un puntero escondido a nuestra vista. Solo que para hacer

Page 110: Introducción al PIC

fácil la programación el compilador lo acepta de esta manera. Si p es un puntero -> p = cadena (para el primer índice del arreglo) es totalmente

válido, se acepta que cadena es un puntero constante, también se podría llamar un puntero nulo (ya que no se ve y tampoco se puede modificar).

Ejemplos validos:

cadena[0] = *cadenacadena[2] = *(cadena + 2)cadena = *(cadena + i)

Nota: el operador () es el primero que atiende el compilador, antes que al resto.

Acomodando el código original, el que tenía la cadena de caracteres:

#include <18F4550.h>#use delay(clock=4000000)//*********************************char cadena[7]={'T','o','d','o','P','i','c'};void main(){ char c, *p; int t; p=cadena; for(t=0;t<7;t++){ c=*(p+t); } delay_cycles(1);}

Page 111: Introducción al PIC

CCS - Funciones

[editar]Introducción

Programando en CCS.

Las funciones son los bloques básicos con los que construimos un programa enCCS. Además de la funcion main() que veremos enseguida, un

programa CCStendrá seguramente varias funciones más, conteniendo cada una un bloque de instrucciones que realizan una tarea

determinada.

Tabla de contenidos

Page 112: Introducción al PIC

 [mostrar]

[editar]Funciones

Las funciones tienen la siguiente forma:

nombre_de_la_funcion() { instruccion; instruccion; . . instruccion; }

Para evitar que surjan errores o avisos (warnings) al compilar nuestros programas, debemos declarar las funciones antes de utilizarlas.

[editar]Prototipos

Existen dos formas de decirle al compilador CCS que tipo de valor devolverá nuestra función. La forma general es la siguiente:

tipo nombre_de_funcion();

donde tipo es cualquiera de los tipos de variables soportados por CCS. Al igual que cualquier instrucción de CCS, la linea debe termina con

y coma).

El siguiente ejemplo declara la funcion ejemplo() que devuelve como resultado un valor del tipo long:

long ejemplo();

[editar]Parámetros

Ademas de determinar el tipo de resultado que devolverá la función, en el prototipo podemos especificar que parametros recibirá, y de que tipo

serán. La forma de hacerlo es la siguiente:

tipo nombre_de_funcion(tipo var1, tipo var2, ..., tipo varN);

La diferencia con el caso anterior es que se han incluido dentro de los () una serie de nombres de variables (var1, var2, ..., varN), cada una asociado a

un tipo en particular.

Supongamos que queremos crear una función que lleve a cabo la suma de dos de tipo int, que le son pasados como argumentos, y nos devuelva el

resultado en formato double. Deberiamos escribir así su prototipo:

double suma(int a, int b);

donde a y b son los valores a sumar. El llamado a la función se puede hacer de la siguiente manera:

Page 113: Introducción al PIC

int a, b;double resultado;a = 10;b = 250;resultado = suma (a, b);

resultado contendrá el valor "300".

[ editar]Return

La forma en que se asigna en la función el valor que esta debe devolver es mediante la instrucción return.

Vemoslo con el ejemplo de la función suma vista mas arriba. La función podría ser como sigue:

double suma(int a, int b){ double auxiliar; auxiliar = (double) (a * b ); return auxiliar;}

Otra forma, mas corta, de escribir la misma función es la siguiente:

double suma(int a, int b){ return (double) a * b; }

[ editar]Void

void significa que la función no devolverá ningún parametro. Supongamos que la función ejemplo() no debe regresar ningún valor luego de ser

llamada. Su prototipo debería ser como sigue:

void ejemplo();

Además, podemos usar void para para indicar que la función no recibe parámetros:

void ejemplo2(void);

en el ejemplo, la función ejemplo2() no recibe parametros, ni devuelve ningún valor.

[ editar]La función main()

Como hemos visto, el lenguaje C permite la utilización de funciones. Pero hay una función especial, llamada main() que obligatoriamente debe estar

Page 114: Introducción al PIC

presente, y es el punto de entrada a todo programa en C que escribamos.

La funcion main() tiene la siguiente forma:

main() { instruccion; instruccion; . . instruccion; }

donde instruccion; puede ser cualquier instrucción válida del CCS o una llamada a otra función.

CCS - Uso de LCDs alfanuméricos.

[editar]Introducción

Programando en CCS.

En CCS no disponemos de instrucciones específicas para el manejo de pantallas LCD. Sin embargo, nada impide que escribamos funciones que sean

capaces de inicializar, escribir o borrar (e incluso leer) los datos de estas pantallas. El hecho de que la mayoría de los módulos

construidos en base al controlador Hitachi HD44780.

Tabla de contenidos

 [mostrar]

[editar]LCD.C

Para ponernos las cosas más faciles, dentro de la carpeta "drivers" de CCS se encuentra un archivo llamado LCD.C, que si lo incluimos en nuestro

proyecto, nos proveerá de las funciones necesarias. Sin embargo, LCD.C tiene algunas limitaciones: tal como está, solo funciona si conectamos

nuestro LCD en el puerto D (o B, con una modificación menor).

Para incluirlo en nuestro programa, basta con hacer lo siguiente:

#INCLUDE "lcd.c"

en cuanto al hardware,las lineas de datos deben conectarse de la siguiente manera:

PORT.D0 -> enable

Page 115: Introducción al PIC

PORT.D1 -> rs

PORT.D2 -> rw

PORT.D4 -> D4

PORT.D5 -> D5

PORT.D6 -> D6

PORT.D7 -> D7

como puede verse, se trata de una comunicación con solo 4 bits de datos. Más adelante veremos como modificar este archivo para que se pueda

emplear con el LCD en otro puerto y/o con otra asignación de pines. El siguiente es el contenido del archivo LCD.C tal como es provisto por

/////////////////////////////////////////////////////////////////////////////// LCD.C //////// Driver for common LCD modules //////// //////// lcd_init() Must be called before any other function. //////// //////// lcd_putc(c) Will display c on the next position of the LCD. //////// The following have special meaning: //////// \f Clear display //////// \n Go to start of second line //////// \b Move back one position //////// //////// lcd_gotoxy(x,y) Set write position on LCD (upper left is 1,1) //////// //////// lcd_getc(x,y) Returns character at position x,y on LCD //////// /////////////////////////////////////////////////////////////////////////////////// (C) Copyright 1996,2003 Custom Computer Services //////// This source code may only be used by licensed users of the CCS C //////// compiler. This source code may only be distributed to other //////// licensed users of the CCS C compiler. No other use, reproduction //////// or distribution is permitted without written permission. //////// Derivative programs created using this software in object code //////// form are not restricted in any way. /////////////////////////////////////////////////////////////////////////////////// As defined in the following structure the pin connection is as follows:// D0 enable// D1 rs// D2 rw// D4 D4// D5 D5// D6 D6// D7 D7

Page 116: Introducción al PIC

//// LCD pins D0-D3 are not used and PIC D3 is not used.//// Un-comment the following define to use port B// #define use_portb_lcd TRUE////struct lcd_pin_map { // This structure is overlayed BOOLEAN enable; // on to an I/O port to gain BOOLEAN rs; // access to the LCD pins. BOOLEAN rw; // The bits are allocated from BOOLEAN unused; // low order up. ENABLE will int data : 4; // be pin B0. } lcd;//#if defined(__PCH__)#if defined use_portb_lcd #byte lcd = 0xF81 // This puts the entire structure#else #byte lcd = 0xF83 // This puts the entire structure#endif#else#if defined use_portb_lcd #byte lcd = 6 // on to port B (at address 6)#else #byte lcd = 8 // on to port D (at address 8)#endif#endif//#if defined use_portb_lcd #define set_tris_lcd(x) set_tris_b(x)#else #define set_tris_lcd(x) set_tris_d(x)#endif//#define lcd_type 2 // 0=5x7, 1=5x10, 2=2 lines#define lcd_line_two 0x40 // LCD RAM address for the second line//BYTE const LCD_INIT_STRING[4] = {0x20 | (lcd_type << 2), 0xc, 1, 6}; // These bytes need to be sent to the LCD // to start it up.// // The following are used for setting // the I/O port direction register.struct lcd_pin_map const LCD_WRITE = {0,0,0,0,0}; // For write mode all pins are outstruct lcd_pin_map const LCD_READ = {0,0,0,0,15}; // For read mode data pins are in//BYTE lcd_read_byte() { BYTE low,high; set_tris_lcd(LCD_READ); lcd.rw = 1;

Page 117: Introducción al PIC

delay_cycles(1); lcd.enable = 1; delay_cycles(1); high = lcd.data; lcd.enable = 0; delay_cycles(1); lcd.enable = 1; delay_us(1); low = lcd.data; lcd.enable = 0; set_tris_lcd(LCD_WRITE); return( (high<<4) | low);}//void lcd_send_nibble( BYTE n ) { lcd.data = n; delay_cycles(1); lcd.enable = 1; delay_us(2); lcd.enable = 0;}//void lcd_send_byte( BYTE address, BYTE n ) { lcd.rs = 0; while ( bit_test(lcd_read_byte(),7) ) ; lcd.rs = address; delay_cycles(1); lcd.rw = 0; delay_cycles(1); lcd.enable = 0; lcd_send_nibble(n >> 4); lcd_send_nibble(n & 0xf);}//void lcd_init() { BYTE i; set_tris_lcd(LCD_WRITE); lcd.rs = 0; lcd.rw = 0; lcd.enable = 0; delay_ms(15); for(i=1;i<=3;++i) { lcd_send_nibble(3); delay_ms(5); } lcd_send_nibble(2); for(i=0;i<=3;++i) lcd_send_byte(0,LCD_INIT_STRING[i]);}//

Page 118: Introducción al PIC

void lcd_gotoxy( BYTE x, BYTE y) { BYTE address; if(y!=1) address=lcd_line_two; else address=0; address+=x-1; lcd_send_byte(0,0x80|address);}//void lcd_putc( char c) { switch (c) { case '\f'  : lcd_send_byte(0,1); delay_ms(2); break; case '\n'  : lcd_gotoxy(1,2); break; case '\b'  : lcd_send_byte(0,0x10); break; default  : lcd_send_byte(1,c); break; }}//char lcd_getc( BYTE x, BYTE y) { char value; lcd_gotoxy(x,y); while ( bit_test(lcd_read_byte(),7) ); // wait until busy flag is low lcd.rs=1; value = lcd_read_byte(); lcd.rs=0; return(value);}

[ editar]Funciones en LCD.C

Hay cuatro funciones implementadas dentro de LCD.C:

[ editar]lcd_init()

Esta funcion es la encargada de enviar los comando de inicializacion necesarios al LCD. Es obligatorio ejecutar esta función antes de utilizar el

display para escribir sobre él. No recibe ni devuelve valores de ningun tipo. Su forma de uso es tan simple como:

lcd_init();

y listo.

[ editar]lcd_putc()

Esta seguramente será la función que mas emplearemos. Es la que se encarga de escribir nuestro mensaje en la pantalla. No devuelve valores, pero si

Page 119: Introducción al PIC

(obviamente) los recibe. La forma de uso es muy simple. Básta con llamarla, pansandole como parámetro una variable o constante tipo

función se encargará de desplegar su contenido sobre el display.

Lcd_putc ("uControl.com.ar");

produce la siguiente salida sobre el display:

Por supuesto, no debemos olvidar de inicializar previamente el display. Además, lcd_putc() reconoce los siguientes comandos que pueden ser

enviados en el texto a mostrar:

\f -> Borra la pantalla.

\n -> Salta al comienzo de la segunda línea.

\b -> Retrocede una posición.

Esto quiere decir que si modificamos nuestro código para que quede así:

Lcd_putc ("uControl.com.ar\n LCD en CCS");

obtendremos el siguiente texto en el LCD:

Por último, si queremos borrar el contenido de la pantalla, bastará con lo siguiente:

Lcd_putc ("\f");

que dejará nuestro display completamente limpio:

[ editar]lcd_gotoxy(x,y)

Esta es la función que nos permite colocar el cursor en la parte que deseemos de la pantalla. Recibe dos parámetros, ambos de tipo

Page 120: Introducción al PIC

de ellos indica la columna en la que aparecerá el primer caracter del texto, y el segundo se refiere a la fila en que lo hará. El siguiente código

ejemplifica el uso de lcd_gotoxy(x,y):

Lcd_putc ("uControl.com.ar"); lcd_gotoxy(5,2); //salto a columna 4, fila 2 Lcd_putc( "LCD en CCS");

hace lo siguiente:

Importante: Tanto las filas como las columnas se cuentan a partir de 1.

[ editar]lcd_getc(x,y)

Esta función recibe como parámetros la columna' y la fila (ambos de tipo byte) de la que deseamos conocer el contenido, y nos devuelve

un char con el contenido. Su uso no podría ser más sencillo:

char a;a = lcd_getc(5,2);

Si el display conserva el texto de la imagen anterior, la variable a contendra el valor "L".

Importante: Tanto las filas como las columnas se cuentan a partir de 1.

[ editar]Modificando LCD.C

Por supuesto, en la mayoría de los casos la conexión entre el microcontrolador y el display LCD no coincidirá con la especificada en el

archivo LCD.C provisto por CCS. Pero eso no quiere decir que debamos rediseñar nuestro proyecto, ni que sea imposible modificar la configuracion

de LCD.C. como puede verse, dentro del codigo de LCD.C se define una estructura de datos que es la encargada de contener la distribución de los

pines a utilizar, junto a la función que desempeñará. Es el siguiente trozo de código:

struct lcd_pin_map { // This structure is overlayed BOOLEAN enable; // on to an I/O port to gain BOOLEAN rs; // access to the LCD pins. BOOLEAN rw; // The bits are allocated from BOOLEAN unused; // low order up. ENABLE will int data : 4; // be pin B0. } lcd;

Supongamos que tenemos un display conectado de la siguiente manera:

PORT.B2 -> enable

Page 121: Introducción al PIC

PORT.B3 -> rs

PORT.B4 -> D4

PORT.B5 -> D5

PORT.B6 -> D6

PORT.B7 -> D7

Notar que no estámos usando el pin RW del display, que estará permanentemente conectado a GND. La estructura deberia quedar asi:

struct lcd_pin_map { BOOLEAN unused1; // RB0 BOOLEAN unused2; // RB1 BOOLEAN enable; // RB2 BOOLEAN rs; // RB3 int data : 4; // RB4-RB7} lcd;

Por supuesto, habrás notado que en lugar del puerto D estamos usando el puerto B, asi que hay que quitar el comentario a la linea

// #define use_portb_lcd TRUE

para que quede así:

#define use_portb_lcd TRUE //LCD conectado al puerto b.

Y como no estamos usando la línea RW del LCD, debemos quitar las dos tres lineas de código en la que se hace referencia a ella. El listado siguente

corresponde al archivo LCD.C con todas las modificaciones mencionadas, más algunas modificaciones en los#INCLUDE del principio, que no

tienen sentido mantener ya que al personalizar el archivo nunca se van a dar algunas de las condiciones contempladas alli. También hemos quitado el

código de la función lcd_getc( x, y) ya que al estar RW conectado de forma permanente aGND, será imposible leer caracteres del display.

///////////////////////////////////////////////////////////////////////////// LCD.C modificada por uControl.com.ar ///////////////////////////////////////////////////////////////////////////// B0 // B1 // B2 E// B3 RS// B4 D4// B5 D5// B6 D6// B7 D7

Page 122: Introducción al PIC

// (Sin 'RW')//// Funciones soportadas:// lcd_init()// lcd_gotoxy( BYTE col, BYTE fila)// lcd_putc( char c)// \f Clear display // \n Go to start of second line // \b Move back one position/////////////////////////////////////////////////////////////////////////////#define use_portb_lcd TRUE //LCD conectado al puerto b.//struct lcd_pin_map { BOOLEAN unused1; // RB0 BOOLEAN unused2; // RB1 BOOLEAN enable; // RB2 BOOLEAN rs; // RB3 int data : 4; // RB4-RB7} lcd;//#byte lcd = 0xF81 // Direccion de la estructura "lcd".#byte lcd = 6 // Direccion del puerto B.#define set_tris_lcd(x) set_tris_b(x)#define lcd_type 2 // Tipo de LCD: 0=5x7, 1=5x10, 2=2 lineas#define lcd_line_two 0x40 // Dirección de la LCD RAM para la 2da. linea////Defino la cadena de inicializacion del LCD.BYTE const LCD_INIT_STRING[4] = {0x20 | (lcd_type << 2), 0xc, 1, 6};////Configuro el estado de cada pin para lectura y escritura:struct lcd_pin_map const LCD_WRITE = {0,0,0,0,0}; // Escribir.struct lcd_pin_map const LCD_READ = {0,0,0,0,15}; // Leer.////Funciones:BYTE lcd_read_byte() { BYTE low,high; set_tris_lcd(LCD_READ); delay_cycles(1); lcd.enable = 1; delay_cycles(1); high = lcd.data; lcd.enable = 0; delay_cycles(1); lcd.enable = 1; delay_us(1); low = lcd.data; lcd.enable = 0; set_tris_lcd(LCD_WRITE); return( (high<<4) | low);}

Page 123: Introducción al PIC

//void lcd_send_nibble( BYTE n ) { lcd.data = n; delay_cycles(1); lcd.enable = 1; delay_us(2); lcd.enable = 0;}//void lcd_send_byte( BYTE address, BYTE n ) { lcd.rs = 0; while ( bit_test(lcd_read_byte(),7) ) ; lcd.rs = address; delay_cycles(1); delay_cycles(1); lcd.enable = 0; lcd_send_nibble(n >> 4); lcd_send_nibble(n & 0xf);}//void lcd_init() { BYTE i; set_tris_lcd(LCD_WRITE); lcd.rs = 0; lcd.enable = 0; delay_ms(15); for(i=1;i<=3;++i) { lcd_send_nibble(3); delay_ms(5); } lcd_send_nibble(2); for(i=0;i<=3;++i) lcd_send_byte(0,LCD_INIT_STRING[i]);}//void lcd_gotoxy( BYTE x, BYTE y) { BYTE address; if(y!=1) address=lcd_line_two; else address=0; address+=x-1; lcd_send_byte(0,0x80|address);}//void lcd_putc( char c) { switch (c) { case '\f'  : lcd_send_byte(0,1); delay_ms(2); break; case '\n'  : lcd_gotoxy(1,2); break;

Page 124: Introducción al PIC

case '\b'  : lcd_send_byte(0,0x10); break; default  : lcd_send_byte(1,c); break; }}

Libreria de gráficos para GLCD K0108 en CCS

[editar]Introducción

Display GLCD

El compilador CCS proporciona una libreria capaz de dibujar primitivas sobre varios modelos de displays LCD gráficos o GLCD

Liquid Cristal Display). Hay versiones de esta libreria para pantallas con diferentes controladores embebidos, como el Samsung KS0108

el Toshiba T6963.

Pero a pesar de que pueden distribuirse libremente los trabajos que hagamos con ellas, no pueden compartirse los programas que las contengan a

menos que la persona que los recibe tambien sea un usuario registrado de CCS.

Esto limita mucho su uso con fines educativos. De hecho, si quisiesemos exponer aqui un programa que grafique algo en un GLCD

violando la licencia, ya que es muy posible que muchos de los lectores de uControl no hayan comprado el compilador.

Es por ello que nos hemos decidido a escribir una libreria propia, que usaremos de ahora en más para nuestros proyectos.

Tabla de contenidos

 [mostrar]

[editar]La librería GLCD_K0108

Puedes descargar la libreria GLCD_K0108.C haciendo click aquí.

Page 125: Introducción al PIC

En las siguientes secciones iremos explicando cada una de sus partes.

IMPORTANTE: El trazado de lineas se basa en el Algoritmo de Bresenham, y las circunferencias se han resuelto mediante el"algoritmo del punto

medio", que divide la circunferencia en 8 partes simétricas, evitando utilizar funciones como seno, coseno o potencias, que volverian muy lenta la

tarea del trazado.

[editar]GLCD_limpiar(color)

Esta es la función que "pinta" toda la pantalla con uno u otro color. Si recibe como parámetro un "1", la pintará completamente de negro. Si recibe un

"0", la limpiará por completo.

Por supuesto, su mayor utilidad es la segunda alternativa.

Su funcionamiento también es muy sencillo, y se "apoya" en GLCD_envia BYTE() para escribir en el GLCD. Recorre ambas mitades del GLCD,

página por página, de arriba hacia abajo, escribiendo "0x00" o "0xFF" según se haya elegido pintar o borrar.

 (Ház clic sobre las imágenes para ampliarlas)

En la primer imágen, utilizando GLCD_limpiar(1);, la pantalla se pinta completamente de negro. En la segunda, medianteGLCD_limpiar(0);

pinta completamente de blanco. Podemos usar esta función para limpiar la pantalla.

Puedes ver un ejemplo de uso de esta función aquí.

[editar]GLCD_inicializa(modo)

Esta es la primer función de la librería que debe llamar nuestro programa.

Se encarga de inicializar el GLCD, y el parametro "modo" determina si estará encendido (si recibe un "1") o apagado (si recibe un "0").

Puedes ver un ejemplo de uso de esta función aquí.

Page 126: Introducción al PIC

[editar]GLCD_punto(x, y, color)

Esta es la "primitiva gráfica" indispensable. A partir de GLCD_punto(x, y, color) escribiremos todas las funciones restantes.

Los parametros que recibe GLCD_punto(x, y, color) son:

x: un byte, es la coordenada "x" (horizontal), con valores válidos de 0 a 127 (izquierda a derecha).

y: un byte, es la coordenada "y" (vertical), con valores válidos de 0 a 63 (arriba a abajo)

color: un bit, "0" = apagado, "1" = encendido.

(Ház clic sobre la imágen para ampliarla)

Puedes ver un ejemplo de uso de esta función aquí.

[ editar]GLCD_linea(x1, y1, x2, y2, color)

La linea tambien resulta indispensable a la hora de dibujar un gráfico.

Los parametros que recibe GLCD_linea(x1, y1, x2, y2, color) son:

x1: un byte, es la coordenada "x" (horizontal) del primer extremo de la linea, con valores válidos de 0 a 127 (izquierda a derecha).

y1: un byte, es la coordenada "y" (vertical) del primer extremo de la linea, con valores válidos de 0 a 63 (arriba a abajo).

x2: un byte, es la coordenada "x" (horizontal) del segundo extremo de la linea, con valores válidos de 0 a 127 (izquierda a derecha).

y2: un byte, es la coordenada "y" (vertical) del segundo extremo de la linea, con valores válidos de 0 a 63 (arriba a abajo).

color: un bit, "0" = linea en blanco, "1" = linea en negro.

Page 127: Introducción al PIC

 (Ház clic sobre las imágenes para ampliarlas)

Puedes ver un ejemplo de uso de esta función aquí.

[ editar]GLCD_rectangulo(x1, y1, x2, y2, color)

Los rectángulos de dibujan (internamente) mediante cuatro llamadas a la función GLCD_linea.

Los parametros que recibe GLCD_rectangulo(x1, y1, x2, y2, color) son:

x1: un byte, es la coordenada "x" (horizontal) de la esquina superior izquierda del rectángulo, con valores válidos de 0 a 127 (izquierda a

derecha).

y1: un byte, es la coordenada "y" (vertical) de la esquina superior izquierda del rectángulo, con valores válidos de 0 a 63 (arriba a abajo).

x2: un byte, es la coordenada "x" (horizontal) de la esquina inferior derecha del rectángulo, con valores válidos de 0 a 127 (izquierda a

derecha).

y2: un byte, es la coordenada "y" (vertical) de la esquina inferior derecha del rectángulo, con valores válidos de 0 a 63 (arriba a abajo).

color: un bit, "0" = rectángulo en blanco, "1" = rectángulo en negro.

Page 128: Introducción al PIC

 (Ház clic sobre las imágenes para ampliarlas)

Puedes ver un ejemplo de uso de esta función aquí.

[ editar]GLCD_caja(x1, y1, x2, y2, color)

Las "cajas" son rectángulos pintados en su interior con el mismo color que el borde exterior. También se dibujan (internamente) mediante llamadas a

la función GLCD_linea.

Los parametros que recibe GLCD_caja(x1, y1, x2, y2, color) son:

x1: un byte, es la coordenada "x" (horizontal) de la esquina superior izquierda del rectángulo, con valores válidos de 0 a 127 (izquierda a

derecha).

y1: un byte, es la coordenada "y" (vertical) de la esquina superior izquierda del rectángulo, con valores válidos de 0 a 63 (arriba a abajo).

x2: un byte, es la coordenada "x" (horizontal) de la esquina inferior derecha del rectángulo, con valores válidos de 0 a 127 (izquierda a

derecha).

y2: un byte, es la coordenada "y" (vertical) de la esquina inferior derecha del rectángulo, con valores válidos de 0 a 63 (arriba a abajo).

color: un bit, "0" = caja en blanco, "1" = caja en negro.

Page 129: Introducción al PIC

 (Ház clic sobre las imágenes para ampliarlas)

Puedes ver un ejemplo de uso de esta función aquí.

[ editar]GLCD_circulo(x1, y1, radio, color)

Esta es la función que dibuja un circulo. El interior del circulo permanece del color del fono. Estrictamente hablando, se dibuja solo la

circunferencia.

Los parametros que recibe GLCD_circulo(x1, y1, radio, color) son:

x1: un byte, es la coordenada "x" (horizontal) del centro del circulo, con valores válidos de 0 a 127 (izquierda a derecha).

y1: un byte, es la coordenada "y" (vertical) del centro del circulo, con valores válidos de 0 a 63 (arriba a abajo).

radio: un byte, es el radio de la circunferencia (en pixeles).

color: un bit, "0" = circulo en blanco, "1" = circulo en negro.

 (Ház clic sobre las imágenes para ampliarlas)

Puedes ver un ejemplo de uso de esta función aquí.

Page 130: Introducción al PIC

[ editar]Definiciones, pines y otras yerbas

Para que la libreria pueda ser adaptada al proyecto que tienes en mente, hemos colocado los siguientes "#define" alc omienzo de la misma, para

determinar que pin del PIC has conectado a cada pin del GLCD:

//Pines a usar#define GLCD_CS1 PIN_E2#define GLCD_CS2 PIN_E1#define GLCD_DI PIN_C3#define GLCD_RW PIN_C2#define GLCD_E PIN_C1#define GLCD_RESET PIN_E0

Si tu circuito emplea pines diferentes a los del ejemplo para manejar el GLCD, deberás cambiar los valores que sea necesario.

El display está "partido" en dos mitades de 64x64 pixeles. Esto implica que al momento de escribir en el debemos seleccionar en cual de las dos

mitades lo estamos haciendo. Para ello dispone de dos lineas de control (ver el pinout del GLCD en la sección correspondiente),

llamadas CS1 y CS2. Asignaremos los valores de 0 y 1 a "GLCD_lado_CS1" y "GLCD_lado_CS2", respectivamente.

//Lados del GLCD#define GLCD_lado_CS1 0#define GLCD_lado_CS2 1

Tambien hemos definido un BYTE que guardará el dato que leyamos desde el GLCD:

BYTE GLCD_leeBYTE(int1 lado);

Page 131: Introducción al PIC

GLCD-K0108 en el PIC SIMULATOR IDE

Veamos ahora las funciones internas básicas de la librería:

[ editar]GLCD_enviaBYTE(lado, dato)

Esta función envia un byte a uno u otro lado del display. Como mencionamos antes, debemos seleccionar previamente, mediante la activación

de CS1 o CS2, cual utilizaremos.

El parámetro (tipo int1) "lado" es el que define a que mitad del GLCD irá a parar el "dato".

El resto de la función no tiene ningún secreto. En ella, se realizan los siguientes pasos:

Se pone el GLCD en "modo escritura", poniendo en bajo GLCD_RW.

Se coloca el dato en el puerto D del PIC durante 1 microsegundo.

Se habilita el GLCD, poniendo en alto GLCD_E durante 2 microsegundos.

Se desabilita el GLCD, poniendo en bajo nuevamente GLCD_E

Se vuelven a nivel bajo CS1 y CS2

[ editar]GLCD_leeBYTE(lado)

Esta es la función que permite leer un byte desde el GLCD.

Igual que la anterior, recibe como parámetro la información que le indica en cual de las dos mitades está el dato a leer.

Realiza los siguientes pasos:

Se configura el puerto D como entrada.

Page 132: Introducción al PIC

Se pone el GLCD en "modo lectura", poniendo en alto GLCD_RW.

Se selcciona la mitad correspondiente, mediante CS1 y CS2.

Se habilita el GLCD, poniendo en alto GLCD_E durante 2 microsegundos.

Se guarda el dato y se desabilita el GLCD, poniendo en bajo nuevamente GLCD_E

Se vuelven a nivel bajo CS1 y CS2

La funcion devuelve un BYTE con el dato leido.

[ editar]GLCD_letra(caracter, color, fuente)

El presente artículo esta incompleto.

En éste momento estamos trabajando en él, y en poco tiempo estará terminado.

El equipo de uControl.

[ editar]GLCD con controlador Samsung KS0108

Por ahora, nuestra librería ofrece soporte para los GLCD de 128x64 píxeles, monocromáticos, que tienen un controlador Samsung KS0108

compatibles).

El que hemos usado en nuestras pruebas es el de la foto siguiente. Se trata de un ELWG12864-YYB-VN, con puntos negros sobre fondo verde, de la

empresa Winstar.

 Este es el modelo de GLCD elegido para las pruebas.

IMPORTANTE: Si tu display no es exactamente este modelo, consulta su hoja de datos para asegurarte que funcion cumple cada uno de sus pines.

Page 133: Introducción al PIC

[ editar]Versión para GLCD con chip SED1565

El amigo Ruber Arranz nos ha mandado una version de esta libreria para displays que inegran el chipset SED1565. Aquí está:

/////////////////////////////////////////////////////////////////////////// Conexiones de pines del GLCD GTK-281 de GEM-TECH //// El Patillaje esta indicado abajo, del lado del GLCD //// y del Lado del PIC 18F-4550 //// Sirve para Controladores como el NT7534 y el SED1565 //// (Tambien puede ocuparse con el GLCD 12864F3, //// Pero el patillaje es distinto) //// //// ------------------------------------ //// GLCD GTK-281 --- PIC 18F4550 //// ------------------------------------ //// 17: Reset <-> A4 :6 //// 4: A0 <-> C2 :17 //// 5: R/W <-> C1 :16 //// 6: E <-> C0 :15 //// 7..14: D0..D7 <-> B0..B7 :33..40 //// ------------------------------------ //// //// Los valores pueden cambiarse con los DEFINE que están más abajo ////---------------------------------------------------------------------//// > GLCD_clear(color) //// "Borra" la pantala, llenando la del color seleccionado. //// color = 0 : puntos apagados //// color = 1 : puntos encendidos //// ////---------------------------------------------------------------------//// > GLCD_init(modo) //// Inicializa el GLCD, y debe llamarse ANTES de cualquier otra. //// modo = 0 : GLCD en ON //// modo = 1 : GLCD en OFF //// //// EN LAS SIGUIENTES FUNCIONES, SIEMPRE SE RESPETA QUE: //// color = 0 : puntos apagados //// color = 1 : puntos encendidos //// Tambien puede usarse directamente ON / OFF //// //// > GLCD_pixel(x,y,color) //// Dibuja un punto en x,y. 0,0 es la esquina superior izquierda. //// //// > GLCD_line(x1,y1,x2,y2,color) //// Dibuja una linea desde (x1,y1) a (x2,y2) //// //// >GLCD_rectangle (x1,y1,x2,y2,color) //// Dibuja un rectangulo con esquinas en (x1,y1) y (x2,y2) //// //// >GLCD_box (x1,y1,x2,y2,color) //

Page 134: Introducción al PIC

// Dibuja un rectangulo pintado con esquinas en (x1,y1) y (x2,y2) //// //// >GLCD_circle (x1,y1,r,color) //// Dibuja un circulo con centro en (x1,y1) y radio R //// //// >GLCD_text35(x,y,textptr,color) //// Escribe un texto de pequeñas dimensiones //// (x,y) - Coordenada superior izquierda del primer caracter //// textptr - Puntero a una matriz de caracteres del texto a mostrar //// ///////////////////////////////////////////////////////////////////////////

#include <STDLIB.H>

//Pines a usar#define GLCD_RESET PIN_C4#define GLCD_A0 PIN_C2#define GLCD_RW PIN_C1#define GLCD_E PIN_C0#define GLCD_TRIS set_tris_b#define GLCD_SAL output_b#define GLCD_ENT input_b

//Definiciones varias#define GLCD_WIDTH 128#define ON 1#define OFF 0

// Declaracion de funcionesvoid GLCD_Data(BYTE dato);void GLCD_Command(BYTE dato);BYTE GLCD_readBYTE();void GLCD_clear(int1 color);void GLCD_init(int1 modo);void GLCD_pixel(int8 x, int8 y, int1 color);void GLCD_line(int x1, int y1, int x2, int y2, int1 color);void GLCD_rectangle(int x1, int y1, int x2, int y2, int1 color);void GLCD_box(int x1, int y1, int x2, int y2, int1 color);void GLCD_circle(int x1, int y1, int radio, int1 color);void glcd_text35(int8 x, int8 y, char* textptr, int1 color);

const int8 TEXT35[95][5]={ 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, //SPACE 0b01000100, 0b01000100, 0b01000100, 0b00000000, 0b01000100, //! 0b10101010, 0b10101010, 0b00000000, 0b00000000, 0b00000000, //" 0b10101010, 0b11101110, 0b10101010, 0b11101110, 0b10101010, //# 0b01100110, 0b11001100, 0b11001100, 0b01100110, 0b11101110, //$ 0b10101010, 0b00100010, 0b01000100, 0b10001000, 0b10101010, //% 0b01000100, 0b10101010, 0b01000100, 0b10101010, 0b01100110, //& 0b01000100, 0b01000100, 0b00000000, 0b00000000, 0b00000000, //'

Page 135: Introducción al PIC

0b01000100, 0b10001000, 0b10001000, 0b10001000, 0b01000100, //( 0b01000100, 0b00100010, 0b00100010, 0b00100010, 0b01000100, //) 0b00000000, 0b10101010, 0b01000100, 0b10101010, 0b00000000, //* 0b00000000, 0b01000100, 0b11101110, 0b01000100, 0b00000000, //+ 0b00000000, 0b00000000, 0b00000000, 0b01000100, 0b10001000, //, 0b00000000, 0b00000000, 0b11101110, 0b00000000, 0b00000000, //- 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b01000100, //. 0b00100010, 0b00100010, 0b01000100, 0b10001000, 0b10001000, /// 0b11101110, 0b10101010, 0b10101010, 0b10101010, 0b11101110, //0 0b01000100, 0b11001100, 0b01000100, 0b01000100, 0b11101110, //1 0b11101110, 0b00100010, 0b11101110, 0b10001000, 0b11101110, //2 0b11101110, 0b00100010, 0b11101110, 0b00100010, 0b11101110, //3 0b10101010, 0b10101010, 0b11101110, 0b00100010, 0b00100010, //4 0b11101110, 0b10001000, 0b11101110, 0b00100010, 0b11101110, //5 0b11001100, 0b10001000, 0b11101110, 0b10101010, 0b11101110, //6 0b11101110, 0b00100010, 0b01000100, 0b10001000, 0b10001000, //7 0b11101110, 0b10101010, 0b11101110, 0b10101010, 0b11101110, //8 0b11101110, 0b10101010, 0b11101110, 0b00100010, 0b01100110, //9 0b00000000, 0b01000100, 0b00000000, 0b01000100, 0b00000000, //: 0b00000000, 0b01000100, 0b00000000, 0b01000100, 0b10001000, //; 0b00100010, 0b01000100, 0b10001000, 0b01000100, 0b00100010, //< 0b00000000, 0b11101110, 0b00000000, 0b11101110, 0b00000000, //= 0b10001000, 0b01000100, 0b00100010, 0b01000100, 0b10001000, //> 0b11001100, 0b00100010, 0b01100110, 0b00000000, 0b01000100, //? 0b01000100, 0b10101010, 0b11101110, 0b10001000, 0b01100110, //@ 0b11101110, 0b10101010, 0b11101110, 0b10101010, 0b10101010, //A 0b11001100, 0b10101010, 0b11101110, 0b10101010, 0b11001100, //B 0b11101110, 0b10001000, 0b10001000, 0b10001000, 0b11101110, //C 0b11001100, 0b10101010, 0b10101010, 0b10101010, 0b11001100, //D 0b11101110, 0b10001000, 0b11101110, 0b10001000, 0b11101110, //E 0b11101110, 0b10001000, 0b11101110, 0b10001000, 0b10001000, //F 0b11101110, 0b10001000, 0b10001000, 0b10101010, 0b11101110, //G 0b10101010, 0b10101010, 0b11101110, 0b10101010, 0b10101010, //H 0b11101110, 0b01000100, 0b01000100, 0b01000100, 0b11101110, //I 0b00100010, 0b00100010, 0b00100010, 0b10101010, 0b11101110, //J 0b10001000, 0b10101010, 0b11001100, 0b11001100, 0b10101010, //K 0b10001000, 0b10001000, 0b10001000, 0b10001000, 0b11101110, //L 0b10101010, 0b11101110, 0b11101110, 0b10101010, 0b10101010, //M 0b00000000, 0b11001100, 0b10101010, 0b10101010, 0b10101010, //N 0b01000100, 0b10101010, 0b10101010, 0b10101010, 0b01000100, //O 0b11101110, 0b10101010, 0b11101110, 0b10001000, 0b10001000, //P 0b01000100, 0b10101010, 0b10101010, 0b11101110, 0b01100110, //Q 0b11101110, 0b10101010, 0b11001100, 0b11101110, 0b10101010, //R 0b11101110, 0b10001000, 0b11101110, 0b00100010, 0b11101110, //S 0b11101110, 0b01000100, 0b01000100, 0b01000100, 0b01000100, //T 0b10101010, 0b10101010, 0b10101010, 0b10101010, 0b11101110, //U 0b10101010, 0b10101010, 0b10101010, 0b10101010, 0b01000100, //V 0b10101010, 0b10101010, 0b11101110, 0b11101110, 0b10101010, //W 0b00000000, 0b10101010, 0b01000100, 0b01000100, 0b10101010, //X 0b10101010, 0b10101010, 0b01000100, 0b01000100, 0b01000100, //Y

Page 136: Introducción al PIC

0b11101110, 0b00100010, 0b01000100, 0b10001000, 0b11101110, //Z 0b11101110, 0b10001000, 0b10001000, 0b10001000, 0b11101110, //[ 0b10001000, 0b10001000, 0b01000100, 0b00100010, 0b00100010, //\ 0b11101110, 0b00100010, 0b00100010, 0b00100010, 0b11101110, //] 0b01000100, 0b10101010, 0b00000000, 0b00000000, 0b00000000, //^ 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b11101110, //_ 0b10001000, 0b01000100, 0b00000000, 0b00000000, 0b00000000, //` 0b00000000, 0b01000100, 0b10101010, 0b10101010, 0b01100110, //a 0b10001000, 0b11001100, 0b10101010, 0b10101010, 0b11001100, //b 0b00000000, 0b01100110, 0b10001000, 0b10001000, 0b01100110, //c 0b00100010, 0b01100110, 0b10101010, 0b10101010, 0b01100110, //d 0b00000000, 0b01000100, 0b10101010, 0b11001100, 0b01100110, //e 0b01100110, 0b01000100, 0b11101110, 0b01000100, 0b01000100, //f 0b00000000, 0b01000100, 0b10101010, 0b01100110, 0b11001100, //g 0b10001000, 0b11001100, 0b10101010, 0b10101010, 0b10101010, //h 0b01000100, 0b00000000, 0b01000100, 0b01000100, 0b01000100, //i 0b01000100, 0b00000000, 0b01000100, 0b01000100, 0b10001000, //j 0b10001000, 0b10001000, 0b10101010, 0b11001100, 0b10101010, //k 0b01000100, 0b01000100, 0b01000100, 0b01000100, 0b01000100, //l 0b00000000, 0b11101110, 0b11101110, 0b10101010, 0b10101010, //m 0b00000000, 0b11001100, 0b10101010, 0b10101010, 0b10101010, //n 0b00000000, 0b01000100, 0b10101010, 0b10101010, 0b01000100, //o 0b00000000, 0b11001100, 0b10101010, 0b11001100, 0b10001000, //p 0b00000000, 0b01100110, 0b10101010, 0b01100110, 0b00100010, //q 0b00000000, 0b10001000, 0b11101110, 0b10001000, 0b10001000, //r 0b00000000, 0b01100110, 0b11001100, 0b00100010, 0b11001100, //s 0b01000100, 0b11101110, 0b01000100, 0b01000100, 0b01000100, //t 0b00000000, 0b10101010, 0b10101010, 0b10101010, 0b01000100, //u 0b00000000, 0b10101010, 0b10101010, 0b01000100, 0b01000100, //v 0b00000000, 0b10101010, 0b10101010, 0b11101110, 0b10101010, //w 0b00000000, 0b10101010, 0b01000100, 0b01000100, 0b10101010, //x 0b00000000, 0b10101010, 0b10101010, 0b01100110, 0b11001100, //y 0b00000000, 0b11101110, 0b01100110, 0b11001100, 0b11101110, //z 0b00100010, 0b01000100, 0b11001100, 0b01000100, 0b00100010, //{ 0b01000100, 0b01000100, 0b01000100, 0b01000100, 0b01000100, //| 0b10001000, 0b01000100, 0b01100110, 0b01000100, 0b10001000, //} 0b00000000, 0b11001100, 0b10101010, 0b00000000, 0b00000000 //~ };

//-----------------------------------------------------------------------//Escribe un byte en la pantalla//-----------------------------------------------------------------------void GLCD_Data(BYTE dato){ output_low (GLCD_RW); // Modo escritura output_high (GLCD_A0); // Modo escritura GLCD_SAL (dato); // Coloco el dato a escribir output_high (GLCD_E); // Aviso que hay un dato a escribir delay_cycles( 2 ); // Espero output_low (GLCD_E); // Termino la escritura del dato

Page 137: Introducción al PIC

delay_cycles( 2 ); // Espero}

//-----------------------------------------------------------------------//Envía un comando a GLCD//-----------------------------------------------------------------------void GLCD_Command(BYTE dato){ output_low (GLCD_RW); // Modo comando output_low (GLCD_A0); // Modo comando GLCD_SAL (dato); // Coloco el Comando a escribir output_high (GLCD_E); // Aviso que estoy enviado un comando delay_cycles( 2 ); // Espero output_low (GLCD_E); // Termino el envío del comando delay_cycles( 2 ); // Espero}

//-----------------------------------------------------------------------// Lee un byte de la pantalla//-----------------------------------------------------------------------BYTE GLCD_readBYTE(){ BYTE dato; GLCD_TRIS (0xFF); // Todo el Puerto como entrada de datos output_high (GLCD_RW); // Modo lectura output_high (GLCD_A0); // Modo lectura output_high (GLCD_E); // Activo la lectura delay_cycles( 2 ); // Espero output_low (GLCD_E); // lectura tonta delay_cycles( 2 ); // Espero output_high (GLCD_E); // Activo la lectura delay_cycles( 2 ); // Espero dato = GLCD_ENT(); // Guardo en "dato" el valor devuelto output_low (GLCD_E); // Termino la lectura output_low (GLCD_RW); // Modo comando output_low (GLCD_A0); // Modo comando GLCD_SAL (0x00); // Limpio el Puerto de salida GLCD_TRIS (0x00); // Todo el Puerto como salida de datos return dato; // Retorno de la rutina con el dato}

//-----------------------------------------------------------------------// Limpia el GLCD (pinta toda la pantalla de un color)//-----------------------------------------------------------------------void GLCD_clear(int1 color){ int8 i, j; for(i = 0; i < 7; ++i) // Recorre las 8 paginas (vertical) { for(j = 0; j < 127; ++j) // Recorre los 128 segmentos (horizontal)

Page 138: Introducción al PIC

{ GLCD_Data(0xFF * color); // Enciende/apaga pixeles } }}

//-----------------------------------------------------------------------//Esta funcion inicializa el LCD.//-----------------------------------------------------------------------void GLCD_init(int1 modo){ // Pone los pines de control en el estado correcto. output_low(GLCD_RESET); delay_ms(1); output_high(GLCD_RESET); GLCD_Command(0x40); // Linea Inicial = 0 GLCD_Command(0xA0); // ADC = normal GLCD_Command(0xA3); // LCD-Bias = 1/7 GLCD_Command(0xC0); // output mode GLCD_Command(0xAC); // static indicator = 0 GLCD_Command(0xA4); // Todos los pixeles apagados GLCD_Command(0xA6); // Display no Invertido GLCD_Command(0xE0); // read/modify/write // Si modo = 1 inicializa encendido. Sino, apagado. if(modo == 1) { GLCD_Command(0xAF); } // Enciendo el GLCD else { GLCD_Command(0xAE); } // Apago el GLCD}

//-----------------------------------------------------------------------// Dibuja un pixel//-----------------------------------------------------------------------void GLCD_pixel(int8 x, int8 y, int1 color){ BYTE dato; int8 Posicion_Y; int8 Posicion_X; x = 127-x; y = 63-y;//Primero verificar que X e Y estan dentro de los límites if ((x<=127)&(y<=63)){ Posicion_Y = y / 8; // Calculo La página a donde pertenece el bit Posicion_Y = 0xB0 + Posicion_Y; // Genero el comando para ir a la página GLCD_Command(Posicion_Y); // Ejecuto el comando Posicion_X = x; swap(Posicion_X); Posicion_X &= 0x0F; Posicion_X |= 0x10;

Page 139: Introducción al PIC

GLCD_Command(Posicion_X); Posicion_X = x; Posicion_X &= 0x0F; GLCD_Command(Posicion_X); dato = GLCD_ReadBYTE(); if(color == 1) bit_set(dato, y%8); // Turn the pixel on else // or bit_clear(dato, y%8); // turn the pixel off GLCD_Data(dato); }}

//-----------------------------------------------------------------------// Dibuja una linea desde (x1,y1) a (x2,y2) de color (0 o 1)//-----------------------------------------------------------------------void GLCD_line(int x1, int y1, int x2, int y2, int1 color){ //Declaro variables------------------- signed int x, y, incremento_x, incremento_y, distancia_x, distancia_y; signed long P; int i;

//Calculo las diferencias entre las coordenadas de origen y destino distancia_x = abs((signed int)(x2 - x1)); distancia_y = abs((signed int)(y2 - y1));

//Inicializo x e y con las coordenadas de origen x = x1; y = y1;

//Calculo el sentido de los incrementos (positivos o negativos) //en funcion de la posicion del origen y el destino if(x1 > x2) incremento_x = -1; else incremento_x = 1; if(y1 > y2) incremento_y = -1; else incremento_y = 1;

//Si la distancia horizontal es mayor a la vertical... if(distancia_x >= distancia_y) { P = 2 * distancia_y - distancia_x; for(i=0; i<=distancia_x; ++i) { GLCD_pixel(x, y, color);

if(P < 0) { P += 2 * distancia_y; x += incremento_x; } else { P += 2*distancia_y - 2*distancia_x; x += incremento_x; y += incremento_y;}

Page 140: Introducción al PIC

} }

//Si la distancia vertical es mayor a la horizontal... else { P = 2 * distancia_x - distancia_y; for(i=0; i<=distancia_y; ++i) { GLCD_pixel(x, y, color); if(P < 0) { P += 2 * distancia_x; y += incremento_y; } else { P += 2 * distancia_x - 2 * distancia_y; x += incremento_x; y += incremento_y; } } }}//-----------------------------------------------------------------------

//-----------------------------------------------------------------------// Dibuja un rectángulo desde (x1,y1) a (x2,y2) de color (0 o 1)//-----------------------------------------------------------------------void GLCD_rectangle(int x1, int y1, int x2, int y2, int1 color){ GLCD_line(x1,y1,x2,y1,color); GLCD_line(x1,y1,x1,y2,color); GLCD_line(x1,y2,x2,y2,color); GLCD_line(x2,y1,x2,y2,color);}//-----------------------------------------------------------------------

//-----------------------------------------------------------------------// Dibuja un rectángulo PINTADO desde (x1,y1) a (x2,y2) de color (0 o 1)//-----------------------------------------------------------------------void GLCD_box(int x1, int y1, int x2, int y2, int1 color){ //Declaro variables------------------- int i;

for(i=y1;i<=y2;i++) { GLCD_line(x1,i,x2,i,color); }

}//-----------------------------------------------------------------------

//-----------------------------------------------------------------------// Dibuja un circulo con centro en (x1,y1), radio y color (0 o 1)//-----------------------------------------------------------------------

Page 141: Introducción al PIC

void GLCD_circle(int x1, int y1, int radio, int1 color) { signed int d, x, y;

//Inicializo las variables. d = 1 - radio; x = 0; y = radio;

//Dibujo los cuatro pixeles que "caen" sobre los ejes cartesianos. GLCD_pixel(x1, y1 + radio, color); GLCD_pixel(x1, y1 - radio, color); GLCD_pixel(x1 + radio, y1, color); GLCD_pixel(x1 - radio, y1, color);

//Este es el bucle que pinta los octavos de la circunferencia. while(x < y) { if(d < 0) {d = d + 2 * x + 3;} else {d = d + 2 * (x - y ) + 5; y = y - 1 ;} x = x + 1;

//Pone el punto en cada uno de los "octantes". GLCD_pixel(x1 + x, y1 + y, color); GLCD_pixel(x1 - x, y1 + y, color); GLCD_pixel(x1 + x, y1 - y, color); GLCD_pixel(x1 - x, y1 - y, color); GLCD_pixel(x1 + y, y1 + x, color); GLCD_pixel(x1 - y, y1 + x, color); GLCD_pixel(x1 + y, y1 - x, color); GLCD_pixel(x1 - y, y1 - x, color); } }

//char characterSet[] = " !#$%&'()*+,-./0123456789:;=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~";void GLCD_text35(int8 x, int8 y, char* textptr, int1 color){ int i, j, k; // Loop counters BYTE pixelData[5]; // Stores character data

for(i=0; textptr[i] != '\0'; ++i, ++x) // Loop through the passed string { if((textptr[i] >= ' ') && (textptr[i] <= '~')) memcpy(pixelData, TEXT35[textptr[i]-' '], 5); else memcpy(pixelData, TEXT35[0], 5); // Default to space

if(x+3 >= GLCD_WIDTH) // Performs character wrapping {

Page 142: Introducción al PIC

x = 0; // Set x at far left position y += 5 + 1; // Set y at next position down } for(j=3; j>0; j--, x++) // Loop through character byte data { for(k=0; k<5; k++) // Loop through the vertical pixels { if(bit_test(pixelData[k], j)) // Check if the pixel should be set { glcd_pixel(x, y+k, color); // Draws the pixel } } } }}

¡Gracias Ruben!

[ editar]Restricciones legales de CCS

Como deciamos en la introducción, dentro de las librerias incluidas en CCS se ha incluido una especie de licencia. Concretamente, en el código

fuente de dichas librerias puede leerse algo como lo siguiente:

///////////////////////////////////////////////////////////////////////////// (C) Copyright 1996, 2004 Custom Computer Services //////// This source code may only be used by licensed users of the CCS //////// C compiler. This source code may only be distributed to other //////// licensed users of the CCS C compiler. No other use, //////// reproduction or distribution is permitted without written //////// permission. Derivative programs created using this software //////// in object code form are not restricted in any way. /////////////////////////////////////////////////////////////////////////////

Esto, traducido al español significa más o menos lo siguiente:

"Este código fuente sólo puede ser utilizado por usuarios con licencia del compilador C CCS. Este código fuente sólo puede ser distribuido a otros

usuarios con licencia del compilador C CCS. No se permite ningún otro uso, reproducción o distribución sin el consentimiento escrito. Los

programas derivados, creados utilizando este software, pueden distribuirse como código objeto sin limitantes."

Page 143: Introducción al PIC

CCS - Poniendo un poco de orden.

[editar]Introducción

Programando en CCS.

Mantener ordenadas aquellas librerías que compartimos entre distintosproyectos, puede ser una tarea a veces complicada, sobre todo cuando

desconocemos métodos eficaces para realizar este trabajo. Este problema existe desde hace muchos años y los desarrolladores de compiladores para

C, fueron incluyendo mecanismos eficientes para dar solución a este problema. Las siguientes líneas nos ayudarán a sacar provecho de esas simples,

pero poderosas herramientas.

Tabla de contenidos

 [mostrar]

El uso de las librerías es fundamental para el desarrollo de proyectos en “C”. Sin embargo, cuando tenemos varios proyectos que comparten las

mismas librerías, una gestión deficiente, puede llevarnos al caos.

[editar]¿Donde reside el problema?

En aquellas librerías que necesitamos reutilizar, y que por su naturaleza, tenemos que modificar para adaptarlas a nuestros proyectos. Un claro

ejemplo de este tipo de librerías es la Flex_LCD.c desarrollada por CCS, que nos permite utilizar los muy comunes LCD’s de 2 líneas

Habitualmente, esta librería debe ser modificada para adaptarla a nuestras necesidades en cada proyecto. Esta situación se presenta cuando nuestros

proyectos requieren el uso de distintos microcontroladores o cuando necesitamos determinados módulos del microcontrolador, cuyos

han sido asignados al LCD dentro de Flex_LCD.c. De aquí en adelante, utilizaremos la librería “Flex_LCD.c” como modelo para el resto del

artículo, pero todo lo expuesto es aplicable cualquier librería.

[editar]¿Como se modifican estas librerías para su uso?

Aquí es donde surge el caos entre los distintos proyectos que tenemos entre manos o que hemos realizado. Analicemos las tres alternativas, de uso

más frecuente:

[editar]La forma usual e ineficaz.

Tenemos una única librería ubicada en el directorio de librerías (library), y cuando nos hace falta, la modificamos. Esta suele ser una práctica muy

habitual. Cada vez que empezamos un nuevo proyecto modificamos la librería y la adaptamos a la necesidad del momento.

Pero: ¿qué ocurre cuando debemos modificar y recompilar un proyecto hecho con anterioridad? Si los pines utilizados en el proyecto anterior y el

actual coinciden, no tendremos problema alguno. Sin embargo, es frecuente que no coincidan los pines asignados al LCD del antiguo proyecto con

los del actual. Por lo que si compilamos un proyecto antiguo, es muy probable que no funcione correctamente.

Page 144: Introducción al PIC

La solución común al problema anterior, es tener anotado en algún lugar la asignación de pines para cada proyecto y modificar la librería antes de

compilar cada uno. Como se pude ver, es un proceso tedioso que exige un alto grado de orden para mantener la funcionalidad de nuestros proyectos.

[ editar]El método de la copia

Una alternativa que puede solucionar el problema anterior, es tener una copia de la librería en el directorio de cada proyecto. Luego modificamos la

copia, para ajustarla a la configuración según sea el caso. Esto permite que podamos compilar cada proyecto una y otra vez, sin necesidad de

modificar la librería, ya que cada proyecto tiene una copia adaptada según sus necesidades.

Es una solución también bastante habitual, pero no idónea; ¿qué ocurre si necesitamos modificar la librería porque tenemos una nueva versión de la

misma? Tendremos que ir buscando por el laberinto de directorios de proyectos cada copia de la librería vieja y sustituirla por la nueva.

Se puede argumentar que hoy en día con la velocidad de proceso y las herramientas de búsqueda de las PC, este trabajo no será en extremo tedioso.

Pero aunque lográsemos encontrar y sustituir todas las copias en un corto espacio de tiempo, tendremos otro problema añadido, y es que cada copia

de la librería está “personalizada” para su proyecto. La situación anterior nos obliga a reconfigurar la nueva versión de la copia, de acuerdo a la

configuración de cada proyecto, trabajo que hicimos la primera vez que copiamos la librería hacia el directorio del proyecto.

[ editar]Utilizando las directivas del pre-procesador

Esta es la forma correcta y eficaz de hacerlo. Este método es el que adoptaremos y nos permitirá manejar las librerías sin sufrir dolores de cabeza.

Consiste en definir la asignación de pines, en algún lugar fuera de la librería, bien en fichero aparte, o bien en el programa principal del proyecto.

¿Cómo podemos modificar la asignación de pines fuera de la librería? La forma de hacerlo es utilizando las directivas del pre-procesador

Las directivas del pre-procesador son un conjunto de instrucciones que se utilizan para indicarle al compilador, que debe hacer, ante determinadas

situaciones. Aunque generalmente muchos programadores desconocen su utilidad con profundidad, estas directivas son una herramienta muy

poderosa para crear variables, reservar memoria, definir constantes, utilizar macros e incluso indicarle al compilador que secciones de código debe

compilar y enlazar. En nuestro caso, utilizaremos las directivas del pre-procesador #ifndef <identifier> … #endif.

Cuando el pre-procesador se topa con la directiva #ifndef, comprueba si ya existe el identificador <identifier>, si éste no existiese, entonces crea uno

con ese nombre, lo agrega a su lista de identificadores y procesa el código ubicado entre #ifndef y #endif, en caso que el identificador [[<identifier>]]

exista, se ignora todo el código ubicado en el cuerpo de la llamada a la directiva.

La técnica descrita anteriormente es precisamente la que vamos a utilizar para gestionar de manera eficiente, el uso de nuestras librerías. Al revisar la

sección de Flex_LCD, donde se asignan los pines al microcontrolador, nos topamos con el siguiente código:

#define LCD_DB4 PIN_B4#define LCD_DB5 PIN_B5#define LCD_DB6 PIN_B6#define LCD_DB7 PIN_B7

Page 145: Introducción al PIC

#define LCD_RS PIN_C0#define LCD_RW PIN_C1#define LCD_E PIN_C2

Ahora simplemente metemos esta sección de código en el cuerpo de una llamada a #ifndef con nombre de identificador _FLEX_LCD

resultante quedará de la siguiente forma:

#ifndef _FLEX_LCD #define _FLEX_LCD #define LCD_DB4 PIN_B4 #define LCD_DB5 PIN_B5 #define LCD_DB6 PIN_B6 #define LCD_DB7 PIN_B7 #define LCD_RS PIN_C0 #define LCD_RW PIN_C1 #define LCD_E PIN_C2#endif

Si no definimos nada en el programa principal o en su fichero de cabecera, el pre-procesador asignará a la LCD los pines según el código de la

librería Flex_LCD. Si queremos modificar la asignación de pines para nuestro proyecto, escribiremos en el fichero principal de nuestro proyecto, o

en su fichero de cabecera, el siguiente fragmento de código:

#define _FLEX_LCD#define LCD_DB4 PIN_C4#define LCD_DB5 PIN_C5#define LCD_DB6 PIN_C6#define LCD_DB7 PIN_C7#define LCD_RS PIN_A0#define LCD_RW PIN_A1#define LCD_E PIN_A2#include Flex_LCD.c

Esto hace que se asignen los pines del microcontrolador a la LCD tal y como se especifica en nuestro programa principal y que la definición de la

librería sea ignorada. Como puede verse, la librería ha sufrido un pequeño cambio que nos ayudará a mantener gestionado su uso y nos facilitará la

vida a partir de este momento. Es muy importante que esta asignación se haga antes de incluir la librería (#include Flex_LCD.c), ya que de no

hacerlo así, el pre-procesador asignará los pines según la definición que se hace dentro de la librería y se producirá un conflicto con la definición

realizada en el programa principal.

Con este método, solo tendremos una librería para todos nuestros proyectos y la personalización se realizará dentro de cada proyecto; sin que por ello

tengamos que hacer copias o modificar el fichero original. Además, la librería estará perfectamente localizable dentro de su directorio, por lo que si

obtuviésemos una nueva versión, bastará con actualizar y modificar una sola copia.

Otra razón para utilizar esta forma de proceder, es la posibilidad de reconocer la dependencia entre los distintos archivos de nuestros proyectos o

entre distintas librerías. Por ejemplo, si creamos una librería que utilice el display como salida, podremos escribir en el código de nuestra librería:

Page 146: Introducción al PIC

#ifndef _FLEX_LCD #error Es necesario incluir la librería Flex_LCD#endif

De esta forma enviamos un mensaje de error para avisar que es preciso incluir una o varias librerías.

Page 147: Introducción al PIC

Compilador PCW CCSEscrito por biblioman   

Vamos a ver en este tutorial algunas caracteristicas que presenta el compilador PCW CCS y que hacen de él una buena opción para

elegirlo como compilador de C para programar Microcontroladores PIC.       Algunas de esas características son: Al compilar genera un código máquina muy compacto y

eficiente. Se integra perfectamente con MPLAB y otros

simuladores/emuladores como PROTEUS para el proceso de depuración.

Incluye una biblioteca muy completa de funciones precompiladas para el acceso al hardware de los dispositivos (entrada/salida, temporizaciones, conversor A/D, transmisión RS-232,bus I2C….,etc.

Incorpora drivers para dispositivos externos, tales como pantallas LCD, teclados numéricos, memorias EEPROM, conversores A/D, relojes en tiempo real, etc.(los drivers son pequeños programas que sirven de interfaz entre los dispositivos hardware y nuestro programa).

Permite insertar partes de código directamente en Ensamblador, manteniendo otras partes del programa en C.

Características del lenguaje C para este compilador El lenguaje C estándar es independiente de cualquier plataforma. Sin embargo, para la programación de microcontroladores es necesario disponer de determinados comandos que se refieran a partes específicas de su hardware, como el acceso a memoria, temporizadores, etc. Por este motivo, además de los comandos, funciones y datos del lenguaje ANSI C, el compilador PCW incluye bibliotecas que incorporan determinados comandos que no son estándar, sino específicos para la familia de microcontroladores PIC. Éstos son básicamente de dos tipos: directivas del preprocesador y funciones precompiladas Directivas del preprocesador. El compilador PCW dispone de 7 tipos básicos de directivas:

Directivas derivadas del estándar de C, que permiten, entre otras funciones, un control básico del código y del flujo en el

Page 148: Introducción al PIC

proceso de compilación:

#DEFINE#ELIF#ELSE#ENDIF#ERROR#IF#IFDEF#IFNDEF#INCLUDE#LIST#NOLIST#PRAGMA#UNDEF Directivas asociadas a las bibliotecas precompiladas, que

proporcionan al compilador información relacionada con estas bibliotecas:

#USE DELAY#USE FAST_IO#USE FIXED_IO#USE I2C#USE RS232#USE STANDARD_IO Directivas relacionadas con la especificación del dispositivo,

por un lado, para definir los mapas de memoria y el juego de instrucciones, y por otro, incluir información necesaria para la programación del dispositivo en los ficheros de salida de la compilación:

#DEVICE#ID#FUSES#TYPE Directivas de cualificación de funciones, para identificar

características especiales de una función:#INLINE#INT_DEFAULT#INT_GLOBAL#INT_xxxxx#SEPARATE  Directivas de control del compilador, para definir opciones

referidas a la compilación del código del programa:#CASE#OPT#ORG#PRIORITY Directivas de control de la memoria del microcontrolador,

para gestionar y reservar el uso de determinadas zonas de memoria para variables:

Page 149: Introducción al PIC

#ASM#BIT#BYTE#ENDASM#LOCATE#RESERVE#ROM#ZERO_RAM Identificadores predefinidos. Todas las directivas citadas

hasta ahora, son comandos destinados a ser interpretados por el compilador, no por el microcontrolador. Dentro del término genérico de directiva se incluyen, además de estos comandos, unas variables que contienen información sobre el proceso de compilación. Estas variables son lo que se denominan identificadores predefinidos del compilador:

__DATE____DEVICE____PCB____PCH____PCM__ En un programa, las directivas se reconocen fácilmente porque comienzan por el símbolo #, mientras que los identificadores empiezan y acaban por doble subrayado (__).

Funciones precompiladas. Se puede facilitar considerablemente la tarea de programación si no es necesario construir por nosotros mismos aquellas funciones que son de utilización más frecuente, como leer la entrada de un teclado o imprimir un determinado mensaje en una pantalla LCD conectada como salida.Existen funciones en C incluidas en el compilador PCW para manejar los diferentes recursos del microcontrolador, desde el bus I2C hasta el conversor A/D.

Utilidades adicionales El entorno PCW incluye, además del IDE y del compilador, una serie de utilidades adicionales con las que se amplían las posibilidades de éste, y que se encuentran en los menús View y Tools de la barra de menús, veamos algunas de ellas:

Monitor del puerto serie: Consiste en un terminal que monitoriza la entrada y la salida del puerto serie del computador.

Selección de dispositivos (Device Selection Tool): Esta utilidad consta de una base de datos con los dispositivos que puede programar el compilador, incluyendo todas sus características hardware, de manera que se puede emplear para buscar aquellos dispositivos que cumplan una serie de propiedades comunes.

Page 150: Introducción al PIC

Editor de dispositivos (Device Editor): Este programa también emplea la base de datos de dispositivos, y permite editar los componentes para modificar sus características hardware, así como añadir nuevos dispositivos o eliminar algunos de ellos.

Conversor numérico: Esta utilidad realiza conversiones entre los tipos de datos unsigned, signed, hex y float.

Extracción de datos de calibrado: Esta opción permite leer los datos de calibración existentes en la memoria de programa de un determinado dispositivo. Estos datos contienen información particular de cada microcontrolador a su salida de fábrica, y se refieren a posibles problemas especiales que pudieran haber tenido lugar durante el desarrollo y fabricación. Mediante esta opción es posible leer estos datos y grabarlos en un fichero .H o .C que incorporará una directiva #ROM para dicho dispositivo, con lo que cada vez que se programe el microcontrolador se incluirán estos datos de calibrado.

Desensamblador: Esta opción lee un fichero en Código máquina y lo traduce a su equivalente en Ensamblador, con lo que se podría insertar este código en un programa en C, mediante las directivas #ASM y #ENDASM.

Puede que haya alguna herramienta de mas o de menos, eso dependerá de la versión de compilador que estemos utilizando

Ahora vamos a ver alunas consideraciones a tener en cuenta cuando vayamos a programar:

En el tutorial que estamos siguiendo sobre programación de Microcontroladores en lenguaje C Hemos visto como hacer algunos ejemplos en C (la verdad es que pocos de momento ), pero no hemos visto nada sobre aspectos de optimización de los programas. La optimización suele ser necesaria en muchas ocasiones, puesto que la memoria del microcontrolador es un recurso escaso, y puede ocurrir que un programa que en un principio ocupa una cantidad de memoria mayor que la disponible, tras una optimización del código reduzca su tamaño de manera que sí pueda programarse. A continuación se indican algunas de las características que incorpora el compilador sobre este aspecto:  Inserción de código Ensamblador: Mediante las directivas

#ASM y #ENDASM es posible insertar código Ensamblador directamente en el código C, con lo que determinados procedimientos se pueden implementar directamente en Ensamblador, con el ahorro de código y tiempo de ejecución que ello implica.

Gestión automática de páginas de código: los microcontroladores PIC disponen de varias páginas de memoria de programa, lo que en la programación manual supone tener en cuenta si los saltos se producen a otra página de código diferente de la activa, y modificar las páginas de código según

Page 151: Introducción al PIC

corresponda, ( y si no que se lo digan a los que programan en Ensamblador).  Este aspecto es gestionado de manera automática por el compilador, el cual, de manera transparente al usuario, insertará las instrucciones necesarias para el cambio de página de código. Además,  durante el proceso de enlazado se analiza el código objeto, de manera que aquellas funciones que son llamadas frecuentemente son situadas en la misma página de código que la función que las llama, disminuyendo de este modo el tamaño del código y el retardo producido por la llamada a la función.

Gestión automática de bancos de memoria: El compilador también gestiona de manera automática el cambio entre bancos de memoria RAM, y trata de minimizar el cambio entre bancos, intentando agrupar las variables locales utilizadas en cada función dentro de un mismo banco.

Mapeo de la memoria del microcontrolador desde el código C: Las directivas #BIT y #BYTE permiten asignar variables del código C a determinadas direcciones de la memoria del microcontrolador, evitando de este modo que el compilador asigne automáticamente variables a zonas de memoria que interesa mantener libres. Esta característica también se puede realizar con otros tipos de datos, como las estructuras.

Mapeo de tipos de datos de tamaño bit: El compilador permite utilizar datos de tamaño 1, 8 y 16 bits, y 32 bits en notación de coma flotante. De estos datos, especial interés tienen los datos de tamaño 1 bit (tipo Short Int), que permite utilizar de manera muy eficiente  la memoria del microcontrolador. Por ejemplo, en lugar de utilizar una variable de tipo #BYTE para implementar un indicador o flag, es posible utilizar una variable de tipo #BIT, con lo que la memoria ocupada es menor y el código relacionado con esta variable se optimiza. Además, las estructuras y uniones también permiten definir campos de bits, optimizando el almacenamiento de información de estos tipos de datos.

Almacenamiento de constantes en memoria de programa: Los tipos de datos constantes, las cadenas de texto y las matrices o arrays son almacenados en la memoria de programa del microcontrolador, en lugar de en la memoria de datos. Como consecuencia, y debido a la arquitectura Harvard de los PIC, en la que los segmentos de memoria de programa y datos son independientes, no es posible tratar las zonas de memoria de programa como datos, por lo que no se permite el empleo de punteros a arrays de constantes, ni tampoco a funciones, una nota a tener en cuenta para los que saben programar en C, pero desconocen las características de este compilador.

Soporte de punteros y referencias: El compilador PCW permite el uso de punteros de 8 y 16 bits. Además, también permite el paso de parámetros por referencia de variables, lo que proporciona la misma potencia que los punteros, mejorando al mismo tiempo la legibilidad del código (esto lo veremos con ejemplos en el curso de C).

Page 152: Introducción al PIC

Eficiente implementación de variables y funciones: La eficiencia de los algoritmos de optimización empleados en este compilador permite que, en el caso de las variables, éstas sean asignadas a zonas de memoria que se emplee la menor memoria posible de forma global, reutilizando para ello posiciones de memoria. Por ejemplo, el compilador asigna de manera automática a una misma dirección de memoria varias variables definidas como locales en funciones diferentes a main, ya que en ningún momento podrá existir más de una de estas variables de manera simultánea, pues su valor dejará de existir al salir de la función donde se encuentran definidas. En cuanto a las funciones, es posible realizar anidaciones de funciones en niveles más profundos que el permitido por la pila hardware, debido a una implementación eficiente de las llamadas a estas funciones.

Generación del código estrictamente necesario: En la compilación del código objeto final sólo se incluyen aquellas funciones de las bibliotecas precompiladas que son utilizadas en el programa. De la misma forma, las funciones de usuario que no son llamadas nunca y las sentencias que no se pueden utilizar debido a la lógica del  programa no son incluidas en el código precompilado.