prog pic c18

53
PROGRAMACIÓN EN C18DEL PIC18F4550 CON BOOTLOADER Por Francisco Javier García Olmos Instituto Politécnico Nacional México 2012 NOTA: Para utilizar este manual teórico-práctico es necesario tener conocimientos básicos de electrónica y circuitos, lógica digital y nociones básicas de programación de computadoras en cualquier lenguaje. Es necesario también tener en cuenta la lectura del manual (data sheet) para manejar los registros necesarios para la configuración de este microcontrolador. 1

Upload: francisco-javier-garcia-olmos

Post on 08-Feb-2016

90 views

Category:

Documents


7 download

TRANSCRIPT

Page 1: Prog Pic c18

PROGRAMACIÓN EN C18DEL PIC18F4550 CON BOOTLOADER

Por Francisco Javier García Olmos

Instituto Politécnico Nacional

México 2012

NOTA: Para utilizar este manual teórico-práctico es necesario tener conocimientos básicos de electrónica y circuitos, lógica digital y nociones básicas de programación de computadoras en cualquier lenguaje. Es necesario también tener en cuenta la lectura del manual (data sheet) para manejar los registros necesarios para la configuración de este microcontrolador.

1

Page 2: Prog Pic c18

Dedicado a mis amigos programadores:

Francisco y Eduardo.

Hay más personas pero el mencionar a todas ellas requeriría un documento extra para mencionarlas, ya que son muchas. Gracias Por Su Apoyo SNAR.

2

Page 3: Prog Pic c18

INTRODUCCIÓN A MPLAB X

Para iniciar con la programación en C18 primero necesitamos saber acerca del IDE gratuito del cual Microchip nos provee; Microchip es la empresa que fabrica los microcontroladores PIC. Este IDE es el que usaremos para realizar nuestros proyectos, probarlos y compilarlos, ya que es multiplataforma y podemos utilizarlo tanto en Windows como en Linux.

Ambiente de desarrollo MPLAB X

El ambiente de desarrollo tiene la siguiente imagen; tome en cuenta que está mos-trado dentro del Sistema operativo Linux.

Del lado izquierdo aparece una lista con los proyectos realizados, archivos y/o clases que se utilizan en MPLAB. Siempre se abrirá con la página de inicio. Tiene su barra de menú, barra de herramientas y en la parte de abajo, oculta ventanas que son necesarias a la hora de compilar, probar y analizar la memoria. Para mayor información, verificar un curso com-pleto de MPLAB, ya que aquí solo hablaremos de las partes más importantes.

3

Page 4: Prog Pic c18

Crear un proyecto para C18

Para crear un proyecto, es necesario dar clic en el segundo icono de la barra de he-rramientas o seleccionar New Proyect del menú File y se abrirá una ventana donde pregunta el tipo de proyecto que deseamos crear; seleccionamos Standaalone Proyect y presionamos Siguiente; nos aparecerá una lista donde tenemos que seleccionar la familia y el dispositivo a usar, en nuestro caso colocamos Advanced 8-bit MCUs (PIC18) en familia y en dispositivo PIC18F4550, posteriormente seleccionamos siguiente. Nos aparecerá una lista de programa-dores a usar; nosotros colocaremos Simulator por que usaremos Bootloader y no es nece-sario un programador mas que solo una vez para ingresarle el Bootloader al PIC. Una vez presionado el botón siguiente (Next) aparecerá una lista con lenguajes de programación, de los cuales vamos a seleccionar C18, ya que usaremos el lenguaje C para PIC18.

Por último, le damos un nombre al proyecto y lo guardamos en el directorio que nos agrade.

4

Page 5: Prog Pic c18

Al realizar los pasos anteriores, se mostrará nuestro proyecto en la parte izquierda del IDE. Ahora es necesario crear un archivo nuevo vació para comenzar a programar.

Para agregar el nuevo archivo hacemos clic derecho en Source Files del proyecto que estamos creando, y le hacemos clic en New y posteriormente en Empty File y se abrirá una ventana donde le colocaremos un nombre. En nuestro caso le llamaremos plantilla.c por que será nuestra plantilla para cualquier proyecto que realicemos con bootloader.

Debemos de agregar también un archivo linker para poder compilar correctamente el programa. Este archivo está dentro de la carpeta bin\lkr en la carpeta donde está instalado C18 (MCC18) el cual es llamado 18f4550_g.lkr. Sin este archivo no se compilará correc-tamente el programa y podemos borrar accidentalmente el bootloader del microcontrolador.

Compilar un programa

Para compilar un programa hecho previamente, solo le damos clic al botón que tiene un martillo, se compilará el proyecto y se creará el archivo ejecutable .HEX que es el que se

5

Page 6: Prog Pic c18

grabará en el PIC con el HID BOOTLOADER.

En Windows este programa es gráfico y se habilitan los botones una vez que se ac-tiva el bootloader de la placa de prueba del PIC. Seleccionamos el archivo .HEX que deseamos grabar y pulsamos sobre el botón Program / Verify y se programará el PIC.

En Linux, hay un programa en línea de comandos que graba de forma satisfactoria el PIC con bootloader y se ejecuta anexando la dirección del archivo .HEX desde la Terminal. Tiene el nombre de hid_bootloader y solo se tiene que ingresar el nombre del archivo .HEX que se desea grabar.

El bootloader del PIC se activa pulsando el SWITCH 3 de la placa de prueba del PIC18F4550 junto con el botón RESET, posteriormente soltar RESET quedando solo el swithc 3 y parpadearán los LEDS RC0 y RC1 de la placa de prueba, en ese momento soltar el SWITCH 3. Una vez realizado esto, la computadora reconocerá al dispositivo como un Human Interface Device USB. En este momento se puede grabar el programa que hemos realizado con el bootloader.

6

Page 7: Prog Pic c18

El PIC18F4550

En este capítulo vamos a ver algunas características del PIC18F4550, que son esen-ciales para entender la programación en C18.

1. Es un dispositivo CMOS de bajo consumo de energía, uso de voltaje +5V y uso máximo de corriente de 25mA, no consumir más de esta corriente ya que si no, podríamos dañar el pin del microcontrolador. También tener en cuenta que los disposi-tivos CMOS son delicados a la corriente estática.

2. Es un microcontrolador que trae en su interior un microprocesador (MPU o ALU), memoria RAM y memoria de programa flash (regrabable) y memoria EEPROM de datos y sus propios bus de datos, control y direcciones internamente.

3. Integrado de 40 pines en su presentación DIP o 44 pines en su presentación para montaje superficial.

4. Es un microcontrolador de 8 bits en su bus de datos, osea que solo puede manejar 8 bits por cálculo.

5. Tiene 5 puertos de entrada y salida: A, B, C, D y E. Tres de ellos son de 8 bits, uno de 7 bits y uno de ellos es de 4 bits, mas sin embargo, estas características no son posibles por el uso de pines por oscilador externo (RA5 y RA6), uso de USB (RC4 y RC5) y por último uso de MCLR (Master Clear Reset) en el pin RE4. Esto nos deja con 1 puertos de 6 bits, dos puertos de 8 bits, uno de cinco y uno de 3 bits.

6. Oscilador interno de 8MHz. Nosotros usaremos el externo de 20MHz por el uso del módulo USB para el BOOTLOADER.

7. Tiene 13 canales analógicos (ADC) de 10 bits.

8. Módulo USB y posibilidad de usar BOOTLOADER.

9. Modulo USART para comunicación serial. Si se requiere comunicación PC-PIC se necesitará un integrado MAX232 para la conversión de voltajes.

10.Tres interrupciones externas.

11. Cuatro temporizadores (Timers) o contadores.

12.Dos modulos capturador/comparador/PWM (CCP).

13.Un módulo embebido capturador/comparador/PWM (ECCP).

14.Un módulo Master Synchronous Serial Port (MSSP).

7

Page 8: Prog Pic c18

Las conexiones básicas del PIC18F4550, les de estado y 2 botones de cambio de flanco (SW2 y SW3) para el inicio del bootloader son las siguientes:

Conexiones básicas:

Conexión a VDD y VSS no mostradas en el diagrama. VDD es de +5V y VSS es tierra GND.

1 push button para MCLR con sus respectivas resistencias de PULL-UP externas.

Oscilador de 20MHz con dos capacitores de 22pF conectados a tierra.

Conexiones extras de la placa de pruebas del PIC18F4550:

2 Leds en RA4 y RA5.

2 Leds en RC0 y RC1 para el estado del Bootloader.

2 push button para las interrupciones de cambio de flanco con sus respectivas resistencias de PULL-UP externas (SW2 y SW3).

8

Page 9: Prog Pic c18

LO BÁSICO EN C18

En este capítulo daré lo básico que tiene el lenguaje C18. No pienso dar un curso de C, ya que el lector deberá de comprender dicho lenguaje de programación, en caso contario, leer una referencia del lenguaje C, como son sus estructuras condicionales (if, else, while, switch, etc), sus palabras reservadas (main, void, break, case, etc) y sus tipos de datos (int, char, unsigned char, etc).

Una vez teniendo en cuenta esto comenzaré definiendo la palabra bootloader que es la que se refiere a este manual de programación.

El bootloader es un programa dentro del microcontrolador PIC que graba otro programa a partir de la dirección 0x1000 de la memoria de programa. Es como un sistema operativo (mas sin embargo no lo es) que se ejecuta cada que presionamos el botón SWITCH 3 después del RESET.

Las direcciones de inicio del PIC son:

0x0000 MCLR (Master Clear Reset)

0x0008 High Vector Sector

0x0018 Low Vector Sector

Pero como nosotros usamos Bootloader, las direcciones cambian.

0x1000 Remaped Reset Sector

0x1008 Remaped High Vector Sector

0x1018 Remaped Low Vector Sector

Posteriormente explicaré a que se refiere cada una de ellas en el capítulo de interrup-ciones, que abarcaremos más adelante.

Los tipos de numeración en C son: hexadecimal que se utiliza colocando 0xNN y el número en hexadecimal, binario que se utiliza colocando 0BNNNNNNNN y la numeración decimal, que se usa solo colocando el número.

Como vemos, lo que se refiere al bootloader, son las direcciones de inicio y posteriormente veremos que la frecuencia también cambia, así que esta programación tam-bién se puede utiliza sin bootloader con el inconveniente que necesitamos un programador como el PICKIT2 para grabar nuestros programas.

