Está en la página 1de 105

Programando el Microcontrolador

PIC18F4550 en Lenguaje C con el


compilador CCS en MPLAB.

PIC18F4550
2

Contenido.

1. El Microcontrolador PIC18F4550.…………………………………………….…… 4
1.1. La familia de Microcontroladores PIC18F.

2. En Ambiente de Programación.………………………………………………….... 9
2.1. El entorno de programación de MPLAB.
2.2. Archivos de cabecera para el PIC18F4550 en C.
2.3. Descarga de los Programas en el PIC18F4550.

3. Declaración de Variables y Algunas Funciones e Instrucciones en C con el


compilador CCS........……………………………………………………………….... 21
3.1 Declaración de variables.
3.2 Instrucciones de C para la manipulación de bit y byte.
3.3 Las instrucciones de C para la creación de rutinas de retardo.
3.4 Funciones estándar de C para la manipulación de caracteres y cadenas de caracteres.

4. Los Puertos Digitales de I/O.………………………………………………………. 29


4.1 Funciones para controlar y configurar los puertos digitales de E/S.
4.2 PRACTICA E4.1. El Diodo Led Intermitente.
4.3 PRACTICA E4.2. Secuencia de Luces con Velocidad Variable.
4.4 PRACTICA E4.3. Múltiples Secuencias de Luces con Velocidad Variable.
4.5 PRACTICA E4.4. Desplegué de Números en Display de 7 Segmentos con Cuatro Dígitos.

5. Las Interrupciones Externas.…………..…………………………………………. 43


5.1 Funciones y directivas para configurar y controlar las interrupciones externas.
5.2 PRACTICA E5.1. Múltiples Secuencias de Luces con Velocidad Variable utilizando
Interrupciones Externas.

6. El Convertidor Analógico a Digital de 10 bits.…………………..…………….. 49


6.1 Funciones de C para configurar y manipular el convertidor A/D.
6.2 PRACTICA E6.1. Vúmetro digital con el convertidor A/D sin interrupción.
6.3 PRACTICA E6.2. Vúmetro digital con el convertidor A/D con interrupción.
6.4 PRACTICA E6.3. Medición de Multivariable con el convertidor A/D con interrupción.

7. El LCD y Teclado Matricial.….…………………………………………………….. 59


7.1 El display LCD de 16 x 2 líneas.
7.2 PRACTICA E7.1. Desplegar mensajes y variables en la LCD.
7.3 El Teclado Matricial.
7.4 PRACTICA E7.2. Visualización de los datos de un teclado matricial 3x4 en la LCD.
7.5 Creación de una librería para un teclado matricial de 4x4.
7.6 PRACTICA E7.3. Visualización de los datos de un teclado matricial 4x4 en la LCD.

8. La Memoria EEPROM de Datos.…….……………………………….……………. 74


8.1 Funciones para Leer y Escribir en la EEPROM de Datos.
8.2 Practica E8.1. Control de Acceso a través del teclado.

9. Los Timers. .………………………………………………………………………..… 79


3

9.1 El Timer 0.
9.1.1 Funciones de C para configurar y operar el timer 0.
9.1.2 Practica E9.1. Generación de una señal de 5 Hz con el TIMER0.
9.2 El Timer 1.
9.2.1 Funciones de C para configurar y operar el timer 1.
9.2.2 Practica E9.2. Un Reloj muy exacto con el TIMER1.
9.3 El Timer 2.
9.3.1 Funciones de C para configurar y operar el timer 2.
9.3.2 Practica E9.3. Generación de una señal de 1kHz con el Timer2.
9.4 El Timer 3.
9.4.1 Funciones de C para configurar y operar el timer 3.

10. Los Módulos CCPx de Captura, Comparación y PWM.……..……………….. 95


10.1 Funciones de C para configurar y operar los módulos Comparadores/Capturadores/PWM.
10.2 El Modulo CCPx en Modo de Captura.
10.2.1 Practica E10.1. Medición del Ancho de Pulso con el CCP1.
10.3 El Modulo CCPx en Modo de Comparador.
10.3.1 Practica E10.2. Generación de una señal de 2 KHz con el CCP2.
10.4 El Modulo CCPx en Modo de PWM.
10.4.1. Practica E10.3.Generación de señal PWM proporcional a la señal del ADC.

11.Bibliografía………………………………………………………………………….…. 106

1. El Microcontrolador PIC18F4550.
4

Los microcontroladores PIC’s de Microchip son los número uno en ventas de microcontroladores.

 Ventajas de los microcontroladores PIC de Microchip:


 Amplia gama: gran variedad de familias que permiten adaptar el microcontrolador a las necesidades
de cada aplicación.
 Herramientas de desarrollo comunes.
 Gran variedad de unidades funcionales embebidas (temporizadores, USART, I2C, SPI, unidades de
comparación/captura/PWM, Convertidores A/D, USB, receptores/transmisores de RF, Ethernet,
etc...)
 Buen soporte (datasheet, libros, notas de aplicación, seminarios, mucha información disponible en
internet).

 Familias de microcontroladores PIC.

 PIC10: microcontroladores de 8 bits, de bajo costo, de 6 pines y bajas prestaciones.


 PIC12: microcontroladores de 8 bits, de bajo costo, de 8 pines y bajas prestaciones.
 PIC 16: microcontroladores de 8 bits, con gran variedad de número de pines y prestaciones medias.
 PIC18: microcontroladores de 8 bits, con gran variedad de número de pines y prestaciones
medias/altas.
 PIC24: microcontroladores de 16 bits y de prestaciones altas.
 dsPIC’s.

1.1. La Familia de Microcontroladores PIC18F.

Las características fundamentales de esta familia son:

 Arquitectura RISC avanzada Harvard: 16 bits con 8 bits de datos.


 77 instrucciones.
 Desde 18 a 80 pines.
 Hasta 64K bytes de programa.
 Multiplicador Hardware 8x8.
 Hasta 3968 bytes de RAM y 1KBytes de EEPROM.
 Frecuencia máxima de reloj 48Mhz. hasta 10 MIPS.
 Pila de 32 niveles.
 Múltiples fuentes de interrupción.
 Periféricos de comunicación avanzados (CAN y USB).

Lista comparativa de los microcontroladores PIC18F2455, PIC18F2550, PIC18F4455, PIC18F4550


5

Arquitectura externa del PIC18F4550.

El microcontrolador PIC18F4550, cuenta con los siguientes periféricos:

 Puertos digitales de entradas / salidas de propósito general:


o Puerto RA con 7 pines (RA0 al RA6).
o Puerto RB con 8 pines (RB0 al RB7).
o Puerto RC con 7 pines (RC0 al RC2 y RC4 al RC7).
o Puerto RD con 8 pines (RD0 al RD7).
o Puerto RE con 3 pines (RE0 al RE2).
6

 Convertidor analógico digital de 10 bits:


o 13 terminales de convertidores A/D (AN0 al AN12).
 Temporizadores de 8 y 16 bits:
o Timer (Timer 0 al Timer 3).
 Modulo Comparador/Capturador y PWM (CCP2).
 Modulo Comparador/Capturador y PWM Mejorado (ECCP1).
 Modulo maestro de comunicación serial síncrona (MSSP):
o Modulo maestro SSP.
o Módulo SPI.
o Módulo I2C.
 Modulo universal de comunicación serial síncrona (EUSART).
 Bus Universal Serial (USB V2.0).
 Modulo comparador.
 Modulo Detector de voltaje Alto/Bajo (HLDV).
 Memoria para datos no volátiles (EEPROM de 256 bytes).
 Interrupciones externas:
o Interrupciones externas (INT0 al INT2).
o Interrupciones de cambio del PORTB (KBI0 al KBI3).
 Puerto paralelo de Comunicación Continua (SPP0 al SPP7).
 Modulo Oscilador muy avanzado.
7

Arquitectura interna del PIC18F4550.


8

Arquitectura del módulo oscilador del PIC18F4550.


9

2. El Ambiente de Programación.
El software utilizado para realizar los programas del microcontrolador es MPLAB IDE. Este software lo podemos
descargar de manera gratuita de la página web oficial de la firma Microchip Inc. en www.microchip.com. MPLAB
IDE es un editor modular, donde puedes desarrollar los códigos de programas para todos los
microcontroladores de Microchip de 8, 16 o 32 bits que hay hasta el momento.

Para realizar la programación de los microcontroladores PIC en un lenguaje de nivel intermedio como el
lenguaje C, es preciso utilizar un compilador de C. Dicho compilador nos genera archivos en formato Intel-
hexadecimal (*.hex), que es el formato necesario para programar (utilizando un programador de PIC) un
microcontrolador de 6, 8, 18 ó 40 patillas. El compilador de C, que utilizamos es el PCW de la casa CCS Inc. A
su vez, el compilador lo integraremos en un entorno de desarrollo integrado (MPLAB IDE) que nos va a permitir
desarrollar todas y cada una de las fases en que se compone un proyecto, desde la edición hasta la
compilación pasando por la depuración de errores. La última fase, a excepción de la depuración y retoques
hardware finales, será programar el PIC.

Al igual que el compilador de Turbo C, éste "traduce" el código C del archivo fuente (.C) a lenguaje máquina
para los microcontroladores PIC, generando así un archivo en formato hexadecimal (*.HEX), siendo este
archivo *.hex, el que se descarga en el microcontrolador.

2.1. El entorno de programación de MPLAB.

Una vez instalado MPLAB IDE y el compilador PCW de CCS en su computadora. Abre MPLAB, este presentara
una pantalla como la que muestra la figura.

En este entorno se programa en base a proyectos, para iniciar un proyecto damos un clic en la opción Project y
seleccionamos de la lista de opciones Project Wizard…, lo que nos abre la pantalla del asistente para la
creación de un nuevo proyecto.
10

De la ventana Project Wizard presionamos Siguiente >, lo que nos permite seleccionar el microcontrolador con
el que vamos a trabajar. Podemos elegir desde los microcontroladores de 8 bits como los PIC 10, PIC12,
PIC14, PIC16, PIC18, los microcontroladores de 16 bits los PIC24 hasta los dsPIC30 y dsPIC33.

Ya elegido el microcontrolador con el que trabajaremos (en nuestro caso es el PIC18F4550), presionamos
Siguiente >, para poder elegir el lenguaje de programación que utilizaremos para crear nuestro código.

Para programar en lenguaje C, de la lista desplegable elegimos la opción CCS C Compiler for
PIC10/12/14/16/18/24/dsPIC30/dsPIC33, lo que nos mostrara la herramienta CCS C compiler (ccsc.exe),
elegimos esta y presionamos Siguiente > nuevamente.
11

En la ventana Step Three, debemos proporcionar un nombre y una ubicación a nuestro proyecto,
asegurándonos de crear una carpeta nueva para guardar en esta los archivos que se generan en nuestro nuevo
proyecto.

Para esto presioné el botón Browse… y se abrirá una nueva venta que nos permitirá dar la ubicación en donde
se guardara nuestro proyecto y además poder asignarle un nombre (se recomienda que el nombre sea corto y
refleje la que el proyecto realizara).

Una vez elegida la ubicación, creado una nueva carpeta para nuestro proyecto y guardado en este lugar nuestro
proyecto, presionamos Siguiente >, lo que nos presenta las opciones para poder agregar o remover archivos a
nuestro proyecto.

Aquí se recomienda agregar un archivo *.c, que contenga algún código el lenguaje C, para no partir de cero en
nuestro nuevo proyecto. Si no se desea agregar algún archivo no hay ningún problema solo presionen
Siguiente> para continuar.
12

Una vez agregados los archivos a tu proyecto, presionamos Siguiente >, y finamente nos presenta un sumario
de características sobre nuestro proyecto, como el dispositivo, el compilador, la ubicación del proyecto y nombre
de nuestro proyecto. De esta última pantalla presionamos Finalizar,

Al presionar finalizar, cerramos el asistente para la creación de un nuevo proyecto y nos mostrara en la pantalla
inicial de MPLAB IDE nuestro proyecto.

El entorno de MPLAB IDE, nos muestra nuestro proyecto, en la parte izquierda de la pantalla y del lado derecho
la pantalla de salidas de compilación donde se muestran los errores y warning de compilación.
13

Ahora comencemos a programar, bien lo primero que necesitamos saber es que, si no agregamos a nuestro
proyecto ningún archivo *.c, tendremos que elegir desde el menú File -> New, para crear un nuevo archivo en
blanco, donde escribiremos nuestro código fuente.

Si nos damos cuenta este nuevo archivo, no tiene ninguna extensión,para darle la extensión la vamos a
guardar como Nombre.c y lo guardaremos dentro de la carpeta del proyecto. Después agregaremos este
archivo Nombre.c a nuestro proyecto dando clic con botón derecho sobre carpeta Source File de nuestro
proyecto y eligiendo la opción Add Files…, esto nos abrirá una pantalla de dialogo, que permite agregar a
nuestro proyecto este archivo que acabamos de guardar en la carpeta de nuestro proyecto como Nombre.c.
14

Una vez agregado este archivo a nuestro proyecto, lo podemos visualizar como un archivo dentro de la carpeta
Source Files de nuestro proyecto.

En este archivo se debe colocar el código en lenguaje C de nuestra aplicación, pero antes debemos saber algo
muy importante. Todas nuestra aplicaciones deben contener al inicio del archivo *.c ciertas sentencia conocidas
como archivos de cabecera.

2.2. Archivos de cabecera para el PIC18F4550 en C.

Los archivos de cabecera que siempre deben contener nuestro archivo.c son:

/*CONFIGURACIÓN PIC*/

#include"18f4550.h" // Incluye la librería del PIC18F4550 que contiene


// Las definiciones de los registros y bits para la
// Configuración de la CPU y los periféricos.
#use delay(clock=48000000) // Ajusta la velocidad de la CPU a 48 MHz.
#build(reset=0x02000,interrupt=0x02008) // Se asigna la dirección de inicio del vector reset y
// de las interrupciones en general.
#org 0x0000, 0x1FFF { } // Se declara el área de FLASH para nuestro programa.

void main( ) { // Inicio de nuestro programa principal.

// En esta área se declaran e inicializan variables locales y periféricos.

for( ; ; ) { // Ciclo infinito que contiene código que se


// ejecutara por siempre.
// Aquí va el cuerpo de la instrucciones a ejecutar por siempre.
}
}

La forma de declarar variables y el formato de programación es idéntica a la programación estructurada en C


con algunas excepciones muy sutiles.

2.3. Descarga de los Programas en el PIC18F4550.

El microcontrolador PIC18F4550, cuenta con un puerto USB 2.0 compatible con Windows, que podemos utilizar
como interfaz para descargar los programas en la memoria Flash (usando los Bootloader), mediante la ayuda
del software proporcionado por el compilador PCW de la casa CCS Inc., que podemos encontrar en la carpeta
15

PICC, el software para descargar nuestro archivo *.hex, se llama Serial Input/Output Monitor y se accede a él
mediante la aplicación Siow.exe. El icono de la aplicación Soiw.exe es:

El circuito que utilizamos para descargar los programas en el PIC18F4550 por medio del puerto USB
(Bootloader) es:

A continuación se muestra una imagen real de la tarjeta del programador del PIC18F4550 que se desarrolló.
16

Para descargar el programa al microcontrolador, realiza los siguientes pasos:

1. Conecta la tarjeta del programador del PIC18F4550, en el puerto USB de tu PC o laptop, para que la
tarjeta entre en modo AUTO PROGRAMADOR (Bootloader), oprime el botón RESET y el botón BOOT
simultáneamente, después de un segundo suelte el botón RESET (manteniendo el botón BOOT
presionado) y después de un segundo suelte el botón BOOT, en ese momento la tarjeta será detectada
por tu computadora, la primera vez te pedirá la ruta de driver para instalarlos.

Ya solo busca manualmente los driver (Ver la secuencia de instalación).

Seleccione la opción, No por el momento y presione Siguiente >.


17

Elija la opción Instalar desde una lista o ubicación especifica (avanzado) y presione Siguiente >.

La ubicación del driver es C:/Archivos de programas/PICC/Drivers/NT,2000,XP,VISTA,7, presione


Aceptar y Siguiente >.

Lo que instalara los controladores y terminamos con cerrar.


18

Observe que instalo el Puertos (COM y LPT) CCS CDC Bootloader (COM8) lo que indica que nuestra
tarjeta fue aceptada correctamente. Podemos configurar el COM que deseemos o nos quedamos con el
que asigno.

2. Ahora abrir la aplicación Siow.exe, este programa nos muestra la siguiente ventana:

Donde elegiremos el puerto COM que se instaló para nuestra tarjeta y todos los demás parámetro que
se alcanzan a ver.

Presionamos OK para aceptar la configuración. Lo que nos presenta la ventana del programa Serial
Input/Output Monitor y nos indica que la tarjeta está conectada y podemos desconectarla desde el
19

botón Disconnect, que se encuentra en la parte inferior derecha de esta ventana o simplemente
presionando el botón Reset de la tarjeta Programadora del PIC.

Con la ventana del software Serial Input/Output Monitor abierta (Indicándonos Disconnect), y la tarjeta
del programador conectada en el puerto USB de su computadora. Ya podemos descargar el archivo
*.hex al microcontrolador y programarlo.

Presionando el botón File  Dowload Sofware, se abrirá una ventana de búsqueda que nos permite
ubicar el archivo *.hex, que cargaremos en el microcontrolador.
20

Una vez seleccionado el archivo *.hex, que se desea descargar, presione el botón Abrir y el software
automáticamente comienza a descargar el código en el microcontrolador.Al terminar el proceso de
Borrando-Programando-Verificando, automáticamente el microcontrolador comienza a correr el
programa y el software queda deshabilitado (Connect). Debes repetir esta secuencia cada vez que
quieras descargar un programa al microcontrolador.

Nota: Recuerde para entrar en modo Bootloader, Conectar la tarjeta del programador del PIC18F4550, en el
puerto USB de tu PC o laptop, para que la tarjeta entre en modo AUTO PROGRAMADOR (Bootloader), oprime
el botón RESET y el botón BOOT simultáneamente, después suelte el botón RESET (manteniendo el botón
BOOT presionado) y después de un segundo suelte el botón BOOT, en ese momento será detectada la tarjeta
programadora por la computadora.

Es importante saber, que puerto COM nos asignó la PC, para direccionar el mismo en el software de descarga
(Soiw.exe).

2.4. Modo Bootloader en los Microcontroladores.

Efectivamente es de mucha utilidad poder "Programar tu PIC si necesidad de un Programador Universal". Esto
que digo es relativo, ya que al menos una vez debes programar como siempre un código residente en memoria
flash, que se encargara de tomar los datos y re-programar su propia memoria Flash.

Mejor dicho, hablemos que es un Bootloader.

El término Bootloader hace referencia a un pequeño programa que se realiza para determinado
microcontrolador, ya sea PIC, Freescale, Atmel, etc. Este código lo que hace es tomar los datos que se le
enviaran por puerto serial UART, USB, Ethernet, el que sea que pueda enviar una cadena de Bytes al
microcontrolador. El microcontrolador va tomando esos datos que le van llegando y los va "Programando" en su
propia memoria Flash.
Que obtenemos con eso, que puedes actualizar en cualquier momento el código del PIC. Utilidades como estas
son de gran utilidad para desarrollar un producto o un proyecto y el usuario final no necesita un programador de
PIC para poder actualizarlo. Un fiel y gran ejemplo de esto es lo que implementan los programadores
("Quemadores") profesionales de PICs.

El programador te llega a casa totalmente funcional, pero en cualquier momento aparece una nueva referencia
de microcontrolador y si quieres actualizar el programador, solo te bajas de internet la actualización, conectas el
micro al USB y listo, le envías la actualización al programador. Listo él se autoprogramó la FLASH y ya tiene la
nueva referencia del micro para trabajar con este.

Lo anterior permite sacar las siguientes conclusiones:

 Es necesario tener un programador para usarlo solo una vez y así grabarle el programa residente en
memoria FLASH. Ya después de esto se usa el Bootloader para grabar la memoria flash del
microcontrolador.
 El programa residente ocupa memoria flash y por ende tienes un poco menos de memoria para la
aplicación real del microcontrolador. No es mucha la que quita, pero hay que tener eso claro.
 Por ningún motivo se puede sobrescribir las posiciones de memoria FLASH en la que se encentra el
programa residente Bootloader. Si se borra por algún error, pierdes el Bootloader y toca nuevamente
programarlo con un grabador externo.

No todos los micros pueden soportar Bootloader, esto se limita a aquellos que tienen la capacidad de
autograbar su memoria FLASH. Por ejemplo el 16F84 no lo permite, por eso sacaron la versión PIC16F628A
que si lo deja y además tiene mejores prestaciones. Ya con lo anterior en teoría, PODEMOS GRABAR EL PIC
SIN NECESIDAD DE UN PROGRAMADOR. Bueno, en este trabajo en el capítulo 14, trataremos el tema de
cómo implementar un Bootloader en un PIC18F4550, usando el puerto USB 2.0 Full-Speed que es su máximo
atractivo.
21