Ahora hablemos de un tema importante que son las instrucciones #pragma. Son instrucciones que se le dan al compilador como parte de código incluido en un programa en C. Especifican secciones de código, archivos de librería y algunas otras instrucciones especí-ficas de C.

9

Page 10: Prog Pic c18

La configuración inicial (la plantilla)

Para iniciar la configuración y antes que nada se debe de incluir el archivo p18f4550.h con la palabra reservada #include. Este archivo tiene algunas definiciones de re-gistros del pic y las direcciones de memoria.

Bits de configuración

Estos controlan el comportamiento del microcontrolador y se colocan con una instruc-ción #pragma config. Los tres básicos son:

#pragma config FOSC = HS ;Configura el oscilador a usar, los posibles osciladores son RC (oscilador RC), XT (cristal de baja frecuencia), HS (cristal de alta frecuencia). Hay más, para mayores referencias consultar el manual.

#pragma config WDT = OFF ;configura el perro guardián (este es un timer que rese-tea al pic en caso de que tenga un ciclo infinito) el valor que nosotros manejamos es OFF (apagado).

#pragma config LVP = OFF ;configura el bajo voltaje de programación, nosotros lo apagamos para que programe en alto voltaje.

Para el Bootloader (modo que nosotros usaremos) hay más bits, están ya colocados en la plantilla y no deberán de ser modificados. Para más información ver la ayuda de C18 sobre los bits de configuración.

Sectores de código

Los sectores de código son aquellos que le dicen al programador (PICKIT2, PICKIT3, etc) en que parte de la memoria de programa va a programar el código. Se colocan en una instrucción #pragma code y se le da un nombre, se coloca un igual y la dirección de memoria.

Ejemplo 1: Colocar el vector de reset en la localidad de memoria 0x0000.

#pragma code RESET_VECTOR_SECTOR = 0x0000

void reset_vector(void)

{

_asm goto _startup _endasm

}

En el ejemplo anterior vemos que exactamente después de la declaración va la función referente al vector la cual llamaremos reset_vector por comodidad, mas sin embargo, puede llevar cualquier nombre. En C18 está prohibido utilizar goto, por lo tanto, para realizar

10

Page 11: Prog Pic c18

un salto de función utilizamos la instrucción _asm y _endasm para realizar una ventana de código en ensamblador.

Ejemplo 2: Colocar el vector reset en la localidad mil hexadecimal.

#pragma code RESET_VECTOR_SECTOR = 0x1000

void reset_vector(void)

{

_asm goto _startup _endasm

}

Para usar bootloader el vector de reset se encuentra en 0x1000 ya que el demás espacio es usado por el propio bootloader previamente cargado.

Con lo explicado anteriormente y con los conocimientos básicos de C, nuestra plan-tilla queda de la siguiente manera:

/**************************************************************** * Autor: García Olmos Francisco Javier * * Fecha: 24 de Enero de 2012 * * Plantilla para usar con Bootloader en C18 * ***************************************************************/

#include <p18f4550.h>

/**************** Bits de Configuracion *************************************/ #pragma config PLLDIV = 5 #pragma config CPUDIV = OSC1_PLL2 #pragma config USBDIV = 2 #pragma config FOSC = HSPLL_HS #pragma config FCMEN = OFF #pragma config IESO = OFF #pragma config PWRT = OFF #pragma config BOR = ON #pragma config BORV = 3 #pragma config VREGEN = ON #pragma config WDT = OFF #pragma config WDTPS = 32768 #pragma config MCLRE = ON #pragma config LPT1OSC = OFF #pragma config PBADEN = OFF #pragma config CCP2MX = ON #pragma config STVREN = ON #pragma config LVP = OFF #pragma config ICPRT = OFF #pragma config XINST = OFF #pragma config CP0 = OFF #pragma config CP1 = OFF #pragma config CP2 = OFF #pragma config CP3 = OFF #pragma config CPB = OFF

11

Page 12: Prog Pic c18

#pragma config CPD = OFF #pragma config WRT0 = OFF #pragma config WRT1 = OFF #pragma config WRT2 = OFF #pragma config WRT3 = OFF #pragma config WRTB = ON #pragma config WRTC = OFF #pragma config WRTD = OFF #pragma config EBTR0 = OFF #pragma config EBTR1 = OFF #pragma config EBTR2 = OFF #pragma config EBTR3 = OFF #pragma config EBTRB = OFF

void ISRH(); void ISRL();

extern void _startup (void); #pragma code REMAPPED_RESET_VECTOR = 0x1000 void _reset (void) {

_asm goto _startup _endasm }

#pragma code HIGH_REMAPED_VECTOR_SECTOR = 0x1008 void high_remaped_vector() {

_asm goto ISRH _endasm

}

#pragma code LOW_REMAPED_VECTOR_SECTOR = 0x1018 void low_remaped_vector() {

_asm goto ISRL _endasm

} #pragma code

#pragma interrupt ISRH void ISRH() {

}

#pragma interruptlow ISRL void ISRL() {

}

void main() {

12

Page 13: Prog Pic c18

while(1) {

} }

Instrucciones básicas del lenguaje C18

El lenguaje C18 es una variante del C ANSI con la posibilidad de programar micro-controladores de la familia PIC18. No se puede programar ninguna otra familia de PIC.

Ventanas de ensamblador

Las ventanas de ensamblador se abren con la instrucción _asm y se cierran con _endasm. Entre estas líneas se puede colocar el código en ensamblador deseado.

Instrucción Nop

La instrucción Nop se utiliza para que el microcontrolador no realice operación alguna. Se utiliza para hacer retardos de tiempo equivalentes a cuatro ciclos de oscilador principal. Su sintaxis es:

Nop();

Uso de registros por bit

Para realizar el cambio de un solo bit de un registro, se utiliza la palabra bits después del registro y se coloca un punto y el nombre del bit. Funciona exactamente como una estruc-tura de datos. Los ejemplos los mostraré más adelante. Un ejemplo de su sintaxis es la siguiente:

REGISTRObits.NOMBREBIT=0;

El valor que se puede colocar frente a esta declaración solo puede ser 0 o 1 (valor binario de 1 bit).

Declarar Variables

Para declarar variables se puede hacer con variables globales dentro del programa o al principio de una función. El compilador marcará error si realizamos declaración de varia-bles cuando ya comenzamos alguna acción en la función.

13

Page 14: Prog Pic c18

PROGRAMACIÓN BÁSICA DEL PIC18F4550

En este apartado vamos a ver como se programa el PIC18F4550, pero antes voy a dar una descripción de los registros que vamos a utilizar en los primeros pasos.

Registros básicos del PIC18F4550

Los dos registros básicos del PIC18F4550 son TRIS y PORT.

Un registro es un mapeo en memoria RAM de algún dispositivo físico del PIC, como son los puertos de entrada o salida, temporizadores, módulo usb, modulo serial, modulo ADC, etc.

Registro TRIS

Los registros TRIS sirven para configurar puertos, lo explicaré más adelante. Hay cinco, que son el número de puertos que tiene el PIC18F4550, los cuales son TRISA, TRISB, TRISC, TRISD y TRISE. Todos son de 8 bits, excepto TRISE que es de 4 bits (nibble bajo) los demás no tienen efecto.

Registros PORT

Los registros PORT sirven para leer y escribir puertos, y también lo explicaré más a-delante. Hay cinco, que son el número de puertos que tiene el PIC18F4550, los cuales son PORTA, PORTB, PORTC, PORD y PORTE. Todos son de 8 bits excepto PORTE que es de 4 bits y se lee como cero el nibble alto del registro.

Configurar puertos

Antes de configurar los puertos es necesario saber que si usamos el oscilador exter-no (que es nuestro caso) el puerto A tendrá dos pines menos los cuales son RA6 y RA7. También están desactivados de forma predeterminada los pines RC4 y RC5 ya que utilizan el módulo USB y no está disponible RC3; esto nos deja el puerto C de bits, a menos que desactivemos el módulo USB. El puerto E tiene solo 3 bits ya que RE3 es usado por MCLR.

Para configurar todo el puerto A como salida (6 pines que tiene el puerto A por que dos se usan con el oscilador) se realiza lo siguiente:

TRISA=0x00;

Configurar el puerto B como salida:

14

Page 15: Prog Pic c18

TRISB=0xFF;

Configurar la primera mitad del puerto como entrada y la segunda mitad del puerto como salida.

TRISD=0xF0; //nibble bajo salida (0) nibble alto entrada (F)

Para los demás puertos es exactamente el mismo procedimiento, considerando las restricciones que tenemos en los pines de los puertos A, C y E.

Leer y escribir puertos

Debemos tener las mismas consideraciones que en el registro TRIS sobre los pines desactivados por el uso de oscilador externo, USB y MCLR. Ahora veamos como se escribe y se leen los puertos.

ESCRIBIR:

PORTD=0xFF; //Se encienden todos los pines del puerto D

LEER:

variable=PORTB; //Se coloca el dato en PORTB en una variable.

Tener en cuenta que la variable debe ser declarada como char o unsigned char.

Consideraciones para el flujo del programa

El PIC cuando es borrado, solo tiene instrucciones NOP en su interior las cuales son el código 0xFFF. Para evitar que al terminar un programa vuelva al inicio de la memoria (VECTOR RESET) debemos de realizar un ciclo infinito que evite que se lea la zona de NOP que está al final del programa. Para esto se utiliza un while infinito al final de la función main.

Otra consideración importante es que cuando encendemos el microcontrolador PIC18F4550 los puertos son configurados como analógicos, para configurar los puertos como digitales tenemos que colocar 0x0F en el registro ADCON1. Ahora no lo explico porque lo vamos a ver más adelante en Convertidor Analógico Digital.

Ahora veamos un pequeño programa. Colocaré solo el código que va dentro de la función principal llamada main para evitar colocar toda la plantilla.

15

Page 16: Prog Pic c18

Ejemplo 1: Encender todo el puerto A.

Solución: Colocar 0xFF o 0x3F en el PORTA al igual que en TRISA, considerando los pines que no están disponibles por el oscilador.

Solución 1.1

void main(){

ADCON1=0x0F; //Configuramos los puertos como digitales.TRISA=0x00; //Configuramos todo el puerto como salida.PORTA=0xFF;while(1){}

}

Solución 1.2

void main(){

ADCON1=0x0F; //Configuramos los registros como digitales.TRISA=0x;PORTA=0x3F;

}

Las dos soluciones nos dan el mismo resultado. Al escribir este código en el pic con el HID Bootloader, veremos que se enciende todo el puerto. En nuestra placa de prueba, encenderán RA4 y RA5.

Ejemplo 2: Encender solo el pin RA4.

Solución: Colocamos 0x00 en TRISA y 0x10 en PORTA o usar el registro por bits para encender el PIN RA4.

Solución 2.1

void main(){

ADCON1=0x0F; //Todos los puertos como digitales.TRISA=0x00;PORTA=0x10;while(1){}

}

16

Page 17: Prog Pic c18

Solución 2.2

void main(){

ADCON1=0x0F;TRISA=0x00;PORTA=0x00;PORTAbits.RA4=1; //Se enciende el pin RA4.While(1){}

}

Los dos ejemplos realizan exactamente lo mismo. Como podemos ver, se puede co-locar todo el puerto como salida aunque no utilicemos el resto de los pines.

Ejemplo 3: Verificar que el puerto B contenga el número 55h o AAh, en el primer ca-so, encender RC0, en el segundo caso encender RC1, cualquier otro caso apagar ambos pi-nes; los dos pines no pueden estar encendidos al mismo tiempo. Hacer la comprobación de forma indefinida.

Solución: Colocar la comprobación dentro del ciclo infinito.

Void main(){

ADCON1=0x0F;TRISC=0x00;TRISB=0xFF;PORTC=0x00;while(1){

if(PORTB==0x55){

PORTC=0x01;}else if(PORTB==0xAA){

PORTC=0x02;}else{

PORTC=0x00;}

}}

Notemos que cada vez que realizamos un programa más avanzado, va aumentando su tamaño. Todo es dependiendo del objetivo a alcanzar. Hay que tener mucha lógica para realizar un programa para microcontrolador.

17

Page 18: Prog Pic c18

Retardos

Para realizar un retardo dentro de un programa podemos usar instrucciones NOP, que equivalen a 4 ciclos del oscilador principal, mas sin embargo, esta condición no se cum-ple cuando usamos Bootloader, ya que realizamos un aumento en frecuencia para el uso de USB. La frecuencia necesaria para la comunicación USB es de 48MHz.

La frecuencia es una cuarta parte, por que cada instrucción se ejecuta en 4 ciclos del oscilador, por lo tanto, la frecuencia de una instrucción es de 12MHz.

Sabemos también que el periodo es inverso de la frecuencia, así que, el tiempo que tarda en realizarse cada instrucción es de 83.33ns.

T=1f

T=1

48MHz=20.83ns

Ti=4TTi=4x(20.83ns)=83.33ns

Sin embargo, C18 cuenta con una serie de funciones de retardo declaradas en la biblioteca delays.h y son las siguietes:

void Delay100TCYx(char n);

void Delay1KTCYx(char n);

void Delay10KTCYx(char n);

Donde n es el multiplicador de TCY.

Tomando en cuenta que cada instrucción toma 83.33ns (1TCY), realicemos un retardo de 10μs. Para ello tenemos que dividir 10μs entre 83.33ns; esto nos dará un número, al cual le llamaremos número mágico de retardo, y lo simbolizaremos con #.

#=10μ s

83.33ns=120

Si utilizamos la primer función 100TCY entonces debemos de dividir el número mági-co en 100 y nos dará un resultado de 1.2. Como no podemos usar punto decimal en varia-bles del tipo char, tenemos que colocar uno y completar el ciclo con 20 Nop().

Ejemplo:

Delay100TCYx(1);for(i=0;i<10;i++)

Nop();

¿Por que se utilizan solo 10 instrucciones? Por que la comparación se toma también como una instrucción, entonces el Nop se tiene que dividir a la mitad.

18

Page 19: Prog Pic c18

El display LCD alfanumérico

El display LCD alfanumérico que mostraré en esta sección, será de 16 caracteres por dos líneas de matriz de 5x7. Realizaré 4 funciones para hacer el proceso lo más fácil posible; no voy a explicar estas funciones a fondo, solo mencionaré como funcionan y qué es lo que hay que hacer en caso de requerir alguna funcionalidad extra. Explicaré el archivo .h y .c que contienen definiciones de las instrucciones del LCD y las funciones mencionadas con anterioridad. Para mayor información consultar el manual técnico (data sheet) del LCD alfanumérico.

Comencemos con el archivo de cabecera LCD.h

/**************************************** ** Cabecera para uso de LCD en PIC * * Autor: Francisco Javier García Olmos * * Fecha: 31 de Enero de 2012 * *****************************************/

;Acciones del display #define CLEAR = 0x01 #define CURSOR_HOME = 0x02

;Control del display

#define DISPLAY_CONTROL = 0x08 #define DON = 0x04 #define CURSOR_ON = 0x02 #define BLINK_ON = 0x01

;Modo de entrada

#define DISPLAY_MODE = 0x04 #define INCREMENT = 0x02 #define SHIFT = 0x01

;Shift_Cursor Display #define SHIFT_CUR_LEFT = 0x13 #define SHIFT_CUR_RIGHT = 0x17 #define SHIFT_DISP_LEFT = 0x1B #define SHIFT_DISP_RIGHT = 0x1F

;Function Set #define FUNCTION_SET = 0x20 #define EIGHT_BIT = 0x10 #define LINE_5x7 = 0x03 #define LINE_5x10 = 0x07 #define LINES_5x7 = 0x0B

#define DDRAM_L1 = 0x80 #define DDRAM_L2 = 0xC0 #define DDRAM_L3 = 0x90 #define DDRAM_L4 = 0xD0

19

Page 20: Prog Pic c18

void Comando_LCD(char);void Dato_LCD(char);void Inicializa_LCD(void);

Ahora mostraré el archivo C con las funciones que se usarán en el proceso de escri-tura y configuración del LCD alfanumérico.

/**************************************** ** Funciones para uso de LCD en PIC * * Autor: Francisco Javier García Olmos * * Fecha: 31 de Enero de 2012 * *****************************************/

#include <LCD.h>#include <delays.h>

#define RS PORTDbits.RD4#define RW PORTDbits.RD5#define E PORTDbits.RD6#define Puerto PORTD

void Comando_LCD(char dato){

char temp;

temp = dato; // Respalda dato en temp dato = dato >> 4; // Corrimiento de cuatro lugares a la derecha Puerto = dato; // Nible alto en Puerto RW = 0; // Escribir en LCD RS = 0; // Indica comando para LCD E = 1; // Activa Pulso de reloj Delay1KTCYx(60); // Retardo de 5msE = 0; // Desactiva Pulso de reloj Puerto = temp; // Nible bajo en Puerto RW = 0; // Escribir en LCD RS = 0; // Indica comando para LCD E = 1; // Activa Pulso de reloj Delay1KTCYx(60); // Retardo de 5msE = 0; // Desactiva Pulso de reloj

}

void Dato_LCD(char dato) {

char temp;

temp = dato; // Respalda dato en temp dato = dato >> 4; // Corrimiento de cuatro lugares a la derecha Puerto = dato; // Nible alto en Puerto RW = 0; // Escribir en LCD RS = 1; // Indica dato para LCD E = 1; // Activa Pulso de reloj Delay1KTCYx(60); // Retardo de 5msE = 0; // Desactiva Pulso de reloj Puerto = temp; // Nible bajo en Puerto RW = 0; // Escribir en LCD

20

Page 21: Prog Pic c18

RS = 1; // Indica dato para LCD E = 1; // Activa Pulso de reloj Delay1KTCYx(60); // Retardo de 5msE = 0; // Desactiva Pulso de reloj

}

void Inicializa_LCD(){

Delay10KTCYx(137);Puerto=0x02;E=1;Delay1KTCYx(60);E=0;Delay1KTCYx(150);Comando_LCD(FUNCTION_SET | LINES_5x7);Comando_LCD(DISPLAY_CONTROL | DON);Comando_LCD(CLEAR);Comando_LCD(DISPLAY_MODE | INCREMENT);

}

Las tres funciones son muy fáciles de entender, mas sin embargo, será tarea extra, ya que por motivos de tiempo y espacio, no podré hacer en este documento, solo explicaré su funcionamiento.

La función Comando_LCD recibe un parámetro y lo escribe en el LCD en forma de comando. La función Dato_LCD también recibe un parámetro pero este lo escribirá en el LCD como un DATO y se mostrará.

La función Inicializa_LCD no recibe parámetros. Sirve para configurar el LCD como un Display LCD de 16x2, de matriz de 5x7 y de incremento automático. No se mostrará el cursor y no parpadeará el caracter. Si se necesitan estas funciones, cambiarlas en esta función.

Procedimiento para escribir en el LCD:

1. Inicializar el LCD.

2. Limpiar pantalla.

3. Moverse a la posición deseada.

4. Escribir.

Bueno, entonces hagamos un pequeño programa que muestre HOLA en la pantalla pero antes mencionaré que se conectará al puerto D.

Ejemplo 1: Mostrar HOLA en el LCD de 16x2.

Solución: Utilizar las funciones explicadas anteriormente, así como el procedimiento. Se puede realizar una función extra para mostrar una cadena.

21

Page 22: Prog Pic c18

Solución 1:

//Nos saltaremos esta parte de la plantilla

void main(){

ADCON1=0x0F;TRISD=0x00;PORTD=0x00;Inicializa_LCD();Comando_LCD(CLEAR);Comando_LCD(DDRAM_L1 + 6);Dato_LCD('H');Dato_LCD('O');Dato_LCD('L');Dato_LCD('A');PORTD=0;while(1){}

}

Solución 2:

// Nos saltaremos esta parte de la plantilla

char texto[]=”HOLA”;

void Cadena_LCD(char *cadena) //Función que mostrará una cadena{

char *aux=cadena;while(aux!=NULL){

Dato_LCD(*aux);aux++;

}}

void main(){

ADCON1=0x0F;TRISD=0x00;PORTD=0x00;Inicializa_LCD();Comando_LCD(CLEAR);Comando_LCD(DDRAM_L1 + 6);Cadena_LCD(texto);PORTD=0;while(1){}

}