3. Declaración de Variables y Algunas Funciones e instrucciones en C con el


compilador CCS.

3.1 Declaración de variables.

Con el compilador CCS en MPLAB para los microcontroladores PIC´s los tipos de datos básicos son:

Tipo Tamaño Unsigned Signed Dígitos


int1 1 bit 0–1 - 1
int8 8 bits 0-255 -128 a 127 2–3
int16 16 bits 0 – 65535 -32768 a 32767 4–5
int32 32 bits 0 – 4294967295 -2147483648 a 2147483647 9 – 10

Tipos estándar de C Tipo definidos


short int1
char unsigned int8
int int 8
long int 16
long long int 32
float float32

Todos los tipos de datos excepto los flotantes (float), short y char, por defecto son signados, sin embargo puede
ser procedido por unsigned o signed. Ejemplo:

unsigned int val1; // es una variable de 8 bits sin signo.


signed int val2; // es una variable de 8 bits con signo.

El compilador CCS en MPLAB proporciona una gran cantidad funciones integradas para acceder y utilizar los
periféricos del microcontrolador PIC. Esto hace que sea muy fácil para los usuarios configurar y controlar los
periféricos sin entrar en detalles en profundidad de los registros asociados con la funcionalidad.

3.2 Instrucciones de C para la manipulación de bit y byte.

Las instrucciones para la manipulación de bit y byte son:

bit_clear( ) make8( ) _mul( ) shift_left( )


bit_set( ) make16( ) rotate_left( ) shift_right( )
bit_test( ) make32( ) rotate_right( ) swap( )

bit_clear( )

Sintaxis: bit_clear(var, bit);

Función: Simplemente pone en 0 el bit especificado (0-7, 0-15 o 0-31) en la variable dada. La
variable var puede ser de 8, 16 y 32 bits y el bit puede ser 0-7, 0-15 o 0-31.

Ejemplo: int x;
x=5; // x = 0b0101
bit_clear(x, 2); // Se pone en cero el bit 2 de x = 1
22

bit_set( )

Sintaxis: bit_clear(var, bit);

Función: Simplemente pone en 1 el bit especificado (0-7, 0-15 o 0-31) en la variable dada. La
variable var puede ser de 8, 16 y 32 bits y el bit puede ser 0-7, 0-15 y 0-31.

Ejemplo: int x;
x=5; // x = 0b0101
bit_set(x, 3); // Se pone en 1 el bit 3 de x = 13

bit_test( )

Sintaxis: value = bit_test(var, bit);

Función: Checa el bit especificado (0-7, 0-15 o 0-31) en la variable dada y regresa el valor que
tiene (0 o 1). La variable var puede ser de 8, 16 y 32 bits y el bit puede ser 0-7, 0-15 y
0-31.