Se puede observar que lo más complicado de entender son los comandos, los datos se mandan consecutivamente sin mayor problema. Un punto importante es que se realizó una suma de 6 para desplazar la memoria y centrar el texto. Si no hubiéramos hecho esta suma, el texto hubiera aparecido desde el primer carácter en el LCD. Observar también que se realizó la función para mostrar texto contenido en una cadena.

22

Page 23: Prog Pic c18

Ahora veamos las conexiones del LCD. El LCD tiene dos pines de alimentación, uno de contraste, tres de control, ocho de datos y dos extras en caso de que tenga luz de fondo (back light).

VSS y VDD se conectan a +5V y tierra, y el contraste VEE se conecta a un potenciómetro de 10KΩ, el pin 4 de control RS (decide si es dato o comando) a RD4, el pin 5 de control RW (lectura / escritura) va conectado a RD5 o a tierra, el pin 6 de control E (habilitado) a RD6. De los siguientes ocho pines de datos solo se utilizarán los últimos 4 (D4, D5, D6, D7) que van conectados a RD0, RD1, RD2, y RD3; los pines D0, D1, D2 y D3 no se conectan. En caso de luz de fondo se conectaran a +5V y tierra los últimos pines.

El siguiente diagrama muestra la forma de conexión del LCD alfanumérico de 16x2.

Forma de conectar el LCD alfanumérico de 16x2 sin luz de fondo.

23

Page 24: Prog Pic c18

PROGRAMACIÓN CON INTERRUPCIONES

En este capítulo veremos la programación con interrupciones, veremos más profun-damente las direcciones de los vectores de interrupción y las funciones ISRL e ISRH. Vere-mos los registros INTCON y RCON que son los relacionados con las interrupciones.

Primero explicaré que es una interrupción. Básicamente es una petición que se le ha-ce al microprocesador o microcontrolador (no son lo mismo) para que deje la tarea principal y haga una tarea específica. Un ejemplo de una interrupción es el RESET, que deja la tarea principal para reiniciar la memoria del microcontrolador.

Clasificación de las interrupciones

Se pueden clasificar las interrupciones de tres formas distintas: Por enmascaramien-to, por prioridad y por su locación.

Clasificación por enmascaramiento

Hay dos tipos de interrupciones: enmascarables y no enmascarables.

1. Las interrupciones enmascarables son aquellas donde el microcontrolador puede decidir si la puede atender o no (enmascara la interrupción).

2. Las interrupciones no enmascarables son aquellas que forzosamente se tienen que atender, por ejemplo, el RESET.

Clasificación por prioridad

Hay dos tipos de interrupciones: de alta prioridad y de baja prioridad.

1. Las interrupciones de alta prioridad son aquellas que no pueden interrumpirse mien-tras son atendidas.

2. Las interrupciones de baja prioridad si pueden interrumpirse por otras mientras son atendidas, mas sin embargo su tiempo de respuesta es más amplio que el de una de alta prioridad.

Clasificación por locación

Hay tres tipos de interrupciones: Internas, cambio de flanco y externas.

1. Las interrupciones internas son las que se provocan por los dispositivos embebidos en el microcontrolador, como son temporizadores, ADC, USB, USART, Comparadores, etc.

2. La interrupción por cambio de flanco es aquella que se activa al cambiar el estado del

24

Page 25: Prog Pic c18

nibble alto del puerto B.

3. Las interrupciones externas son las que se provocan al activar los tres primeros pines del puerto B (RB0, RB1 y RB2); se identifican como INT0, INT1 e INT2. INT0 siempre es de alta prioridad.

Vectores de interrupción

Antes de comenzar, explicaré que es un vector. En algunos lenguajes de programa-ción como C ANSI, C++, BASIC, etc, se pueden realizar vectores de datos y se manejan los datos a través de un apuntador, el cual contiene una dirección de memoria. Nosotros nos referiremos a un vector de interrupción a la posición de memoria a la cual el microcontrolador se va a referir cuando se realice una interrupción. En los microcontroladores PIC18 hay 3 vectores de interrupción: RESET, alta prioridad y baja prioridad. Abajo explicare sus direccio-nes:

0x0000 MCLR Vector de interrupción de RESET.

0x0008 ISRH Vector de interrupción de alta prioridad.

0x0018 ISRL Vector de interrupción de baja prioridad.

Estas direcciones se cargan automáticamente en el stack de direcciones al realizarse una interrupción. Mas sin embargo, cuando trabajamos con el BOOTLOADER, estas direcciones cambian.

0x1000 REMAPED MCLR Vector remapeado de RESET.

0x1008 REMAPED ISRH Vector remapeado de alta prioridad.

0x1018 REMAPED ISRL Vector remapeado de baja prioridad.

Como observamos, son las que están en nuestra plantilla. Podríamos hacer la rutina de interrupción en esta dirección pero el espacio es muy pequeño, lo que realizamos es un salto a las funciones llamadas ISRH e ISRL. En estas rutinas, ya podemos extendernos lo que sea necesario, siempre y cuando nos alcance la memoria de programa del microcontro-lador PIC.

Estos vectores los declararemos con #pragma code como ya lo habíamos menciona-do anteriormente.

#pragma code REMAPED_RESET_VECTOR = 0x1000

void reset()

{

_asm goto _startup _endasm

}

25

Page 26: Prog Pic c18

Configurar las interrupciones

Las interrupciones se configuran cambiando sus bits específicos en los registros de interrupción INTCON, INTCON2, INTCON3, PIR1, PIR2, IPR1, PIR2, PIE1 y PIE2. Para las interrupciones externas y de cambio de flanco se usan solo los tres primeros registros men-cionados anteriormente. La única interrupción que no se configura es RESET.

El método que se utiliza es el siguiente:

1. Borramos la bandera (FLAG) de interrupción.

2. Le damos una prioridad ya sea alta o baja, excepto INT0 que es de alta prioridad.

3. Habilitamos la interrupción.

4. Habilitamos las interrupciones globales con RCON.

5. Habilitar las prioridades globales de las interrupciones con INTCON.

Las interrupciones globales son todas aquellas interrupciones enmascarables que hay dentro del microcontrolador. Se activan colocando el bit IPEN del registro RCON en uno. Las prioridades globales se activan colocando en uno el bit GIEH para las interrupciones de alta prioridad y GIEL para las interrupciones de baja prioridad o ambas del registro INTCON.

Las interrupciones externas tienen que configurar también el flanco en el cual estas trabajan; este flanco puede ser de subida o de bajada, esto significa que al presionar un botón, el voltaje cambia de bajo a alto para subida o de alto a bajo para bajada.

Una configuración adicional es el uso de resistencias PULL-UP. Estas resistencias realizan que el puerto B siempre esté estado alto y evitan que se usen resistencias fuera del microcontrolador. Se activan colocando el bit RBPU en cero del registro INTCON2. Al utilizar estas resistencias, el flanco de las interrupciones externas debe de ser de bajada y el botón debe de estar conectado a tierra para hacer un cambio a estado bajo.

Para mayor referencia de los registros INTCON, RCON y los demás mencionados anteriormente, revisar el manual.

Revisar las interrupciones

Cuando una interrupción se ha realizado, se enciende la bandera (FLAG) respectiva de cada interrupción. Dependiendo de la configuración verificamos que se ha realizado la interrupción en las funciones ISRH o ISRL.

La interrupción externa INT1 tiene su bandera (FLAG) en INTCON. El bit se llama INT0IF y está en cero mientras no se ha producido la interrupción y cambia a uno cuando se ha realizado. Una acción muy importante es borrar la bandera de forma manual para evitar que se realice el proceso de interrupción cuando no se ha interrumpido el microcontrolador por esta interrupción.

26

Page 27: Prog Pic c18

Ejemplo: Revisar si se realizó la interrupción INT1 como interrupción de baja prioridad.

#pragma interruptlow ISRLvoid ISRL(){

if(INTCON3bits.INT1IF) //Revisar si se realizó por INT1{

INTCON3bits.INT1IF=0; //Borrar la bandera//Seguir con el proceso de interrupción

}}

Se observa que se realizó la palabra bits para verificar cambios en el bit INT1IF del registro INTCON3. Si hubiera más interrupciones habría que hacer el mismo procedimiento para cada una de ellas.

Interrupciones externas

En este apartado me enfocaré solamente en las interrupciones externas. Realizaré el código de configuración de las tres interrupciones y posteriormente realizaré su verificación en las funciones ISRH e ISRL.

Ejemplo 1: Realizar la configuración de las interrupciones externas con resistencias de pull-up y flanco de bajada. INT1 es de alta prioridad e INT2 es de baja prioridad. Colocar el Puerto C como salida.

Solución 1: Realizar el procedimiento de configuración de las interrupciones toman-do en cuenta que INT0 siempre es de alta prioridad. Colocar el puerto B como entrada y el puerto C como salida colocando TRISB en uno y TRISC en cero.

void main(){

ADCON1=0x0F;TRISB=0xFF;TRISC=0x00;PORTC=0x00;

//Configurar interrupciones

//INT0INTCONbits.INT0IF=0; //Limpiamos la bandera IF.INTCON2bits.INTEDG0=0; //Flanco de bajada.INTCONbits.INT0IE=1; //Habilitamos la interrupción

//INT1INTCON3bits.INT1IF=0; //Limpiamos la bandera IF.INTCON2bits.INTEDG1=0; //Flanco de bajada.INTCON3bits.INT1IP=1; //INT1 de alta prioridad.

27

Page 28: Prog Pic c18

INTCON3bits.INT1IE=1; //Habilitamos la interrupción.

//INT2INTCON3bits.INT2IF=0; //Limpiamos la bandera IF.INTCON2bits.INTEDG2=0; //Flanco de bajada.INTCON3bits.INT2IP=0; //INT2 de baja prioridad.INTCON3bits.INT2IE=1; //Habilitamos la interrupción.

//Resistencias de PULL-UPINTCON2bits.RBPU=0;

//Interrupciones globalesRCONbits.IPEN=1; //Habilitamos interrupciones globales.INTCONbits.GIEH=1; //Habilitamos las interrupciones globales de alta

//prioridad.INTCONbits.GIEL=1; //Habilitamos las interrupciones globales de baja

//prioridad.while(1){}

}

Del ejemplo anterior marque las configuraciones alta y baja prioridad.

Ejemplo 2: Usar INT0 para encender RC0, INT1 para encender RC1 e INT2 para apagar ambos bits.

Solución 2: Verificar las interrupciones en las funciones de alta y baja prioridad.

void ISRH(){

if(INTCONbits.INT0IF) //Revisamos si INT0.{

INTCONbits.INT0IF=0; //Limpiamos la banderaPORTCbits.RC0=1; //Encendemos RC0

}if(INTCON3bits.INT1IF) //Revisamos si INT1.{

INTCON3bits.INT1IF=0; //Limpiamos la banderaPORTCbits.RC1=1; //Encendemos RC1

}}

void ISRL(){

if(INTCON3bits.INT2IF) //Revisamos si INT2{

INTCON2bits.INT2IF=0; //Limpiamos la banderaPORTC=0; //Apagamos el puerto.

}}

En este ejemplo vemos el cambio de verificación entre funciones de baja y alta priori-dad y la forma en la que se realizan los procedimientos requeridos para sus tareas respec-tivas.

28

Page 29: Prog Pic c18

Interrupción del puerto B por cambio de flanco

Esta interrupción se activa cuando cambiamos el estado del nibble alto del puerto B (RB4, RB5, RB6 y RB7). Se activa cuando pasamos de estado bajo a alto y cuando pasamos de estado alto a bajo, por eso su nombre de cambio de flanco.

La interrupción al igual que las interrupciones externas, se configura y se le asigna prioridad sus respectivos bits y según su prioridad, se verifica con su bandera (RBIF).

Ejemplo 1: Configurar la interrupción de cambio de flanco como interrupción de alta prioridad. Utilizar resistencias de PULL-UP. Configurar el puerto C como salida.

Solución 1: Realizar el procedimiento para configurar interrupciones. Se tiene que configurar el puerto B como entrada.

void main(){

ADCON1=0x0F;TRISB=0xFF;TRISC=0x00;PORTC=0x00;

//Configurar las interrupcionesINTCONbits.RBIF=0; //Limpiamos la bandera.INTCON2bits.RBIP=1; //Le asignamos alta prioridad.INTCONbits.RBIE=1; //Habilitamos la interrupción.

//Resistencias de pull-upINTCON2bits.RBPU=0;

//Interrupciones globalesRCONbits.IPEN=1; //Habilitamos interrupciones globales.INTCONbits.GIEH=1; //Habilitamos las interrupciones globales de alta

//prioridad.//No es necesario habilitar las interrupciones globales de baja prioridad//por que no las usaremos.

while(1){}

}

Como observamos en los comentarios, no activamos las interrupciones de baja prioridad ya que no las vamos a usar.

Ejemplo 2: Revisar si se provocó una interrupción por cambio de flanco. Si sucedió, verificar que botón fue apretado. RB4 encenderá o apagará según el estado de RC0, RB5 encenderá o apagará según el estado de RC1, RB6 cambiará el estado de cada Led y RB7 apagará los Leds.

29

Page 30: Prog Pic c18

Solución 2: Verificar que haya sucedido la interrupción y verificar que botón está en cero (estado apretado). Tomar en cuenta que se utiliza lógica negativa por el uso de las resis-tencias de PULL-UP.

void ISRH(){

if(INTCONbits.RBIF){

INTCONbits.RBIF=0; //limpiamos la bandera.if(!PORTBbits.RB4){

PORTCbits.RC0^=1; //Cambiar el estado de RC0.}else if(!PORTBbits.RB5){

PORTCbits.RC1^=1; //Cambiar el estado de RC1.}else if(!PORTBbits.RB6){

PORTCbits.RC0^=1; //Cambiar el estado de RC0.PORTCbits.RC1^=1; //Cambiar el estado de RC1.

}else if(!PORTBbits.RB7){

PORTC=0; //Apagar el puerto.}

}}

Como podemos observar, además de preguntar por la interrupción, hay que pregun-tar por el botón que se ha apretado. Como usamos lógica negativa, cuando se presiona un botón, el bit queda en estado bajo y el microcontrolador lo reconoce como cero lógico.

¿Qué pasa si se aprietan dos botones al mismo tiempo? Pues viendo el algoritmo, el más bajo será el que reconozca como apretado, el otro, será desechado. También podríamos usar operaciones AND para ver si se apretaron dos al mismo tiempo. Sin embargo por falta de espacio y de tiempo, será tarea adicional del lector hacerlo.

A partir de ahora, todo lo que veamos será programado con interrupciones. Todos los módulos embebidos en el microcontrolador tienen interrupciones y se verificarán y configura-rán exactamente igual que como lo hicimos con estas dos interrupciones.

30

Page 31: Prog Pic c18

PROGRAMACIÓN DE HARDWARE INTERNO Y EXTERNO

En este capítulo enseñaré a programar hardware externo e interno. Como hardware externo, enseñaré el uso de Display de 7 segmentos, teclado matricial de 4x4 y como conec-tar transistores, puente H y compuertas lógicas con el PIC18F4550. Como hardware interno enseñaré a programar los temporizadores y contadores, así como el módulo ADC.

El display de 7 segmentos

El display de 7 segmentos funciona con un código llamado BCD. Podemos realizar el algoritmo lógico de conversión de binario a BCD o realizar un vector en C que contenga di -cho código guardado en memoria. El vector se trabaja exactamente igual a C ANSI.

Ejemplo 1: Realizar un vector con el código en BCD ordenado de menor a mayor para controlar un display de 7 segmentos.

Solución 1: Declarar el vector de tipo char con 10 espacios de memoria como varia-ble global.

char bcd[10]={0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6B, 0x7B, 0x87, 0xEF, 0x6F};

Como vemos, es fácil realizar este vector. Ahora veremos como recuperar el código en una interrupción.

Ejemplo 2: Realizar el algoritmo de interrupción de INT0. Para que cada vez se pre-sione, aumente un contador hasta el 9, llegando a este punto, reiniciarlo. Mostar el resultado en BCD en el puerto D.

Solución 2: Realizar el algoritmo en ISRH comprobando la interrupción INT0, reali-zando comparaciones, incrementos y obtener los datos del vector realizado anteriormente.

#pragma interrupt ISRHvoid ISRH(){

if(INTCONbits.INT0IF){

INTCONbits.INT0IF=0; //Borramos la banderaif(contador==9)

contador=0; //Si el contador es 9 colocamos ceroelse

contador++; //Aumentamos el contador.PORTD=bcd[contador]; //Colocamos en el puerto D el código en BCD.

31

Page 32: Prog Pic c18

}}

Con este pequeño algoritmo, hemos realizado un contador con interrupciones y un display de 7 segmentos.

Nota 1: Como estamos usando lógica positiva, solo podemos usar un display de 7 segmentos de cátodo común. Utilizar lógica negativa en la tabla para usar un display de áno-do común o utilizar un inversor externo 74LS04 o 74HS04.

Nota 2: PRECAUCIÓN, el PIC18F4550 solo puede manejar corrientes inferiores a los 25mA, si superamos esta corriente podemos quemar el pin, puerto o todo el microcontro-lador. Para realizar operaciones con más corrientes utilizar BUFFER de corriente (74LS245), Inversores (74LS04) o transistores en forma de interruptor.

Teclado Matricial

El teclado matricial que demostraré en este apartado, es uno de 4x4 (16 teclas). Utilizaremos para su decodificación la interrupción de cambio de nivel y la mitad del puerto B como salida. Este teclado tiene 4 filas y cuatro columnas, lo cual nos da un número de 8 líneas de datos (cables de alimentación).

Todas las lineas de alimentación del teclado irán al puerto B. La mitad alimentará al teclado y la otra mitad realizará la interrupción de cambio de nivel cuando una tecla se pre-sione, para ello es necesario activar las resistencias de PULL-UP y colocar el primer nibble en cero. Cuando se realice una interrupción se tendrá que colocar el puerto en 1 e ir cam-biando el cero de lugar hasta obtener la tecla que se presionó. La comprobación se hace en cero por el uso de resistencias de PULL-UP, entonces se utiliza la lógica negativa.

Procedimiento:

1. El primer paso es colocar en cero el nible bajo o todo el puerto B.

2. Al presionar una tecla se interrumpe el microcontrolador.

3. Colocar el nible bajo en uno o todo el puerto B.

4. Colocar un cero en el bit 0 del puerto B. Si está la tecla, verificar el código en el nibble alto para decodificarla y finalizar, en caso contrario, seguir al paso 5.

5. Colocar un cero en el bit 1 del puerto B. Si está la tecla, verificar el código en el nibble alto para decodificarla y finalizar, en caso contrario, seguir al paso 6.

6. Colocar un cero en el bit 2 del puerto B. Si está la tecla, verificar el código en el nibble alto para decodificarla y finalizar, en caso contrario, seguir al paso 7.

7. Colocar un cero en el bit 3 del puerto B. Decodificar la tecla usando el nibble alto. Si no se encuentra, se ha dejado de apretar la tecla, entonces, salir.

Recordemos que al dejar de presionar la tecla se vuelve a realizar la interrupción por el cambio de flanco, en este caso, no decodificaremos nada y tenemos que salir sin mostrar nada.

32

Page 33: Prog Pic c18

Para decodificar las teclas se verifica que pin se apaga. Utilizar el nibble bajo como multiplicador el nibble alto como sumador. El nible bajo codifica filas y el alto codifica colum-nas. Ejemplo: La tecla 8 está en la posición 9 tomando como referencia la posición inicial co-mo cero (es la décima tecla); está en la fila 3 columna 2.

Encontraremos presionada la tecla cuando el cero esté en RB2 (fila 3) y el pin que se activará será RB5 (columna 2). Entonces tendríamos que realizar una multiplicación de filas – 1 por cuatro (número de filas) y sumar columnas – 1. Tecla = ((Columnas – 1) * 4)+ (Filas – 1) = (3 – 1) * 4 + (2 – 1) = (2 * 4) + 1 = 8 + 1 = 9.

Si realizamos una tabla con valores ASCII (diferentes a los BCD) y recorremos el número de tecla y lo mostramos en el LCD alfanumérico, tendremos las teclas decodificadas.

Aquí dejo una imagen del teclado matricial para que vean cuales son las teclas que tiene y cual es el orden que tienen cada una de ellas.