Ejemplo: if( bit_test(x,3) || !bit_test (x,1) ) { // Ejecuta el ciclo si el bit 3 es 1 o el bit 1 es 0.

make8( )

Sintaxis: int8 = make8(var, offet);

Función: Extrae el byte en el offset (puede ser 0,1,2 o 3) de var (es un int16 o int32) y lo
deposita en la variable int8 (variable de 8 bits).

Ejemplo: long long x; // x es una variable de 32 bit o 4 byte.


int y; // y es una variable de 8 bit o 1 byte.

x= 0x65231597; // x tiene el numero hexadecimal 65231597.


y = make8(x,3); // Deposita en y la cifra más significativa ( MSB) de x.

make16( )

Sintaxis: int16 = make16(varhigh, varlow);

Función: Hace un número de 16 bits (int16) de dos números de 8 bits (varhigh (MSB) y varlow
(LSB). Si los parámetros son de 16 o 32 bits se utiliza el byte menos significativo LSB.

Ejemplo: long long x; // x es una variable de 32 bit o 4 byte.


int y; // y es una variable de 8 bit o 1 byte.

x = 0x65231597; // x tiene el numero hexadecimal 65231597.


y = make8(x,3); // Deposita en y la cifra más significativa ( MSB) de x.

make32( )

Sintaxis: int32 = make32(var1, var2, var3, var4);

Función: Hace un número de 32 bits (int32) de cualquier combinación de números de 8 o 16 bits.


El número de parámetros pueden ser 4 (var1, var2, var3, var4). El más significativo
(MSB) es el primero.
23

Ejemplo: long long x; // x es una variable de 32 bit o 4 byte.


int y; // y es una variable de 8 bit o 1 byte.
long z;

x = make32(1,2,3,4); // x tiene el numero hexadecimal 0x01020304.

y = 0x12;
z = 0x4321;

x = make32(y,z); // x es 0x00124321;
x = make32(y,y,z); // x es 0x12124321;

_mul( )

Sintaxis: prod = _mul(var1, var2);

Función: Lleva a cabo una multiplicación optimizada. Las variables var1 y var2 pueden ser de 8
o 16 bits. Esta función evita la sobrecarga y cambia los parámetros por tipos más
grandes. Entrega en prod un entero de 16 bits si los parámetros son de 8 bits y 32 bits
si los parámetros son de 16 bits.

Ejemplo: int a=50, b=100; // a y b son de 8 bits.


long int c; // c es de 24 bits.

c = _mul(a, b); // c es igual a 5000.

rotate_left( )

Sintaxis: rótate_left(addrees, bytes);

Función: Rota hacia la izquierda un bit que se encuentra dentro de una variable o arreglo. El
puntero addrees apunta al inicio del arreglo y la variable bytes es el contador del
número de rotaciones.

Ejemplo: int x = 0x86;

Rótate_left(&x,1); // Rota a x hacia la izquierda una posición x es 0x0d.

rotate_right( )

Sintaxis: rotate_right(addrees, bytes);

Función: Rota hacia la derecha un bit que se encuentra dentro de una variable o arreglo. El
puntero addrees apunta al inicio del arreglo y la variable bytes es el contador del
número de rotaciones.

Ejemplo: int x[4] = {12,58,63,87}; // x es un vector con 4 espacios tipo byte.

rotate_right(&x,2); // Rota a x hacia la derecha dos posición x = {63,87,12,58}.

shift_left( )

Sintaxis: shift_left(addrees, bytes, value);


24

Función: Desplaza hacia la izquierda un bit dentro de una variable o arreglo. El puntero addrees
apunta al inicio del arreglo, la variable bytes es el contador del número de
desplazamientos y value es el 0 o 1 que se desplazara. Retorna el bit desplazado.

Ejemplo: int buffer[3]; // buffer es un vector de 3 espacios tipo byte.


Int i; // i es una variable de 8 bits.

for(i=0; i<=24;++i){ // Este ciclo deposita en el buffer 24 lecturas del


while(! input(PIN_A2)); // PIN_A3 cada transición de 0 a 1 del PIN_A2.
shift_left(&buffer, i, input(PIN_A3);
while(input(PIN_A2));
}
shift_right( )

Sintaxis: shift_right(addrees, bytes, value);

Función: Desplaza hacia la derecha un bit dentro de una variable o arreglo. El puntero addrees
apunta al inicio del arreglo, la variable bytes es el contador del número de
desplazamientos y value es el 0 o 1 que se desplazara. Retorna el bit desplazado.

Ejemplo: int data = 0x35, i; // data e i son variables de 8 bits.

for(i=0; i<8;++i) // Este ciclo desplaza 8 bits de data fuera PIN_A0.


output_bit(PIN_A0, shift_right(&data,i,0));

swap( )

Sintaxis: swap(Ivalue);

Función: Intercambia el nible (4 bits) superior con el inferior de una variable Ivalue de 8 bits.

Ejemplo: int x = 0x45; // x es de 8 bits.

swap(x); // x es ahora 0x54.

Muchas veces en algunos programas es necesario realizar un retardo o esperar cierto tiempo antes de
continuar con la ejecución de nuestro programa, para esto el compilador CCS cuenta con funciones de retardo.

3.3 Las instrucciones de C para la creación de rutinas de retardo.

Las instrucciones para la creación de retardos en c son:

delay_cycles( ) delay_ms( ) delay_us( )

delay_cycles( )

Sintaxis: delay_cycles(count);

Función: Crea un retardo del número (count es una constante de 0 - 255) de ciclos de trabajo
especificados. Un ciclo de trabajo son 4 periodos del reloj oscilador.

Ejemplo: delay_cycles(1); // Es equivalente a ejecutar la instrucción NOP.


delay_cycles(25); // A 20 MHz es equivalente a 5 us de retardo.
25

delay_ms( )

Sintaxis: delay_ms(time);

Función: Crea un retardo del orden de los mili segundos (time es una variable o constante de 0 -
65535) del tiempo especificados. Esta función no utiliza ningún timer interno solo
instrucciones y requiere de la directiva #USE DELAY.

Ejemplo: #use delay (clock = 20000000) // Se configura el reloj de la CPU a 20 MHz.

delay_ms(1000); // Realiza un retardo de 1 segundo.

delay_us( )

Sintaxis: delay_us(time);

Función: Crea un retardo del orden de los micro segundos (time es una variable o constante de 0
- 65535) del tiempo especificados. Esta función no utiliza ningún timer interno solo
instrucciones y requiere de la directiva #USE DELAY.

Ejemplo: #use delay (clock = 20000000) // Se configura el reloj de la CPU a 20 MHz.

unsigned int periodo=100, duty = 25; // periodo y duty son 8 bit sin signo.

while(true){ // ciclo infinito para crear una señal PWM de


output_high(PIN_B0); // 10 kHz con un ciclo de trabajo de 25%.
delay_us(duty);
output_low(PIN_B0);
delay_us(period - duty);
}

Existe un grupo muy extenso de funciones de C para la manipulación de caracteres y cadenas de caracteres en
el compilador CCS en MPLAB.

3.4 Funciones estándar de C para la manipulación de caracteres y cadenas de caracteres.

Las funciones para la manipulación de caracteres y cadenas de caracteres en C son:

atof( ) isprint(x) strcpy( ) strrchr( )


atoi( ) ispunct(x) strcspn( ) strspn( )
atol32( ) isspace(char) strerror( ) strstr( )
atol( ) isupper(char) stricmp( ) strtod( )
isalnum( ) isxdigit(char) strlen( ) strtok( )
isalpha(char) itoa( ) strlwr( ) strtol( )
isamong( ) sprintf( ) strncat( ) strtoul( )
iscntrl(x) strcat( ) strncmp( ) strxfrm( )
isdigit(char) strchr( ) strncpy( ) tolower( )
isgraph(x) strcmp( ) strpbrk( ) toupper( )
islower(char) strcoll( ) strcopy( )

atof( )

Sintaxis: result = atof(string);


26

Función: Convierte una cadenas de caracteres (string es un puntero y apunta el inicio de la


cadena) en flotante de 32 bit (result es una variable flotante de 32 bits). Esta función
requiere de la librería #include <stdlib.h>.

Ejemplo: #include <stdlib.h> // Se incluye la librería stdlib.h

char str[10] = {“123.45”}; // Se inicializa el vector str.


float x;

x = atof(str); // x = 123.45

atoi( ), atol( ) , atoi32( )

Sintaxis: ivalue = atoi(string);


lvalue = atol(string);
i32value = atoi32(string);

Función: Convierte una cadenas de caracteres (string es un puntero y apunta el inicio de la


cadena) en un entero de 8, 16 o 32 bits (ivalue es una variable int8, lvalue es una
variable int16 y i32value es una variable int32). Esta función requiere de la librería
#include <stdlib.h>.

Ejemplo: #include <stdlib.h> // Se incluye la librería stdlib.h

char str[10] = {“123”}; // se inicializa el vector str.


float x;

x = atoi(str); // x = 123.

isalnum(char), isalpha(char), isdigit(char),


islower(char), isspace(char) , isupper(char) ,
isxdigit(char) , iscntrl(char) , isgraph(char),
isprint(char), ispunct(char)

Sintaxis: value = isalnum(datac); , value = isalpha(datac);


value = isdigit(datac); , value = islower(datac);
value = isspace(datac); , value = isupper(datac);
value = isxdigit(datac); , value = iscntrl(datac);
value = isgraph(datac); , value = isprint(datac);
value = ispunct(datac);

Función: Checa un carácter (datac es un carácter de 8 bits) para ver si cumple con los siguientes
criterios y retorna false (0) o true (1) (value es int1). Esta función requiere de la librería
#include <ctype.h>.

isalnum(x) x es 0..9, 'A'..'Z', or 'a'..'z'.


isalpha(x) x es 'A'..'Z' or 'a'..'z’.
isdigit(x) x es '0'..'9'.
islower(x) x es 'a'..'z'.
isupper(x) x es 'A'..'Z.
isspace(x) x es un espacio.
isxdigit(x) x es '0'..'9', 'A'..'F', or 'a'..'f
iscntrl(x) x es menor que un espacio.
27

isgraph(x) x es mayor que un espacio.


isprint(x) x es mayor o igual que un espacio.
ispunct(x) x es mayor que un espacio y no es una letra o un número.

Ejemplo: #include <ctype.h>

char id[20] = {“Hola son las 9 hrs”};

If(isalpha(id[ 0 ])) {
valid_id = true;
for(i=1;i<strlend(id);i++)
valid_id = valid_id && isalnum(id[ i ]);

} else
valid_id = false;
itoa( )

Sintaxis: string = itoa(i32value, i8base, string);

Función: Convierte un entero signado de 32,48 o 64 bits (i32value) en una cadena de caracteres
(string es un puntero que indica el inicio del vector) que representa la cantidad
pudiendo elegir la base del numero con base variable (i8base es una variable 8 bits que
sirve para colocar la base del número 2, 8, 10 y 16), si no logra la conversión regresa un
0. Esta función requiere de la librería #include <stdlib.h>.

Ejemplo: #include <stdlib.h>

long long x = 1234; // x es una variable de 32 bits..


char str[ 5 ] ;

itoa(x, 10, str); // str = “1234”.

sprintf( )

Sintaxis: sprintf(string, cstring, values…);

Función: Esta función es similar a la función printf() de C, imprime una cadenas de caracteres
(cstring cadenas de caracteres constante y values son variables separadas por comas
que se pueden mandar a imprimir) en un arreglo (string arreglo tipo char).

Ejemplo: char mystr[20];


long año = 2013 ;
int día = 4

sprintf(mystr , “%u de febrero dé %lu”,dia,año); //mystr = “4 de febrero de 2013”.

memchr( ), memcmp( ), strcat( ), strchr( )


strcmp( ), strcoll( ), strcspn( ), strerror( )
stricmp( ) , strlen( ), strlwr( ), strncat( )
strncmp( ), strncpy( ), strpbrk( ), strrchr( )
strspn( ), strstr( ), strxfrm( ), strcpy( )

Sintaxis: ptr = strcat(s1, s2); Concatena s2 en s1.


ptr = strchr(s1, c); Encuentra c en s1 y retorna &s1[ i ].
ptr = strrchr(s1, c); Hace lo mismo pero busca al inverso.
28

cresult = strcmp(s1, s2); Compara s1 con s2.


iresult = strncomp(s1, s2, n);
Compara s1 con s2 (n bytes).
iresult = stricomp(s1, s2); Compara e ignora el caso.
ptr = strncopy(s1, s2, n); Copia n caracteres de s2 en s1.
ptr = strcpy(s1, s2); Copia s2 en s1.
iresult = strcspn(s1, s2); Cuenta los caracteres de s1 que no están en s2.
iresult = strspn(s1, s2); Cuenta los caracteres de s1 que están en s2.
iresult = strlen(s1); Cuenta el número de caracteres en s1.
ptr = strlwr(s1); Convierte la cadena s1 a minúsculas.
ptr = strpbrk(s1, s2); Busca si el primer carácter de s1 también esta s2.
ptr = strstr(s1, s2); Busca s2 en s1.
ptr = strncat(s1, s2); Concatena n byte de s2 en s1.
iresult = strcoll(s1, s2); Compara s1 con s2.
res = strxfrm(s1, s2, n); Transforma los n caracteres de s2 y los pone en s1, de
forma que strcmp(s1, s2) sera igual strcoll (s1, s2).
iresul = memcmp(m1, m2, n); Compara n con m2 (n bytes).
ptr = memchr(m1, c, n); Encuentra c en los primeros n caracteres de m1 y
retorna &m1[ i ].
ptr = strerror(errnum); Traza un mapa del número de error en errnum en una
cadena de mensaje de error. Los parámetros "errnum"
son int8. Devuelve un puntero para la cadena.

Función: s1 y s2 son punteros de un arreglo de caracteres, n es un contador del número máximo


de caracteres, c es un carácter de 8 bits, m1 y m2 son punteros de memoria. Las
asignaciones ptr son una copia del puntero s1, iresult es un int8, res es un numero
entero y los resultados son 1 para menor que, 0 para igual o 1 para mayor que. esta
funciones requieren de la librería #include <string.h>.

Ejemplo: char str1[10], str2[10]; // Se crean dos arreglos.

strcpy(str1,"Hola "); // str1 = “Hola ”.


strcpy(str2,"como están"); // str2 = “como están”.
strcat(str1,str2); // str1 = “Hola como están”.
printf("str1 tiene %u caracteres", strlen(str1)); // Se imprime “str1 tiene 15 caracteres”.

tolower( )
toupper( )

Sintaxis: result = tolower(cvalue); Transforma una letra en minúscula.


result = toupper(cvalue); Transforma una letra en mayúscula.

Función: Estas funciones cambian una letra del alfabeto (cvalue es un carácter), si el carácter no
es letra no lo cambia. Retorna un carácter de 8 bits en result.

Ejemplo: swith(toupper(getc( ))) { // Espera que reciba un dato el RS232,


// la transforma a mayúscula si es letra.
case ‘R’: read_cmd ( );
break;
case ‘W’: write_cmd ( );
break;
case ‘Q’: done = true;
break;
}
29

4. Los Puertos Digitales I/O.

El microcontrolador PIC18F4550 cuenta con 5 puertos digitales de entradas / salidas que incluyen un total de 35
líneas de E/S:

Puertos Función Descripción


Puerto A RA0 – RA6 (I/O) Tiene 7 terminales configurables como entradas o
salidas.
Puerto B RB0 – RB7 (I/O) Tiene 8 terminales configurables como entradas o
salidas.
Puerto C RC0 - RC2 y RC6 - RC7 (I/O) y Tiene 5 terminales configurables como entradas o
RC4 – RC5 ( I ) salidas, 2 terminales que siempre son salidas y el bit
RC3 no existe.
Puerto D RD0 – RD7 (I/O) Tiene 8 terminales configurables como entradas o
salidas.
Puerto E RE0 – R2 (I/O) y RE3 ( I ) Tiene 3 terminales configurables como entradas o
salidas y RE3 solo puede ser entrada.

Los puertos digitales cuentan con tres registros de 8 bits para su configuración y control:

 TRIS registro que sirve para configurar la dirección de los puertos, como entrada (1) o salida (0).
 PORT registro contiene los estados actuales de los pines de los puertos digitales.
 LAT registro sirve para leer, modificar y escribir los estado de los pines de los puertos digitales.

4.1 Funciones para controlar y configurar los puertos digitales de E/S.

En lenguaje C existen un grupo de funciones que nos sirven para manipular los puertos digitales.

get_tris_x( ) input_x( ) output_float( ) output_low( )


input( ) output_X( ) output_high( ) output_toggle( )
input_state( ) output_bit( ) output_drive( ) port_x_pullups( )
set_tris_x( ) input_change_x( )

get_tris_x( )

Sintaxis: value = get_tris_a( );


value = get_tris_b( );
value = get_tris_c( );
value = get_tris_d( );
value = get_tris_e( );
.
.
value = get_tris_k( );

Función: Devuelve el valor del registro TRIS del puerto A, B, C, D, E, F, G, H, J, o K.

Ejemplo: tris_a = get_tris_a( );

set_tris_x( )

Sintaxis: set_tris_a(value);
30

set_tris_b(value);
set_tris_c(value);
set_tris_d(value);
set_tris_e(value);
.
.
set_tris_k(value);

Función: Permite que los registros de dirección de los puertos digitales E/S (TRI-State) sean
modificados. Value es un numero de 8 bit y cada bit representa un pin digital de E/S.
Uno 1 indica que el pin es una entrada digital y un 0 indica que es una salida digital.

Ejemplo: set_tris_b(0x0f); // Los pines del puerto B, B0 al B3 serán entradas digitales.


// Y los pines B4 al B7 serán salidas digitales.

input_x( )

Sintaxis: value = input_a( );


value = input_b( );
value = input_ c( );

value = input_d( );
value = input_e( );
.
.
value = input_k( );

Función: Devuelve un byte entero (8 bit) con los estados de los pin de un puerto digital.

Ejemplo: data = input_b( ); // Lee el contenido del puerto B y lo deposita en data.

input( )

Sintaxis: value = input(pin);

Función: Devuelve el estado (false (0) o true (1)) del pin digital seleccionado. La palabra pin es
una constante y puede ser PIN_A0 – PIN_A6, PIN_B0 – PIN_B7, PIN_C0 – PIN_C7,
PIN_D0 – PIN_D7 o PIN_E0 – PIN_E3.

Ejemplo: if (input(PIN_A5)) { // Checa si el PIN_A5 es 1 y ejecuta línea de código.

Líneas de código a ejecutar.


}

Input_state( )

Sintaxis: value = input_state(pin);

Función: Lee el nivel del pin seleccionado y entrega el estado lógico que tenga sea 0 o 1. La
palabra pin es una constante y puede ser PIN_A0 – PIN_A6, PIN_B0 – PIN_B7,
PIN_C0 – PIN_C7, PIN_D0 – PIN_D7 o PIN_E0 – PIN_E3.

Ejemplo: level = input_state(PIN_A3); // Lee el PIN_A3 y deposita su valor en level.


31

input_change_x( )

Sintaxis: value = input_changer_a( );


value = input_changer_b( );
value = input_changer_c( );
value = input_changer_d( );
value = input_changer_e( );
.
.
value = input_changer_k( );

Función: Lee los nivel del puerto seleccionado y los compara con los resultados de la última vez
que la función input_charge_x( ) fue llamada. Entrega un 1 si el valor cambio y un 0 si el
valor es el mismo.

Ejemplo: pin_check = input_change_b( ); // Lee el puerto B y si ha cambiado deposita


// un 1 en pin_check o un 0 si no hay cambio.

output_x( )

Sintaxis: output_a(value);
output_b(value);
output_c(value);
output_d(value);
output_e(value);
.
.
output_k(value);

Función: Entrega por el puerto seleccionado el valor de value (byte entero de 8 bit).

Ejemplo: output_c(0xf5); // Saca por el puerto C la cifra 0xf5, lo que pone en 1 los pines
// PIN_C4 –PIN_ C7 y en 0 los pines PIN_C0 – PIN_C3.

output_bit( )

Sintaxis: output_bit(pin, value);

Función: Entrega por el bit del puerto seleccionado el valor de value (bit 0 o 1). La palabra pin es
una constante y puede ser PIN_A0 – PIN_A6, PIN_B0 – PIN_B7, PIN_C0 – PIN_C7,
PIN_D0 – PIN_D7 o PIN_E0 – PIN_E3.

Ejemplo: output_bit( PIN_B7, 0); // Saca un 0 por el PIN_B7.


output_bit( PIN_B0, input( PIN_C3 ) ); // Saca por el PIN_B0 la del PIN_C3.

output_high( )

Sintaxis: output_high(pin);

Función: Pone el pin especificado en nivel alto. La palabra pin es una constante y puede ser
PIN_A0 – PIN_A6, PIN_B0 – PIN_B7, PIN_C0 – PIN_C7, PIN_D0 – PIN_D7 o PIN_E0
– PIN_E3.
32

Ejemplo: output_high(PIN_A0); // Pone en nivel alto el PIN_A0.

output_low( )

Sintaxis: output_low(pin);

Función: Pone el pin especificado en nivel bajo. La palabra pin es una constante y puede ser
PIN_A0 – PIN_A6, PIN_B0 – PIN_B7, PIN_C0 – PIN_C7, PIN_D0 – PIN_D7 o PIN_E0
– PIN_E3.

Ejemplo: output_low(PIN_C0); // Pone en nivel bajo el PIN_C0.

output_float( )

Sintaxis: output_float(pin);

Función: Ajusta el pin especificado en modo entrada. Esto permitirá que el pin flote en nivel alto
para representar nivel alto en colector abierto en la conexión. La palabra pin es una
constante y puede ser PIN_A0 – PIN_A6, PIN_B0 – PIN_B7, PIN_C0 – PIN_C7,
PIN_D0 – PIN_D7 o PIN_E0 – PIN_E3.

Ejemplo: if( (data & 0x80) == 0 ) // Checa si el bit_8 de data es cero


output_low(pin_A0); // Si se verdadero saca un 0 por PIN_A0.
else // Si no entonces cambia el PIN_A0
output_float(pin_A0); // como colector abierto con nivel alto.

output_toggle( )

Sintaxis: output_toggle(pin);

Función: Alterna entre 1 y 0 el estado del pin especificado. La palabra pin es una constante y
puede ser PIN_A0 – PIN_A6, PIN_B0 – PIN_B7, PIN_C0 – PIN_C7, PIN_D0 – PIN_D7
o PIN_E0 – PIN_E3.

Ejemplo: output_toggle(PIN_B4); // Si el PIN_B4 es 1 la hace 0 y si es 0 la hace 1.

output_drive( )

Sintaxis: output_drive(pin);

Función: Pone el pin especificado en modo salida. La palabra pin es una constante y puede ser
PIN_A0 – PIN_A6, PIN_B0 – PIN_B7, PIN_C0 – PIN_C7, PIN_D0 – PIN_D7 o PIN_E0
– PIN_E3.

Ejemplo: output_driver(PIN_A4); // Ajusta el PIN_A4 como salida.


output_bit(PIN_B0, input(PIN_A4)) // Saca el contenido del PIN_A4 por PIN_B0.

port_x_pullups( )

Sintaxis: port_x_pullups(value);

Función: Ajusta las pullups de entrada. Con TRUE se activan y con FALSE se desactivan.

Ejemplo: port_b_pullups(true); // Activa las pullups del puerto B.


33

4.2 PRACTICA E4.1. El Diodo Led Intermitente.

En esta práctica realizaremos el código para lograr que un diodo Led conectado con lógica inversa en el pin 0
del puerto E (RE0, aunque podemos elegir cualquier otro pin de los puertos A, B, C, D o E del PIC18F4550), se
encienda y se apaguen a una frecuencia de 2 Hz (o cualquier otra frecuencia que queramos), utilizando solo los
puertos digitales y la función delay (que puede ser delay_ms (retardo en milisegundo), delay_us (retardo en
micro segundos) o delay_cycle (retardo en ciclos en maquina)) que ya proporciona el compilador. El circuito que
se armara es el siguiente, en esta imagen solo se muestra la conexión del diodo Led:

Código Fuente de la Práctica E4.1.

/* Este programa hace pulsar a 2 Hz un led conectado en el RE0 mediante


el uso de puertos digitales y la función delay_ms(value).

Realizo: M.C. Julio Cesar Gallo Sánchez */

/*Configuración del PIC18F4550*/

#include"18f4550.h" // Incluye la librería del PIC18F4550 que contiene las


// definiciones de los periféricos.
#use delay(clock=48000000) // Ajusta la velocidad de la CPU a 48 MHz.
#build(reset=0x02000,interrupt=0x02008) // Se asigna la dirección de inicio del vector reset y
// de las interrupciones en general.
#org 0x0000, 0x1FFF { } // Se declara el área de FLASH para nuestro programa.

// Zona para la declaración de variables y constantes globales.

#define LED PIN_E0 // Se define la palabra LED como PIN_E0.

/* Programa principal */
void main(void){
output_drive(LED); // Se configura el pin del LED como salida.
output_high(LED); // Se manda a apagar el LED.

while(true){ // Ciclo infinito con while.


output_toggle(LED); // Se alterna la salida hacia del LED.
delay_ms(250); // Retardo de 250 ms para que el LED oscile a 2 Hz.
}
34

4.3 PRACTICA E4.2. Secuencia de Luces con Velocidad Variable.

En esta práctica realizaremos el código para lograr una secuencia de 8 luces con velocidad variable. Se
conectaran 8 diodos leds en el puerto D con lógica inversa (podemos utilizar también el puerto B). La velocidad
se aumenta presionando el botón UP conectado en el pin RE0 y se decrementa presionando el botón DOWN
conectado en el pin RE1, los botones entregan cero cuando son presionados y uno cuando no son presionados.
El circuito que se armara es el siguiente, solo muestra los diodos leds y los botones:

Código Fuente de la Práctica E4.2.

/* Programa para generar una secuencia de luces con


velocidad variable, utilizando 8 leds conectados
en el puerto D con lógica inversa. La velocidad
se incrementa presionando el botón UP (RE0) y se
decrementa presionando el botón DOWN (RE1).

Realizo: M.C. Julio Cesar Gallo Sánchez. */

/*Configuración del PIC18F4550*/

#include "18f4550.h"
#use delay(clock=48000000)
#build(reset=0x02000,interrupt=0x02008)
#org 0x0000,0x1FFF {}

// Área para la definición y creación de variables.


#define UP PIN_E0 // Se define RE0 como UP.
#define DOWN PIN_E1 // Se define RE1 como DOWN.

const char secu[16] = {0x00,0x01,0x02,0x04,0x08,0x10,0x20,0x40, // Se crea una arreglo con la secuencia.


0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x01};
char cont = 0; // Variable para contar la secuencia.
unsigned long vel = 20; // variable para asignar la velocidad en ms.
35

/* Inicio del programa principal*/

void main(void){

set_tris_d(0x00); // Se configura todo el puerto D como salidas.


output_d(0xff); // Se mandan a apagar los leds.
output_float(UP); // Se configura el UP como entrada.
output_float(DOWN); // Se configura el DOWN como entrada.

for( ; ; ) {

output_d(~secu[cont]); // Se saca el código de la secuencia.


cont++; // Se incrementa el contador para obtener la siguiente secuencia.
if(cont == 16) // La tabla de secuencias contiene 16 códigos, entonces el contador
cont = 0; // solo contar de 0 a 15.

delay_ms(vel/3); // Se ejecuta un retardo de 1/3 de la velocidad de la secuencia.

if(input(DOWN) == 0){ // Se checa si el botón DOWN está presionado.

switch(vel){ // Se disminuye la velocidad aumentando vel.


case 20: vel = 80; break;
case 80: vel = 150; break;
case 150: vel = 250; break;
case 250: vel = 350; break;
case 350: vel = 500; break;
}
delay_ms(20); // Retardo para eliminar rebotes del botón.
while(input(DOWN) == 0); // Espera mientras el botón DOWN este presionado.
}
delay_ms(vel/3); // Se ejecuta un retardo de 1/3 de la velocidad de la secuencia.

if(input(UP) == 0){ // Se checa si el botón UP está presionado

switch(vel){ // Se aumenta la velocidad disminuyendo vel.


case 500: vel = 350; break;
case 350: vel = 250; break;
case 250: vel = 150; break;
case 150: vel = 80; break;
case 80: vel = 20; break;
}
delay_ms(20); // Retardo para eliminar rebotes del botón.
while(input(UP) == 0); // Espera mientras el botón UP este presionado.
}

delay_ms(vel/3); // Se ejecuta un retardo de 1/3 de la velocidad de la secuencia.


}

36

4.4 PRACTICA E4.3. Múltiples Secuencias de Luces con Velocidad Variable.

En esta práctica realizaremos el código para lograr 5 secuencia de 8 luces con velocidad variable. Se
conectaran 8 diodos leds en el puerto D con lógica inversa (podemos utilizar también el puerto B). La velocidad
iniciara lenta y se aumenta presionando el botón UP conectado en el pin RE0 (después de alcanzar la velocidad
máxima al presionar el botón de nuevo regresa a la velocidad más lenta) y al presionar el botón SHIFT
conectado en el pin RE1, se cambiara de secuencia eligiendo entre 4 distintas. Los botones entregan cero
cuando son presionados y uno cuando no son presionados. El circuito que se armara es el siguiente, solo
muestra los diodos leds y los botones:

Código Fuente de la Práctica E4.3.

/* Programa para generar 5 secuencias de luces con


velocidad variable, utilizando 8 leds conectados
en el puerto D con lógica inversa. La velocidad
se incrementa presionando el botón UP (RE0) y se
elige otra secuencia presionando el botón SHIFT (RE1).

Realizo: M.C. Julio Cesar Gallo Sánchez. */

/*Configuración del PIC18F4550*/

#include "18f4550.h"
#use delay(clock=48000000)
#build(reset=0x02000,interrupt=0x02008)
#org 0x0000,0x1FFF {}

// Área para la definición y creación de variables.

#define UP PIN_E0 // Se define RE0 como UP.


#define SHIFT PIN_E1 // Se define RE1 como SHIFT.
37

const char secu1[16] = {0x00,0x01,0x02,0x04,0x08,0x10,0x20,0x40, // Se crea la secuencia1.


0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x01};
const char secu2[32] = {0x00,0x01,0x03,0x07,0x0f,0x1f,0x3f,0x7f,0xff, // Se crea la secuencia2.
0xfe,0xfc,0xf8,0xf0,0xe0,0xc0,0x80,0x00,0x80,
0xc0,0xe0,0xf0,0xf8,0xfc,0xfe,0xff,0x7f,0x3f,
0x1f,0x0f,0x07,0x03,0x01};

const char secu3[23] = {0x00,0x81,0x42,0x24,0x18,0x99,0x5a,0x3c, // Se crea la secuencia3.


0xbd,0x7e,0xff,0xe7,0xc3,0xa5,0x66,0xe7,
0xc3,0x81,0x42,0xc3,0x81,0x00,0x81};

const char secu4[32] = {0x00,0x01,0x03,0x07,0x0f,0x1f,0x3f,0x7f,0xff, // Se crea la secuencia4.


0xfe,0xfc,0xf8,0xf0,0xe0,0xc0,0x80,0x00,0x80,
0xc0,0xe0,0xf0,0xf8,0xfc,0xfe,0xff,0x7f,0x3f,
0x1f,0x0f,0x07,0x03,0x01};

const char secu5[72] = {0x00,0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80, // Se crea la secuencia5.


0x81,0x82,0x84,0x88,0x90,0xa0,0xc0,0xc1,0xc2,
0xc4,0xc8,0xd0,0xe0,0xe1,0xe2,0xe4,0xe8,0xf0,
0xf1,0xf2,0xf4,0xf8,0xf9,0xfa,0xfc,0xfd,0xfe,0xff,
0x7f,0xbf,0x3f,0x5f,0x9f,0x1f,0x2f,0x4f,0x8f,0x0f,
0x17,0x27,0x47,0x87,0x7,0x0b,0x13,0x23,0x43,
0x83,0x03,0x05,0x09,0x11,0x21,0x41,0x81,0x01,
0x02,0x04,0x08,0x10,0x20,0x40,0x80};

char cont = 0, selec = 1; // Variable para contar la secuencia.


unsigned long vel = 20; // variable para asignar la velocidad en ms.

/* Inicio del programa principal*/

void main(void){

set_tris_d(0x00); // Se configura todo el puerto D como salidas.


output_d(0xff); // Se mandan a apagar los leds.
output_float(UP); // Se configura el UP como entrada.
output_float(SHIFT); // Se configura el SHIFT como entrada.

for( ; ; ) {

if(selec == 1) {
if(cont >= 16) // La tabla de secu1 contiene 16 códigos.
cont = 0; // solo contar de 0 a 15.

output_d(~secu1[cont]); // Se saca el código de la secu1.


cont++; // Se incrementa el cont.

}
else if(selec == 2) {
if(cont >= 32) // La tabla de secu2 contiene 32 códigos.
cont = 0; // solo contar de 0 a 31.

output_d(~secu2[cont]); // Se saca el código de la secu2.


cont++; // Se incrementa el cont.
}
else if(selec == 3) {
if(cont >= 23) // La tabla de secu3 contiene 23 códigos.
cont = 0; // solo contar de 0 a 22.
38

output_d(~secu3[cont]); // Se saca el código de la secu3.


cont++; // Se incrementa el cont.
}
else if(selec == 4) {
if(cont >= 32) // La tabla de secu4 contiene 32 códigos.
cont = 0; // solo contar de 0 a 31.

output_d(~secu4[cont]); // Se saca el código de la secu4.


cont++; // Se incrementa el cont.
}
else if(selec == 5) {
if(cont >= 72) // La tabla de secu5 contiene 72 códigos.
cont = 0; // solo contar de 0 a 71.

output_d(~secu5[cont]); // Se saca el código de la secu5.


cont++; // Se incrementa el cont.
}

delay_ms(vel/3); // Se ejecuta un retardo de 1/3 de la velocidad de la secuencia.

if(input(SHIFT) == 0){ // Se checa si el botón SHIF está presionado.

if(selec == 5) // Se checa si selec == 5 para hacerlo 0, y sino


selec = 1; // se incrementa en 1.
else
selec++;

delay_ms(20); // Retardo para eliminar rebotes del botón.


while(input(SHIFT) == 0); // Espera mientras el botón SHIFT este presionado.
}

delay_ms(vel/3); // Se ejecuta un retardo de 1/3 de la velocidad de la secuencia.

if(input(UP) == 0){ // Se checa si el botón UP está presionado

switch(vel){ // Se aumenta la velocidad disminuyendo vel.


case 500: vel = 350; break;
case 350: vel = 250; break;
case 250: vel = 150; break;
case 150: vel = 80; break;
case 80: vel = 20; break;
case 20: vel = 500; break; // Con velocidad máxima regresa y la mínima.
}
delay_ms(20); // Retardo para eliminar rebotes del botón.
while(input(UP) == 0); // Espera mientras el botón UP este presionado.
}

delay_ms(vel/3); // Se ejecuta un retardo de 1/3 de la velocidad de la secuencia.

}
}
39

4.5 PRACTICA E4.4. Desplegué de Números en Display de 7 Segmentos con Cuatro Dígitos.

En esta práctica desarrollaremos el código para imprimir cifras numéricas de cuatro dígitos en displays de 7
segmentos de ánodo común multiplexados, utilizando el puerto D para conectar los segmentos y los pines RE0
(unidades), RE1 (decenas), RE2 (centenas) y RB0 (millares) para la activación de los ánodos comunes,
además contara con dos botones para incrementar (UP en RC0) o decrementar (DOWN en RC1) el número
desplegado. El circuito que se armara es el siguiente:

Código Fuente de la Práctica E4.4.

/* Programa para desplegar cifras numéricas de 4 dígitos enteras y flotantes


en displays de 7 segmentos, los segmentos serán conectados en el puerto D,
los ánodos comunes son RE0 (unidades), RE1(decenas), RE2 (centenas) y
RB0 (millares).

Se incrementara el valor desplegado con un botón UP (RC0) y se decrementara


otro botón DOWN (RC1).

Realizo: M.C. Julio Cesar Gallo Sánchez. */

/*Configuración del PIC18F4550*/

#include "18f4550.h"
#use delay(clock=48000000)
#build(reset=0x02000,interrupt=0x02008)
#org 0x0000,0x1FFF {}

// Área para la definición y creación de variables.

#define UP PIN_C0 // Se define RC0 como UP botón para incrementos.


#define DOWN PIN_C1 // Se define RC1 como DOWN botón para decrementos.
#define UNID PIN_E0 // Se define RE0 como UNID, AC de la unidades.
#define DECE PIN_E1 // Se define RE1 como DECE, AC de las decenas.
40

#define CENT PIN_E2 // Se define RE2 como CENT, AC de las centenas.


#define MILL PIN_B0 // Se define RB0 como MILL, AC de los millares.
#define PUNT PIN_D7 // se define RD7 como PUNT, el punto decimal.
// '0' '1' '2' '3' '4' '5' '6' '7' '8' '9'
const char segm[10] = {0xC0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90};
unsigned long cont = 0; // Variable entera para desplegar en los display.
float contf = 0.0; // Variable flotante para desplegar en los display.

// Declaración de funciones
void des_7int(unsigned long numi);
void des_7float(float numf, char ndf);

/* Inicio del programa principal*/


void main(void){

set_tris_d(0x00); // Se configura todo el puerto D como salidas.


output_d(0xff); // Se mandan apagan los segmentos.
output_drive(UNID); // Se configuran UNID, DECE y CENT como salidas.
output_drive(DECE);
output_drive(CENT);
output_drive(MILL);
output_high(UNID); // Se manda a nivel alto los pines UNID, DECE y CENT.
output_high(DECE);
output_high(CENT);
output_high(MILL);

output_float(UP); // Se configuran el UP y DOWN como entrada.


output_float(DOWN);

for( ; ; ) { // ciclo infinito.


des_7int(cont); // Función que despliega enteros en los display de 7 segmentos.
//des_7float(contf,1); // Función que despliega flotantes en los display de 7 segmentos.

if(input(DOWN) == 0){ // Se checa si el botón DOWN está presionado.


if(cont != 0)
cont--;

if(contf != 0.0)
contf -= 0.1;

delay_ms(20); // Retardo para eliminar rebotes del botón.


while(input(DOWN) == 0); // Espera mientras el botón DOWN este presionado.
}
else if(input(UP) == 0){ // Se checa si el botón UP está presionado
cont++;
if(cont == 10000)
cont = 9999;

contf += 0.1;
if(contf == 1000.0)
contf = 999.9;

delay_ms(20); // Retardo para eliminar rebotes del botón.


while(input(UP) == 0); // Espera mientras el botón UP este presionado.
}
delay_ms(50); // Retardo para no desplegar tan rápido.
}
41

}
// Función para desplegar cifras numéricas enteras positivas de 4 dígitos en displays de 7 segmentos.

void des_7int(unsigned long numi){

char numia; // Se declaran una variable auxiliar tipo char.

numia = numi % 10; // Se extraen las unidades de numi y se depositan en numia.


output_d(segm[numia]);// Se despliegan las unidades.
output_low(UNID);
delay_us(200);
output_high(UNID);

if(numi > 9){


numia = (numi / 10) % 10; // Se extraen las decenas de numi y se depositan en numia.
output_d(segm[numia]);// Se despliegan las decenas.
output_low(DECE);
delay_us(200);
output_high(DECE);
}

if(numi > 99){


numia = (numi / 100) % 10; // Se extraen las centenas de numi y se depositan en numia.
output_d(segm[numia]);// Se despliegan las centenas.
output_low(CENT);
delay_us(200);
output_high(CENT);
}

if(numi > 999){


numia = (numi / 1000) % 10; // Se extraen las centenas de numi y se depositan en numia.
output_d(segm[numia]);// Se despliegan las centenas.
output_low(MILL);
delay_us(200);
output_high(MILL);
}
}

// Función para desplegar cifras numéricas flotantes positivas de 3 dígitos enteros y 1 decimal en display de 7
segmentos.

void des_7float(float numf, char ndf){

char numfa; // Se declaran una variable auxiliar tipo char.


unsigned long numaux; // Se declara la variable auxiliar munaux tipo long.

if(ndf == 1)
numaux = numf * 10; // Se recuperan las milésimas.
else if(ndf == 2)
numaux = numf * 100; // Se recuperan las milésimas y centésimas.

numfa = numaux % 10; // Se extrae las milésimas de numaux y se depositan en numfa.


if(ndf == 1 || ( ndf == 2 && numfa != 0)){
output_d(segm[numfa]); // Se despliegan las milésimas.
output_low(UNID);
delay_us(200);
42

output_high(UNID);
}

numfa = (numaux / 10) % 10; // Se extraen las unidades de numaux y se depositan en numfa.
output_d(segm[numfa]); // Se despliegan las unidades y el punto decimal.
if(ndf == 1)
output_low(PUNT);
output_low(DECE);
delay_us(200);
output_high(DECE);

if(numaux > 99){


numfa = (numaux / 100) % 10; // Se extraen las decimas de numaux y se depositan en numfa.
output_d(segm[numfa]); // Se despliegan las decenas.
if(ndf == 2)
output_low(PUNT);
output_low(CENT);
delay_us(200);
output_high(CENT);
}

if(numaux > 999){


numfa = (numaux / 1000) % 10; // Se extraen las centenas de numaux y se depositan en numfa.
output_d(segm[numfa]); // Se despliegan las centenas.
output_low(MILL);
delay_us(200);
output_high(MILL);
}
}
43

5. Las Interrupciones Externas.


Este microcontrolador cuenta con 21 fuentes de interrupciones. Se distinguen dos grupos de interrupciones:

Grupo de interrupciones generales:


 Interrupciones del temporizador 0.
 Interrupciones por cambio en el puerto B.
 Interrupción externa 0.
 Interrupción externa 1.
 Interrupción externa 2.
Grupo de interrupciones de Periféricos:
 Interrupción del SSP.
 Interrupción de A/D.
 Interrupción del Receptor EUSART.
 Interrupción del Transmisor EUSART.
 Interrupción del MSSP.
 Interrupción del CCP1.
 Interrupción del Temporizador 2.
 Interrupción del Temporizador 1.
 Interrupción de fallo del oscilador.
 Interrupción del comparador.
 Interrupción del USB.
 Interrupción de escritura de la EEPROM.
 Interrupción de colisión del bus MSSP.
 Interrupción del detector de anomalías en VDD.
 Interrupción del temporizador 3.
 Interrupción del CCP2.

El sistema de interrupciones disponen de dos niveles de prioridad, nivel alto y nivel bajo, todas las
interrupciones pueden ser programadas con cualquiera de los dos prioridades, salvo la interrupción externa 0
que siempre tiene prioridad alta.

Todas las interrupciones disponen de tres bits de configuración (excepto la interrupción externa 0):
 Bit de habilitación de interrupción, que permite habilitar a nivel individual la interrupción.
 Bandera de interrupción, que se pone en 1 cuando se produce la condición de interrupción
independientemente si la interrupción está habilitada o no (esta bandera debe ponerse en 0 por
software cuando se procesa la interrupción).
 Bit de prioridad de interrupción, que establece si la interrupción es de alta o baja prioridad.

En este momento nos enfocaremos en las interrupciones externas (INT0, INT1 y INT2) y la interrupción por
cambio del puerto B (KBI0, KBI1, KBI2 y KBI3).

Las interrupciones externas se activan cuando detectan flancos ascendentes o descendentes en terminales
físicas del microcontrolador y tiene tres vectores de interrupción independientes para cada una de estas.
Cuando se configura el pin correspondiente como interrupción externa, la función de puerto digital de entrada
del pin sigue estando presente. Los pines que contiene estas interrupciones son:

RB0/AN12/INT0/FLT0/SDI/SDA RB1/AN10/INT1/SCK/SCL RB2/AN8/INT2/VMO

La interrupción por cambio del puerto B, se activa cuando acurre un cambio en alguno de los estados de los
pines RB4 al RB7, cuando son estos pines son configurados como entradas, y tiene un solo vector de
interrupción. Cuando la interrupción es atendida es necesario limpiar la bandera de esta. Los pines asociados a
estas interrupciones son:
44

RB4/AN11/RBI0/CSSPP RB5/RBI1/PGM RB6/RBI2/PGC RB7/RBI3/PGD

5.1 Funciones y directivas para configurar y controlar las interrupciones externas.

En lenguaje C existen un grupo de funciones que nos sirven para manipular las interrupciones externas e
interrupciones en general.

disable_interrupts( ) enable_interrupts( ) ext_int_edge( ) clear_interrupt( )


#int_ext #int_ext1 #int_ext2 #priority
#int_rb #device high_ints = true

disable_interrups( )

Sintaxis: disable_interrups(level);

Función: Desactiva interrupciones dependiendo del comando level (es una constante definida).
Las constantes que se pueden usar para las interrupciones externas son GLOBAL,
INT_EXT, INT_EXT1, INT_EXT2 y INT_RB, recuerde que esta función es para
interrupciones en general.

Ejemplo: disable_interrupts(GLOBAL); // Apaga todas las interrupciones


disable_interrupts(INT_EXT); // Apaga la interrupción del INT0.

enable_interrups( )

Sintaxis: disable_interrups(level);

Función: Permite las interrupciones dependiendo del comando level (es una constante definida).
Las constantes que se pueden usar para las interrupciones externas son GLOBAL,
INT_EXT, INT_EXT1, INT_EXT2 y INT_RB, recuerde que esta función es para
interrupciones en general.

Ejemplo: enable_interrupts(GLOBAL); // Habilita todas las interrupciones.


enable_interrupts(INT_EXT1); // Enciende la interrupción del INT1.

ext_int_edge( )

Sintaxis: ext_int_edge(source, level);

Función: Determina la forma de activación de la interrupción externa. La constante source


solamente puede ser 0, 1 o 2 y edge solo puede ser H_TO_L (activación por flanco
descendente) o L_TO_H (activación por flanco ascendente), esta instrucción es
exclusiva de las interrupciones externas.

Ejemplo: ext_int_edge(2, L_TO_H); // Selecciona activación por flanco ascendente


// de la interrupción externa INT2.

clear_inerrup( )

Sintaxis: clear_interrup(level);

Función: Limpia la bandera de la interrupción particular. La constantlevel puede ser INT_RB,


INT_TIMER1, entre otras.
45

Ejemplo: clear_interrup(INT_RB); // Limpia la bandera de interrupción del KBI.

#device high_ints = true

Función: Esta directiva genera el código para utilizar la selección de alta y baja prioridad en las
interrupciones.se usa para fijar la prioridad de las interrupciones..

Ejemplo: #device high_ints = true // Uso de prioridad alta y baja para interrupciones.

#priority

Sintaxis: #priority ints

Función: Esta directiva se usa para fijar la prioridad de las interrupciones, se enlistan las
interrupciones separadas por comas (ints es una lista de una o más interrupciones
separadas por comas), en donde la primera de la lista es la de más alta prioridad. Si
dos interrupciones acurren al mismo tiempo se atiende primero la de más alta prioridad.
Si una interrupción es activada no se puede interrumpir.

Ejemplo: #device high_ints = true // Interrupciones prioridad alta y baja.


#priority rb, ad // La KBI tiene la mayor prioridad.

#int_ext, #int_ext1
#int_ext2, #int_rb

Función: Estas directivas se utilizan para marcar el inicio del vector de las interrupciones INT0
(#int_ext), INT1 (#int_ext1), INT2 (#int_ext2) y KBI (#int_rb), existen otras más para las
interrupciones del ADC, TIMERS, COMUNICACIONES, etc. .

Ejemplo: void main(void) { // Programa principal.

enable_interrupts(GLOBAL); // habilita las interrupciones globales.


enable_interrupts(INT_EXT1); // Enciende la interrupción externa INT1.
ext_int_edge(1,L_TO_H); // Activación de INT1 por flanco ascendente.

while(true) { // Ciclo infinito.

}
}

#int_ext1 // Vector de la interrupción INT1


void función_int1( ){ // Cuerpo de la función de la INT1.

Grupo de instrucciones a ejecutar

}
46

5.2 PRACTICA E5.1. Múltiples Secuencias de Luces con Velocidad Variable utilizando Interrupciones
Externas.

En esta práctica realizaremos el código para lograr 5 secuencia de 8 luces con velocidad variable. Se
conectaran 8 diodos leds en el puerto D con lógica inversa (podemos utilizar también el puerto B).

La velocidad iniciara lenta y se aumenta presionando el botón UP conectado en el pin INT0 (después de
alcanzar la velocidad máxima al presionar el botón de nuevo regresa a la velocidad más lenta) y al presionar el
botón SHIFT conectado en el pin INT1, se cambiara de secuencia eligiendo entre 5 distintas. Los botones
entregan cero cuando son presionados y uno cuando no son presionados.

Retomaremos el código que hicimos en la práctica 3 (Múltiples Secuencias de Luces con Velocidad Variable) y
habilitaremos las interrupciones externas INT0 e INT1, además reubicaremos parte del código en los vectores
de las interrupciones. El circuito que se armara es el siguiente, solo muestra los diodos leds y los botones:

Código Fuente de la Práctica E5.1.

/* Programa para generar 5 secuencias de luces con


velocidad variable, utilizando 8 leds conectados
en el puerto D con lógica inversa. La velocidad
se incrementa presionando el botón UP (INT0) y se
elige otra secuencia presionando el botón SHIFT (INT1).

Realizo: M.C. Julio Cesar Gallo Sánchez. */

/*Configuración del PIC18F4550*/

#include "18f4550.h"
#use delay(clock=48000000)
#build(reset=0x02000,interrupt=0x02008)
#org 0x0000,0x1FFF {}

// Área para la definición y creación de variables.


47

#define UP PIN_B0 // Se define RB0 como UP.


#define SHIFT PIN_B1 // Se define RB1 como SHIFT.

const char secu1[16] = {0x00,0x01,0x02,0x04,0x08,0x10,0x20,0x40, // Se crea la secuencia1.


0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x01};

const char secu2[32] = {0x00,0x01,0x03,0x07,0x0f,0x1f,0x3f,0x7f,0xff, // Se crea la secuencia2.


0xfe,0xfc,0xf8,0xf0,0xe0,0xc0,0x80,0x00,0x80,
0xc0,0xe0,0xf0,0xf8,0xfc,0xfe,0xff,0x7f,0x3f,
0x1f,0x0f,0x07,0x03,0x01};

const char secu3[23] = {0x00,0x81,0x42,0x24,0x18,0x99,0x5a,0x3c, // Se crea la secuencia3.


0xbd,0x7e,0xff,0xe7,0xc3,0xa5,0x66,0xe7,
0xc3,0x81,0x42,0xc3,0x81,0x00,0x81};

const char secu4[32] = {0x00,0x01,0x03,0x07,0x0f,0x1f,0x3f,0x7f,0xff, // Se crea la secuencia4.


0xfe,0xfc,0xf8,0xf0,0xe0,0xc0,0x80,0x00,0x80,
0xc0,0xe0,0xf0,0xf8,0xfc,0xfe,0xff,0x7f,0x3f,
0x1f,0x0f,0x07,0x03,0x01};

const char secu5[72] = {0x00,0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80, // Se crea la secuencia5.


0x81,0x82,0x84,0x88,0x90,0xa0,0xc0,0xc1,0xc2,
0xc4,0xc8,0xd0,0xe0,0xe1,0xe2,0xe4,0xe8,0xf0,
0xf1,0xf2,0xf4,0xf8,0xf9,0xfa,0xfc,0xfd,0xfe,
0xff,0x7f,0xbf,0x3f,0x5f,0x9f,0x1f,0x2f,0x4f,
0x8f,0x0f,0x17,0x27,0x47,0x87,0x7,0x0b,0x13,
0x23,0x43,0x83,0x03,0x05,0x09,0x11,0x21,0x41,
0x81,0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};

char cont = 0, selec = 1; // Variable para contar la secuencia.


unsigned long vel = 500; // variable para asignar la velocidad en ms.

/* Inicio del programa principal*/

void main(void){

set_tris_d(0x00); // Se configura todo el puerto D como salidas.


output_d(0xff); // Se mandan a apagar los leds.
output_float(UP); // Se configura el UP como entrada.
output_float(SHIFT); // Se configura el SHIFT como entrada.

enable_interrupts(GLOBAL); // Se habilitan las interrupciones globales.


enable_interrupts(INT_EXT1); // Se activa el uso de la INT1.
enable_interrupts(INT_EXT); // Se activa el uso de la INT0.
ext_int_edge(1,H_TO_L); // Se elige que las interrupciones externas
ext_int_edge(0,H_TO_L); // se active por flanco descendente.

for( ; ; ) {
switch(selec){
case 1: if(cont >= 16) // La tabla de secu1 contiene 16 códigos.
cont = 0; // solo contar de 0 a 15.
output_d(~secu1[cont]); // Se saca el código de la secu1.
cont++; // Se incrementa el cont.
break;
case 2: if(cont >= 32) // La tabla de secu2 contiene 32 códigos.
cont = 0; // solo contar de 0 a 31.
48

output_d(~secu2[cont]); // Se saca el código de la secu2.


cont++; // Se incrementa el cont.
break;
case 3: if(cont >= 23) // La tabla de secu3 contiene 23 códigos.
cont = 0; // solo contar de 0 a 22.

output_d(~secu3[cont]); // Se saca el código de la secu3.


cont++; // Se incrementa el cont.
break;
case 4: if(cont >= 32) // La tabla de secu4 contiene 32 códigos.
cont = 0; // solo contar de 0 a 31.
output_d(~secu4[cont]); // Se saca el código de la secu4.
cont++; // Se incrementa el cont.
break;

case 5: if(cont >= 72) // La tabla de secu5 contiene 72 códigos.


cont = 0; // solo contar de 0 a 71.
output_d(~secu5[cont]); // Se saca el código de la secu5.
cont++; // Se incrementa el cont.
break;
}
delay_ms(vel); // Se ejecuta un retardo de 1/3 de la velocidad de la secuencia.
}
}

// Interrupción externa INT0.

#int_ext
void ext_isr0() {
switch(vel){ // Se aumenta la velocidad disminuyendo vel.
case 500: vel = 350; break;
case 350: vel = 250; break;
case 250: vel = 150; break;
case 150: vel = 80; break;
case 80: vel = 20; break;
case 20: vel = 500; break; // Con velocidad máxima regresa y la mínima.
}

if(!input(UP)){
delay_us(500);
}
}

// Interrupción externa INT1.

#int_ext1
void ext_isr1() {
if(selec == 5) // Se checa si selec == 5 para hacerlo 0, y si no
selec = 1; // se incrementa en 1.
else
selec++;

if(!input(SHIFT)){
delay_us(500);
}
}
49

6. El Convertidor Analógico a Digital de 10 Bits.


Este microcontrolador cuenta con un módulo convertidor de analógico a digital (A/D) de 10 bits multiplexado en
13 canales, los canales AN2 (-VREF) y AN3 (+VREF) pueden ser configurados para cambiar el voltaje de
referencia del convertidor en un rango de +- 10V. Este convertidor A/D cuenta con una interrupción de
conversión completa que nos sirve para darnos cuenta, que el convertidor termino la conversión.

Los pines utilizados por el convertidor AD son:

RA0/AN0 RA1/AN1 RA2/AN2/-VREF RA3/AN3/+VREF RA5/AN4 RE3/AN5 RE1/AN6


RE2/AN7 RB2/AN8 RB3/AN9 RB1/AN10 RB4/AN11 RB0/AN12

6.1 Funciones de C para configurar y manipular el convertidor A/D.

set_adc_channel( ) setup_adc_ports( ) setup_adc( ) #device


read_adc( ) adc_done( ) #int_ad

set_adc_channel( )

Sintaxis: set_adc_channel(chan);

Función: Especifica el canal del convertidor AD que se usara para la próxima llamada de la
instrucción read_adc( ). Se recomienda esperar unos 10 us para que se complete la
operación de selección. La constante chan es un número entre el 0 y el 12 para elegir
el canal del convertidor.

Ejemplo: set_adc_channel(2); // Selecciona el canal AN2.


delay_us(10); // Esperar 10 us para completar la tarea anterior. .
value = read_adc( ); // Inicia la conversión y lee la conversión.

setup_adc_ports( )

Sintaxis: setup_adc_ports(value);
setup_adc_ports(port, [reference]);

Función: Selecciona los pines que serán analógicos, digitales y los voltajes VREF que se usaran.
La constante value o port puede ser NO_ANALOGS, ALL_ANALOG, AN0_TO_AN11,
AN0_TO_AN10, AN0_TO_AN9, AN0_TO_AN8, AN0_TO_AN7, AN0_TO_AN6,
AN0_TO_AN5, AN0_TO_AN4, AN0_TO_AN3, AN0_TO_AN2, AN0_TO_AN1, AN0,
otras constantes opcionales son VSS_VDD, VREF_VREF, VREF_VDD, VSS_VREF.

Ejemplo: setup_adc_ports( ALL_ANALOG ); // Se eligen todos los canales analógicos.


set_adc_channel(2); // Selecciona el canal AN2.

setup_adc( )

Sintaxis: setup_adc(mode);

Función: Configura la velocidad de conversión y apaga el convertidor A/D. La constante mode


puede ser ADC_OFF, ADC_CLOCK_DIV_2, ADC_CLOCK_DIV_4,
ADC_CLOCK_DIV_8, ADC_CLOCK_DIV_16, ADC_CLOCK_DIV_32,
ADC_CLOCK_DIV_64, ADC_CLOCK_INTERNAL otras constantes son
ADC_TAD_MUL_0, ADC_TAD_MUL_2, ADC_TAD_MUL_4, ADC_TAD_MUL_6,
ADC_TAD_MUL_8, ADC_TAD_MUL_12, ADC_TAD_MUL_16, ADC_TAD_MUL_20.
50

Ejemplo: setup_adc_ports( AN0 ); // Se elige el canal analógico AN0.


setup_adc(ADC_CLOCK_DIV_8 ); // La velocidad de conversión clock/8;
set_adc_channel( 0 ); // Seleccionamos el AN0;
value = read_adc(); // Se inicia una conversión y se lee el dato.
setup_adc( ADC_OFF ); // Se apaga el ADC.

read_adc( )

Sintaxis: value = read_adc(mode);

Función: Lee el valor digital del convertidor (entrega 16 bits u 8 bits). Los modos de lectura mode
son ADC_START_AND_READ (Inicia la conversión y lee el valor digital, esta es el
modo por defaul), ADC_START_ONLY (solo empieza la conversión y regresa),
ADC_READ_ONLY (Lee la última conversión).

Ejemplo: setup_adc( ADC_CLOCK_INTERNAL ); // Se elige velocidad del reloj interno.


setup_adc_ports(AN0_TO_AN1); // Se eligen los canales AN0 y AN1.
set_adc_channel(1); // Se selecciona el canal AN1.
delay_us(10);

read_adc(ADC_START_ONLY); // Se inicia la conversion del canal AN1


value = read_adc(ADC_READ_ONLY); // Se lee la conversion del canal.

adc_done( )

Sintaxis: value = adc_done( );

Función: Sondea si el convertidor ADC tiene lista la conversión o está ocupado, retorna un TRUE
(1) si la conversión esta lista y un FALSE (0) siel convertidor está ocupado, esta
instrucción se utiliza para manejar el A/D sin interrupción.

Ejemplo: long value; // Se declara una variable de 16 bits

setup_adc_ports(AN0_TO_AN1, VSS_VDD); // Se habilitan los AN0 y AN1 con


// –VREF = VSS y +VREF = VDD.
setup_adc(ADC_CLOCK_DIV_4 | ADC_TAD_MUL_8); // Velocidad de conversión /4
// TAD de 8 ciclos.
set_adc_channel(0); // Se selecciona el AN0.
delay_us(10); // Se espera a que se realice la selección anterior.

read_adc(ADC_START_ONLY); // Se inicia una conversión.

while(!adc_done()); // Espera mientras la conversión no esté lista


value = read_adc(ADC_READ_ONLY); // Finalmente se lee la conversión.

#int_ad

Función: Esta directiva se utiliza para marcar el inicio del vector de la interrupción del A/D, que se
ejecuta cuando tiene una conversión completa.

Ejemplo: long adc1; // Se declara una variable de 16 bits.

void main(void) { // Programa principal.


enable_interrupts(GLOBAL); // habilita las interrupciones globales.
51

enable_interrupts(INT_AD); // Enciende la interrupción externa INT1.


setup_adc( ADC_CLOCK_INTERNAL );// Se elige velocidad del reloj interno.
setup_adc_ports(AN0_TO_AN1); // Se habilitan los canales AN0 y AN1.
set_adc_channel(1); // Se selecciona el canal AN1.
read_adc(ADC_START_ONLY); // Se inicia una conversión.

while(true) { // Ciclo infinito.

}
}

#int_ad // Vector de la interrupción del ADC


void función_adc( ){ // Cuerpo de la función del AD.
adc1 = read_adc(ADC_READ_ONLY); // Se Lee la conversión.
}

#device

Sintaxis: #device adc = 8


#device adc = 16

Función: Esta directiva se utiliza para elegir si queremos que el convertidor trabaje a 8 o 16 bits.

Ejemplo: #device adc = 16

long adc1; // Se declara una variable de 16 bits.

void main(void) { // Programa principal.


enable_interrupts(GLOBAL); // habilita las interrupciones globales.
enable_interrupts(INT_AD); // Enciende la interrupción externa INT1.
setup_adc( ADC_CLOCK_INTERNAL );// Se elige velocidad del reloj interno.
setup_adc_ports(AN0_TO_AN1); // Se habilitan los canales AN0 y AN1.
set_adc_channel(1); // Se selecciona el canal AN1.
read_adc(ADC_START_ONLY); // Se inicia una conversión.

while(true) { // Ciclo infinito.

}
}

#int_ad // Vector de la interrupción del ADC


void función_adc( ){ // Cuerpo de la función del AD.
adc1 = read_adc(ADC_READ_ONLY); // Se Lee la conversión.
}
52

6.2 PRACTICA E6.1. Vúmetro digital con el convertidor A/D sin interrupción.

En esta práctica se desarrollara el código para realizar la lectura del voltaje en la terminal AN0 del convertidor
A/D y se mandaran iluminar unos leds conectados en el puerto D de acuerdo al voltaje censado, iluminando se
todos cuando el voltaje 5V y ninguno cuando el voltaje sea 0V. El circuito que se armara es el siguiente:

Código Fuente de la Práctica E6.1.

/* Programa para realizar la función de un vúmetro


digital utilizando el convertidor A/D a 10 bits con el
canal AN0, los indicadores serán 8 leds conectados
con lógica inversa en el puerto D.

Realizo: M.C. Julio Cesar Gallo Sánchez. */

/*CONFIGURACIÓN PIC*/

#include "18f4550.h"

#device adc=10 //Seleccionamos convertidor a 10 bits.

#use delay(clock=48000000)
#build(reset=0x02000,interrupt=0x02008)
#org 0x0000,0x1FFF { }

// Declaración y definición de variables

unsigned long adc0;


53

/* Inicio del programa*/

void main(void){

set_tris_d(0x00); // Se configura en Puerto D como salidas Digitales.


output_d(0xff); // Se apagan los leds conectados en el Puerto D.

setup_adc(ADC_CLOCK_INTERNAL|ADC_CLOCK_DIV_16); // Seleccionamos la fuente de


// reloj interno/16.
setup_adc_ports(AN0); // Habilita el AN0 del ADC.
set_adc_channel(0); // Selecciona el AN0 para realizar conversiones.

while(true){ // Ciclo infinito

adc0 = read_adc(); // Inicia una conversión y lee el resultado.

if(adc0 < 10) // Si el voltaje es menor a 50mV se apagan los leds.


output_d(0xff);
else if(adc0 >=10 && adc0 < 128) // Entre 50 y 640mV se enciende el primer led.
output_d(0xfe);
else if(adc0 >= 128 && adc0 < 256) // Entre 640mV y 1.28V se activan los 2 primeros leds.
output_d(0xfc);
else if(adc0 >= 256 && adc0 < 384) // Entre 1.28V y 1.92V se activan los 3 primeros leds.
output_d(0xf8);
else if(adc0 >= 384 && adc0 < 512) // Entre 1.92V y 2.56V se activan los 4 primeros leds.
output_d(0xf0);
else if(adc0 >= 512 && adc0 < 640) // Entre 2.56V y 3.2V se activan los 5 primeros leds.
output_d(0xe0);
else if(adc0 >= 640 && adc0 < 768) // Entre 3.2V y 3.84V se activan los 6 primeros leds.
output_d(0xc0);
else if(adc0 >= 768 && adc0 < 896) // Entre 3.84V y 4.48V se activan los 7 primeros leds.
output_d(0x80);
else // De 4.48 en adelante se encienden todos los leds.
output_d(0x00);

delay_ms(50); // retardo para realizar 20 lecturas por segundo.

}
}
54

6.3 PRACTICA E6.2. Vúmetro digital con el convertidor A/D con interrupción.

En esta práctica se desarrollara el código para realizar la lectura del voltaje en la terminal AN0 del convertidor
A/D utilizando la interrupción de conversión completa y se mandaran iluminar unos leds conectados en el puerto
D de acuerdo al voltaje censado, iluminando todos cuando el voltaje 5V y ninguno cuando el voltaje sea 0V. El
circuito que se armara es el siguiente:

Código Fuente de la Práctica E6.2.

/* Programa para realizar la función de un vúmetro


Digital, utilizando el convertidor A/D con interrupción
,usaremos el canal AN0 y los indicadores serán 8 leds
conectados en el puerto D con lógica inversa.

Realizo: M.C. Julio Cesar Gallo Sánchez. */

/*CONFIGURACIÓN PIC*/

#include "18f4550.h"
#device adc=10
#use delay(clock=48000000)
#build(reset=0x02000,interrupt=0x02008)
#org 0x0000,0x1FFF {}

// Declaración y definición de variables

unsigned long adc0; // variable para guardar la lectura del A/D.


char adcnew; // Variable para avisar que el dato en adc0 es nuevo.

void main(void){

set_tris_d(0x00); // Se configura en Puerto D como salidas Digitales.


output_d(0xff); // Se apagan los led conectados en el Puerto D.

enable_interrupts(GLOBAL); // Habilita las interrupciones globales.


enable_interrupts(INT_AD); // Enciende la interrupción del ADC.
setup_adc(ADC_CLOCK_DIV_16); // Fuente de reloj interno/16.
55

setup_adc_ports(AN0); // Habilita el AN0 del ADC.

adcnew = 0; // Se inicializa la variable que indica un


// que hay un nuevo valor en adc0.
set_adc_channel(0); // Selecciona el AN0
read_adc(ADC_START_ONLY); // Se inicia una conversión.

while(true){ // ciclo infinito.

if(adcnew == 1){ // Se verifica si hay un dato nuevo en adc0.

if(adc0 < 10) // Si el voltaje es menor a 50mV se apagan los leds.


output_d(0xff);
else if(adc0 >=10 && adc0 < 128) // 50 y 640mV se enciende el primer led.
output_d(0xfe);
else if(adc0 >= 128 && adc0 < 256) // 640mV y 1.28V los dos primeros leds.
output_d(0xfc);
else if(adc0 >= 256 && adc0 < 384) // 1.28V y 1.92V los tres primeros leds.
output_d(0xf8);
else if(adc0 >= 384 && adc0 < 512) // 1.92V y 2.56V los cuatro primeros leds.
output_d(0xf0);
else if(adc0 >= 512 && adc0 < 640) // 2.56V y 3.2V los cinco primeros leds.
output_d(0xe0);
else if(adc0 >= 640 && adc0 < 768) // 3.2V y 3.84V los seis primeros leds.
output_d(0xc0);
else if(adc0 >= 768 && adc0 < 896) // 3.84V y 4.48V los siete primeros leds.
output_d(0x80);
else // 4.48V en adelante todos los leds.
output_d(0x00);

adcnew = 0; // se hace adcnew 0 marcando que no hay lectura nueva.


delay_ms(50); // retardo para realizar 20 lecturas por segundo.
setup_adc(ADC_CLOCK_DIV_16); // Fuente de reloj interno/16.
read_adc(ADC_START_ONLY); // Se inicia una conversión.
}
}
}

#INT_AD // Marca el inicio del vector de la interrupción del A/D.


void ADC_CCompleta(void){
adc0 = read_adc(ADC_READ_ONLY); // Lectura del canal 0.
adcnew = 1; // Avisa que el dato está disponible en adc0.
setup_adc(ADC_OFF); // El ADC se apaga.
}
56

6.4 PRACTICA E6.3. Medición de Multivariable con el convertidor A/D con interrupción.

En esta práctica se desarrollara el código para realizar la lectura del voltaje de las terminales AN0, AN1, AN2 y
AN3 del convertidor A/D utilizando la interrupción de conversión completa y se mandaran iluminar unos leds
conectados en el puerto D de acuerdo al voltaje censado, se mostrara el valor del puerto analógico
seleccionado en el puerto D y se utilizaran los pines RE0 (S0) y RE1 (S1) para seleccionar el canal. El circuito
que se armara es el siguiente:

Código Fuente de la Práctica E6.3.

/* Programa para realizar la lectura de múltiples variables


analógicas con el ADC, utilizando el convertidor A/D con
interrupción usaremos los canales AN0 al AN3. y los indicadores
serán 8 leds conectados en el puerto D con lógica inversa.

Las lecturas de los canales serán puestas en el puerto D y serán


elegidas mediante dos selectores S0 (RE0) y S1 (RE1).

S1 S0 CANAL
0 0 AN0
0 1 AN1
1 0 AN2
1 1 AN3

Realizo: M.C. Julio Cesar Gallo Sánchez. */


57

/*CONFIGURACIÓN PIC*/

#include "18f4550.h"
#device adc=10
#use delay(clock=48000000)
#build(reset=0x02000,interrupt=0x02008)
#org 0x0000,0x1FFF {}

// Declaración y definición de variables y funciones.

#define S0 PIN_E0 // Se define S0 como el RE0


#define S1 PIN_E1 // Se define S1 como el RE1

unsigned long adc0,adc1,adc2,adc3,adcd; // variable para guardar la lectura del A/D.


char adcan,sel; // Variable para saber cuál canal hiso la conversión adcan y variable.
// para elegir que canal se mostrara en el puerto D.
void vúmetro(unsigned long vum); // Se declara la función vúmetro(long) para poderla usar.

/* Inicio del programa principal*/

void main(void){
set_tris_d(0x00); // Se configura en Puerto D como salidas Digitales.
output_d(0xff); // Se apagan los leds conectados en el Puerto D.
output_float(S0); // Se configuran el RE0 y RE1 como entradas.
output_float(S1);

enable_interrupts(GLOBAL); // Habilita las interrupciones globales.


enable_interrupts(INT_AD); // Enciende la interrupción del ADC.
setup_adc(ADC_CLOCK_DIV_16); // Fuente de reloj interno/16.
setup_adc_ports(AN0_TO_AN3); // Habilitan las entradas analógicas AN0 al AN3 del ADC.

set_adc_channel(0); // Seleccionamos el AN0


adcan = 0; // Para saber que el AN0 fue elegido para conversión.
read_adc(ADC_START_ONLY); // Se inicia una conversión.

while(true){
if(adcan == 10){ // Se verifica si todos los canales tienen dato nuevo.

sel = input_e() & 0x03; // Se deposita en sel el código del puerto que desplegara.
switch(sel){
case 0: adcd = adc0; break; // Si S1 y S0 son 0 0.
case 1: adcd = adc1; break; // Si S1 y S0 son 0 1.
case 2: adcd = adc2; break; // Si S1 y S0 son 1 0.
case 3: adcd = adc3; break; // Si S1 y S0 son 1 1.
}

vúmetro(adcd); // Función para mostrar en el puerto D el volar del canal elegido.

delay_ms(50); // Retardo para realizar 20 lecturas por segundo.


setup_adc(ADC_CLOCK_DIV_16); // Fuente de reloj interno/16.
set_adc_channel(0); // Seleccionamos el AN0
adcan = 0; // Para saber que el AN0 fue elegido para conversión.
read_adc(ADC_START_ONLY); // Se inicia una conversión.
}
}
}
58

/* Función para el vúmetro con el puerto D.*/

void vumetro(unsigned long vum){

if(vum < 10) // Si el voltaje es menor a 50mV se apagan los leds.


output_d(0xff);
else if(vum >=10 && vum < 128) // Entre 50 y 640mV se enciende el primer led.
output_d(0xfe);
else if(vum >= 128 && vum < 256) // Entre 640mV y 1.28V se encienden los dos primeros leds.
output_d(0xfc);
else if(vum >= 256 && vum < 384) // Entre 1.28V y 1.92V se encienden los tres primeros leds.
output_d(0xf8);
else if(vum >= 384 && vum < 512) // Entre 1.92V y 2.56V se encienden los cuatro primeros leds.
output_d(0xf0);
else if(vum >= 512 && vum < 640) // Entre 2.56V y 3.2V se encienden los cinco primeros leds.
output_d(0xe0);
else if(vum >= 640 && vum < 768) // Entre 3.2V y 3.84V se encienden los seis primeros leds.
output_d(0xc0);
else if(vum >= 768 && vum < 896) // Entre 3.84V y 4.48V se encienden los siete primeros leds.
output_d(0x80);
else // De 4.48 en adelante se encienden todos los leds.
output_d(0x00);

#INT_AD // Marca el inicio del vector de la interrupción del A/D.


void ADC_CCompleta(void){

if(adcan == 0){ // Si el AN0 fue el que realizo una conversión.


adc0 = read_adc(ADC_READ_ONLY); // Lectura del canal 0.
set_adc_channel(1); // Seleccionamos el AN1.
adcan = 1; // Para saber que el AN1 fue elegido para conversión.
}
else if(adcan == 1){ // Si el AN1 fue el que realizo una conversión.
adc1 = read_adc(ADC_READ_ONLY); // Lectura del canal 1.
set_adc_channel(2); // Selecciona el AN2
adcan = 2; // Para saber que el AN2 fue elegido para conversión.
}
else if(adcan == 2){ // Si el AN2 fue el que realizo una conversión.
adc2 = read_adc(ADC_READ_ONLY); // Lectura del canal 0.
set_adc_channel(3); // Seleccionamos el AN3
adcan = 3; // Para saber que el AN3 fue elegido para conversión.
}
else if(adcan == 3){ // Si el AN3 fue el que realizo una conversión.
adc3 = read_adc(ADC_READ_ONLY); // Lectura del canal 0.
setup_adc(ADC_OFF); // El ADC se apaga.
adcan = 10; // Avisamos que todos los canales tiene conversión nueva.
}

if(adcan != 10) // Si todavía faltan canales de realizar conversión.


read_adc(ADC_START_ONLY); // Se inicia una conversión del canal seleccionado.
}
59

7. El Display LCD y el Teclado Matricial.


El compilador CCS cuenta con librerías que podemos dar de alta para poder manipular el LCD de 16 x 2 y un
teclado matricial de 3 x 4.

7.1 El display LCD de 16 x 2 líneas.

Este display de cristal liquido cuenta con dos renglones con capacidad de presentar hasta 16 caracteres que
pueden ser alfanumérico o signos especiales como el punto, la coma, los signos de admiración ¡!, ¿?, ( ), [ ],{ },
”, #, $, &, =, +, -, *, /, entre otros.

Las terminales de conexión del display son:

Pin No. Señal Función


1 GND Tierra nivel de 0V
2 VCC +5 Volts de alimentación
3 Vo Contraste
4 RS 1 (Datos) y 0 (Comandos)
5 R/W Leer / Escribir
6 E Enable
7 DB0 Bits 0 del Bus de datos
8 DB1 Bits 1 del Bus de datos
9 DB2 Bits 2 del Bus de datos
10 DB3 Bits 3 del Bus de datos
11 DB4 Bits 4 del Bus de datos
12 DB5 Bits 5 del Bus de datos
13 DB6 Bits 6 del Bus de datos
14 DB7 Bits 7 del Bus de datos
15 LED A Ánodo del LED (opcional)
16 LED K Cátodo del LED (opcional)

La librería que se utiliza para controlar el display LCD con interfaz de 4 bits es #include<lcd.c>, antes de incluirla
se deben definir los pines que serán utilizados para el bus de datos (DB4, DB5, DB6 y DB7) y para el bus de
control (RS, E/W y E) de la LCD, los pines DB0 al DB3 de la LCD no se conectan con interfaz de 4 bits.

Ejemplo: #define LCD_ENABLE_PIN PIN_B5 // El RB5 será el E de la LCD.


#define LCD_RW_PIN PIN_B6 // El RB6 será el R/W de la LCD.
#define LCD_RS_PIN PIN_B7 // El RB7 será el RS de la LCD.
#define LCD_DATA4 PIN_D4 // El RD4 será el DB4 de la LCD.
#define LCD_DATA5 PIN_D5 // El RD5 será el DB5 de la LCD.
#define LCD_DATA6 PIN_D6 // El RD6 será el DB6 de la LCD.
#define LCD_DATA7 PIN_D7 // El RD7 será el DB7 de la LCD.
#include <lcd.c> // Se incluye la librería de la LCD.

La librería lcd.c, contiene funciones que podemos utilizar para manejar la LCD.

lcd_init ( ) lcd_putc ( ) printf ( )


lcd_gotoxy ( ) lcd_getc ( )

lcd_init ( )

Sintaxis: lcd_init( );

Función: Inicializa la LCD, limpia la pantalla y posiciona el cursor en la posición (1,1), esta
función debe llamarse antes de cualquier otra función.
60

lcd_putc ( )

Sintaxis: lcd_putc (c);

Función: Despliega en la LCD un carácter (c es un carácter o variable de 8 bits).


\a Coloca el cursor en la posición superior izquierda (1,1).
\f Limpia la LCD y coloca el cursor en al posición (1,1).
\n Coloca el cursor en el inicio de la segunda línea (1,2).
\b Regresa el cursor una posición.

Ejemplo: lcd_init( ); // inicializa la LCD.

lcd_putc(\f); // Limpia la LCD y coloca el cursor en la posición (1,1).

lcd_putc(‘H’); // Despliega en la LCD “HOLA”.


lcd_putc(‘O’);
lcd_putc(‘L’);
lcd_putc(‘A’);

lcd_gotoxy ( )

Sintaxis: lcd_gotoxy (x, y);

Función: Posiciona el cursor en la posición (x, y) del LCD, x es una variable int8 que representa
las columnas puede ser 1 a 15 e y es una variable tipo int8 que representa las
columnas pude ser 1 (línea superior) o 2 (línea inferior). La posición superior izquierda
es (1, 1).

Ejemplo: lcd_init( ); // inicializa la LCD.

lcd_putc(\f); // Limpia la LCD y coloca el cursor en la posición (1,1).


lcd_gotoxy(7,1) ; // Posicionamos el cursor posición (5, 1) línea superior.

lcd_putc(‘H’); // Despliega “ HI ” en el centro de la LCD.


lcd_putc(‘I’);

lcd_getc ( )

Sintaxis: value = lcd_getc (x, y);

Función: Retorna el carácter contenido en la posición (x, y) en value.

Ejemplo: char leer;

lcd_init( ); // inicializa la LCD.

lcd_putc(\f); // Limpia la LCD y coloca el cursor en la posición (1,1).


lcd_putc(‘V’); // Despliega “Value” en el centro de la LCD.
lcd_putc(‘a’);
lcd_putc(‘l’);
lcd_putc(‘u’);
lcd_putc(‘e’);

leer = lcd_getc(4,1); // La variable lee = ‘u’.


61

printf ( )

Sintaxis: printf(fname, cstring, values…);

Función: Esta función nos permite desplegar una cadena de caracteres (cstring) y variables
(values) separadas por comas, utilizando como medio de despliegue la LCD utilizando
la función lcd_putc( ) (fname,si no colocas una función por default despliega por el
puerto serie RS232).

El formato para desplegar variable es colocar el signo % seguido de:


c carácter.
s cadenas de caracteres.
u Entera sin signo de 8 bits.
d Entera signada de 8 bits.
lu Entera sin signo de 16 bits.
ld Entera signada de 16 bits.
x Entero hexadecimal en minúsculas de 8 bits.
X Entero hexadecimal en mayúsculas de 8 bits.
lx Entero hexadecimal en minúsculas de 16 bits.
lX Entero hexadecimal en mayúsculas de 16 bits.
f flotante decimal truncado.
g Flotante decimal redondeado.
e Flotante en formato exponencial.

Ejemplos de los formatos:


value = 0x12. value = 0xfe.
%03u 018 254
%u 18 254
%2u 18 indefinido
%d 18 -2
%x 12 fe
%X 12 FE
%4X 0012 00FE
%3.1f 1.8 25.4

Ejemplo: int a = 23; // Se declara e inicializa a = 23.


long b= -1234; // Se declara e inicializa b = -1234.
float c= 2343.54; // Se declara e inicializa c = 2343.54.

lcd_init( ); // Se inicializa la LCD.

printf(lcd_putc, “\fa=%u y b=%lu”, a, b); // Se despliega:


printf(lcd_putc, “c=%f”, c); // “a=23 y b=-1234” en la línea superior.
// “c=2343.54” en la línea inferior.
62

7.2 PRACTICA E7.1. Desplegar mensajes y variables en la LCD.

En esta práctica se desarrollara el código para desplegar en una LCD de 16 x 2, algunos mensajes y variables
utilizando la librería lcd.c. Usaremos la LCD con interfaz de 4 bits conectando el bus de datos en RD4 al RD7,
RS con RB7, R/W con RB6 y E con RB5. El circuito que se armara es el siguiente:

Código Fuente de la Práctica E7.1.

/* Este programa despliega mensajes y variables en la LCD


de 16 x 2, utilizando la librería adc.c del compilador CCS.
Las terminales que se utilizaran para conectar el bus de
datos de la LCD son RD4 al RD7 y el bus de control esta
conectado en E (RB5), R/W (RB6) y RS (RB7).

Realizo: M.C. Julio Cesar Gallo Sánchez. */

/*CONFIGURACION PIC*/

#include "18f4550.h"

#use delay(clock=48000000)
#build(reset=0x02000,interrupt=0x02008)
#org 0x0000,0x1FFF {}

#define LCD_ENABLE_PIN PIN_B5 // Definición del RB5 para el E del LCD


#define LCD_RW_PIN PIN_B6 // Definición del RB6 para el R/W del LCD
#define LCD_RS_PIN PIN_B7 // Definición del RB7 para el RS del LCD
#define LCD_DATA4 PIN_D4 // Definición del RD4 para el DB4 del LCD
#define LCD_DATA5 PIN_D5 // Definición del RD5 para el DB5 del LCD
#define LCD_DATA6 PIN_D6 // Definición del RD6 para el DB6 del LCD
#define LCD_DATA7 PIN_D7 // Definición del RD7 para el DB7 del LCD
#include <lcd.c> // Se incluye al proyecto la librería lcd.c

/* Programa principal*/
63

void main() {
char k = 56;
int b=56;
float g = 2.345;

lcd_init(); // Inicializo la lcd.

for(;;){ // Ciclo infinito.

lcd_gotoxy(2,1); // Posicionamos el cursor en la posición (2,1).


printf(lcd_putc, "Hola, vamos",); // Desplegamos "Hola, vamos".
lcd_gotoxy(2,2); // Posicionamos el cursor en la posición (2,2).
printf(lcd_putc, "a desplegar."); // Desplegamos "a desplegar.".
delay_ms(2000); // Retardo de 2 segundos para ver el mensaje.

printf(lcd_putc, "\fk = %c y b = %u", k, b); // Se despliega en (1,1) "k = s y b = 56".


printf(lcd_putc, "\ng = %1.2f", g); // Se despliega en (1,2) "g = 2.34".
delay_ms(2000); // Retardo de 2 segundos para ver el mensaje.

}
}

7.3 El Teclado Matricial.

Los teclados matriciales son ensamblados en forma de matriz como se muestra en la figura:

Este diagrama muestra un teclado como una matriz de 4x4 16 teclas configuradas en cuatro columnas y
cuatro renglones, cuando no se oprime ninguna tecla no hay conexión entre renglones y columnas. Cuando se
oprime una tecla se hace una conexión entre la columna y el renglón de la tecla.
64

Muchos teclados comerciales ya traen incluido un decodificador, que escanea el teclado y si una tecla es
presionada regresa un número que identifica la tecla. Otra alternativa es adquirir por separado un chip
decodificador (74LS922) y conectarlo al teclado como muestra la figura.

El decodificador mostrado tiene 8 entradas, las 4 entradas X son conectadas a las columnas del teclado y las 4
entradas Y son conectadas a los renglones. No se muestran los capacitores que gobiernan la rapidez a la que
se escanea el teclado.

Cuando se oprime una tecla el código de 4 bits de la tecla aparecerá en las 4 lineas de salida Q, y la línea de
dato disponible DA se pone en bajo. Si conectamos una interrupción externa en línea DA, cuando se oprima el
alguna tecla, la rutina de servicio de la interrupción, leerá los 4 bits de salida Q y procesara el dato. La terminal
de habilitación de salida OE, permite mantener en el tercer estado las salidas hasta que decidamos leerlas.

El chip del decodificador se encarga de eliminar el rebote de las teclas, lo que libera al programador de esta
responsabilidad, esta es una ventaja al utilizar un chip decodificador.

Siempre que se utiliza un teclado matricial con un microcontrolador, es necesario conectar también un display
LCD y el circuito típico es:
65

Se observa que se necesitan prácticamente dos puertos de 8 pines (un total de 15 terminales digitales) para
conectar el teclado de 4x4 y la LCD con interfaz de 4 bits, estas son muchas terminales consumidas.

Existe un método para eliminar el número de terminales digitales empleadas y consiste en multiplexar el bus de
datos de cuatro bits que utiliza la LCD, con los renglones del teclado matricial. El circuito quedaría como se
muestra a continuación y nos ahorramos 4 terminales:

En este circuito se consume todo un puerto de 8 bits y además 3 terminales de cualquier otro puerto sumando
un total de 11 terminales.

Con el microcontrolador PIC18F4550, se recomienda utilizar el puerto D (como si fuera el puerto P1) para
conectar el teclado matricial y el bus de datos de la LCD, ya que este no contiene funciones multiplexadas en
estas terminales y para los pines E, RS y R/W de la LCD utilizar un grupo de tres terminales que puedan ser
configuradas como puertos de salidas.

El compilador CCS cuenta con una librería (kbd.c) que sirve para decodificar la tecla presionada en un teclado
matricial de 3x4 conectado en el puerto D o en el puerto B según se elija. Para lograr conectar de manera
correcta el teclado matricial de 3x4 con la LCD utilizando el puerto D o el puerto B, tendremos que abrir el
archivo kbd.c y configurar los pines que utilizaremos como renglones y columnas para el teclado.

En la siguiente figura se muestra cómo hacerlo.


66

Al abrir la librería kbd.c, que se encuentra en la carpeta Archivos de Programa\ PICC\Drivers\, necesitamos
ubicar en el código la sección que se muestra en la imagen y realizar los cambios que se muestran y guardar
los cambios.

Realizados los cambios anteriores ya podemos conectar el teclado matricial de 3x4 en las terminales marcadas
del puerto B o el puerto D, según elijamos cual será la interfaz del teclado matricial de 3x4.

Para utilizar esta librería es necesario primero definir una directiva para elegir en cual puerto se conectara el
teclado, como se muestra en seguida:

#define use_portb_kbd TRUE // Elegimos el puerto B como puerto del Teclado.


#include <kbd.c> // Incluimos la librería del teclado en nuestro proyecto.

O simplemente:

#include <kbd.c> // Cuando solamente Incluimos la librería del teclado, por default
// el puerto D es elegido para conectar el teclado.

La librería kbd.c, contiene funciones que podemos utilizar para manejar el teclado matricial de 3x4.

kbd_init ( ) kbd_getc ( )

kbd_init ( )

Sintaxis: kbd_init ( );

Función: Esta función inicializa el teclado y debe ser llamada antes que cualquier otra función.
67

kbd_getc ( )

Sintaxis: value = kbd_getc ( );

Función: Devuelve una clave de acuerdo a la tecla presionada o 0 si no se presiona ninguna. La


variable value es tipo int8 y contendrá el código de la tecla presionada.

Los códigos que devuelve cuando se presiona una tecla son:

‘1’ ‘2’ ‘3’


‘4’ ‘5’ ‘6’
‘7’ ‘8’ ‘9’
‘*’ ‘0’ ‘#’

Esta función debe usarse frecuentemente, mientras se requiera checar si se están


presionando teclas.

Ejemplo: #include <kbd.c> // Incluimos la librería kbd.c y el puerto D como la interfaz.

char k;
void main() {
char k; // Se declara la variable donde se guarda el código de la tecla.
kbd_init();

while(true) { // ciclo infinito.

k = kbd_getc( ); // Se guarda el código generado en k.

}
68

7.4 PRACTICA E7.2. Visualización de los datos de un teclado matricial 3x4 en la LCD.

En esta práctica se desarrollara el código para desplegar en la LCD los códigos generados cuando se presiona
una tecla del teclado matricial conectado en el puerto D, utilizando la librería kbd.c del compilador CCS. Los
pines utilizados se pueden ver en el diagrama del circuito que se muestra a continuación:

Código Fuente de la Práctica E7.2.

/* Programa para visualizar los caracteres que genera


la librería kbd.c con un teclado matricial de 3x4
conectado en el puerto D, en la LCD.
Realizo: M.C. Julio Cesar Gallo Sánchez */

/*CONFIGURACION PIC*/

#include "18f4550.h"

#use delay(clock=48000000)
#build(reset=0x02000,interrupt=0x02008)
#org 0x0000,0x1FFF {}

#define LCD_ENABLE_PIN PIN_B5


#define LCD_RW_PIN PIN_B6
#define LCD_RS_PIN PIN_B7
#define LCD_DATA4 PIN_D4
#define LCD_DATA5 PIN_D5
#define LCD_DATA6 PIN_D6
#define LCD_DATA7 PIN_D7
#include <lcd.c>

#include <kbd.c> // Renglones (RD4 - RD7) y las Columnas (RD0 - RD2)


// Colocar pull up en los renglones si no está conectado
// el LCD.
69

/*Programa principal*/

void main( ) {
char k; // k es la variable donde se guardara el código de la tecla presionada.
int x; // variable para desplegar el valor entero.
lcd_init(); // Se inicializa la LCD.
kbd_init(); // Se inicializa el Teclado de 3x4.
printf(lcd_putc,"\fListo...\n"); // Se despliega el mensaje "Listo...".

for( ; ; ){ // Ciclo Infinito.

k = kbd_getc(); // Se inspecciona el teclado y se deposita en k el codigo.


x = k - 48; // Conversión numérica

if(k != 0){
if(k == '*') // Si se presiona la tecla '*' se limpia la LCD.
lcd_putc('\f'); // y se posiciona el cursor en la posición (1,1).
else
lcd_putc(k); //Imprime carácter de la tecla presionada.

delay_ms(1000); // Retardo de 1s para espera a que visualicen la LCD.


printf(lcd_putc,"\f Car=%c",k); // Imprime carácter
delay_ms(1000); // Retardo de 1s para espera a que visualicen la LCD.
printf(lcd_putc,"\f Car=%u",k); //Imprime valor ASCII
delay_ms(1000); // Retardo de 1s para espera a que visualicen la LCD.
printf(lcd_putc,"\f Num=%u",x); //Imprime valor numérico
delay_ms(1000); // Retardo de 1s para espera a que visualicen la LCD.

printf(lcd_putc, "\fListo...\n"); // Este mensaje se visualizara si no se presiona tecla.


}
}
}

7.5 Creación de una librería para un teclado matricial de 4x4.

Si lo que se quiere es conectar un teclado de 4x4, la librería kbd.c, puede ser modificada y guardada con otro
nombre por ejemplo kbd1.c, las modificaciones que se deben realizar son:
70
71

Una vez realizado todos los cambios mostrados anteriormente, guarde el archivo como kbd1.c, en la misma
dirección de kbd.c y listo, ya tenemos una librería para un teclado matricial de 4x4.
72

7.6 PRACTICA E7.3. Visualización de los datos de un teclado matricial 4x4 en la LCD.

En esta práctica se desarrollara el código para desplegar en la LCD los códigos generados cuando se presiona
una tecla del teclado matricial de 4x4 conectado en el puerto D, utilizando la librería kbd1.c que creamos a
partir de la librería kbd.c del compilador CCS. Los pines utilizados se pueden ver en el diagrama del circuito
que se muestra a continuación:

Código Fuente de la Práctica E7.3.

/* Programa para visualizar los caracteres que genera


la librería kbd1.c con un teclado matricial de 4x4
conectado en el puerto D, en la LCD.

Realizo: M.C. Julio Cesar Gallo Sánchez */

/*CONFIGURACION PIC*/

#include "18f4550.h"

#use delay(clock=48000000)
#build(reset=0x02000,interrupt=0x02008)
#org 0x0000,0x1FFF {}

#define LCD_ENABLE_PIN PIN_B5


#define LCD_RW_PIN PIN_B6
#define LCD_RS_PIN PIN_B7
#define LCD_DATA4 PIN_D4
#define LCD_DATA5 PIN_D5
#define LCD_DATA6 PIN_D6
#define LCD_DATA7 PIN_D7
#include <lcd.c>

#include <kbd1.c> // Renglones (RD4 - RD7) y las Columnas (RD0 – RD3)


// Colocar pullup en los renglones si no está conectado
// el LCD.
73

/*Programa principal*/

void main( ) {

char k; // k es la variable donde se guardara el código de la tecla presionada.

lcd_init(); // Se inicializa la LCD.


kbd_init(); // Se inicializa el Teclado de 4x4.

printf(lcd_putc,"\fListo...\n"); // Se despliega el mensaje "Listo...".

for(;;) { // Ciclo Infinito.

k = kbd_getc(); // Se inspecciona el teclado y se deposita en k el código.

if(k != 0){
if(k == '*') // Si se presiona la tecla '*' se limpia la LCD.
printf(lcd_putc, "\fListo...\n"); / Se despliega el mensaje "Listo...".
else
lcd_putc(k); //Imprime carácter de la tecla presionada.

delay_ms(350);
}
}
}
74

8. La Memoria EEPROM de Datos.


El microcontrolador PI18F4550, cuenta con una memoria EEPROM para datos no volátiles de 256 bytes, esta
almacena datos que se deben conservar aun en ausencia de la tención de alimentación. La EEPROM puede
ser escrita y leída de forma individual en cada una de las 256 posiciones de memoria, en tiempo de ejecución a
través de unos registros y permite hasta 1,000,000 de ciclos de borrado y escritura.

Cuando se realiza una operación de escritura la circuitería interna del PIC, se encarga de borrar previamente la
posición en la que se desea escribir. La duración de un ciclo completo de borrado/escritura de un byte en la
memoria EEPROM es de unos 4 ms.

8.1 Funciones para Leer y Escribir en la EEPROM de Datos.

En lenguaje C existen un grupo de funciones que nos sirven para leer y escribir datos en la EEPROM.

read_eeprom ( ) write_eeprom ( ) #rom

read_eeprom ( )

Sintaxis: value = read_eeprom (address);

Función: Lee un byte de la EEPROM en la dirección especificada (address), la dirección


comienza en 0 y el rango depende de la EEPROM. Retorna un byte de 8 bits y adress
es una variable de 8 o 16 bits.

Ejemplo: #define VOLUMEN 10

char volumen;

volumen = read_eeprom(VOLUMEN); // se lee la dirección 10 de la EEPROM.

write_eeprom ( )

Sintaxis: write_eeprom (address, value);

Función: Escribe un byte (value) en la dirección especificada (adress) de la EEPROM, esta


función tarda varios milisegundos en ejecutarse. La dirección adress es una variable de
8 o 16 bits.

Ejemplo: #define VOLUMEN 10

char volumen = 154;

write_eeprom(VOLUMEN, volumen); // Guarda la volumen en la dirección 10.

#rom

Sintaxis: #rom address = { list };

Función: Admite la inserción de datos en la EEPROM desde el archivo *.hex. La dirección


(address) de la directiva #rom, es parte del espacio de memoria y no debe coincidir con
la directiva #org.

Ejemplo: #rom 0x2100 = { 1, 2, 3, 4, 5, 6, 7, 8}; // Se asigna la dirección e inicializa EEPROM.


75

8.2 Practica E8.1. Control de Acceso a través del teclado.

En esta práctica se desarrolla el código para realizar un control de acceso mediante el teclado de 3x4, la LCD y
la memoria para datos EEPROM. El usuario presionara la palabra clave, si esta correcta se activara por dos
segundos un relevador conectado en el pin RA0. El circuito que se armara es el siguiente:

Código Fuente de la Práctica E8.1.

/* Programa para Controlar el acceso utilizando la.


memoria de datos EEEPROM, el teclado 3x4 y la LCD
conectada en el puerto D. El pin RA0 se utilizara
para activar un relevador durante 2 segundos cuando el
código sea correcto.

Realizo: M.C. Julio Cesar Gallo Sánchez */

/*CONFIGURACION PIC*/

#include "18f4550.h"

#use delay(clock=48000000)
#build(reset=0x02000,interrupt=0x02008)
#org 0x0000,0x1FFF {}

#define LCD_ENABLE_PIN PIN_B5


#define LCD_RW_PIN PIN_B6
#define LCD_RS_PIN PIN_B7
#define LCD_DATA4 PIN_D4
#define LCD_DATA5 PIN_D5
#define LCD_DATA6 PIN_D6
#define LCD_DATA7 PIN_D7
#include <lcd.c>
76

#include <kbd.c> // Renglones (RD4 - RD7) y las Columnas (RD0 - RD2)


// Colocar pullup en los renglones si no esta conectado
// el LCD.
#include <string.h> // Librería para manipular caracteres y cadena de caracteres.
#rom 0x2100={'9','8','5','6','0','2','8','2',8} // Posición 0,1,2,3,4,5,6,7,8 de la eeprom con los datos
// '9','8','5','6','0','2','8','2' y 8 respectivamente.
#define RELE PIN_A0

/*Programa principal*/

void main( ) {

char k;
int i,npass;
char data[8], clave[8]; //Matrices para guardar clave y datos

npass = read_eeprom(8);

lcd_init( ); // Se inicializa la LCD.


kbd_init( ); // Se inicializa el Teclado de 3x4.

for (i=0; i <= npass-1; i++) { //Pasa datos de eeprom a la matriz clave
clave[ i ] = read_eeprom(i);
}

for(;;) { // Ciclo Infinito.

inicio:
printf(lcd_putc,"\fPress Password: \n");

i = 0; // Posición de la matriz

while(i <= 7){ // Para ocho datos maximo.


k = kbd_getc(); // Lee el teclado
if (k != 0){ // Si se ha pulsado alguna tecla
if(k == '#')
break;
else if(k == '*')
goto cambio;
else{
data[i] = k; // se guarda en la posición correspondiente
i++; // de la matriz
lcd_putc('*'); // Se imprime un *
}
delay_ms(250);
}
}

if(i == npass && strncmp(data, clave, npass) == 0){


printf(lcd_putc,"\f Puerta Abierta "); // Compara los datos con la clave
output_high(RELE); // Si es igual da pulso al relé
delay_ms(2000);
output_low(RELE);
}
else
printf(lcd_putc,"\fPuerta Cerrada"); //Clave erronea
77

goto inicio;

cambio:
printf(lcd_putc,"\f Password Old \n");
i = 0; //posición de la matriz

while(i <= 7){ // Para ocho datos maximo.


k = kbd_getc(); // Lee el teclado

if (k != 0){ // Si se ha pulsado alguna tecla


if(k == '#')
break;
else if(k == '*')
goto inicio;
else{
data[i] = k; // se guarda en la posición correspondiente
i++; // de la matriz
lcd_putc('*'); // Se imprime un *
}
delay_ms(250);
}
}

if(i == npass && strncmp(data, clave, npass) == 0){

printf(lcd_putc,"\f Password Old ");


printf(lcd_putc,"\n Correcta ");
delay_ms(2000);
printf(lcd_putc,"\f Password New \n");

i = 0; //posición de la matriz

while(i <= 7){ // Para ocho datos maximo.


k = kbd_getc(); // Lee el teclado

if (k != 0){ // Si se ha pulsado alguna tecla


if(k == '#' && i > 0)
break;
else if(k == '*')
goto inicio;
else{
data[i] = k; // se guarda en la posición correspondiente
i++; // de la matriz
lcd_putc(k); // Se imprime un k
}
delay_ms(250);
}
}

npass = i;
lcd_gotoxy(1,1);
printf(lcd_putc," Clave Aceptada "); // Compara los datos con la clave
delay_ms(2000);
lcd_gotoxy(1,1);
printf(lcd_putc," Copiando Datos "); // Compara los datos con la clave
78

for(i = 0; i <= npass-1; i++){


write_eeprom(i,data[i]);
}
write_eeprom(8,npass);

for (i=0; i <= npass-1; i++) { //Pasa datos de eeprom a la matriz clave
clave[i] = read_eeprom(i);
}
delay_ms(1500);
goto inicio;
}

else {
printf(lcd_putc,"\f Password Old "); //Clave erronea
printf(lcd_putc,"\n Incorrecto ");
delay_ms(2000);
}

}
79

9. Los Timers.
El microcontrolador PIC18F4550 cuenta con 4 módulos timers:

 Timer 0
 Timer 1
 Timer 2
 Timer 3

9.1 El Timer 0.

El timer 0, tiene las siguientes características:

 Configurable como temporizador / contador de 8 o 16 bits.


 Prescala programable con 8 estados.
 Interrupción por sobre flujo del registro contador.
 Fuente del reloj configurable como interna o externa.
 Configurable el Flanco del reloj externo.

En el diagrama interno del timer 0, muestra cómo podemos utilizar el pin T0CKI (RA4) para conectar la fuente
externa de reloj o elegir reloj interno con ¼ de la frecuencia del oscilador interno. Cuenta con 1 bits para elegir si
se elige prescala o no y 3 bit para programar la prescala para dividir la fuente del reloj entre 2, 4, 8, 16, 32, 64,
128 y 256. Si decides trabajar el timer 0 con 8 bits el registro TMR0L es el registro contador o si decides trabajar
el timer 0 con 16 bits los registros TMR0H y TMR0L contiene la cuenta. Los registros TMR0H y TMR0L se
pueden leer y escribir mediante software. Todos los timer son contadores ascendentes y cuando llegan a su
número máximo del conteo se activa la bandera de sobre flujo o se activa la interrupción si esta se habilito.

9.1.1 Funciones de C para configurar y operar el timer 0.

El compilador CCS cuenta con un grupo de funciones para configurar y controlar la operación del timer 0, estas
funciones son:

setup_timer_0 ( ) set_timer0 ( ) o set_rtcc( )


get_timer0 ( ) o get_rtcc ( ) #int_timer0
80

Recuerde que el timer 0, cuenta con la interrupción por sobre flujo y las funciones para configurar las
interrupciones se vieron en el Capitulo 6. Las Interrupciones Externas.

setup_timer_0 ( )

Sintaxis: setup_timer_0 (mode);

Función: Configura el timer 0, la constante mode pueden ser uno o dos definiciones contenidas
en el archivo *.h del dispositivo, algunas de estas definiciones son RTCC_INTERNAL,
RTCC_EXT_L_TO_H o RTCC_EXT_H_TO_L, RTCC_DIV_2, RTCC_DIV_2,
RTCC_DIV_8, RTCC_DIV_16, RTCC_DIV_32, RTCC_DIV_64 y RTCC_DIV_256,
RTCC_OFF y RTCC_8_BIT, entre otras.

Ejemplo: setup_timer_0 (RTCC_DIV_2 | RTCC_EXT_L_TO_H); // Configura prescala Tosc/2 y


// Reloj externo con flanco ascendente.

set_timer0 ( )

Sintaxis: set_timer0 (value);


set_rtcc (value);

Función: Ajusta el registro contador del timer 0, la variable value puede ser de 8 o 16 bits. RTCC
y TIMER0 son lo mismo, cuando el contador del timer 0 llega a su cuenta máxima da
vuelta e inicia de nuevo con 0 y continua contando.

Ejemplo: // Con el reloj interno en 20 MHz, sin prescala, y configurado el timer 0 a 8 bits
// la cuenta es cada 200 ns (Tosc/4).

#use delay (clock = 20000000)

setup_timer_0(RTCC_INTERNAL | RTCC_8_BIT); // Reloj interno y 8 bits.


set_timer0 (81); // El sobre flujo será cada 35 ns = (256 – 81) * 200 ns.

get_timer0 ( )

Sintaxis: value = get_timer0 ( );


value = get_rtcc ( );

Función: Retorna el valor del registro contador del timer0, la variable value puede ser de 8 o 16
bits. RTCC y TIMER0 son lo mismo.

Ejemplo: set_timer0 (0); //Ajusta el registro contador a 0.

while (get_timer0( ) < 200); // Mientras el registro contador sea menos a 200.

#int_timer0

Función: Esta directiva marca el inicio del vector de la interrupción por sobre flujo del timer 0. el
sobre flujo acurre cuando el registro contador del timer0, llega a su valor máximo de
conteo y regresa a cero.

Ejemplo: #use delay (clock = 20000000)

void main ( ){
81

enable_interrupts(INT_TIMER0); // Se habilita la interrupción timer0.


enable_interrupts(GLOBAL); // Se habilitan las interrupciones globales.

setup_timer_0 (RTCC_INTERNAL | RTCC_8_BIT); // Tosc/4 y 8 bits.


set_timer0 ( 6 ); // Sobre flujo cada 50 us
output_drive(PIN_E0); // RE0 como salidas.

for ( ; ; ){

}
}

#int_timer0
void TIMER0_OVF(void){ // Se genera una señal de 10 kHz.
set_timer0 ( 6 );
output_toggle(PIN_E0);
clear_interrupt(INT_TIMER0); // Se limpia la bandera del timer0.
}
82

9.1.2 Practica E9.1. Generación de una señal de 5 Hz con el TIMER0.

En esta práctica se desarrollara el código para generar una señal de 5 Hz, utilizando el timer0 con reloj interno y
la terminal RE0. El circuito que se armara es el siguiente:

Código Fuente de la Práctica E9.1.

/* Programa para generar una señal de 5 Hz con el timer0


con reloj interno y la terminal RE0.

Realizo: Julio Cesar Gallo Sánchez */

/*CONFIGURACION PIC*/

#include "18f4550.h"

#use delay(clock=20000000)
#build(reset=0x02000,interrupt=0x02008)
#org 0x0000,0x1FFF {}

/* Programa principal*/

void main(void)
{
output_drive(PIN_E0); // RE0 como salida
enable_interrupts(GLOBAL); // Habilita interrupciones globales.
enable_interrupts(INT_TIMER0); // Habilita la interrupción del Timer0.
setup_timer_0(RTCC_INTERNAL | RTCC_DIV_16); //Tosc/4 , prescala 16.
set_timer0(34286); // Sobre flujo cada 0.1 s.

while(true){

}
}

#int_timer0 // Inicio del vector interrupción del timer0.


void timer0_ovf() { // Interrupción por sobre flujo.
set_timer0(34286); // Sobre flujo cada 0.1 s.
output_toggle(PIN_E0); // Frecuencia de 5Hz.
}
83

9.2 El Timer 1.

El timer 1, tiene las siguientes características:

 Configurable como timer / contador de 16 bits.


 Fuente del reloj puede ser externa o interna.
 Dispone de un oscilador propio que puede funcionar como:
o Señal de reloj del temporizador 1.
o Señal de reloj de la cpu en modo bajo consumo.
 Prescala programables de 4 estados.
 Interrupción por sobre flujo.
 El registro de conteo se puede leer y escribir por software.

En la figura, se apreciar que el timer 1,puede elegir entre oscilador externo (con los pines RC0/T1OSO y
RC1/T1OSI que sirven para colocar un cristal XTAL) e interno trabajando a Fosc/4. Tiene un selector de
prescala para dividir la frecuencia de la fuente del reloj entre 1, 2, 4 y 8 lo que nos permite hacer más lenta la
cuenta del timer. Este timer utiliza los registros TMR1H y TMR1L para realizar el conteo, si el registro contador
llega al número máximo, se reinicia en 0 y continua contando. Cada vez que el registro contador llega al número
máximo y se reinicia, se activa una bandera de sobre flujo lo que puede activa la interrupción de sobre flujo del
timer 1, si es que esta se habilito.

9.2.1 Funciones de C para configurar y operar el timer 1.

El compilador CCS cuenta con un grupo de funciones para configurar y controlar la operación del timer 1, estas
funciones son:

setup_timer_1 ( ) set_timer1 ( )
get_timer1 ( ) #int_timer1

Recuerde que el timer 1, cuenta con la interrupción por sobre flujo y las funciones para configurar las
interrupciones se vieron en el Capitulo 6. Las Interrupciones Externas.
84

setup_timer_1 ( )

Sintaxis: setup_timer_1 (mode);

Función: Configura el timer 1, la constante mode pueden ser uno o dos definiciones contenidas
en el archivo *.h del dispositivo, algunas de estas definiciones son T1_DISABLED,
T1_INTERNAL, T1_EXTERNAL y T1_EXTERNAL_SYNC, T1_CLK_OUT,
T1_DIV_BY_1, T1_DIV_BY_2, T1_DIV_BY_4 y T1_DIV_BY_8.

Si el timer se configuro con reloj interno a 20MHz y el la prescala T1_DIV_BY_8, el


timer realizara incrementos cada 1.6 us y generando sobre flujo cada 104.8576 ms.

Ejemplo: setup_timer_1 ( T1_DISABLED ); // Deshabilita el timer 1.


setup_timer_1 ( T1_INTERNAL | T1_DIV_BY_4 ); // Tosc/4 y prescala/4.

set_timer1 ( )

Sintaxis: set_timer1 (value);

Función: Ajusta el registro contador del timer 1, la variable value es de 16 bits. Cuando el
contador del timer 1 llega a su cuenta máxima,da vuelta e inicia de nuevo con 0 y
continúa contando.

Ejemplo: // Con el reloj interno en 20 MHz, sin prescala, y configurado el timer 0 a 8 bits
// la cuenta es cada 200 ns (Tosc/4).

#use delay (clock = 20000000)

setup_timer_1(T1_INTERNAL); // Reloj interno.


set_timer1 (20000); // sobre flujo será cada 9.1072 ms = (65536 – 20000) * 200 ns.

get_timer1 ( )

Sintaxis: value = get_timer1 ( );

Función: Retorna el valor del registro contador del timer 1, la variable value debe ser de 8 o 16
bits.

Ejemplo: set_timer1 (150); //Ajusta el registro contador a 150.

while (get_timer1( ) < 500); // Mientras el registro contador sea menos a 500.

#int_timer1

Función: Esta directiva marca el inicio del vector de la interrupción por sobre flujo del timer 1. El
sobre flujo acurre cuando el registro contador del timer 1, llega a su valor máximo de
conteo y regresa a cero.

Ejemplo: #use delay (clock = 20000000)

void main ( ){

enable_interrupts(INT_TIMER1); // Se habilita la interrupción timer1.


85

enable_interrupts(GLOBAL); // Se habilitan las interrupciones globales.

setup_timer_1 (T1_INTERNAL | T1_DIV_BY_8); // Tosc/4 y prescala/8.


set_timer1 ( 15536 ); // Sobre flujo cada 80 ms
output_drive(PIN_E0); // RE0 como salidas.

for ( ; ; ){

}
}

#int_timer1
void TIMER1_OVF(void){ // Se genera una señal de 6.25 Hz.
set_timer1 ( 15536 );
output_toggle(PIN_E0);
}
86

9.2.2 Practica E9.2. Un Reloj muy exacto con el TIMER1.

En esta práctica desarrollaremos el código para crear un reloj muy preciso utilizando el timer 1 con reloj externo
tipo cristal XTAL, tendremos tres botones para ajustar los minutos, las horas y am o pm, desplegaremos la hora
en la LCD. El circuito que se armara es el siguiente:

Código Fuente de la Práctica E9.2.

/* Programa para crear un reloj muy preciso con el timer1


con reloj externo tipo XTAL y la LCD conectada en el
puerto D. Las interrupciones INT0, INT1 y INT2 servirán
para incrementar AM/PM, los minutos y las horas.

Realizo: Julio Cesar Gallo Sánchez */

/*CONFIGURACION PIC*/
#include "18f4550.h"
#use delay(clock=20000000)
#build(reset=0x02000,interrupt=0x02008)
#org 0x0000,0x1FFF {}

#define LCD_ENABLE_PIN PIN_B5 // Definición del RB5 para el E del LCD


#define LCD_RW_PIN PIN_B6 // Definición del RB6 para el R/W del LCD
#define LCD_RS_PIN PIN_B7 // Definición del RB7 para el RS del LCD
#define LCD_DATA4 PIN_D4 // Definición del RD4 para el DB4 del LCD
#define LCD_DATA5 PIN_D5 // Definición del RD5 para el DB5 del LCD
#define LCD_DATA6 PIN_D6 // Definición del RD6 para el DB6 del LCD
#define LCD_DATA7 PIN_D7 // Definición del RD7 para el DB7 del LCD
#include <lcd.c> // Se incluye al proyecto la librería lcd.c
87

char segundos, minutos, horas, dato;


short apm;
char ampm[2];

/* Programa principal*/

void main(void) {
enable_interrupts(GLOBAL); // Habilita interrupciones globales.
enable_interrupts(INT_TIMER1); // Habilita la interrupción del Timer1.
setup_timer_1(T1_EXTERNAL | T1_DIV_BY_1); //XTAL externo , prescala/1.
set_timer1(32768); // Sobre flujo cada 1 s.

enable_interrupts(INT_EXT2); // Se activa el uso de la INT2.


enable_interrupts(INT_EXT1); // Se activa el uso de la INT1.
enable_interrupts(INT_EXT); // Se activa el uso de la INT0.
ext_int_edge(2,H_TO_L); // Se elige que las interrupciones externas
ext_int_edge(1,H_TO_L); // se active por flanco descendente.
ext_int_edge(0,H_TO_L);

output_float(PIN_B0); // Se configuras los RB0 al RB2 como entradas.


output_float(PIN_B1);
output_float(PIN_B2);
lcd_init(); // Se inicializa la LCD.

dato = 0; // Candado para Imprimir datos cada segundo


apm = 0; // hora am = 0 y pm = 1;
segundos = 0;
minutos = 0;
horas = 12;

while(true){
if(dato == 1){ // Se imprime la hora cada segundo.
printf(lcd_putc, "\f Son las: \n");
if(apm == 0)
ampm = "AM";
else
ampm = "PM";
printf(lcd_putc, " %2u:%2u:%2u %2s ", horas, minutos, segundos, ampm);
}
}
}

#int_timer1 // Inicio del vector interrupción del timer1.


void timer0_ovf() { // Interrupción por sobre flujo.
set_timer0(32768); // Sobre flujo cada 1 s.
dato = 1;

if(segundos == 59){ // se incrementan los segundos, los minutos


segundos = 0; // y las horas.
if(minutos == 59){
minutos = 0;
if(horas == 12){
horas = 1;
apm = ~apm;
}
else
horas++;
88

}
else
minutos++;
}
else
segundos++;

#int_ext // Cambia entre AM / PM.


void ext_isr0() {

apm = ~apm;

if(~input(PIN_B0))
delay_us(500);
}

#int_ext1 // Incrementa los minutos hasta 59 y regresa a 0.


void ext_isr1() {
if(minutos == 59)
minutos = 0;
else
minutos++;
if(~input(PIN_B1))
delay_us(500);
}

#int_ext2 // incrementa las horas 12 y regresa a 1.


void ext_isr2() {
if(horas == 12)
horas = 1;
else
horas++;
if(~input(PIN_B2))
delay_us(500);
}

9.3 El Timer 2.

El timer 2, tiene las siguientes características:

 Temporizador de 8 bits.
 Prescala programables con 3 opciones.
 Postscala programable con 16 opciones.
 Cuenta con el registro de comparación.
 Interrupción cuando el registro de comparación es igual al del timer.
 Se puede utilizar junto con los módulos CCP y ECCP.
 Se puede utilizar como señal de reloj del modulo MSSP en modo SPI.

En la figura se puede observar, que este timer tiene como reloj interno Fosc/4 y se puede dividir la frecuencia
del reloj entre 1, 4 y 16. Cuando el registro del timer TMR2 es igual al registro de comparación PR2 se activa la
bandera y se genera la interrupción del timer 2 si está habilitada.
89

9.3.1 Funciones de C para configurar y operar el timer 2.

El compilador CCS cuenta con un grupo de funciones para configurar y controlar la operación del timer 1, estas
funciones son:

setup_timer_2 ( ) set_timer2( )
get_timer2( ) #int_timer2

Recuerde que el timer 2, cuenta con la interrupción que ocurre cuando TMR2 = PR2 y las funciones para
configurar las interrupciones se vieron en el Capitulo 6. Las Interrupciones Externas.

setup_timer_2 ( )

Sintaxis: setup_timer_2 (mode, period, postscale);

Función: Configura el timer 2, la constante mode pueden ser uno o dos definiciones contenidas
en el archivo *.h del dispositivo, algunas de estas definiciones son T2_DISABLED,
T2_DIV_BY_1, T2_DIV_BY_4 y T2_DIV_BY_16. La variable period es de 8 bits y
puede ser desde 1 hasta 255 y determina el periodo,y la variable postscale es un
número entre 1 y 16 que determina el número de sobre flujos antes de ejecutar la
interrupción.

Ejemplo: setup_timer_2 ( T2_DIV_BY_4, 0xc0, 2); // Con 20MHz, los incrementos del timer
// son cada 800 ns, con sobre flujos
// cada 153.6 us y la interrupción
// ocurrirá a los 307.2 us.
set_timer2 ( )

Sintaxis: set_timer2 (value);

Función: Ajusta el registro contador del timer 2, la variable value es de 8 bits. Cuando el contador
del timer 2, llega a su cuenta máxima, da vuelta e inicia de nuevo con 0 y continúa
contando.

Ejemplo: // Con el reloj interno en 20 MHz, sin prescala, y configurado el timer 0 a 8 bits
// la cuenta es cada 200 ns (Tosc/4).

#use delay (clock = 20000000)

setup_timer_2(T2_DIV_BY_1, 150, 4); // Fosc/4, periodo = 30 ms y 4 postscala.


set_timer2 (0); // sobre flujo será cada 12 ms = 30 ms * 4.
90

get_timer2 ( )

Sintaxis: value = get_timer2 ( );

Función: Retorna el valor del registro contador del timer 2, la variable value debe ser de 8 bits.

Ejemplo: set_timer2 (50); //Ajusta el registro contador a 50.

while (get_timer1( ) < 500); // Mientras el registro contador sea menos a 150.

#int_timer2

Función: Esta directiva marca el inicio del vector de la interrupción del timer 2. La interrupción del
timer 2, ocurre cuando el registro contador TMR2 = al registro del periodo PR2, el
número de veces que la postscala marca. Cuando el registro contador del timer 2, llega
a su valor máximo de conteo y regresa a cero y continua contando.

Ejemplo: #use delay (clock = 20000000)

void main ( ){

enable_interrupts(INT_TIMER2); // Se habilita la interrupción timer1.


enable_interrupts(GLOBAL); // Se habilitan las interrupciones globales.

setup_timer_2 (T2_DIV_BY_1, 100, 2); // Tosc/4, periodo = 20 ms y postscala 1.


set_timer2 ( 0 ); // Sobre flujo cada 40 ms
output_drive(PIN_E0); // RE0 como salidas.

for ( ; ; ){

}
}

#int_timer2
void TIMER2_EQ(void){ // Se genera una señal de 25 Hz.
output_toggle(PIN_E0);
}
91

9.3.2 Practica E9.3. Generación de una señal de 1kHz con el Timer2.

En esta práctica se desarrollara el código para generar una señal de 1kHz utilizando timer
el 2, el pin que
utilizaremos como salida es el RE0. El circuito que se armara es el siguiente:

Código Fuente de la Práctica E9.3.

/* Programa para generar una señal de 1 kHz con el timer2


mediante la terminal RE0.

Realizo: Julio Cesar Gallo Sánchez */

/*CONFIGURACION PIC*/

#include "18f4550.h"

#use delay(clock=8000000)
#build(reset=0x02000,interrupt=0x02008)
#org 0x0000,0x1FFF {}

/* Programa principal*/

void main(void)
{
output_drive(PIN_E0); // RE0 como salida
enable_interrupts(GLOBAL); // Habilita interrupciones globales.
enable_interrupts(INT_TIMER2); // Habilita la interrupción del Timer2.
setup_timer_2(T2_DIV_BY_1, 250, 4); // (Tosc*4)*4 , periodo = 500 us.
set_timer2(0);

while(true){

}
}

#int_timer2 // Inicio del vector interrupción del timer2.


void timer2_eq() { // Interrupción por sobre flujo.
output_toggle(PIN_E0); // Frecuencia de 1 kHz.
}
92

9.4 El Timer 3.

El timer 3, tiene las siguientes características:

 Configurable como timer / contador de 16 bits.


 Fuente del reloj puede ser externa o interna.
 Dispone de varias opciones de señal de reloj en el modo temporizador:
o Oscilador con o sin presacala.
o Oscilador del timer 1 con o sin prescala.
 Prescala programables de 4 estados.
 Interrupción por sobre flujo.
 El registro de conteo se puede leer y escribir por software.

En la figura, se apreciar que el timer 3, puede elegir entre oscilador externo (con los pines RC0/T1OSO y
RC1/T1OSI que sirven para colocar un cristal XTAL) e interno trabajando a Fosc/4. Tiene un selector de
prescala para dividir la frecuencia de la fuente del reloj entre 1, 2, 4 y 8 lo que nos permite hacer más lenta la
cuenta del timer. Este timer utiliza los registros TMR3H y TMR3L para realizar el conteo, si el registro contador
llega al número máximo, se reinicia en 0 y continua contando. Cada vez que el registro contador llega al número
máximo y se reinicia, se activa una bandera de sobre flujo lo que puede activa la interrupción de sobre flujo del
timer 3, si es que esta se habilito.

9.4.1 Funciones de C para configurar y operar el timer 3.

El compilador CCS cuenta con un grupo de funciones para configurar y controlar la operación del timer 2, estas
funciones son:

setup_timer_3 ( ) set_timer3 ( )
get_timer3 ( ) #int_timer3
93

Recuerde que el timer 3, cuenta con la interrupción por sobre flujo y las funciones para configurar las
interrupciones se vieron en el Capitulo 6. Las Interrupciones Externas.
setup_timer_3 ( )

Sintaxis: setup_timer_3 (mode);

Función: Configura el timer 3, la constante mode pueden ser uno o dos definiciones contenidas
en el archivo *.h del dispositivo, algunas de estas definiciones son T3_DISABLED,
T3_INTERNAL, T3_EXTERNAL y T3_EXTERNAL_SYNC, T3_CLK_OUT,
T3_DIV_BY_1, T3_DIV_BY_2, T3_DIV_BY_4 y T3_DIV_BY_8.

Si el timer se configuro con reloj interno a 20MHz y el la prescala T3_DIV_BY_8, el


timer realizara incrementos cada 1.6 us y generando sobre flujo cada 104.8576 ms.

Ejemplo: setup_timer_3 ( T3_DISABLED ); // Deshabilita el timer 3.


setup_timer_3 ( T3_INTERNAL | T3_DIV_BY_4 ); // Tosc/4 y prescala/4.

set_timer3 ( )

Sintaxis: set_timer3 (value);

Función: Ajusta el registro contador del timer 3, la variable value es de 16 bits. Cuando el
contador del timer 3 llega a su cuenta máxima, da vuelta e inicia de nuevo con 0 y
continúa contando.

Ejemplo: // Con el reloj interno en 20 MHz, sin prescala, y configurado el timer 0 a 8 bits
// la cuenta es cada 200 ns (Tosc/4).

#use delay (clock = 20000000)

setup_timer_3(T3_INTERNAL); // Reloj interno.


set_timer3 (20000); // sobre flujo será cada 9.1072 ms = (65536 – 20000) * 200 ns.

get_timer3 ( )

Sintaxis: value = get_timer3 ( );

Función: Retorna el valor del registro contador del timer 3, la variable value debe ser de 8 o 16
bits.

Ejemplo: set_timer3 (150); //Ajusta el registro contador a 150.

while (get_timer3( ) < 500); // Mientras el registro contador sea menos a 500.

#int_timer3

Función: Esta directiva marca el inicio del vector de la interrupción por sobre flujo del timer 3. El
sobre flujo acurre cuando el registro contador del timer 3, llega a su valor máximo de
conteo y regresa a cero.

Ejemplo: #use delay (clock = 20000000)

void main ( ){
94

enable_interrupts(INT_TIMER3); // Se habilita la interrupción timer1.


enable_interrupts(GLOBAL); // Se habilitan las interrupciones globales.

setup_timer_3 (T3_INTERNAL | T3_DIV_BY_8); // Tosc/4 y prescala/8.


set_timer3 ( 15536 ); // Sobre flujo cada 80 ms
output_drive(PIN_E0); // RE0 como salidas.

for ( ; ; ){

}
}

#int_timer3
void TIMER1_OVF(void){ // Se genera una señal de 6.25 Hz.
set_timer3 ( 15536 );
output_toggle(PIN_E0);
}

El timer 1 y el timer 3 son totalmente idénticos y comparten las terminales RC0/T1OSO y RC1/T1OSI que nos
sirven para conectar una fuente de reloj externo y como contador de pulsos.
95

10. Los Módulos CCPx de Captura, Comparación y PWM.


El microcontrolador PIC18F4550 cuenta con dos módulos de comparación, captura y un PWM, los cuales
cuentan con tres modos de funcionamiento:

 Modo captura: se utiliza para medir eventos externos, como la duración de pulsos digitales.
 Modo de comparación: se utiliza para generar señales digitales con temporizadores programables.
 Modo PWM: se utiliza para generar señales de modulación de ancho de pulso.

10.1 Funciones de C para configurar y operar los módulos Comparadores/Capturadores/PWM.

El compilador CCS cuenta con un grupo de funciones que nos sirven para configura y controlar los módulos
Comparadores, capturadores y PWM.

setup_ccp1 ( ) setup_ccp2 ( ) set_pwm1_duty ( ) set_pwm2_duty ( )

setup_ccp1 ( )
setup_ccp2 ( )

Sintaxis: setup_ccp1 (mode); o setup_ccp1 (mode, pwm);


setup_ccp2 (mode); o setup_ccp2 (mode, pwm);

Función: Configura e inicializa la CCP (Comparador/Capturador/PWM). Los módulos CCP


cuentan con tres modos de operación. El modo captura, copia el registro de conteo del
timer 1 (por default) o timer 3 en el registro del CCPx (x = 1 o 2) cuando un evento
ocurre en el pin de entrada (RC1/CCP1 o RC2/CCP2). En el modo comparador provoca
una acción cuando el registro de conteo del timer 1 (por default)o timer3 es igual al
registro del CCPx (x = 1 o 2). En el modo PWM, genera una señal cuadrada, con ajuste
del tiempo en alto mediante el timer 2.

La constante mode, puede ser los siguientes valores:

Deshabilita el CCP:
CCP_OFF Deshabilita el modulo CCP
T3_CCP1_TO_2 Elije el timer 3 para los módulos CCP1 y CCP2
T3_CCP2 Elije el timer 3 solo para CCP2

Ajustan el modo captura:


CCP_CAPTURE_FE Capturador flanco descendente.
CCP_CAPTURE_RE Capturador flanco ascendente.
CCP_CAPTURE_DIV_4 Capturador cada 4 flancos de subida.
CCP_CAPTURE_DIV_16 Capturador cada 16 flancos de subida.

Ajustan el modo comparador:


CCP_COMPARE_SET_ON_MATCH Salida en alto en la comparación.
CCP_COMPARE_CLR_ON_MATCH Salida en bajo en la comparación.
CCP_COMPARE_INT Interrupcion en la comparacion.
CCP_COMPARE_RESET_TIMER Resetea el timer en la comparacion.

Ajustan el modo PWM:


CCP_PWM Habilita el PWM.

La constante pwm, se utiliza para el modulo ECCP (Modulo CCP mejorado):


CCP_PWM_H_H
CCP_PWM_H_L
CCP_PWM_L_H
96

CCP_PWM_L_L
CCP_PWM_FULL_BRIDGE
CCP_PWM_FULL_BRIDGE_REV
CCP_PWM_HALF_BRIDGE

CCP_SHUTDOWN_ON_COMP1
CCP_SHUTDOWN_ON_COMP2
CCP_SHUTDOWN_ON_COMP
CCP_SHUTDOWN_ON_INT0
CCP_SHUTDOWN_ON_COMP1_INT0
CCP_SHUTDOWN_ON_COMP2_INT0
CCP_SHUTDOWN_ON_COMP_INT0

CCP_SHUTDOWN_AC_L
CCP_SHUTDOWN_AC_H
CCP_SHUTDOWN_AC_F
CCP_SHUTDOWN_BD_L
CCP_SHUTDOWN_BD_H
CCP_SHUTDOWN_BD_F
CCP_SHUTDOWN_RESTART
CCP_DELAY

Ejemplo: setup_ccp1(CCP_CAPTURE_RE ); // Se configura el CCP1 como capturador de


// Flanco ascendente con el timer1.
setup_ccp2(CCP_CAPTURE_RE | T3_CCP2) // Se configura el CCp2 como
// Capturador de flanco ascendente con el timer 3

set_pwm1_duty ( )
set_pwm2_duty ( )

Sintaxis: set_pwm1_duty (value);


set_pwm2_duty (value);

Función: Escribe un valor de 10 bits en el PWM para ajustar el ciclo de trabajo. Un valor de 8 bits
puede ser usado, si los bits más significativos no son requeridos. El valor value puede
ser una constante o variable de 8 o 16 bits. La ecuación para calcular el ciclo el trabajo
para 10 bits es:

duty cycle = value / [ 4 * (PR2 +1 ) ]

Donde PR2 es el máximo valor del registro de comparación del time 2.

Ejemplo: // Clock 4 MHz

int value = 112;


setup_timer_2(T2_DIV_BY_1, 224, 1); // Tpwm = 225 us.
setup_ccp1(CCP_PWM); // Se elije el PWM en el CCP1.
set_pwm1_duty(value); // Ciclo de trabajo 50%.

10.2 El Modulo CCPx en Modo de Captura.

En el modo de captura, el valor del timer 1 o del timer 3, pasa al registro CCP_x (x = 1 o 2) del modulo CCP1 o
CCP2, cuando se produce alguno de los siguientes eventos:

 En cada flanco de Bajada (CCP_CAPTURE_FE).


 En cada flanco de subida (CCP_CAPTURE_RE).
97

 En cada 4 flancos de subida (CCP_CAPTURE_DIV_4).


 En cada 16 flancos de subida (CCP_CAPTURE_DIV_16).

La manera de elegir el canal CCPx que deseamos y el timer asociado a este es la siguiente:

 Para elegir el modulo CCP1 con el timer 1:


setup_ccp1(CCP_CAPTURE_RE); // Capturador de flanco de subida.
 Para elegir el modulo CCP1 con el timer 3:
setup_ccp1(CCP_CAPTURE_FE | T3_CCP1_TO_2); // Capturador de flanco de bajada.
 Para elegir el modulo CCP2 con el timer 1:
setup_ccp2(CCP_CAPTURE_FE); // Capturador de flanco de bajada.
 Para elegir el modulo CCP2 con el timer 3:
setup_ccp2(CCP_CAPTURE_RE | T3_CCP2); // Capturador de flanco de subida.

El modulo CCPx en el modo capturador tiene la capacidad de generar interrupción interna, cada vez que se
detecte un evento en las terminales RC1/CCP1 y RC2/CCP2, y las directivas asociadas son #int_ccp1 y
#int_ccp2.

Las instrucciones para configurar las interrupciones, se vieron en la sección 5, Interrupciones Externas.
98

10.2.1 Practica E10.1. Medición del Ancho de Pulso con el CCP1.

En este programa desarrollaremos el código para realizar la medición del ancho de pulso de una señal
cuadrada, mediante el modulo CCP2 en modo captura. La lectura en microsegundos se desplegara en la LCD.
El circuito que se debe armar es:

Código Fuente de la Práctica E10.1.

/* Programa medir el ancho del pulso de una señal


cuadrada con el modulo CCP2. La lectura del
ancho se desplegara en la LCD.

Realizo: Julio Cesar Gallo Sánchez */

/*CONFIGURACION PIC*/

#include "18f4550.h"

#use delay(clock=20000000)
#build(reset=0x02000,interrupt=0x02008)
#org 0x0000,0x1FFF {}

#define LCD_ENABLE_PIN PIN_B5


#define LCD_RW_PIN PIN_B6
#define LCD_RS_PIN PIN_B7
#define LCD_DATA4 PIN_D4
#define LCD_DATA5 PIN_D5
#define LCD_DATA6 PIN_D6
#define LCD_DATA7 PIN_D7
#include <lcd.c>

/* Declaración de variables*/

short npul = 0; //Entra otro pulso


long TFB = 0,TFS = 0,TF =0; //Tiempo flancos
99

float AP = 0.0; //Valor final del ancho de pulso


short flanco = 0; //Cambio de flanco de disparo

/* Programa principal*/

void main(void) {

lcd_init();
setup_timer_1(T1_INTERNAL | T1_DIV_BY_8); //Configuración TMR1
setup_ccp2(CCP_CAPTURE_RE); //Configuración modo Captura en flanco de subida
flanco = 0; //Control de cambio a 0

enable_interrupts(int_ccp2); //Habilitación interrupción modulo CCP


enable_interrupts(global); //Habilitación interrupción global

while(true) {
if(npul == 1){ //¿Pulso nuevo?
TF = TFB - TFS; //Ancho de pulso.
AP = TF * 1.6; //Ancho de pulso en microsegundos (a 20MHz:1.6us)
printf(lcd_putc,"\nTon = %6.1f us ", AP);
npul = 0; //Pulso ya medido, espera nuevo
}
}
}

#int_ccp2
void ccp2_RFE( ) { //Función interrupción
if(flanco == 0){ //Flanco de subida
TFS = CCP_2; //Carga del valor del registro CCPR1 en flanco subida
setup_ccp2(CCP_CAPTURE_FE); //Configuración modo Captura en flanco de bajada
flanco = 1; //Control de cambio de flanco
}
else { //Flanco de Bajada
TFB = CCP_2; //Carga del valor del registro CCPR1 en flanco bajada
setup_ccp2(CCP_CAPTURE_RE); //Configuración modo Captura en flanco de subida
flanco = 0; //Control de cambio de flanco

if(npul == 0) //Fin de pulso...


npul = 1; //pulso a medir
}
}

10.3 El Modulo CCPx en Modo de Comparador.

En el modo comparación, el registro CCP_x (x = 0 o 1) se compara continuamente con el temporizador


asociado (timer 1 o 3). Existen varios modos de comparación:

 El pin de salida RC1/CCP2 o RC2/CCP1 se inicializa a 0. Cada vez que se produce la igualdad entre el
registro CCP_x y el temporizador asociado se complementa el pin de salida y se pone a 1 la bandera
del módulo CCP. (CCP_COMPARE_INT_AND_TOGGLE).
 La salida RC1/CCP2 o RC2/CCP1 se inicializa a 0. Cuando se produce la igualdad entre el registro
CCP_x y el temporizador asociado se pone a 1 el pin de salida y se pone a 1 la bandera del modulo
CCP. (CCP_COMPARE_SET_ON_MATCH).
100

 El pin de salida RC1/CCP2 o RC2/CCP1 se inicializa a 1. Cada vez que se produce la igualdad entre el
registro CCP_x y el temporizador asociado se pone a 0 el pin de salida y se pone a 1 la bandera del
módulo CCP. (CCP_COMPARE_CLR_ON_MATCH).
 El pin de salida RC1/CCP2 o RC2/CCP1 toma el valor del latch correspondiente. Cuando se produce la
igualdad el registro CCP_x y el temporizador asociado se pone a 1 la bandera del modulo CCP.
(CCP_COMPARE_INT).
 El pin de salida RC1/CCP2 o RC2/CCP1 toma el valor del latch correspondiente. Cuando se produce la
igualdad el registro CCP_x y el temporizador asociado se inicializa a cero el timer asociado, se inicia
una conversión del A/D y se pone a 1 la bandera del modulo CCP.
(CCP_COMPARE__RESET_TIMER).
101

10.3.1 Practica E10.2. Generación de una señal de 2 Khz con el CCP2.

En esta práctica se desarrollara el código para generar una señal de 2 kHz, mediante el modulo CCP2, con el
los modos Comparación, RC2 inicia en 0, coincidencia puesta en 1 (CCP_COMPARE_SET_ON_MATCH) y el
modo Comparación, RC2 inicia en 1, coincidencia puesta en 0 (CCP_COMPARE_CLR_ON_MATCH). El
circuito que se debe armar es:

Código Fuente de la Práctica E10.2.

/* Programa para generar una señal cuadrada de 2 kHz


con el modulo CCP2 con el modo Comparador.

Realizo: Julio Cesar Gallo Sánchez */

/*CONFIGURACION PIC*/

#include "18f4550.h"

#use delay(clock=20000000)
#build(reset=0x02000,interrupt=0x02008)
#org 0x0000,0x1FFF {}

/* Declaración de Variables*/

short cambio = 0;

/* Programa principal*/

void main(void) {

disable_interrupts(global);
setup_timer_1(T1_INTERNAL | T1_DIV_BY_1); // Configuración TMR1
setup_ccp2(CCP_COMPARE_SET_ON_MATCH); // Configuración inicial modulo CCP
CCP_2 = 1250; // Inicialización del registro CCPR2 para un Duty del 50%

enable_interrupts(int_ccp2); // Habilitación interrupción modulo CCP2


enable_interrupts(global); // Habilitación interrupción general

while(true) {
}
102

#int_ccp2
void ccp2_int(){ // Función de interrupción

if(++cambio == 1){
setup_ccp2(CCP_COMPARE_CLR_ON_MATCH); // Modo Comparación, cambio a 0
}
else{
setup_ccp2(CCP_COMPARE_SET_ON_MATCH); // Modo Comparación, cambio a 1
cambio = 0;
}

set_timer1(0); // Borrado de TMR1


CCP_2 = 1250; // Inicialización del registro CCPR2 para un Duty del 50%
}

10.4 El Modulo CCPx en Modo de PWM.

El modo PWM permite generar una señal PWM con ciclo de trabajo y frecuencia programable. El
funcionamiento de este modulo es el siguiente:

 Un registro de 10 bits se compara constantemente con un contador de 10 bits (compuesto por el


registro del timer 2 (TMR2) y una prescala de 2 bits).
 Cuando el valor del contador se hace igual al del registro se pone a 0 el pin de salida del modulo CCP.
 En paralelo el registro de comparación (PR2) del timer 2, se comparar constantemente con el registro
del timer 2, cuando el valor de TMR2 alcanza PR2:
o Se pone a 1 el pin de salida del modulo CCP.
o Se inicia a 0 el valor del contador de 10 bits (registro TMR2 y 2 bits de prescala).
o El valor del registro de 10 bits, se recargan con el valor del registro CCP_x.
103

Para generar la señal PWM debe seguir los siguientes pasos:

Configurar el pin RC1/CCP2 o RC2/CCP1 como salida.


Configurar el timer 2 para que trabaje como temporizador, se puede utilizar la prescala pero la postcala no tiene
uso en el modo PWM.
Configurar el modulo CCP en modo PWM.
Establecer el periodo de la señal PWM mediante el valor del registro de comparación PR2 del timer 2. El
periodo de la señal será:

( 2 + 1) ∗ 4 ∗ ( 2)
( ) =

Establecer el ciclo de trabajo de la señal PWM mediante la función ccp_pwmx_duty( valor ) donde x = 1 o 2. El
ciclo de trabajo de la señal será:

( )∗( 2)
=

104

10.4.1. Practica E10.3.Generación de señal PWM proporcional a la señal medida del ADC.

En esta práctica desarrollaremos el código para generar una señal PWM con periodo de 720 us, mediante el
modulo CCP1, el ciclo de trabajo será proporcional a la señal mediada por el convertidor A/D con el canal 0. el
circuito que se armara es el siguiente:

Código Fuente de la Práctica E10.3.

/* Programa para generar una señal PWM con el modulo CCP1


proporcional a la señal del convertidor A/D canal 0.

Realizo: Julio Cesar Gallo Sánchez */

/*CONFIGURACION PIC*/

#include "18f4550.h"
#device adc=10
#use delay(clock=20000000)
#build(reset=0x02000,interrupt=0x02008)
#org 0x0000,0x1FFF {}

/* Declaración de Variables*/

long valor = 0;

/* Programa principal*/

void main(void) {

disable_interrupts(global);
setup_adc_ports(AN0); // Habilitación RA0 analógico
setup_adc(ADC_CLOCK_INTERNAL); // Reloj interno RC
105

set_adc_channel(0); // Canal 0

setup_timer_2(T2_DIV_BY_1,224,1); // PR2=224, Tpwm=720us


setup_ccp1(CCP_PWM); // CCP1 en modo PWM
setup_ccp2(CCP_COMPARE_RESET_TIMER); // CCP2 modo COMPARACION…
// y disparo especial
setup_timer_1(T1_INTERNAL | T1_DIV_BY_4); // Configuración TMR1
set_timer1(0); // Puesta a 0
ccp_2 = 1250; // Muestreo cada 1ms a 20MHz
enable_interrupts(INT_AD); // Habilitación Interrupción AD
enable_interrupts(global); // Habilitación Interrupción global
read_adc(ADC_START_ONLY); // Inicia la conversión del ADC.

while(true) {
}
}

#int_ad
void adc_int(){ // Función interrupción AD

valor = read_adc(); // Valor de fuente analógica...


set_pwm1_duty(valor); // a Duty de PWM
}

También podría gustarte