En el mercado se puede encontrar el teclado matricial con letras en vez de los sím-bolos de operaciones aritméticas, en su lugar, letras A, B, C, D, * en vez de ON/C y # en vez de igual. Sin embargo, dependiendo de la codificación que le demos será la ejecución que realizará. En nuestro caso, solo se mostrará la tecla en el LCD alfanumérico.

Realicemos un programa completo. En este programa pondremos a prueba todo lo a-prendido en los temas anteriores y algo de imaginación, así como algunos trucos para su realización.

Ejemplo 1: Realizar el programa de decodificación del teclado matricial en una interrupción de cambio de flanco de alta prioridad, el vector con código ASCII de cada tecla y el código para mostrar en el LCD.

33

Page 34: Prog Pic c18

Solución 1: Realizar un programa completo que incluya tablas, LCD, variables e in-terrupciones. La decodificación se deberá realizar con el procedimiento mencionado anterior-mente.

Nota: Para evitar perdernos en el programa, ordenaremos las instrucciones en fun-ciones. De ahora en adelante el lector debería tomar este hábito para hacer el código más e-legante.

/************************************************************************ Autor: Francisco Javier García Olmos ** Fecha: 1 de Febrero de 2012 ** Archivo: teclado.c ** Descripción: Programa para decodificar el teclado matricial ************************************************************************/

#include <p18f4550.h>#include <delays.h>#include “lcd.h”

/**************** Bits de Configuracion *************************************/ #pragma config PLLDIV = 5 #pragma config CPUDIV = OSC1_PLL2 #pragma config USBDIV = 2 #pragma config FOSC = HSPLL_HS #pragma config FCMEN = OFF #pragma config IESO = OFF #pragma config PWRT = OFF #pragma config BOR = ON #pragma config BORV = 3 #pragma config VREGEN = ON #pragma config WDT = OFF #pragma config WDTPS = 32768 #pragma config MCLRE = ON #pragma config LPT1OSC = OFF #pragma config PBADEN = OFF #pragma config CCP2MX = ON #pragma config STVREN = ON #pragma config LVP = OFF #pragma config ICPRT = OFF #pragma config XINST = OFF #pragma config CP0 = OFF #pragma config CP1 = OFF #pragma config CP2 = OFF #pragma config CP3 = OFF #pragma config CPB = OFF #pragma config CPD = OFF #pragma config WRT0 = OFF #pragma config WRT1 = OFF #pragma config WRT2 = OFF #pragma config WRT3 = OFF #pragma config WRTB = ON #pragma config WRTC = OFF #pragma config WRTD = OFF #pragma config EBTR0 = OFF

34

Page 35: Prog Pic c18

#pragma config EBTR1 = OFF #pragma config EBTR2 = OFF #pragma config EBTR3 = OFF #pragma config EBTRB = OFF

char teclas[]=”789/456X123-C0=+”;char mensaje[]=”TECLA PRESIONADA”;char estado[4]={0B11111110, 0B11111101, 0B11111011, 0B11110111};char filas, columnas, tecla, puerto, i;

void ISRH(); void ISRL();

extern void _startup (void); #pragma code REMAPPED_RESET_VECTOR = 0x1000 void _reset (void) {

_asm goto _startup _endasm }

#pragma code HIGH_REMAPED_VECTOR_SECTOR = 0x1008 void high_remaped_vector() {

_asm goto ISRH _endasm

}

#pragma code LOW_REMAPED_VECTOR_SECTOR = 0x1018 void low_remaped_vector() {

_asm goto ISRL _endasm

} #pragma code

#pragma interrupt ISRH void ISRH() {

}

#pragma interruptlow ISRL void ISRL() {

if(INTCONbits.RBIF){

INTCONbits.RBIF=0; //Borramos la banderatecla=0;filas=0;columnas=0;for(i=0;i<4;i++){

PORTB=estado[i];puerto=PORTB;puerto=puerto>>4; //Cambiamos los nibles.

35

Page 36: Prog Pic c18

puerto=puerto | 0xF0; //Solo nos interesa la mitad del puerto//pero como es lógica negativa se usa operación OR.

puerto=~puerto; //Complemento para lograr lógica positiva.if(puerto){

columnas=i;if(puerto & 0B00000001)

filas=0else if(puerto & 0B00000010)

filas=1;else if(puerto & 0B00000100)

filas=2;else

filas=3;

tecla=(columnas*4)+filas;Comando_LCD(DDRAM_L2+8);Dato_LCD(teclas[tecla]);PORTB=0;

}}

}}

void Cadena_LCD(char *cadena){

char *aux=cadena;while(*aux){

Dato_LCD(*aux);aux++;

}}

void Configura_Puertos(){

ADCON1=0x0F;TRISB=0xF0;TRISD=0x00;PORTD=0x00;

}

void Configura_Interrupciones(){

INTCONbits.RBIF=0; //Limpiamos la bandera.INTCON2bits.RBIP=1; //Le asignamos alta prioridad.INTCONbits.RBIE=1; //Habilitamos la interrupción.

}

void main() {

Configura_Puertos();Configura_Interrupciones();Inizializa_LCD();Comando_LCD(DDRAM_L1);Cadena_LCD(mensaje);

36

Page 37: Prog Pic c18

//Resistencias de PULL-UPINTCON2bits.RBPU=0;//Interrupciones GlobalesRCONbits.IPEN=1; //Prioridades de interrupciones.INTCONbits.GIEH=1; //Interrupciones globales de alta prioridad.while(1) {

} }

El vector de teclas es para el teclado mostrado en la figura anterior, hay teclados co-merciales diferentes con diferentes teclas como ya se mencionó anteriormente; hay que cambiar el orden de las teclas para esos teclados.

Posiblemente se te haga difícil de entender este programa por su lógica, pero está comprobado, lo armé físicamente y lo simulé en el programa Proteus 7 (Software de simula-ción), con un PIC18F4620 parecido al PIC18F4550. Muestro la imagen siguiente de la simu-lación.

Así como está el programa, no funciona en el software Proteus 7, necesitas cambiar las direcciones de interrupción como si no tuviera bootloader. Cambiando eso, funciona per-fectamente.

37

Page 38: Prog Pic c18

Temporizadores y contadores

El PIC18F4550 tiene 4 temporizadores (TIMER) con funciones diferentes pero muy parecidas entre si. Podemos utilizar el Timer como contador de eventos externos, lo explicaré más adelante, ya que explicaré su registro de configuración. En este manual solo explicaré dos Timer (Timer 0 y Timer 1), explicaré su funcionamiento, y explicaré como hacer un se-gundero, un contador de eventos externos y un generador de frecuencias.

¿Qué se puede hacer con un Timer?

• Generador de frecuencia.

• Modulador de ancho de pulso (PWM).

• Segundero.

• Reloj con fecha a partir del segundero.

• Medidor de frecuencia.

• Medidor de capacitancia a partir del medidor de frecuencia.

• Medidor de resistencia a partir del medidor de frecuencia.

• Medidor de inductancias a partir del medidor de frecuencia.

• Control de motores de CD a partir de PWM.

• Control de servomotores a partir de PWM.

• Muestreadores de datos a partir de ADC (ADC lo explicaré mas adelante).

• Reloj de pulsos.

• Dimmer Digital (Controlador de intensidad de luz de focos de CA).

• Comunicación serial a partir del reloj de pulsos.

Por motivos de espacio y de tiempo no daré lo que es comunicación serial en este manual (USART, USB y otros más).

Registro de configuración

Explicaré muy brevemente el registro de configuración del TIMER 0 llamado T0CON. Este registro controla el funcionamiento del Timer para usarse como temporizador o como contador, configura su prescaler y el número de bits a usar.

El registro T0CON (para el timer 0) tiene los siguientes elementos:

bit 7 TMR0ON: Enciende o apaga el Timer.

bit 6 T08BIT: En 1 configura el timer a 8 bits, en cero, lo configura a 16 bits.

bit 5 T0CS: En uno, selecciona al pin RA4 como entrada de eventos, en cero,

38

Page 39: Prog Pic c18

selecciona al oscilador principal para el retardo.

bit 4 T0SE: Solo se usa para entrada de eventos de RA4, en uno, selecciona flanco de bajada, en cero, selecciona flancos de subida.

bit 3 PSA: En 1 no asignamos prescaler, en cero, asignamos prescaler.

bits 2 a 0 TOPS: Selector de prescaler en caso de que PSA sea cero.

111 = 1:256

110 = 1:128 Prescale value

101 = 1:64 Prescale value

100 = 1:32 Prescale value

011 = 1:16 Prescale value

010 = 1:8 Prescale value

001 = 1:4 Prescale value

000 = 1:2 Prescale value

Timers

¿Qué es un Timer? Es un contador de ciclos de máquina o contador de ciclos de un oscilador externo, que al desbordarse produce una interrupción. También puede contar pul -sos externos.

¿Cómo funciona un Timer? Cuenta números del 0 al 255 cuando se configura en 8 bits (lo explico más adelante) o al 65536 en caso de configurarlo de 16 bits. Puede contar ciclos de máquina (en caso de usar el oscilador principal) o contar eventos externos (usando osciladores externos o pulsos de algún circuito externo como el famosisimo NE555, flip-flops, o simplemente botones PUSH) en un pin específico del cada timer. Cuando el timer llega a su cuenta máxima más uno (se desborda) activa una interrupción. Dicha interrupción, se puede utilizar como interrupción de alta prioridad o de baja prioridad (Ver tema configurar interrupciones).

¿Cómo usar un Timer? Antes de comenzar, vamos a definir un término que se llama prescaler, que es una división de frecuencia. Ejemplo, si tengo una frecuencia principal de 20MHz y un prescaler de 1, tengo la misma frecuencia final de 20MHz, si tengo el prescaler de 2, la frecuencia es de 10MHz, si tengo un prescaler de 4 tengo una frecuencia de 5MHz. Como se observa, el prescaler provoca que mis tiempos sean más largos y pueda alcanzar una cierta frecuencia más fácilmente.

Para utilizar el timer vamos a recurrir nuevamente al número mágico # usado en retardos, pero con una pequeña variante, que es la multiplicación por el prescaler y la posibilidad de usar 16 bits.

39

Page 40: Prog Pic c18

t=4∗presc∗(2n

−#)

Fosc

#=2n−(

t∗Fosc4∗presc

)

#=2n−(

t∗48 MHz4∗presc

)

Donde:

n es el número de bits, pueden ser 8 o 16.

t es el tiempo del timer.

Focs es la frecuencia del oscilador, en caso de bootloader es de 48MHz.

Presc es el prescaler utilizado, este valor se puede proponer de 1 a 128.

Ejemplo 1: Realizar el cálculo del número mágico para obtener un retardo de aproxi-madamente 1.25ms con 8 bits. Proponer el prescaler.

Solución 1: Después de varios intentos, obtuve un prescaler de 64 para un buen nú-mero mágico no muy grande, pero tampoco muy pequeño.

#=28−(

1.25ms∗48MHz4∗64

)

#=256−(60000

256)

#=256−243.37#=21.62#≈22

Este número será colocado en el registros TMR0L y colocar cero en TMR0H. En ca-so de ser un número de 16 bits, se debe de convertir en hexadecimal y el nibble alto deberá de colocarse en TMR0H y el nibble bajo deberá de colocarse en TMR0L.

¿Cómo configuro el timer? Según el registro de configuración T0CON deberemos de colocar los siguientes valores:

• Encender el timer.

• Configurarlo a 8 bits (para el ejemplo de 1.25ms).

• Seleccionar el oscilador principal.

• No importan los flancos. Colocar cero.

• Asignar prescaler.

• El prescaler es de 64.

El número que se debe de colocar en binario sería B'11000101' en hexadecimal nos quedaría 0xC5.

40

Page 41: Prog Pic c18

Ejemplo 2: Realizar el programa de configuración del Timer 0 a 8 bits para 1.25ms y realizar su interrupción de alta prioridad para cambiar el estado de RC0 en este tiempo.

Solución 2: Configurar el programa con los números obtenidos anteriormente y configurar la interrupción del TIMER0. En la función ISRH colocar la operación xor para cam-biar el estado de RC0.

#pragma interrupt ISRHvoid ISRH(){

if(INTCONbits.TMR0IF){

INTCONbits.TMR0IF=0; //Borramos la bandera.PORTCbits.RC0^=1; //Cambio de estado del pin RC0.TMR0H=0;

}}

void Configura_Puertos(){

ADCON1=0x0F;TRISC=0;PORTC=0;

}

void Configura_Interrupciones(){

INTCONbits.TMR0IF=0; //Limpio la bandera.INTCON2bits.TMR0IP=1; //Alta prioridad.INTCONbits.TMR0IE=1; //Habilito la interrupción.

}

void Configura_Timer(){

TMR0H=0;TMR0L=22;T0CON=0xC5;

}

void main(){

Configura_Puertos();Configura_Interrupciones();Configura_Timer();RCONbits.IPEN=1;INTCONbits.GIEH=1;while(1){}

}

Como observamos en la función de interrupción, volvemos a configurar el Timer ya que, como mencioné anteriormente la interrupción se realiza cuando el contador vuelve a ce-ro y debemos volver a cargar el número para que cuente nuevamente el ciclo de 1.25ms.

Nota: El Timer 1 tiene una configuración muy parecida al TIMER 0, con la diferencia

41

Page 42: Prog Pic c18

que el Timer 2 puede contener un oscilador externo en RC0 y RC1 para un conteo de even-tos. Si colocamos un oscilador de baja frecuencia, podemos realizar un segundero. Reco-miendo usar el de 32KHz (32768 Hz) y cargar en TMR1H 0x80 y TMR0L 0x00. Este programa lo explicaré en la sección segundero.

Contadores

Los contadores de eventos son Timers que en vez de contar ciclos de oscilador, cuentan pulsos externos. Si contamos el número de pulsos en un segundo, tenemos un frecuenciometro; por el contrario, si queremos que a cierto número de cuentas se realice una interrupción, debemos de colocar un número en TMR0L para alcanzar dicha cuenta. En este caso, como no usamos oscilador interno, no dividimos la frecuencia en 4.

Para configurar el contador en el registro T0CON debemos de considerar lo si -guiente:

• Encender el Timer

• Configurarlo a 8 o 16 bits.

• Seleccionar eventos externos.

• Seleccionar el flanco del pulso.

• No asignar prescaler para tiempo real.

• El prescaler es irrelevante por que no ha sido asignado.

Por lógica digital todos los valores irrelevantes los consideramos cero. El valor obtenido con flancos de subida es 0xF8.

Ejemplo 1: Realizar un programa con TIMER0 que interrumpa al microcontrolador cuando sea apretado 5 veces un botón en RA4. La interrupción deberá ser de alta prioridad y el flanco deberá de ser de subida y al realizarse deberá de cambiar de estado RC0 de forma repetitiva. Tomar en cuenta que RA4 no tiene resistencias de PULL-UP ni PULL-DOWN.

Solución 1: El número mágico deberá ser 251 para que cuando lleguemos a 255+1 se interrumpa el microcontrolador. El circuito externo deberá ser un push conectado a +5V con una resistencia de 1K a tierra. El nodo de la resistencia y botón, deberá ser llevado a RA4.

#pragma interrupt ISRHvoid ISRH(){

if(INTCONbits.TMR0IF){

INTCONbits.TMR0IF=0; //Limpiamos la bandera.PORTCbits.RC0^=1; //Cambiamos el estado.Configura_Timer();

}}

42

Page 43: Prog Pic c18

void Configura_Puertos(){

ADCON1=0x0F;TRISC=0x00; //Puerto C como salida.PORTC=0x00;TRISA=0xFF; //Puerto A como entrada.

}

void Configura_Interrupciones(){

INTCONbits.TMR0IF=0; //Limpiar la banderaINTCON2bits.TMR0IP=1; //Interrupción de alta prioridad.INTCONbits.TMR0IE=1; //Habilito la interrupción.

}

void Configura_Timer(){

TMR0H=0x00;TMR0L=0x05;T0CON=0xF8;

}

void main(){

Configura_Puertos();Configura_Interrupciones();Configura_Timer();RCONbits.IPEN=1;INTCONbits.GIEH=1;while(1){}

}

Como observamos es realmente similar a un temporizador, pero cambiando solo el número de T0CON.

Como explique anteriormente podemos desactivar la interrupción y contar el número de pulsos en 1 seg. Leemos el registro TMR0L y/o TMR0H y con eso conseguimos realizar un frecuenciometro.

El Convertidor Analógico Digital

El convertidor analógico digital (Analogic to Digital Converter ADC) es un módulo em-bebido en el microcontrolador y su función es realizar la cuantificación de niveles de voltaje en periodos de tiempo discretos (muestreo). Para los que no saben lo que hablo y como no daré clases de comunicaciones digitales o procesamiento digital de señales, lo explicaré con las palabras más concretas que se puedan: Cambia un valor de voltaje analógico en un valor digital en un pequeño instante.

Para utilizar el convertidor analógico-digital (ADC) es necesario configurar tres regis-tros: ADCON0, ADCON1 (ya usado anteriormente) y ADCON2. El más difícil de configurar y de entender es ADCON2, pues este registro configura tiempos de retardo para carga del ca-pacitor de medición, tiempos de conversión y tiempos de digitalización; además de esto, tam-bién configura la justificación del resultado. ADCON1 configura los puertos analógicos o digi-

43

Page 44: Prog Pic c18

tales a usar como ya lo habíamos realizado anteriormente, así como fuentes de referencia en caso de usarlas. El registro ADCON0 configura el canal con el que se va a medir y pone en marcha la conversión.

El resultado de la conversión puede ser justificado a la izquierda o derecha para obtener un resultado en 8 o 10 bits respectivamente en los registros ADRESH y ADRESL.

Justificación a la derecha me da un resultado de 10 bits.

ADRESH -> 0B00000011

ADRESL -> 0B11111111

Los bits de mayor peso digital están en ADRESH y los demás están en ADRESL.

Para la justificación a la izquierda tenemos lo siguiente:

ADRESH -> 0B11111111

ADRESL -> 0B11000000

Los bits de mayor peso también están en ADRESH, y en ADRESL solo tenemos 3 bits que pueden ser descartados por tener menor peso digital, equivalentes a milivolts que podemos despreciar. En este caso solo haremos caso de ADRESH, que es un registro de 8 bits, por ende, tenemos el resultado de 8 bits.

Ahora explicare cada uno de los tres registros, comenzando por ADCON2, que es el más difícil y terminando con ADCON0.

El registro ADCON2 puede solo tener un valor siempre, y no se cambiará. Este valor lo calculé con las fórmulas y valores que propone el manual del microcontrolador PIC18F4550 y siempre será el mismo, pero a pesar de esto, explicaré el registro con sus bits.

bit 7 ADFM: 1 Justificado a la derecha, 0 Justidicado a la izquierda.

bit 6 No se usa.

bit 5-4 ACQT: Tiempos de adquisición del dato. Para obtenerlo se usa la fórmula M*TAD=2.4ms donde M es el valor de ACQT y TAD es n veces Tosc (n se verá en el siguiente apartado).

111 = 20 TAD

110 = 16 TAD

101 = 12 TAD

100 = 8 TAD

011 = 6 TAD

010 = 4 TAD

001 = 2 TAD

000 = 0 TAD

bit 2-0 ADCS Tiempos de carga del ADC. Para obtenerlo se usa la fórmula n*Tosc >= 700ns, donde n es el ADCS y Tosc es el inverso de la frecuencia del oscilador (48MHz para bootloader).

44

Page 45: Prog Pic c18

111 = FRC (clock derived from A/D RC oscillator)

110 = FOSC/64

101 = FOSC/16

100 = FOSC/4

011 = FRC (clock derived from A/D RC oscillator)

010 = FOSC/32

001 = FOSC/8

000 = FOSC/2

El valor a colocar en este registro cuando usamos bootloader es de 0x0E. Sin bootloader este valor es de 0x15. Recordemos que los valores de la frecuencia cambian con el bootloader, por eso, el valor de ADCON2 difiere.

El registro ADCON1 se utiliza para configurar las fuentes de referencia, pines analó-gicos y pines digitales.

bit 7-6 No se usan

bit 5 VCFG1: Voltaje de referencia negativo, cuando es uno se usa VREF- (AN2), cuando es cero se usa VSS (GND).

bit 4 VCFG0: Voltaje de referencia psotivo, cuando es uno se usa VREF+ (AN3), cuando es cero se usa VDD (+5V)

bit 3-0 PCFG: Selección de canales. (Ver tabla de manual, donde D es pin digital y A es pin analógico).

Por motivos de aprendizaje colocaré la tabla que viene en el manual.

PCF AN12 AN11 AN10 AN9 AN8 AN7 AN6 AN5 AN4 AN3 AN2 AN1 AN0

0000 A A A A A A A A A A A A A

0001 A A A A A A A A A A A A A

0010 A A A A A A A A A A A A A

0011 D A A A A A A A A A A A A

0100 D D A A A A A A A A A A A

0101 D D D A A A A A A A A A A

0110 D D D D A A A A A A A A A

0111 D D D D D A A A A A A A A

1000 D D D D D D A A A A A A A

1001 D D D D D D D A A A A A A

1010 D D D D D D D D A A A A A

45

Page 46: Prog Pic c18

1011 D D D D D D D D D A A A A

1100 D D D D D D D D D D A A A

1101 D D D D D D D D D D D A A

1110 D D D D D D D D D D D D A

1111 D D D D D D D D D D D D D

Como podemos apreciar en la tabla, si no utilizamos voltajes de referencia y selec-cionamos todos los pines digitales, obtenemos el número 0x0F, que es el valor por defecto que hasta este momento se utilizó en el registro ADCON1. Ahora, dependiendo de nuestras necesidades, colocaremos el valor respectivo en este registro para seleccionar pines analógicos.

El registro ADCON0 se usa para seleccionar el canal a medir, ya que solo podemos medir un canal en un instante de tiempo. También se utiliza para encender el módulo ADC y para iniciar una conversión.

bit 7-6 no se usan.

bit 5-2 Selección del canal a medir.

0000 = Channel 0 (AN0)

0001 = Channel 1 (AN1)

0010 = Channel 2 (AN2)

0011 = Channel 3 (AN3)

0100 = Channel 4 (AN4)

0101 = Channel 5 (AN5)(1,2)

0110 = Channel 6 (AN6)(1,2)

0111 = Channel 7 (AN7)(1,2)

1000 = Channel 8 (AN8)

1001 = Channel 9 (AN9)

1010 = Channel 10 (AN10)

1011 = Channel 11 (AN11)

1100 = Channel 12 (AN12)

1101 = Unimplemented(2)

1110 = Unimplemented(2)

1111 = Unimplemented(2)

bit 1 GO/(!DONE): Estado de la conversión.

bit 0 ADCON: Encendido o apagado del ADC.

46

Page 47: Prog Pic c18

El módulo ADC al igual que todos los módulos del PIC18F4550 tiene su interrupción y se configura como se explicó en el capítulo interrupciones.

Ahora que explique algo acerca de los registros de configuración del ADC y sus registros de resultado, veamos un pequeño ejemplo.

Ejemplo 1: Realizar un programa que codifique el valor obtenido en AN0, sin fuentes de referencia, justificado a la izquierda con resultado de 8 bits. Si el valor obtenido es menor a 1.5v conmutar RC0 a 50ms, si es mayor a 1.5v y menor a 3v, conmutar RC0 a 200ms y si es mayor a 3 volts, conmutar RC0 a 500ms. Utilizar Timer0 para realizar el retardo. Las inte-rrupciones serán de alta prioridad.

Solución 1: Configurar ADC como se mencionó anteriormente, configurar las inte-rrupciones como se mencionó en su capítulo respectivo y conectar adecuadamente un potenciómetro en el pin RA0 (AN0).

Recomendación 1: Por electrónica lineal, utilizaremos un amplificador en modo seguidor para evitar que el voltaje se modifique por la resistencia de entrada del PIC.

Recomendación 2: Utilizar el Timer0 a 16 bits.

Primero, calculamos el número mágico de los tiempos proponiendo el prescaler de 64.

47

Page 48: Prog Pic c18

500ms:

#= 2^16 - (500ms * 48MHz) / (4 * 64)#=18661#=0x48E5

200ms: #=2^16 - (200ms * 48MHz) / (4 * 64)#=28036#=0x6D84

50ms: #=2^16 - (50ms * 48MHz) / (4 * 64)#=56161#=0xDB61

Segundo, calculamos los valores de voltaje:

para 1.5 v:

resolución=5v/255 (8bits)res=0.196

n=1.5/resn=76.5 aprox 77

para 3v

n=3/resn=153

Ahora con estos valores programamos el código.

char valoradc; //Variable global para guardar el resultado de la medición.

#pragma interrupt ISRHvoid ISRH(){

if(INTCONbits.TMR0IF){

INTCONbits.TMR0IF=0; //Limpiamos la bandera.PORTCbits.RC0^=1; //Conmutamos RC0.if(valoradc==77){

//Cargamos 0xDB61 al Timer0TMR0H=0xDB;TMR0L=0x61;T0CON=0x85;

}else if(valoradc==153){

48

Page 49: Prog Pic c18

//Cargamos 0x6D84 al Timer0TMR0H=0x6D;TMR0L=0x84;T0CON=0x85;

}else{

//Cargamos 0x48E al Timer0TMR0H=0x48;TMR0L=0xE5;T0CON=0x85;

}}if(PIR1bits.ADIF){

PIR1BITS.ADIF=0; //Limpiamos la bandera.valoradc=ADRESH; //Copiamos el valor de ADRESH a valoradcADCON0bits.GO=1; //Iniciamos una nueva conversión.

}}

void Configura_Puertos(){

TRISC=0x00;PORTC=0x00;

}

void Configura_Interrupciones(){

//Interrupciones del ADCPIR1bits.ADIF=0;PIE1bits.ADIE=1;IPR1bits.ADIP=1;

//Interrupciones del TimerINTCONbits.TMR0IF=0;INTCONbits.TMR0IE=1;INTCON2bits.TMR0IP=1;

}

void Configura_ADC(){

ADCON2=0x0E;ADCON1=0x0E;ADCON0=0x01; //ADC encendido pero sin realizar conversión.

}

void Configura_Timer(){

//Valor inicial 50ms.TMR0H=0xDB;TMR0L=0x61;T0CON=0x85;

}

49

Page 50: Prog Pic c18

void main(){

Configura_Puertos();Configura_Interrupciones();Configura_ADC();Configura_Timer();RCONbits.IPEN=1;INTCONbits.GIEH=1;ADCON0bits.GO=1; //Iniciamos la conversiónwhile(1){}

}

Con este ejemplo podemos verificar que se pueden utilizar varias funciones de los módulos del PIC18F4550 para realizar un objetivo común.

Estos ejemplos son cosas básicas que se pueden realizar con el PIC18F4550, hay cosas más complicadas, elaboradas y divertidas que se pueden realizar con este microcon-trolador que con un poco de lógica digital y algunos conocimientos de electrónica se pueden realizar.

50

Page 51: Prog Pic c18

ALGUNAS COSAS EXTRAS

En este capítulo último, mencionaré algunas cosas extras que debes saber para rea-lizar proyectos y/o prácticas que te puedan servir en el desarrollo personal del lector. Como mencioné anteriormente, solo es cuestión de tener un poco de imaginación, conocimientos básicos de lógica digital y algo de electrónica. También lo que el lector debe de realizar es el estudio de las partes recomendadas del Manual Técnico del PIC18F4550 que se puede obtener de la página de Microchip, ya que hay algunas cosas que no se mencionan en este documento que serían esenciales que el lector tuviera en cuenta.

Conversión de Números

En este apartado enseñaré como obtener los números de Unidad, Decena y Centena de un registro de 8 bits. Esto te servirá para poder convertir cada uno en BCD o en ASCII, para un display de 7 segmentos o para el LCD alfanumérico. Una vez obtenidos estos números deberás realizar un vector con los valores correspondientes de BCD o ASCII.

Código para obtener Unidad, Decena y Centena.

char udc[3]{0,0,0};char i;

void Obtener_UDC(char numero){

for(i=3;i<=0; i--){

udc[i]=numero%10;numero/=10;

}}

En el vector se guardarán Centena, Decena y Unidad en ese orden. Ahora, ya pode-mos realizar algunas aplicaciones que tengan que mostrar números en un display de 7 seg-mentos o en el LCD alfanumérico.

Segundero

Ahora explicaré como realizar un segundero con el TIMER1 del PIC18F4550. El re-gistro T1CON se configura exactamente igual que T0CON como ya mencioné anteriormente en el capítulo Timers, con la excepción de la entrada de eventos que es en los pines RC0 y RC1. En estos pines colocaremos un oscilador de 32768 Hz que normalmente lo conocere-mos como oscilador de 32KHz.

¿Porqué usar un oscilador de baja frecuencia? Recordemos que tenemos un contador con cuenta máxima de 65535 cuentas, recordemos que la frecuencia es el número de cambios que se realizan en 1 segundo. Si tenemos una frecuencia menor a 65535, pode-mos obtener un número mágico para que se desborde en exactamente 1 segundo; nuestro caso es un oscilador de 35768 Hz entonces necesitaremos colocar ese número en el conta-dor y su desborde será de exactamente 1 segundo. Este número en hexadecimal es 0x8000.

51

Page 52: Prog Pic c18

¿Cómo conectar el oscilador? Se conecta el oscilador a los pines RC0 y RC1 junto con un capacitor de 15nF o 22nF a tierra para evitar ruido en la señal.

¿Cómo configurar el Timer 1 para realizar el segundero? Se configuran las interrup-ciones como se explicó en Configurar Interrupciones y el registro TMR1H se carga 0x80 y TMR1L se carga con 0x00, por último, se coloca el número 0x8F en el registro T1CON. La interrupción se realizará cada segundo.

Una vez realizado el código del segundero, podría el lector realizar un reloj digital ya sea con un display de 7 segmentos o con el LCD alfanumérico.

52

Page 53: Prog Pic c18

FIN DEL CURSO

Este curso básico ha terminado. Como últimas recomendaciones puedo decir que uses una fuente externa ya que el lector podrá causar fallo en los puertos USB de la computadora. Ahora solo me queda decir:

Felicidades, has terminado este curso básico de programación en C18 del PIC18F4550 con bootloader.

Ahora no me queda mas que agradecerles el leer este documento. Éxito.

Yo se que es un curso básico, pero con un poquito de imaginación puedes realizar cosas grandiosas con este microcontrolador de la familia PIC18.

Hay muchas cosas por aprender, por conocer y por programar, pero si tu no las haces, nunca serán verdaderas y solo se quedarán en la imaginación.

53